Viewing file: c/nkeep/nkeep.c | Back to directory listing
Author: Loren Segal | Last modified: February 21 2006 12:00 am | Download

/*
	Nick holder
 
		Jinx
*/
 
#ifndef WIN32	/* Not Win32 */
	#include <stdio.h>
	#include <pcreposix.h>
	#include <unistd.h>
	#include <string.h>
	#include <sys/types.h>
	#include <sys/socket.h>
	#include <netdb.h>
	#include <pthread.h>
	#define SOCKET_ERROR -1
 
	#define NEWTHREAD(a,b)	pthread_create(&threadid, 0, (void *)&a, (void *)b)
#else		/* Win32 specific defines */
	#include <windows.h>
	#include <winsock.h>
	#include <stdio.h>
	#include <pcreposix.h> 
	#include <time.h>
	#include <io.h>
	#include <fcntl.h>
 
	#undef  fdopen			/* Undefine first */
	#define fdopen(a,b)		_fdopen(_open_osfhandle((SOCKET)a, _O_RDONLY|_O_BINARY), b)
	#define sleep(a)		Sleep(1000*a)
	#define pthread_t		char /* Let's just make it a char */
	#define NEWTHREAD(a,b)	CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&a, (LPVOID)b, 0, 0)
#endif		/* End compatibility defines */
 
#define WHOIS_CMD "WHOIS %s\n%c"
 
#define MOTD_END 			376
#define NICKNAME_IN_USE 	433
#define NO_SUCH_NICKNAME	401
 
#define DEFAULT_IDENT			"nkeep"
#define DEFAULT_FULLNAME		"Nick Keeper by Jinx"
#define DEFAULT_PORT			6667
#define DEFAULT_WHOISDELAY		60
#define DEFAULT_CONNECTDELAY	5
#define DEFAULT_LOGGING			0
 
/* User Settings */
char MyNickname[100];
char PrimaryNickname[100];
char AlternateNickname[100];
char ServerName[100];
char ActiveChannel[100];
char MainChannel[100];
char Ident[20];
char FullName[100];
int Port;
int WhoisDelay;
int ConnectDelay;
int Logging;
 
int s;
int connected;
FILE *logfile = NULL;
 
enum IRCMatchNames 
{
	MATCH_ALL,
	MATCH_ADDR_ALL,
	MATCH_ADDR_NICK,
	MATCH_ADDR_END,
	MATCH_ADDR_IDENT,
	MATCH_ADDR_DOMAIN,
	MATCH_NUMERIC,
	MATCH_NULL,
	MATCH_TARGET,
	MATCH_DATA,
	MATCH_EXTRA,
	NUM_MATCHES
};
 
regex_t *linematch;
 
char *maketimestamp()
{
	char *timestamp = malloc(8);
	time_t timenow = time(NULL);
	struct tm *t = malloc(sizeof(struct tm));
	t = localtime(&timenow);
	sprintf(timestamp, "[%02d:%02d]", t->tm_hour, t->tm_min);
	return timestamp;
}
 
#ifdef DEBUG /* DEBUGGING ONLY */ 
char *GetMatchName(int i)
{
	switch(i)
	{
		case 0:		return "MATCH_ALL";
		case 1:		return "MATCH_ADDR_ALL";
		case 2:		return "MATCH_ADDR_NICK";
		case 3:		return "MATCH_ADDR_END";
		case 4:		return "MATCH_ADDR_IDENT";
		case 5:		return "MATCH_ADDR_DOMAIN";
		case 6:		return "MATCH_NUMERIC";
		case 7:		return "MATCH_NULL";
		case 8:		return "MATCH_TARGET";
		case 9:		return "MATCH_DATA";
		case 10:	return "MATCH_EXTRA";
	}
	return "";
}
#endif /* DEBUG END */
 
