ticl

tiny irc channel linker
git clone git://git.ircforever.org/ticl
Log | Files | Refs | Submodules | README | LICENSE

main.c (26468B)


      1 /*
      2  * This work is dedicated to the public domain.
      3  * See COPYING file for more information.
      4  */
      5 
      6 #include <errno.h>
      7 #include <execinfo.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <signal.h>
     12 #include <time.h>
     13 #include <unistd.h>
     14 
     15 #ifdef __gnu_linux__
     16 #include <sys/epoll.h>
     17 #else
     18 #include <sys/event.h>
     19 #endif
     20 
     21 #define LOG_LEVEL	1
     22 
     23 #include "htable.h"
     24 #include "util.c"
     25 
     26 #define CLONE_COOLDOWN	1
     27 #define CLONE_ADDEND	10
     28 #define RECONN_TIME	10
     29 
     30 #define FD_ADDEND	100
     31 #define NET_ADDEND	10
     32 #define USER_ADDEND	100
     33 
     34 #define BUFSIZE		1024
     35 #define NICK_LEN	16
     36 #define HANDLE_EVENTS	10	/* no. of events to handle at a time */
     37 
     38 #define EV_READ		1
     39 #define EV_WRITE	2
     40 
     41 enum {
     42 	IDLE = 0,
     43 	RESET,
     44 	CLONING,
     45 	EXIT
     46 };
     47 
     48 struct fd_data {
     49 	char		*user;	/* nick		*/
     50 	int		netid;	/* net index	*/
     51 	int	 	suffix;	/* suffix count	*/
     52 	int	 	ready;	/* joined	*/
     53 };
     54 
     55 struct fd_ref {
     56 	char		*user;	/* nick		*/
     57 	int		netid;	/* net index	*/
     58 	struct fd_ref	*next;	/* next node	*/
     59 };
     60 
     61 struct network {
     62 	int		 fd;	/* fd		*/
     63 	char		*name;	/* name		*/
     64 	char		*symb;	/* symbol	*/
     65 	char		*host;	/* host		*/
     66 	char		*port;	/* port		*/
     67 	char		*chan;	/* channel	*/
     68 };
     69 
     70 static time_t		 ntime = -1;	/* next timeout		*/
     71 static int		 state = IDLE;	/* program state 	*/
     72 static int		 fifofd;	/* fifo fd		*/
     73 static int		 break_evloop;	/* break event loop	*/
     74 static char		*fifopath;	/* fifo path		*/
     75 static char		 msg[BUFSIZE];	/* message buffer	*/
     76 
     77 #ifdef __gnu_linux__
     78 static int		 epfd;		/* epoll instance	*/
     79 #else
     80 static int		 kqfd;		/* kqueue instance	*/
     81 #endif
     82 
     83 static struct network	 *networks;	/* networks array	*/
     84 static int		  netlen;	/* array length		*/
     85 static int		  netcap;	/* array capacity	*/
     86 static struct fd_data	**fdtodata;	/* fd -> data pointer	*/
     87 static int		  fdcap;	/* fdtodata capacity	*/
     88 static struct htable	 *usertofds;	/* user -> array of fds of clones
     89 					indexed according to networks array */
     90 
     91 static struct fd_ref	 *reconn_list_head, /* re-connection queue */
     92 			 *reconn_list_tail;
     93 
     94 /* functions prototype */
     95 void	 fd_register(int, int);
     96 void	 fifo_read(void);
     97 time_t	 event_timeout(void);
     98 void	 fd_write(int);
     99 void	 fd_read(int);
    100 void	 net_add(char *, char *, char *, char *, char *);
    101 void	 net_del(int, char *, int);
    102 void	 user_add(char *, int, int);
    103 void	 user_del(char *, char *);
    104 void	 reconn_list_add(char *, int);
    105 void	 reconn_list_del(char *, int);
    106 int	 fd_new(int, char *);
    107 void	 fd_del(int, char *, int);
    108 void	 fd_reconn(int, char *);
    109 int	*clone_get_fds(char *, int);
    110 void	 nick_add_symb(char *, int);
    111 void	 privmsg_update(char *, char *, int, int);
    112 void	 print_table(void);
    113 void	 print_htable(void);
    114 void	 print_users(void);
    115 void	 print_reconn_list(void);
    116 void	 print_border(void);
    117 ssize_t	 writeall(int, char *);
    118 
    119 int
    120 main(int argc, char *argv[])
    121 {
    122 #ifdef __gnu_linux__
    123 	struct epoll_event	epevs[HANDLE_EVENTS];
    124 #else
    125 	struct kevent		kevs[HANDLE_EVENTS];
    126 	struct timespec		tmsp;
    127 #endif
    128 	int	i, fd, nev;	/* no. of returned events to handle */
    129 	time_t	timeout;
    130 
    131 	/* set stdout to unbufferd */
    132 	setvbuf(stdout, NULL, _IONBF, 0);
    133 
    134 	/* handle arguments */
    135 	if (argc != 2)
    136 		fatal("usage: ticl fifo");
    137 	else
    138 		fifopath = argv[1];
    139 
    140 	/* init global variables */
    141 	netcap = NET_ADDEND;
    142 	fdcap = FD_ADDEND;
    143 	networks = emalloc((size_t)netcap * sizeof(struct network));
    144 	fdtodata = emalloc((size_t)fdcap * sizeof(struct fd_data *));
    145 	usertofds = htcreate(hash_str, (key_cmp_fn *)strcmp, free, free,
    146 			USER_ADDEND);
    147 
    148 #ifdef __gnu_linux__
    149 	if ((epfd = epoll_create1(0)) == -1)
    150 		fatal("epoll_create1:");
    151 #else
    152 	if ((kqfd = kqueue()) == -1)
    153 		fatal("kqueue:");
    154 #endif
    155 	fifofd = fifo_open(fifopath);
    156 	fd_register(fifofd, EV_READ);
    157 
    158 	/* event loop */
    159 	while (state != EXIT) {
    160 		if (ntime == -1) {
    161 			timeout = -1;
    162 		} else {
    163 			timeout = ntime - time(NULL);
    164 			if (timeout < 0)
    165 				timeout = 0;
    166 		}
    167 #ifdef __gnu_linux__
    168 		nev = epoll_wait(epfd, epevs, HANDLE_EVENTS, (int)timeout * 1000);
    169 		if (nev == -1)
    170 			fatal("epoll_wait:");
    171 #else
    172 		tmsp.tv_sec = timeout;
    173 		tmsp.tv_nsec = 0;
    174 		nev = kevent(kqfd, NULL, 0, kevs, HANDLE_EVENTS, timeout < 0 ? NULL : &tmsp);
    175 		if (nev == -1)
    176 			fatal("kevent:");
    177 #endif
    178 		else if (nev == 0) {
    179 			ntime = event_timeout();
    180 			continue;
    181 		}
    182 
    183 		for (i = 0; i < nev; i++) {
    184 #ifdef __gnu_linux__
    185 			fd = epevs[i].data.fd;
    186 #else
    187 			fd = (int)kevs[i].ident;
    188 #endif
    189 			if (fd == fifofd) {
    190 				fifo_read();
    191 				break;
    192 #ifdef __gnu_linux__
    193 			} else if (epevs[i].events & EPOLLOUT) {
    194 #else
    195 			} else if (kevs[i].filter == EVFILT_WRITE) {
    196 #endif
    197 				fd_write(fd);
    198 #ifdef __gnu_linux__
    199 			} else if (epevs[i].events & EPOLLIN) {
    200 #else
    201 			} else if (kevs[i].filter == EVFILT_READ) {
    202 #endif
    203 				fd_read(fd);
    204 			} else {
    205 				fatal("unknown event");
    206 			}
    207 
    208 			if (reconn_list_head != NULL && ntime == -1)
    209 				ntime = time(0) + RECONN_TIME;
    210 			if (break_evloop) {
    211 				break_evloop = FALSE;
    212 				break;
    213 			}
    214 		}
    215 	}
    216 
    217 	/*
    218 	 * delete and free all the networks with their users
    219 	 *	- delete in reverse order to prevent swapping.
    220 	 */
    221 	snprintf(msg, sizeof(msg), "QUIT :relay shutting down\r\n");
    222 	for (i = netlen-1; i >= 0; i--)
    223 		net_del(i, msg, FALSE);
    224 
    225 	free(networks);
    226 	free(fdtodata);
    227 	htdestroy(usertofds);
    228 	return 0;
    229 }
    230 
    231 void
    232 fd_register(int fd, int mode)
    233 {
    234 #ifdef __gnu_linux__
    235 	struct epoll_event ev;
    236 
    237 	if (mode == EV_READ)
    238 		ev.events = EPOLLIN;
    239 	else if (mode == EV_WRITE)
    240 		ev.events = EPOLLOUT;
    241 	else
    242 		fatal("unknown event mode");
    243 
    244 	ev.data.fd = fd;
    245 	if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1)
    246 		fatal("epoll_ctl:");
    247 #else
    248 	struct kevent ev;
    249 	int filter;
    250 
    251 	if (mode == EV_READ)
    252 		filter = EVFILT_READ;
    253 	else if (mode == EV_WRITE)
    254 		filter = EVFILT_WRITE;
    255 	else
    256 		fatal("unknown event mode");
    257 
    258 	EV_SET(&ev, fd, filter, EV_ADD, 0, 0, 0);
    259 	if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1)
    260 		fatal("kevent:");
    261 #endif
    262 }
    263 
    264 void
    265 fifo_read(void)
    266 {
    267 	char	 buffer[BUFSIZE];
    268 	char	*buf;
    269 	char	*cmd;
    270 	ssize_t	 n;
    271 
    272 	n = readline(fifofd, buffer, sizeof(buffer));
    273 	if (n == -1) {
    274 		fatal("read:");
    275 	} else if (n == 0) {
    276 		/* reopen fifo again */
    277 		close(fifofd);
    278 		fifofd = fifo_open(fifopath);
    279 		fd_register(fifofd, EV_READ);
    280 		return;
    281 	}
    282 	if (*buffer == '\0')
    283 		return;
    284 
    285 	buf = buffer;
    286 	cmd = split(&buf, ' ');
    287 	if (strcmp(cmd, "netadd") == 0) {
    288 		char *name = split(&buf, ' ');
    289 		char *symb = split(&buf, ' ');
    290 		char *host = split(&buf, ' ');
    291 		char *port = split(&buf, ' ');
    292 		char *chan = buf;
    293 		if (!*name || !*symb || !*host || !*port || !*chan)
    294 			warnf("usage: netadd <name> <symbol> <host> <port> <channel>");
    295 		else
    296 			net_add(name, symb, host, port, chan);
    297 	} else if (strcmp(cmd, "netdel") == 0) {
    298 		char *name = buf;
    299 		if (!*name) {
    300 			warnf("usage: netdel <name>");
    301 		} else {
    302 			int i;
    303 			for (i = 0; i < netlen; i++) {
    304 				if (strcmp(name, networks[i].name) == 0) {
    305 					snprintf(msg, sizeof(msg), "QUIT :netdel: %s\r\n", networks[i].name);
    306 					net_del(i, msg, FALSE);
    307 					return;
    308 				}
    309 			}
    310 			warnf("%s: network doesn't exist", name);
    311 		}
    312 	} else if (strcmp(cmd, "print") == 0) {
    313 		print_table();
    314 	} else if (strcmp(cmd, "htable") == 0) {
    315 		print_htable();
    316 	} else if (strcmp(cmd, "users") == 0) {
    317 		print_users();
    318 	} else if (strcmp(cmd, "reconn") == 0) {
    319 		print_reconn_list();
    320 	} else if (strcmp(cmd, "exit") == 0) {
    321 		state = EXIT;
    322 	} else {
    323 		warnf("%s is not a command", cmd);
    324 	}
    325 }
    326 
    327 time_t
    328 event_timeout(void)
    329 {
    330 	static struct htiter it;
    331 
    332 	int i, j, *fds;
    333 	char *user;
    334 
    335 	if (state == IDLE) {
    336 		struct fd_ref *tmp;
    337 		debug(1, "Reconnecting");
    338 		while(reconn_list_head != NULL) {
    339 			user = reconn_list_head->user;
    340 			i = reconn_list_head->netid;
    341 
    342 			if (user == NULL) {
    343 				networks[i].fd = fd_new(i, user);
    344 			} else {
    345 				if ((fds = htsearch(usertofds, user)) == NULL)
    346 					fatal("%s: user doesn't exist", user);
    347 				if (fds[i] == -3 &&
    348 				    networks[i].fd > 0 &&
    349 				    fdtodata[networks[i].fd]->ready)
    350 					fds[i] = fd_new(i, user);
    351 			}
    352 			tmp = reconn_list_head;
    353 			reconn_list_head = reconn_list_head->next;
    354 			free(tmp);
    355 		}
    356 		reconn_list_tail = reconn_list_head;
    357 		return -1;
    358 	} else if (state == RESET) {
    359 		state = CLONING;
    360 		htiter_init(&it);
    361 	}
    362 	debug(1, ".");
    363 
    364 	j = 0;
    365 	while (htiterate(usertofds, &it)) {
    366 		user = (char *)it.node->key;
    367 		fds = (int *)it.node->val;
    368 		for (i = 0; i < netlen; i++) {
    369 			if (fds[i] == 0 && networks[i].fd > 0 &&
    370 					fdtodata[networks[i].fd]->ready)
    371 				fds[i] = fd_new(i, user);
    372 		}
    373 		j++;
    374 		if (j >= CLONE_ADDEND)
    375 			return time(NULL) + CLONE_COOLDOWN;
    376 	}
    377 	state = IDLE;
    378 	return -1;
    379 }
    380 
    381 void
    382 fd_write(int fd)
    383 {
    384 #ifdef __gnu_linux__
    385 	struct epoll_event ev;
    386 	ev.events = EPOLLIN;
    387 	ev.data.fd = fd;
    388 	if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1)
    389 		fatal("epoll_ctl:");
    390 #else
    391 	struct kevent ev;
    392 	EV_SET(&ev, fd, EVFILT_WRITE, EV_DISABLE, 0, 0, 0);
    393 	if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1)
    394 		fatal("kevent:");
    395 	fd_register(fd, EV_READ);
    396 #endif
    397 	if (fdtodata[fd]->user == NULL) {	/* watcher */
    398 		snprintf(msg, sizeof(msg), "NICK watcher\r\nUSER watcher 0 * :watcher\r\n");
    399 		writeall(fd, msg);
    400 	} else {			/* user */
    401 		snprintf(msg, sizeof(msg), "NICK %s\r\nUSER user 0 * :user\r\n", fdtodata[fd]->user);
    402 		writeall(fd, msg);
    403 	}
    404 }
    405 
    406 void
    407 fd_read(int fd)
    408 {
    409 	char	 buffer	[BUFSIZE];
    410 	char	 backup	[BUFSIZE];
    411 	char	 wnick	[NICK_LEN];	/* watcher nick */
    412 	char	*buf;
    413 	char	*cmd;
    414 	char	*nick;
    415 	int	 i, netid;
    416 	ssize_t	 n;
    417 
    418 	netid = fdtodata[fd]->netid;
    419 
    420 	n = readline(fd, buffer, sizeof(buffer));
    421 	if (n == -1) {
    422 		warnf("%d: read:", fd);
    423 		snprintf(msg, sizeof(msg), "QUIT :read failed\r\n");
    424 		fd_reconn(fd, msg);
    425 		return;
    426 	} else if (n == 0) {
    427 		warnf("%d: read: connection closed", fd);
    428 		snprintf(msg, sizeof(msg), "QUIT :connection closed\r\n");
    429 		fd_reconn(fd, msg);
    430 		return;
    431 	}
    432 	if (*buffer == '\0')
    433 		return;
    434 
    435 	/* clone the buffer */
    436 	strlcpy(backup, buffer, sizeof(backup));
    437 	buf = buffer;
    438 
    439 	/* set watcher nick */
    440 	strlcpy(wnick, "watcher", sizeof(wnick));
    441 	for (i = 0; i < fdtodata[fd]->suffix; i++)
    442 		strcat(wnick, "_");
    443 
    444 	/* first column */
    445 	cmd = split(&buf, ' ');
    446 	if (strcmp(cmd, "NOTICE") == 0) {	/* ignore */
    447 		return;
    448 	} else if (strcmp(cmd, "ERROR") == 0) {
    449 		warnf("%d: %s", fd, backup);
    450 		snprintf(msg, sizeof(msg), "QUIT :ERROR\r\n");
    451 		fd_reconn(fd, msg);
    452 		return;
    453 	} else if (strcmp(cmd, "PING") == 0) {
    454 		snprintf(msg, sizeof(msg), "PONG %s\r\n", buf);
    455 		writeall(fd, msg);
    456 		return;
    457 	}
    458 	/* strip nick from first column */
    459 	nick = split(&cmd, '!');
    460 	if (nick[0] == ':')
    461 		nick++;
    462 
    463 	/* second column */
    464 	cmd = split(&buf, ' ');
    465 
    466 	/* ignore all the info messages */
    467 	if ((strcmp(cmd, "002") == 0) ||
    468 	    (strcmp(cmd, "003") == 0) ||
    469 	    (strcmp(cmd, "004") == 0) ||
    470 	    (strcmp(cmd, "005") == 0) ||
    471 	    (strcmp(cmd, "251") == 0) ||
    472 	    (strcmp(cmd, "252") == 0) ||
    473 	    (strcmp(cmd, "253") == 0) || /* unknown connection(s) */
    474 	    (strcmp(cmd, "254") == 0) ||
    475 	    (strcmp(cmd, "255") == 0) ||
    476 	    (strcmp(cmd, "265") == 0) ||
    477 	    (strcmp(cmd, "266") == 0) ||
    478 	    (strcmp(cmd, "250") == 0) ||
    479 	    (strcmp(cmd, "332") == 0) ||
    480 	    (strcmp(cmd, "333") == 0) ||
    481 	    (strcmp(cmd, "375") == 0) ||
    482 	    (strcmp(cmd, "372") == 0) ||
    483 	    (strcmp(cmd, "376") == 0) ||
    484 	    (strcmp(cmd, "396") == 0) ||
    485 	    (strcmp(cmd, "366") == 0) ||
    486 	    (strcmp(cmd, "MODE") == 0) ||
    487 	    (strcmp(cmd, "TOPIC") == 0) ||
    488 	    (strcmp(cmd, "NOTICE") == 0)) {
    489 		return;
    490 	} else if (strcmp(cmd, "432") == 0) {	/* Erroneous Nickname */
    491 		fatal("%d: %s", fd, backup);
    492 	} else if (strcmp(cmd, "433") == 0) {	/* Nickname already in use */
    493 		split(&buf, ' ');
    494 		nick = split(&buf, ' ');
    495 		strcat(nick, "_");
    496 		fdtodata[fd]->suffix++;
    497 		if (strlen(nick) > NICK_LEN) {
    498 			warnf("%s: nickname too long", nick);
    499 			snprintf(msg, sizeof(msg), "QUIT :%s: nickname too long\r\n", nick);
    500 			if (fdtodata[fd]->user == NULL)
    501 				net_del(netid, msg, FALSE);
    502 			else
    503 				fd_del(fd, msg, FALSE);
    504 			return;
    505 		} else {
    506 			snprintf(msg, sizeof(msg), "NICK %s\r\n", nick);
    507 			writeall(fd, msg);
    508 		}
    509 		return;
    510 	} else if (strcmp(cmd, "001") == 0) {
    511 		snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan);
    512 		writeall(fd, msg);
    513 		return;
    514 	} else if (strcmp(cmd, "PRIVMSG") == 0) {
    515 		char privmsg[BUFSIZE] = "";
    516 		int *fds;
    517 
    518 		if (fdtodata[fd]->user == NULL) {	/* if watcher */
    519 			nick_add_symb(nick, netid);
    520 			if ((fds = htsearch(usertofds, nick)) == NULL)
    521 				return;
    522 
    523 			split(&buf, ':');	/* set buf to msg */
    524 			for (i = 0; i < netlen; i++) {
    525 				printf("%s\n", buf);
    526 				if (fds[i] > 0) {
    527 					privmsg_update(privmsg, buf, netid, fds[i]);
    528 					snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", networks[i].chan, privmsg);
    529 					writeall(fds[i], msg);
    530 				}
    531 			}
    532 		} else {
    533 			char *netsymb;
    534 			char *user = split(&buf, ' ');
    535 
    536 			/* ignore messages from channel (it is handled by watcher) */
    537 			if (user[0] == '#' || user[0] == '&')
    538 				return;
    539 
    540 			nick_add_symb(nick, netid);
    541 			if ((fds = htsearch(usertofds, nick)) == NULL)
    542 				return;
    543 
    544 			/* split user nick and network symbol */
    545 			*strrchr(user, ']') = '\0';
    546 			netsymb = strrchr(user, '[');
    547 			*netsymb++ = '\0';
    548 
    549 			/* get the network index */
    550 			for (i = 0; i < netlen; i++) {
    551 				if (strcmp(netsymb, networks[i].symb) == 0)
    552 					break;
    553 			}
    554 
    555 			split(&buf, ':');	/* set buf to msg */
    556 			privmsg_update(privmsg, buf, netid, fds[i]);
    557 			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg);
    558 			writeall(fds[i], msg);
    559 		}
    560 		return;
    561 	}
    562 	else if (strcmp(cmd, "353") == 0) {
    563 		fdtodata[fd]->ready = TRUE;
    564 		/* FALLBACK */
    565 	}
    566 
    567 	/* from now, the messages are handled by watcher */
    568 	if (fdtodata[fd]->user != NULL)	/* if clone */
    569 		return;
    570 
    571 	if (strcmp(cmd, "353") == 0) {
    572 		char *nick;
    573 		split(&buf, ':');
    574 		state = RESET;
    575 		ntime = 0;
    576 
    577 		/* then add all new users */
    578 		while (*(nick = split(&buf, ' ')) != '\0') {
    579 			if (*nick == '@' ||
    580 			    *nick == '&' ||
    581 			    *nick == '~' ||
    582 			    *nick == '%' ||
    583 			    *nick == '+' ||
    584 			    *nick == '\\')
    585 				nick++;
    586 			if (strcmp(nick, wnick) != 0)
    587 				user_add(nick, netid, FALSE);
    588 		}
    589 		return;
    590 	} else if (strcmp(cmd, "JOIN") == 0) {
    591 		/* if real user */
    592 		if ((strcmp(nick, wnick) != 0) &&
    593 		    (clone_get_fds(nick, netid) == NULL)) {
    594 			if (state != IDLE)
    595 				warnf("%s: ignored (network cloning)", nick);
    596 			else
    597 				user_add(nick, netid, TRUE);
    598 		}
    599 		return;
    600 	} else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) {
    601 		split(&buf, ':'); /* ignore ':' and assign QUIT/PART msg to buf */
    602 		snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf);
    603 		nick_add_symb(nick, netid);
    604 		if (htsearch(usertofds, nick) != NULL)
    605 			user_del(nick, msg);
    606 		return;
    607 	} else if (strcmp(cmd, "NICK") == 0) {
    608 		int *fds, i;
    609 		char *newnick;
    610 
    611 		nick_add_symb(nick, netid);
    612 		if ((fds = htsearch(usertofds, nick)) == NULL)
    613 			return;
    614 
    615 		/* set buf to new nick */
    616 		split(&buf, ':');
    617 		/* allocate a new nick with net symbol and replace the old one */
    618 		newnick = emalloc((strlen(buf) + strlen(networks[netid].symb) + 2 + 1) * sizeof(char));
    619 		sprintf(newnick, "%s[%s]", buf, networks[netid].symb);
    620 		snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick);
    621 		for (i = 0; i < netlen; i++) {
    622 			if (fds[i] > 0) {
    623 				fdtodata[fds[i]]->user = newnick;
    624 				fdtodata[fds[i]]->suffix = 0;
    625 				writeall(fds[i], msg);
    626 			} else if (fds[i] == -3) {
    627 				reconn_list_del(nick, i);
    628 				reconn_list_add(newnick, i);
    629 			}
    630 		}
    631 		htmodkey(usertofds, nick, newnick);
    632 		return;
    633 	} else if (strcmp(cmd, "KICK") == 0) {
    634 		/* :<user_who_kicked>!~username@host KICK <channel> <user_who_is_being_kicked> :<kick_msg> */
    635 		int *fds;
    636 		char *user;
    637 
    638 		split(&buf, ' ');		/* channel name				*/
    639 		user = split(&buf, ' ');	/* user who is being kicked		*/
    640 		split(&buf, ':');		/* ignore ':' and store reason to buf	*/
    641 
    642 		/* if watcher is being kicked, delete the network */
    643 		if (strcmp(user, wnick) == 0) {
    644 			snprintf(msg, sizeof(msg), "QUIT :netdel: %s (%s is kicked by %s)\r\n",
    645 					networks[netid].name, wnick, nick);
    646 			fd_reconn(fd, msg);
    647 			return;
    648 		}
    649 
    650 		/* if message is from a real user, delete the user */
    651 		if ((fds = clone_get_fds(user, netid)) == NULL) {
    652 			snprintf(msg, sizeof(msg), "QUIT :userdel: %s (kicked by %s)\r\n", user, nick);
    653 			nick_add_symb(user, netid);
    654 			user_del(user, msg);
    655 			return;
    656 		}
    657 
    658 		/* delete the kicked clone */
    659 		snprintf(msg, sizeof(msg), "QUIT :kicked by %s\r\n", nick);
    660 		fd_reconn(fds[netid], msg);
    661 
    662 		/* get the original user netid */
    663 		for (i = 0; i < netlen; i++) {
    664 			if (fds[i] == -1)
    665 				break;
    666 		}
    667 		/* send notice in the channel through watcher */
    668 		if (networks[i].fd > 0) {
    669 			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s is kicked by %s [%s]\r\n",
    670 					networks[i].chan, user, nick, buf);
    671 			writeall(networks[i].fd, msg);
    672 		}
    673 		return;
    674 	}
    675 	warnf("%d: %s", fd, backup);
    676 	return;
    677 }
    678 
    679 void
    680 net_add(char *name, char *symb, char *host, char *port, char *chan)
    681 {
    682 	struct network *n;
    683 	int i;
    684 
    685 	for (i = 0; i < netlen; i++) {
    686 		if (strcmp(networks[i].name, name) == 0) {
    687 			warnf("%s: network name already exists", name);
    688 			return;
    689 		}
    690 		if (strcmp(networks[i].symb, symb) == 0) {
    691 			warnf("%s: network symbol already exists", symb);
    692 			return;
    693 		}
    694 		if ((strcmp(networks[i].host, host) == 0) &&
    695 		    (strcmp(networks[i].port, port) == 0) &&
    696 		    (strcmp(networks[i].chan, chan) == 0)) {
    697 			warnf("%s:%s%s: network configuration already exists",
    698 			    host, port, chan);
    699 			return;
    700 		}
    701 	}
    702 
    703 	/* if full, resize the network and user fds */
    704 	if (netlen == netcap) {
    705 		struct htiter it;
    706 		htiter_init(&it);
    707 
    708 		networks = erealloc(networks, (size_t)(netcap + NET_ADDEND) *
    709 				sizeof(struct network));
    710 		while (htiterate(usertofds, &it)) {
    711 			it.node->val = erealloc(it.node->val,
    712 					(size_t)(netcap + NET_ADDEND) *
    713 					sizeof(int));
    714 			/* zero out the extended array */
    715 			for (i = netcap; i < netcap + NET_ADDEND; i++)
    716 				((int *)it.node->val)[i] = 0;
    717 		}
    718 		netcap += NET_ADDEND;
    719 	}
    720 
    721 	/* add a network */
    722 	n = &networks[netlen];
    723 	n->name = strdup(name);
    724 	n->symb = strdup(symb);
    725 	n->host = strdup(host);
    726 	n->port = strdup(port);
    727 	n->chan = strdup(chan);
    728 	n->fd	= fd_new(netlen, NULL);
    729 	netlen++;
    730 }
    731 
    732 void
    733 net_del(int netid, char *msg, int reconnect)
    734 {
    735 	int *fds;
    736 	char *user;
    737 	struct network *n;
    738 	struct htiter lit, it;	/* last, current iterator */
    739 
    740 	n = &networks[netid];
    741 	htiter_init(&it);
    742 	htiter_init(&lit);
    743 	while (htiterate(usertofds, &it)) {
    744 		user = (char *)it.node->key;
    745 		fds = (int *)it.node->val;
    746 		if (fds[netid] == -1) {
    747 			user_del(it.node->key, msg);
    748 			it = lit; /* this node is deleted */
    749 		} else {
    750 			if (fds[netid] > 0)
    751 				fd_del(fds[netid], msg, FALSE);
    752 			else if (fds[netid] == -3)
    753 				reconn_list_del(user, netid);
    754 
    755 			if (!reconnect) {
    756 				if(netid != netlen-1) {
    757 					fds[netid] = fds[netlen-1];
    758 					if (fds[netid] > 0) {
    759 						fdtodata[fds[netid]]->netid = netid;
    760 					} else if (fds[netid] == -3) {
    761 						reconn_list_del(user, netlen-1);
    762 						reconn_list_add(user, netid);
    763 					}
    764 				}
    765 				fds[netlen-1] = 0;
    766 			} else {
    767 				fds[netid] = 0;
    768 			}
    769 		}
    770 		lit = it;
    771 	}
    772 
    773 	if (n->fd > 0)
    774 		fd_del(n->fd, msg, reconnect);
    775 	else if (n->fd == -3)
    776 		reconn_list_del(NULL, netid);
    777 
    778 	if (!reconnect) {
    779 		free(n->name);
    780 		free(n->symb);
    781 		free(n->host);
    782 		free(n->port);
    783 		free(n->chan);
    784 		/* swap */
    785 		if(netid != netlen-1) {
    786 			networks[netid] = networks[netlen-1];
    787 			if (networks[netid].fd > 0) {
    788 				fdtodata[n->fd]->netid = netid;
    789 			} else if (networks[netid].fd == -3) {
    790 				reconn_list_del(NULL, netlen-1);
    791 				reconn_list_add(NULL, netid);
    792 			}
    793 		}
    794 		netlen--;
    795 	}
    796 }
    797 
    798 void
    799 user_add(char *unick, int netid, int clone)
    800 {
    801 	size_t	 len;
    802 	char	*nick;
    803 	int	*fds;
    804 
    805 	len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1;
    806 	if (len-1 > NICK_LEN) {
    807 		warnf("%s[%s]: nickname too long", unick, networks[netid].symb);
    808 		return;
    809 	}
    810 
    811 	/* resize hash table if storage is low */
    812 	if ((usertofds->cap - usertofds->len) < USER_ADDEND)
    813 		usertofds = htresize(usertofds, usertofds->cap + USER_ADDEND);
    814 
    815 	/* allocate a new user */
    816 	nick = emalloc(len * sizeof(char));
    817 	fds = ecalloc((size_t)netcap, sizeof(int));
    818 	sprintf(nick, "%s[%s]", unick, networks[netid].symb);
    819 	fds[netid] = -1;
    820 
    821 	if (clone) {
    822 		int i;
    823 		for (i = 0; i < netlen; i++) {
    824 			if (fds[i] == 0 && networks[i].fd > 0 &&
    825 					fdtodata[networks[i].fd]->ready)
    826 				fds[i] = fd_new(i, nick);
    827 		}
    828 	}
    829 
    830 	if (htinsert(usertofds, nick, fds) == -1)
    831 		fatal("%s: user already exists", nick);
    832 }
    833 
    834 void
    835 user_del(char *user, char *msg)
    836 {
    837 	int i, *fds;
    838 
    839 	if ((fds = htsearch(usertofds, user)) == NULL)
    840 		fatal("%s: user doesn't exist", user);
    841 	for (i = 0; i < netlen; i++) {
    842 		if (fds[i] > 0) {
    843 			fd_del(fds[i], msg, FALSE);
    844 		} else if (fds[i] == -3) {
    845 			reconn_list_del(user, i);
    846 		}
    847 	}
    848 	htremove(usertofds, user);
    849 }
    850 
    851 void
    852 reconn_list_add(char *user, int netid)
    853 {
    854 	struct fd_ref *node;
    855 
    856 	node = emalloc(sizeof(struct fd_ref));
    857 	node->user = user;
    858 	node->netid = netid;
    859 	node->next = NULL;
    860 	if (reconn_list_tail == NULL) {
    861 		reconn_list_head = reconn_list_tail = node;
    862 	} else {
    863 		reconn_list_tail->next = node;
    864 		reconn_list_tail = reconn_list_tail->next;
    865 	}
    866 }
    867 
    868 void
    869 reconn_list_del(char *user, int netid)
    870 {
    871 	struct fd_ref *n, *pn;	/* current and previous node */
    872 
    873 	n = reconn_list_head;
    874 	pn = NULL;
    875 	while (n != NULL) {
    876 		if (n->netid == netid &&
    877 		    ((user == NULL && n->user == NULL) ||
    878 		     (user != NULL && n->user != NULL &&
    879 		      (strcmp(n->user, user) == 0)))) {
    880 			if (n == reconn_list_head && n == reconn_list_tail)
    881 				reconn_list_head = reconn_list_tail = NULL;
    882 			else if (n == reconn_list_head)
    883 				reconn_list_head = n->next;
    884 			else if (n == reconn_list_tail)
    885 				reconn_list_tail = pn;
    886 			else
    887 				pn->next = n->next;
    888 			free(n);
    889 			return;
    890 		}
    891 		pn = n;
    892 		n = n->next;
    893 	}
    894 	fatal("%d:%s: failed to find in re-connection list", netid, user);
    895 }
    896 
    897 int
    898 fd_new(int netid, char *user)
    899 {
    900 	int fd;
    901 	struct network *n;
    902 	struct fd_data *data;
    903 
    904 	n = &networks[netid];
    905 	fd = dial(n->host, n->port);
    906 	if (fd == -1) {
    907 		warnf("%s:%s: failed to connect", n->host, n->port);
    908 		return -2;
    909 	}
    910 	fd_register(fd, EV_WRITE);
    911 
    912 	if (user == NULL)
    913 		debug(1, "%d: netadd: %s[%s]", fd, n->name, n->symb);
    914 	else
    915 		debug(1, "%d: add[%s]: %s", fd, n->symb, user);
    916 
    917 	if (fd+1 > fdcap) {
    918 		fdcap *= 2;
    919 		fdtodata = erealloc(fdtodata, (size_t)fdcap * sizeof(struct fd_data *));
    920 	}
    921 
    922 	data = emalloc(1 * sizeof(struct fd_data));
    923 	data->netid = netid;
    924 	data->user = user;
    925 	data->suffix = 0;
    926 	data->ready = FALSE;
    927 
    928 	fdtodata[fd] = data;
    929 
    930 	return fd;
    931 }
    932 
    933 void
    934 fd_del(int fd, char *msg, int reconnection)
    935 {
    936 	char	*user;
    937 	int	 netid;
    938 	int	*fds;
    939 
    940 	user = fdtodata[fd]->user;
    941 	netid = fdtodata[fd]->netid;
    942 
    943 	if (user == NULL) {
    944 		debug(1, "%d: netdel: %s[%s]", fd, networks[netid].name, networks[netid].symb);
    945 		networks[netid].fd = reconnection ? -3 : -2;
    946 	} else {
    947 		debug(1, "%d: del[%s]: %s", fd, networks[netid].symb, user);
    948 		if ((fds = htsearch(usertofds, user)) == NULL)
    949 			fatal("%s: user doesn't exist", user);
    950 		fds[netid] = reconnection ? -3 : -2;
    951 	}
    952 
    953 	if (fdtodata[fd]->ready)
    954 		writeall(fd, msg);
    955 	close(fd);
    956 
    957 	if (reconnection)
    958 		reconn_list_add(user, netid);
    959 	free(fdtodata[fd]);
    960 	fdtodata[fd] = NULL;
    961 	break_evloop = TRUE;
    962 }
    963 
    964 void
    965 fd_reconn(int fd, char *msg)
    966 {
    967 	if (fdtodata[fd]->user == NULL)
    968 		net_del(fdtodata[fd]->netid, msg, TRUE);
    969 	else
    970 		fd_del(fd, msg, TRUE);
    971 }
    972 
    973 int *
    974 clone_get_fds(char *nick, int netid)
    975 {
    976 	unsigned int s;
    977 	int *fds = NULL;
    978 
    979 	/* count suffix */
    980 	for (s = 0; nick[strlen(nick)-s-1] == '_'; s++);
    981 	/* remove suffix */
    982 	if (s > 0)
    983 		nick[strlen(nick)-s] = '\0';
    984 
    985 	fds = htsearch(usertofds, nick);
    986 	/* if match but suffix doesn't match */
    987 	if ((fds != NULL) && (fdtodata[fds[netid]]->suffix != (int)s))
    988 		fds = NULL;
    989 
    990 	/* add suffix back */
    991 	if (s > 0)
    992 		nick[strlen(nick)] = '_';
    993 
    994 	return fds;
    995 }
    996 
    997 void
    998 nick_add_symb(char *nick, int netid)
    999 {
   1000 	strcat(nick, "[");
   1001 	strcat(nick, networks[netid].symb);
   1002 	strcat(nick, "]");
   1003 }
   1004 
   1005 /*
   1006  * trim all the nicknames to original nick
   1007  * src will be destructed
   1008  */
   1009 void
   1010 privmsg_update(char *dst, char *src, int netid, int fd)
   1011 {
   1012 	char d;		/* delimiter */
   1013 	char *n;
   1014 	int i, *fds;
   1015 
   1016 	while (src != NULL) {
   1017 		n = strpbrk(src, " :;,<>@&~%+\\");
   1018 		if (n == NULL) {
   1019 			d = '\0';
   1020 		} else {
   1021 			d = *n;
   1022 			*n = '\0';
   1023 			n++;
   1024 		}
   1025 
   1026 		/* check if the word is nick */
   1027 		if ((fds = clone_get_fds(src, netid)) != NULL) {
   1028 			int netid2 = fdtodata[fd]->netid;
   1029 			*strrchr(src, '[') = '\0';
   1030 			src[strlen(src)] = '[';
   1031 			if (fds[netid2] > 0) {
   1032 				nick_add_symb(dst, netid2);
   1033 				for (i = 0; i < fdtodata[fds[netid2]]->suffix; i++)
   1034 					strcat(dst, "_");
   1035 			}
   1036 		} else {
   1037 			strcat(dst, src);
   1038 		}
   1039 
   1040 		strncat(dst, &d, 1);
   1041 		src = n;
   1042 	}
   1043 }
   1044 
   1045 void
   1046 print_table(void)
   1047 {
   1048 	int	i, *fds, diff, tabs;
   1049 	char	*nick;
   1050 	struct htiter it;
   1051 
   1052 	if (netlen == 0)
   1053 		return;
   1054 
   1055 	print_border();
   1056 	/* print networks */
   1057 	printf("Networks\t\t");
   1058 	for (i = 0; i < netlen; i++) {
   1059 		printf("%s->%d", networks[i].symb, networks[i].fd);
   1060 		/* print suffix */
   1061 		if (networks[i].fd > 0 && fdtodata[networks[i].fd]->suffix > 0)
   1062 			printf("(%d)\t", fdtodata[networks[i].fd]->suffix);
   1063 		else
   1064 			printf("\t\t");
   1065 	}
   1066 	printf("\n");
   1067 
   1068 	htiter_init(&it);
   1069 	while (htiterate(usertofds, &it)) {
   1070 		fds  = (int *)it.node->val;
   1071 		nick = (char *)it.node->key;
   1072 		/* print tabbed user nick */
   1073 		printf("%s", nick);
   1074 		diff = 24 - (int)strlen(nick);
   1075 		tabs = ((diff / 8) + (diff % 8 > 0));
   1076 		printf("%.*s", tabs, "\t\t\t");
   1077 		/* print tabbed fds */
   1078 		for (i = 0; i < netlen; i++) {
   1079 			printf("%d", fds[i]);
   1080 			/* print suffix */
   1081 			if ((fds[i] > 0) && (fdtodata[fds[i]]->suffix > 0))
   1082 				printf("(%d)", fdtodata[fds[i]]->suffix);
   1083 			printf("\t\t");
   1084 		}
   1085 		printf("\n");
   1086 	}
   1087 	print_border();
   1088 }
   1089 
   1090 void
   1091 print_htable(void)
   1092 {
   1093 	struct htiter it;
   1094 	int index = -1;
   1095 
   1096 	print_border();
   1097 	htiter_init(&it);
   1098 	while (htiterate(usertofds, &it)) {
   1099 		if (index != (int)it.index) {
   1100 			/* ignore first new line */
   1101 			if (index != -1)
   1102 				printf("\n");
   1103 			printf("%d", it.index-1);
   1104 			index = (int)it.index;
   1105 		}
   1106 		printf(" -> %s", (char *)it.node->key);
   1107 	}
   1108 	printf("\n");
   1109 	print_border();
   1110 }
   1111 
   1112 void
   1113 print_users(void)
   1114 {
   1115 	struct htiter it;
   1116 	int i = 0;
   1117 
   1118 	print_border();
   1119 	htiter_init(&it);
   1120 	while (htiterate(usertofds, &it))
   1121 		printf("%d: %s\n", i++, (char *)it.node->key);
   1122 	print_border();
   1123 }
   1124 
   1125 void
   1126 print_reconn_list(void)
   1127 {
   1128 	struct fd_ref *n;
   1129 
   1130 	print_border();
   1131 	n = reconn_list_head;
   1132 	while(n != NULL) {
   1133 		printf("%d: %s\n", n->netid, n->user);
   1134 		n = n->next;
   1135 	}
   1136 	print_border();
   1137 }
   1138 
   1139 void
   1140 print_border(void)
   1141 {
   1142 	int i;
   1143 	for (i = 0; i < 64; i++)
   1144 		printf("-");
   1145 	printf("\n");
   1146 }
   1147 
   1148 ssize_t
   1149 writeall(int fd, char *buf)
   1150 {
   1151 	ssize_t left, sent, n;
   1152 	left = (ssize_t)strlen(buf);
   1153 	sent = 0;
   1154 	while (sent < left) {
   1155 		if ((n = write(fd, buf+sent, (size_t)left)) == -1) {
   1156 			warnf("%d: write:", fd);
   1157 			snprintf(msg, sizeof(msg), "QUIT :write failed\r\n");
   1158 			fd_reconn(fd, msg);
   1159 			return -1;
   1160 		}
   1161 		sent += n;
   1162 		left -= n;
   1163 	}
   1164 	return sent;
   1165 }