ref: c280d99d0354f96611acaa905c46c3eee4e3a37b
dir: /main.c/
/* * This work is dedicated to the public domain. * See COPYING file for more information. */ #include <errno.h> #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <time.h> #include <unistd.h> #ifdef __gnu_linux__ #include <sys/epoll.h> #else #include <sys/event.h> #endif #define LOG_LEVEL 1 #include "htable.h" #include "util.c" #define CLONE_COOLDOWN 1 #define CLONE_ADDEND 10 #define RECONN_TIME 10 #define FD_ADDEND 100 #define NET_ADDEND 10 #define USER_ADDEND 100 #define BUFSIZE 1024 #define NICK_LEN 16 #define HANDLE_EVENTS 10 /* no. of events to handle at a time */ #define EV_READ 1 #define EV_WRITE 2 char wnick [NICK_LEN]; /* watcher nick */ enum { IDLE = 0, RESET, CLONING, EXIT }; struct fd_data { char *user; /* nick */ int netid; /* net index */ int suffix; /* suffix count */ int ready; /* joined */ }; struct fd_ref { char *user; /* nick */ int netid; /* net index */ struct fd_ref *next; /* next node */ }; struct network { int fd; /* fd */ char *name; /* name */ char *symb; /* symbol */ char *host; /* host */ char *port; /* port */ char *chan; /* channel */ }; static time_t ntime = -1; /* next timeout */ static int state = IDLE; /* program state */ static int fifofd; /* fifo fd */ static int break_evloop; /* break event loop */ static char *fifopath; /* fifo path */ static char msg[BUFSIZE]; /* message buffer */ #ifdef __gnu_linux__ static int epfd; /* epoll instance */ #else static int kqfd; /* kqueue instance */ #endif static struct network *networks; /* networks array */ static int netlen; /* array length */ static int netcap; /* array capacity */ static struct fd_data **fdtodata; /* fd -> data pointer */ static int fdcap; /* fdtodata capacity */ static struct htable *usertofds; /* user -> array of fds of clones indexed according to networks array */ static struct fd_ref *reconn_list_head, /* re-connection queue */ *reconn_list_tail; /* functions prototype */ void fd_register(int, int); void fifo_read(void); time_t event_timeout(void); void fd_write(int); void fd_read(int); void net_add(char *, char *, char *, char *, char *); void net_del(int, char *, int); void user_add(char *, int, int); void user_del(char *, char *); void reconn_list_add(char *, int); void reconn_list_del(char *, int); int fd_new(int, char *); void fd_del(int, char *, int); void fd_reconn(int, char *); int *clone_get_fds(char *, int); void nick_add_symb(char *, int); void privmsg_update(char *, char *, int, int); void print_table(void); void print_htable(void); void print_users(void); void print_reconn_list(void); void print_border(void); ssize_t writeall(int, char *); int main(int argc, char *argv[]) { #ifdef __gnu_linux__ struct epoll_event epevs[HANDLE_EVENTS]; #else struct kevent kevs[HANDLE_EVENTS]; struct timespec tmsp; #endif int i, fd, nev; /* no. of returned events to handle */ time_t timeout; /* set stdout to unbufferd */ setvbuf(stdout, NULL, _IONBF, 0); /* handle arguments */ if (argc < 2 || argc > 3) fatal("usage: ticl fifo [wnick]"); else fifopath = argv[1]; if (argc == 3) wnick = argv[2]; else wnick = "watcher"; /* init global variables */ netcap = NET_ADDEND; fdcap = FD_ADDEND; networks = emalloc((size_t)netcap * sizeof(struct network)); fdtodata = emalloc((size_t)fdcap * sizeof(struct fd_data *)); usertofds = htcreate(hash_str, (key_cmp_fn *)strcmp, free, free, USER_ADDEND); #ifdef __gnu_linux__ if ((epfd = epoll_create1(0)) == -1) fatal("epoll_create1:"); #else if ((kqfd = kqueue()) == -1) fatal("kqueue:"); #endif fifofd = fifo_open(fifopath); fd_register(fifofd, EV_READ); /* event loop */ while (state != EXIT) { if (ntime == -1) { timeout = -1; } else { timeout = ntime - time(NULL); if (timeout < 0) timeout = 0; } #ifdef __gnu_linux__ nev = epoll_wait(epfd, epevs, HANDLE_EVENTS, (int)timeout * 1000); if (nev == -1) fatal("epoll_wait:"); #else tmsp.tv_sec = timeout; tmsp.tv_nsec = 0; nev = kevent(kqfd, NULL, 0, kevs, HANDLE_EVENTS, timeout < 0 ? NULL : &tmsp); if (nev == -1) fatal("kevent:"); #endif else if (nev == 0) { ntime = event_timeout(); continue; } for (i = 0; i < nev; i++) { #ifdef __gnu_linux__ fd = epevs[i].data.fd; #else fd = (int)kevs[i].ident; #endif if (fd == fifofd) { fifo_read(); break; #ifdef __gnu_linux__ } else if (epevs[i].events & EPOLLOUT) { #else } else if (kevs[i].filter == EVFILT_WRITE) { #endif fd_write(fd); #ifdef __gnu_linux__ } else if (epevs[i].events & EPOLLIN) { #else } else if (kevs[i].filter == EVFILT_READ) { #endif fd_read(fd); } else { fatal("unknown event"); } if (reconn_list_head != NULL && ntime == -1) ntime = time(0) + RECONN_TIME; if (break_evloop) { break_evloop = FALSE; break; } } } /* * delete and free all the networks with their users * - delete in reverse order to prevent swapping. */ snprintf(msg, sizeof(msg), "QUIT :relay shutting down\r\n"); for (i = netlen-1; i >= 0; i--) net_del(i, msg, FALSE); free(networks); free(fdtodata); htdestroy(usertofds); return 0; } void fd_register(int fd, int mode) { #ifdef __gnu_linux__ struct epoll_event ev; if (mode == EV_READ) ev.events = EPOLLIN; else if (mode == EV_WRITE) ev.events = EPOLLOUT; else fatal("unknown event mode"); ev.data.fd = fd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) fatal("epoll_ctl:"); #else struct kevent ev; int filter; if (mode == EV_READ) filter = EVFILT_READ; else if (mode == EV_WRITE) filter = EVFILT_WRITE; else fatal("unknown event mode"); EV_SET(&ev, fd, filter, EV_ADD, 0, 0, 0); if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1) fatal("kevent:"); #endif } void fifo_read(void) { char buffer[BUFSIZE]; char *buf; char *cmd; ssize_t n; n = readline(fifofd, buffer, sizeof(buffer)); if (n == -1) { fatal("read:"); } else if (n == 0) { /* reopen fifo again */ close(fifofd); fifofd = fifo_open(fifopath); fd_register(fifofd, EV_READ); return; } if (*buffer == '\0') return; buf = buffer; cmd = split(&buf, ' '); if (strcmp(cmd, "netadd") == 0) { char *name = split(&buf, ' '); char *symb = split(&buf, ' '); char *host = split(&buf, ' '); char *port = split(&buf, ' '); char *chan = buf; if (!*name || !*symb || !*host || !*port || !*chan) warnf("usage: netadd <name> <symbol> <host> <port> <channel>"); else net_add(name, symb, host, port, chan); } else if (strcmp(cmd, "netdel") == 0) { char *name = buf; if (!*name) { warnf("usage: netdel <name>"); } else { int i; for (i = 0; i < netlen; i++) { if (strcmp(name, networks[i].name) == 0) { snprintf(msg, sizeof(msg), "QUIT :netdel: %s\r\n", networks[i].name); net_del(i, msg, FALSE); return; } } warnf("%s: network doesn't exist", name); } } else if (strcmp(cmd, "print") == 0) { print_table(); } else if (strcmp(cmd, "htable") == 0) { print_htable(); } else if (strcmp(cmd, "users") == 0) { print_users(); } else if (strcmp(cmd, "reconn") == 0) { print_reconn_list(); } else if (strcmp(cmd, "exit") == 0) { state = EXIT; } else { warnf("%s is not a command", cmd); } } time_t event_timeout(void) { static struct htiter it; int i, j, *fds; char *user; if (state == IDLE) { struct fd_ref *tmp; debug(1, "Reconnecting"); while(reconn_list_head != NULL) { user = reconn_list_head->user; i = reconn_list_head->netid; if (user == NULL) { networks[i].fd = fd_new(i, user); } else { if ((fds = htsearch(usertofds, user)) == NULL) fatal("%s: user doesn't exist", user); if (fds[i] == -3 && networks[i].fd > 0 && fdtodata[networks[i].fd]->ready) fds[i] = fd_new(i, user); } tmp = reconn_list_head; reconn_list_head = reconn_list_head->next; free(tmp); } reconn_list_tail = reconn_list_head; return -1; } else if (state == RESET) { state = CLONING; htiter_init(&it); } debug(1, "."); j = 0; while (htiterate(usertofds, &it)) { user = (char *)it.node->key; fds = (int *)it.node->val; for (i = 0; i < netlen; i++) { if (fds[i] == 0 && networks[i].fd > 0 && fdtodata[networks[i].fd]->ready) fds[i] = fd_new(i, user); } j++; if (j >= CLONE_ADDEND) return time(NULL) + CLONE_COOLDOWN; } state = IDLE; return -1; } void fd_write(int fd) { #ifdef __gnu_linux__ struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd; if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) fatal("epoll_ctl:"); #else struct kevent ev; EV_SET(&ev, fd, EVFILT_WRITE, EV_DISABLE, 0, 0, 0); if (kevent(kqfd, &ev, 1, NULL, 0, NULL) == -1) fatal("kevent:"); fd_register(fd, EV_READ); #endif if (fdtodata[fd]->user == NULL) { /* watcher */ snprintf(msg, sizeof(msg), "NICK %s\r\n" "USER %s 0 * :%s\r\n", wnick, wnick, wnick); writeall(fd, msg); } else { /* user */ snprintf(msg, sizeof(msg), "NICK %s\r\nUSER user 0 * :user\r\n", fdtodata[fd]->user); writeall(fd, msg); } } void fd_read(int fd) { char buffer [BUFSIZE]; char backup [BUFSIZE]; char *buf; char *cmd; char *nick; int i, netid; ssize_t n; netid = fdtodata[fd]->netid; n = readline(fd, buffer, sizeof(buffer)); if (n == -1) { warnf("%d: read:", fd); snprintf(msg, sizeof(msg), "QUIT :read failed\r\n"); fd_reconn(fd, msg); return; } else if (n == 0) { warnf("%d: read: connection closed", fd); snprintf(msg, sizeof(msg), "QUIT :connection closed\r\n"); fd_reconn(fd, msg); return; } if (*buffer == '\0') return; /* clone the buffer */ strlcpy(backup, buffer, sizeof(backup)); buf = buffer; /* set watcher nick */ strlcpy(wnick, "watcher", sizeof(wnick)); for (i = 0; i < fdtodata[fd]->suffix; i++) strcat(wnick, "_"); /* first column */ cmd = split(&buf, ' '); if (strcmp(cmd, "NOTICE") == 0) { /* ignore */ return; } else if (strcmp(cmd, "ERROR") == 0) { warnf("%d: %s", fd, backup); snprintf(msg, sizeof(msg), "QUIT :ERROR\r\n"); fd_reconn(fd, msg); return; } else if (strcmp(cmd, "PING") == 0) { snprintf(msg, sizeof(msg), "PONG %s\r\n", buf); writeall(fd, msg); return; } /* strip nick from first column */ nick = split(&cmd, '!'); if (nick[0] == ':') nick++; /* second column */ cmd = split(&buf, ' '); /* ignore all the info messages */ if ((strcmp(cmd, "002") == 0) || (strcmp(cmd, "003") == 0) || (strcmp(cmd, "004") == 0) || (strcmp(cmd, "005") == 0) || (strcmp(cmd, "251") == 0) || (strcmp(cmd, "252") == 0) || (strcmp(cmd, "253") == 0) || /* unknown connection(s) */ (strcmp(cmd, "254") == 0) || (strcmp(cmd, "255") == 0) || (strcmp(cmd, "265") == 0) || (strcmp(cmd, "266") == 0) || (strcmp(cmd, "250") == 0) || (strcmp(cmd, "332") == 0) || (strcmp(cmd, "333") == 0) || (strcmp(cmd, "375") == 0) || (strcmp(cmd, "372") == 0) || (strcmp(cmd, "376") == 0) || (strcmp(cmd, "396") == 0) || (strcmp(cmd, "366") == 0) || (strcmp(cmd, "MODE") == 0) || (strcmp(cmd, "TOPIC") == 0) || (strcmp(cmd, "NOTICE") == 0)) { return; } else if (strcmp(cmd, "432") == 0) { /* Erroneous Nickname */ fatal("%d: %s", fd, backup); } else if (strcmp(cmd, "433") == 0) { /* Nickname already in use */ split(&buf, ' '); nick = split(&buf, ' '); strcat(nick, "_"); fdtodata[fd]->suffix++; if (strlen(nick) > NICK_LEN) { warnf("%s: nickname too long", nick); snprintf(msg, sizeof(msg), "QUIT :%s: nickname too long\r\n", nick); if (fdtodata[fd]->user == NULL) net_del(netid, msg, FALSE); else fd_del(fd, msg, FALSE); return; } else { snprintf(msg, sizeof(msg), "NICK %s\r\n", nick); writeall(fd, msg); } return; } else if (strcmp(cmd, "001") == 0) { snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan); writeall(fd, msg); return; } else if (strcmp(cmd, "PRIVMSG") == 0) { char privmsg[BUFSIZE] = ""; int *fds; if (fdtodata[fd]->user == NULL) { /* if watcher */ nick_add_symb(nick, netid); if ((fds = htsearch(usertofds, nick)) == NULL) return; split(&buf, ':'); /* set buf to msg */ for (i = 0; i < netlen; i++) { printf("%s\n", buf); if (fds[i] > 0) { privmsg_update(privmsg, buf, netid, fds[i]); snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", networks[i].chan, privmsg); writeall(fds[i], msg); } } } else { char *netsymb; char *user = split(&buf, ' '); /* ignore messages from channel (it is handled by watcher) */ if (user[0] == '#' || user[0] == '&') return; nick_add_symb(nick, netid); if ((fds = htsearch(usertofds, nick)) == NULL) return; /* split user nick and network symbol */ *strrchr(user, ']') = '\0'; netsymb = strrchr(user, '['); *netsymb++ = '\0'; /* get the network index */ for (i = 0; i < netlen; i++) { if (strcmp(netsymb, networks[i].symb) == 0) break; } split(&buf, ':'); /* set buf to msg */ privmsg_update(privmsg, buf, netid, fds[i]); snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg); writeall(fds[i], msg); } return; } else if (strcmp(cmd, "353") == 0) { fdtodata[fd]->ready = TRUE; /* FALLBACK */ } /* from now, the messages are handled by watcher */ if (fdtodata[fd]->user != NULL) /* if clone */ return; if (strcmp(cmd, "353") == 0) { char *nick; split(&buf, ':'); state = RESET; ntime = 0; /* then add all new users */ while (*(nick = split(&buf, ' ')) != '\0') { if (*nick == '@' || *nick == '&' || *nick == '~' || *nick == '%' || *nick == '+' || *nick == '\\') nick++; if (strcmp(nick, wnick) != 0) user_add(nick, netid, FALSE); } return; } else if (strcmp(cmd, "JOIN") == 0) { /* if real user */ if ((strcmp(nick, wnick) != 0) && (clone_get_fds(nick, netid) == NULL)) { if (state != IDLE) warnf("%s: ignored (network cloning)", nick); else user_add(nick, netid, TRUE); } return; } else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) { split(&buf, ':'); /* ignore ':' and assign QUIT/PART msg to buf */ snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf); nick_add_symb(nick, netid); if (htsearch(usertofds, nick) != NULL) user_del(nick, msg); return; } else if (strcmp(cmd, "NICK") == 0) { int *fds, i; char *newnick; nick_add_symb(nick, netid); if ((fds = htsearch(usertofds, nick)) == NULL) return; /* set buf to new nick */ split(&buf, ':'); /* allocate a new nick with net symbol and replace the old one */ newnick = emalloc((strlen(buf) + strlen(networks[netid].symb) + 2 + 1) * sizeof(char)); sprintf(newnick, "%s[%s]", buf, networks[netid].symb); snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick); for (i = 0; i < netlen; i++) { if (fds[i] > 0) { fdtodata[fds[i]]->user = newnick; fdtodata[fds[i]]->suffix = 0; writeall(fds[i], msg); } else if (fds[i] == -3) { reconn_list_del(nick, i); reconn_list_add(newnick, i); } } htmodkey(usertofds, nick, newnick); return; } else if (strcmp(cmd, "KICK") == 0) { /* :<user_who_kicked>!~username@host KICK <channel> <user_who_is_being_kicked> :<kick_msg> */ int *fds; char *user; split(&buf, ' '); /* channel name */ user = split(&buf, ' '); /* user who is being kicked */ split(&buf, ':'); /* ignore ':' and store reason to buf */ /* if watcher is being kicked, delete the network */ if (strcmp(user, wnick) == 0) { snprintf(msg, sizeof(msg), "QUIT :netdel: %s (%s is kicked by %s)\r\n", networks[netid].name, wnick, nick); fd_reconn(fd, msg); return; } /* if message is from a real user, delete the user */ if ((fds = clone_get_fds(user, netid)) == NULL) { snprintf(msg, sizeof(msg), "QUIT :userdel: %s (kicked by %s)\r\n", user, nick); nick_add_symb(user, netid); user_del(user, msg); return; } /* delete the kicked clone */ snprintf(msg, sizeof(msg), "QUIT :kicked by %s\r\n", nick); fd_reconn(fds[netid], msg); /* get the original user netid */ for (i = 0; i < netlen; i++) { if (fds[i] == -1) break; } /* send notice in the channel through watcher */ if (networks[i].fd > 0) { snprintf(msg, sizeof(msg), "PRIVMSG %s :%s is kicked by %s [%s]\r\n", networks[i].chan, user, nick, buf); writeall(networks[i].fd, msg); } return; } warnf("%d: %s", fd, backup); return; } void net_add(char *name, char *symb, char *host, char *port, char *chan) { struct network *n; int i; for (i = 0; i < netlen; i++) { if (strcmp(networks[i].name, name) == 0) { warnf("%s: network name already exists", name); return; } if (strcmp(networks[i].symb, symb) == 0) { warnf("%s: network symbol already exists", symb); return; } if ((strcmp(networks[i].host, host) == 0) && (strcmp(networks[i].port, port) == 0) && (strcmp(networks[i].chan, chan) == 0)) { warnf("%s:%s%s: network configuration already exists", host, port, chan); return; } } /* if full, resize the network and user fds */ if (netlen == netcap) { struct htiter it; htiter_init(&it); networks = erealloc(networks, (size_t)(netcap + NET_ADDEND) * sizeof(struct network)); while (htiterate(usertofds, &it)) { it.node->val = erealloc(it.node->val, (size_t)(netcap + NET_ADDEND) * sizeof(int)); /* zero out the extended array */ for (i = netcap; i < netcap + NET_ADDEND; i++) ((int *)it.node->val)[i] = 0; } netcap += NET_ADDEND; } /* add a network */ n = &networks[netlen]; n->name = strdup(name); n->symb = strdup(symb); n->host = strdup(host); n->port = strdup(port); n->chan = strdup(chan); n->fd = fd_new(netlen, NULL); netlen++; } void net_del(int netid, char *msg, int reconnect) { int *fds; char *user; struct network *n; struct htiter lit, it; /* last, current iterator */ n = &networks[netid]; htiter_init(&it); htiter_init(&lit); while (htiterate(usertofds, &it)) { user = (char *)it.node->key; fds = (int *)it.node->val; if (fds[netid] == -1) { user_del(it.node->key, msg); it = lit; /* this node is deleted */ } else { if (fds[netid] > 0) fd_del(fds[netid], msg, FALSE); else if (fds[netid] == -3) reconn_list_del(user, netid); if (!reconnect) { if(netid != netlen-1) { fds[netid] = fds[netlen-1]; if (fds[netid] > 0) { fdtodata[fds[netid]]->netid = netid; } else if (fds[netid] == -3) { reconn_list_del(user, netlen-1); reconn_list_add(user, netid); } } fds[netlen-1] = 0; } else { fds[netid] = 0; } } lit = it; } if (n->fd > 0) fd_del(n->fd, msg, reconnect); else if (n->fd == -3) reconn_list_del(NULL, netid); if (!reconnect) { free(n->name); free(n->symb); free(n->host); free(n->port); free(n->chan); /* swap */ if(netid != netlen-1) { networks[netid] = networks[netlen-1]; if (networks[netid].fd > 0) { fdtodata[n->fd]->netid = netid; } else if (networks[netid].fd == -3) { reconn_list_del(NULL, netlen-1); reconn_list_add(NULL, netid); } } netlen--; } } void user_add(char *unick, int netid, int clone) { size_t len; char *nick; int *fds; len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1; if (len-1 > NICK_LEN) { warnf("%s[%s]: nickname too long", unick, networks[netid].symb); return; } /* resize hash table if storage is low */ if ((usertofds->cap - usertofds->len) < USER_ADDEND) usertofds = htresize(usertofds, usertofds->cap + USER_ADDEND); /* allocate a new user */ nick = emalloc(len * sizeof(char)); fds = ecalloc((size_t)netcap, sizeof(int)); sprintf(nick, "%s[%s]", unick, networks[netid].symb); fds[netid] = -1; if (clone) { int i; for (i = 0; i < netlen; i++) { if (fds[i] == 0 && networks[i].fd > 0 && fdtodata[networks[i].fd]->ready) fds[i] = fd_new(i, nick); } } if (htinsert(usertofds, nick, fds) == -1) fatal("%s: user already exists", nick); } void user_del(char *user, char *msg) { int i, *fds; if ((fds = htsearch(usertofds, user)) == NULL) fatal("%s: user doesn't exist", user); for (i = 0; i < netlen; i++) { if (fds[i] > 0) { fd_del(fds[i], msg, FALSE); } else if (fds[i] == -3) { reconn_list_del(user, i); } } htremove(usertofds, user); } void reconn_list_add(char *user, int netid) { struct fd_ref *node; node = emalloc(sizeof(struct fd_ref)); node->user = user; node->netid = netid; node->next = NULL; if (reconn_list_tail == NULL) { reconn_list_head = reconn_list_tail = node; } else { reconn_list_tail->next = node; reconn_list_tail = reconn_list_tail->next; } } void reconn_list_del(char *user, int netid) { struct fd_ref *n, *pn; /* current and previous node */ n = reconn_list_head; pn = NULL; while (n != NULL) { if (n->netid == netid && ((user == NULL && n->user == NULL) || (user != NULL && n->user != NULL && (strcmp(n->user, user) == 0)))) { if (n == reconn_list_head && n == reconn_list_tail) reconn_list_head = reconn_list_tail = NULL; else if (n == reconn_list_head) reconn_list_head = n->next; else if (n == reconn_list_tail) reconn_list_tail = pn; else pn->next = n->next; free(n); return; } pn = n; n = n->next; } fatal("%d:%s: failed to find in re-connection list", netid, user); } int fd_new(int netid, char *user) { int fd; struct network *n; struct fd_data *data; n = &networks[netid]; fd = dial(n->host, n->port); if (fd == -1) { warnf("%s:%s: failed to connect", n->host, n->port); return -2; } fd_register(fd, EV_WRITE); if (user == NULL) debug(1, "%d: netadd: %s[%s]", fd, n->name, n->symb); else debug(1, "%d: add[%s]: %s", fd, n->symb, user); if (fd+1 > fdcap) { fdcap *= 2; fdtodata = erealloc(fdtodata, (size_t)fdcap * sizeof(struct fd_data *)); } data = emalloc(1 * sizeof(struct fd_data)); data->netid = netid; data->user = user; data->suffix = 0; data->ready = FALSE; fdtodata[fd] = data; return fd; } void fd_del(int fd, char *msg, int reconnection) { char *user; int netid; int *fds; user = fdtodata[fd]->user; netid = fdtodata[fd]->netid; if (user == NULL) { debug(1, "%d: netdel: %s[%s]", fd, networks[netid].name, networks[netid].symb); networks[netid].fd = reconnection ? -3 : -2; } else { debug(1, "%d: del[%s]: %s", fd, networks[netid].symb, user); if ((fds = htsearch(usertofds, user)) == NULL) fatal("%s: user doesn't exist", user); fds[netid] = reconnection ? -3 : -2; } if (fdtodata[fd]->ready) writeall(fd, msg); close(fd); if (reconnection) reconn_list_add(user, netid); free(fdtodata[fd]); fdtodata[fd] = NULL; break_evloop = TRUE; } void fd_reconn(int fd, char *msg) { if (fdtodata[fd]->user == NULL) net_del(fdtodata[fd]->netid, msg, TRUE); else fd_del(fd, msg, TRUE); } int * clone_get_fds(char *nick, int netid) { unsigned int s; int *fds = NULL; /* count suffix */ for (s = 0; nick[strlen(nick)-s-1] == '_'; s++); /* remove suffix */ if (s > 0) nick[strlen(nick)-s] = '\0'; fds = htsearch(usertofds, nick); /* if match but suffix doesn't match */ if ((fds != NULL) && (fdtodata[fds[netid]]->suffix != (int)s)) fds = NULL; /* add suffix back */ if (s > 0) nick[strlen(nick)] = '_'; return fds; } void nick_add_symb(char *nick, int netid) { strcat(nick, "["); strcat(nick, networks[netid].symb); strcat(nick, "]"); } /* * trim all the nicknames to original nick * src will be destructed */ void privmsg_update(char *dst, char *src, int netid, int fd) { char d; /* delimiter */ char *n; int i, *fds; while (src != NULL) { n = strpbrk(src, " :;,<>@&~%+\\"); if (n == NULL) { d = '\0'; } else { d = *n; *n = '\0'; n++; } /* check if the word is nick */ if ((fds = clone_get_fds(src, netid)) != NULL) { int netid2 = fdtodata[fd]->netid; *strrchr(src, '[') = '\0'; src[strlen(src)] = '['; if (fds[netid2] > 0) { nick_add_symb(dst, netid2); for (i = 0; i < fdtodata[fds[netid2]]->suffix; i++) strcat(dst, "_"); } } else { strcat(dst, src); } strncat(dst, &d, 1); src = n; } } void print_table(void) { int i, *fds, diff, tabs; char *nick; struct htiter it; if (netlen == 0) return; print_border(); /* print networks */ printf("Networks\t\t"); for (i = 0; i < netlen; i++) { printf("%s->%d", networks[i].symb, networks[i].fd); /* print suffix */ if (networks[i].fd > 0 && fdtodata[networks[i].fd]->suffix > 0) printf("(%d)\t", fdtodata[networks[i].fd]->suffix); else printf("\t\t"); } printf("\n"); htiter_init(&it); while (htiterate(usertofds, &it)) { fds = (int *)it.node->val; nick = (char *)it.node->key; /* print tabbed user nick */ printf("%s", nick); diff = 24 - (int)strlen(nick); tabs = ((diff / 8) + (diff % 8 > 0)); printf("%.*s", tabs, "\t\t\t"); /* print tabbed fds */ for (i = 0; i < netlen; i++) { printf("%d", fds[i]); /* print suffix */ if ((fds[i] > 0) && (fdtodata[fds[i]]->suffix > 0)) printf("(%d)", fdtodata[fds[i]]->suffix); printf("\t\t"); } printf("\n"); } print_border(); } void print_htable(void) { struct htiter it; int index = -1; print_border(); htiter_init(&it); while (htiterate(usertofds, &it)) { if (index != (int)it.index) { /* ignore first new line */ if (index != -1) printf("\n"); printf("%d", it.index-1); index = (int)it.index; } printf(" -> %s", (char *)it.node->key); } printf("\n"); print_border(); } void print_users(void) { struct htiter it; int i = 0; print_border(); htiter_init(&it); while (htiterate(usertofds, &it)) printf("%d: %s\n", i++, (char *)it.node->key); print_border(); } void print_reconn_list(void) { struct fd_ref *n; print_border(); n = reconn_list_head; while(n != NULL) { printf("%d: %s\n", n->netid, n->user); n = n->next; } print_border(); } void print_border(void) { int i; for (i = 0; i < 64; i++) printf("-"); printf("\n"); } ssize_t writeall(int fd, char *buf) { ssize_t left, sent, n; left = (ssize_t)strlen(buf); sent = 0; while (sent < left) { if ((n = write(fd, buf+sent, (size_t)left)) == -1) { warnf("%d: write:", fd); snprintf(msg, sizeof(msg), "QUIT :write failed\r\n"); fd_reconn(fd, msg); return -1; } sent += n; left -= n; } return sent; }