ii

My fork of https://tools.suckless.org/ii/
git clone https://git.inz.fi/ii
Log | Files | Refs | README | LICENSE

ii.c (20056B)


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