ref: 8df402e53d02ca2c0b976715ce57c28750dd99d9
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 <sys/select.h> #include <sys/stat.h> #include <unistd.h> #include "htable.h" #include "util.c" #define BUFFER_LEN 1024 #define MAX_FD 1024 #define MAX_NICK_LEN 16 #define NET_ADDEND 10 #define USER_ADDEND 5 #define FD_TYPE_LINKER 1 #define FD_TYPE_CLONE 2 typedef struct { int fd; /* fd of linker */ char *name; /* name */ char *symb; /* symbol */ char *host; /* hostname */ char *port; /* port */ char *chan; /* channel */ } Network; static int isrunning = 1; static fd_set fdset; /* fd_set (select) */ static int nfds; /* number of fds */ static int fdnet [MAX_FD]; /* fd -> network index */ static int fdtype [MAX_FD]; /* fd -> fd type */ static int fdsuf [MAX_FD]; /* fd -> nick suffix */ static Network *networks; /* linked list of networks */ static int netlen; /* current network length */ static int netcap; /* total memory allocated */ static Htable *users; /* functions prototype */ void handle_fifo_input(int); void handle_server_output(int); void net_add(char *, char *, char *, char *, char *); void net_del(char *); void net_del_raw(int); void net_resize(void); void net_update(void); void user_add(char *, int); void user_del(char *, char *); int *user_fds(char *, int); int clone_add(int, char *); int fd_add(char *, char *); void fd_del(int, char *); 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 fifo_fd, r, i; fd_set rdset; struct stat fifo_st; /* set stdout to unbufferd */ setvbuf(stdout, NULL, _IONBF, 0); /* check arguments */ if (argc != 2) { printf("usage: %s <fifo>\n", argv[0]); return 1; } /* init networks and users */ 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); /* init fdnet array to -1 */ for (i = 0; i < MAX_FD; i++) fdnet[i] = -1; /* crate or/and open fifo */ if (stat(argv[1], &fifo_st) == 0) { if (!(fifo_st.st_mode & S_IFIFO)) { printf("error: '%s' is not a fifo file\n", argv[1]); return 1; } } else if (mkfifo(argv[1], S_IRWXU) != 0) { printf("error: failed to create fifo file '%s'\n", argv[1]); return 1; } fifo_fd = open(argv[1], O_RDWR); if (fifo_fd < 0) { printf("error: cannot open() '%s'\n", argv[1]); return 1; } /* initialize fdset */ FD_ZERO(&fdset); FD_SET(fifo_fd, &fdset); nfds = fifo_fd + 1; /* select loop */ while (isrunning) { rdset = fdset; r = select(nfds, &rdset, 0, 0, NULL); if (r < 0) { if (errno == EINTR) continue; printf("error: select: %s\n", strerror(errno)); terminate(1); } else if (r == 0) { printf("error: select timeout\n"); terminate(1); } for (i = 0; i < nfds; i++) { if (!FD_ISSET(i, &rdset)) continue; if (i == fifo_fd) handle_fifo_input(i); else handle_server_output(i); break; /* one fd at a time because of FD_CLR */ } } terminate(0); return 0; } void handle_fifo_input(int fd) { char buffer[BUFFER_LEN]; char *buf; char *cmd; if (readline(fd, buffer, sizeof(buffer)) < 1) { printf("error: %d: failed to read from fifo: %s\n", fd, strerror(errno)); terminate(1); } /* 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 fd) { char backup [BUFFER_LEN]; char buffer [BUFFER_LEN]; char linker_nick [MAX_NICK_LEN]; char *buf; char *cmd; char *nick; int i; if (readline(fd, buffer, sizeof(buffer)) < 1) { printf("error: %d: remote host closed connection: %s\n", fd, strerror(errno)); terminate(1); } /* clone the buffer */ strcpy(backup, buffer); buf = buffer; /* set linker nick */ strcpy(linker_nick, "linker"); for (i = 0; i < fdsuf[fd]; i++) strcat(linker_nick, "_"); /* remove CRLFs */ for (i = 0; i < (int)strlen(buf); i++) { if (buf[i] == '\r' || buf[i] == '\n') { buf[i] = '\0'; break; } } /* 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) { fdprintf(fd, "PONG %s\r\n", buf); 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, ' '); strcat(nick, "_"); fdsuf[fd]++; if (strlen(nick) > MAX_NICK_LEN) { /* remove suffix from the nick */ nick[strlen(nick) - (size_t)fdsuf[fd]] = '\0'; printf("error: cannot append suffix, nick '%s' is too big\n", nick); if (strcmp(nick, "linker") == 0) { net_del(networks[fdnet[fd]].name); } else { user_del(nick, "nick is too big"); } } else { fdprintf(fd, "NICK %s\r\n", nick); } return; } else if (strcmp(cmd, "001") == 0) { fdprintf(fd, "JOIN %s\r\n", networks[fdnet[fd]].chan); return; } else if (strcmp(cmd, "PRIVMSG") == 0) { char msg[BUFFER_LEN] = ""; int *fds; if (fdtype[fd] == FD_TYPE_LINKER) { nick_add_symb(nick, fd); if ((fds = htsearch(users, nick)) == NULL) return; split(&buf, ':'); /* set buf to msg */ privmsg_update(msg, buf, fd); for (i = 0; i < netlen; i++) { if (fds[i] > 0) fdprintf(fds[i], "PRIVMSG %s :%s\r\n", networks[i].chan, 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, fd); 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(msg, buf, fd); fdprintf(fds[i], "PRIVMSG %s :%s\r\n", user, msg); } return; } /* these messages are handled by linker */ if (fdtype[fd] == FD_TYPE_CLONE) { 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, ':'); net_update(); while (*(nick = split(&buf, ' ')) != '\0') { if (*nick == '@' || *nick == '&' || *nick == '~' || *nick == '%' || *nick == '+' || *nick == '\\') nick++; if (strcmp(nick, linker_nick) != 0) user_add(nick, fd); } return; } else if (strcmp(cmd, "JOIN") == 0) { if ((strcmp(nick, linker_nick) != 0) && (user_fds(nick, fd) == NULL)) /* if not clone */ user_add(nick, fd); return; } else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) { nick_add_symb(nick, fd); user_del(nick, buf); return; } else if (strcmp(cmd, "NICK") == 0) { int *fds, i; char *newnick; nick_add_symb(nick, fd); 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[fdnet[fd]].symb) + 2 + 1, sizeof(char)); sprintf(newnick, "%s[%s]", buf, networks[fdnet[fd]].symb); htsetkey(users, nick, newnick); for (i = 0; i < netlen; i++) { if (fds[i] > 0) fdprintf(fds[i], "NICK %s\r\n", newnick); } 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, ' '); char quit_msg[BUFFER_LEN]; /* set the quit msg */ snprintf(quit_msg, BUFFER_LEN, "kicked by %s", nick); if (strcmp(user, linker_nick) == 0) { net_del(networks[fdnet[fd]].name); return; } /* delete the user if the message from the same network */ if ((fds = user_fds(user, fd)) == NULL) { nick_add_symb(user, fd); user_del(user, quit_msg); return; } /* remove netsymb and suffix */ *strrchr(user, '[') = '\0'; /* get the original user fd index */ for (i = 0; i < netlen; i++) { if (fds[i] == -1) break; } /* set buf to msg */ split(&buf, ':'); /* send notice in the channel through linker */ fdprintf(networks[i].fd, "PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]\r\n", networks[i].chan, user, nick, networks[fdnet[fd]].name, chan, buf); /* close the kicked fd */ fd_del(fds[fdnet[fd]], quit_msg); fds[fdnet[fd]] = -2; return; } printbuffer: printf("%d: %s\n", fd, backup); } void net_add(char *name, char *symb, char *host, char *port, char *chan) { int i, fd; Network *n; /* if name or 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 no space */ if (netlen == netcap) net_resize(); /* connect */ if ((fd = fd_add(host, port)) == -1) return; /* send NICK and USER commands */ fdprintf(fd, "NICK linker\r\n"); fdprintf(fd, "USER linker 0 * :linker\r\n"); /* add a network */ fdnet[fd] = netlen; fdtype[fd] = FD_TYPE_LINKER; n = &networks[netlen]; n->fd = fd; 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, index, *fds; char quit_msg [BUFFER_LEN]; Htiter it = {0}; /* current iterator */ Htiter lastit = {0}; /* last iterator */ /* check if the name exists */ index = -1; for (i = 0; i < netlen; i++) { if (strcmp(name, networks[i].name) == 0) { index = i; break; } } if (index == -1) { printf("error: network name '%s' doesn't exist\n", name); return; } /* set the quit msg */ snprintf(quit_msg, BUFFER_LEN, "unlinking network %s", name); /* set all fds with last network index to the current index. */ for (i = 0; i < nfds; i++) { if (fdnet[i] == netlen-1) fdnet[i] = index; } while (htiterate(users, &it)) { fds = (int *)it.node->val; /* delete the user */ if (fds[index] == -1) { user_del(it.node->key, quit_msg); /* this node is deleted and has no next */ it = lastit; /* delete the clone */ } else { if (fds[index] > 0) fd_del(fds[index], quit_msg); /* swap last index with the index */ fds[index] = fds[netlen-1]; fds[netlen-1] = 0; } lastit = it; } fd_del(networks[index].fd, quit_msg); net_del_raw(index); printf("%d: network '%s' deleted\n", networks[index].fd, name); /* swap the network with the last */ networks[index] = networks[netlen-1]; netlen--; } void net_del_raw(int index) { Network *n = &networks[index]; free(n->name); free(n->symb); free(n->host); free(n->port); free(n->chan); } void net_resize(void) { Htiter it = {0}; /* resize networks array */ if ((networks = realloc(networks, ((size_t)netcap + NET_ADDEND) * sizeof(Network))) == NULL) goto error; memset(networks + netcap, 0, NET_ADDEND * sizeof(Network)); /* also resize all the user's array */ while (htiterate(users, &it)) { if ((it.node->val = realloc(it.node->val, ((size_t)netcap + NET_ADDEND) * sizeof(int))) == NULL) goto error; memset((int *)it.node->val + netcap, 0, NET_ADDEND * sizeof(int)); } netcap += NET_ADDEND; return; error: printf("error: realloc: %s\n", strerror(errno)); terminate(1); } void net_update(void) { int *fds; Htiter it = {0}; while (htiterate(users, &it)) { fds = (int *)it.node->val; /* netlen-1 can be occupied if networks are added quickly * and has not enough time to connect to it's server */ if (fds[netlen-1] == 0) { fds[netlen-1] = clone_add(netlen-1, it.node->key); } } } void user_add(char *unick, int nfd) { int i, *fds; Network *n; size_t len; char *nick; n = &networks[fdnet[nfd]]; len = strlen(unick) + strlen(n->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); /* allocate a new user */ nick = ecalloc(len, sizeof(char)); fds = ecalloc((size_t)netcap, sizeof(int)); sprintf(nick, "%s[%s]", unick, n->symb); /* clone the user on all other network */ for (i = 0; i < netlen; i++) { /* set -1 on user's network */ if (i == fdnet[nfd]) { 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 exits\n", nick); return; } for (i = 0; i < netlen; i++) { if (fds[i] > 0) fd_del(fds[i], msg); } htremove(users, nick); } int * user_fds(char *nick, int nfd) { unsigned int suf; int *fds = NULL; /* count suffix */ for (suf = 0; nick[strlen(nick)-suf-1] == '_'; suf++); /* remove suffix */ if (suf > 0) nick[strlen(nick)-suf] = '\0'; fds = htsearch(users, nick); /* if match but suffix doesn't match */ if ((fds != NULL) && (fdsuf[fds[fdnet[nfd]]] != (int)suf)) fds = NULL; /* add back suffix */ if (suf > 0) nick[strlen(nick)] = '_'; return fds; } int clone_add(int netindex, char *nick) { int fd; Network *n = &networks[netindex]; if ((fd = fd_add(n->host, n->port)) == -1) return -1; /* send NICK and USER commands */ fdprintf(fd, "NICK %s\r\n", nick); fdprintf(fd, "USER %s 0 * :%s\r\n", n->name, n->name); /* add a user */ fdnet[fd] = netindex; fdtype[fd] = FD_TYPE_CLONE; fdsuf[fd] = 0; return fd; } int fd_add(char *host, char *port) { int fd; if ((fd = dial(host, port)) == -1) return -1; FD_SET(fd, &fdset); if (fd+1 > nfds) nfds = fd+1; return fd; } void fd_del(int fd, char *msg) { fdprintf(fd, "QUIT :%s\r\n", msg); close(fd); FD_CLR(fd, &fdset); fdnet[fd] = -1; } void nick_add_symb(char *nick, int fd) { strcat(nick, "["); strcat(nick, networks[fdnet[fd]].symb); strcat(nick, "]"); } /* * trim all the nicknames to original nick * src will be destructed */ void privmsg_update(char *dst, char *src, int fd) { 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, fd) != NULL) *strrchr(src, '[') = '\0'; strcat(dst, src); strncat(dst, &d, 1); src = n; } } void terminate(int status) { int i; for (i = 0; i < nfds; i++) { if (fdnet[i] >= 0) { /* printf("%d: shutting down\n", i); */ fd_del(i, "linker shutting down"); } } /* delete all the users */ htdestroy(users); /* delete all the networks */ for (i = 0; i < netlen; i++) net_del_raw(i); free(networks); if (status == 0) printf("exit successfully\n"); 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) && (fdsuf[fds[i]] > 0)) printf("(%d)", fdsuf[fds[i]]); 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"); }