/* dircproxy * Copyright (C) 2002 Scott James Remnant . * All Rights Reserved. * * irc_server.c * - Handling of servers connected to the proxy * - Reconnection to servers * - Functions to send data to servers in the correct protocol format * -- * @(#) $Id: irc_server.c,v 1.60 2002/01/31 14:56:37 scott Exp $ * * This file is distributed according to the GNU General Public * License. For full details, read the top of 'main.c' or the * file called COPYING that was distributed with this code. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sprintf.h" #include "net.h" #include "dns.h" #include "timers.h" #include "dcc_net.h" #include "irc_log.h" #include "irc_net.h" #include "irc_prot.h" #include "irc_string.h" #include "irc_client.h" #include "irc_server.h" /* forward declarations */ static void _ircserver_reconnect(struct ircproxy *, void *); static void _ircserver_connect2(struct ircproxy *, void *, struct in_addr *, const char *); static void _ircserver_connect3(struct ircproxy *, void *, struct in_addr *, const char *); static void _ircserver_connected(struct ircproxy *, int); static void _ircserver_connected2(struct ircproxy *, void *, struct in_addr *, const char *); static void _ircserver_connectfailed(struct ircproxy *, int, int); static void _ircserver_data(struct ircproxy *, int); static void _ircserver_error(struct ircproxy *, int, int); static int _ircserver_gotmsg(struct ircproxy *, const char *); static int _ircserver_close(struct ircproxy *); static int _ircserver_lost(struct ircproxy *); static void _ircserver_ping(struct ircproxy *, void *); static void _ircserver_stoned(struct ircproxy *, void *); static void _ircserver_antiidle(struct ircproxy *, void *); static int _ircserver_forclient(struct ircproxy *, struct ircmessage *); static int _ircserver_send_dccreject(struct ircproxy *, const char *); /* Time/date format for strftime(3) */ #define CTCP_TIMEDATE_FORMAT "%a, %d %b %Y %H:%M:%S %z" /* hook for timer code to reconnect to a server */ static void _ircserver_reconnect(struct ircproxy *p, void *data) { debug("Reconnecting to server"); /* Choose the next server. If we have no more, choose the first again and increment attempts */ p->conn_class->next_server = p->conn_class->next_server->next; if (!p->conn_class->next_server) { p->conn_class->next_server = p->conn_class->servers; p->server_attempts++; } debug("%sAttempt %d", (p->server_status & IRC_SERVER_SEEN ? "" : "Initial "), p->server_attempts + 1); if (p->conn_class->server_maxattempts && (p->server_attempts >= p->conn_class->server_maxattempts)) { /* If we go over maximum reattempts, then give up */ if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Giving up on servers. Time to quit"); p->conn_class = 0; if (p->client_status & IRC_CLIENT_CONNECTED) { ircclient_send_error(p, "Maximum connection attempts exceeded"); ircclient_close(p); } debug("Giving up on servers, reattempted too much"); p->dead = 1; } else if (!(p->server_status & IRC_SERVER_SEEN) && p->conn_class->server_maxinitattempts && (p->server_attempts >= p->conn_class->server_maxinitattempts)) { /* Otherwise check initial attempts if its our first time */ if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Giving up on servers. Time to quit"); p->conn_class = 0; if (p->client_status & IRC_CLIENT_CONNECTED) { ircclient_send_error(p, "Maximum initial connection attempts exceeded"); ircclient_close(p); } debug("Giving up on servers, can't get initial connection"); p->dead = 1; } else { /* Attempt a new connection */ ircserver_connect(p); } } /* Called to initiate a connection to a server */ int ircserver_connect(struct ircproxy *p) { char *server; debug("Connecting to server (stage 1)"); if (timer_exists((void *)p, "server_recon")) { debug("Connection already in progress"); if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Connection already in progress..."); return 0; } server = x_strdup(p->conn_class->next_server->str); if (strchr(server, ':') != strrchr(server, ':')) { /* More than one :, second denotes password */ char *pass; pass = strrchr(server, ':'); *(pass++) = 0; if (strlen(pass)) p->serverpassword = x_strdup(pass); } if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Looking up %s...", server); /* DNS lookup the server */ dns_filladdr((void *)p, server, p->conn_class->server_port, 1, &(p->server_addr), DNS_FUNCTION(_ircserver_connect2), 0); free(server); return 0; } /* Called to initiate a connection to a server once its been looked up */ static void _ircserver_connect2(struct ircproxy *p, void *data, struct in_addr *addr, const char *host) { if (!host || !addr) { debug("DNS failure, retrying"); timer_new((void *)p, "server_recon", p->conn_class->server_retry, TIMER_FUNCTION(_ircserver_reconnect), (void *)0); free(p->serverpassword); p->serverpassword = 0; return; } debug("Resolved server"); /* Copy the found information into p */ free(p->servername); p->servername = x_strdup(host); p->server_addr.sin_addr.s_addr = addr->s_addr; if (p->conn_class->local_address) { dns_addrfromhost((void *)p, 0, p->conn_class->local_address, DNS_FUNCTION(_ircserver_connect3)); } else { _ircserver_connect3(p, 0, 0, 0); } } /* Called to initiate a connection to a server once its been looked up and the local_host has been looked up */ static void _ircserver_connect3(struct ircproxy *p, void *data, struct in_addr *addr, const char *host) { int ret; #ifdef HAVE_SETEUID int switched = 0; pid_t old_euid; #endif /* HAVE_SETEUID */ debug("Connecting to %s port %d", p->servername, ntohs(p->server_addr.sin_port)); if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Connecting to %s port %d", p->servername, ntohs(p->server_addr.sin_port)); #ifdef HAVE_SETEUID old_euid = geteuid(); /* Switch to a user */ if (p->conn_class->switch_user) { struct passwd *pwd; /* switch_user can be a username or a user id */ pwd = getpwnam(p->conn_class->switch_user); if (pwd) { debug("Switching to user '%s'", p->conn_class->switch_user); } else { uid_t uid; /* Make sure that its "0" not an invalid user if atoi returns 0 */ uid = atoi(p->conn_class->switch_user); if (uid || !strcmp(p->conn_class->switch_user, "0")) { pwd = getpwuid(uid); if (pwd) debug("Switching to user #%d", uid); } } /* Set the effective user id if we're root */ if (pwd && (getuid() == 0)) { if (!seteuid(pwd->pw_uid)) { switched = 1; } else { syscall_fail("seteuid", 0, 0); } } /* Warn if we didn't */ if (!switched) { if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "(warning) Couldn't switch to username %s", p->conn_class->switch_user); } } #endif /* HAVE_SETEUID */ p->server_sock = net_socket(); #ifdef HAVE_SETEUID /* Switch back to our original euid */ if (switched) { if (seteuid(old_euid)) { /* Oh, Fuck! */ syscall_fail("seteuid", 0, 0); abort(); } } #endif /* HAVE_SETEUID */ if (p->server_sock == -1) { ret = -1; } else { if (p->conn_class->server_keepalive) net_keepalive(p->server_sock); if (p->conn_class->local_address) { if (addr) { struct sockaddr_in local_addr; memset(&local_addr, 0, sizeof(struct sockaddr_in)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = addr->s_addr; if (bind(p->server_sock, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in))) { if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "(warning) Couldn't use local address %s", host); } else { free(p->hostname); p->hostname = (host ? x_strdup(host) : p->conn_class->local_address); } } else { if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "(warning) Couldn't find address for %s", p->conn_class->local_address); } } if (connect(p->server_sock, (struct sockaddr *)&(p->server_addr), sizeof(struct sockaddr_in)) && (errno != EINPROGRESS)) { syscall_fail("connect", p->servername, 0); net_close(&(p->server_sock)); ret = -1; } else { ret = 0; } } if (ret) { if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Connection failed: %s", strerror(errno)); debug("Connection failed: %s", strerror(errno)); net_close(&(p->server_sock)); timer_new((void *)p, "server_recon", p->conn_class->server_retry, TIMER_FUNCTION(_ircserver_reconnect), (void *)0); free(p->serverpassword); p->serverpassword = 0; } else { p->server_status |= IRC_SERVER_CREATED; net_hook(p->server_sock, SOCK_CONNECTING, (void *)p, ACTIVITY_FUNCTION(_ircserver_connected), ERROR_FUNCTION(_ircserver_connectfailed)); debug("Connection in progress"); } } /* Called when a new server has connected */ static void _ircserver_connected(struct ircproxy *p, int sock) { if (sock != p->server_sock) { error("Unexpected socket %d in _ircserver_connected, expected %d", sock, p->server_sock); net_close(&sock); return; } debug("Connection succeeded"); p->server_status |= IRC_SERVER_CONNECTED; net_hook(p->server_sock, SOCK_NORMAL, (void *)p, ACTIVITY_FUNCTION(_ircserver_data), ERROR_FUNCTION(_ircserver_error)); if (p->conn_class->server_throttle) net_throttle(p->server_sock, p->conn_class->server_throttle[0], p->conn_class->server_throttle[1]); if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Connected to server"); if (p->conn_class->log_events & IRC_LOG_SERVER) irclog_notice(p, p->nickname, PACKAGE, "Connected to server: %s", p->servername); /* Need to try and look up our local hostname now we have a socket to somewhere that will tell us */ if (!p->hostname) { struct sockaddr_in sock_addr; int len; len = sizeof(struct sockaddr_in); if (!getsockname(p->server_sock, (struct sockaddr *)&sock_addr, &len)) { dns_hostfromaddr((void *)p, 0, sock_addr.sin_addr, DNS_FUNCTION(_ircserver_connected2)); return; } else { syscall_fail("getsockname", "", 0); _ircserver_connected2(p, 0, 0, 0); } } else { _ircserver_connected2(p, 0, 0, 0); } } /* Called when a new server has connected and we have a local hostname */ static void _ircserver_connected2(struct ircproxy *p, void *data, struct in_addr *addr, const char *name) { char *username; if (!p->hostname && name) p->hostname = x_strdup(name); username = ircprot_sanitize_username(p->username); if (p->serverpassword) { ircserver_send_command(p, "PASS", ":%s", p->serverpassword); free(p->serverpassword); p->serverpassword = 0; } /* When connecting to a server, may as well try to start fresh, eh? */ if (p->setnickname && strcmp(p->nickname, p->setnickname)) { free(p->nickname); p->nickname = x_strdup(p->setnickname); } ircserver_send_command(p, "NICK", ":%s", p->nickname); ircserver_send_command(p, "USER", "%s 0 * :%s", username, p->realname); p->server_status |= IRC_SERVER_INTRODUCED; free(username); /* Begin stoned server checking */ if (p->conn_class->server_pingtimeout) { timer_new((void *)p, "server_ping", (int)(p->conn_class->server_pingtimeout / 2), TIMER_FUNCTION(_ircserver_ping), (void *)0); timer_new((void *)p, "server_stoned", p->conn_class->server_pingtimeout, TIMER_FUNCTION(_ircserver_stoned), (void *)0); } /* Begin anti-idle */ if (p->conn_class->idle_maxtime) timer_new((void *)p, "server_antiidle", p->conn_class->idle_maxtime, TIMER_FUNCTION(_ircserver_antiidle), (void *)0); } /* Called when a connection fails */ static void _ircserver_connectfailed(struct ircproxy *p, int sock, int bad) { if (sock != p->server_sock) { error("Unexpected socket %d in _ircserver_connectfailed, expected %d", sock, p->server_sock); net_close(&sock); return; } debug("Connection failed"); if (IS_CLIENT_READY(p)) ircclient_send_notice(p, "Connection failed: %s", strerror(errno)); net_close(&(p->server_sock)); p->server_status &= ~(IRC_SERVER_CREATED); timer_new((void *)p, "server_recon", p->conn_class->server_retry, TIMER_FUNCTION(_ircserver_reconnect), (void *)0); } /* Called when a server sends us stuff. */ static void _ircserver_data(struct ircproxy *p, int sock) { char *str; if (sock != p->server_sock) { error("Unexpected socket %d in _ircserver_data, expected %d", sock, p->server_sock); net_close(&sock); return; } str = 0; while (!p->dead && (p->server_status & IRC_SERVER_CONNECTED) && net_gets(p->server_sock, &str, "\r\n") > 0) { debug("<< '%s'", str); _ircserver_gotmsg(p, str); free(str); } } /* Called on server disconnection or error */ static void _ircserver_error(struct ircproxy *p, int sock, int bad) { if (sock != p->server_sock) { error("Unexpected socket %d in _ircserver_error, expected %d", sock, p->server_sock); net_close(&sock); return; } if (bad) { debug("Socket error"); } else { debug("Server disconnected"); } _ircserver_close(p); } /* Called when we get an irc protocol data from a server */ static int _ircserver_gotmsg(struct ircproxy *p, const char *str) { struct ircmessage msg; int squelch = 1; int important = 0; if (ircprot_parsemsg(str, &msg) == -1) return -1; /* Check source */ if (!msg.src.orig) { msg.src.orig = x_strdup(p->servername); msg.src.fullname = x_strdup(p->servername); msg.src.name = x_strdup(p->servername); } /* 437 is bizarre, it either means Nickname is juped or Channel is juped */ if (!irc_strcasecmp(msg.cmd, "437")) { if (msg.numparams >= 2) { if (!irc_strcasecmp(p->nickname, msg.params[1])) { /* Our nickname is Juped - make it a 433 */ free(msg.cmd); msg.cmd = x_strdup("433"); } else { /* Channel is juped - make it a 471 */ free(msg.cmd); msg.cmd = x_strdup("471"); } } } if (!irc_strcasecmp(msg.cmd, "001")) { /* Use 001 to get the servername */ if (msg.src.type & IRC_SERVER) { free(p->servername); p->servername = x_strdup(msg.src.name); } } else if (!irc_strcasecmp(msg.cmd, "002")) { /* Ignore 002 */ } else if (!irc_strcasecmp(msg.cmd, "003")) { /* Ignore 003 */ } else if (!irc_strcasecmp(msg.cmd, "004")) { /* 004 contains all the juicy info, use it */ if (msg.numparams >= 5) { free(p->servername); free(p->serverver); free(p->serverumodes); free(p->servercmodes); p->servername = x_strdup(msg.params[1]); p->serverver = x_strdup(msg.params[2]); p->serverumodes = x_strdup(msg.params[3]); p->servercmodes = x_strdup(msg.params[4]); p->server_status |= IRC_SERVER_GOTWELCOME | IRC_SERVER_SEEN; p->server_attempts = 0; if (IS_CLIENT_READY(p) && !(p->client_status & IRC_CLIENT_SENTWELCOME)) ircclient_welcome(p); } /* Also use this numeric to send everything state-related to the client. From this moment on, we assume the server is happy. */ /* Restore the user mode */ if (p->modes) ircserver_send_command(p, "MODE", "%s +%s", p->nickname, p->modes); /* Restore the away message */ if (p->awaymessage) { ircserver_send_command(p, "AWAY", ":%s", p->awaymessage); } else if (!(p->client_status & IRC_CLIENT_AUTHED) && p->conn_class->away_message) { ircserver_send_command(p, "AWAY", ":%s", p->conn_class->away_message); } /* Restore the channel list */ if (p->channels) { struct ircchannel *c; c = p->channels; while (c) { if (!c->unjoined) { if (c->key) { ircserver_send_command(p, "JOIN", "%s :%s", c->name, c->key); } else { ircserver_send_command(p, "JOIN", ":%s", c->name); } } c = c->next; } } } else if (!irc_strcasecmp(msg.cmd, "005")) { /* Ignore 005 */ } else if (!irc_strcasecmp(msg.cmd, "375")) { /* Ignore 375 unless allow_motd */ if (p->allow_motd) squelch = 0; } else if (!irc_strcasecmp(msg.cmd, "372")) { /* Ignore 372 unless allow_motd */ if (p->allow_motd) squelch = 0; } else if (!irc_strcasecmp(msg.cmd, "376")) { /* Ignore 376 unless allow_motd */ if (p->allow_motd) { squelch = 0; p->allow_motd = 0; } } else if (!irc_strcasecmp(msg.cmd, "422")) { /* Ignore 422 unless allow_motd */ if (p->allow_motd) { squelch = 0; p->allow_motd = 0; } } else if (!irc_strcasecmp(msg.cmd, "431") || !irc_strcasecmp(msg.cmd, "432") || !irc_strcasecmp(msg.cmd, "433") || !irc_strcasecmp(msg.cmd, "436") || !irc_strcasecmp(msg.cmd, "438")) { /* Our nickname got rejected. Don't update setnickname! */ if (msg.numparams >= 2) { /* Fall back on our original if we can */ if (strlen(msg.params[0]) && strcmp(msg.params[0], "*")) { if (p->client_status == IRC_CLIENT_ACTIVE) ircclient_send_selfcmd(p, "NICK", ":%s", msg.params[0]); ircclient_nick_changed(p, msg.params[0]); ircclient_checknickname(p); squelch = 0; } else { /* We don't have a nickname anymore. Don't free it, so we've still really got the old one lying around. */ p->client_status &= ~(IRC_CLIENT_GOTNICK); /* If we don't have a client connected, then we have to regenerate a new nickname ourselves... Otherwise we can just let the client do it */ if (!(p->client_status & IRC_CLIENT_CONNECTED)) { ircclient_generate_nick(p, msg.params[1]); } else { /* Have to anti-squelch this manually */ net_send(p->client_sock, "%s\r\n", msg.orig); } } } else { squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "471") || !irc_strcasecmp(msg.cmd, "473") || !irc_strcasecmp(msg.cmd, "474")) { if (msg.numparams >= 2) { /* Can't join a channel */ /* No client connected? Lets rejoin it for it */ if (p->client_status != IRC_CLIENT_ACTIVE) { struct ircchannel *chan; chan = ircnet_fetchchannel(p, msg.params[1]); if (chan) { chan->inactive = 1; ircnet_rejoin(p, chan->name); } } else { /* Let it handle it */ ircnet_delchannel(p, msg.params[1]); } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "403") || !irc_strcasecmp(msg.cmd, "475") || !irc_strcasecmp(msg.cmd, "476") || !irc_strcasecmp(msg.cmd, "405")) { if (msg.numparams >= 2) { struct ircchannel *c; /* Can't join a channel, permanent error */ c = ircnet_fetchchannel(p, msg.params[1]); if (c) { /* No client connected? Better notify it */ if (p->client_status != IRC_CLIENT_ACTIVE) { if (p->conn_class->log_events & IRC_LOG_ERROR) { if (msg.numparams >= 3) { irclog_notice(p, p->nickname, PACKAGE, "Couldn't rejoin %s: %s (%s)", msg.params[1], msg.params[2], msg.cmd); } else { irclog_notice(p, p->nickname, PACKAGE, "Couldn't rejoin %s (%s)", msg.params[1], msg.cmd); } } /* Set it to an unjoined channel until the client comes back */ c->unjoined = 1; } else { /* Client connected, so we really can't join it - delete it */ ircnet_delchannel(p, msg.params[1]); } } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "411")) { /* Ignore 411 if squelch_411 */ if (p->squelch_411) { p->squelch_411 = 0; } else { squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "324")) { /* Here be channel modes */ if (msg.numparams >= 2) { struct ircchannel *c; /* Set this to 1 in a minute if we need to */ squelch = 0; c = ircnet_fetchchannel(p, msg.params[1]); if (c) { if (msg.numparams >= 3) { ircnet_channel_mode(p, c, &msg, 2); } else { free(c->key); } /* Look for the channel in the squelch_modes list */ if (p->squelch_modes) { struct strlist *s, *l; l = 0; s = p->squelch_modes; while (s) { if (!irc_strcasecmp(msg.params[1], s->str)) { struct strlist *n; n = s->next; free(s->str); free(s); /* Was in the squelch list, so remove it and stop looking */ s = *(l ? &(l->next) : &(p->squelch_modes)) = n; squelch = 1; break; } else { l = s; s = s->next; } } } } } } else if (!irc_strcasecmp(msg.cmd, "477")) { /* No channel modes for this channel */ if (msg.numparams >= 2) { struct ircchannel *c; /* Set this to 1 in a minute if we need to */ squelch = 0; c = ircnet_fetchchannel(p, msg.params[1]); if (c) { debug("No channel modes for %s", c->name); free(c->key); /* Look for the channel in the squelch_modes list */ if (p->squelch_modes) { struct strlist *s, *l; l = 0; s = p->squelch_modes; while (s) { if (!irc_strcasecmp(msg.params[1], s->str)) { struct strlist *n; n = s->next; free(s->str); free(s); /* Was in the squelch list, so remove it and stop looking */ s = *(l ? &(l->next) : &(p->squelch_modes)) = n; squelch = 1; break; } else { l = s; s = s->next; } } } } } } else if (!irc_strcasecmp(msg.cmd, "PING")) { /* Reply to pings for the client */ if (msg.numparams == 1) { net_sendurgent(p->server_sock, "PONG :%s\r\n", msg.params[0]); debug("=> 'PONG :%s'", msg.params[0]); } else if (msg.numparams >= 2) { net_sendurgent(p->server_sock, "PONG %s :%s\r\n", msg.params[0], msg.params[1]); debug("=> 'PONG %s :%s'", msg.params[0], msg.params[1]); } /* but let it see them */ squelch = 0; } else if (!irc_strcasecmp(msg.cmd, "PONG")) { /* Use pongs to reset the server_stoned timer */ if (p->allow_pong) squelch = 0; if (p->conn_class->server_pingtimeout) { timer_del((void *)p, "server_stoned"); timer_new((void *)p, "server_stoned", p->conn_class->server_pingtimeout, TIMER_FUNCTION(_ircserver_stoned), (void *)0); p->allow_pong = 0; } } else if (!irc_strcasecmp(msg.cmd, "NICK")) { if (_ircserver_forclient(p, &msg)) { /* Server telling us our nickname */ if (msg.numparams >= 1) { if (strcmp(p->nickname, msg.params[0])) { if (IS_CLIENT_READY(p)) ircclient_send_selfcmd(p, "NICK", ":%s", msg.params[0]); ircclient_nick_changed(p, msg.params[0]); if (p->conn_class->log_events & IRC_LOG_NICK) irclog_notice(p, p->nickname, p->servername, "You changed your nickname to %s", msg.params[0]); } /* Is this as a result of a client NICK command? */ if (p->expecting_nick) { ircclient_setnickname(p); p->expecting_nick = 0; } ircclient_checknickname(p); } } else { /* Someone changing their nickname */ if (msg.numparams >= 1) { if (p->conn_class->log_events & IRC_LOG_NICK) irclog_notice(p, p->nickname, p->servername, "%s changed nickname to %s", msg.src.fullname, msg.params[0]); } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "MODE")) { if (msg.numparams >= 2) { struct ircchannel *c; if (!irc_strcasecmp(p->nickname, msg.params[0])) { /* Personal mode change */ int param; if (p->conn_class->log_events & IRC_LOG_MODE) irclog_notice(p, p->nickname, p->servername, "Your mode was changed: %s", msg.paramstarts[1]); for (param = 1; param < msg.numparams; param++) ircclient_change_mode(p, msg.params[param]); /* Check for refuse modes */ if (p->modes && p->conn_class->refuse_modes && (strcspn(p->modes, p->conn_class->refuse_modes) != strlen(p->modes))) { char *mode; debug("Got refusal mode from server"); ircserver_send_command(p, "QUIT", ":Don't like this server - %s %s", PACKAGE, VERSION); mode = x_sprintf("-%s", p->conn_class->refuse_modes); debug("Auto-mode-change '%s'", mode); ircclient_change_mode(p, mode); free(mode); _ircserver_close(p); } } else if ((c = ircnet_fetchchannel(p, msg.params[0]))) { /* Channel mode change */ ircnet_channel_mode(p, c, &msg, 1); if (p->conn_class->log_events & IRC_LOG_MODE) irclog_notice(p, c->name, p->servername, "%s changed mode: %s", msg.src.fullname, msg.paramstarts[1]); } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "TOPIC")) { if (msg.numparams >= 2) { struct ircchannel *c; /* Channel topic change */ c = ircnet_fetchchannel(p, msg.params[0]); if (p->conn_class->log_events & IRC_LOG_TOPIC) irclog_notice(p, c->name, p->servername, "%s changed topic: %s", msg.src.fullname, msg.paramstarts[1]); squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "JOIN")) { if (_ircserver_forclient(p, &msg)) { /* Server telling us we joined a channel */ if (msg.numparams >= 1) { struct ircchannel *c; c = ircnet_fetchchannel(p, msg.params[0]); if (c && c->inactive) { /* Must have got KICK'd or something ... */ c->inactive = 0; /* If a client is connected, tell it we just joined and give it what they missed */ if (p->client_status == IRC_CLIENT_ACTIVE) { net_send(p->client_sock, "%s\r\n", msg.orig); if (p->conn_class->chan_log_enabled) irclog_autorecall(p, msg.params[0]); } } else if (c && c->unjoined) { /* Ah, rejoined a channel we left */ c->unjoined = 0; squelch = 0; } else if (!c) { struct strlist *s; /* Orginary join */ ircnet_addchannel(p, msg.params[0]); /* Ask for the channel modes */ s = (struct strlist *)malloc(sizeof(struct strlist)); s->str = x_strdup(msg.params[0]); s->next = p->squelch_modes; p->squelch_modes = s; ircserver_send_command(p, "MODE", ":%s", msg.params[0]); squelch = 0; } else { /* Bizarre, joined a channel we thought we were already on */ squelch = 0; } if ((p->client_status != IRC_CLIENT_ACTIVE) && (p->conn_class->detach_message)) { int slashme; char *msg; msg = p->conn_class->detach_message; if ((strlen(msg) >= 5) && !strncasecmp(msg, "/me ", 4)) { /* Starts with /me */ slashme = 1; msg += 4; } else { slashme = 0; } if (slashme) { ircserver_send_command(p, "PRIVMSG", "%s :\001ACTION %s\001", c->name, msg); } else { ircserver_send_command(p, "PRIVMSG", "%s :%s", c->name, msg); } } if (p->conn_class->log_events & IRC_LOG_JOIN) irclog_notice(p, msg.params[0], p->servername, "You joined the channel"); } } else { if (msg.numparams >= 1) { if (p->conn_class->log_events & IRC_LOG_JOIN) irclog_notice(p, msg.params[0], p->servername, "%s joined the channel", msg.src.fullname); } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "PART")) { if (_ircserver_forclient(p, &msg)) { /* Server telling us we left a channel */ if (msg.numparams >= 1) { struct ircchannel *c; if (p->conn_class->log_events & IRC_LOG_PART) irclog_notice(p, msg.params[0], p->servername, "You left the channel"); c = ircnet_fetchchannel(p, msg.params[0]); /* Ignore server PARTs for unjoined channels */ if (c && !c->unjoined) ircnet_delchannel(p, msg.params[0]); squelch = 0; } } else { if (msg.numparams >= 1) { if (p->conn_class->log_events & IRC_LOG_PART) irclog_notice(p, msg.params[0], p->servername, "%s left the channel", msg.src.fullname); } squelch = 0; } } else if (!irc_strcasecmp(msg.cmd, "KICK")) { if (msg.numparams >= 2) { if (!irc_strcasecmp(p->nickname, msg.params[1])) { /* We got kicked off a channel */ if (p->conn_class->log_events & IRC_LOG_KICK) { if (msg.numparams >= 3) { irclog_notice(p, msg.params[0], p->servername, "Kicked off by %s: %s", msg.src.fullname, msg.params[2]); } else { irclog_notice(p, msg.params[0], p->servername, "Kicked off by %s", msg.src.fullname); } } /* No client connected? Lets rejoin it for it */ if (p->client_status != IRC_CLIENT_ACTIVE) { struct ircchannel *chan; chan = ircnet_fetchchannel(p, msg.params[0]); if (chan) { chan->inactive = 1; ircnet_rejoin(p, chan->name); } } else { /* Let it handle it */ ircnet_delchannel(p, msg.params[0]); } squelch = 0; } else { squelch = 0; if (p->conn_class->log_events & IRC_LOG_KICK) { if (msg.numparams >= 3) { irclog_notice(p, msg.params[0], p->servername, "%s kicked off by %s: %s", msg.params[1], msg.src.fullname, msg.params[2]); } else { irclog_notice(p, msg.params[0], p->servername, "%s kicked off by %s", msg.params[1], msg.src.fullname); } } } } } else if (!irc_strcasecmp(msg.cmd, "QUIT")) { /* Somebody left IRC */ if (p->conn_class->log_events & IRC_LOG_QUIT) { if (msg.numparams >= 1) { irclog_notice(p, p->nickname, p->servername, "%s quit from IRC: %s", msg.src.fullname, msg.params[0]); } else { irclog_notice(p, p->nickname, p->servername, "%s quit from IRC", msg.src.fullname); } } squelch = 0; } else if (!irc_strcasecmp(msg.cmd, "ERROR")) { /* Errors are important enough to always forward to the client */ important = 1; squelch = 0; } else if (!irc_strcasecmp(msg.cmd, "PRIVMSG")) { /* All PRIVMSGs go to the client unless we fiddle */ squelch = 0; if (msg.numparams >= 2) { struct strlist *list, *s; char *str; ircprot_stripctcp(msg.params[1], &str, &list); /* Privmsgs get logged */ if (str && strlen(str)) { if (p->conn_class->log_events & IRC_LOG_TEXT) irclog_msg(p, msg.params[0], msg.src.orig, "%s", str); } free(str); /* Handle CTCP */ str = x_strdup(msg.params[1]); s = list; while (s) { struct ctcpmessage cmsg; struct strlist *n; char *unquoted; int r; n = s->next; r = ircprot_parsectcp(s->str, &cmsg); unquoted = s->str; free(s); s = n; if (r == -1) { free(unquoted); continue; } if (!strcmp(cmsg.cmd, "ACTION")) { if (p->conn_class->log_events & IRC_LOG_ACTION) irclog_ctcp(p, (msg.params != NULL ) ? msg.params[0]: "none", msg.src.orig, "%s", cmsg.orig); } else if (!strcmp(cmsg.cmd, "DCC") && p->conn_class->dcc_proxy_incoming) { struct sockaddr_in vis_addr; int len; /* We need our local address to do anything DCC related */ len = sizeof(struct sockaddr_in); if ((p->client_status == IRC_CLIENT_ACTIVE) && getsockname(p->client_sock, (struct sockaddr *)&vis_addr, &len)) { syscall_fail("getsockname", "", 0); } else if ((cmsg.numparams >= 4) && (!irc_strcasecmp(cmsg.params[0], "CHAT") || !irc_strcasecmp(cmsg.params[0], "SEND"))) { char *tmp, *ptr, *dccmsg, *rejmsg; struct in_addr l_addr, r_addr; int l_port, r_port, t_port; char *capfile = 0; char *rest = 0; int type = 0; /* Find out what type of DCC request this is */ if (!irc_strcasecmp(cmsg.params[0], "CHAT")) { /* Can only proxy chats if we have a client */ if (p->client_status == IRC_CLIENT_ACTIVE) type = DCC_CHAT; } else if (!irc_strcasecmp(cmsg.params[0], "SEND")) { /* Check if we're capturing it, instead of proxying */ if (p->conn_class->dcc_capture_directory && ((p->client_status != IRC_CLIENT_ACTIVE) || p->conn_class->dcc_capture_always)) { char *file; /* Filename is after / or \ characters, this fixes any security issues we might have with it */ debug("Filename given '%s'", cmsg.params[1]); file = strrchr(cmsg.params[1], '/'); if (file) { char *ptr; file++; ptr = strrchr(file, '\\'); if (ptr) file = ptr + 1; } else { file = strrchr(cmsg.params[1], '\\'); if (file) { file++; } else { file = cmsg.params[1]; } } debug("Filtered to '%s'", file); /* Assuming we got a filename ... */ if (file && strlen(file)) { type = DCC_SEND_CAPTURE; if (p->conn_class->dcc_capture_withnick) { capfile = x_sprintf("%s/%s.%s", p->conn_class->dcc_capture_directory, msg.src.name, file); } else { capfile = x_sprintf("%s/%s", p->conn_class->dcc_capture_directory, file); } debug("Capture to '%s'", capfile); } } else if (p->client_status == IRC_CLIENT_ACTIVE) { /* Proxying - so client must be active. See whether to send it fast or normally */ if (p->conn_class->dcc_send_fast) { type = DCC_SEND_FAST; } else { type = DCC_SEND_SIMPLE; } } } /* Check whether there's a tunnel port */ t_port = 0; if (p->conn_class->dcc_tunnel_incoming) t_port = dns_portfromserv(p->conn_class->dcc_tunnel_incoming); /* Eww, host order, how the hell does this even work between machines of a different byte order? */ if (!t_port) { r_addr.s_addr = strtoul(cmsg.params[2], (char **)NULL, 10); r_port = atoi(cmsg.params[3]); } else { r_addr.s_addr = INADDR_LOOPBACK; r_port = ntohs(t_port); } l_addr.s_addr = ntohl(vis_addr.sin_addr.s_addr); if (cmsg.numparams >= 5) rest = cmsg.paramstarts[4]; /* Strip out this CTCP from the message, replacing it in a moment with dccmsg */ tmp = x_sprintf("\001%s\001", unquoted); ptr = strstr(str, tmp); dccmsg = 0; /* Save this in case we need it later */ rejmsg = x_sprintf(":%s NOTICE %s :\001DCC REJECT %s %s " "(%s: unable to proxy)\001", p->nickname, msg.src.name, cmsg.params[0], cmsg.params[1], PACKAGE); /* Set up a dcc proxy, note: type is 0 if there isn't a client active and we're not capturing it. This will send a reject back which is exactly what we want to do. */ if (ptr && type && !dccnet_new(type, p->conn_class->dcc_proxy_timeout, p->conn_class->dcc_proxy_ports, p->conn_class->dcc_proxy_ports_sz, &l_port, r_addr, r_port, capfile, p->conn_class->dcc_capture_maxsize, DCCN_FUNCTION(_ircserver_send_dccreject), p, rejmsg)) { if (capfile) { dccmsg = x_strdup(""); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "Captured DCC %s from %s into %s", cmsg.params[0], msg.src.fullname, capfile); } else { dccmsg = x_sprintf("\001DCC %s %s %lu %u%s%s\001", cmsg.params[0], cmsg.params[1], l_addr.s_addr, l_port, (rest ? " " : ""), (rest ? rest : "")); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "DCC %s Request from %s", cmsg.params[0], msg.src.fullname); } } else if (ptr) { dccmsg = x_strdup(""); _ircserver_send_dccreject(p, rejmsg); } /* Don't need this anymore */ free(rejmsg); /* Cut out the old CTCP and replace with dccmsg */ if (ptr) { char *oldstr; *ptr = 0; ptr += strlen(tmp); oldstr = str; str = x_sprintf("%s%s%s", oldstr, dccmsg, ptr); free(oldstr); free(dccmsg); } free(tmp); if (capfile) free(capfile); } else { /* Unknown DCC */ debug("Unknown or Unimplemented DCC request - %s", cmsg.params[0]); } } else if (!strcmp(cmsg.cmd, "PING") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { if (cmsg.numparams >= 1) { ircserver_send_command(p, "NOTICE", "%s :\001PING %s\001", msg.src.name, cmsg.paramstarts[0]); } else { ircserver_send_command(p, "NOTICE", "%s :\001PING\001", msg.src.name); } if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "ECHO") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { if (cmsg.numparams >= 1) ircserver_send_command(p, "NOTICE", "%s :\001ECHO %s\001", msg.src.name, cmsg.paramstarts[0]); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "TIME") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { char tbuf[40]; time_t now; time(&now); strftime(tbuf, sizeof(tbuf), CTCP_TIMEDATE_FORMAT, localtime(&now)); ircserver_send_command(p, "NOTICE", "%s :\001TIME %s\001", msg.src.name, tbuf); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "CLIENTINFO") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { ircserver_send_command(p, "NOTICE", "%s :\001CLIENTINFO %s\001", msg.src.name, "ACTION DCC VERSION CLIENTINFO USERINFO " "FINGER PING TIME ECHO"); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "VERSION") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { ircserver_send_command(p, "NOTICE", "%s :\001VERSION %s %s - %s\001", msg.src.name, PACKAGE, VERSION, "http://dircproxy.sourceforge.net/"); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "USERINFO") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { ircserver_send_command(p, "NOTICE", "%s :\001USERINFO %s -- %s\001", msg.src.name, PACKAGE, "Saving the world from " "mutant carrots since 1899!"); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else if (!strcmp(cmsg.cmd, "FINGER") && p->conn_class->ctcp_replies && (p->client_status != IRC_CLIENT_ACTIVE)) { ircserver_send_command(p, "NOTICE", "%s :\001FINGER %s %s\001", msg.src.name, PACKAGE, "proxying for unconnected client"); if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } else { /* Unknown CTCP */ if (p->conn_class->log_events & IRC_LOG_CTCP) irclog_notice(p, msg.params[0], p->servername, "CTCP %s from %s", cmsg.cmd, msg.src.fullname); } ircprot_freectcp(&cmsg); free(unquoted); } /* Send str */ if (strlen(str) && (p->client_status == IRC_CLIENT_ACTIVE)) net_send(p->client_sock, ":%s PRIVMSG %s :%s\r\n", msg.src.orig, msg.params[0], str); squelch = 1; free(str); } } else if (!irc_strcasecmp(msg.cmd, "NOTICE")) { if (msg.numparams >= 1) { struct strlist *list; char *str; ircprot_stripctcp(msg.params[1], &str, &list); if (str && strlen(str)) { if (p->conn_class->log_events & IRC_LOG_TEXT) irclog_notice(p, msg.params[0], msg.src.orig, "%s", str); } free(str); if (list) { struct strlist *s; s = list; while (s) { struct ctcpmessage cmsg; struct strlist *n; int r; n = s->next; r = ircprot_parsectcp(s->str, &cmsg); free(s->str); free(s); s = n; if (r == -1) continue; if (p->conn_class->log_events & IRC_LOG_CTCP) { if (cmsg.numparams >= 1) { irclog_notice(p, msg.params[0], p->servername, "CTCP %s reply from %s: %s", cmsg.cmd, msg.src.fullname, cmsg.paramstarts[0]); } else { irclog_notice(p, msg.params[0], p->servername, "CTCP %s reply from %s", cmsg.cmd, msg.src.fullname); } } ircprot_freectcp(&cmsg); } } } /* All NOTICEs go to the client */ squelch = 0; } else { squelch = 0; } if (!squelch && ((p->client_status == IRC_CLIENT_ACTIVE) || (important && (p->client_status & IRC_CLIENT_CONNECTED)))) { net_send(p->client_sock, "%s\r\n", msg.orig); } ircprot_freemsg(&msg); return 0; } /* Close the server socket itself */ int ircserver_close_sock(struct ircproxy *p) { net_close(&(p->server_sock)); p->server_status &= ~(IRC_SERVER_CREATED | IRC_SERVER_CONNECTED | IRC_SERVER_INTRODUCED | IRC_SERVER_GOTWELCOME); /* Make sure these don't get triggered */ timer_del((void *)p, "server_ping"); timer_del((void *)p, "server_stoned"); timer_del((void *)p, "server_antiidle"); timer_del((void *)p, "server_recon"); return 0; } /* Close the connection to the server */ static int _ircserver_close(struct ircproxy *p) { ircserver_close_sock(p); if (IS_CLIENT_READY(p)) { ircclient_send_notice(p, "Lost connection to server"); _ircserver_lost(p); } if (p->conn_class->log_events & IRC_LOG_SERVER) irclog_notice(p, p->nickname, PACKAGE, "Lost connection to server: %s", p->servername); timer_new((void *)p, "server_recon", p->conn_class->server_retry, TIMER_FUNCTION(_ircserver_reconnect), (void *)0); return 0; } /* Lost the connection to the server while client is active, so send it PARTs so it doesn't get confused by later JOINs */ static int _ircserver_lost(struct ircproxy *p) { struct ircchannel *c; if (IS_CLIENT_READY(p)) { c = p->channels; while (c) { ircclient_send_selfcmd(p, "PART", ":%s", c->name); c = c->next; } } return 0; } /* Drop the connection to the server and reconnect */ int ircserver_connectagain(struct ircproxy *p) { if (IS_SERVER_READY(p)) { if (IS_CLIENT_READY(p)) { ircclient_send_notice(p, "Dropped connnection to server"); if (p->conn_class->log_events & IRC_LOG_SERVER) irclog_notice(p, p->nickname, PACKAGE, "Dropped connection to server: %s", p->servername); _ircserver_lost(p); } ircserver_send_command(p, "QUIT", ":Reconnecting to server - %s %s", PACKAGE, VERSION); } if (p->server_status & IRC_SERVER_CREATED) ircserver_close_sock(p); /* Reset seen so that we start with initattempts again */ p->server_status &= ~(IRC_SERVER_SEEN); p->server_attempts = 0; debug("Connecting again"); ircserver_connect(p); return 0; } /* hook for timer code to ping server */ static void _ircserver_ping(struct ircproxy *p, void *data) { /* Server might not be ready yet 8*/ if (IS_SERVER_READY(p)) { debug("Pinging the server"); net_sendurgent(p->server_sock, "PING :%s\r\n", p->servername); debug("=> 'PING :%s'", p->servername); } timer_new((void *)p, "server_ping", (int)(p->conn_class->server_pingtimeout / 2), TIMER_FUNCTION(_ircserver_ping), (void *)0); } /* hook for timer code to close a stoned server */ static void _ircserver_stoned(struct ircproxy *p, void *data) { /* Server is, like, stoned. Yeah man! */ if (IS_SERVER_READY(p)) { debug("Server is stoned, reconnecting"); ircserver_send_command(p, "QUIT", ":Getting off stoned server - %s %s", PACKAGE, VERSION); _ircserver_close(p); } } /* hook for timer code to send empty privmsg to prevent idling */ static void _ircserver_antiidle(struct ircproxy *p, void *data) { if (IS_SERVER_READY(p)) { debug("Sending anti-idle"); p->squelch_411 = 1; ircserver_send_command(p, "PRIVMSG", ""); } timer_new((void *)p, "server_antiidle", p->conn_class->idle_maxtime, TIMER_FUNCTION(_ircserver_antiidle), (void *)0); } /* Reset idle timer */ void ircserver_resetidle(struct ircproxy *p) { timer_del((void *)p, "server_antiidle"); timer_new((void *)p, "server_antiidle", p->conn_class->idle_maxtime, TIMER_FUNCTION(_ircserver_antiidle), (void *)0); } /* Check if a message is bound for us, and if so check our username and hostname vars are the same */ static int _ircserver_forclient(struct ircproxy *p, struct ircmessage *msg) { if (!(msg->src.type & IRC_USER)) return 0; if (irc_strcasecmp(p->nickname, msg->src.name)) return 0; if (msg->src.username) { free(p->username); p->username = x_strdup(msg->src.username); } if (msg->src.hostname) { free(p->hostname); p->hostname = x_strdup(msg->src.hostname); } return 1; } /* send a command to the server with no prefix */ int ircserver_send_command(struct ircproxy *p, const char *command, const char *format, ...) { va_list ap; char *msg; int ret; va_start(ap, format); msg = x_vsprintf(format, ap); va_end(ap); ret = net_send(p->server_sock, "%s %s\r\n", command, msg); debug("-> '%s %s'", command, msg); free(msg); return ret; } /* Send a DCC reject message */ static int _ircserver_send_dccreject(struct ircproxy *p, const char *msg) { int ret = 1; if (p && p->conn_class && p->conn_class->dcc_proxy_sendreject && (p->server_status == IRC_SERVER_ACTIVE)) { ret = net_send(p->server_sock, "%s\r\n", msg); debug("-> '%s'", msg); } return ret; }