/* ARISA - IRC Network Functions * Copyright (C) 2003, 2004 Carl Ritson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #define WANT_POLL #include "arisa.h" #include "line-drive.h" #include #include #include #include #include #include #include /* structures/enums */ #define MODEBUFSIZE 32 /* Message types - only those we are interested in */ enum mst_t { UNKNOWN = 0, PING = 901, // 900 commands, internal numerics ERROR = 902, PRIVMSG = 903, NOTICE = 904, JOIN = 905, PART = 906, KICK = 907, NICK = 908, QUIT = 909, // KILL = 910, MODE = 911, TOPIC = 912, RPL_WELCOME = 001, // RFC commands we are interested in RPL_NETINFO = 005, // this isn't the RFC definition RPL_USERHOST = 302, // RPL_WHOISUSER = 311, RPL_CHANNELMODEIS = 324, RPL_TOPIC = 332, RPL_NAMREPLY = 353, RPL_BANLIST = 367, // RPL_WHOIS_CUSTOM = 378, ERR_NOSUCHNICK = 401, ERR_TOOMANYCHANNELS = 405, ERR_ERRONEUSNICKNAME = 432, ERR_NICKNAMEINUSE = 433, ERR_NICKCOLLISION = 436, ERR_NOTONCHANNEL = 442, ERR_YOUREBANNEDCREEP = 465, ERR_CHANNELISFULL = 471, ERR_INVITEONLYCHAN = 473, ERR_BANNEDFROMCHAN = 474, ERR_BADCHANNELKEY = 475 }; typedef struct netlocal_t { int socket; int signal_fd; int server_no; netinfo_t *info; network_t *network; time_t last_connect; time_t last_message; time_t last_join; time_t last_send; pqueue_t rqueue; int *window; int window_size; int on_failback_ip; /* send and recv line driving buffers */ char *snd_msg; linebuf_t *snd_buf; linebuf_t *rcv_buf; /* these buffers are used to store character modes of the network */ char chantype[MODEBUFSIZE]; char acmode[MODEBUFSIZE]; // argument carrying modes char pcmode[MODEBUFSIZE]; // partial argument carrying modes char ncmode[MODEBUFSIZE]; // non-argument carrying modes char mprefix[MODEBUFSIZE]; // user modes char nprefix[MODEBUFSIZE]; // names prefixes, 1-1 mprefix } netlocal_t; /* INTERNAL Functions */ static void set_default_modes(netlocal_t *l) { xstrncpy(l->chantype, "#&", MODEBUFSIZE); xstrncpy(l->acmode, "bk", MODEBUFSIZE); xstrncpy(l->pcmode, "l", MODEBUFSIZE); xstrncpy(l->ncmode, "inpst",MODEBUFSIZE); xstrncpy(l->mprefix, "ov", MODEBUFSIZE); xstrncpy(l->nprefix, "@+", MODEBUFSIZE); } static inline int is_channel(netlocal_t *l, const char *str) { return (strchr(l->chantype,str[0]) != NULL); } static inline int is_chan_nick_mode(netlocal_t *l, char m) { return (strchr(l->mprefix,m) != NULL); } static inline int is_chan_nick_prefix(netlocal_t *l, char p) { return (strchr(l->nprefix,p) != NULL); } static inline int is_chan_arg_mode(netlocal_t *l, char m) { return (strchr(l->acmode,m) != NULL); } static inline int is_chan_parg_mode(netlocal_t *l, char m) { return (strchr(l->pcmode,m) != NULL); } static inline int is_chan_noarg_mode(netlocal_t *l, char m) { return (strchr(l->ncmode,m) != NULL); } static char nprefix_from_mode(netlocal_t *l, char m) { int i; for(i = 0; l->mprefix[i] != '\0'; ++i) { if(l->mprefix[i] == m) return l->nprefix[i]; } return ' '; } #define EM_LOCK 0x1 #define EM_DUP 0x2 #define EM_PRIORITY 0x4 static int is_dcc_ctcp(const char *msg); // pre-declaration static int enqueue_msg(netinfo_t *ni, const char *msg, int flags) { pqueue_t *pq = NULL; if(ni == NULL || msg == NULL) return -1; if(flags & EM_LOCK) LOCK(ni); if(strncasecmp("PING",msg,4) == 0 || strncasecmp("PONG",msg,4) == 0 || strncasecmp("NICK",msg,4) == 0) pq = &(ni->p_squeue); else if(strncasecmp("JOIN",msg,4) == 0) pq = &(ni->j_squeue); else if(is_dcc_ctcp(msg)) pq = &(ni->d_squeue); else pq = &(ni->r_squeue); //D("pq: %p, msg: \"%s\"",pq,msg); if(flags & EM_PRIORITY) pqueue_push_front(pq, flags & EM_DUP ? xstrdup(msg) : (void *)msg); else pqueue_push_back(pq, flags & EM_DUP ? xstrdup(msg) : (void *)msg); if(flags & EM_LOCK) UNLOCK(ni); return 0; } static void irc_init(netlocal_t *l, network_t *n) { int fds[2] = {-1,-1}; if(pipe(fds) != 0) { LOGTP(L_ERR,"Failed to allocate signaling pipe"); } /* signal pipe should be nonblocking */ fcntl(fds[0],F_SETFL,O_NONBLOCK); fcntl(fds[1],F_SETFL,O_NONBLOCK); l->socket = -1; l->signal_fd = fds[0]; // reading side l->server_no = -1; l->window = NULL; l->window_size = 0; l->info = alloc_netinfo(); l->network = n; l->snd_msg = NULL; l->snd_buf = alloc_linebuf(512); l->rcv_buf = alloc_linebuf(512); set_default_modes(l); pqueue_init(&l->rqueue,0); l->on_failback_ip = 0; pqueue_set_notification_fd(&l->info->p_squeue,fds[1]); pqueue_set_notification_fd(&l->info->j_squeue,fds[1]); pqueue_set_notification_fd(&l->info->d_squeue,fds[1]); pqueue_set_notification_fd(&l->info->r_squeue,fds[1]); LOCK(n); n->info = l->info; n->state = NETWORK_RUNNING; UNLOCK(n); } static void irc_free(netlocal_t *l) { LOCK(l->network); LOCK(l->info); l->network->state = NETWORK_OFFLINE; //D("close: %d",l->signal_fd); close(l->signal_fd); if(l->snd_msg != NULL) { xfree(l->snd_msg); l->snd_msg = NULL; } free_linebuf(l->snd_buf); free_linebuf(l->rcv_buf); pqueue_clear(&l->rqueue,xfree,1); // do 1 close, since they share an fd pqueue_clear(&l->info->p_squeue,xfree,1); pqueue_set_notification_fd(&l->info->p_squeue,-1); pqueue_set_notification_fd(&l->info->j_squeue,-1); pqueue_set_notification_fd(&l->info->d_squeue,-1); pqueue_set_notification_fd(&l->info->r_squeue,-1); if(l->window != NULL) xfree(l->window); l->network->info = NULL; UNLOCK(l->network); UNLOCK(l->info); free_netinfo(l->info); } static int pick_server(netlocal_t *l) { char *p1,*p2,*p3; int serv; LOCK(l->network); LOCK(l->info); /* free existing server */ if(l->info->server != NULL) { xfree(l->info->server); l->info->server = NULL; } /* free existing password */ if(l->info->password != NULL) { xfree(l->info->password); l->info->password = NULL; } /* check there are servers to pick from */ if(l->network->servers == NULL) { LOGTP(L_ERR,"Failed to pick a server, due to there being none specified",l->network->name); UNLOCK(l->info); UNLOCK(l->network); return -1; } /* pick a "random" server, preferably not the last one */ if(l->network->no_servers > 2) { do { serv = lrand(l->network->no_servers); } while(serv == l->server_no); } else if(l->network->no_servers == 2) { if(l->server_no == 0) { serv = 1; } else { serv = 0; } } else { serv = 0; } l->server_no = serv; /* split the server down to hostname/ip and port */ p1 = xstrdup(l->network->servers[serv]); p2 = strchr(p1,':'); if(p2 != NULL) { *p2 = '\0'; p2++; p3 = strchr(p2,'/'); if(p3 != NULL) { *p3 = '\0'; p3++; l->info->password = xstrdup(p3); } l->info->port = (unsigned short) atoi(p2); } else { p3 = strchr(p1,'/'); if(p3 != NULL) { *p3 = '\0'; p3++; l->info->password = xstrdup(p3); } l->info->port = (unsigned short) 6667; } l->info->server = xstrdup(p1); xfree(p1); UNLOCK(l->info); UNLOCK(l->network); return 0; } static int connect_server(netlocal_t *l) { char buffer[32],*tmp; struct sockaddr_in addr,baddr; unsigned short server_port; int ret,val; time_t now; LOCK(l->info); if(l->info->server == NULL) { UNLOCK(l->info); LOGTP(L_ERR,"Attempt to connect with no server picked",l->network->name); return -1; } tmp = xstrdup(l->info->server); server_port = l->info->port; UNLOCK(l->info); addr.sin_family = AF_INET; addr.sin_port = htons(server_port); ret = aresolv_nametoaddr(global->aresolver,AF_INET,tmp,&(addr.sin_addr)); if(ret != 0) { LOGTP(L_ERR,"Failed to resolve server %s",tmp); xfree(tmp); return -1; } else xfree(tmp); baddr.sin_family= AF_INET; baddr.sin_port = 0; LOCK(l->network); if(l->network->settings->bind_ip != NULL) { tmp = xstrdup(l->network->settings->bind_ip); UNLOCK(l->network); ret = aresolv_nametoaddr(global->aresolver,AF_INET, tmp,&(baddr.sin_addr)); if(ret != 0) { LOGTP(L_ERR, "Failed to resolve bind-ip %s, not using it", tmp); baddr.sin_addr.s_addr = INADDR_ANY; } xfree(tmp); } else { UNLOCK(l->network); baddr.sin_addr.s_addr = INADDR_ANY; } /* hostptr is now void, since we reuse the buffer */ inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer)); LOCK(l->network); LOGTP(L_IRC,"Connecting to %s:%d",buffer,server_port); l->network->state = NETWORK_CONNECTING; UNLOCK(l->network); if(l->socket != -1) { //D("close: %d",l->socket); close(l->socket); } l->socket = socket(PF_INET, SOCK_STREAM, 0); //D("socket: %d",l->socket); if(l->socket == -1) { char errbuf[96]; LOGTP(L_ERR,"Failed to get socket for connection to %s:%d, Error: %s", buffer,server_port, lstrerror_r(errno,errbuf,sizeof(errbuf))); return -1; } if(bind(l->socket,(struct sockaddr *)&baddr,sizeof(baddr)) == -1) { char errbuf[96]; val = errno; inet_ntop(AF_INET, &baddr.sin_addr, buffer, sizeof(buffer)); LOGTP(L_ERR,"Failed to bind to %s, Error: %s", buffer,lstrerror_r(val,errbuf,sizeof(errbuf))); //D("close: %d",l->socket); close(l->socket); l->socket = -1; return -1; } if(interruptable_connect(l->socket,(struct sockaddr *)&addr, sizeof(addr)) == -1) { char errbuf[96]; LOGTP(L_ERR,"Failed to connect to %s:%d, Error: %s", buffer,server_port, lstrerror_r(errno,errbuf,sizeof(errbuf))); //D("close: %d",l->socket); close(l->socket); l->socket = -1; return -1; } LOCK(l->network); LOGTP(L_IRC,"Connected to %s:%d",buffer,server_port); l->network->state = NETWORK_CONNECTED; UNLOCK(l->network); val = 1; if(setsockopt(l->socket,SOL_SOCKET,SO_KEEPALIVE,&val,sizeof(val)) == -1) { char errbuf[96]; LOGTP(L_ERR,"Failed to set SO_KEEPALIVE for connection to %s:%d, Error: %s", buffer,server_port, lstrerror_r(errno,errbuf,sizeof(errbuf))); // Not Fatal? } if(fcntl(l->socket,F_SETFL,O_NONBLOCK) == -1) { char errbuf[96]; LOGTP(L_ERR,"Failed to set O_NONBLOCK for connection to %s:%d, Error :%s", buffer,server_port, lstrerror_r(errno,errbuf,sizeof(errbuf))); //D("close: %d",l->socket); close(l->socket); l->socket = -1; return -1; // Fatal } /* Set all timestamps to now */ now = xtime(); l->last_connect = now; l->last_message = now; l->last_join = now; l->last_send = now; if(l->snd_msg != NULL) { enqueue_msg(l->info,l->snd_msg,EM_LOCK); l->snd_msg = NULL; } clear_linebuf(l->snd_buf); clear_linebuf(l->rcv_buf); LOCK(l->info); l->info->connected = now; pqueue_clear(&l->info->p_squeue,xfree,0); pqueue_clear(&l->info->j_squeue,xfree,0); UNLOCK(l->info); return 0; } /* a NULL disconnect message (*msg) means error state disconnect */ static void disconnect_server(netlocal_t *l, char *msg) { char buffer[512]; int i; if(l->socket != -1) { if(msg != NULL) { snprintf(buffer,sizeof(buffer)-1,"QUIT :%s\r\n",msg); send(l->socket,buffer,strlen(buffer),0); } //D("shutdown: %d", l->socket); shutdown(l->socket,SHUT_RDWR); //D("close: %d",l->socket); close(l->socket); l->socket = -1; } LOCK(l->network); LOCK(l->info); LOGTP(L_INF,"Disconnected %s:%d, %s%s", l->info->server,l->info->port, msg != NULL ? "Reason: " : "", msg != NULL ? msg : ""); if(l->info->nick != NULL) { xfree(l->info->nick); l->info->nick = NULL; } if(l->info->server != NULL) { xfree(l->info->server); l->info->server = NULL; } pqueue_clear(&l->info->p_squeue,xfree,0); pqueue_clear(&l->info->j_squeue,xfree,0); nickhash_flush(l->info->nick_tracker); /* clear channels */ UNLOCK(l->info); l->network->state = NETWORK_RUNNING; if(l->window != NULL) { xfree(l->window); l->window = NULL; l->window_size = 0; } if(l->network->channels != NULL) { for(i = 0; i < l->network->no_channels; ++i) { LOCK(l->network->channels[i]); l->network->channels[i]->joined = 0; UNLOCK(l->network->channels[i]); } } UNLOCK(l->network); pqueue_clear(&l->rqueue,xfree,0); } static int recv_msgs(netlocal_t *l, int limit) { int count = 0; int ret; do { ret = recv_line(l->socket, l->rcv_buf); if(ret > 0) { pqueue_push_back(&l->rqueue,dup_linebuf(l->rcv_buf)); l->last_message = xtime(); count++; } else if(errno != 0 && errno != EAGAIN && errno != EINTR) { char errbuf[96]; LOGTP(L_ERR,"Got Error: %s", lstrerror_r(errno,errbuf,sizeof(errbuf))); return -1; } } while(ret > 0 && count < limit); return count; } static void tokenise_msg(char *msg, char ***tokens, int *no_tokens) { char *tail = NULL; int pos; *tokens = NULL; *no_tokens = 0; if(msg[0] == '\0') return; for(pos = 0; msg[pos] != '\0'; ++pos) { if(msg[pos] == ' ' && msg[pos+1] == ':') { msg[pos] = '\0'; tail = &(msg[pos+2]); break; } } *tokens = split_opt(msg,msg,no_tokens); if(tail != NULL) { (*no_tokens)++; *tokens = xreloc(*tokens,sizeof(char *)*(*no_tokens)); (*tokens)[(*no_tokens)-1] = tail; } } // FIXME: move this to the top of the file with the mst_t enum definition static struct { const char *name; enum mst_t type; int len; } message_types[] = { // optimise order to speed look up {"PRIVMSG", PRIVMSG, 7}, {"PING", PING, 4}, {"NOTICE", NOTICE, 6}, {"JOIN", JOIN, 4}, {"PART", PART, 4}, {"MODE", MODE, 4}, {"NICK", NICK, 4}, {"QUIT", QUIT, 4}, {"KICK", KICK, 4}, {"ERROR", ERROR, 5}, {"TOPIC", TOPIC, 5}, {"RPL_WELCOME", RPL_WELCOME, 0}, {"RPL_NETINFO", RPL_NETINFO, 0}, {"RPL_USERHOST", RPL_USERHOST, 0}, // {"RPL_WHOISUSER", RPL_WHOISUSER, 0}, // {"RPL_WHOIS_CUSTOM", RPL_WHOIS_CUSTOM, 0}, {"RPL_CHANNELMODEIS", RPL_CHANNELMODEIS, 0}, {"RPL_TOPIC", RPL_TOPIC, 0}, {"RPL_NAMREPLY", RPL_NAMREPLY, 0}, {"RPL_BANLIST", RPL_BANLIST, 0}, {"ERR_TOOMANYCHANNELS", ERR_TOOMANYCHANNELS, 0}, {"ERR_ERRONEUSNICKNAME",ERR_ERRONEUSNICKNAME, 0}, {"ERR_NICKNAMEINUSE", ERR_NICKNAMEINUSE, 0}, {"ERR_NICKCOLLISION", ERR_NICKCOLLISION, 0}, {"ERR_NOTONCHANNEL", ERR_NOTONCHANNEL, 0}, {"ERR_YOUREBANNEDCREEP",ERR_YOUREBANNEDCREEP, 0}, {"ERR_CHANNELISFULL", ERR_CHANNELISFULL, 0}, {"ERR_INVITEONLYCHAN", ERR_INVITEONLYCHAN, 0}, {"ERR_BANNEDFROMCHAN", ERR_BANNEDFROMCHAN, 0}, {"ERR_BADCHANNELKEY", ERR_BADCHANNELKEY, 0}, {NULL} }; static enum mst_t str_to_mst(const char *type) { int i,tmp; if(type == NULL) return UNKNOWN; if(!isdigit(type[0])) { tmp = strlen(type); for(i = 0; message_types[i].name != NULL; ++i) { if(message_types[i].len == tmp) { if(strcasecmp(message_types[i].name,type) == 0) return message_types[i].type; } } } else if(strlen(type) == 3) { tmp = atoi(type); /* This means only known numeric types go through, * other types will be mapped to unknown. */ for(i = 0; message_types[i].name != NULL; ++i) { if(message_types[i].type == tmp) return message_types[i].type; } } return UNKNOWN; } /* p2n - prefix to nickname */ static char *p2n(const char *prefix) { char *nick; int i; if(prefix == NULL) return strdup(""); for(i = 0; prefix[i] != '!' && prefix[i] != '\0'; ++i) ; if(prefix[i] == '!') { nick = xalloc(i+1); memcpy(nick,prefix,i); nick[i] = '\0'; } else { nick = strdup(prefix); } return nick; } /* p2h - prefix to hostname */ static char *p2h(const char *prefix) { char *host; int i,len; if(prefix == NULL) return strdup(""); len = strlen(prefix); for(i = 0; prefix[i] != '@' && prefix[i] != '\0'; ++i) ; if(prefix[i] == '@') { host = xalloc((len - i) + 1); memcpy(host,prefix+i+1,(len - i)); host[len - i] = '\0'; } else { host = strdup(""); } return host; } // lock assumed static void channel_reset_state(channel_t *chan) { strarr_free(&(chan->bans),&(chan->no_bans)); if(chan->mode != NULL) { xfree(chan->mode); chan->mode = NULL; } if(chan->topic != NULL) { xfree(chan->topic); chan->topic = NULL; } chan->limit = 0; } static void channel_change_mode(channel_t *chan, int op, char mode, const char *arg) { char buffer[64]; int i; LOCK(chan); if(mode == 'b') { for(i = 0; i < chan->no_bans; ++i) { if(strcasecmp(chan->bans[i],arg) == 0) break; } if(i >= chan->no_bans && op == MODE_ADD) PTRARR_ADD(&(chan->bans),&(chan->no_bans),xstrdup(arg)); else if(i < chan->no_bans && op == MODE_DEL) PTRARR_DEL(&(chan->bans),&(chan->no_bans),chan->bans[i]); } else if(mode == 'k' && chan->track_key) { if(op == MODE_ADD) { if(chan->key != NULL) xfree(chan->key); chan->key = xstrdup(arg); } else if(op == MODE_DEL && chan->key != NULL) { xfree(chan->key); chan->key = NULL; } } else if(mode == 'e') { // ignore } else if(mode == 'l') { if(op == MODE_ADD) chan->limit = atoi(arg); else if(op == MODE_DEL) chan->limit = 0; } else { if(chan->mode != NULL) xstrncpy(buffer,chan->mode,sizeof(buffer)); else buffer[0] = '\0'; for(i = 0; buffer[i] != '\0'; ++i) { if(buffer[i] == mode) break; } if(op == MODE_ADD && buffer[i] == '\0') { if(i+2 < sizeof(buffer)) { buffer[i] = mode; buffer[i+1] = '\0'; } if(chan->mode != NULL) xfree(chan->mode); chan->mode = xstrdup(buffer); } else if(op == MODE_DEL && buffer[i] == mode) { for(; buffer[i] != '\0'; ++i) buffer[i] = buffer[i+1]; if(chan->mode != NULL) xfree(chan->mode); chan->mode = xstrdup(buffer); } } UNLOCK(chan); } static int is_dcc_ctcp(const char *msg) { char *p; if(strncasecmp(msg,"PRIVMSG",7) == 0 || strncasecmp(msg,"NOTICE",6) == 0) { if((p = strchr(msg,' ')) != NULL) { if((p = strchr(p+1,' ')) != NULL) { if((p = strchr(p,':')) != NULL) { if(strncasecmp(p,":\001DCC",5) == 0) return 1; } } } } return 0; } static void handle_ctcp(network_t *n, channel_t *chan, const char *host, const char *hostmask, const char *nick, const char *msg) { char buffer[512], temp[512]; int i,j; /* Copy the CTCP quoted part of the message into our buffer. */ for(i = 0; msg[i] != '\001' && msg[i] != '\0'; ++i) ; if(msg[i] != '\001') return; for(i++, j = 0; msg[i] != '\001' && msg[i] != '\0'; ++i, ++j) buffer[j] = msg[i]; buffer[j] = '\0'; if(strncasecmp(buffer,"ACTION",6) == 0) { return; // trap this here to save processing } else if(strcasecmp(buffer,"VERSION") == 0) { snprintf(buffer,sizeof(buffer), "VERSION %s",ARISA_VERSION); irc_send_ctcp_notice(n,nick,buffer); LOGTP(L_IRC,"CTCP VERSION From %s",nick); } else if(strncasecmp(buffer,"PING",4) == 0 && strlen(buffer) > 5) { snprintf(temp,sizeof(temp), "PING %s",buffer+5); irc_send_ctcp_notice(n,nick,temp); LOGTP(L_IRC,"CTCP PING From %s:%s",nick,buffer+4); } else if(strncasecmp(buffer,"DCC",3) == 0) { dcc_cmd(n,host,hostmask,nick,buffer); } else if(strncasecmp(buffer,"XDCC",4) == 0) { xdcc_cmd(n,host,hostmask,nick,buffer); } else LOGTP(L_IRC,"CTCP \"%s\" From %s",buffer,nick); } static void xmit_msg(network_t *net, enum mst_t type, channel_t *chan, const char *nick, const char *data, int r) { const size_t bufsize = 510; char *buffer; int i; if(net == NULL || nick == NULL) return; LOCK(net); if(net->no_listeners == 0 && chan == NULL) { UNLOCK(net); return; } else if(net->no_listeners == 0 && chan != NULL) { LOCK(chan); if(chan->no_listeners == 0) { UNLOCK(chan); UNLOCK(net); return; } } buffer = xalloc(bufsize); buffer[0] = '\0'; if((type == PRIVMSG || type == NOTICE) && data != NULL) { if(chan != NULL) { if(strncasecmp(data,"\001ACTION",7) != 0) snprintf(buffer,bufsize, "%%K>>%%n[%s/%s] %s<%%n%s%s>%%n %s", net->name,chan->name, r == 1 ? COLOUR_BLUE : COLOUR_MAGENTA, nick, r == 1 ? COLOUR_BLUE : COLOUR_MAGENTA, data); else { snprintf(buffer,bufsize, "%%K>>%%n[%s/%s] %%K*%%n %%9%s%%9 %s", net->name,chan->name,nick, data + 8); buffer[strlen(buffer)-1] = '\0'; } } else if(r == 1) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%b<%%n%s%%b>%%n %s", net->name,nick,data); } else snprintf(buffer,bufsize, "%%K>>%%n[%s] %%m[%%n%s%%m]%%n %s", net->name,nick,data); } else if(type == JOIN && chan != NULL) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%R-%%W:%%R-%%n %%C%s%%n joined %s", net->name,nick,chan->name); } else if(type == PART && chan != NULL) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%R-%%W:%%R-%%n %%c%s%%n left %s", net->name,nick,chan->name); } else if(type == KICK && chan != NULL) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%R-%%W:%%R-%%n %%c%s%%n was kicked from %s by %%c%s%%n", net->name,nick,chan->name, data != NULL ? data : ""); } else if(type == NICK && data != NULL) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%R-%%W:%%R-%%n %%W%s%%n changed nick to %%c%s%%n", net->name,nick,data); } else if(type == QUIT) { snprintf(buffer,bufsize, "%%K>>%%n[%s] %%R-%%W:%%R-%%n %%W%s%%n quit", net->name,nick); } if(buffer[0] != '\0') { irc_strip_colours(buffer); if(chan != NULL) { for(i = 0; i < chan->no_listeners; ++i) pqueue_push_back(chan->listeners[i], xstrdup(buffer)); } for(i = 0; i < net->no_listeners; ++i) pqueue_push_back(net->listeners[i],xstrdup(buffer)); } xfree(buffer); if(chan != NULL) UNLOCK(chan); UNLOCK(net); } static void rxmit_msg(network_t *net, const char *msg) { char buffer[512],nick[32]; channel_t *chan; char **parts; int i,no_parts; if((strncasecmp("PRIVMSG",msg,7) != 0 && strncasecmp("NOTICE",msg,6) != 0) || is_dcc_ctcp(msg)) return; xstrncpy(buffer,msg,sizeof(buffer)); for(i = strlen(buffer) - 1; i >= 0; --i) { if(buffer[i] == '\n' || buffer[i] == '\r') buffer[i] = '\0'; else break; } if(i < 0) return; no_parts = 3; parts = split_opt(buffer,buffer,&no_parts); if(no_parts != 3) { if(parts != NULL) xfree(parts); return; } parts[2]++; // remove leading : chan = irc_channel_find(net,parts[1]); nick[0] = '\0'; if(chan != NULL) { LOCK(net); if(net->info != NULL) { LOCK(net->info); xstrncpy(nick,net->info->nick,sizeof(nick)); UNLOCK(net->info); } UNLOCK(net); } else xstrncpy(nick,parts[1],sizeof(nick)); if(nick[0] != '\0') xmit_msg(net,PRIVMSG,chan,nick,parts[2],0); xfree(parts); } static void perform_a_magic_trick(network_t *n, const char *nick, const char *host) { /* Okay, in a strange mood when I named this function. * What it goes and finds sends/chats/uploads/queues for nick * which are missing a host and fills it in. */ int i,j; LOCK(global); for(i = 0; i < global->no_interfaces; ++i) { interface_t *intf = global->interfaces[i]; LOCK(intf); if(intf->type == INTERFACE_SEND) { for(j = 0; j < intf->send.no_pools; ++j) { pool_t *p = intf->send.pools[j]; int k; LOCK(p); for(k = 0; k < p->no_csends; ++k) { LOCK(p->csends[k]); if(p->csends[k]->host == NULL && p->csends[k]->network == n && irc_strcasecmp(p->csends[k]->nick,nick) == 0) p->csends[k]->host = xstrdup(host); UNLOCK(p->csends[k]); } UNLOCK(p); } } else if(intf->type == INTERFACE_RECV) { for(j = 0; j < intf->recv.no_uploads; ++j) { LOCK(intf->recv.uploads[j]); if(intf->recv.uploads[j]->host == NULL && intf->recv.uploads[j]->network == n && intf->recv.uploads[j]->nick != NULL) if(irc_strcasecmp(intf->recv.uploads[j]->nick,nick) == 0) intf->recv.uploads[j]->host = xstrdup(host); UNLOCK(intf->recv.uploads[j]); } } else if(intf->type == INTERFACE_CHAT) { for(j = 0; j < intf->chat.no_chats; ++j) { LOCK(intf->chat.chats[j]); if(intf->chat.chats[j]->host == NULL && intf->chat.chats[j]->network == n && intf->chat.chats[j]->nick != NULL) if(irc_strcasecmp(intf->chat.chats[j]->nick,nick) == 0) intf->chat.chats[j]->host = xstrdup(host); UNLOCK(intf->chat.chats[j]); } } UNLOCK(intf); } for(i = 0; i < global->no_queues; ++i) { queue_t *q = global->queues[i]; LOCK(q); for(j = 0; j < q->no_qsends; ++j) { LOCK(q->qsends[j]); if(q->qsends[j]->host == NULL && q->qsends[j]->network == n && irc_strcasecmp(q->qsends[j]->nick,nick) == 0) q->qsends[j]->host = xstrdup(host); UNLOCK(q->qsends[j]); } UNLOCK(q); } UNLOCK(global); } static void handle_ping(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char buffer[512]; snprintf(buffer,sizeof(buffer),"PONG %s",tokens[1]), enqueue_msg(l->info,buffer,EM_LOCK | EM_DUP); } static void handle_error(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { disconnect_server(l,tokens[1]); } //4 static void handle_privmsg(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1], *dst = tokens[2], *data = tokens[3]; char *host = NULL, *nick = NULL, *tmp = NULL; char buffer[512]; channel_t *chan = NULL; if(is_channel(l,dst)) { /* channel message */ chan = irc_channel_find(l->network,dst); } else { chan = NULL; } if(chan == NULL) { if(ignore_test(global->ignore_list,src)) { return; // IGNORE } } host = p2h(src); nick = p2n(src); xmit_msg(l->network,type,chan,nick,data,1); if(data[0] == '\001') { handle_ctcp(l->network,chan,host,src,nick,data); } else if(strncasecmp(data,"!list",5) == 0) { tmp = strchr(data,' '); if(tmp == NULL && strlen(data) == 5) xdcc_info(l->network,chan,host,src,nick); else if(tmp != NULL) { while(isspace(*tmp)) tmp++; if(*tmp == '\0') xdcc_info(l->network,chan,host,src,nick); else { LOCK(l->info); strncpy(buffer,l->info->nick,sizeof(buffer)); UNLOCK(l->info); if(irc_strcasecmp(tmp,buffer) == 0) { tmp += strlen(buffer); if(*tmp == '\0' || isspace(*tmp)) xdcc_info(l->network,chan, host,src,nick); } } } } else if(chan != NULL && strncasecmp(data,"xdcc list",9) == 0) { if(data[nextchar(data,9)] == '\0') xdcc_info(l->network,chan,host,src,nick); } else if(chan == NULL && (strncasecmp(data,"xdcc",4) == 0)) { //D("host:%s, nick: %s, data: %s",host,nick,data); xdcc_cmd(l->network,host,src,nick,data); } else if(chan == NULL) LOGTP(L_IRC,"%s From %s: %s", (type == PRIVMSG ? "MSG" : "NOTICE"),nick,data); xfree(nick); xfree(host); } //3 static void handle_join(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1], *data = tokens[2], buffer[128]; char *nick = p2n(src); channel_t *chan = irc_channel_find(l->network,data); //D("data: \"%s\", chan: %p, nick: \"%s\"",data,chan,nick); LOCK(l->info); if(chan != NULL) nickhash_add(l->info->nick_tracker,chan,nick); if(irc_strcasecmp(l->info->nick,nick) == 0) { UNLOCK(l->info); if(chan != NULL) { LOCK(chan); chan->joined = xtime(); chan->last_plist = chan->joined; channel_reset_state(chan); UNLOCK(chan); snprintf(buffer,sizeof(buffer),"MODE %s",data); irc_send_raw(l->network,buffer); snprintf(buffer,sizeof(buffer),"MODE %s b",data); irc_send_raw(l->network,buffer); LOGTP(L_IRC,"Joined Channel: %s",data); } else LOGTP(L_IRC,"Received JOIN for Unknown Channel: %s",data); } else UNLOCK(l->info); xmit_msg(l->network,JOIN,chan,nick,NULL,1); xfree(nick); } //3-4 static void handle_part(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1], *data = tokens[2]; // parted channel char *nick = p2n(src); channel_t *chan = irc_channel_find(l->network,data); int i; //D("chan: %p, src: \"%s\", nick: \"%s\"",chan,src,nick); LOCK(l->info); if(chan != NULL) nickhash_del(l->info->nick_tracker,chan,nick); if(irc_strcasecmp(l->info->nick,nick) == 0) { UNLOCK(l->info); LOCK(l->network); /* Special case, we want PART's for deleted * channels. */ for(i = 0, chan = NULL; i < l->network->no_channels && chan == NULL; ++i) { LOCK(l->network->channels[i]); if(irc_strcasecmp(l->network->channels[i]->name,data) == 0) chan = l->network->channels[i]; else UNLOCK(l->network->channels[i]); } if(chan != NULL) { chan->joined = 0; channel_reset_state(chan); UNLOCK(chan); nickhash_flush_channel(l->info->nick_tracker,chan); LOGTP(L_IRC,"Parted From %s",data); } else { LOGTP(L_IRC,"Received PART for unknown channel %s",data); } UNLOCK(l->network); } else UNLOCK(l->info); xmit_msg(l->network,PART,chan,nick,NULL,1); xfree(nick); } //4 static void handle_kick(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1], *dst = tokens[2], *data = tokens[3]; char *nick = p2n(src); // who did the kick channel_t *chan = irc_channel_find(l->network,dst); LOCK(l->info); if(irc_strcasecmp(data,l->info->nick) == 0) { UNLOCK(l->info); if(chan != NULL) { LOCK(chan); chan->joined = 0; channel_reset_state(chan); UNLOCK(chan); LOGTP(L_IRC,"Was Kicked From %s, By %s, Reason: %s", dst,nick,tokens[no_tokens-1]); } else LOGTP(L_IRC,"Recieved KICK for unknown channel %s",dst); } else { if(chan != NULL) nickhash_del(l->info->nick_tracker,chan,data); UNLOCK(l->info); } xmit_msg(l->network,KICK,chan,data,nick,1); xfree(nick); } //3 static void handle_nick(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1], *data = tokens[2]; char *nick = p2n(src); int i,tracking; //D("nick:\"%s\", data:\"%s\"",nick,data); LOCK(l->network); tracking = l->network->settings->nick_tracking; LOCK(l->info); nickhash_rename(l->info->nick_tracker,nick,data); if(irc_strcasecmp(nick,l->info->nick) == 0) { if(l->info->nick != NULL) xfree(l->info->nick); l->info->nick = xstrdup(data); LOGTP(L_IRC,"Changed Nick to: %s",data); } UNLOCK(l->info); UNLOCK(l->network); if(tracking) { admin_nick_change(l->network,nick,data); LOCK(global); for(i = 0; i < global->no_interfaces; ++i) interface_nick_change(global->interfaces[i], l->network,nick,data); for(i = 0; i < global->no_queues; ++i) queue_nick_change(global->queues[i], l->network,nick,data); UNLOCK(global); } xmit_msg(l->network,NICK,NULL,nick,data,1); xfree(nick); } //2-3 static void handle_quit(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *src = tokens[1]; // who quit char *nick = p2n(src); LOCK(l->network); LOCK(l->info); //D("QUIT, nick: %s",nick); nickhash_del(l->info->nick_tracker,NULL,nick); if(irc_strcasecmp(l->network->settings->nick,l->info->nick) != 0) { if(irc_strcasecmp(l->network->settings->nick,nick) == 0) { char buffer[128]; //LOGTP(L_INF,"Attempting to change nick to: %s",nick); snprintf(buffer,sizeof(buffer),"NICK %s",nick); enqueue_msg(l->info,buffer,EM_DUP); } } UNLOCK(l->info); UNLOCK(l->network); xmit_msg(l->network,QUIT,NULL,nick,NULL,1); xfree(nick); } //3- static void handle_rpl_welcome(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *dst = tokens[2]; // our nick in non-prefix form LOCK(l->network); LOCK(l->info); if(l->info->nick != NULL) { if(irc_strcasecmp(l->info->nick,dst) != 0) { xfree(l->info->nick); l->info->nick = xstrdup(dst); } } else { l->info->nick = xstrdup(dst); } l->network->state = NETWORK_CONNECTED; LOGTP(L_IRC,"Login Complete, Set Nick: %s",l->info->nick); /* Now we know our nickname look up our IP */ enqueue_msg(l->info,"USERHOST $BOT",EM_DUP); UNLOCK(l->info); UNLOCK(l->network); } #if 0 //5,6- static void determine_ip(netlocal_t *l); // pre-declaration static void handle_rpl_whois(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *nick = tokens[3]; // whois was of this nick char *host = NULL; struct in_addr addr; int ret; LOCK(l->info); if(l->info->nick != NULL) { if(irc_strcasecmp(l->info->nick,nick) == 0) { if(type == RPL_WHOISUSER) host = tokens[5]; else if(type == RPL_WHOIS_CUSTOM) { char *data = tokens[4]; if(strncasecmp(data,"is connecting from ",19) == 0) host = p2h(data + 19); } UNLOCK(l->info); } else if(type == RPL_WHOISUSER) { UNLOCK(l->info); /* MMMMMMMagic! */ perform_a_magic_trick(l->network,nick,tokens[5]); } } else UNLOCK(l->info); if(host != NULL) { ret = aresolv_nametoaddr(global->aresolver,AF_INET,host,&addr); if(ret == 0) { LOGTP(L_IRC,"Connected via \"%s\"",host); LOCK(l->info); l->info->self_ip = ntohl((unsigned long) addr.s_addr); UNLOCK(l->info); l->on_failback_ip = 0; } else determine_ip(l); if(type == RPL_WHOIS_CUSTOM) xfree(host); } } #endif // 4 static void determine_ip(netlocal_t *l); // pre-declaration static void handle_rpl_userhost(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { struct in_addr addr; char buffer[256]; char *nick, *host, *tmp; int ret = -1; xstrncpy(buffer,tokens[3],sizeof(buffer)); if((tmp = strchr(buffer,'=')) == NULL) return; *tmp = '\0'; if(tmp == buffer) return; if(*(tmp-1) == '*') *(tmp-1) = '\0'; if(*(tmp+1) == '\0') return; nick = buffer; host = (tmp+1); if((tmp = strchr(host,'@')) != NULL) host = (tmp+1); else if(*host == '+' || *host == '-') host++; trim(nick); trim(host); LOCK(l->info); if(l->info->nick != NULL) { if(irc_strcasecmp(l->info->nick,nick) == 0) ret = 0; } UNLOCK(l->info); if(ret == 0) { ret = aresolv_nametoaddr(global->aresolver,AF_INET,host,&addr); LOGTP(L_IRC,"Server says we are connected via \"%s\"",host); if(ret == 0) { LOCK(l->info); l->info->self_ip = ntohl((unsigned long) addr.s_addr); UNLOCK(l->info); inet_ntop(AF_INET,&addr,buffer,sizeof(buffer)); LOGTP(L_IRC,"Advertising address \"%s\"",buffer); } else { determine_ip(l); } } else { perform_a_magic_trick(l->network,nick,host); } } // 5-6 static void handle_rpl_namreply(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *data = tokens[3]; char **nicks; int no_nicks; channel_t *chan; int i; //D("RPL_NAMREPLY, data: \"%s\"",data); if(strlen(data) == 1) { if( data[0] == '=' || data[0] == '*' || data[0] == '@') { if(no_tokens < 5) return; else data = tokens[4]; } //D("RPL_NAMREPLY, data change: \"%s\"",data); } chan = irc_channel_find(l->network,data); if(chan == NULL) return; no_nicks = 0; nicks = split_opt(tokens[no_tokens-1],tokens[no_tokens-1],&no_nicks); LOCK(l->info); for(i = 0; i < no_nicks; ++i) { if(strlen(nicks[i]) == 0) continue; if(!is_chan_nick_prefix(l,nicks[i][0])) nickhash_add_mode(l->info->nick_tracker,chan, nicks[i],MODE_REG); else // first char is mode nickhash_add_mode(l->info->nick_tracker,chan, nicks[i]+1,mode_from_char(nicks[i][0])); } UNLOCK(l->info); if(nicks != NULL) xfree(nicks); } //4- static void handle_err_nosuchnick(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *nick = p2n(tokens[3]); // failed nick queue_remove(NULL,NULL,l->network,nick,NULL,NULL,NULL); xfree(nick); } //4- static void handle_err_nicknameinuse(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *dst = tokens[2]; char buffer[64]; // nicknames int i; if(strcmp(dst,"*") == 0) { /* Part of the login, we don't have a nick yet * since it seems are primary is in use. */ LOCK(l->info); if(l->info->nick == NULL) { /* re-serialise locks */ UNLOCK(l->info); LOCK(l->network); LOCK(l->info); l->info->nick = xstrdup(l->network->settings->nick); UNLOCK(l->network); } if(strlen(l->info->nick) < 9) { snprintf(buffer,sizeof(buffer)-1,"%s_",l->info->nick); } else { /* So your nick is now 9 chars, and we * don't want to go beyond the RFC so * we try something other than adding * _ to the end of the nick. */ for(i = 1; i < strlen(l->info->nick); ++i) { buffer[i-1] = l->info->nick[i]; } buffer[i-1] = l->info->nick[0]; buffer[i] = '\0'; } if(l->info->nick != NULL) xfree(l->info->nick); l->info->nick = xstrdup(buffer); snprintf(buffer,sizeof(buffer)-1,"NICK %s\r\n",l->info->nick); enqueue_msg(l->info,buffer,EM_DUP); UNLOCK(l->info); } } //4- static void handle_channel_error(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *data = tokens[4]; int i; switch (type) { case ERR_CHANNELISFULL: case ERR_INVITEONLYCHAN: case ERR_BANNEDFROMCHAN: case ERR_BADCHANNELKEY: /* chan = irc_channel_find(l->network,data); if(chan != NULL) { LOCK(chan); chan->joined = 0; UNLOCK(chan); } */ LOGTP(L_IRC,"Failed to Join Channel %s: %s", tokens[3],data); break; case ERR_NOTONCHANNEL: LOCK(l->network); /* special case, for deleted channels */ for(i = 0; i < l->network->no_channels; ++i) { LOCK(l->network->channels[i]); if(irc_strcasecmp(l->network->channels[i]->name,data) == 0) l->network->channels[i]->joined = 0; UNLOCK(l->network->channels[i]); } UNLOCK(l->network); break; default: break; } } //3- static void handle_mode(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char **modes, **args; char *dst; // what had its mode changed channel_t *chan; int i,mpos,apos,state,arg_start; if(type == MODE) { dst = tokens[2]; arg_start = 3; } else if(type == RPL_CHANNELMODEIS && no_tokens >= 4) { dst = tokens[3]; arg_start = 4; } else return; if(!is_channel(l,dst)) return; // user mode, ignore chan = irc_channel_find(l->network,dst); if(chan == NULL) { LOGTP(L_IRC,"Recieved MODE for unknown channel %s",dst); return; } if(type == RPL_CHANNELMODEIS) { LOCK(chan); if(chan->mode != NULL) { xfree(chan->mode); chan->mode = NULL; } UNLOCK(chan); } modes = xalloc(sizeof(char *)*(no_tokens-3)); args = xalloc(sizeof(char *)*(no_tokens-3)); for(i = 0; i < (no_tokens-3); ++i) { modes[i] = NULL; args[i] = NULL; } mpos = 0; apos = 0; for(i = arg_start; i < no_tokens; ++i) { if(tokens[i][0] == '+' || tokens[i][0] == '-') { //D("mode %d \"%s\"",mpos,tokens[i]); modes[mpos++] = tokens[i]; } else { //D("arg %d \"%s\"",apos,tokens[i]); args[apos++] = tokens[i]; } } LOCK(l->network); LOCK(l->info); mpos = 0; apos = 0; while(mpos < (no_tokens-3) && modes[mpos] != NULL) { if(modes[mpos][0] == '+') state = MODE_ADD; else if(modes[mpos][0] == '-') state = MODE_DEL; else { mpos++; continue; } for(i = 1; modes[mpos][i] != '\0'; ++i) { if(is_chan_nick_mode(l,modes[mpos][i]) && args[apos] != NULL) { nickhash_change_mode( l->info->nick_tracker, chan,args[apos++], state, mode_from_char(nprefix_from_mode(l,modes[mpos][i]))); } else if(is_chan_arg_mode(l,modes[mpos][i]) && args[apos] != NULL) { channel_change_mode(chan, state,modes[mpos][i], args[apos++]); } else if(is_chan_parg_mode(l,modes[mpos][i])) { if(state == MODE_ADD && args[apos] != NULL) channel_change_mode(chan, state,modes[mpos][i], args[apos++]); else if(state == MODE_DEL) channel_change_mode(chan, state,modes[mpos][i],NULL); } else if(is_chan_noarg_mode(l,modes[mpos][i])) { channel_change_mode(chan, state,modes[mpos][i],NULL); } } mpos++; } UNLOCK(l->info); UNLOCK(l->network); if(modes != NULL) xfree(modes); if(args != NULL) xfree(args); } static void handle_topic(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { channel_t *chan = NULL; char *topic = NULL; //D("no_tokens: %d",no_tokens); if(type == TOPIC) { chan = irc_channel_find(l->network,tokens[2]); topic = tokens[3]; } else if(type == RPL_TOPIC && no_tokens >= 5) { chan = irc_channel_find(l->network,tokens[3]); topic = tokens[4]; } else return; if(chan != NULL) { LOCK(chan); if(chan->topic != NULL) xfree(chan->topic); chan->topic = xstrdup(topic); UNLOCK(chan); } } static void handle_banlist(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { channel_t *chan = irc_channel_find(l->network,tokens[3]); if(chan != NULL) { LOCK(chan); PTRARR_ADD(&(chan->bans),&(chan->no_bans),xstrdup(tokens[4])); UNLOCK(chan); } } static void handle_netinfo(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { char *data; int i,j,k; for(i = 3; i < no_tokens; ++i) { if(strncasecmp(tokens[i],"CHANTYPES=",10) == 0) { data = strchr(tokens[i],'='); xstrncpy(l->chantype,data+1,MODEBUFSIZE); } else if(strncasecmp(tokens[i],"PREFIX=(",8) == 0) { data = strchr(tokens[i],'('); data++; for(j = 0; data[j] != '\0' && data[j] != ')'; ++j) ; if(j < (MODEBUFSIZE+1)) xstrncpy(l->mprefix,data,j+1); // copies j data += j+1; xstrncpy(l->nprefix,data,MODEBUFSIZE); } else if(strncasecmp(tokens[i],"CHANMODES=",10) == 0) { data = strchr(tokens[i],'='); data++; k = 0; for(j = 0; data[j] != '\0' && data[j] != ',' && k < MODEBUFSIZE-1; ++j) l->acmode[k++] = data[j]; if(data[j] != '\0') j++; for(; data[j] != '\0' && data[j] != ',' && k < MODEBUFSIZE-1; ++j) l->acmode[k++] = data[j]; l->acmode[k]= '\0'; if(data[j] != '\0') j++; k = 0; for(; data[j] != '\0' && data[j] != ',' && k < MODEBUFSIZE-1; ++j) l->pcmode[k++] = data[j]; l->pcmode[k] = '\0'; if(data[j] != '\0') j++; k = 0; for(; data[j] != '\0' && data[j] != ',' && k < MODEBUFSIZE-1; ++j) l->ncmode[k++] = data[j]; } } //D("chantypes: \"%s\"",l->chantype); //D("acmodes: \"%s\"",l->acmode); //D("pcmodes: \"%s\"",l->pcmode); //D("ncmodes: \"%s\"",l->ncmode); //D("mprefix: \"%s\"",l->mprefix); //D("nprefix: \"%s\"",l->nprefix); } static void handle_unknown(netlocal_t *l, enum mst_t type, char **tokens, int no_tokens) { // just to prevent worst case look ups return; } static const struct { enum mst_t type; int min_args; // these include cmd,src,dst int max_args; void (*func)(netlocal_t *, enum mst_t, char **, int); } msg_hooks[] = { /* ordered to optomise lookup */ {PRIVMSG, 4, 4, handle_privmsg}, {PING, 2, 2, handle_ping}, {UNKNOWN, 0,-1, handle_unknown}, {NOTICE, 4, 4, handle_privmsg}, {JOIN, 3, 3, handle_join}, {QUIT, 2, 3, handle_quit}, {NICK, 3, 3, handle_nick}, {MODE, 3,-1, handle_mode}, {PART, 3, 4, handle_part}, {KICK, 4, 5, handle_kick}, {ERROR, 2, 2, handle_error}, {RPL_NAMREPLY, 5, 6, handle_rpl_namreply}, {RPL_CHANNELMODEIS, 3,-1, handle_mode}, {RPL_USERHOST, 4, 4, handle_rpl_userhost}, // {RPL_WHOISUSER, 6,-1, handle_rpl_whois}, // {RPL_WHOIS_CUSTOM, 5, 5, handle_rpl_whois}, {RPL_TOPIC, 4, 5, handle_topic}, {TOPIC, 4, 5, handle_topic}, {RPL_BANLIST, 5,-1, handle_banlist}, {RPL_WELCOME, 3,-1, handle_rpl_welcome}, {RPL_NETINFO, 4,-1, handle_netinfo}, {ERR_NOSUCHNICK, 4,-1, handle_err_nosuchnick}, {ERR_NICKNAMEINUSE, 4,-1, handle_err_nicknameinuse}, {ERR_CHANNELISFULL, 4,-1, handle_channel_error}, {ERR_INVITEONLYCHAN, 4,-1, handle_channel_error}, {ERR_BANNEDFROMCHAN, 4,-1, handle_channel_error}, {ERR_BADCHANNELKEY, 4,-1, handle_channel_error}, {ERR_NOTONCHANNEL, 4,-1, handle_channel_error}, {0, 0, 0, NULL} }; static int process_msgs(netlocal_t *l) { char **tokens; char *msg,*tmp; int i,found,no_tokens; enum mst_t type; while((msg = pqueue_pop_front(&l->rqueue)) != NULL) { tokenise_msg(msg,&tokens,&no_tokens); if(no_tokens > 0) { if(tokens[0][0] == ':') { tmp = tokens[0]; tokens[0] = tokens[1]; tokens[1] = tmp + 1; } type = str_to_mst(tokens[0]); for(i = 0, found = -1; msg_hooks[i].func != NULL && found == -1; ++i) { if(msg_hooks[i].type == type) found = i; } //D("type: %s, tokens: %d, found: %d",tokens[0],no_tokens,found); if(found != -1) { i = found; /* This means we can bind more than one function to a message type, but they must be next to each other in the list. */ while(msg_hooks[i].func != NULL && msg_hooks[i].type == type) { if((no_tokens >= msg_hooks[i].min_args) && (no_tokens <= msg_hooks[i].max_args || msg_hooks[i].max_args == -1)) { msg_hooks[i].func(l,type,tokens,no_tokens); } else D("%d, %d does not fit %d to %d", type,no_tokens, msg_hooks[i].min_args, msg_hooks[i].max_args); i++; } } se_irc_msg(l->network,tokens,no_tokens); } if(tokens != NULL) xfree(tokens); xfree(msg); if(l->socket == -1) { /* Error occur and we have been disconnected */ return -1; } } return 0; } static int login_server(netlocal_t *l) { char buffer[512]; channel_t *chan; int i; LOCK(l->network); LOCK(l->info); if(l->info->nick != NULL) { if(strcmp(l->info->nick,l->network->settings->nick) != 0) { xfree(l->info->nick); l->info->nick = xstrdup(l->network->settings->nick); } } else { l->info->nick = xstrdup(l->network->settings->nick); } if(l->info->password == NULL) { snprintf(buffer,sizeof(buffer)-1, "NICK %s\r\nUSER %s * * :%s\r\n", l->info->nick, l->network->settings->loginname, l->network->settings->realname ); } else { snprintf(buffer,sizeof(buffer)-1, "PASS %s\r\nNICK %s\r\nUSER %s * * :%s\r\n", l->info->password, l->info->nick, l->network->settings->loginname, l->network->settings->realname ); } l->network->state = NETWORK_LOGIN; UNLOCK(l->info); UNLOCK(l->network); if(l->snd_msg != NULL) xfree(l->snd_msg); l->snd_msg = xstrdup(buffer); load_linebuf(l->snd_buf,buffer); i = send_line(l->socket,l->snd_buf); if(i <= 0 && errno != EAGAIN) { xfree(l->snd_msg); l->snd_msg = NULL; return -1; } else if(i > 0) { xfree(l->snd_msg); l->snd_msg = NULL; } LOCK(l->network); LOCK(l->info); if(l->network->channels != NULL) { for(i = l->network->no_channels - 1; i >= 0; --i) { chan = l->network->channels[i]; LOCK(chan); if(!chan->deleted) { if(chan->key == NULL) snprintf(buffer,(sizeof(buffer))-1, "JOIN %s", chan->name); else snprintf(buffer,(sizeof(buffer))-1, "JOIN %s %s", chan->name,chan->key); UNLOCK(chan); enqueue_msg(l->info,buffer,EM_DUP); } else { UNLOCK(chan); } } } if(l->network->on_connects != NULL) { for(i = l->network->no_on_connects - 1; i >= 0; --i) enqueue_msg(l->info,l->network->on_connects[i], EM_DUP | EM_PRIORITY); } l->info->self_ip = 0; UNLOCK(l->info); /* Initialize Send Averaging Window */ l->window_size = l->network->settings->average_window; if(l->window_size < 5) l->window_size = 5; UNLOCK(l->network); l->window = xalloc(sizeof(int)*l->window_size); for(i = 0; i < l->window_size; ++i) l->window[i] = 0; l->last_join = xtime(); l->last_send = xtime(); return 0; } static int window_sum(netlocal_t *l) { int i,sum; for(i = 0, sum = 0; i < l->window_size; ++i) { sum += l->window[i]; } return sum; } static char *dequeue_msg(netlocal_t *l, time_t now) { enum networkstate_t state; time_t join_delay, on_connect_delay; char *msg = NULL; LOCK(l->network); state = l->network->state; join_delay = l->network->settings->join_delay; on_connect_delay = l->network->settings->on_connect_delay; UNLOCK(l->network); LOCK(l->info); msg = pqueue_pop_front(&(l->info->p_squeue)); if(msg == NULL && state != NETWORK_LOGIN) { if((l->last_join + join_delay) < now) { msg = pqueue_pop_front(&(l->info->j_squeue)); if(msg != NULL) l->last_join = now; } if((l->last_connect + on_connect_delay) < now) { if(msg == NULL && l->info->self_ip != 0) msg = pqueue_pop_front(&(l->info->d_squeue)); if(msg == NULL) msg = pqueue_pop_front(&(l->info->r_squeue)); } } UNLOCK(l->info); return msg; } static size_t expand_msg(netlocal_t *l, const char *msg, char *buffer, size_t bufsize) { int i, j; for(i = 0, j = 0; msg[i] != '\0' && j < bufsize; ++i) { if(msg[i] == '$') { if(strncmp(msg+i,"$BOT",4) == 0) { LOCK(l->info); j += IMIN( snprintf(buffer+j, bufsize-j, "%s",l->info->nick), bufsize-j-1 ); UNLOCK(l->info); i += 3; // will get another 1 from ++i } else if(strncmp(msg+i,"$IIP",4) == 0) { LOCK(l->info); j += IMIN( snprintf(buffer+j,bufsize-j,"%lu", (unsigned long)l->info->self_ip), bufsize-j-1 ); UNLOCK(l->info); i += 3; // will get another 1 from ++i } else { buffer[j++] = '$'; } } else { buffer[j++] = msg[i]; } } j += snprintf(buffer+j,bufsize-j,"\r\n"); return j; } static int send_msgs(netlocal_t *l) { char buffer[512], *p; float messages, message_rate, message_rate_max; time_t shift, now = xtime(); int i, ret = 0; /* take local copies of some settings for the duration of the loop */ LOCK(l->network); message_rate = (float) l->network->settings->message_rate; message_rate_max= (float) l->network->settings->message_rate_max; UNLOCK(l->network); if(message_rate <= 0.0) message_rate = 0.5; if(message_rate_max <= 0.0) message_rate_max = 2.0; /* zero the average window for time we haven't been sending messages */ shift = l->last_send; if((shift - now) > l->window_size) { for(i = 0; i < l->window_size; ++i) l->window[i] = 0; messages = 0.0; } else { while(shift < now) { shift++; l->window[shift % l->window_size] = 0; } messages = (float) window_sum(l); } while (( (float) l->window[shift % l->window_size] < (float) ceil(message_rate_max * ( (message_rate - (messages / (float) l->window_size)) / message_rate)) ) && ( (messages / (float) l->window_size) < message_rate )) { if(l->snd_msg == NULL) { p = dequeue_msg(l,now); if(p == NULL) break; l->snd_msg = p; expand_msg(l,p,buffer,sizeof(buffer)); rxmit_msg(l->network,buffer); load_linebuf(l->snd_buf,buffer); } ret = send_line(l->socket,l->snd_buf); if(ret > 0) { if(l->snd_msg != NULL) { xfree(l->snd_msg); l->snd_msg = NULL; } l->last_send = shift; l->window[shift % l->window_size]++; messages += 1.0; } else { if(ret == -1) { if(errno != EAGAIN || errno == EINTR) { char errbuf[96]; LOGTP(L_ERR,"Got Error: %s", lstrerror_r(errno,errbuf, sizeof(errbuf))); } else ret = 0; } break; } } /* try to thin a large queue */ LOCK(l->info); if(pqueue_length(&l->info->r_squeue) >= IMAX(50,(int)(message_rate*300.0))) { LOGTP(L_ERR,"Send queue exceeds %d messages, completely flushing it.",IMAX(50,(int)(message_rate*300.0))); pqueue_clear(&l->info->r_squeue,xfree,0); } UNLOCK(l->info); return ret; } static void check_channels(netlocal_t *l) { char buffer[512]; channel_t *chan; int i,chans; LOCK(l->network); LOCK(l->info); chans = l->network->no_channels; for(i = 0; i < l->network->no_channels; ++i) { chan = l->network->channels[i]; LOCK(chan); if(chan->joined == 0 && chan->deleted == 0) { if(chan->key == NULL) snprintf(buffer,sizeof(buffer),"JOIN %s", chan->name); else snprintf(buffer,sizeof(buffer),"JOIN %s %s", chan->name,chan->key); enqueue_msg(l->info,buffer,EM_DUP); } else if(chan->joined != 0 && chan->deleted) { snprintf(buffer,sizeof(buffer),"PART %s",chan->name); enqueue_msg(l->info,buffer,EM_DUP); chan->joined = 0; } if(chan->deleted) { if(IS_PURGE_TIME(chan->deleted,xtime())) { l->network->channels[i] = NULL; nickhash_flush_channel(l->info->nick_tracker, chan); UNLOCK(chan); free_channel(chan); chans--; continue; // skip UNLOCK() } } UNLOCK(chan); } if(chans < l->network->no_channels) { /* Purge deleted channels */ PTRARR_DEL(&(l->network->channels), &(l->network->no_channels),NULL); } UNLOCK(l->info); UNLOCK(l->network); } static void channel_plists(netlocal_t *l, char *buffer, size_t bufsize) { char cname[64]; char **names = NULL, **msgs; packlist_t *list,**lists; pformat_t pformat; channel_t *c; pqueue_t cq,*sq; time_t now = xtime(); int i,j,no_names = 0, no_lists; pqueue_init(&cq,0); LOCK(l->network); for(i = 0; i < l->network->no_channels; ++i) { c = l->network->channels[i]; LOCK(c); if(c->deleted == 0 && c->plist != 0 && (c->last_plist + (c->plist*60)) < now && now > c->nolist) pqueue_push_back(&cq,c); UNLOCK(c); } LOCK(l->info); sq = &(l->info->r_squeue); UNLOCK(l->info); UNLOCK(l->network); if(pqueue_length(&cq) == 0) return; LOCK(global); lists = xalloc(sizeof(packlist_t *)*(global->no_packlists+1)); no_lists = 0; for(i = 0; i < global->no_packlists; ++i) { LOCK(global->packlists[i]); if(global->packlists[i]->deleted == 0) lists[no_lists++] = global->packlists[i]; UNLOCK(global->packlists[i]); } UNLOCK(global); if(no_lists == 0) { xfree(lists); return; } while((c = pqueue_pop_front(&cq)) != NULL) { LOCK(c); c->last_plist = now; pformat = c->pformat; xstrncpy(cname,c->name,sizeof(cname)); UNLOCK(c); for(i = 0; i < no_lists; ++i) { list = lists[i]; if(packlist_check_access_channel(list,l->network,c)) { if(pformat == PFORMAT_MINIMAL) { LOCK(list); snprintf(buffer,bufsize,"PRIVMSG %s :%s**%s XDCC %s**%s Packlist: %s\r\n", IRC_BOLD,IRC_BOLD, IRC_BOLD,IRC_BOLD, cname,list->name); UNLOCK(list); pqueue_push_back(sq,xstrdup(buffer)); snprintf(buffer,bufsize,"PRIVMSG %s :",cname); packlist_xdl_body(list,sq,COLOUR_IRC,buffer,"\r\n"); } else if(pformat == PFORMAT_SUMMARY) { LOCK(list); PTRARR_ADD(&names,&no_names,xstrdup(list->name)); UNLOCK(list); } else if(pformat == PFORMAT_FULL) { snprintf(buffer,bufsize,"PRIVMSG %s :",cname); packlist_xdl_head(list,sq, COLOUR_IRC,buffer,"\r\n",1); packlist_xdl_body(list,sq, COLOUR_IRC,buffer,"\r\n"); packlist_xdl_tail(list,sq, COLOUR_IRC,l->network,c, buffer,"\r\n"); } else if(pformat == PFORMAT_TAGONLY) { snprintf(buffer,bufsize,"PRIVMSG %s :",cname); packlist_xdl_tail(list,sq, COLOUR_IRC,l->network,c, buffer,"\r\n"); } } } if(pformat == PFORMAT_SUMMARY) { LOCK(l->network); msgs = xdcc_info_msgs(names,no_names,&j, l->network->settings->info_line); UNLOCK(l->network); strarr_free(&names,&no_names); for(i = 0; i < j; ++i) { snprintf(buffer,bufsize, "PRIVMSG %s :%s\r\n", cname,msgs[i]); xfree(msgs[i]); pqueue_push_back(sq,xstrdup(buffer)); } if(msgs != NULL) xfree(msgs); } } xfree(lists); } static void determine_ip(netlocal_t *l) { struct sockaddr_in addr; socklen_t len = sizeof(addr); char buffer[64]; if(l->socket != -1) { getsockname(l->socket,(struct sockaddr *)&addr,&len); inet_ntop(AF_INET,&(addr.sin_addr),buffer,sizeof(buffer)); LOGTP(L_INF,"Advertising address \"%s\" (failback)",buffer); LOCK(l->info); l->info->self_ip = ntohl(addr.sin_addr.s_addr); UNLOCK(l->info); l->on_failback_ip = 1; } } static void main_loop(netlocal_t *l) { #define MAX_LOOP_PER_SEC 10 #define SIG 0 #define SOK 1 char buffer[512]; int timeout; struct pollfd fds[2]; int irc_timeout = 180, msg_limit = 100; int ret; time_t last_plists, last_nick_check, last_sys_check; time_t last_ping, last_channel_check, last_msg_limit_fill; time_t tmp, now = xtime(); antispin_t as; antispin_init(&as); last_plists = now; last_nick_check = now; last_sys_check = now; last_ping = now; last_channel_check = now; last_msg_limit_fill = 0; fds[SIG].fd = l->signal_fd; fds[SIG].events = POLLIN; fds[SOK].fd = l->socket; fds[SOK].events = POLLIN; while(l->socket != -1 && (!thread_should_end())) { antispin_test_and_sleep(&as,MAX_LOOP_PER_SEC); /* Message Processing */ /* trust no one! */ fds[SIG].revents = 0; fds[SOK].revents = 0; /* configure message limit */ if(test_update_timer(0,&last_msg_limit_fill,1)) { LOCK(l->network); msg_limit = l->network->settings->recv_per_sec; UNLOCK(l->network); } /* configure timeout */ LOCK(l->info); if((pqueue_length(&l->info->p_squeue) + pqueue_length(&l->info->j_squeue) + pqueue_length(&l->info->d_squeue) + pqueue_length(&l->info->r_squeue)) > 0 || linebuf_inuse(l->snd_buf)) { timeout = 1000; } else { timeout = 10000; } UNLOCK(l->info); if(msg_limit > 0) { fds[SOK].events = POLLIN; } else { timeout = 1000; fds[SOK].events = 0; } poll(fds,2,timeout); /* clear signal pipe */ if(fds[SIG].revents & POLLIN) { do { ret = read(l->signal_fd,buffer,1); } while(ret > 0); } /* If a bit other than POLLIN was set then it's an error. */ if(fds[SOK].revents & ~POLLIN) { char errbuf[96]; if(fds[SOK].revents & POLLERR) { socklen_t len; int val; len = sizeof(val); ret = getsockopt(l->socket,SOL_SOCKET,SO_ERROR, &val,&len); lstrerror_r(ret != -1 ? val : errno, errbuf,sizeof(errbuf)); } else { lstrerror_r(ECONNRESET,errbuf,sizeof(errbuf)); } LOGTP(L_ERR,"Got Error: %s",errbuf); disconnect_server(l,NULL); break; } /* check for pending messages */ if(fds[SOK].revents & POLLIN) { ret = recv_msgs(l,msg_limit); if(ret > 0) { msg_limit -= ret; if(process_msgs(l) == -1) break; // link failure } else if(ret == -1) { disconnect_server(l,NULL); break; } } /* send outgoing messages */ if(send_msgs(l) == -1) { // link failure disconnect_server(l,NULL); break; } /* Periodic Checks */ now = xtime(); if((now - l->last_join) > 60 && test_timer(now,last_channel_check,60)) { check_channels(l); last_channel_check = now; } LOCK(l->network); irc_timeout = l->network->settings->irc_timeout; UNLOCK(l->network); /* work out if we have pinged out without knowing it */ tmp = l->last_message; if(abs(now - tmp) > irc_timeout) { LOGTP(L_ERR,"No messages for %d Seconds, Closing Link",now - tmp); disconnect_server(l,"Ping Timeout"); break; } else if(abs(now - tmp) > (irc_timeout - 30) && abs(now - last_ping) > 30) { irc_send_raw(l->network,"PING searching_for_life"); last_ping = now; } if(test_update_timer(now,&last_plists,60)) channel_plists(l,buffer,sizeof(buffer)); if(test_update_timer(now,&last_nick_check,60)) { LOCK(l->network); LOCK(l->info); if(irc_strcasecmp(l->info->nick, l->network->settings->nick) != 0) { //LOGTP(L_INF,"Attempting to change nick to: %s", // l->network->settings->nick); snprintf(buffer,sizeof(buffer),"NICK %s", l->network->settings->nick); enqueue_msg(l->info,buffer,EM_DUP); } UNLOCK(l->info); UNLOCK(l->network); } if(test_update_timer(now,&last_sys_check,300)) { LOCK(l->info); if(l->info->self_ip == 0 || l->on_failback_ip) enqueue_msg(l->info,"USERHOST $BOT",EM_DUP); UNLOCK(l->info); } } #undef SIG #undef SOK #undef MAX_LOOP_PER_SEC } /* EXPORTED Functions */ void *irc_thread(void *arg) { netlocal_t local; netlocal_t *l; time_t last_loop = 0; int ret,sleepfor; thread_signal_started(&(((network_t *)arg)->thread)); irc_init(&local,(network_t *)arg); l = &local; LOCK(l->network); LOG_TITLE("Network %s",l->network->name); UNLOCK(l->network); LOGTP(L_INF,"Started"); while(!thread_should_end()) { LOCK(l->network); if((xtime() - last_loop) >= l->network->settings->reconnect_delay) sleepfor = 0; else sleepfor = (l->network->settings->reconnect_delay) - (xtime() - last_loop); UNLOCK(l->network); if(sleepfor != 0) { if(thread_sleep(sleepfor) != 0) { if(thread_should_end()) break; } } last_loop = xtime(); pick_server(l); ret = connect_server(l); if(ret == 0) { ret = login_server(l); if(ret == -1) disconnect_server(l,NULL); } if(ret != -1) main_loop(l); /* LOCK(l->info); D("%d,%d,%d,%d", pqueue_length(&l->info->p_squeue), pqueue_length(&l->info->j_squeue), pqueue_length(&l->info->d_squeue), pqueue_length(&l->info->r_squeue)); UNLOCK(l->info); */ } /* the end! */ LOGTP(L_INF,"Shutdown"); if(l->socket != -1) disconnect_server(l,ARISA_VERSION); irc_free(l); thread_signal_finished(); return NULL; } int irc_network_add(network_t *n) { int i,ret = -1; LOCK(global); LOCK(n); for(i = 0; i < global->no_networks; ++i) { LOCK(global->networks[i]); if(!global->networks[i]->deleted && strcmp(global->networks[i]->name,n->name) == 0) { LOGP(L_ERR,"Error trying to add duplicate network: %s",n->name); UNLOCK(global->networks[i]); break; } UNLOCK(global->networks[i]); } if(i == global->no_networks) { /* we completed the scan without break'ing */ PTRARR_ADD(&global->networks,&global->no_networks,n); validity_insert(n); LOGP(L_INF,"Added Network %s",n->name); ret = 0; } // else ret = -1 (already) if(ret == 0 && !n->settings->no_autoconnect) ret = thread_create(&(n->thread),irc_thread,n); UNLOCK(n); UNLOCK(global); return ret != 0 ? -1 : 0; } int irc_network_run(network_t *n) { int ret = 0; if(n->deleted == 0 && n->state == NETWORK_OFFLINE && n->info == NULL && !thread_is_running(&(n->thread))) ret = thread_create(&(n->thread),irc_thread,n); return ret != 0 ? -1 : 0; } thread_t *irc_network_stop(network_t *n) { char buffer[64]; thread_t *ret = &n->thread; thread_end(ret); snprintf(buffer,sizeof(buffer),"QUIT :%s",ARISA_VERSION); enqueue_msg(n->info,buffer,EM_LOCK | EM_DUP); return ret; } static int purge_network_ref(network_t *n, const time_t now) { interface_t *intf; int i,j,k,ref = 0; LOCK(global); for(i = 0; i < global->no_interfaces; ++i) { intf = global->interfaces[i]; LOCK(intf); if(intf->type == INTERFACE_SEND) { for(j = 0; j < intf->send.no_pools; ++j) { LOCK(intf->send.pools[j]); for(k = 0; k < intf->send.pools[j]->no_csends; ++k) { LOCK(intf->send.pools[j]->csends[k]); if(intf->send.pools[j]->csends[k]->network == n) { intf->send.pools[j]->csends[k]->state = STATE_DELETED; ref++; } UNLOCK(intf->send.pools[j]->csends[k]); } UNLOCK(intf->send.pools[j]); } } else if(intf->type == INTERFACE_RECV) { for(j = 0; j < intf->recv.no_uploads; ++j) { LOCK(intf->recv.uploads[j]); if(intf->recv.uploads[j]->network == n) { intf->recv.uploads[j]->state = STATE_DELETED; ref++; } UNLOCK(intf->recv.uploads[j]); } } else if(intf->type == INTERFACE_CHAT) { for(j = 0; j < intf->chat.no_chats; ++j) { LOCK(intf->chat.chats[j]); if(intf->chat.chats[j]->network == n) { intf->chat.chats[j]->state = STATE_DELETED; ref++; } UNLOCK(intf->chat.chats[j]); } } UNLOCK(intf); } for(i = 0; i < global->no_queues; ++i) queue_remove(global->queues[i],NULL,n,NULL,NULL,NULL,NULL); for(i = 0; i < global->no_admins; ++i) { LOCK(global->admins[i]); if(global->admins[i]->network == n) global->admins[i]->network = NULL; UNLOCK(global->admins[i]); } UNLOCK(global); access_purge_network(n); return ref; } int irc_network_del(network_t *n) { time_t now = xtime(); int i; purge_network_ref(n,now); LOCK(global); LOCK(n); if(thread_is_running(&(n->thread))) thread_end(&(n->thread)); n->deleted = now; validity_delete(n); for(i = 0; i < n->no_channels; ++i) { LOCK(n->channels[i]); n->channels[i]->deleted = now; validity_delete(n->channels[i]); UNLOCK(n->channels[i]); } LOGP(L_INF,"Deleted Network %s",n->name); UNLOCK(n); UNLOCK(global); purge_network_ref(n,now); return 0; } network_t *irc_network_find(const char *name) { network_t *ret = NULL; int i; if(name == NULL) return NULL; LOCK(global); for(i = 0; i < global->no_networks && ret == NULL; ++i) { LOCK(global->networks[i]); if(!global->networks[i]->deleted && strcmp(global->networks[i]->name,name) == 0) { ret = global->networks[i]; validity_insert(ret); } UNLOCK(global->networks[i]); } UNLOCK(global); return ret; } /* purge previously deleted networks */ void irc_networks_purge(const time_t now) { network_t *n; int i; pqueue_t dq; //D("IRC Networks Purge Begins"); pqueue_init(&dq,0); LOCK(global); for(i = 0; i < global->no_networks; ++i) { LOCK(global->networks[i]); if(IS_PURGE_TIME(global->networks[i]->deleted,now)) pqueue_push_back(&dq,global->networks[i]); UNLOCK(global->networks[i]); } UNLOCK(global); if(pqueue_length(&dq) == 0) { //D("IRC Networks Purge Ends"); return; } for(i = pqueue_length(&dq); i > 0; --i) { n = pqueue_pop_front(&dq); if(purge_network_ref(n,now) == 0) { LOCK(n); if(n->info == NULL) pqueue_push_back(&dq,n); UNLOCK(n); } } if(pqueue_length(&dq) != 0) { while((n = pqueue_pop_front(&dq)) != NULL) { LOCK(global); LOCK(n); PTRARR_DEL(&global->networks,&global->no_networks,n); UNLOCK(global); UNLOCK(n); thread_join(&(n->thread),NULL); free_network(n); } } //D("IRC Networks Purge Ends"); } int irc_channel_add(network_t *n, channel_t *c) { char buffer[512]; int i,ret = -1; LOCK(n); LOCK(c); for(i = 0; i < n->no_channels; ++i) { LOCK(n->channels[i]); if(!n->channels[i]->deleted && strcasecmp(n->channels[i]->name,c->name) == 0) { UNLOCK(n->channels[i]); break; } UNLOCK(n->channels[i]); } if(i == n->no_channels) { PTRARR_ADD(&n->channels,&n->no_channels,c); validity_pair_insert(n,c); c->joined = 0; ret = 0; if(c->key == NULL) snprintf(buffer,sizeof(buffer)-1,"JOIN %s",c->name); else snprintf(buffer,sizeof(buffer)-1,"JOIN %s %s", c->name,c->key); ret = 0; } UNLOCK(c); if(ret == 0 && n->state == NETWORK_CONNECTED) { UNLOCK(n); irc_send_raw(n,buffer); } else UNLOCK(n); return ret; } int irc_channel_del(network_t *n, channel_t *c) { char buffer[512]; int sendpart = 0; LOCK(n); LOCK(c); if(!c->deleted) { c->deleted = xtime(); validity_delete(c); if(c->joined) { snprintf(buffer,sizeof(buffer)-1,"PART %s",c->name); sendpart = 1; } } UNLOCK(c); UNLOCK(n); access_purge_channel(c); if(sendpart) irc_send_raw(n,buffer); // this will also signal the thread return 0; } channel_t *irc_channel_find(network_t *n, const char *name) { channel_t *ret = NULL; int i; if(name == NULL) return NULL; LOCK(n); for(i = 0; i < n->no_channels && ret == NULL; ++i) { LOCK(n->channels[i]); if(!n->channels[i]->deleted && strcasecmp(n->channels[i]->name,name) == 0) { ret = n->channels[i]; validity_pair_insert(n,ret); } UNLOCK(n->channels[i]); } UNLOCK(n); return ret; } /* All of these msg sends are quite in-efficient. * I doubt it matters too much, but for every irc_send level added at least * another 512 will be taken from the heap. So a ctcp call will use about 2k * of heap... Again I don't think it matters. (heap/stack) */ int irc_send_raw(network_t *n, const char *msg) { int ret = -1; if(n != NULL && msg != NULL) { LOCK(n); ret = enqueue_msg(n->info,msg,EM_LOCK | EM_DUP); UNLOCK(n); } return ret; } int irc_send_msg(network_t *n, const char *nick, const char *msg) { char buffer[512]; if(nick != NULL && msg != NULL) { snprintf(buffer,sizeof(buffer)-1,"PRIVMSG %s :%s",nick,msg); return irc_send_raw(n,buffer); } return -1; } int irc_send_notice(network_t *n, const char *nick, const char *msg) { char buffer[512]; if(nick != NULL && msg != NULL) { snprintf(buffer,sizeof(buffer)-1,"NOTICE %s :%s",nick,msg); return irc_send_raw(n,buffer); } return -1; } int irc_send_ctcp_msg(network_t *n, const char *nick, const char *msg) { char buffer[512]; if(msg != NULL) { snprintf(buffer,sizeof(buffer)-1,"\001%s\001",msg); return irc_send_msg(n,nick,buffer); } return -1; } int irc_send_ctcp_notice(network_t *n, const char *nick, const char *msg) { char buffer[512]; if(msg != NULL) { snprintf(buffer,sizeof(buffer)-1,"\001%s\001",msg); return irc_send_notice(n,nick,buffer); } return -1; } int irc_tolower(int c) { if(isalnum(c)) return tolower(c); else { switch (c) { case '[': c = '{'; break; case ']': c = '}'; break; case '|': c = '|'; break; default: c = tolower(c); } } return c; } int irc_strcasecmp(const char *s1, const char *s2) { unsigned int s1p,s2p; int c1,c2; if(s1 == NULL && s2 == NULL) return 0; else if(s1 == NULL || s2 == NULL) return 1; for(s1p = 0, s2p = 0; s1[s1p] != '\0' && s2[s2p] != '\0'; ++s1p,++s2p) { c1 = irc_tolower(s1[s1p]); c2 = irc_tolower(s2[s2p]); if(c1 == c2) continue; else if(c1 < c2) return -1; else return 1; } if(s1[s1p] != s2[s2p]) { if(s1[s1p] == '\0') return -1; else /* s2[s1p] == '\0' */ return 1; } return 0; } int irc_hostmask_match(const char *host, const char *mask) { if(host == NULL || mask == NULL) return 0; return wild_match_cf(host,mask,irc_tolower); } void irc_strip_colours(char *buffer) { int i,j; for(i = 0, j = 0; buffer[i] != '\0';) { if(buffer[i] == '\003') { /* colour codes */ if(isdigit(buffer[i+1])) { i += 2; if(isdigit(buffer[i])) i++; if(i == 3 && buffer[i] == ',') i++; if(i == 4 && isdigit(buffer[i])) i++; if(i == 5 && isdigit(buffer[i])) i++; } else i++; } else if(buffer[i] == '\002' || buffer[i] == '\017' || buffer[i] == '\037') /* bold/off/underline */ ++i; else buffer[j++] = buffer[i++]; } buffer[j] = '\0'; } int irc_listen(network_t *n, channel_t *c, pqueue_t *msgq) { int tmp,ret = -1; if(n != NULL && c == NULL) { LOCK(n); tmp = n->no_listeners; if(n->deleted == 0) PTRARR_ADD(&n->listeners,&n->no_listeners,msgq); if(tmp != n->no_listeners) ret = 0; UNLOCK(n); } else if(c != NULL) { LOCK(c); tmp = c->no_listeners; if(c->deleted == 0) PTRARR_ADD(&c->listeners,&c->no_listeners,msgq); if(tmp != c->no_listeners) ret = 0; UNLOCK(c); } return ret; } int irc_unlisten(network_t *n, channel_t *c, pqueue_t *msgq) { int i,j; if(n == NULL && c == NULL) { LOCK(global); for(i = 0; i < global->no_networks; ++i) { LOCK(global->networks[i]); PTRARR_DEL(&global->networks[i]->listeners, &global->networks[i]->no_listeners, msgq); for(j = 0; j < global->networks[i]->no_channels; ++j) { LOCK(global->networks[i]->channels[j]); PTRARR_DEL(&global->networks[i]->channels[j]->listeners, &global->networks[i]->channels[j]->no_listeners, msgq); UNLOCK(global->networks[i]->channels[j]); } UNLOCK(global->networks[i]); } UNLOCK(global); } else if(n != NULL) { LOCK(n); PTRARR_DEL(&n->listeners,&n->no_listeners,msgq); for(i = 0; i < n->no_channels; ++i) { LOCK(n->channels[i]); PTRARR_DEL(&n->channels[i],&n->no_channels,msgq); UNLOCK(n->channels[i]); } UNLOCK(n); } else if(c != NULL) { LOCK(c); PTRARR_DEL(&c->listeners,&c->no_listeners,msgq); UNLOCK(c); } return 0; // alway succeed... we guess } int irc_network_valid(network_t *n) { int i,ret = 0; if(n == NULL) return 0; if(validity_test(n)) return 1; LOCK(global); for(i = 0; i < global->no_networks; ++i) { if(global->networks[i] == n) break; } if(i < global->no_networks) { // network is in array LOCK(n); if(n->deleted == 0) ret = 1; UNLOCK(n); } UNLOCK(global); return ret; } int irc_network_rename(network_t *n, const char *name) { int i,ret = 0; if(n == NULL || name == NULL) return -1; LOCK(global); for(i = 0; i < global->no_networks && ret == 0; ++i) { LOCK(global->networks[i]); if(global->networks[i]->deleted == 0 && strcmp(global->networks[i]->name,name) == 0) ret = -1; UNLOCK(global->networks[i]); } if(ret == 0) { LOCK(n); if(n->name != NULL) xfree(n->name); n->name = xstrdup(name); UNLOCK(n); } UNLOCK(global); return ret; } int irc_channel_valid(network_t *n, channel_t *c) { int i; if(n == NULL || c == NULL) return 0; if(validity_pair_test(n,c)) return 1; if(irc_network_valid(n)) { LOCK(n); if(n->deleted != 0) { UNLOCK(n); return 0; } for(i = 0; i < n->no_channels; ++i) { if(n->channels[i] == c) { LOCK(c); i = c->deleted; if(c->deleted == 0) validity_pair_insert(n,c); UNLOCK(c); UNLOCK(n); if(i == 0) return 1; else return 0; } } UNLOCK(n); } return 0; } int irc_channel_rename(network_t *n, channel_t *c, const char *name) { int i,j, ret = 0; if(c == NULL || name == NULL) return -1; if(n == NULL) { LOCK(global); for(i = 0; i < global->no_networks && n == NULL; ++i) { LOCK(global->networks[i]); if(global->networks[i]->deleted == 0) { for(j = 0; j < global->networks[i]->no_channels && n == NULL; ++j) { if(global->networks[i]->channels[j] == c) n = global->networks[i]; } } UNLOCK(global->networks[i]); } if(n != NULL) LOCK(n); UNLOCK(global); if(n == NULL) return -1; } else LOCK(n); if(n->deleted != 0) { UNLOCK(n); return -1; } for(i = 0; i < n->no_channels && ret == 0; ++i) { LOCK(n->channels[i]); if(n->channels[i]->deleted == 0 && irc_strcasecmp(n->channels[i]->name,name) == 0) ret = -1; UNLOCK(n->channels[i]); } if(ret == 0) { char buffer[512],key[512]; LOCK(c); if(c->name != NULL) { if(c->joined != 0) snprintf(buffer,sizeof(buffer), "PART %s",c->name); else buffer[0] = '\0'; xfree(c->name); } else buffer[0] = '\0'; c->joined = 0; c->name = xstrdup(name); if(c->key != NULL) xstrncpy(key,c->key,sizeof(key)); else key[0] = '\0'; UNLOCK(c); LOCK(n->info); if(n->state == NETWORK_CONNECTED) { if(buffer[0] != '\0') enqueue_msg(n->info,buffer,EM_DUP); if(key[0] != '\0') snprintf(buffer,sizeof(buffer), "JOIN %s %s",name,key); else snprintf(buffer,sizeof(buffer),"JOIN %s",name); enqueue_msg(n->info,buffer,EM_DUP); } UNLOCK(n->info); } UNLOCK(n); return ret; } /** Settings Data **/ static setting_t netset_data[] = { {"nick", ST_STR, OFFSETOF(netset_t,nick), 0,NULL,NULL,NULL,NULL}, {"loginname", ST_STR, OFFSETOF(netset_t,loginname), 0,NULL,NULL,NULL,NULL}, {"realname", ST_STR, OFFSETOF(netset_t,realname), 0,NULL,NULL,NULL,NULL}, {"message-rate", ST_FLOAT,OFFSETOF(netset_t,message_rate), 0,"0.1",NULL,NULL,NULL}, {"message-rate-max", ST_INT, OFFSETOF(netset_t,message_rate_max), 0,"1",NULL,NULL,NULL}, {"recv-per-sec", ST_INT, OFFSETOF(netset_t,recv_per_sec), 0,"1",NULL,NULL,NULL}, {"average-window", ST_INT, OFFSETOF(netset_t,average_window), 0,"1",NULL,NULL,NULL}, {"max-target", ST_INT, OFFSETOF(netset_t,max_target), 0,"1","5",NULL,NULL}, {"irc-timeout", ST_INT, OFFSETOF(netset_t,irc_timeout), 0,"0",NULL,NULL,NULL}, {"on-connect-delay", ST_INT, OFFSETOF(netset_t,on_connect_delay), 0,"0",NULL,NULL,NULL}, {"join-delay", ST_INT, OFFSETOF(netset_t,join_delay), 0,"0",NULL,NULL,NULL}, {"reconnect-delay", ST_INT, OFFSETOF(netset_t,reconnect_delay), 0,"0",NULL,NULL,NULL}, {"no-autoconnect", ST_BOOL,OFFSETOF(netset_t,no_autoconnect), VALUE_BOOL_ONOFF,NULL,NULL,NULL,NULL}, {"nick-tracking", ST_BOOL, OFFSETOF(netset_t,nick_tracking), VALUE_BOOL_ONOFF,NULL,NULL,NULL,NULL}, {"bind-ip", ST_NSTR,OFFSETOF(netset_t,bind_ip), 0,NULL,NULL,NULL,NULL}, {"info-line", ST_NSTR,OFFSETOF(netset_t,info_line), 0,NULL,NULL,NULL,NULL}, {NULL} }; static setting_t channel_data[] = { {"key", ST_NSTR, OFFSETOF(channel_t,key), 0,NULL,NULL,NULL,NULL}, {"plist", ST_INT, OFFSETOF(channel_t,plist), 0,"0",NULL,NULL}, {"track-key", ST_BOOL, OFFSETOF(channel_t,track_key), 0,NULL,NULL,NULL,NULL}, {"pformat", ST_ENUM, OFFSETOF(channel_t,pformat), 0,NULL,NULL,pformat_to_str,str_to_pformat}, {NULL} }; int netset_apply(netset_t *ns, const char *name, const char *value) { value_t *v = value_string(name,value); int ret = apply_setting(netset_data,ns,v); xfree(v); return ret; } int netset_read(netset_t *ns, pqueue_t *out) { return read_settings(netset_data,ns,out); } int network_apply_setting(network_t *n, const char *name, const char *value) { int ret; LOCK(n); ret = netset_apply(n->settings,name,value); if(strncasecmp(name,"ni",2) == 0 && strlen(name) <= 4) { if(value != NULL && n->info != NULL) { char buffer[128]; snprintf(buffer,sizeof(buffer),"NICK %s",value); enqueue_msg(n->info,buffer,EM_LOCK | EM_DUP); } } UNLOCK(n); return ret; } int network_read_settings(network_t *n, pqueue_t *out) { int ret; LOCK(n); ret = netset_read(n->settings,out); UNLOCK(n); return ret; } int channel_apply_setting(channel_t *c, const char *name, const char *value) { value_t *v = value_string(name,value); int ret; LOCK(c); ret = apply_setting(channel_data,c,v); UNLOCK(c); xfree(v); return ret; } int channel_read_settings(channel_t *c, pqueue_t *out) { int ret; LOCK(c); ret = read_settings(channel_data,c,out); UNLOCK(c); return ret; }