Mercurial > hg > nnchat
diff nnchat.c @ 0:728243125263
Import.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 20 Mar 2008 00:15:03 +0000 |
parents | |
children | 351e96e01f4c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nnchat.c Thu Mar 20 00:15:03 2008 +0000 @@ -0,0 +1,764 @@ +#ifdef __WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <netdb.h> +#endif + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include "th_args.h" +#include "th_string.h" +#include <string.h> +#include <errno.h> +#include <time.h> + + +#define SET_ALLOC_SIZE (128) +#define SET_SELECT_USEC (100000) + + +/* Options + */ +int optPort = 8005; +int optUserColor = 0x408060; +char *optServer = "www11.servemedata.com", + *optUserName = NULL, + *optUserName2 = NULL, + *optPassword = NULL, + *optLogFilename = NULL; + +FILE *optLogFile = NULL; + + +/* Arguments + */ +optarg_t optList[] = { + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'p', "port", "Connect to port", OPT_ARGREQ }, + { 3, 's', "server", "Server to connect to", OPT_ARGREQ }, + { 4, 'C', "color", "Initial color in RGB hex 000000", OPT_ARGREQ }, + { 5, 'l', "logfile", "Log filename", OPT_ARGREQ }, +/* + { 6, 'p', "plaintext", "Use plaintext logging", OPT_NONE }, +*/ +}; + +const int optListN = (sizeof(optList) / sizeof(optarg_t)); + + +void argShowHelp() +{ + th_args_help(stdout, optList, optListN, th_prog_name, + "[options] <username> <password>"); +} + + +#ifdef __WIN32 +const char *hstrerror(int err) +{ + return "???"; +} +#endif + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosityLevel++; + break; + + case 2: + optPort = atoi(optArg); + break; + + case 3: + optServer = optArg; + break; + + case 4: + if (sscanf(optArg, "%06x", &optUserColor) != 1) { + THERR("Invalid color argument '%s', should be a RGB hex triplet '000000'.\n", + optArg); + return FALSE; + } + THMSG(1, "Using color #%06x\n", optUserColor); + break; + + case 5: + optLogFilename = optArg; + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (!optUserName) + optUserName = currArg; + else if (!optPassword) + optPassword = currArg; + else { + THERR("Username '%s' already specified on commandline!\n", optUserName); + return FALSE; + } + + return TRUE; +} + + +BOOL sendToSocket(int sock, char *buf, const size_t bufLen) +{ + size_t bufLeft = bufLen; + char *bufPtr = buf; + + while (bufLeft > 0) { + ssize_t bufSent; + bufSent = send(sock, bufPtr, bufLeft, 0); + if (bufSent < 0) return FALSE; + bufLeft -= bufSent; + bufPtr += bufSent; + } + return TRUE; +} + + +void printMsg(char *fmt, ...) +{ + char tmpStr[64] = ""; + va_list ap; + time_t timeStamp; + struct tm *tmpTime;; + + timeStamp = time(NULL); + if ((tmpTime = localtime(&timeStamp)) != NULL) { + strftime(tmpStr, sizeof(tmpStr), "[%H:%M:%S] ", tmpTime); + } + + if (optLogFile) { + fputs(tmpStr, optLogFile); + va_start(ap, fmt); + vfprintf(optLogFile, fmt, ap); + va_end(ap); + fflush(optLogFile); + } + + fputs(tmpStr, stdout); + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); +} + + +BOOL bufRealloc(char **buf, size_t *size, size_t add) +{ + return ((*buf = th_realloc(*buf, *size + add)) != NULL); +} + +#define pushChar(x) bufPushChar(&result, &resSize, &resPos, x) +BOOL bufPushChar(char **buf, size_t *size, size_t *pos, char ch) +{ + if (*pos >= *size && !bufRealloc(buf, size, SET_ALLOC_SIZE)) + return FALSE; + + (*buf)[*pos] = ch; + (*pos)++; + return TRUE; +} + +#define pushStr(x) bufPushStr(&result, &resSize, &resPos, x) +BOOL bufPushStr(char **buf, size_t *size, size_t *pos, char *str) +{ + size_t tmpLen; + + if (!str) return FALSE; + tmpLen = strlen(str); + + if ((*pos + tmpLen) >= *size && !bufRealloc(buf, size, tmpLen + SET_ALLOC_SIZE)) + return FALSE; + + strcpy(*buf + *pos, str); + (*pos) += tmpLen; + return TRUE; +} + + +char *encodeStr1(char *str) +{ + char *result, *s = str; + size_t resSize, resPos = 0; + + if (!str) return NULL; + + resSize = strlen(str) + SET_ALLOC_SIZE; + if ((result = th_malloc(resSize)) == NULL) + return NULL; + + while (*s) { + switch (*s) { + case 32: + pushChar('+'); + break; + + default: + if (th_isalnum(*s)) + pushChar(*s); + else { + char tmpStr[4]; + sprintf(tmpStr, "%2X", (unsigned char) *s); + pushChar('%'); + pushStr(tmpStr); + } + break; + } + s++; + } + pushChar(0); + + return result; +} + +int getxdigit(int c, int shift) +{ + int i; + + if (c >= 'A' && c <= 'F') + i = c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + i = c - 'a' + 10; + else if (c >= '0' && c <= '9') + i = c - '0'; + else + return -1; + + return i << shift; +} + +char *decodeStr1(char *str) +{ + char *result, *s = str; + size_t resSize, resPos = 0; + int c; + + if (!str) return NULL; + + resSize = strlen(str) + SET_ALLOC_SIZE; + if ((result = th_malloc(resSize)) == NULL) + return NULL; + + while (*s) { + switch (*s) { + case '+': + pushChar(' '); + s++; + break; + + case '%': + s++; + if (*s == '%') + pushChar('%'); + else if ((c = getxdigit(*s, 4)) >= 0) { + int i = getxdigit(*(++s), 0); + if (i >= 0) { + pushChar(c | i); + } else { + pushChar('§'); + pushChar(*s); + } + } else { + pushChar('§'); + pushChar(*s); + } + s++; + break; + + default: + pushChar(*s); + s++; + } + } + pushChar(0); + + return result; +} + + +char *stripTags(char *str) +{ + char *result, *s = str; + size_t resSize, resPos = 0; + + if (!str) return NULL; + + resSize = strlen(str) + SET_ALLOC_SIZE; + if ((result = th_malloc(resSize)) == NULL) + return NULL; + + while (*s) { + if (*s == '<') { + while (*s && *s != '>') s++; + if (*s == '>') s++; + } else + pushChar(*s++); + } + pushChar(0); + + return result; +} + + +typedef struct { + char c; + char *ent; +} html_entity_t; + + +html_entity_t HTMLEntities[] = { + { '<', "<" }, + { '>', ">" }, + /* + { '&', "&" }, + { 'ä', "ä" }, + { 'ö', "ö" }, + { 'Ä', "Ä" }, + { 'Ö', "Ö" }, + */ +}; + +const int numHTMLEntities = (sizeof(HTMLEntities) / sizeof(html_entity_t)); + + +char *encodeStr2(char *str) +{ + char *result, *s = str; + size_t resSize, resPos = 0; + + if (!str) return NULL; + + resSize = strlen(str) + SET_ALLOC_SIZE; + if ((result = th_malloc(resSize)) == NULL) + return NULL; + + while (*s) { + int i; + BOOL found = FALSE; + for (i = 0; i < numHTMLEntities; i++) + if (HTMLEntities[i].c == *s) { + pushStr(HTMLEntities[i].ent); + found = TRUE; + break; + } + if (!found) pushChar(*s); + + s++; + } + pushChar(0); + + return result; +} + + +char *decodeStr2(char *str) +{ + char *result, *s = str; + size_t resSize, resPos = 0; + + if (!str) return NULL; + + resSize = strlen(str); + if ((result = th_malloc(resSize)) == NULL) + return NULL; + + while (*s) { + if (*s == '&') { + int i; + BOOL found = FALSE; + for (i = 0; i < numHTMLEntities; i++) { + html_entity_t *ent = &HTMLEntities[i]; + int len = strlen(ent->ent); + if (!strncmp(s, ent->ent, len)) { + pushChar(ent->c); + s += len; + found = TRUE; + break; + } + } + if (!found) pushChar(*s++); + } else + pushChar(*s++); + } + pushChar(0); + + return result; +} + + +BOOL sendUserMsg(int sock, char *user, char *fmt, ...) +{ + char tmpBuf[4096], tmpBuf2[4096+256]; + int n; + va_list ap; + + va_start(ap, fmt); + n = vsnprintf(tmpBuf, sizeof(tmpBuf), fmt, ap); + va_end(ap); + + if (n < 0) return FALSE; + + snprintf(tmpBuf2, sizeof(tmpBuf2), + "<USER>%s</USER><MESSAGE>%s</MESSAGE>", + user, tmpBuf); + + return sendToSocket(sock, tmpBuf2, strlen(tmpBuf2) + 1); +} + + +int handleUser(int sock, char *str) +{ + const char *msg = "</USER><MESSAGE>"; + char *p = str, *q, *s; + + (void) sock; + + s = strstr(str, msg); + if (!s) return 1; + *s = 0; + s += strlen(msg); + + q = strstr(s, "</MESSAGE>"); + if (!q) return 3; + *q = 0; + + s = decodeStr1(s); + if (!s) return -1; + + p = decodeStr1(p); + if (!p) { + th_free(s); + return -2; + } + + /* FIXME: decodeStr2() */ + + if (*s == '/') { + char *t = stripTags(s+1); + printMsg("* %s\n", t); + th_free(t); + } else { + char *t = stripTags(s); + printMsg("<%s> %s\n", p, t); + th_free(t); + } + + th_free(s); + th_free(p); + return 0; +} + + +int handleLogin(int sock, char *str) +{ + if (!strncmp(str, "FAILURE", 7)) { + printMsg("Login failure.\n"); + return -2; + } else if (!strncmp(str, "SUCCESS", 7)) { + printMsg("Login success.\n"); + sendUserMsg(sock, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); + return 0; + } else + return 1; +} + + +int handleAddUser(int sock, char *str) +{ + char *s = strstr(str, "</ADD_USER>"); + + (void) sock; + + if (!s) return 1; + *s = 0; + printMsg("! %s ADDED.\n", str); + return 0; +} + + +int handleDeleteUser(int sock, char *str) +{ + char *s = strstr(str, "</DELETE_USER>"); + + (void) sock; + + if (!s) return 1; + *s = 0; + printMsg("! %s DELETED.\n", str); + return 0; +} + + +int handleFoo(int sock, char *str) +{ + (void) sock; (void) str; + + return 0; +} + + +typedef struct { + char *cmd; + int (*handler)(int, char *); +} protocmd_t; + + +protocmd_t protoCmds[] = { + { "<USER>", handleUser }, + { "<LOGIN_", handleLogin }, + { "<DELETE_USER>", handleDeleteUser }, + { "<ADD_USER>", handleAddUser }, + { "<NUMCLIENTS>", handleFoo }, +}; + +const int nprotoCmds = (sizeof(protoCmds) / sizeof(protocmd_t)); + + +int handleProtocol(int sock, char *buf, size_t bufLen) +{ + int i; + + for (i = 0; i < nprotoCmds; i++) { + size_t cmdLen = strlen(protoCmds[i].cmd); + if (cmdLen < bufLen && !strncmp(buf, protoCmds[i].cmd, cmdLen)) { + return protoCmds[i].handler(sock, buf + cmdLen); + } + } + + return 1; +} + + +int handleInput(int sock, char *buf, size_t bufLen) +{ + char *tmpStr, *tmpStr2; + BOOL result; + + /* Trim right */ + buf[--bufLen] = 0; + while (bufLen > 0 && (buf[bufLen] == '\n' || buf[bufLen] == '\r' || th_isspace(buf[bufLen]))) + buf[bufLen--] = 0; + + //fprintf(stderr, "'%s'\n", buf); fflush(stderr); + + /* Check command */ + if (*buf == 0) { + return 1; + } else if (*buf == '@') { + /* Send 1-pass encoded 'RAW' */ + buf++; + printf("RAW>%s\n", buf); + fflush(stdout); + + tmpStr = encodeStr1(buf); + if (!tmpStr) return -2; + + result = sendUserMsg(sock, optUserName2, "%s", tmpStr); + th_free(tmpStr); + if (result) + return 0; + else + return -1; + } else { + /* Send double-encoded */ + printf("ENC>%s\n", buf); + fflush(stdout); + + tmpStr = encodeStr2(buf); + if (!tmpStr) return -2; + tmpStr2 = encodeStr1(tmpStr); + if (!tmpStr2) { + th_free(tmpStr); + return -3; + } + + result = sendUserMsg(sock, optUserName2, "%s", tmpStr2); + th_free(tmpStr); + th_free(tmpStr2); + if (result) + return 0; + else + return -1; + } +} + + +int main(int argc, char *argv[]) +{ + int tmpSocket; + struct hostent *tmpHost; + struct sockaddr_in tmpAddr; + BOOL exitProg = FALSE; + + /* Initialize */ + th_init("NNChat", "Newbie Nudes chat client", "0.2", NULL, NULL); + th_verbosityLevel = 0; + + /* Parse arguments */ + th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, FALSE); + + /* Check the mode and arguments */ + if (optUserName == NULL || optPassword == NULL) { + THERR("User/pass not specified, get some --help\n"); + return -1; + } + + /* Open logfile */ + if (optLogFilename) { + THMSG(1, "Opening logfile '%s'\n", optLogFilename); + + if ((optLogFile = fopen(optLogFilename, "a")) == NULL) { + THERR("Could not open logfile for appending!\n"); + return -9; + } + } + + /* Okay ... */ + THMSG(1, "Trying to resolve host '%s' ...\n", optServer); + tmpHost = gethostbyname(optServer); + if (tmpHost == NULL) { + THERR("Could not resolve hostname: %s.\n", + hstrerror(h_errno)); + return -3; + } + THMSG(2, "True hostname: %s\n", tmpHost->h_name); + + tmpAddr.sin_family = AF_INET; + tmpAddr.sin_port = htons(optPort); + tmpAddr.sin_addr = *((struct in_addr *) tmpHost->h_addr); + + THMSG(1, "Connecting to %s:%d ...\n", + inet_ntoa(tmpAddr.sin_addr), optPort); + + if ((tmpSocket = socket(PF_INET, SOCK_STREAM, 0)) == -1) { + THERR("Could not open socket: %s\n", strerror(errno)); + return -2; + } + + THMSG(2, "Using socket %d.\n", tmpSocket); + + if (connect(tmpSocket, (struct sockaddr *) &tmpAddr, sizeof(tmpAddr)) == -1) { + THERR("Could not connect: %s\n", strerror(errno)); + return -5; + } + + THMSG(1, "Connected, logging in as '%s'.\n", optUserName); + optUserName2 = encodeStr1(optUserName); + + sendUserMsg(tmpSocket, optUserName2, "%%2Flogin%%20%%2Dsite%%20NN%%20%%2Dpassword%%20%s", optPassword); + + struct timeval tv; + fd_set sockfds; + fd_set inputfds; + + + FD_ZERO(&inputfds); + FD_SET(0, &inputfds); + + FD_ZERO(&sockfds); + FD_SET(tmpSocket, &sockfds); + + while (!exitProg) { + ssize_t gotBuf; + int result; + char tmpBuf[4096]; + fd_set tmpfds; + + /* Check for incoming data from the server */ + tv.tv_sec = 0; + tv.tv_usec = SET_SELECT_USEC; + tmpfds = sockfds; + if ((result = select(tmpSocket+1, &tmpfds, NULL, NULL, &tv)) == -1) { + THERR("Error occured in select(sockfds): %s\n", strerror(errno)); + exitProg = TRUE; + } else if (FD_ISSET(tmpSocket, &tmpfds)) { + gotBuf = recv(tmpSocket, tmpBuf, sizeof(tmpBuf), 0); + + if (gotBuf < 0) { + THERR("Error in recv: %s\n", strerror(errno)); + exitProg = TRUE; + } else if (gotBuf == 0) { + THERR("Server closed connection.\n"); + exitProg = TRUE; + } else { + /* Handle protocol data */ + tmpBuf[gotBuf] = 0; + result = handleProtocol(tmpSocket, tmpBuf, gotBuf); + + if (result > 0) { + /* Couldn't handle the message for some reason */ + THERR("Could not handle: %s\n", tmpBuf); + } else if (result < 0) { + /* Fatal error, quit */ + THERR("Fatal error with message: %s\n", tmpBuf); + exitProg = TRUE; + } + } + } + + /* Check for user input */ + tv.tv_sec = 0; + tv.tv_usec = SET_SELECT_USEC; + tmpfds = inputfds; + if ((result = select(1, &tmpfds, NULL, NULL, &tv)) == -1) { + THERR("Error occured in select(inputfds): %s\n", strerror(errno)); + exitProg = TRUE; + } else if (FD_ISSET(0, &tmpfds)) { + gotBuf = read(0, tmpBuf, sizeof(tmpBuf)); + + if (gotBuf < 0) { + THERR("Error in reading stdio.\n"); + exitProg = TRUE; + } else { + /* Call the user input handler */ + result = handleInput(tmpSocket, tmpBuf, gotBuf); + if (result < 0) { + THERR("Fatal error handling user input: %s\n", + tmpBuf); + exitProg = TRUE; + } + } + } + + fflush(stdout); + fflush(stderr); + } + + /* .. */ + th_free(optUserName2); + close(tmpSocket); + + if (optLogFile) { + THMSG(1, "Closing logfile.\n"); + fclose(optLogFile); + } + + THMSG(1, "Connection terminated.\n"); + + return 0; +}