int copymatches(char *buffer, regmatch_t *copy_matches, int copysize, char **copy_match_strings)
{
	int i, length;
	for (i = 0; i < copysize; i++)
	{
		if (copy_matches[i].rm_eo >= 0)
		{
			length = copy_matches[i].rm_eo - copy_matches[i].rm_so;
			copy_match_strings[i] = (char *)malloc(length + 1);
			strncpy(copy_match_strings[i], buffer+copy_matches[i].rm_so, length);
			*(copy_match_strings[i]+length) = '\0';
		}
#ifdef DEBUG /* DEBUGGING ONLY */ 
		else
		{
			copy_match_strings[i] = (char *)malloc(1);
			*copy_match_strings[i] = '\0';
		}
		printf("match[%s]\t\t-\t\"%s\"\n", GetMatchName(i), copy_match_strings[i]);
#endif /* DEBUG END */
	}
	return 0;
}
 
/*********************** 
TimerCheckPrimaryNick:
Performs a WHOIS command
on PrimaryNick every WhoisDelay seconds
***********************/
int TimerCheckPrimaryNick()
{
	char *cmd = malloc(strlen(WHOIS_CMD) + strlen(PrimaryNickname) + 1);
	while(1)
	{
		sleep(WhoisDelay);	/* compatibility macro */
		sprintf(cmd, WHOIS_CMD, PrimaryNickname, NULL);
		if (connected) send(s, cmd, strlen(cmd), 0);
	}
}
 
 
int readline(char *inbuf, FILE *sockfd)
{
	int len;
#ifndef WIN32 /* Not Win32 */
	if (fgets(inbuf, 4096, sockfd) == NULL)
	{
		return -1;
	}
#else
	int r, bytes = 0;
	memset(inbuf, 0, 4096);
	do
	{
		if ((r = recv(s, (inbuf+bytes), 1, 0)) == SOCKET_ERROR)
		{
			return -1;
		}
		bytes += r;
	} while (*(inbuf+bytes-1) != '\n');
#endif
	len = strlen(inbuf) - 1;
	*(inbuf+len) = '\0';
	if (*(inbuf+len-1) == '\r') *(inbuf+len-1) = '\0';
	return 0;
}
 
