User Tools

Site Tools


user:abrunda1:c_irc_bot

C

thorbot.c

/*
 * name:        thorbot
 * compile:     cc -g -Wall -pthread -o ircbot ircbot.c
 * xlint:       lint -aabchruH -lposix ircbot.c
 * usage:       ./ircbot nick host port listenhost listenport nickfile >> logfile
 * 
 */
 
 
#define _GNU_SOURCE	/* to make it compile without warning on glibc systems */
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
 
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
 
 
#define nil	NULL
#define strequiv(s1, s2)	(strcasecmp(s1, s2) == 0)
#define streql(s1, s2)		(strcmp(s1, s2) == 0)
 
 
#define IRCBOT_VERSION		"thorbot, version 0.1"
enum
{
	IRCMSG_MAXLEN = 512,            	/* maximum length of an irc (protocol) message */
	BBUFFERSIZE   = IRCMSG_MAXLEN+1,	/* size of the buffer for incoming irc data */
	SOCK_MAX      = 32,			/* maximum number of sockets to listen on */
	NICK_MAXLEN   = 128,			/* should be more than enough' */
	NOFLOOD_DELAY = 700			/* milliseconds between the sending of two irc messages */
};
 
 
/* types of the data send to the event processing main loop */
enum
{
	INTYPEircmsg,		/* irc message from ircd */
	INTYPEraw,		/* raw message to be send to the ircd */
	INTYPEbuiltin		/* builtin command */
};
 
/* type of the data send to the thread that sends irc messages to the ircd (without flooding) */
enum
{
	OUTTYPEimmediate,	/* accompanied data should be send immediately (e.g. for PING) */
	OUTTYPEnormal		/* normal data */
};
 
 
typedef unsigned long long uvlong;
typedef long long vlong;
typedef struct Source Source;
typedef struct Msg Msg;
typedef struct Elem Elem;
typedef struct Queue Queue;
typedef struct Nick Nick;
typedef struct Nicks Nicks;
typedef struct Buf Buf;
 
 
/* irc source of the message */
struct Source {
	char   *nick, *server;	/* when server is non-nil, nick will contain the same value as server */
	char   *user;		/* user an irc message-line originated from, may be nil */
	char   *host;		/* host the user is on, may be nil */
};
 
/* irc messages as received from the ircd */
struct Msg {
	Source *src;		/* source of the message, may be nil */
	char   *cmd;		/* irc command, always non-nil */
	char  **params;		/* parameters for the command, may be nil */
	int	nparams;	/* number of parameters */
};
 
/* element of the event processing queues */
struct Elem {
	void   *data;		/* data part of the element */
	int	type;		/* type of data (INTYPE* for incoming, OUTTYPE* for outgoing) */
	Elem   *next;		/* next element in the list, may be nil for the last element */
};
 
/* event processing queue */
struct Queue {
	Elem   *first;		/* head of the queue, may be nil */
	pthread_mutex_t	mutex;	/* to control access to the queue */
	pthread_cond_t	nonempty;	/* signalled when something is put in the queue */
};
 
/* nick with permission to let the bot execute commands */
struct Nick {
	char   *name;
	Nick   *next;
};
 
/* list of nicks with permission */
struct Nicks {
	Nick   *first;
};
 
/* buffer with incoming data from the ircd */
struct Buf {
	char    s[BBUFFERSIZE];	/* data buffer */
	int	slen;		/* length of data in the buffer s */
	int	fd;		/* file descriptor to read from when s is empty */
	int	eof;		/* whether or not we have seen eof on fd */
};
 
 
void	handlequeue(char *, const char *, const char *, const char *, Nicks *);	/* main event processing loop */
void   *Fircreader(void *);			/* thread: reads and parses lines from ircd, then puts them in incoming queue */
void   *Fircwriter(void *);			/* thread: writes lines in outgoing queue to ircd */
void   *Ftcpacceptor(void *);			/* thread: accepts incoming tcp control connections (and creates an Ftcpreader for it) */
void   *Ftcpreader(void *);			/* thread: reads data from tcp control connection, data is put in the outgoing queue */
int	tcpopen(const char *, const char *);	/* opens connection to ircd */
void    tcpbind(const char *, int []);		/* binds to localhost tcp ports for accepting incoming control connections */
 
Msg    *msg_parse(char *);			/* parses a line received from the irc connection, Msg is allocated */
Msg    *msg_new(void);				/* allocates and initializes a Msg */
void	msg_free(Msg *);			/* frees any memory allocated in Msg */
Source *source_parse(char *);			/* parse the source of the message (prefix' in rc terms) */
 
