/* * ------------------------------------------------------- * Copyright (C) 2003-2007 Tommi Saviranta * (C) 2002 Lee Hardy * (C) 1998-2002 Sebastian Kienzl * ------------------------------------------------------- * 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. */ #ifdef HAVE_CONFIG_H #include #endif /* ifdef HAVE_CONFIG_H */ #include "server.h" #include "client.h" #include "llist.h" #include "messages.h" #include "irc.h" #include "miau.h" #include "error.h" #include "tools.h" #include "perm.h" #include "ignore.h" #include "commands.h" #include "qlog.h" #include "chanlog.h" #include "privlog.h" #include "log.h" #include "onconnect.h" #include "automode.h" #include "remote.h" #include "etc.h" #include "common.h" #include "dcc.h" #include #include #include #if HAVE_CRYPT_H #include #endif /* ifdef HAVE_CRYPT_H */ serverlist_type servers; server_info i_server; connection_type c_server; /* * Drop connection to the server. * * Give client (and server) a reason, which may be set to NULL. */ void server_drop(char *reason) { llist_node *node; llist_node *nextnode; const char *part_reason; if (reason == NULL) { part_reason = SERV_DISCONNECT; } else { part_reason = reason; } /* As we're no longer connected to server, send queue is useless. */ irc_clear_queue(); if (c_server.socket) { char buf[IRC_MSGLEN]; int r; snprintf(buf, IRC_MSGLEN, "QUIT :%s\r\n", reason); buf[IRC_MSGLEN - 1] = '\0'; r = sock_setblock(c_server.socket); irc_write_real(&c_server, buf); #ifdef CHANLOG chanlog_write_entry_all(LOG_QUIT, LOGM_QUIT, get_short_localtime(), status.nickname, reason, "", ""); #endif /* ifdef CHANLOG */ sock_close(&c_server); } i_server.connected = 0; /* * Walk active_channels and either remove channels or move to * passive_channels, depending on cfg.rejoin. */ node = active_channels.head; while (node != NULL) { channel_type *data; nextnode = node->next; data = (channel_type *) node->data; #ifdef AUTOMODE /* We no longer know if we're an operator or not. */ data->oper = -1; #endif /* ifdef AUTOMODE */ /* * We don't need to reset channel topic, it will be erased/reset * when joining the channel. */ if (cfg.chandiscon == 1) { irc_mwrite(&c_clients, ":%s NOTICE %s :%s", status.nickname, (char *) data->name, part_reason); } else if (cfg.chandiscon == 2) { /* * RFC 2812 says "Servers MUST be able to parse * arguments in the form of a list of target, but * SHOULD NOT use lists when sending PART messages to * clients." and therefore we don't part all the * channels with one command. */ irc_mwrite(&c_clients, ":%s!%s@%s PART %s :%s", status.nickname, i_client.username, i_client.hostname, (char *) data->name, part_reason); } if (cfg.rejoin == 1) { char *simple; llist_node *node; /* * Need to move channel from active_channels to * passive_channels. Also revert real channel name * (which we know sure sure) back to simple form. * There's no need to reset simple name -- it won't * change. */ simple = channel_simplify_name(data->name); xfree(data->name); data->name = simple; data->name_set = 0; node = llist_create(data); llist_add_tail(node, &passive_channels); } else { /* * * Not moving channels from list to list, therefore * freeing resources. */ channel_free(data); } /* Remove channel node from old list. */ llist_delete(node, &active_channels); node = nextnode; } if (cfg.chandiscon == 3) { irc_mwrite(&c_clients, ":miau PRIVMSG %s: %s", status.nickname, part_reason); } /* Reset server-name. */ xfree(i_server.realname); i_server.realname = xstrdup("miau"); /* Don't try connecting next server right away. */ timers.connect = 0; #ifdef UPTIME status.startup = 0; /* Reset uptime-counter. */ #endif /* ifdef UPTIME */ } /* void server_drop(char *reason) */ /* * Set "fallback" or "failsafe" server. * * This server is needed when no server have been defined and we need some * real information about the server we're connected to. */ void server_set_fallback(const llist_node *safenode) { server_type *fallback = (server_type *) servers.servers.head->data; server_type *safe = (server_type *) safenode->data; /* Free old data. */ /* * If we try to replace failsafe-server with failsafe-server, data * will be freed before it can be copied. This is why we won't do * that. */ if (fallback == safe) { return; } xfree(fallback->name); xfree(fallback->password); /* Copy current values to fallback server. */ fallback->name = xstrdup(safe->name); fallback->port = safe->port; fallback->password = (safe->password != NULL) ? xstrdup(safe->password) : NULL; fallback->working = 1; fallback->timeout = safe->timeout; } /* void server_set_fallback(const llist_node *safenode) */ /* * Resets all servers to 'working'. */ void server_reset(void) { llist_node *node; for (node = servers.servers.head->next; node != NULL; node = node->next) { ((server_type *) node->data)->working = 1; } } /* void server_reset(void) */ /* * Change server. * * If next is set and there are no more servers to connect, miau will either * reset all servers to working or quit, depending on the configuration. * * If disable != 0, old server will be marked as disfunctional. */ void server_change(int next, int disable) { llist_node *i; if (status.good_server == 1) { status.good_server = 0; return; } i_server.connected = 0; if (servers.amount == 1) { server_check_list(); return; } if (status.reconnectdelay == CONN_DISABLED) { return; } i = i_server.current; if (servers.fresh == 1) { /* * We don't know which server of those new ones we are on, so * let's use the fallback one. It's the server we're on anyway. */ i_server.current = servers.servers.head; i = servers.servers.head; } if (disable == 1 && i != servers.servers.head) { ((server_type *) i_server.current->data)->working = 0; } if (next == 1) { do { i_server.current = i_server.current->next; if (i_server.current == NULL) { i_server.current = servers.servers.head->next; } } while (! ((server_type *) i_server.current->data)->working && i_server.current != i); } if (((server_type *) i_server.current->data)->working == 0) { if (cfg.nevergiveup == 1) { report(MIAU_OUTOFSERVERSNEVER); server_reset(); i_server.current = servers.servers.head->next; } else { error(MIAU_OUTOFSERVERS); exit(EXIT_SUCCESS); } } if (next == 0) { report(SOCK_RECONNECTNOW, ((server_type *) i_server.current->data)->name); timers.connect = cfg.reconnectdelay - 1; if (timers.connect < 0) { timers.connect = 0; } } else if (i == i_server.current) { report(SOCK_RECONNECT, ((server_type *) i_server.current->data)->name, cfg.reconnectdelay); timers.connect = 0; } } /* void server_change(int next, int disable) */ void server_commands(char *command, char *param, int *pass) { upcase(command); if (xstrcmp(command, "PING") == 0) { *pass = 0; if (param != NULL && *param == ':') param++; /* Don't make this global (see ERROR) */ if (param) { /* * Although there should be no need to PONG the server * if there is stuff in queue (server shouldn't need * to ping client if it's sending data), we priorize * PONGs over everything else - just in case. */ irc_write_head(&c_server, "PONG :%s", param); } } else if (xstrcmp(command, "ERROR") == 0) { *pass = 0; server_drop((param == NULL) ? SERV_ERR : param); irc_mwrite(&c_clients, MIAU_CLOSINGLINK, (param == NULL) ? SERV_ERR : param); drop_newclient(NULL); error(IRC_SERVERERROR, (param == NULL) ? "unknown" : param); server_change(1, i_server.connected == 0); } } /* void server_commands(char *command, char *param, int *pass) */ static void parse_msg_me_ctcp(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) { #ifdef DCCBOUNCE if (c_clients.connected > 0 && cmdindex == CMD_PRIVMSG && cfg.dccbounce && (xstrcasecmp(param2 + 2, "DCC\1") == 0)) { char dcct[IRC_MSGLEN]; strncpy(dcct, param2 + 1, IRC_MSGLEN); if (dcc_initiate(dcct, IRC_MSGLEN, 0)) { irc_mwrite(&c_clients, ":%s PRIVMSG %s :%s", origin, param1, dcct); *pass = 0; } } #endif /* ifdef DCCBOUNCE */ #ifdef DCCBOUNCE #ifdef CTCTPREPLIES else #endif /* ifdef CTCPREPLIES */ #endif /* ifdef DCCBOUNCE */ #ifdef CTCPREPLIES if (! is_ignore(hostname, IGNORE_CTCP) && c_clients.connected == 0 && status.allowreply == 1) { report(CLNT_CTCP, param2 + 1, origin); if (xstrcmp(param2 + 2, "VERSION\1") == 0) { irc_notice(&c_server, nick, VERSIONREPLY); } else if (xstrcmp(param2 + 2, "PING") == 0) { if (strlen(param2 + 1) > 6) { irc_notice(&c_server, nick, "%s", param2 + 1); } } else if (xstrcmp(param2 + 2, "CLIENTINFO\1") == 0) { irc_notice(&c_server, nick, CLIENTINFOREPLY); } ignore_add(hostname, 6, IGNORE_CTCP); status.allowreply = 0; timers.reply = 0; } /* CTCP-replies */ else if (is_ignore(hostname, IGNORE_CTCP) || status.allowreply == 0) { report(CLNT_CTCPNOREPLY, param2 + 1, origin); } #endif /* ifdef CTCPREPLIES */ } /* static void parse_msg_me_ctcp(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) */ static int parse_msg_me(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) { if (is_perm(&ignorelist, origin)) { return 0; } #ifdef PRIVLOG /* Should we log? */ if ((c_clients.connected == 0 && (cfg.privlog & 0x01)) || (c_clients.connected > 0 && (cfg.privlog & 0x02))) { privlog_write(nick, PRIVLOG_IN, cmdindex, param2 + 1); } #endif /* ifdef PRIVLOG */ /* Is this a special (CTCP/DCC) -message ? */ if (param2[1] == '\1') { parse_msg_me_ctcp(origin, nick, hostname, param1, param2, cmdindex, pass); return 0; } #ifdef NEED_CMDPASSWD /* Remote command for bouncer. */ else if (cfg.cmdpasswd != NULL && param2 != NULL && param2[0] != '\0' && param2[1] == ':') { int passok = 0; int passlen; char *lparam; lparam = xstrdup(param2 + 2); passlen = pos(lparam, ' '); if (passlen != -1) { lparam[passlen] = '\0'; if (strlen(cfg.cmdpasswd) == 13) { /* Assume it's crypted */ if (xstrcmp(crypt(lparam, cfg.cmdpasswd), cfg.cmdpasswd) == 0) { passok = 1; } } else if (xstrcmp(lparam, cfg.cmdpasswd) == 0) { passok = 1; } lparam[passlen] = ' '; } if (passok) { char *t; char *command; char *params = NULL; t = strtok(lparam, " "); command = strtok(NULL, " "); if (command != NULL) { params = strchr(command, '\0') + 1; } if (params != NULL) { upcase(command); *pass = remote_cmd(command, params, nick); xfree(lparam); return 0; } } xfree(lparam); } #endif /* ifdef NEED_CMDPASSWD */ /* Normal PRIVMSG/NOTICE to client. */ #ifdef INBOX #ifndef QUICKLOG /* * Note that we do inbox here only is privmsglog is enabled and * quicklogging is disabled. */ if (inbox != NULL) { /* termination + validity guaranteed */ fprintf(inbox, "%s <%s> %s\n", get_short_localtime(), origin, param2 + 1); fflush(inbox); } #endif /* ifdef QUICKLOG */ #endif /* ifdef INBOX */ if (cfg.forwardmsg) { int pos; timers.forward = 0; if (forwardmsg == NULL) { /* initial size */ /* need space for terminator */ forwardmsgsize = 1; } pos = forwardmsgsize - 1; forwardmsgsize += strlen(origin) + strlen(param2 + 1) + 4; /* strlen("() \n") == 4 */ forwardmsg = (char *) xrealloc(forwardmsg, forwardmsgsize); /* paranoid! */ snprintf(forwardmsg + pos, forwardmsgsize - pos, "(%s) %s\n", origin, param2 + 1); forwardmsg[forwardmsgsize - 1] = '\0'; } return 1; } /* static int parse_msg_me(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) */ static int parse_msg_chan(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) { #ifdef CHANLOG channel_type *chptr; #endif /* ifdef CHANLOG */ const char *chan; /* channel wallops - notice @#channel etc :-) */ if ((param1[0] == '@' || param1[0] == '%' || param1[0] == '+') && channel_is_name(param1 + 1) != 0) { chan = param1 + 1; } else { chan = param1; } #ifdef CHANLOG /* * evil kludge: it's way too easy to confuse normal message to * channel "++foo" with a channel wallop (mode + to channel * "+foo"), so we have to try both. At least we know to try * the more obvious first. */ chptr = channel_find(chan, LIST_ACTIVE); if (chptr == NULL) { chptr = channel_find(param1, LIST_ACTIVE); } if (chptr != NULL && chanlog_has_log(chptr, LOG_MESSAGE)) { char *t; t = log_prepare_entry(nick, param2 + 1); if (t == NULL) { if (cmdindex == CMD_PRIVMSG + MINCOMMANDVALUE) { chanlog_write_entry(chptr, LOGM_MESSAGE, get_short_localtime(), nick, param2 + 1); } else { /* must be notice then */ chanlog_write_entry(chptr, LOGM_NOTICE, get_short_localtime(), nick, param2 + 1); } } else { chanlog_write_entry(chptr, "%s", t); } } #endif /* ifdef CHANLOG */ return 0; } /* static int parse_msg_chan(const char *origin, const char *nick, const char *hostname, const char *param1, const char *param2, int cmdindex, int *pass) */ static int parse_privmsg(char *param1, char *param2, char *nick, char *hostname, const int cmdindex, int *pass) { char *origin; int osize; int isprivmsg = 0; if (nick == NULL || hostname == NULL || param2 == NULL) { #ifdef ENDUSERDEBUG enduserdebug("parse_privmsg(): " "param1 = %s, param2 = %s, " "nick = %s, hostname = %d", param1 == NULL ? "NULL" : param1, param2 == NULL ? "NULL" : param2, nick == NULL ? "NULL" : nick, hostname == NULL ? "NULL" : hostname); #endif /* ifdef ENDUSERDEBUG */ return 0; } /* paranoid */ osize = strlen(nick) + strlen(hostname) + 2; origin = xmalloc(osize); snprintf(origin, osize, "%s!%s", nick, hostname); origin[osize - 1] = '\0'; /* who is it for? */ if (xstrcasecmp(param1, status.nickname) == 0) { parse_msg_me(origin, nick, hostname, param1, param2, cmdindex, pass); isprivmsg = 1; } else { parse_msg_chan(origin, nick, hostname, param1, param2, cmdindex, pass); } xfree(origin); return isprivmsg; } /* static int parse_privmsg(char *param1, char *param2, char *nick, char *hostname, const int cmdindex, int *pass) */ int server_read(void) { char *backup = NULL; char *origin, *command, *param1, *param2; int rstate; int pass = 0; int commandno; rstate = irc_read(&c_server); if (rstate <= 0) { return rstate; } if (c_server.buffer[0] == '\0') { return 0; } /* new data... go for it ! */ pass = 1; backup = xstrdup(c_server.buffer); if (c_server.buffer[0] == ':') { /* reply */ origin = strtok(c_server.buffer + 1, " "); command = strtok(NULL, " "); param1 = strtok(NULL, " "); param2 = strtok(NULL, "\0"); #ifdef DEBUG #ifdef OBSOLETE printf("[%s] [%s] [%s] [%s]\n", origin, command, param1, param2); #endif /* ifdef OBSOLETE */ #endif /* ifdef DEBUG */ if (command != 0) { commandno = atoi(command); if (commandno == 0) { commandno = MINCOMMANDVALUE + command_find(command); } server_reply(commandno, backup, origin, param1, param2, &pass); } } else { /* Command */ command = strtok(c_server.buffer, " "); param1 = strtok(NULL, "\0"); if (command) { server_commands(command, param1, &pass); } } /* We wouldn't need to check c_clients.connected... */ if (c_clients.connected > 0 && pass) { /* * Having '"%s", buffer' instead of plain * 'buffer' is essential because we don't want * our string processed any further by va. */ irc_mwrite(&c_clients, "%s", backup); } xfree(backup); return 0; } /* int server_read(void) */ /* * Check number of servers and consistency of i_server.currect. * * If there are only fallback-server (or no servers at all !?) left, * warn the user about this. Also, if the server we're connected to is on the * list, set i_server.current to index of it. */ void server_check_list(void) { llist_node *ptr; if (servers.amount <= 1) { /* There are no other servers ! */ /* This is important ! */ irc_mwrite(&c_clients, ":miau NOTICE %s :%s", status.nickname, CLNT_NOSERVERS); /* Don't try to reconnect. */ status.reconnectdelay = CONN_DISABLED; return; } /* Next try to connect due in cfg.reconndelay seconds. */ status.reconnectdelay = cfg.reconnectdelay; /* See if our server is on the list. */ for (ptr = servers.servers.head->next; ptr != NULL; ptr = ptr->next) { /* We'll just forget the passwords. Right ? */ if (xstrcmp(((server_type *) i_server.current->data)->name, ((server_type *) ptr->data)->name) == 0 && ((server_type *) i_server.current->data)->port == ((server_type *) ptr->data)->port) { i_server.current = ptr; servers.fresh = 0; } } } /* void server_check_list(void) */ void server_reply(const int command, char *original, char *origin, char *param1, char *param2, int *pass) { channel_type *chptr; char *work = NULL; char *nick, *hostname; char *t; int isprivmsg = 0; int n; int newserv_disconn = 0; t = strchr(origin, '!'); if (t != NULL) { *t = '\0'; t++; nick = xstrdup(origin); hostname = xstrdup(t); } else { nick = xstrdup(origin); hostname = xstrdup(origin); } switch (command) { /* Replies. */ /* Just signed in to server. */ case RPL_WELCOME: i_server.connected++; xfree(i_server.realname); i_server.realname = xstrdup(origin); xfree(status.nickname); status.nickname = xstrdup(param1); xfree(i_server.greeting[0]); i_server.greeting[0] = xstrdup(param2); n = lastpos(i_server.greeting[0], ' '); if (n != -1) { i_server.greeting[0][n] = '\0'; } xfree(status.idhostname); t = strchr(param2, '!'); if (t != NULL) { status.idhostname = xstrdup(t + 1); status.goodhostname = pos(status.idhostname, '@') + 1; } else { /* * While not giving hostname is ok with RFC, * clients like Chatzilla expect to get it. * Thanks to James Ross and Oliver Eikemeier * for pointing this out. * * http://bugzilla.mozilla.org/show_bug.cgi?id=242095 */ status.idhostname = xstrdup("miau@miau"); status.goodhostname = 5; } #ifdef UPTIME if (! status.startup) { time(&status.startup); } #endif /* ifdef UPTIME */ report(IRC_CONNECTED, i_server.realname); #ifdef ONCONNECT onconnect_do(); #endif /* ifdef ONCONNECT */ /* Set user modes if any and if no clients connected. */ if (cfg.usermode != NULL && c_clients.connected == 0) { irc_write(&c_server, "MODE %s %s", status.nickname, cfg.usermode); } /* * Be default we're not away, but set_away() may * change this. */ status.awaystate &= ~AWAY; set_away(NULL); /* No special message. */ /* See if we should join channels. */ timers.join = JOINTRYINTERVAL; /* Also reset channel's join-count. */ LLIST_WALK_H(passive_channels.head, channel_type *); data->jointries = JOINTRIES_UNSET; LLIST_WALK_F; for (n = 0; n < RPL_ISUPPORT_LEN; n++) { FREE(i_server.isupport[n]); } newserv_disconn = NEWSERV_DISCONN_ALWAYS; break; /* More registeration-time replies... */ case RPL_YOURHOST: { const char *t = i_server.greeting[0]; if (t != NULL && xstrcasecmp(param2, t) != 0) { newserv_disconn = NEWSERV_DISCONN_MYINFO; } } case RPL_CREATED: case RPL_MYINFO: xfree(i_server.greeting[command - 1]); i_server.greeting[command - 1] = xstrdup(param2); if (newserv_disconn == 0) { newserv_disconn = NEWSERV_DISCONN_ALWAYS; } break; /* Supported features */ case RPL_ISUPPORT: for (n = 0; n < RPL_ISUPPORT_LEN; n++) { if (i_server.isupport[n] == NULL) { i_server.isupport[n] = xstrdup(param2); break; } } newserv_disconn = NEWSERV_DISCONN_ALWAYS; break; /* This server is restricted. */ case RPL_RESTRICTED: if (cfg.jumprestricted) { server_drop(CLNT_RESTRICTED); report(SERV_RESTRICTED); server_change(1, 1); } break; /* Channel has no topic. */ case RPL_NOTOPIC: /* * : RPL_NOTOPIC * :No topic is set */ t = strchr(param2, ' '); if (t != NULL) { *t = '\0'; } else { break; } chptr = channel_find(param2, LIST_ACTIVE); if (chptr != NULL) { channel_topic(chptr, NULL); } break; /* Channel topic is... */ case RPL_TOPIC: /* : RPL_TOPIC : */ { channel_type *chptr; char *p; p = strchr(param2, ' '); if (p != NULL) { *p++ = '\0'; } else { break; } chptr = channel_find(param2, LIST_ACTIVE); if (chptr != NULL) { channel_topic(chptr, p); } } break; /* Who set this topic ? */ case RPL_TOPICWHO: /* * : RPL_TOPICWHO *