ii

irc it, simple FIFO based irc client
git clone git://git.suckless.org/ii
Log | Files | Refs | README | LICENSE

ii.c (20039B)


      1 /* See LICENSE file for license details. */
      2 #include <sys/select.h>
      3 #include <sys/socket.h>
      4 #include <sys/stat.h>
      5 #include <sys/types.h>
      6 #include <sys/un.h>
      7 
      8 #include <ctype.h>
      9 #include <errno.h>
     10 #include <fcntl.h>
     11 #include <limits.h>
     12 #include <netdb.h>
     13 #include <netinet/in.h>
     14 #include <pwd.h>
     15 #include <signal.h>
     16 #include <stdarg.h>
     17 #include <stdio.h>
     18 #include <stdlib.h>
     19 #include <string.h>
     20 #include <time.h>
     21 #include <unistd.h>
     22 
     23 char *argv0;
     24 
     25 #include "arg.h"
     26 
     27 #ifdef NEED_STRLCPY
     28 size_t strlcpy(char *, const char *, size_t);
     29 #endif /* NEED_STRLCPY */
     30 
     31 #define IRC_CHANNEL_MAX   200
     32 #define IRC_MSG_MAX       512 /* guaranteed to be <= than PIPE_BUF */
     33 #define PING_TIMEOUT      300
     34 
     35 enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
     36 
     37 typedef struct Channel Channel;
     38 struct Channel {
     39 	int fdin;
     40 	char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
     41 	char inpath[PATH_MAX];      /* input path */
     42 	char outpath[PATH_MAX];     /* output path */
     43 	Channel *next;
     44 };
     45 
     46 static Channel * channel_add(const char *);
     47 static Channel * channel_find(const char *);
     48 static Channel * channel_join(const char *);
     49 static void      channel_leave(Channel *);
     50 static Channel * channel_new(const char *);
     51 static void      channel_normalize_name(char *);
     52 static void      channel_normalize_path(char *);
     53 static int       channel_open(Channel *);
     54 static void      channel_print(Channel *, const char *);
     55 static int       channel_reopen(Channel *);
     56 static void      channel_rm(Channel *);
     57 static void      create_dirtree(const char *);
     58 static void      create_filepath(char *, size_t, const char *, const char *, const char *);
     59 static void      ewritestr(int, const char *);
     60 static void      handle_channels_input(int, Channel *);
     61 static void      handle_server_output(int);
     62 static int       isnumeric(const char *);
     63 static void      loginkey(int, const char *);
     64 static void      loginuser(int, const char *, const char *);
     65 static void      proc_channels_input(int, Channel *, char *);
     66 static void      proc_channels_privmsg(int, Channel *, char *);
     67 static void      proc_server_cmd(int, char *);
     68 static int       read_line(int, char *, size_t);
     69 static void      run(int, const char *);
     70 static void      setup(void);
     71 static void      sighandler(int);
     72 static int       tcpopen(const char *, const char *);
     73 static size_t    tokenize(char **, size_t, char *, int);
     74 static int       udsopen(const char *);
     75 static void      usage(void);
     76 
     77 static int      isrunning = 1;
     78 static time_t   last_response = 0;
     79 static Channel *channels = NULL;
     80 static Channel *channelmaster = NULL;
     81 static char     nick[32];          /* active nickname at runtime */
     82 static char     _nick[32];         /* nickname at startup */
     83 static char     ircpath[PATH_MAX]; /* irc dir (-i) */
     84 static char     msg[IRC_MSG_MAX];  /* message buf used for communication */
     85 
     86 static void
     87 usage(void)
     88 {
     89 	fprintf(stderr, "usage: %s <-s host> [-i <irc dir>] [-p <port>] "
     90 	        "[-u <sockname>] [-n <nick>] [-k <password>] "
     91 	        "[-f <fullname>]\n", argv0);
     92 	exit(1);
     93 }
     94 
     95 static void
     96 ewritestr(int fd, const char *s)
     97 {
     98 	size_t len, off = 0;
     99 	int w = -1;
    100 
    101 	len = strlen(s);
    102 	for (off = 0; off < len; off += w) {
    103 		if ((w = write(fd, s + off, len - off)) == -1)
    104 			break;
    105 		off += w;
    106 	}
    107 	if (w == -1) {
    108 		fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno));
    109 		exit(1);
    110 	}
    111 }
    112 
    113 /* creates directories bottom-up, if necessary */
    114 static void
    115 create_dirtree(const char *dir)
    116 {
    117 	char tmp[PATH_MAX], *p;
    118 	struct stat st;
    119 	size_t len;
    120 
    121 	strlcpy(tmp, dir, sizeof(tmp));
    122 	len = strlen(tmp);
    123 	if (len > 0 && tmp[len - 1] == '/')
    124 		tmp[len - 1] = '\0';
    125 
    126 	if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode))
    127 		return; /* dir exists */
    128 
    129 	for (p = tmp + 1; *p; p++) {
    130 		if (*p != '/')
    131 			continue;
    132 		*p = '\0';
    133 		mkdir(tmp, S_IRWXU);
    134 		*p = '/';
    135 	}
    136 	mkdir(tmp, S_IRWXU);
    137 }
    138 
    139 static void
    140 channel_normalize_path(char *s)
    141 {
    142 	for (; *s; s++) {
    143 		if (isalpha((unsigned char)*s))
    144 			*s = tolower((unsigned char)*s);
    145 		else if (!isdigit((unsigned char)*s) && !strchr(".#&+!-", *s))
    146 			*s = '_';
    147 	}
    148 }
    149 
    150 static void
    151 channel_normalize_name(char *s)
    152 {
    153 	char *p;
    154 
    155 	while (*s == '&' || *s == '#')
    156 		s++;
    157 	for (p = s; *s; s++) {
    158 		if (!strchr(" ,&#\x07", *s)) {
    159 			*p = *s;
    160 			p++;
    161 		}
    162 	}
    163 	*p = '\0';
    164 }
    165 
    166 static void
    167 create_filepath(char *filepath, size_t len, const char *path,
    168 	const char *channel, const char *suffix)
    169 {
    170 	int r;
    171 
    172 	if (channel[0]) {
    173 		r = snprintf(filepath, len, "%s/%s", path, channel);
    174 		if (r < 0 || (size_t)r >= len)
    175 			goto error;
    176 		create_dirtree(filepath);
    177 		r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
    178 		if (r < 0 || (size_t)r >= len)
    179 			goto error;
    180 	} else {
    181 		r = snprintf(filepath, len, "%s/%s", path, suffix);
    182 		if (r < 0 || (size_t)r >= len)
    183 			goto error;
    184 	}
    185 	return;
    186 
    187 error:
    188 	fprintf(stderr, "%s: path to irc directory too long\n", argv0);
    189 	exit(1);
    190 }
    191 
    192 static int
    193 channel_open(Channel *c)
    194 {
    195 	int fd;
    196 	struct stat st;
    197 
    198 	/* make "in" fifo if it doesn't exist already. */
    199 	if (lstat(c->inpath, &st) != -1) {
    200 		if (!(st.st_mode & S_IFIFO))
    201 			return -1;
    202 	} else if (mkfifo(c->inpath, S_IRWXU)) {
    203 		return -1;
    204 	}
    205 	c->fdin = -1;
    206 	fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0);
    207 	if (fd == -1)
    208 		return -1;
    209 	c->fdin = fd;
    210 
    211 	return 0;
    212 }
    213 
    214 static int
    215 channel_reopen(Channel *c)
    216 {
    217 	if (c->fdin > 2) {
    218 		close(c->fdin);
    219 		c->fdin = -1;
    220 	}
    221 	return channel_open(c);
    222 }
    223 
    224 static Channel *
    225 channel_new(const char *name)
    226 {
    227 	Channel *c;
    228 	char channelpath[PATH_MAX];
    229 
    230 	strlcpy(channelpath, name, sizeof(channelpath));
    231 	channel_normalize_path(channelpath);
    232 
    233 	if (!(c = calloc(1, sizeof(Channel)))) {
    234 		fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno));
    235 		exit(1);
    236 	}
    237 
    238 	strlcpy(c->name, name, sizeof(c->name));
    239 	channel_normalize_name(c->name);
    240 
    241 	create_filepath(c->inpath, sizeof(c->inpath), ircpath,
    242 	                channelpath, "in");
    243 	create_filepath(c->outpath, sizeof(c->outpath), ircpath,
    244 	                channelpath, "out");
    245 	return c;
    246 }
    247 
    248 static Channel *
    249 channel_find(const char *name)
    250 {
    251 	Channel *c;
    252 	char chan[IRC_CHANNEL_MAX];
    253 
    254 	strlcpy(chan, name, sizeof(chan));
    255 	channel_normalize_name(chan);
    256 	for (c = channels; c; c = c->next) {
    257 		if (!strcmp(chan, c->name))
    258 			return c; /* already handled */
    259 	}
    260 	return NULL;
    261 }
    262 
    263 static Channel *
    264 channel_add(const char *name)
    265 {
    266 	Channel *c;
    267 
    268 	c = channel_new(name);
    269 	if (channel_open(c) == -1) {
    270 		fprintf(stderr, "%s: cannot create channel: %s: %s\n",
    271 		         argv0, name, strerror(errno));
    272 		free(c);
    273 		return NULL;
    274 	}
    275 	if (!channels) {
    276 		channels = c;
    277 	} else {
    278 		c->next = channels;
    279 		channels = c;
    280 	}
    281 	return c;
    282 }
    283 
    284 static Channel *
    285 channel_join(const char *name)
    286 {
    287 	Channel *c;
    288 
    289 	if (!(c = channel_find(name)))
    290 		c = channel_add(name);
    291 	return c;
    292 }
    293 
    294 static void
    295 channel_rm(Channel *c)
    296 {
    297 	Channel *p;
    298 
    299 	if (channels == c) {
    300 		channels = channels->next;
    301 	} else {
    302 		for (p = channels; p && p->next != c; p = p->next)
    303 			;
    304 		if (p && p->next == c)
    305 			p->next = c->next;
    306 	}
    307 	free(c);
    308 }
    309 
    310 static void
    311 channel_leave(Channel *c)
    312 {
    313 	if (c->fdin > 2) {
    314 		close(c->fdin);
    315 		c->fdin = -1;
    316 	}
    317 	/* remove "in" file on leaving the channel */
    318 	unlink(c->inpath);
    319 	channel_rm(c);
    320 }
    321 
    322 static void
    323 loginkey(int ircfd, const char *key)
    324 {
    325 	snprintf(msg, sizeof(msg), "PASS %s\r\n", key);
    326 	ewritestr(ircfd, msg);
    327 }
    328 
    329 static void
    330 loginuser(int ircfd, const char *host, const char *fullname)
    331 {
    332 	snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n",
    333 	         nick, nick, host, fullname);
    334 	puts(msg);
    335 	ewritestr(ircfd, msg);
    336 }
    337 
    338 static int
    339 udsopen(const char *uds)
    340 {
    341 	struct sockaddr_un sun;
    342 	size_t len;
    343 	int fd;
    344 
    345 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
    346 		fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno));
    347 		exit(1);
    348 	}
    349 
    350 	sun.sun_family = AF_UNIX;
    351 	if (strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
    352 		fprintf(stderr, "%s: UNIX domain socket path truncation\n", argv0);
    353 		exit(1);
    354 	}
    355 	len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family);
    356 	if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
    357 		fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno));
    358 		exit(1);
    359 	}
    360 	return fd;
    361 }
    362 
    363 static int
    364 tcpopen(const char *host, const char *service)
    365 {
    366 	struct addrinfo hints, *res = NULL, *rp;
    367 	int fd = -1, e;
    368 
    369 	memset(&hints, 0, sizeof(hints));
    370 	hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
    371 	hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
    372 	hints.ai_socktype = SOCK_STREAM;
    373 
    374 	if ((e = getaddrinfo(host, service, &hints, &res))) {
    375 		fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e));
    376 		exit(1);
    377 	}
    378 
    379 	for (rp = res; rp; rp = rp->ai_next) {
    380 		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    381 		if (fd == -1)
    382 			continue;
    383 		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
    384 			close(fd);
    385 			fd = -1;
    386 			continue;
    387 		}
    388 		break; /* success */
    389 	}
    390 	if (fd == -1) {
    391 		fprintf(stderr, "%s: could not connect to %s:%s: %s\n",
    392 			argv0, host, service, strerror(errno));
    393 		exit(1);
    394 	}
    395 
    396 	freeaddrinfo(res);
    397 	return fd;
    398 }
    399 
    400 static int
    401 isnumeric(const char *s)
    402 {
    403 	errno = 0;
    404 	strtol(s, NULL, 10);
    405 	return errno == 0;
    406 }
    407 
    408 static size_t
    409 tokenize(char **result, size_t reslen, char *str, int delim)
    410 {
    411 	char *p = NULL, *n = NULL;
    412 	size_t i = 0;
    413 
    414 	for (n = str; *n == ' '; n++)
    415 		;
    416 	p = n;
    417 	while (*n != '\0') {
    418 		if (i >= reslen)
    419 			return 0;
    420 		if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0]))
    421 			delim = ':'; /* workaround non-RFC compliant messages */
    422 		if (*n == delim) {
    423 			*n = '\0';
    424 			result[i++] = p;
    425 			p = ++n;
    426 		} else {
    427 			n++;
    428 		}
    429 	}
    430 	/* add last entry */
    431 	if (i < reslen && p < n && p && *p)
    432 		result[i++] = p;
    433 	return i; /* number of tokens */
    434 }
    435 
    436 static void
    437 channel_print(Channel *c, const char *buf)
    438 {
    439 	FILE *fp = NULL;
    440 	time_t t = time(NULL);
    441 
    442 	if (!(fp = fopen(c->outpath, "a")))
    443 		return;
    444 	fprintf(fp, "%lu %s\n", (unsigned long)t, buf);
    445 	fclose(fp);
    446 }
    447 
    448 static void
    449 proc_channels_privmsg(int ircfd, Channel *c, char *buf)
    450 {
    451 	snprintf(msg, sizeof(msg), "<%s> %s", nick, buf);
    452 	channel_print(c, msg);
    453 	snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf);
    454 	ewritestr(ircfd, msg);
    455 }
    456 
    457 static void
    458 proc_channels_input(int ircfd, Channel *c, char *buf)
    459 {
    460 	char *p = NULL;
    461 	size_t buflen;
    462 
    463 	if (buf[0] == '\0')
    464 		return;
    465 	if (buf[0] != '/') {
    466 		proc_channels_privmsg(ircfd, c, buf);
    467 		return;
    468 	}
    469 
    470 	msg[0] = '\0';
    471 	if ((buflen = strlen(buf)) < 2)
    472 		return;
    473 	if (buf[2] == ' ' || buf[2] == '\0') {
    474 		switch (buf[1]) {
    475 		case 'j': /* join */
    476 			if (buflen < 3)
    477 				return;
    478 			if ((p = strchr(&buf[3], ' '))) /* password parameter */
    479 				*p = '\0';
    480 			if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') ||
    481 				(buf[3] == '!'))
    482 			{
    483 				/* password protected channel */
    484 				if (p)
    485 					snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1);
    486 				else
    487 					snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]);
    488 				channel_join(&buf[3]);
    489 			} else if (p) {
    490 				if ((c = channel_join(&buf[3])))
    491 					proc_channels_privmsg(ircfd, c, p + 1);
    492 				return;
    493 			}
    494 			break;
    495 		case 't': /* topic */
    496 			if (buflen >= 3)
    497 				snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]);
    498 			break;
    499 		case 'a': /* away */
    500 			if (buflen >= 3) {
    501 				snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]);
    502 				channel_print(c, msg);
    503 			}
    504 			if (buflen >= 3)
    505 				snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]);
    506 			else
    507 				snprintf(msg, sizeof(msg), "AWAY\r\n");
    508 			break;
    509 		case 'n': /* change nick */
    510 			if (buflen >= 3) {
    511 				strlcpy(_nick, &buf[3], sizeof(_nick));
    512 				snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]);
    513 			}
    514 			break;
    515 		case 'l': /* leave */
    516 			if (c == channelmaster)
    517 				return;
    518 			if (buflen >= 3)
    519 				snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]);
    520 			else
    521 				snprintf(msg, sizeof(msg),
    522 				         "PART %s :leaving\r\n", c->name);
    523 			ewritestr(ircfd, msg);
    524 			channel_leave(c);
    525 			return;
    526 			break;
    527 		case 'q': /* quit */
    528 			if (buflen >= 3)
    529 				snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]);
    530 			else
    531 				snprintf(msg, sizeof(msg),
    532 				         "QUIT %s\r\n", "bye");
    533 			ewritestr(ircfd, msg);
    534 			isrunning = 0;
    535 			return;
    536 			break;
    537 		default: /* raw IRC command */
    538 			snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
    539 			break;
    540 		}
    541 	} else {
    542 		/* raw IRC command */
    543 		snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
    544 	}
    545 	if (msg[0] != '\0')
    546 		ewritestr(ircfd, msg);
    547 }
    548 
    549 static void
    550 proc_server_cmd(int fd, char *buf)
    551 {
    552 	Channel *c;
    553 	const char *channel;
    554 	char *argv[TOK_LAST], *cmd = NULL, *p = NULL;
    555 	unsigned int i;
    556 
    557 	if (!buf || buf[0] == '\0')
    558 		return;
    559 
    560 	/* clear tokens */
    561 	for (i = 0; i < TOK_LAST; i++)
    562 		argv[i] = NULL;
    563 
    564 	/* check prefix */
    565 	if (buf[0] == ':') {
    566 		if (!(p = strchr(buf, ' ')))
    567 			return;
    568 		*p = '\0';
    569 		for (++p; *p == ' '; p++)
    570 			;
    571 		cmd = p;
    572 		argv[TOK_NICKSRV] = &buf[1];
    573 		if ((p = strchr(buf, '!'))) {
    574 			*p = '\0';
    575 			argv[TOK_USER] = ++p;
    576 		}
    577 	} else {
    578 		cmd = buf;
    579 	}
    580 
    581 	/* remove CRLFs */
    582 	for (p = cmd; p && *p != '\0'; p++) {
    583 		if (*p == '\r' || *p == '\n')
    584 			*p = '\0';
    585 	}
    586 
    587 	if ((p = strchr(cmd, ':'))) {
    588 		*p = '\0';
    589 		argv[TOK_TEXT] = ++p;
    590 	}
    591 
    592 	tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
    593 
    594 	if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) {
    595 		return;
    596 	} else if (!strcmp("PING", argv[TOK_CMD])) {
    597 		snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]);
    598 		ewritestr(fd, msg);
    599 		return;
    600 	} else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) {
    601 		/* server command */
    602 		snprintf(msg, sizeof(msg), "%s%s",
    603 				argv[TOK_ARG] ? argv[TOK_ARG] : "",
    604 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    605 		channel_print(channelmaster, msg);
    606 		return; /* don't process further */
    607 	} else if (!strcmp("ERROR", argv[TOK_CMD]))
    608 		snprintf(msg, sizeof(msg), "-!- error %s",
    609 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
    610 	else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) {
    611 		if (argv[TOK_TEXT])
    612 			argv[TOK_CHAN] = argv[TOK_TEXT];
    613 		snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s",
    614 				argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
    615 	} else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) {
    616 		snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s",
    617 				argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
    618 		/* if user itself leaves, don't write to channel (don't reopen channel). */
    619 		if (!strcmp(argv[TOK_NICKSRV], nick))
    620 			return;
    621 	} else if (!strcmp("MODE", argv[TOK_CMD])) {
    622 		snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s",
    623 				argv[TOK_NICKSRV],
    624 				argv[TOK_CHAN] ? argv[TOK_CHAN] : "",
    625 				argv[TOK_ARG]  ? argv[TOK_ARG] : "",
    626 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    627 	} else if (!strcmp("QUIT", argv[TOK_CMD])) {
    628 		snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"",
    629 				argv[TOK_NICKSRV], argv[TOK_USER],
    630 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    631 	} else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] &&
    632 	          !strcmp(_nick, argv[TOK_TEXT])) {
    633 		strlcpy(nick, _nick, sizeof(nick));
    634 		snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick);
    635 		channel_print(channelmaster, msg);
    636 	} else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) {
    637 		snprintf(msg, sizeof(msg), "-!- %s changed nick to %s",
    638 				argv[TOK_NICKSRV], argv[TOK_TEXT]);
    639 	} else if (!strcmp("TOPIC", argv[TOK_CMD])) {
    640 		snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"",
    641 				argv[TOK_NICKSRV],
    642 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    643 	} else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) {
    644 		snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")",
    645 				argv[TOK_NICKSRV], argv[TOK_ARG],
    646 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    647 	} else if (!strcmp("NOTICE", argv[TOK_CMD])) {
    648 		snprintf(msg, sizeof(msg), "-!- \"%s\"",
    649 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    650 	} else if (!strcmp("PRIVMSG", argv[TOK_CMD])) {
    651 		snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV],
    652 				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
    653 	} else {
    654 		return; /* can't read this message */
    655 	}
    656 	if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick))
    657 		channel = argv[TOK_NICKSRV];
    658 	else
    659 		channel = argv[TOK_CHAN];
    660 
    661 	if (!channel || channel[0] == '\0')
    662 		c = channelmaster;
    663 	else
    664 		c = channel_join(channel);
    665 	if (c)
    666 		channel_print(c, msg);
    667 }
    668 
    669 static int
    670 read_line(int fd, char *buf, size_t bufsiz)
    671 {
    672 	size_t i = 0;
    673 	char c = '\0';
    674 
    675 	do {
    676 		if (read(fd, &c, sizeof(char)) != sizeof(char))
    677 			return -1;
    678 		buf[i++] = c;
    679 	} while (c != '\n' && i < bufsiz);
    680 	buf[i - 1] = '\0'; /* eliminates '\n' */
    681 	return 0;
    682 }
    683 
    684 static void
    685 handle_channels_input(int ircfd, Channel *c)
    686 {
    687 	char buf[IRC_MSG_MAX];
    688 
    689 	if (read_line(c->fdin, buf, sizeof(buf)) == -1) {
    690 		if (channel_reopen(c) == -1)
    691 			channel_rm(c);
    692 		return;
    693 	}
    694 	proc_channels_input(ircfd, c, buf);
    695 }
    696 
    697 static void
    698 handle_server_output(int ircfd)
    699 {
    700 	char buf[IRC_MSG_MAX];
    701 
    702 	if (read_line(ircfd, buf, sizeof(buf)) == -1) {
    703 		fprintf(stderr, "%s: remote host closed connection: %s\n",
    704 		        argv0, strerror(errno));
    705 		exit(1);
    706 	}
    707 	fprintf(stdout, "%lu %s\n", (unsigned long)time(NULL), buf);
    708 	fflush(stdout);
    709 	proc_server_cmd(ircfd, buf);
    710 }
    711 
    712 static void
    713 sighandler(int sig)
    714 {
    715 	if (sig == SIGTERM || sig == SIGINT)
    716 		isrunning = 0;
    717 }
    718 
    719 static void
    720 setup(void)
    721 {
    722 	struct sigaction sa;
    723 
    724 	memset(&sa, 0, sizeof(sa));
    725 	sa.sa_handler = sighandler;
    726 	sigaction(SIGTERM, &sa, NULL);
    727 	sigaction(SIGINT, &sa, NULL);
    728 }
    729 
    730 static void
    731 run(int ircfd, const char *host)
    732 {
    733 	Channel *c, *tmp;
    734 	fd_set rdset;
    735 	struct timeval tv;
    736 	char ping_msg[IRC_MSG_MAX];
    737 	int r, maxfd;
    738 
    739 	snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
    740 	while (isrunning) {
    741 		maxfd = ircfd;
    742 		FD_ZERO(&rdset);
    743 		FD_SET(ircfd, &rdset);
    744 		for (c = channels; c; c = c->next) {
    745 			if (c->fdin > maxfd)
    746 				maxfd = c->fdin;
    747 			FD_SET(c->fdin, &rdset);
    748 		}
    749 		memset(&tv, 0, sizeof(tv));
    750 		tv.tv_sec = 120;
    751 		r = select(maxfd + 1, &rdset, 0, 0, &tv);
    752 		if (r < 0) {
    753 			if (errno == EINTR)
    754 				continue;
    755 			fprintf(stderr, "%s: select: %s\n", argv0, strerror(errno));
    756 			exit(1);
    757 		} else if (r == 0) {
    758 			if (time(NULL) - last_response >= PING_TIMEOUT) {
    759 				channel_print(channelmaster, "-!- ii shutting down: ping timeout");
    760 				exit(2); /* status code 2 for timeout */
    761 			}
    762 			ewritestr(ircfd, ping_msg);
    763 			continue;
    764 		}
    765 		if (FD_ISSET(ircfd, &rdset)) {
    766 			handle_server_output(ircfd);
    767 			last_response = time(NULL);
    768 		}
    769 		for (c = channels; c; c = tmp) {
    770 			tmp = c->next;
    771 			if (FD_ISSET(c->fdin, &rdset))
    772 				handle_channels_input(ircfd, c);
    773 		}
    774 	}
    775 }
    776 
    777 int
    778 main(int argc, char *argv[])
    779 {
    780 	Channel *c, *tmp;
    781 	struct passwd *spw;
    782 	const char *key = NULL, *fullname = NULL, *host = "";
    783 	const char *uds = NULL, *service = "6667";
    784 	char prefix[PATH_MAX];
    785 	int ircfd, r;
    786 
    787 	/* use nickname and home dir of user by default */
    788 	if (!(spw = getpwuid(getuid()))) {
    789 		fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno));
    790 		exit(1);
    791 	}
    792 	strlcpy(nick, spw->pw_name, sizeof(nick));
    793 	snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir);
    794 
    795 	ARGBEGIN {
    796 	case 'f':
    797 		fullname = EARGF(usage());
    798 		break;
    799 	case 'i':
    800 		strlcpy(prefix, EARGF(usage()), sizeof(prefix));
    801 		break;
    802 	case 'k':
    803 		key = getenv(EARGF(usage()));
    804 		break;
    805 	case 'n':
    806 		strlcpy(nick, EARGF(usage()), sizeof(nick));
    807 		break;
    808 	case 'p':
    809 		service = EARGF(usage());
    810 		break;
    811 	case 's':
    812 		host = EARGF(usage());
    813 		break;
    814 	case 'u':
    815 		uds = EARGF(usage());
    816 		break;
    817 	default:
    818 		usage();
    819 		break;
    820 	} ARGEND
    821 
    822 	if (!*host)
    823 		usage();
    824 
    825 	if (uds)
    826 		ircfd = udsopen(uds);
    827 	else
    828 		ircfd = tcpopen(host, service);
    829 
    830 #ifdef __OpenBSD__
    831 	/* OpenBSD pledge(2) support */
    832 	if (pledge("stdio rpath wpath cpath dpath", NULL) == -1) {
    833 		fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno));
    834 		exit(1);
    835 	}
    836 #endif
    837 
    838 	r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host);
    839 	if (r < 0 || (size_t)r >= sizeof(ircpath)) {
    840 		fprintf(stderr, "%s: path to irc directory too long\n", argv0);
    841 		exit(1);
    842 	}
    843 	create_dirtree(ircpath);
    844 
    845 	channelmaster = channel_add(""); /* master channel */
    846 	if (key)
    847 		loginkey(ircfd, key);
    848 	loginuser(ircfd, host, fullname && *fullname ? fullname : nick);
    849 	setup();
    850 	run(ircfd, host);
    851 	if (channelmaster)
    852 		channel_leave(channelmaster);
    853 
    854 	for (c = channels; c; c = tmp) {
    855 		tmp = c->next;
    856 		channel_leave(c);
    857 	}
    858 
    859 	return 0;
    860 }