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;
}