/* dircproxy * Copyright (C) 2002 Scott James Remnant . * All Rights Reserved. * * irc_net.c * - Socket to listen for new connections on * - The list of connection classes * - The list of currently active proxies * - Miscellaneous IRC functions * -- * @(#) $Id: irc_net.c,v 1.45 2001/12/21 20:15:55 keybuk 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 "net.h" #include "dns.h" #include "timers.h" #include "sprintf.h" #include "irc_log.h" #include "irc_string.h" #include "irc_client.h" #include "irc_server.h" #include "irc_net.h" /* forward declarations */ static int _ircnet_listen(struct sockaddr_in *); static struct ircproxy *_ircnet_newircproxy(void); static int _ircnet_client_connected(struct ircproxy *); static void _ircnet_acceptclient(void *, int); static void _ircnet_freeproxy(struct ircproxy *); static void _ircnet_rejoin(struct ircproxy *, void *); /* list of connection classes */ struct ircconnclass *connclasses = 0; /* whether we are a dedicated proxy or not */ static int dedicated_proxy = 0; /* list of currently proxied connections */ static struct ircproxy *proxies = 0; /* socket we are listening for new client connections on */ static int listen_sock = -1; /* Create a socket to listen on. 0 = ok, other = error */ int ircnet_listen(const char *port) { struct sockaddr_in local_addr; local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = INADDR_ANY; local_addr.sin_port = dns_portfromserv(port); if (!local_addr.sin_port) return -1; return _ircnet_listen(&local_addr); } /* Does the actual work of creating a listen socket. 0 = ok, other = error */ int _ircnet_listen(struct sockaddr_in *local_addr) { int this_sock; this_sock = net_socket(); if (this_sock == -1) return -1; if (local_addr) { if (bind(this_sock, (struct sockaddr *)local_addr, sizeof(struct sockaddr_in))) { syscall_fail("bind", "listen", 0); net_close(&this_sock); return -1; } } if (listen(this_sock, SOMAXCONN)) { syscall_fail("listen", 0, 0); net_close(&this_sock); return -1; } if (listen_sock != -1) { debug("Closing existing listen socket %d", listen_sock); net_close(&listen_sock); } debug("Listening on socket %d", this_sock); listen_sock = this_sock; net_hook(listen_sock, SOCK_LISTENING, 0, ACTIVITY_FUNCTION(_ircnet_acceptclient), 0); return 0; } /* Creates a new ircproxy structure */ static struct ircproxy *_ircnet_newircproxy(void) { struct ircproxy *p; p = (struct ircproxy *)malloc(sizeof(struct ircproxy)); memset(p, 0, sizeof(struct ircproxy)); return p; } /* Finishes off the creation of an ircproxy structure */ static int _ircnet_client_connected(struct ircproxy *p) { p->next = proxies; proxies = p; return ircclient_connected(p); } /* Creates a client hooked onto a socket */ int ircnet_hooksocket(int sock) { struct ircproxy *p; int len; p = _ircnet_newircproxy(); p->client_sock = sock; len = sizeof(struct sockaddr_in); if (getpeername(p->client_sock, (struct sockaddr *)&(p->client_addr), &len)) { syscall_fail("getpeername", "", 0); free(p); return -1; } p->die_on_close = 1; net_create(&(p->client_sock)); if (p->client_sock != -1) { return _ircnet_client_connected(p); } else { free(p); return -1; } } /* Accept a client. */ static void _ircnet_acceptclient(void *data, int sock) { struct ircproxy *p; int len; p = _ircnet_newircproxy(); len = sizeof(struct sockaddr_in); p->client_sock = accept(sock, (struct sockaddr *)&(p->client_addr), &len); if (p->client_sock == -1) { syscall_fail("accept", 0, 0); free(p); return; } net_create(&(p->client_sock)); if (p->client_sock != -1) { _ircnet_client_connected(p); } else { free(p); } return; } /* Fetch a proxy for a connection class if one exists */ struct ircproxy *ircnet_fetchclass(struct ircconnclass *class) { struct ircproxy *p; p = proxies; while (p) { if (!p->dead && (p->conn_class == class)) return p; p = p->next; } return 0; } /* Fetch a channel from a proxy */ struct ircchannel *ircnet_fetchchannel(struct ircproxy *p, const char *name) { struct ircchannel *c; c = p->channels; while (c) { if (!irc_strcasecmp(c->name, name)) return c; c = c->next; } return 0; } /* Add a channel to a proxy */ int ircnet_addchannel(struct ircproxy *p, const char *name) { struct ircchannel *c; if (ircnet_fetchchannel(p, name)) return 0; c = (struct ircchannel *)malloc(sizeof(struct ircchannel)); memset(c, 0, sizeof(struct ircchannel)); c->name = x_strdup(name); debug("Joined channel '%s'", c->name); if (p->channels) { struct ircchannel *cc; cc = p->channels; while (cc->next) cc = cc->next; cc->next = c; } else { p->channels = c; } /* Intialise the channel log */ irclog_init(p, c->name); /* Open the channel log */ if (p->conn_class->chan_log_enabled && p->conn_class->chan_log_always) { if (irclog_open(p, c->name)) ircclient_send_channotice(p, c->name, "(warning) Unable to log channel: %s", c->name); } return 0; } /* Remove a channel from a proxy */ int ircnet_delchannel(struct ircproxy *p, const char *name) { struct ircchannel *c, *l; l = 0; c = p->channels; debug("Parted channel '%s'", name); while (c) { if (!irc_strcasecmp(c->name, name)) { if (l) { l->next = c->next; } else { p->channels = c->next; } ircnet_freechannel(c); return 0; } else { l = c; c = c->next; } } debug(" (which didn't exist)"); return -1; } /* Got a channel mode change */ int ircnet_channel_mode(struct ircproxy *p, struct ircchannel *c, struct ircmessage *msg, int modes) { int add = 1; int param; char *ptr; if (msg->numparams < (modes + 1)) return -1; debug("Channel '%s' mode change '%s'", c->name, msg->paramstarts[modes]); ptr = msg->params[modes]; param = modes + 1; while (*ptr) { switch (*ptr) { case '+': add = 1; break; case '-': add = 0; break; /* RFC2812 modes that have a parameter */ case 'O': case 'o': case 'v': case 'b': case 'e': case 'I': case 'l': param++; break; /* Channel key */ case 'k': if (add) { if (msg->numparams >= (param + 1)) { debug("Set channel '%s' key '%s'", c->name, msg->params[param]); free(c->key); c->key = x_strdup(msg->params[param]); } else { debug("Bad mode from server, said +k without a key"); } } else if (c->key) { debug("Remove channel '%s' key"); free(c->key); c->key = 0; } param++; break; } ptr++; } return 0; } /* Free an ircchannel structure, returns the next */ struct ircchannel *ircnet_freechannel(struct ircchannel *chan) { struct ircchannel *ret; ret = chan->next; irclog_free(&(chan->log)); free(chan->name); free(chan->key); free(chan); return ret; } /* Free an ircproxy structure */ static void _ircnet_freeproxy(struct ircproxy *p) { debug("Freeing proxy"); if (p->server_status & IRC_SERVER_CONNECTED) { ircserver_send_command(p, "QUIT", ":Terminated with extreme prejudice - %s %s", PACKAGE, VERSION); ircserver_close_sock(p); } if (p->client_status & IRC_CLIENT_CONNECTED) { ircclient_send_error(p, "dircproxy going bye-bye"); ircclient_close(p); } dns_delall((void *)p); timer_delall((void *)p); free(p->client_host); free(p->nickname); free(p->setnickname); free(p->oldnickname); free(p->username); free(p->hostname); free(p->realname); free(p->servername); free(p->serverver); free(p->serverumodes); free(p->servercmodes); free(p->serverpassword); free(p->password); free(p->awaymessage); free(p->modes); if (p->channels) { struct ircchannel *c; c = p->channels; while (c) c = ircnet_freechannel(c); } if (p->squelch_modes) { struct strlist *s; s = p->squelch_modes; while (s) { struct strlist *n; n = s->next; free(s->str); free(s); s = n; } } irclog_free(&(p->other_log)); irclog_closetempdir(p); free(p); } /* Get rid of any dead proxies */ int ircnet_expunge_proxies(void) { struct ircproxy *p, *l; l = 0; p = proxies; while (p) { if (p->dead) { struct ircproxy *n; n = p->next; _ircnet_freeproxy(p); if (l) { p = l->next = n; } else { p = proxies = n; } } else { l = p; p = p->next; } } return 0; } /* Get rid of all the proxies and connection classes */ void ircnet_flush(void) { ircnet_flush_proxies(&proxies); ircnet_flush_connclasses(&connclasses); } /* Get rid of all the proxies */ void ircnet_flush_proxies(struct ircproxy **p) { while (*p) { struct ircproxy *t; t = *p; *p = (*p)->next; _ircnet_freeproxy(t); } *p = 0; } /* Get rid of all the connection classes */ void ircnet_flush_connclasses(struct ircconnclass **c) { while (*c) { struct ircconnclass *t; t = *c; *c = (*c)->next; ircnet_freeconnclass(t); } *c = 0; } /* Free a connection class structure */ void ircnet_freeconnclass(struct ircconnclass *class) { struct strlist *s; free(class->server_port); free(class->server_throttle); free(class->initial_modes); free(class->drop_modes); free(class->refuse_modes); free(class->local_address); free(class->away_message); free(class->quit_message); free(class->attach_message); free(class->detach_message); free(class->detach_nickname); free(class->chan_log_copydir); free(class->chan_log_program); free(class->other_log_copydir); free(class->other_log_program); free(class->dcc_proxy_ports); free(class->dcc_capture_directory); free(class->dcc_tunnel_incoming); free(class->dcc_tunnel_outgoing); free(class->switch_user); free(class->motd_file); free(class->orig_local_address); free(class->password); s = class->servers; while (s) { struct strlist *t; t = s; s = s->next; free(t->str); free(t); } s = class->masklist; while (s) { struct strlist *t; t = s; s = s->next; free(t->str); free(t); } s = class->channels; while (s) { struct strlist *t; t = s; s = s->next; free(t->str); free(t); } free(class); } /* hook to rejoin a channel after a kick */ static void _ircnet_rejoin(struct ircproxy *p, void *data) { struct ircchannel *c; debug("Rejoining '%s'", (char *)data); c = ircnet_fetchchannel(p, (char *)data); if (c) { if (c->key) { ircserver_send_command(p, "JOIN", "%s :%s", c->name, c->key); } else { ircserver_send_command(p, "JOIN", ":%s", c->name); } } else { ircserver_send_command(p, "JOIN", ":%s", (char *)data); } free(data); } /* Set a timer to rejoin a channel */ int ircnet_rejoin(struct ircproxy *p, const char *name) { char *str; str = x_strdup(name); if (p->conn_class->channel_rejoin == 0) { _ircnet_rejoin(p, (void *)str); } else if (p->conn_class->channel_rejoin > 0) { debug("Will rejoin '%s' in %d seconds", str, p->conn_class->channel_rejoin); timer_new((void *)p, 0, p->conn_class->channel_rejoin, TIMER_FUNCTION(_ircnet_rejoin), (void *)str); } return 0; } /* Dedicate this proxy and create a listening socket */ int ircnet_dedicate(struct ircproxy *p) { struct ircconnclass *c; if (dedicated_proxy) return -1; /* Can't dedicate if there are multiple proxies */ if ((p != proxies) || p->next) { debug("Multiple active proxies, won't dedicate"); return -1; } debug("Dedicating proxy"); if (_ircnet_listen(0)) return -1; c = connclasses; while (c) { if (c == p->conn_class) { c = c->next; } else { struct ircconnclass *t; t = c; c = c->next; ircnet_freeconnclass(t); } } connclasses = p->conn_class; connclasses->next = 0; /* Okay we're dedicated */ dedicated_proxy = 1; ircnet_announce_dedicated(p); return 0; } /* send the dedicated listening port to the user */ int ircnet_announce_dedicated(struct ircproxy *p) { struct sockaddr_in listen_addr; unsigned int port; int len; if (!IS_CLIENT_READY(p)) return -1; len = sizeof(struct sockaddr_in); if (!getsockname(listen_sock, (struct sockaddr *)&listen_addr, &len)) { port = ntohs(listen_addr.sin_port); } else { syscall_fail("getsockname", "listen_sock", 0); return -1; } ircclient_send_notice(p, "Reconnect to this session at %s:%d", p->hostname, port); return 0; } /* tell the client they can't reconnect */ int ircnet_announce_nolisten(struct ircproxy *p) { if (!IS_CLIENT_READY(p)) return -1; ircclient_send_notice(p, "You cannot reconnect to this session"); return 0; } /* tell the client whether we're dedicated or not listening */ int ircnet_announce_status(struct ircproxy *p) { if (p->die_on_close) { return ircnet_announce_nolisten(p); } else if (dedicated_proxy) { return ircnet_announce_dedicated(p); } else { return 0; } }