Queue  *qnew(void);				/* allocate and initialize a queue */
void	qappend(Queue *, int, void *);		/* append message to the queue */
void	qprepend(Queue *, int, void *);		/* prepend message to the queue */
void   *qremove(Queue *, int *);		/* retrieve and remove first element from queue */
void	qappendf(Queue *, int, const char *, ...);	/* append printf-like string to the queue */
void	qprependf(Queue *, int, const char *, ...);	/* same as qappendf but prepend */
void	qflushout(void);			/* flushes all non-important lines in outgoing queue */
 
Nicks  *nnew(const char *);			/* initialize nicks from the specified file */
void    nfree(Nicks *);				/* free Nicks */
void    nadd(Nicks *, char *);			/* add a nick */
void    nremove(Nicks *, char *);		/* remove the nick */
int	nhas(Nicks *, const char *);		/* check if the nick is present (allowed) */
void	nprint(Nicks *);			/* print a list of nicks to stderr, for debugging */
void    nsave(Nicks *, const char *);		/* save the nicks to the specified file */
 
Buf    *bnew(int);				/* create new buffer for reading incoming data from ircd */
int	breadln(Buf *, char *, int);		/* read a line (an irc message) from the buffer, keeps the trailing newline */
 
void    *xmalloc(size_t s)		{ void *p; p = malloc(s); if (p == nil) err(2, "xmalloc"); return p; }
void    *xrealloc(void *p, size_t s)	{ p = realloc(p, s); if (p == nil) err(2, "xrealloc"); return p; }
char    *xstrdup(char *str)		{ char *p; p = malloc(strlen(str) + 1); if (p == nil) err(2, "xstrdup"); strcpy(p, str); return p; }
 
 
/*
 * these are the only global variables (accessed by more than one thread),
 * these are protected by a pthread mutex and condition.
 */
