ref: 5ff9649e6c51bae0dd09f218e4943fa5f4d458be
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 "utils.h" #include "htable.h" #define BUFFER_LEN 1024 #define MAX_FD 1024 #define NET_ADDEND 10 #define USER_ADDEND 100 #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 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 = NET_ADDEND; /* total memory allocated */ /* * hash table of users * key -> <nickname> + '[' + <network_symbol> + ']' * value -> array of linked clones fds indexed * corresponding to its connected network */ static Htable *users; static int isrunning = 1; /* prototype */ void print_table(void); void print_users(void); int fdadd(char *host, char *port); void fddel(int fd, char *msg); void netadd(char *name, char *symb, char *host, char *port, char *chan); void __netdel(int index); void netdel(char *name); int cloneadd(int netindex, char *nick); void clonesupdate(void); void useradd(char *unick, int nfd); void userdel(char *nick, char *msg); void append_netsymb(char *nick, int fd); int *getuserfds(char *nick, int nfd); void privmsg_update(char *dst, char *src, int fd); void handle_server(int fd); void handle_fifo(int fd); void print_table(void) { int i, *fds; Htiter it = {0}; if (netlen == 0) return; /* print networks */ printf("Networks\t"); for (i = 0; i < netlen; i++) printf("%s[%s](%d)\t", networks[i].name, networks[i].symb, networks[i].fd); printf("\n"); while (htiterate(users, &it)) { fds = (int *)it.node->val; /* print user nick */ printf("%s\t", (char *)it.node->key); /* print clones fd separated by tabs */ for (i = 0; i < netlen; i++) { printf("%d", fds[i]); if ((fds[i] > 0) && (fdsuf[fds[i]] > 0)) printf("(%d)", fdsuf[fds[i]]); printf("\t"); } printf("\n"); } } void print_users(void) { Htiter it = {0}; int index = -1; while (htiterate(users, &it)) { if (index != (int)it.index) { printf("\n%d", it.index); index = (int)it.index; } printf(" -> %s", (char *)it.node->key); } printf("\n"); } int fdadd(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 fddel(int fd, char *msg) { ircsend(fd, "QUIT :%s", msg); close(fd); FD_CLR(fd, &fdset); fdnet[fd] = -1; } void netadd(char *name, char *symb, char *host, char *port, char *chan) { int i, fd; Network *n; Htiter it = {0}; /* resize */ if (netlen < netcap) { netcap += NET_ADDEND; networks = realloc(networks, (size_t)netcap * sizeof(Network)); while (htiterate(users, &it)) it.node->val = realloc(it.node->val, (size_t)netcap * sizeof(int)); } /* if name or symbol or host:port is already taken */ for (i = 0; i < netlen; i++) { if (strcmp(networks[i].name, name) == 0) { printf("error: network name '%s' already taken\n", name); return; } if (strcmp(networks[i].symb, symb) == 0) { printf("error: network symbol '%s' already taken\n", symb); return; } /* if ((strcmp(networks[i].host, host) == 0) && (strcmp(networks[i].port, port) == 0)) { printf("error: network host '%s' and port '%s' already taken by network '%s'\n", host, port, networks[i].name); return; }*/ } printf("info: adding network '%s'\n", name); if ((fd = fdadd(host, port)) == -1) return; /* send NICK and USER commands */ ircsend(fd, "NICK linker"); ircsend(fd, "USER linker localhost %s :%s", host, name); /* 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++; } void __netdel(int index) { Network *n = &networks[index]; free(n->name); free(n->symb); free(n->host); free(n->port); free(n->chan); } void netdel(char *name) { int i, netindex = -1, *fds; char quit_msg[BUFFER_LEN]; Htiter it = {0}; /* current iterator */ Htiter lastit = {0}; /* last iterator */ /* check if the netname exists */ for (i = 0; i < netlen; i++) { if (strcmp(name, networks[i].name) == 0) { netindex = i; break; } } if (netindex == -1) { printf("error: network name '%s' doesn't exist\n", name); return; } printf("info: deleting network '%s'\n", name); /* 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] = netindex; } while (htiterate(users, &it)) { fds = (int *)it.node->val; /* delete the user */ if (fds[netindex] == -1) { userdel(it.node->key, quit_msg); /* this node is deleted and has no next */ it = lastit; /* delete the clone */ } else { if (fds[netindex] > 0) fddel(fds[netindex], quit_msg); /* swap last index with the netindex */ fds[netindex] = fds[netlen-1]; fds[netlen-1] = 0; } lastit = it; } fddel(networks[netindex].fd, quit_msg); __netdel(netindex); /* swap the network with the last */ networks[netindex] = networks[netlen-1]; netlen--; } int cloneadd(int netindex, char *nick) { int fd; Network *n = &networks[netindex]; printf("info: adding clone '%s' on network '%s'\n", nick, n->name); if ((fd = fdadd(n->host, n->port)) == -1) return -1; /* send NICK and USER commands */ ircsend(fd, "NICK %s", nick); ircsend(fd, "USER username localhost %s :real name", n->host); /* add a user */ fdnet[fd] = netindex; fdtype[fd] = FD_TYPE_CLONE; fdsuf[fd] = 0; return fd; } /* update clones in the last network added */ void clonesupdate(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] = cloneadd(netlen-1, it.node->key); } } void useradd(char *unick, int nfd) { int i, *fds; Network *n; size_t l; char *nick; n = &networks[fdnet[nfd]]; /* too long name */ l = strlen(unick) + strlen(n->symb) + 2 + 1; if (l > 16) return; nick = ecalloc(l, sizeof(char)); fds = ecalloc((size_t)netcap, sizeof(int)); sprintf(nick, "%s[%s]", unick, n->symb); /* resize */ if ((users->cap - users->len) < USER_ADDEND) htresize(users, users->cap + USER_ADDEND); for (i = 0; i < netlen; i++) { /* set -1 on user network */ if (i == fdnet[nfd]) { fds[i] = -1; } else { fds[i] = cloneadd(i, nick); } } if (htinsert(users, nick, fds) == -1) fatal("error: user '%s' already exist", nick); } void userdel(char *nick, char *msg) { int *fds, i; if ((fds = htsearch(users, nick)) == NULL) return; for (i = 0; i < netlen; i++) { if (fds[i] > 0) fddel(fds[i], msg); } htremove(users, nick); } void append_netsymb(char *nick, int fd) { strcat(nick, "["); strcat(nick, networks[fdnet[fd]].symb); strcat(nick, "]"); } int * getuserfds(char *nick, int nfd) { unsigned int l; int *fds = NULL; for (l = 0; nick[strlen(nick)-l-1] == '_'; l++); if (l > 0) nick[strlen(nick)-l] = '\0'; fds = htsearch(users, nick); /* if match but suffix doesn't match */ if ((fds != NULL) && (fdsuf[fds[fdnet[nfd]]] != (int)l)) fds = NULL; /* add the removed delimiter */ if (l > 0) nick[strlen(nick)] = '_'; return fds; } /* trim all the nicknames to original nick * this 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 (getuserfds(src, fd) != NULL) *strrchr(src, '[') = '\0'; strcat(dst, src); strncat(dst, &d, 1); src = n; } } void handle_server(int fd) { char backup [BUFFER_LEN]; char buffer [BUFFER_LEN]; char *buf; char *cmd; char *nick; int i; char linker[BUFFER_LEN] = "linker"; if (dreadline(fd, buffer, sizeof(buffer)) < 1) fatal("error: %d: remote host closed connection: %s", fd, strerror(errno)); strcpy(backup, buffer); buf = buffer; /* 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) { ircsend(fd, "PONG %s", buf); return; } /* strip nick from first column */ nick = split(&cmd, '!'); if (nick[0] == ':') nick++; /* second column */ cmd = split(&buf, ' '); /* 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; } /* set linker nick */ for (i = 0; i < fdsuf[fd]; i++) strcat(linker, "_"); /* 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, "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]++; ircsend(fd, "NICK %s", nick); return; } else if (strcmp(cmd, "001") == 0) { ircsend(fd, "JOIN %s", networks[fdnet[fd]].chan); return; /* ========================= only linker ========================= */ } else if (strcmp(cmd, "353") == 0) { char *nick; split(&buf, ':'); clonesupdate(); while (*(nick = split(&buf, ' ')) != '\0') { if (*nick == '@' || *nick == '&' || *nick == '~' || *nick == '%' || *nick == '+' || *nick == '\\') nick++; if (strcmp(nick, linker) != 0) useradd(nick, fd); } return; } else if (strcmp(cmd, "JOIN") == 0) { if ((strcmp(nick, linker) != 0) && (getuserfds(nick, fd) == NULL)) /* if not clone */ useradd(nick, fd); return; } else if ((strcmp(cmd, "QUIT") == 0) || (strcmp(cmd, "PART") == 0)) { append_netsymb(nick, fd); userdel(nick, buf); return; } else if (strcmp(cmd, "NICK") == 0) { int *fds, i; char *newnick; append_netsymb(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) ircsend(fds[i], "NICK %s", 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) == 0) { netdel(networks[fdnet[fd]].name); return; } /* delete the user if the message from the same network */ if ((fds = getuserfds(user, fd)) == NULL) { append_netsymb(user, fd); userdel(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 */ ircsend(networks[i].fd, "PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]", networks[i].chan, user, nick, networks[fdnet[fd]].name, chan, buf); /* close the kicked fd */ fddel(fds[fdnet[fd]], quit_msg); fds[fdnet[fd]] = -2; return; /* ========================= only linker ========================= */ } else if (strcmp(cmd, "PRIVMSG") == 0) { char msg[BUFFER_LEN] = ""; int *fds; if (fdtype[fd] == FD_TYPE_LINKER) { append_netsymb(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) ircsend(fds[i], "PRIVMSG %s :%s", 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; append_netsymb(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); ircsend(fds[i], "PRIVMSG %s :%s", user, msg); } return; } printbuffer: printf("%d: %s\n", fd, backup); } void handle_fifo(int fd) { char buffer[BUFFER_LEN]; char *buf; char *cmd; if (dreadline(fd, buffer, sizeof(buffer)) == -1) fatal("error: dreadline() failed"); /* 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 == '\0') || (*symb == '\0') || (*host == '\0') || (*port == '\0') || (*chan == '\0')) printf("usage: netadd <name> <symbol> <hostname> <port> <channel>\n"); netadd(name, symb, host, port, chan); } else if (strcmp(cmd, "netdel") == 0) { char *name = buf; if (*name == '\0') printf("usage: netdel <name>\n"); netdel(name); } else if (strcmp(cmd, "print") == 0) { print_table(); } 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); } } int main(int argc, char *argv[]) { int fifofd, r, i; fd_set rdset; struct stat fifo_st; /* check arguments */ if (argc != 2) fatal("usage: %s <fifo>", argv[0]); /* warning */ printf("WARNING: This software is provided 'as-is', without any express or " "implied warranty.\nIn no event shall the author(s) be liable " "for any damages arising from the use of this software.\n"); /* init networks and users */ networks = ecalloc((size_t)netcap, sizeof(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)) fatal("error: file %s is not a fifo", argv[1]); } else if (mkfifo(argv[1], S_IRWXU) != 0) { fatal("error: cannot create fifo file %s", argv[1]); } fifofd = open(argv[1], O_RDWR); if (fifofd < 0) fatal("error: cannot open() '%s'", argv[1]); /* set stdout to unbufferd */ setvbuf(stdout, NULL, _IONBF, 0); /* initialize fdset */ FD_ZERO(&fdset); FD_SET(fifofd, &fdset); nfds = fifofd + 1; /* select loop */ while (isrunning) { rdset = fdset; r = select(nfds, &rdset, 0, 0, NULL); if (r < 0) { if (errno == EINTR) continue; fatal("error: select: %s", strerror(errno)); } else if (r == 0) { fatal("error: select() timeout"); } for (i = 0; i < nfds; i++) { if (FD_ISSET(i, &rdset)) { if (i == fifofd) handle_fifo(i); else handle_server(i); /* handle one fd at one time because FD_CLR */ break; } } } for (i = 0; i < nfds; i++) { if (fdnet[i] > 0) { printf("shutting down %d\n", i); fddel(i, "linker shutting down"); } } /* delete all the users */ htdestroy(users); /* delete all the networks */ for (i = 0; i < netlen; i++) __netdel(i); free(networks); return 0; }