ref: 93e6ec6e9bef1c1bc2b3fa75836b422aa96ca13b
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));
/* fd_del(pfdset[id].fd); */
} else if ((errno != EAGAIN) && (errno != EINTR)) {
printf("error: read: %s\n", strerror(errno));
}
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");
}