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[] = {
	{ '<', "&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;
}