From 4a15604d108c8eb20b4d5d2238177b28622c255f Mon Sep 17 00:00:00 2001 From: Sean Middleditch Date: Mon, 16 Mar 2009 17:10:58 -0400 Subject: [PATCH] added libtelnet_printf2 which does CRLF automagic stuff --- libtelnet.c | 44 ++++++- libtelnet.h | 5 +- telnet-chatd.c | 324 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+), 2 deletions(-) create mode 100644 telnet-chatd.c diff --git a/libtelnet.c b/libtelnet.c index 97055e6..e905287 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -858,7 +858,7 @@ void libtelnet_begin_compress2(libtelnet_t *telnet) { } /* send formatted data through libtelnet_send_data */ -int libtelnet_send_printf(libtelnet_t *telnet, const char *fmt, ...) { +int libtelnet_printf(libtelnet_t *telnet, const char *fmt, ...) { char buffer[4096]; va_list va; int rs; @@ -873,3 +873,45 @@ int libtelnet_send_printf(libtelnet_t *telnet, const char *fmt, ...) { return rs; } + +/* send formatted data with \r and \n translation in addition to IAC IAC */ +int libtelnet_printf2(libtelnet_t *telnet, const char *fmt, ...) { + static const unsigned char CRLF[] = { '\r', '\n' }; + static const unsigned char CRNUL[] = { '\r', '\0' }; + char buffer[4096]; + va_list va; + int rs, i, l; + + /* format */ + va_start(va, fmt); + rs = vsnprintf(buffer, sizeof(buffer), fmt, va); + va_end(va); + + /* send */ + for (l = i = 0; i != rs; ++i) { + /* special characters */ + if (buffer[i] == LIBTELNET_IAC || buffer[i] == '\r' || + buffer[i] == '\n') { + /* dump prior portion of text */ + if (i != l) + _send(telnet, (unsigned char *)buffer + l, i - l); + l = i + 1; + + /* IAC -> IAC IAC */ + if (buffer[i] == LIBTELNET_IAC) + libtelnet_send_command(telnet, LIBTELNET_IAC); + /* automatic translation of \r -> CRNUL */ + else if (buffer[i] == '\r') + _send(telnet, CRNUL, 2); + /* automatic translation of \n -> CRLF */ + else if (buffer[i] == '\n') + _send(telnet, CRLF, 2); + } + } + + /* send whatever portion of buffer is left */ + if (i != l) + _send(telnet, (unsigned char *)buffer + l, i - l); + + return rs; +} diff --git a/libtelnet.h b/libtelnet.h index f03b92f..e13e3ee 100644 --- a/libtelnet.h +++ b/libtelnet.h @@ -235,6 +235,9 @@ extern void libtelnet_begin_compress2(libtelnet_t *telnet); # define LIBTELNET_GNU_PRINTF(f,a) #endif -extern int libtelnet_send_printf(libtelnet_t *telnet, const char *fmt, ...); +extern int libtelnet_printf(libtelnet_t *telnet, const char *fmt, ...); + +/* send formatted data with \r and \n translated */ +extern int libtelnet_printf2(libtelnet_t *telnet, const char *fmt, ...); #endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/telnet-chatd.c b/telnet-chatd.c new file mode 100644 index 0000000..5d9d204 --- /dev/null +++ b/telnet-chatd.c @@ -0,0 +1,324 @@ +/* + * Sean Middleditch + * sean@sourcemud.org + * + * The author or authors of this code dedicate any and all copyright interest + * in this code to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and successors. We + * intend this dedication to be an overt act of relinquishment in perpetuity of + * all present and future rights to this code under copyright law. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ZLIB +#include "zlib.h" +#endif + +#include "libtelnet.h" + +#define MAX_USERS 64 +#define LINEBUFFER_SIZE 256 + +struct user_t { + char *name; + int sock; + libtelnet_t telnet; + char linebuf[256]; + int linepos; +}; + +static struct user_t users[MAX_USERS]; + +static void linebuffer_push(char *buffer, size_t size, int *linepos, + char ch, void (*cb)(const char *line, int overflow, void *ud), + void *ud) { + + /* CRLF -- line terminator */ + if (ch == '\n' && *linepos > 0 && buffer[*linepos - 1] == '\r') { + /* NUL terminate (replaces \r in buffer), notify app, clear */ + buffer[*linepos - 1] = 0; + cb(buffer, 0, ud); + *linepos = 0; + + /* CRNUL -- just a CR */ + } else if (ch == 0 && *linepos > 0 && buffer[*linepos - 1] == '\r') { + /* do nothing, the CR is already in the buffer */ + + /* anything else (including technically invalid CR followed by + * anything besides LF or NUL -- just buffer if we have room + * \r + */ + } else if (*linepos != size) { + buffer[(*linepos)++] = ch; + + /* buffer overflow */ + } else { + /* terminate (NOTE: eats a byte), notify app, clear buffer */ + buffer[size - 1] = 0; + cb(buffer, size - 1, ud); + *linepos = 0; + } +} + +static void _message(const char *from, const char *msg) { + int i; + for (i = 0; i != MAX_USERS; ++i) { + if (users[i].sock != -1) { + libtelnet_printf2(&users[i].telnet, "%s: %s\n", from, msg); + } + } +} + +static void _send(int sock, const unsigned char *buffer, unsigned int size) { + int rs; + + /* ignore on invalid socket */ + if (sock == -1) + return; + + /* send data */ + while (size > 0) { + if ((rs = send(sock, buffer, size, 0)) == -1) { + if (errno != EINTR && errno != ECONNRESET) { + fprintf(stderr, "send() failed: %s\n", strerror(errno)); + exit(1); + } else { + return; + } + } else if (rs == 0) { + fprintf(stderr, "send() unexpectedly returned 0\n"); + exit(1); + } + + /* update pointer and size to see if we've got more to send */ + buffer += rs; + size -= rs; + } +} + +/* process input line */ +static void _online(const char *line, int overflow, void *ud) { + struct user_t *user = (struct user_t*)ud; + int i; + + /* if the user has no name, this is his "login" */ + if (user->name == 0) { + /* must not be empty, must be at least 32 chars */ + if (strlen(line) == 0 || strlen(line) > 32) { + libtelnet_printf2(&user->telnet, "Invalid name.\nEnter name: "); + return; + } + + /* must not already be in use */ + for (i = 0; i != MAX_USERS; ++i) { + if (users[i].name != 0 && strcmp(users[i].name, line) == 0) { + libtelnet_printf2(&user->telnet, "Name in use.\nEnter name: "); + return; + } + } + + /* keep name */ + user->name = strdup(line); + libtelnet_printf2(&user->telnet, "Welcome, %s!\n", line); + return; + } + + /* if line is "quit" then, well, quit */ + if (strcmp(line, "quit") == 0) { + close(user->sock); + user->sock = -1; + _message(user->name, "** HAS QUIT **"); + free(user->name); + user->name = 0; + return; + } + + /* just a message -- send to all users */ + _message(user->name, line); +} + +static void _input(struct user_t *user, const unsigned char *buffer, + unsigned int size) { + unsigned int i; + for (i = 0; i != size; ++i) + linebuffer_push(user->linebuf, sizeof(user->linebuf), &user->linepos, + (char)buffer[i], _online, user); +} + +static void _event_handler(libtelnet_t *telnet, libtelnet_event_t *ev, + void *user_data) { + struct user_t *user = (struct user_t*)user_data; + + switch (ev->type) { + /* data received */ + case LIBTELNET_EV_DATA: + _input(user, ev->buffer, ev->size); + break; + /* data must be sent */ + case LIBTELNET_EV_SEND: + _send(user->sock, ev->buffer, ev->size); + break; + /* error */ + case LIBTELNET_EV_ERROR: + close(user->sock); + user->sock = -1; + if (user->name != 0) { + _message(user->name, "** HAS HAD AN ERROR **"); + free(user->name); + user->name = 0; + } + libtelnet_free(&user->telnet); + break; + default: + /* ignore */ + break; + } +} + +int main(int argc, char **argv) { + unsigned char buffer[512]; + short listen_port; + int listen_sock; + int rs; + int i; + struct sockaddr_in addr; + socklen_t addrlen; + struct pollfd pfd[MAX_USERS + 1]; + + /* check usage */ + if (argc != 2) { + fprintf(stderr, "Usage:\n ./telnet-chatd \n"); + return 1; + } + + /* initialize data structures */ + memset(&pfd, 0, sizeof(pfd)); + memset(users, 0, sizeof(users)); + for (i = 0; i != MAX_USERS; ++i) + users[i].sock = -1; + + /* parse listening port */ + listen_port = strtol(argv[1], 0, 10); + + /* create listening socket */ + if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "socket() failed: %s\n", strerror(errno)); + return 1; + } + + /* reuse address option */ + rs = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &rs, sizeof(rs)); + + /* bind to listening addr/port */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(listen_port); + if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + fprintf(stderr, "bind() failed: %s\n", strerror(errno)); + return 1; + } + + /* listen for clients */ + if (listen(listen_sock, 5) == -1) { + fprintf(stderr, "listen() failed: %s\n", strerror(errno)); + return 1; + } + + printf("LISTENING ON PORT %d\n", listen_port); + + /* initialize listening descriptors */ + pfd[MAX_USERS].fd = listen_sock; + pfd[MAX_USERS].events = POLLIN; + + /* loop for ever */ + for (;;) { + /* prepare for poll */ + for (i = 0; i != MAX_USERS; ++i) { + if (users[i].sock != -1) { + pfd[i].fd = users[i].sock; + pfd[i].events = POLLIN; + } else { + pfd[i].fd = -1; + pfd[i].events = 0; + } + } + + /* poll */ + rs = poll(pfd, MAX_USERS + 1, -1); + if (rs == -1 && errno != EINTR) { + fprintf(stderr, "poll() failed: %s\n", strerror(errno)); + return 1; + } + + /* new connection */ + if (pfd[MAX_USERS].revents & POLLIN) { + /* acept the sock */ + addrlen = sizeof(addr); + if ((rs = accept(listen_sock, (struct sockaddr *)&addr, + &addrlen)) == -1) { + fprintf(stderr, "accept() failed: %s\n", strerror(errno)); + return 1; + } + + printf("Connection received.\n"); + + /* find a free user */ + for (i = 0; i != MAX_USERS; ++i) + if (users[i].sock == -1) + break; + if (i == MAX_USERS) { + printf(" rejected (too many users)\n"); + _send(rs, (unsigned char *)"Too many users.\r\n", 14); + close(rs); + } + + /* init, welcome */ + users[i].sock = rs; + libtelnet_init(&users[i].telnet, _event_handler, 0, &users[i]); + libtelnet_printf2(&users[i].telnet, "Enter name: "); + } + + /* read from client */ + for (i = 0; i != MAX_USERS; ++i) { + /* skip users that aren't actually connected */ + if (users[i].sock == -1) + continue; + + if (pfd[i].revents & POLLIN) { + if ((rs = recv(users[i].sock, buffer, sizeof(buffer), 0)) > 0) { + libtelnet_push(&users[i].telnet, buffer, rs); + } else if (rs == 0) { + printf("Connection closed.\n"); + close(users[i].sock); + if (users[i].name != 0) { + _message(users[i].name, "** HAS DISCONNECTED **"); + free(users[i].name); + users[i].name = 0; + } + libtelnet_free(&users[i].telnet); + users[i].sock = -1; + break; + } else if (errno != EINTR) { + fprintf(stderr, "recv(client) failed: %s\n", + strerror(errno)); + exit(1); + } + } + } + } + + /* not that we can reach this, but GCC will cry if it's not here */ + return 0; +}