Queue *inq;
Queue *outq;
 
 
int
main(int argc, char *argv[])
{
	char   *listenhost, *listenport;
	pthread_t Tircreader, Tircwriter, Ttcpacceptor[SOCK_MAX];
	char   *host, *port;
	char   *nickfile;
	struct	passwd *pw;
	int	ircfd;
	int     bindfds[SOCK_MAX+1];
	int	i;
	char   *nick;
	char   *userinfo;
	Nicks  *nicks;
 
 
	/* to make it accept and print warnings for options like it should */
	if (getopt(argc, argv, "") != -1) {
		fprintf(stderr, "usage: bot nick host port listenhost listenport nickfile\n");
		exit(1);
	}
	argc -= optind;
	argv += optind;
 
	if (argc != 6) {
		warnx("invalid number of arguments");
		fprintf(stderr, "usage: bot nick host port listenhost listenport nickfile\n");
		exit(1);
	}
 
	/*
	 * a copy of nick is needed because the nick may change, then the
	 * current nick is freed and a new one allocated
	 */
	nick = xstrdup(argv[0]);
	host = argv[1];
	port = argv[2];
	listenhost = argv[3];
	listenport = argv[4];
	nickfile = argv[5];
 
	/* find username and info to present to ircd */
	pw = getpwnam(getlogin());
	if (pw == nil)
		errx(1, "could not find passwd entry for %s", getlogin());
 
	/* read nicks that are allowed to execute commands */
	nicks = nnew(nickfile);
 
	/*
	 * allocate and initialize event queues.  one for incoming commands
	 * (from ircd or listenhost:listenport) and one for messages to be send
	 * to the ircd.
	 */
	inq = qnew();
	outq = qnew();
 
	/* commands to be executed need a clean environment */
	if (fcntl(STDIN_FILENO,  F_SETFD, 1) == -1 ||
	    fcntl(STDOUT_FILENO, F_SETFD, 1) == -1 ||
	    fcntl(STDERR_FILENO, F_SETFD, 1) == -1)
		err(1, "setting close-on-exec for stdin, stdout and stderr");
 
	/*
	 * connect to the ircd.  start listening for incoming commands.  on
	 * error, exit is called
	 */
	ircfd = tcpopen(host, port);
	tcpbind(listenport, bindfds);
 
	/* create the threads */
	pthread_create(&Tircreader, nil, Fircreader, &ircfd);
	pthread_create(&Tircwriter, nil, Fircwriter, &ircfd);
	for (i = 0; bindfds[i] != -1; ++i)
		pthread_create(&Ttcpacceptor[i], nil, Ftcpacceptor, &bindfds[i]);
 
	/* send the login commands to get the connection started */
	userinfo = pw->pw_gecos;
	if (streql(userinfo, ""))
		userinfo = "none";
	qappendf(outq, OUTTYPEimmediate, "NICK %s\r\n", nick);
	qappendf(outq, OUTTYPEimmediate, "USER %s 0 * :%s\r\n", pw->pw_name, userinfo);
 
	/* into message handling loop, this receives incoming matches and dispatches outgoing ones */
	handlequeue(nick, listenhost, listenport, nickfile, nicks);
	exit(0);	/* not reached */
}
 
 
void
handlequeue(char *nick, const char *listenhost, const char *listenport, const char *nickfile, Nicks *nicks)
{
	void   *data;
	int	type;
	Msg    *msg;
	char	botpidbuf[64];
	char   *target;
	char   *param;
 
	assert(snprintf(botpidbuf, sizeof botpidbuf, "%llu", (uvlong)getpid()) != -1);
 
	for (;;) {
		/*
		 * this call blocks (properly with mutexes and non-empty-queue
		 * condition) until something needs to be processed
		 */
		data = qremove(inq, &type);
 
		/* cleanup possible child processes, doesn't block */
		for (;;) {
			int	r;
			int	status;
 
			r = wait3(&status, WNOHANG, nil);
			if (r == 0 || (r == -1 && errno == ECHILD))
				break;		/* no children at all (0) or no exited children (-1) */
			if (r == -1) {
				warn("wait3");	/* error in wait3 */
				break;
			}
		}
		errno = 0;
 
		/* handle the data retrieved from the queue, based on the type of the data */
		switch (type) {
		case INTYPEircmsg:	/* incoming irc message from ircd */
			msg = (Msg *)data;
 
			/*
			 * ping message from ircd
			 *
			 * <<< PING :irc.snt.utwente.nl
			 * >>> PONG brein irc.snt.utwente.nl
			 */
			if (strequiv(msg->cmd, "PING") && msg->nparams >= 1)
				qprependf(outq, OUTTYPEimmediate, "PONG %s %s\r\n", nick, msg->params[0]);
 
			/*
			 * handle messages telling our nick changed
			 *
			 * >>> NICK newbrein
			 * <<< :brein!plan@tunnel4040.ipv6.xs4all.nl NICK :newbrein
			 */
			if (strequiv(msg->cmd, "NICK") && msg->nparams == 1 &&
			    msg->src != nil && msg->src->nick != nil && strequiv(msg->src->nick, nick)) {
				free(nick);
				nick = xstrdup(msg->params[0]);
			}
 
			/* private messages from nicks and with an argument may contain a command or a ctcp version */
			if (msg->src != nil && msg->src->nick != nil && strequiv(msg->cmd, "PRIVMSG") && msg->nparams >= 2) {
 
				param = msg->params[1];
 
				/*
				 * only accept commands from known people (both in a channel and in private)
				 * skip some common smileys
				 *
				 * <<< :Oksel!mechiel@tunnel4040.ipv6.xs4all.nl PRIVMSG #deadbeef :;help
				 * or
				 * <<< :Oksel!mechiel@tunnel4040.ipv6.xs4all.nl PRIVMSG brein :;wn whatsoever
				 */
				if (nhas(nicks, msg->src->nick) && param[0] == ';' && 
				    !(streql(param, ";)") || streql(param, ";-)") ||
				      streql(param, ";(") || streql(param, ";-("))) {
 
					target = msg->params[0];
					if (strequiv(msg->params[0], nick))	/* private message to bot, send response to nick, not channel */
						target = msg->src->nick;
 
					switch (fork()) {
					case 0:
						setenv("target", target, 1);
						setenv("nick", nick, 1);
						setenv("nickfile", nickfile, 1);
						setenv("fromuser", msg->src->nick, 1);
						if (target[0] == '#')
							setenv("fromchannel", target, 1);
						setenv("bothost", listenhost, 1);
						setenv("botport", listenport, 1);
						setenv("botpid", botpidbuf, 1);
 
						/* note: all fd's have close-on-exec set */
						execlp("./botcmd", "./botcmd", param + 1, (char *)nil);
						warn("execlp ./botcmd");
						break;
					case -1:
						warn("forking for execlp");
						break;
					}
				} else if (strequiv(param, "\001VERSION\001")) {
					/* handle a ctcp version request */
					qappendf(outq, OUTTYPEnormal, "NOTICE %s :\001VERSION %s\001\r\n", msg->src->nick, IRCBOT_VERSION);
				}
			}
 
			msg_free(msg); msg = nil;
			break;
		case INTYPEraw:		/* raw command to send to ircd */
			qappendf(outq, OUTTYPEnormal, "%s\r\n", (char *)data);
			free(data); data = nil;
			break;
		case INTYPEbuiltin:	/* built in commands */
			if (streql("flush", (char *)data)) {
				qflushout();
			} else if (streql("readnicks", (char *)data)) {
				nfree(nicks);
				nicks = nnew(nickfile);
			} else {
				warnx("unexpected builtin command read in handlequeue: %s", (char *)data);
			}
			free(data); data = nil;
			break;
		default:
			warnx("unknown message received in main, type=%d", type);
			break;
		}
	}
}
 
 
void *
Fircreader(void *ircfdp)
{
	char    buf[IRCMSG_MAXLEN+1];
	int	count;
	Buf    *bin;
	Msg    *msg;
	struct	timeval now;
	uvlong	msecs;
 
	bin = bnew(*(int *)ircfdp);
	for (;;) {
		/* read a message line from the ircd (including \r\n if present) */
		count = breadln(bin, buf, sizeof buf);
		if (count == 0)
			errx(1, "eof from ircd");
		if (count == -1)
			err(1, "reading from ircd");
 
		gettimeofday(&now, nil);
		msecs = (uvlong)now.tv_sec*1000 + (uvlong)now.tv_usec/1000;
		printf("<<< %llu %s", msecs, buf);
		fflush(stdout);
		msg = msg_parse(buf);
		if (msg == nil)
			continue;	/* message has been printed by msg_parse */
 
		/* insert the irc message into the event queue */
		if (strequiv(msg->cmd, "PING"))
			qprepend(inq, INTYPEircmsg, msg);
		else
			qappend(inq, INTYPEircmsg, msg);
	}
}
 
 
void *
Fircwriter(void *ircfdp)
{
	char   *line;
	int	type;
	uvlong	msecs;
	int	length, count;
	struct timeval	lastwrite = {0, 0};
	struct timeval  now;
 
	for (;;) {
		line = qremove(outq, &type);
		if (type == OUTTYPEimmediate) {
			length = strlen(line);
			count = write(*(int *)ircfdp, line, length);
			if (count != length)
				warnx("ircwriter: line not written entirely, length=%d count=%d", length, count);
 
			gettimeofday(&lastwrite, nil);
		} else {
			if (type != OUTTYPEnormal)
				warnx("ircwriter: unknown message, type=%d, writing as if type=OUTTYPEnormal", type);
 
			gettimeofday(&now, nil);
			msecs = (now.tv_sec-lastwrite.tv_sec)*1000 + (now.tv_usec-lastwrite.tv_usec)/1000;
			if (msecs < NOFLOOD_DELAY)
				usleep((NOFLOOD_DELAY-msecs) * 1000);
 
			length = strlen(line);
			count = write(*(int *)ircfdp, line, length);
			if (count != length)
				warnx("ircwriter: line not written entirely, length=%d count=%d", length, count);
 
			gettimeofday(&lastwrite, nil);
		}
 
		msecs = (uvlong)lastwrite.tv_sec*1000 + (uvlong)lastwrite.tv_usec/1000;
		printf(">>> %llu %s", msecs, line);
		fflush(stdout);
		free(line);
	}
}
 
 
void *
Ftcpacceptor(void *bindfdp)
{
	int	tcpfd;
	struct	sockaddr sa;
	socklen_t  salen;
	pthread_t *Ttcpreader;
 
	for (;;) {
		tcpfd = accept(*(int *)bindfdp, (struct sockaddr *)&sa, &salen);
		if (tcpfd < 0) {
			if (errno == EINTR)
				continue;
			err(1, "accept");
		}
 
		if (fcntl(tcpfd, F_SETFD, 1) == -1) {
			warn("setting close-on-exec flag for incoming connection");
			close(tcpfd);
			continue;
		}
 
		Ttcpreader = xmalloc(sizeof Ttcpreader[0]);
		pthread_create(Ttcpreader, nil, Ftcpreader, &tcpfd);
	}
}
 
 
void *
Ftcpreader(void *tcpfdp)
{
	char	buf[BBUFFERSIZE];
	int	count;
	Buf    *bin;
	char   *cp;
	char   *errstr;
 
	/*
	 * bnew and breadln were actually meant for reading irc messages with a
	 * maximum length of 512 characters.  the tcp connection has this limit
	 * too now, no problem though.
	 */
	bin = bnew(*(int *)tcpfdp);
	for (;;) {
		/* read a message line from the client (including \n if present) */
		count = breadln(bin, buf, sizeof buf);
		if (count == 0) {
			close(*(int *)tcpfdp);
			pthread_exit(nil);
		}
		if (count == -1) {
			warn("tcpreader: error reading from client");
			close(*(int *)tcpfdp);
			pthread_exit(nil);
		}
		if (buf[strlen(buf) - 1] == '\n') {
			buf[strlen(buf) - 1] = '\0';
			if (buf[strlen(buf) - 1] == '\r')	/* telnet(1) appends an \r, remove it */
				buf[strlen(buf) - 1] = '\0';
		}
 
		if (streql(buf, "flush") || streql(buf, "readnicks")) {
			qprepend(inq, INTYPEbuiltin, xstrdup(buf));
		} else if (buf[0] == ';') {
			qappend(inq, INTYPEraw, xstrdup(buf+1));
		} else if (buf[0] == '%') {
			cp = strchr(buf, ' ');
			if (cp == nil || cp == buf+1) {
				errstr = "no message to send to nick or empty nick\n";
				break;
			} else {
				*cp = '\0';
				qappendf(inq, INTYPEraw, "PRIVMSG %s :%s", buf+1, cp+1);
			}
		} else if (buf[0] == '#') {
			cp = strchr(buf, ' ');
			if (cp == nil || cp == buf+1) {
				errstr = "no message to send to channel or empty channel\n";
				break;
			} else {
				*cp = '\0';
				qappendf(inq, INTYPEraw, "PRIVMSG %s :%s", buf, cp+1);
			}
		} else {
			errstr = "no nick or channel specified\n";
			break;
		}
	}
 
	if (write(*(int *)tcpfdp, errstr, strlen(errstr)) != strlen(errstr))
		warnx("tcpreader: error string not written entirely to connection");
	close(*(int *)tcpfdp);
	pthread_exit(nil);
}
 
 
int
tcpopen(const char *host, const char *port)
{
	int	gaierrno;
	struct	addrinfo hints = { 0, PF_UNSPEC, SOCK_STREAM, 0, 0, nil, nil, nil };
	struct	addrinfo *res0, *res;
	char   *cause;
	int	save_errno;
	int	fd;
 
	gaierrno = getaddrinfo(host, port, &hints, &res0);
	if (gaierrno)
		errx(1, "getaddrinfo: %s", gai_strerror(gaierrno));
 
	fd = -1;
	for (res = res0; res != nil; res = res->ai_next) {
		fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 
		if (fd < 0) {
			fd = -1;
			cause = "socket";
			continue;
		}
 
		if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
			save_errno = errno;
			(void)close(fd);
			errno = save_errno;
 
			fd = -1;
			cause = "connect";
			continue;
		}
 
		break;
	}
	freeaddrinfo(res0);
 
	if (fd == -1)
		err(1, "connecting to %s:%s: %s", host, port, cause);
 
	if (fcntl(fd, F_SETFD, 1) == -1)
		err(1, "setting close-on-exec flag for irc connection");
 
	return fd;
}
 
 
void
tcpbind(const char *port, int bindfds[SOCK_MAX+1])
{
	struct	addrinfo hints = { AI_PASSIVE, PF_UNSPEC, SOCK_STREAM, 0, 0, nil, nil, nil };
	struct	addrinfo *res, *res0;
	int	gaierrno, save_errno;
	int	fd, nsock;
	char   *cause;
 
	gaierrno = getaddrinfo(nil, port, &hints, &res0);
	if (gaierrno)
		errx(1, "getaddrinfo: %s", gai_strerror(gaierrno));
 
	cause = nil;
	nsock = 0;
	for (res = res0; res != nil && nsock < SOCK_MAX; res = res->ai_next) {
		fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (fd < 0) {
			cause = "socket";
			continue;
		}
 
		if (bind(fd, res->ai_addr, res->ai_addrlen) < 0 || fcntl(fd, F_SETFD, 1) == -1) {
			cause = "bind or fcntl";
			save_errno = errno;
			close(fd);
			errno = save_errno;
 
			continue;
		}
		(void)listen(fd, 5);
		bindfds[nsock++] = fd;
		bindfds[nsock] = -1;
	}
	if (nsock == 0)
		err(1, cause);
 
	freeaddrinfo(res0);
}
 
 
/*
 * see rfc2812 for a detailed description of irc messages
 *
 * simply put, an irc message looks like this:
 *
 * message = (:$prefix)? $command ($param)* crlf
 * prefix = servername | nickname ((!user)?@host)?
 * command = string | three-digit-number
 * param = string
 *
 * a parameter starting with a colon is special.  the parameter ends at
 * the end of the line, i.e. does not end with the next space.
 */
