ref: 99aaa335c1a504b5210fd9e354a49749cec5c88b
dir: /main.c/
/* * This work is dedicated to the public domain. * See COPYING file for more information. */ #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <poll.h> #include <unistd.h> #include "htable.h" #include "util.c" #define BUFFER_LEN 1024 #define MAX_NICK_LEN 16 #define FD_ADDEND 100 #define POLLFD_ADDEND 100 #define NET_ADDEND 10 #define USER_ADDEND 100 #define FIFO_FD 1 #define LINKER_FD 2 #define CLONE_FD 3 typedef struct { int fd; /* fd of linker */ int join; /* is joined */ char *name; /* name */ char *symb; /* symbol */ char *host; /* hostname */ char *port; /* port */ char *chan; /* channel */ } Network; /* user defined data for each fd */ struct fdData { int netid; /* net index */ int type; /* fd type */ int suffix; /* fd suffix count */ }; static struct pollfd *pfdset; /* pollfd set */ static struct fdData *fddata; /* fd user data set */ static int pfdlen; /* pollfd length */ static int pfdcap; /* pollfd capacity */ static int *fdtoid; /* fd to pfdset index */ static int fdscap; /* fds capacity */ static Network *networks; /* linked list of networks */ static int netlen; /* current network length */ static int netcap; /* total memory allocated */ static Htable *users; /* users-clones hash table */ char msg[BUFFER_LEN]; static int isrunning = 1; /* functions prototype */ void handle_fifo_input(int, char *); void handle_server_output(int); void net_add(char *, char *, char *, char *, char *); void net_del(char *); void net_del_raw(int); void net_update(int); void user_add(char *, int); void user_del(char *, char *); int *user_fds(char *, int); int clone_add(int, char *); void fd_add(int, int, int, int); void fd_del(int); void nick_add_symb(char *, int); void privmsg_update(char *, char *, int); void terminate(int); void print_table(void); void print_htable(void); void print_users(void); void print_border(void); int main(int argc, char *argv[]) { int i, n; /* set stdout to unbufferd */ setvbuf(stdout, NULL, _IONBF, 0); /* check arguments */ if (argc != 2) { printf("usage: %s <fifo>\n", argv[0]); return 1; } /* init global variables */ pfdcap = POLLFD_ADDEND; pfdset = ecalloc((size_t)pfdcap, sizeof(struct pollfd)); fddata = ecalloc((size_t)pfdcap, sizeof(struct fdData)); fdscap = FD_ADDEND; fdtoid = ecalloc((size_t)fdscap, sizeof(int)); netcap = NET_ADDEND; networks = ecalloc((size_t)netcap, sizeof(Network)); /* * hash table of users * key -> <nickname> + '[' + <network_symbol> + ']' * value -> array of linked clones fds indexed * corresponding to its connected network */ users = htcreate((KeyLenFn *)strlen, (KeyCmpFn *)strcmp, free, free, USER_ADDEND); /* add fifo_fd */ fd_add(fifo_open(argv[1]), 0, FIFO_FD, 0); /* select loop */ while (isrunning) { if ((n = poll(pfdset, (nfds_t)pfdlen, -1)) == -1) { printf("error: poll: %s\n", strerror(errno)); terminate(1); } for (i = 0; i < pfdlen; i++) { if (!(pfdset[i].revents & (POLLIN|POLLHUP))) continue; /* printf("poll: %d\n", pfdset[i].fd); */ if (fddata[i].type == FIFO_FD) handle_fifo_input(i, argv[1]); else handle_server_output(i); /* * handle one ready fd at one time because if we * close upcoming ready fd, it cause infinite loop. */ break; } } terminate(0); return 0; } void handle_fifo_input(int id, char *path) { char buffer[BUFFER_LEN]; char *buf; char *cmd; ssize_t n; /* if failed to read data */ if ((n = readline(pfdset[id].fd, buffer, sizeof(buffer))) < 1) { if (n == 0) { /* restart again */ fd_del(pfdset[id].fd); fd_add(fifo_open(path), 0, FIFO_FD, 0); } else if ((errno != EAGAIN) && (errno != EINTR)) { printf("error: read: %s\n", strerror(errno)); } return; } /* printf("fifo: %s\n", buffer); */ 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) printf("usage: netadd <name> <symbol> <hostname> <port> <channel>\n"); else net_add(name, symb, host, port, chan); } else if (strcmp(cmd, "netdel") == 0) { char *name = buf; if (!*name) printf("usage: netdel <name>\n"); else net_del(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, "exit") == 0) { isrunning = 0; } else { printf("error: %s is not a command\n", cmd); } } void handle_server_output(int id) { char buffer [BUFFER_LEN]; char backup [BUFFER_LEN]; char linker_nick [MAX_NICK_LEN]; char *buf; char *cmd; char *nick; int i, netid; ssize_t n; /* if failed to read data */ if ((n = readline(pfdset[id].fd, buffer, sizeof(buffer))) < 1) { if (n == 0) { printf("error: remote host closed connection: %s\n", strerror(errno)); terminate(1); /* fd_del(pfdset[id].fd); */ } else if ((errno != EAGAIN) && (errno != EINTR)) { printf("error: read: %s\n", strerror(errno)); terminate(1); } return; } /* remove CRLFs */ for (i = 0; i < (int)strlen(buffer); i++) { if (buffer[i] == '\r' || buffer[i] == '\n') { buffer[i] = '\0'; break; } } /* clone the buffer */ strcpy(backup, buffer); buf = buffer; netid = fddata[id].netid; /* set linker nick */ strcpy(linker_nick, "linker"); for (i = 0; i < fddata[id].suffix; i++) strcat(linker_nick, "_"); /* first column */ cmd = split(&buf, ' '); if (strcmp(cmd, "NOTICE") == 0) { return; } else if (strcmp(cmd, "ERROR") == 0) { goto printbuffer; } else if (strcmp(cmd, "PING") == 0) { snprintf(msg, sizeof(msg), "PONG %s\r\n", buf); writeall(pfdset[id].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, "003") == 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, "375") == 0) || (strcmp(cmd, "372") == 0) || (strcmp(cmd, "376") == 0) || (strcmp(cmd, "396") == 0) || (strcmp(cmd, "366") == 0) || (strcmp(cmd, "MODE") == 0) || (strcmp(cmd, "NOTICE") == 0)) { return; } else if (strcmp(cmd, "433") == 0) { /* Nickname already in use */ split(&buf, ' '); nick = split(&buf, ' '); if (strlen(nick)+1 > MAX_NICK_LEN) { printf("error: cannot append suffix, nick '%s' is too big\n", nick); if (strcmp(nick, linker_nick) == 0) { net_del(networks[netid].name); } else { snprintf(msg, sizeof(msg), "QUIT :nick is too big\r\n"); user_del(nick, msg); } } else { strcat(nick, "_"); fddata[id].suffix++; snprintf(msg, sizeof(msg), "NICK %s\r\n", nick); writeall(pfdset[id].fd, msg); } return; } else if (strcmp(cmd, "001") == 0) { snprintf(msg, sizeof(msg), "JOIN %s\r\n", networks[netid].chan); writeall(pfdset[id].fd, msg); return; } else if (strcmp(cmd, "PRIVMSG") == 0) { char privmsg[BUFFER_LEN] = ""; int *fds; if (fddata[id].type == LINKER_FD) { nick_add_symb(nick, netid); if ((fds = htsearch(users, nick)) == NULL) return; split(&buf, ':'); /* set buf to msg */ privmsg_update(privmsg, buf, netid); for (i = 0; i < netlen; i++) { if (fds[i] > 0) { 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 linker) */ if ((user[0] == '#') || (user[0] == '&')) return; nick_add_symb(nick, netid); if ((fds = htsearch(users, 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); snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", user, privmsg); writeall(fds[i], msg); } return; } /* these messages are handled by linker */ if (fddata[id].type == CLONE_FD) { if ((strcmp(cmd, "353") == 0) || (strcmp(cmd, "JOIN") == 0) || (strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0) || (strcmp(cmd, "KICK") == 0) || (strcmp(cmd, "NICK") == 0)) return; } else if (strcmp(cmd, "353") == 0) { char *nick; split(&buf, ':'); networks[netid].join = 1; net_update(netid); while (*(nick = split(&buf, ' ')) != '\0') { if (*nick == '@' || *nick == '&' || *nick == '~' || *nick == '%' || *nick == '+' || *nick == '\\') nick++; if (strcmp(nick, linker_nick) != 0) user_add(nick, netid); } return; } else if (strcmp(cmd, "JOIN") == 0) { if ((strcmp(nick, linker_nick) != 0) && (user_fds(nick, netid) == NULL)) /* if not clone */ user_add(nick, netid); return; } else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) { nick_add_symb(nick, netid); snprintf(msg, sizeof(msg), "QUIT :%s\r\n", buf); user_del(nick, msg); return; } else if (strcmp(cmd, "NICK") == 0) { int *fds, i; char *newnick; nick_add_symb(nick, netid); if ((fds = htsearch(users, nick)) == NULL) return; /* set buf to new nick */ split(&buf, ':'); /* allocate a newnick and append the netsym and then replace the old */ newnick = ecalloc(strlen(buf) + strlen(networks[netid].symb) + 2 + 1, sizeof(char)); sprintf(newnick, "%s[%s]", buf, networks[netid].symb); htsetkey(users, nick, newnick); snprintf(msg, sizeof(msg), "NICK %s\r\n", newnick); for (i = 0; i < netlen; i++) { if (fds[i] > 0) writeall(fds[i], msg); } return; } else if (strcmp(cmd, "KICK") == 0) { /* :<nick_which_is_kicking>!~user@host KICK <channel> <nick_which_has_been_kicked> :<kick_msg> */ int *fds; char *chan = split(&buf, ' '); char *user = split(&buf, ' '); /* set the quit msg */ snprintf(msg, sizeof(msg), "QUIT : kicked by %s\r\n", nick); /* delete whole network if it is the linker */ if (strcmp(user, linker_nick) == 0) { net_del(networks[netid].name); return; } /* delete the user if the message from the same network */ if ((fds = user_fds(user, netid)) == NULL) { nick_add_symb(user, netid); user_del(user, msg); return; } /* close the kicked fd */ writeall(fds[netid], msg); fd_del(fds[netid]); fds[netid] = -2; /* * send notice in the channel through linker */ /* get the original user netid */ for (i = 0; i < netlen; i++) { if (fds[i] == -1) break; } /* set buf to msg */ split(&buf, ':'); /* remove netsymb and suffix */ *strrchr(user, '[') = '\0'; /* send notice */ snprintf(msg, sizeof(msg), "PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]\r\n", networks[i].chan, user, nick, networks[netid].name, chan, buf); writeall(networks[i].fd, msg); return; } printbuffer: printf("%d: %s\n", pfdset[id].fd, backup); } void net_add(char *name, char *symb, char *host, char *port, char *chan) { int i, fd; Network *n; /* if name, symbol or configuration already exists */ for (i = 0; i < netlen; i++) { if (strcmp(networks[i].name, name) == 0) { printf("error: network name '%s' already exists\n", name); return; } if (strcmp(networks[i].symb, symb) == 0) { printf("error: network symbol '%s' already exists\n", symb); return; } if ((strcmp(networks[i].host, host) == 0) && (strcmp(networks[i].port, port) == 0) && (strcmp(networks[i].chan, chan) == 0)) { printf("error: network configuration already exists\n"); return; } } /* resize if full */ if (netlen == netcap) { Htiter it = {0}; networks = erecalloc(networks, (size_t)netcap, NET_ADDEND, sizeof(Network)); while (htiterate(users, &it)) it.node->val = erecalloc(it.node->val, (size_t)netcap, NET_ADDEND, sizeof(int)); netcap += NET_ADDEND; } /* connect */ if ((fd = dial(host, port)) == -1) return; fd_add(fd, netlen, LINKER_FD, 0); /* send NICK and USER commands */ snprintf(msg, sizeof(msg), "NICK linker\r\n"); writeall(fd, msg); snprintf(msg, sizeof(msg), "USER linker 0 * :linker\r\n"); writeall(fd, msg); /* add a network */ n = &networks[netlen]; n->fd = fd; n->join = 0; n->name = strdup(name); n->symb = strdup(symb); n->host = strdup(host); n->port = strdup(port); n->chan = strdup(chan); netlen++; printf("%d: network '%s' added\n", fd, name); } void net_del(char *name) { int i, netid, *fds; Htiter it = {0}; /* current iterator */ Htiter lastit = {0}; /* last iterator */ /* get netid */ netid = -1; for (i = 0; i < netlen; i++) { if (strcmp(name, networks[i].name) == 0) { netid = i; break; } } if (netid == -1) { printf("error: network '%s' doesn't exist\n", name); return; } /* set the quit msg */ snprintf(msg, sizeof(msg), "QUIT :unlinking network %s\r\n", name); /* reconstruct the user-clones table */ while (htiterate(users, &it)) { fds = (int *)it.node->val; /* delete all the users of deleting network */ if (fds[netid] == -1) { user_del(it.node->key, msg); /* this node is deleted */ it = lastit; /* delete the clones */ } else { if (fds[netid] > 0) { writeall(fds[netid], msg); fd_del(fds[netid]); } /* swap last with current one */ fds[netid] = fds[netlen-1]; } lastit = it; } /* set pollfds netid with last netid to current netid. */ for (i = 0; i < pfdlen; i++) { if (fddata[i].netid == netlen-1) fddata[i].netid = netid; } writeall(networks[netid].fd, msg); fd_del(networks[netid].fd); net_del_raw(netid); printf("%d: network '%s' deleted\n", networks[netid].fd, name); /* swap the network with the last */ networks[netid] = networks[netlen-1]; netlen--; } void net_del_raw(int netid) { Network *n = &networks[netid]; free(n->name); free(n->symb); free(n->host); free(n->port); free(n->chan); } void net_update(int netid) { int *fds; Htiter it = {0}; while (htiterate(users, &it)) { fds = (int *)it.node->val; fds[netid] = clone_add(netid, it.node->key); } } void user_add(char *unick, int netid) { int i, *fds; size_t len; char *nick; len = strlen(unick) + strlen(networks[netid].symb) + 2 + 1; /* too long nick */ if (len-1 > MAX_NICK_LEN) { printf("error: user nick '%s' is too big\n", unick); return; } /* resize hash table if store is low */ if ((users->cap - users->len) < USER_ADDEND) htresize(users, users->cap + USER_ADDEND); printf("useradd: %s\n", unick); /* allocate a new user */ nick = ecalloc(len, sizeof(char)); fds = ecalloc((size_t)netcap, sizeof(int)); sprintf(nick, "%s[%s]", unick, networks[netid].symb); /* clone the user on all other network */ for (i = 0; i < netlen; i++) { if (networks[i].join == 0) continue; if (i == netid) { fds[i] = -1; } else { fds[i] = clone_add(i, nick); } } /* insert it to the users hash table */ if (htinsert(users, nick, fds) == -1) { /* this shouldn't happen as it was already checked */ printf("error: user '%s' already exists\n", nick); terminate(1); } } void user_del(char *nick, char *msg) { int *fds, i; if ((fds = htsearch(users, nick)) == NULL) { printf("error: user '%s' doesn't exists\n", nick); return; } for (i = 0; i < netlen; i++) { if (fds[i] > 0) { writeall(fds[i], msg); fd_del(fds[i]); } } htremove(users, nick); } int * user_fds(char *nick, int netid) { unsigned int suffix; int *fds = NULL; /* count suffix */ for (suffix = 0; nick[strlen(nick)-suffix-1] == '_'; suffix++); /* remove suffix */ if (suffix > 0) nick[strlen(nick)-suffix] = '\0'; fds = htsearch(users, nick); /* if match but suffix doesn't match */ if ((fds != NULL) && (fddata[fdtoid[fds[netid]]].suffix != (int)suffix)) fds = NULL; /* add suffix back */ if (suffix > 0) nick[strlen(nick)] = '_'; return fds; } int clone_add(int netid, char *nick) { int fd; Network *n = &networks[netid]; if ((fd = dial(n->host, n->port)) == -1) return -1; fd_add(fd, netid, CLONE_FD, 0); /* send NICK and USER commands */ snprintf(msg, sizeof(msg), "NICK %s\r\n", nick); writeall(fd, msg); snprintf(msg, sizeof(msg), "USER user 0 * :user\r\n"); writeall(fd, msg); return fd; } void fd_add(int fd, int netid, int type, int suffix) { /* resize if full */ if (fd+1 == fdscap) { fdtoid = erecalloc(fdtoid, (size_t)fdscap, FD_ADDEND, sizeof(int)); fdscap += FD_ADDEND; } if (pfdlen == pfdcap) { pfdset = erecalloc(pfdset, (size_t)pfdcap, POLLFD_ADDEND, sizeof(struct pollfd)); fddata = erecalloc(fddata, (size_t)pfdcap, POLLFD_ADDEND, sizeof(struct fdData)); pfdcap += POLLFD_ADDEND; } pfdset[pfdlen].fd = fd; pfdset[pfdlen].events = POLLIN; fddata[pfdlen].netid = netid; fddata[pfdlen].type = type; fddata[pfdlen].suffix = suffix; fdtoid[fd] = pfdlen; pfdlen++; } void fd_del(int fd) { close(fd); pfdset[fdtoid[fd]] = pfdset[pfdlen-1]; fddata[fdtoid[fd]] = fddata[pfdlen-1]; fdtoid[fd] = pfdlen-1; pfdlen--; } 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) { char d; /* delimiter */ char *n; while (src != NULL) { n = strpbrk(src, " :;,<>@&~%+\\"); if (n == NULL) { d = '\0'; } else { d = *n; *n = '\0'; n++; } /* check if the word is nick */ if (user_fds(src, netid) != NULL) *strrchr(src, '[') = '\0'; strcat(dst, src); strncat(dst, &d, 1); src = n; } } void terminate(int status) { int i; snprintf(msg, sizeof(msg), "QUIT :linker shutting down\r\n"); for (i = 0; i < pfdlen; i++) { if (fddata[i].type != FIFO_FD) writeall(pfdset[i].fd, msg); fd_del(pfdset[i].fd); } /* delete all the users */ htdestroy(users); /* delete all the networks */ for (i = 0; i < netlen; i++) net_del_raw(i); free(networks); free(pfdset); free(fddata); free(fdtoid); if (status == 0) { printf("exit successfully\n"); exit(0); } else { printf("aborted\n"); exit(1); } } void print_table(void) { int i, *fds, diff, tabs; Htiter it = {0}; char *nick; if (netlen == 0) return; print_border(); /* print networks */ printf("Networks\t\t"); for (i = 0; i < netlen; i++) printf("%s(%d)\t", networks[i].symb, networks[i].fd); printf("\n"); while (htiterate(users, &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 clones fds */ for (i = 0; i < netlen; i++) { printf("%d", fds[i]); /* print suffix */ if ((fds[i] > 0) && (fddata[fdtoid[fds[i]]].suffix > 0)) printf("(%d)", fddata[fdtoid[fds[i]]].suffix); printf("\t"); } printf("\n"); } print_border(); } void print_htable(void) { Htiter it = {0}; int index = -1; print_border(); while (htiterate(users, &it)) { if (index != (int)it.index) { /* ignore first new line */ if (index != -1) printf("\n"); printf("%d", it.index); index = (int)it.index; } printf(" -> %s", (char *)it.node->key); } printf("\n"); print_border(); } void print_users(void) { Htiter it = {0}; int i = 0; print_border(); while (htiterate(users, &it)) printf("%d: %s\n", i++, (char *)it.node->key); print_border(); } void print_border(void) { int i; for (i = 0; i < 64; i++) printf("-"); printf("\n"); }