/*********************** 
SocketHandler:
Main socket thread. Handles 
all socket data from the server.
***********************/
int SocketHandler(int delay)
{
	struct hostent *h;
	struct sockaddr_in addr;
	char *timestamp, *event;
	int numeric = 0;
	pthread_t threadid = 0;
 
	char *match_strings[NUM_MATCHES];
	regmatch_t *matches = malloc(NUM_MATCHES * sizeof(regmatch_t) + 1);
 
	char *inbuf = malloc(4096);
	char *outbuf = malloc(4096);
 
	FILE *sockfd;
 
	// Setup the socket
	if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR) 
	{
	        perror("Could not create socket");
        	return 0;
	}
	h = gethostbyname(ServerName);
	memcpy(&addr.sin_addr.s_addr, h->h_addr_list[0], sizeof(h->h_addr_list));
	addr.sin_port				= htons((unsigned short)Port);
    	addr.sin_family			= AF_INET;
 
	// sleep for delay seconds
	if (delay)
	{
		timestamp = maketimestamp();
		printf("%s * Sleeping for %d seconds before next connection...\n", timestamp, delay);
		sleep(delay);
	}
	timestamp = maketimestamp();
	printf("%s * Connecting to %s...\n", timestamp, ServerName);
 
	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR)
		return 0;
 
	sprintf(outbuf, "NICK %s\nUSER %s %s %s :%s\n", PrimaryNickname, Ident, Ident, ServerName, FullName);
	send(s, outbuf, strlen(outbuf), 0);
	
	sockfd = fdopen(s, "r");	/* compatibility macro */
	while(1)
	{
		// Get time
		timestamp = maketimestamp();
 
		// Read Line by Line
		if (readline(inbuf, sockfd) == SOCKET_ERROR)
		{
			printf("%s ERROR! * Disconnected, retrying connection...\n", timestamp);
			connected = 0;
			NEWTHREAD(SocketHandler, ConnectDelay);
			return 0;			
		}
 
		*outbuf = '\0';
 
//#ifdef DEBUG	/* DEBUGGING ONLY */
		// Output all server data
		printf("%s [DEBUG] %s\n", timestamp, inbuf);
//#endif
 
		// Get data into variables
		if (regexec(linematch, inbuf, NUM_MATCHES, matches, 0) == REG_NOMATCH)
		{
#ifdef DEBUG	/* DEBUGGING ONLY */
			printf("%s [DEBUG] No match\n", timestamp);
#endif
		}
		else
		{
			copymatches(inbuf, matches, NUM_MATCHES, match_strings);
 
			if ((numeric = atoi(match_strings[MATCH_NUMERIC])) > 0)
			{
				// NUMERIC Event handling
				switch(numeric)
				{
					case NO_SUCH_NICKNAME:
						if (strncmp(match_strings[MATCH_DATA], PrimaryNickname, strlen(PrimaryNickname)) == 0)
						{
							printf("%s [COMMAND] * %s is not taken, changing nickname...\n", timestamp, PrimaryNickname);
							sprintf(outbuf, "NICK %s\n%c", PrimaryNickname, NULL);
						}
						break;
					case NICKNAME_IN_USE:
						if (!connected) 
						{
							if (strcmp(MyNickname, PrimaryNickname) == 0)
							{
								sprintf(outbuf, "NICK %s\n%c", AlternateNickname, NULL);
							}
							else
							{
								sprintf(outbuf, "NICK %s\n%c", PrimaryNickname, NULL);
							}
						}
						break;
					case MOTD_END:
						if (connected) break;
						connected = 1;
						strcpy(ServerName, match_strings[MATCH_ADDR_NICK]);
						printf("%s * Connected to %s:%d\n", timestamp, ServerName, Port);
						strcpy(MyNickname, match_strings[MATCH_TARGET]);
						if (*MainChannel != '\0')
						{
							printf("%s [COMMAND] * Joining (MAIN_CHANNEL) %s...\n", timestamp, MainChannel);
							sprintf(outbuf, "JOIN %s\n%c", MainChannel, '\0');
						}
						break;
					default:
						break;
				}
			}
			else
			{
				event = match_strings[MATCH_NUMERIC];
				// TEXT Event handling
				if (*event != '\0')
				{
					if (strcmp(event, "NICK") == 0)
					{
						printf("%s * %s is now known as %s\n", timestamp, match_strings[MATCH_ADDR_NICK], match_strings[MATCH_DATA]);
						if (strcmp(match_strings[MATCH_ADDR_NICK], MyNickname) == 0)
						{
							strcpy(MyNickname, match_strings[MATCH_DATA]);
						}
					}
					else if (strcmp(event, "QUIT") == 0)
					{
						printf("%s * Quits: %s (%s) (%s)\n", timestamp, match_strings[MATCH_ADDR_NICK], 
									match_strings[MATCH_ADDR_ALL], match_strings[MATCH_DATA]);
						if (strcmp(match_strings[MATCH_ADDR_NICK], PrimaryNickname) == 0)
						{
							printf("%s [COMMAND] * Primary nickname %s has left the building, changing nickname...\n", timestamp, PrimaryNickname);
							sprintf(outbuf, "NICK %s\n%c", PrimaryNickname, NULL);
						}
					}
					else if (strcmp(event, "JOIN") == 0)
					{
						printf("%s * Joins: %s (%s) on %s\n", timestamp, match_strings[MATCH_ADDR_NICK], 
									match_strings[MATCH_ADDR_ALL], match_strings[MATCH_DATA]);
						if (strcmp(match_strings[MATCH_ADDR_NICK], MyNickname) == 0) // I joined
						{
							strcpy(ActiveChannel, match_strings[MATCH_DATA]);
						}
					}
					else if (strcmp(event, "PRIVMSG") == 0)	// Message
					{
						if (strcmp(match_strings[MATCH_TARGET], MyNickname) == 0) // Private
						{
							printf("%s -> *%s* %s\n", timestamp, match_strings[MATCH_ADDR_NICK], match_strings[MATCH_DATA]);
 
							if (strcmp(match_strings[MATCH_DATA], "!off") == 0 && strcmp(MyNickname, AlternateNickname) != 0)
							{
								printf("%s [COMMAND] * %s wants me off %s, changing nickname...\n", timestamp, match_strings[MATCH_ADDR_NICK], MyNickname);
								sprintf(outbuf, "NICK %s\n%c", AlternateNickname, NULL);
							}
							if (strcmp(match_strings[MATCH_DATA], "!on") == 0 && strcmp(MyNickname, PrimaryNickname) != 0)
							{
								printf("%s [COMMAND] * %s wants me on %s, attempting to change nickname...\n", timestamp, match_strings[MATCH_ADDR_NICK], PrimaryNickname);
								sprintf(outbuf, "NICK %s\n%c", PrimaryNickname, NULL);
							}
						}
						else if (*match_strings[MATCH_TARGET] == '#') // Channel
						{
							printf("%s <%s:%s> %s\n", timestamp, match_strings[MATCH_ADDR_NICK], match_strings[MATCH_TARGET], match_strings[MATCH_DATA]);
						}
					}
				}
 
			}
		}
 
		// Send data if we have any on queue
		if (*outbuf) 
		{
			send(s, outbuf, strlen(outbuf), 0);
		}
	}
	fclose(sockfd);
	return 0;
}
 
