ii

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

ii.c (20148B)


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