Mercurial > hg > nnchat
view 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 source
#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; }