int parse_config(char *filename)
{
	FILE *fp = fopen(filename, "r");
	char line[4096], setting[100], value[100];
	int r, i;
	if (fp == NULL)
	{
		printf("ERROR! Config file %s not found.\n", filename);
		return -1;
	}
	while(fgets(line, 4096, fp) != NULL)
	{
		r = sscanf(line, "%[^=]=%[^\r\n]", setting, value);
		if (r == 0) continue;
		sscanf(setting, "%s", setting);
		for(i = 0; i < (int)strlen(setting); i++) setting[i] = tolower(setting[i]);
 
		// Check settings
		if (!strcmp(setting, "server"))
		{
			strcpy(ServerName, value);
		}
		else if (!strcmp(setting, "port"))
		{
			Port = atoi(value);
		}
		else if (!strcmp(setting, "pnick"))
		{
			strcpy(PrimaryNickname, value);
		}
		else if (!strcmp(setting, "anick"))
		{
			strcpy(AlternateNickname, value);
		}
		else if (!strcmp(setting, "whois-delay"))
		{
			WhoisDelay = atoi(value);
		}
		else if (!strcmp(setting, "connect-delay"))
		{
			ConnectDelay = atoi(value);
		}
		else if (!strcmp(setting, "channel"))
		{
			strcpy(MainChannel, value);
		}
		else if (!strcmp(setting, "ident"))
		{
			strcpy(Ident, value);
		}
		else if (!strcmp(setting, "fullname"))
		{
			strcpy(FullName, value);
		}
	}
	return 0;
}
 
int usage(int argc, int i, char *program)
{
	if (i+1 >= argc)
	{
		printf("usage: %s [-c config-file] [-s server] [-p port] [-n primary-nick]\n", program);
		printf("[-a alternate-nick] [-m main-channel] [-i ident] [-f fullname]\n");
		printf("[--whois-delay delay] [--connect-delay delay] [--?]\n\n");
		printf("Main Options:\n");
		printf("-c\t--config-file\tuse another configuration file (default=nkeep.conf)\n");
		printf("-h\t--?\t\tprint this help.\n\n");
		printf("Override Options: (Use these to override the config file)\n");
		printf("-s\t--server\toverride server ip in configuration.\n");	
		printf("-p\t--port\t\toverride default port.\n");	
		printf("-n\t--pnick\t\tnickname to keep.\n");		
		printf("-a\t--anick\t\tnickname to sit on when pnick is taken.\n");
		printf("-m\t--channel\tchannel to idle in.\n");	
		printf("-i\t--ident\t\toverride default ident.\n");
		printf("-f\t--fullname\toverride full name.\n");
		printf("-w\t--whois-delay\toverride whois delay time.\n");
		printf("-d\t--connect-delay\toverride reconnect delay time.\n");
		return -1;
	}
	return 0;
}
 
