ref: 8c6dee8c450f9a7d9b97e51d47429d353a42ff9d
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
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)
fatal("usage: ticl fifo");
else
fifopath = argv[1];
/* 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, EV_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 watcher\r\nUSER watcher 0 * :watcher\r\n");
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 wnick [NICK_LEN]; /* watcher nick */
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;
}