Msg *
msg_parse(char *str)
{
	int	stop;
	char   *cp;
	Msg    *msg;
 
	assert(str != nil);
 
	msg = msg_new();
 
	if (str[0] == ':') {
		++str;
		cp = strchr(str, ' ');
		if (cp == nil) {
			warnx("parsing: message contains prefix but no space (and thus no command)");
			goto error;
		}
		*cp = '\0';
 
		msg->src = source_parse(str);
		if (msg->src == nil)
			goto error;	/* diagnostics printed by source_parse */
 
		str = cp+1;
	}
 
	stop = 0;
 
	cp = strchr(str, ' ');
	if (cp == nil) {
		cp = strchr(str, '\r');
		if (cp == nil) {
			warnx("parsing: message doesn't contain carriage return (for end) or space (for parameter delimiter) after command");
			goto error;
		}
		stop = 1;
	}
 
	*cp = '\0';
	msg->cmd = xstrdup(str);
	str = cp+1;
 
	while (!stop) {
		if (str[0] == ':') {
			++str;
			stop = 1;
		}
 
                /* when we saw a colon and thus stop is set, don't
                 * look for a space but for the closing carriage return */
		cp = strchr(str, stop ? '\r' : ' ');
		if (cp == nil) {
			/* the last argument is not required to have a colon */
			cp = strchr(str, '\r');
			if (cp == nil) {
				warnx("parsing: no carriage return after arguments");
				goto error;
			}
			stop = 1;
		}
		*cp = '\0';
		msg->params = xrealloc(msg->params, sizeof msg->params[0] * (msg->nparams+1));
		msg->params[msg->nparams++] = xstrdup(str);
 
		str = cp+1;
	}
 
	/* debug code... */
	if (0) {
		int	i;
 
		if (msg->src != nil) {
			if (msg->src->host != nil) {
				fprintf(stderr, "src->nick=%s\n", msg->src->nick);
				if (msg->src->user != nil)
					fprintf(stderr, "src->user=%s\n", msg->src->user);
				else
					fprintf(stderr, "src->user=(nil)\n");
				fprintf(stderr, "src->host=%s\n", msg->src->host);
			} else {
				fprintf(stderr, "nick=server=%s\n", msg->src->nick);
			}
		} else {
			fprintf(stderr, "src=(nil)\n");
		}
 
		fprintf(stderr, "cmd=%s\n", msg->cmd);
		fprintf(stderr, "nparams=%d\n", msg->nparams);
		for (i = 0; i < msg->nparams; ++i)
			fprintf(stderr, "params[%d]=%s\n", i, msg->params[i]);
	}
 
	return (Msg *)msg;
 
error:
	msg_free((Msg *)msg); msg = nil;
	return nil;
}
 
 
Source *
source_parse(char *str)
{
	Source *source;
	char   *cp;
 
	assert(str != nil);
 
	source = xmalloc(sizeof source[0]);
	source->nick = nil;
	source->server = nil;
	source->user = nil;
	source->host = nil;
 
	cp = strchr(str, '@');
	if (cp == nil) {
		/* the source is either a servername or a nick without user/host part */
		source->nick = source->server = xstrdup(str);
		return source;
	}
 
	source->host = xstrdup(cp+1);
	*cp = '\0';	/* make location of @ end of string, thus str only contains nick with optionally !user */
 
	cp = strchr(str, '!');
	if (cp == nil) {
		source->nick = xstrdup(str);
		return source;
	}
 
	source->user = xstrdup(cp+1);
	*cp = '\0';	/* make location of ! end of string, thus str only contains nick */
	source->nick = xstrdup(str);
 
	return source;
}
 
 
Msg *
msg_new(void)
{
	Msg    *msg;
 
	msg = xmalloc(sizeof msg[0]);
	msg->src = nil;
	msg->cmd = nil;
	msg->params = nil;
	msg->nparams= 0;
 
	return msg;
}
 
 
void
msg_free(Msg *msg)
{
	int	i;
 
	if (msg == nil)
		return;
 
	for (i = 0; i < msg->nparams; ++i)
		free(msg->params[i]);
	free(msg->params);
	free(msg->cmd);
	if (msg->src != nil) {
		free(msg->src->nick);
		msg->src->server = nil;		/* server is always same as nick or nil */
		free(msg->src->user);
		free(msg->src->host);
		free(msg->src);
	}
	free(msg);
}
 
 
Queue *
qnew(void)
{
	Queue *new;
 
	new = xmalloc(sizeof new[0]);
	new->first = nil;
	pthread_mutex_init(&new->mutex, nil);
	pthread_cond_init(&new->nonempty, nil);
 
	return new;
}
 
 
void
qappend(Queue *q, int type, void *data)
{
	Elem   *new;
	Elem   *oldlast;
 
	assert(q != nil);
	assert(data != nil);
 
	new = xmalloc(sizeof new[0]);
	new->data = data;
	new->type = type;
	new->next = nil;
 
	pthread_mutex_lock(&q->mutex);
	if (q->first == nil) {
		q->first = new;
	} else {
		oldlast = q->first;
		while (oldlast->next != nil)
			oldlast = oldlast->next;
		oldlast->next = new;
	}
	pthread_cond_signal(&q->nonempty);
	pthread_mutex_unlock(&q->mutex);
}
 
 
void
qprepend(Queue *q, int type, void *data)
{
	Elem   *new;
 
	assert(q != nil);
	assert(data != nil);
 
	new = xmalloc(sizeof new[0]);
	new->data = data;
	new->type = type;
	new->next = nil;
 
	pthread_mutex_lock(&q->mutex);
	new->next = q->first;
	q->first = new;
	pthread_cond_signal(&q->nonempty);
	pthread_mutex_unlock(&q->mutex);
}
 
 
void *
qremove(Queue *q, int *typep)
{
	void   *r;
	Elem   *newfirst;
 
	assert(q != nil);
 
	pthread_mutex_lock(&q->mutex);
	if (q->first == nil)
		pthread_cond_wait(&q->nonempty, &q->mutex);
 
	assert(q->first != nil);
 
	r = q->first->data;
	if (typep != nil)
		(*typep) = q->first->type;
	newfirst = q->first->next;
	free(q->first);
	q->first = newfirst;
	pthread_mutex_unlock(&q->mutex);
 
	return r;
}
 
 
void
qappendf(Queue *q, int type, const char *fmt, ...)
{
	char   *tmp;
	va_list ap;
 
	va_start(ap, fmt);
	vasprintf(&tmp, fmt, ap);
	va_end(ap);
 
	qappend(q, type, tmp);
}
 
 
void
qprependf(Queue *q, int type, const char *fmt, ...)
{
	char   *tmp;
	va_list ap;
 
	va_start(ap, fmt);
	vasprintf(&tmp, fmt, ap);
	va_end(ap);
 
	qprepend(q, type, tmp);
}
 
 
void
qflushout(void)
{
	Elem   *newfirst;
	Elem   *newcur;
	Elem   *cur, *tmp;
 
	pthread_mutex_lock(&outq->mutex);
 
	newfirst = newcur = nil;
	cur = outq->first;
	while (cur != nil) {
		if (cur->type == OUTTYPEimmediate) {
			if (newfirst == nil) {
				newfirst = newcur = cur;
				newfirst->next = nil;
			} else {
				newcur->next = cur;
				newcur = newcur->next;
				newcur->next = nil;
			}
			cur = cur->next;
		} else {
			free(cur->data);
			tmp = cur;
			cur = cur->next;
			free(tmp);
		}
	}
	outq->first = newfirst;
 
	pthread_mutex_unlock(&outq->mutex);
}
 
 
Nicks *
nnew(const char *file)
{
	Nicks  *n;
	FILE   *fp;
	char    buf[NICK_MAXLEN + 1];
 
	n = xmalloc(sizeof n[0]);
	n->first = nil;
 
	fp = fopen(file, "r");
	if (fp == nil)
		err(1, "opening %s", file);
 
	/* read nicks from file and add them to the Nicks structure */
	for (;;) {
		if (fgets(buf, sizeof buf, fp) == nil) {
			if (ferror(fp))
				err(1, "reading from %s", file);
			break;
		}
		if (strchr(buf, '\n') != nil)
			buf[strlen(buf) - 1] = '\0';
		nadd(n, xstrdup(buf));
	}
 
	fclose(fp);
 
	return n;
}
 
 
void
nfree(Nicks *n)
{
	Nick   *cur, *tmp;
 
	for (cur = n->first;  cur != nil;  tmp = cur->next, free(cur), cur = tmp)
		free(cur->name);
	free(n);
}
 
 
void
nadd(Nicks *n, char *name)
{
	Nick   *new;
	Nick  **ptr;
	int	wslen;
 
	/* remove leading and trailing whitespace from nick */
	wslen = strspn(name, " \t\n");
	memmove(name, name+wslen, strlen(name+wslen) + 1);
	if (strpbrk(name, " \t\n") != nil)
		*(strpbrk(name, " \t\n")) = '\0';
 
	if (nhas(n, name) || strlen(name) == 0)	/* avoid duplicates, refuse empty nicks */
		return;
 
	new = xmalloc(sizeof new[0]);
	new->name = name;
	new->next = nil;
 
	ptr = &n->first;
	while (*ptr != nil)
		ptr = &(*ptr)->next;
	*ptr = new;
}
 
 
void
nremove(Nicks *n, char *name)
{
	Nick   *tmp;
	Nick  **newnext;
 
	newnext = &n->first;
	while (*newnext != nil) {
		if (strequiv((*newnext)->name, name)) {
			tmp = *newnext;
			*newnext = (*newnext)->next;
			free(tmp->name);
			free(tmp);
 
			return;
		} else {
			newnext = &(*newnext)->next;
		}
	}
}
 
 
int
nhas(Nicks *n, const char *name)
{
	Nick   *cur;
 
	for (cur = n->first; cur != nil; cur = cur->next)
		if (strequiv(cur->name, name))
			return 1;
	return 0;
}
 
 
void
nprint(Nicks *n)
{
	Nick   *cur;
 
	fprintf(stderr, "bot: nprint: ");
	for (cur = n->first; cur != nil; cur = cur->next)
		fprintf(stderr, "`%s' ", cur->name);
	fprintf(stderr, "\n");
}
 
 
void
nsave(Nicks *n, const char *file)
{
	Nick   *cur;
	FILE   *fp;
 
	fp = fopen(file, "w");
	if (fp == nil) {
		warn("opening %s", file);
		return;
	}
 
	for (cur = n->first; cur != nil; cur = cur->next)
		fprintf(fp, "%s\n", cur->name);
	if (ferror(fp))
		warnx("ferror after writing %s", file);
 
	fclose(fp);
}
 
 
Buf *
bnew(int fd)
{
	Buf    *new;
 
	assert(fd >= 0);
 
	new = xmalloc(sizeof new[0]);
	new->fd = fd;
	new->s[0] = '\0';
	new->slen = 0;
	new->eof = 0;
 
	return new;
}
 
 
/* trailing newline is kept if present, there could be no newline at all */
int
breadln(Buf *b, char *dest, int destsize)
{
	int	count;
	char   *newline;
	int	flushtonewline;
 
	assert(destsize >= sizeof b->s);
 
	if (b->eof)
		return 0;
 
	flushtonewline = 0;
	while (strchr(b->s, '\n') == nil) {
		if (b->slen == sizeof b->s - 1) {
			/* line too long, flush entire line */
			flushtonewline = 1;
			b->s[0] = '\0';
			b->slen = 0;
		}
 
		count = read(b->fd, b->s + b->slen, (BBUFFERSIZE - 1) - b->slen);
		if (count == 0) {
			b->eof = 1;
 
			memmove(dest, b->s, b->slen);
			dest[b->slen] = '\0';
			count = b->slen;
			b->s[0] = '\0';
			b->slen = 0;
 
			return count;
		}
		if (count == -1)
			return -1;
		b->slen += count;
		b->s[b->slen] = '\0';
 
		if (flushtonewline) {
			newline = strchr(b->s, '\n');
			if (newline == nil) {
				b->s[0] = '\0';
				b->slen = 0;
			} else {
				memmove(b->s, newline+1, b->slen - (newline - b->s + 1) + 1);
				b->slen -= newline - b->s + 1;
				flushtonewline = 0;
			}
		}
	}
 
	newline = strchr(b->s, '\n');
	assert(newline != nil);
 
	count = newline - b->s + 1;
	strncpy(dest, b->s, count);
	dest[count] = '\0';
	memmove(b->s, b->s+count, b->slen - count + 1);
	b->slen -= count;
 
	return count;
}
user/abrunda1/c_irc_bot.txt · Last modified: 2010/02/18 14:52 by abrunda1