int main(int argc, char **argv)
{
	char input[4096], *outbuffer = malloc(4096);
	int re = 0, i;
	regmatch_t *matches = malloc(NUM_MATCHES * sizeof(regmatch_t) + 1);
	pthread_t threadid = 0;
 
#ifdef WIN32 /* Win32 - Starting WSA */
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD( 1, 1 ); 
    if(WSAStartup(wVersionRequested, &wsaData ) != 0)
	{
		return 0;
	}
#endif
 
	/* Default settings */
	strcpy(Ident, DEFAULT_IDENT);
	strcpy(FullName, DEFAULT_FULLNAME);
	*MainChannel = '\0';
	Port = DEFAULT_PORT;
	WhoisDelay = DEFAULT_WHOISDELAY;
	ConnectDelay = DEFAULT_CONNECTDELAY;
	Logging = DEFAULT_LOGGING;
 
	printf("Nickname Keeper by Jinx\n");
	if (argc < 2)	// Use default config
	{
		printf("Loading default configuration file...\n");
		if (parse_config("nkeep.conf") < 0) return 0;
	}
	else
	{
		parse_config("nkeep.conf");	// Load default settings, if any.
		for (i = 1; i < argc; i++)
		{
			if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--config"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				if (parse_config(argv[i+1]) < 0) return 0;
			}
			else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--server"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(ServerName, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				Port = atoi(argv[i+1]);
			}
			else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--connect-delay"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				ConnectDelay = atoi(argv[i+1]);
			}
			else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--pnick"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(PrimaryNickname, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--anick"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(AlternateNickname, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--whois-delay"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				WhoisDelay = atoi(argv[i+1]);
			}
			else if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--channel"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(MainChannel, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--ident"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(Ident, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--fullname"))
			{
				if (usage(argc, i, argv[0]) < 0) return 0;
				strcpy(FullName, argv[i+1]);
			}
			else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help") 
					|| *argv[i-1] != '-' || *argv[i] == '-') // None of the above
			{
				usage(0, 0, argv[0]);
				return 0;
			}
		}
	}
	if (*ServerName == '\0' || Port < 0 || Port > 65536  || *PrimaryNickname == '\0' || *AlternateNickname == '\0')
	{
		printf("ERROR! Missing server or nickname information.\n");
		return 0;
	}
	strcpy(MyNickname, PrimaryNickname);
 
	// Compile regex match
	linematch = malloc(sizeof(regex_t));
	if (regcomp(linematch, "^:(([^!\\s]+)(!([^@]+)@([^\\s]+))?)\\s+([^\\s]+)\\s+(([^\\s]+)\\s+)?:?(.+)$", 0) != 0)
	{
		return 0;
	}
 
	connected = 0;
 
	printf("Loaded nkeep with settings:\n\nSERVER=%s:%d PNICK=%s ANICK=%s\nWHOISDELAY=%d CONNECTDELAY=%d CHANNEL=%s IDENT=%s\nFULLNAME=%s\n\n", 
			ServerName, Port, PrimaryNickname, AlternateNickname, WhoisDelay, ConnectDelay, MainChannel, Ident, FullName);
	
	// Create threads
	NEWTHREAD(TimerCheckPrimaryNick, 0);	/* compatibility macro */
	NEWTHREAD(SocketHandler, 0);			/* compatibility macro */
 
	while(1)
	{
		fgets(input, 4096, stdin);
		*(input+strlen(input)-1) = '\0';
		memset(outbuffer, 0, 4096);
		if (input[0] == '/')
		{
			strcpy(outbuffer, &input[1]);
		}
		else
		{
			if (connected && *ActiveChannel != '\0')
			{
				sprintf(outbuffer, "PRIVMSG %s :%s\n", ActiveChannel, input);
			}
		}
		send(s, outbuffer, strlen(outbuffer), 0);
	}
	return 0;
}