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[] = {
+	{ '<', "&lt;" },
+	{ '>', "&gt;" },
+	/*
+	{ '&', "&amp;" },
+	{ 'ä', "&auml;" },
+	{ 'ö', "&ouml;" },
+	{ 'Ä', "&Auml;" },
+	{ 'Ö', "&Ouml;" },
+	*/
+};
+
+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;
+}