/* listen.c : sample plugin for irssi Copyright (C) 1999 Timo Sirainen 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 */ #include "module.h" #include "signals.h" #include "net-sendbuffer.h" #include "servers-redirect.h" #include "levels.h" #include "settings.h" #include "irc.h" #include "irc-channels.h" #include "fe-common/core/printtext.h" GSList *proxy_listens; GSList *proxy_clients; static GString *next_line; static void remove_client(CLIENT_REC *rec) { g_return_if_fail(rec != NULL); proxy_clients = g_slist_remove(proxy_clients, rec); g_free(rec->proxy_address); net_disconnect(rec->handle); g_source_remove(rec->tag); line_split_free(rec->buffer); g_free_not_null(rec->nick); g_free(rec); } static void proxy_redirect_event(CLIENT_REC *client, const char *args, int last, ...) { va_list vargs; GString *str; char *event; int argpos, group; g_return_if_fail(client != NULL); va_start(vargs, last); str = g_string_new(NULL); group = 0; while ((event = va_arg(vargs, char *)) != NULL) { argpos = va_arg(vargs, int); g_string_sprintf(str, "proxy %p", client->handle); group = server_redirect_single_event(SERVER(client->server), args, last > 0, group, event, str->str, argpos); last--; } g_string_free(str, TRUE); va_end(vargs); } static void grab_who(CLIENT_REC *client, const char *channel) { char *chlist, *chanevent; char **list, **tmp; /* /WHO a,b,c may respond with either one "a,b,c End of WHO" message or three different "a End of WHO", "b End of WHO", .. messages */ chlist = g_strdup(channel); list = g_strsplit(channel, ",", -1); for (tmp = list; *tmp != NULL; tmp++) { if (strcmp(*tmp, "0") == 0) { /* /who 0 displays everyone */ **tmp = '*'; } chanevent = g_strdup_printf("%s %s", chlist, *tmp); proxy_redirect_event(client, chanevent, 2, "event 401", 1, "event 315", 1, "event 352", -1, NULL); g_free(chanevent); } g_strfreev(list); g_free(chlist); } static void handle_client_connect_cmd(CLIENT_REC *client, const char *cmd, const char *args) { const char *password; password = settings_get_str("irssiproxy_password"); if (password != NULL && strcmp(cmd, "PASS") == 0) { if (strcmp(password, args) == 0) client->pass_sent = TRUE; else { /* wrong password! */ remove_client(client); } } else if (strcmp(cmd, "NICK") == 0) { g_free_not_null(client->nick); client->nick = g_strdup(args); } else if (strcmp(cmd, "USER") == 0) { if (client->nick == NULL || (*password != '\0' && !client->pass_sent)) { /* stupid client didn't send us NICK/PASS, kill it */ remove_client(client); } else { client->connected = TRUE; plugin_proxy_dump_data(client); } } } static void handle_client_cmd(CLIENT_REC *client, char *cmd, char *args) { GIOChannel *server_handle; if (!client->connected) { handle_client_connect_cmd(client, cmd, args); return; } if (strcmp(cmd, "QUIT") == 0) { remove_client(client); return; } if (strcmp(cmd, "PING") == 0) { char *server = strchr(args, ':'); if (server == NULL || strcmp(server+1, client->proxy_address) == 0) { if (server != NULL) { if (server[-1] == ' ') server--; *server = '\0'; } if (*args == '\0') args = client->nick; proxy_outdata(client, ":%s PONG proxy :%s\n", client->proxy_address, args); return; } } if (strcmp(cmd, "NOTICE") == 0) { char *str = g_strdup_printf("%s :\001IRSSILAG ", client->nick); if (strncmp(args, str, strlen(str)) == 0) proxy_outserver(client, "NOTICE %s", args); g_free(str); } if (client->server == NULL || !client->server->connected) { proxy_outdata(client, ":%s NOTICE %s :Not connected to server", client->proxy_address, client->nick); return; } server_handle = net_sendbuffer_handle(client->server->handle); net_transmit(server_handle, cmd, strlen(cmd)); net_transmit(server_handle, " ", 1); net_transmit(server_handle, args, strlen(args)); net_transmit(server_handle, "\n", 1); if (strcmp(cmd, "WHO") == 0) grab_who(client, args); else if (strcmp(cmd, "WHOIS") == 0) { char *p; /* convert dots to spaces */ for (p = args; *p != '\0'; p++) if (*p == ',') *p = ' '; proxy_redirect_event(client, args, 2, "event 318", -1, "event 402", -1, "event 401", 1, "event 311", 1, "event 301", 1, "event 312", 1, "event 313", 1, "event 317", 1, "event 319", 1, NULL); } else if (strcmp(cmd, "ISON") == 0) proxy_redirect_event(client, NULL, 1, "event 303", -1, NULL); else if (strcmp(cmd, "USERHOST") == 0) proxy_redirect_event(client, args, 1, "event 302", -1, "event 401", 1, NULL); else if (strcmp(cmd, "MODE") == 0) { /* convert dots to spaces */ char *slist, *str, mode, *p; int argc; p = strchr(args, ' '); if (p != NULL) *p++ = '\0'; mode = p == NULL ? '\0' : *p; slist = g_strdup(args); argc = 1; for (p = slist; *p != '\0'; p++) { if (*p == ',') { *p = ' '; argc++; } } /* get channel mode / bans / exception / invite list */ str = g_strdup_printf("%s %s", args, slist); switch (mode) { case '\0': while (argc-- > 0) proxy_redirect_event(client, str, 3, "event 403", 1, "event 443", 1, "event 324", 1, NULL); break; case 'b': while (argc-- > 0) proxy_redirect_event(client, str, 2, "event 403", 1, "event 368", 1, "event 367", 1, NULL); break; case 'e': while (argc-- > 0) proxy_redirect_event(client, str, 4, "event 403", 1, "event 482", 1, "event 472", -1, "event 349", 1, "event 348", 1, NULL); break; case 'I': while (argc-- > 0) proxy_redirect_event(client, str, 4, "event 403", 1, "event 482", 1, "event 472", -1, "event 347", 1, "event 346", 1, NULL); break; } g_free(str); g_free(slist); } else if (strcmp(cmd, "PRIVMSG") == 0) { /* send the message to other clients as well */ char *params, *target, *msg; params = event_get_params(args, 2 | PARAM_FLAG_GETREST, &target, &msg); proxy_outserver_all_except(client, "PRIVMSG %s", args); signal_emit("message public", 5, client->server, msg, client->nick, client->proxy_address, target); g_free(params); } } static void sig_listen_client(CLIENT_REC *client) { char tmpbuf[1024], *str, *cmd, *args; int ret, recvlen; g_return_if_fail(client != NULL); while (g_slist_find(proxy_clients, client) != NULL) { recvlen = net_receive(client->handle, tmpbuf, sizeof(tmpbuf)); ret = line_split(tmpbuf, recvlen, &str, &client->buffer); if (ret == -1) { /* connection lost */ remove_client(client); break; } if (ret == 0) break; cmd = g_strdup(str); args = strchr(cmd, ' '); if (args != NULL) *args++ = '\0'; else args = ""; if (*args == ':') args++; g_strup(cmd); handle_client_cmd(client, cmd, args); g_free(cmd); } } static void sig_listen(LISTEN_REC *listen) { CLIENT_REC *rec; IPADDR ip; GIOChannel *handle; char host[MAX_IP_LEN]; int port; g_return_if_fail(listen != NULL); /* accept connection */ handle = net_accept(listen->handle, &ip, &port); if (handle == NULL) return; net_ip2host(&ip, host); rec = g_new0(CLIENT_REC, 1); rec->listen = listen; rec->handle = handle; rec->proxy_address = g_strdup(listen->ircnet); rec->server = IRC_SERVER(server_find_chatnet(listen->ircnet)); rec->tag = g_input_add(handle, G_INPUT_READ, (GInputFunction) sig_listen_client, rec); proxy_clients = g_slist_append(proxy_clients, rec); printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE, "Proxy: Client connected from %s", host); } static void sig_incoming(IRC_SERVER_REC *server, const char *line) { g_return_if_fail(line != NULL); /* send server event to all clients */ g_string_sprintf(next_line, "%s\n", line); } static void sig_server_event(IRC_SERVER_REC *server, const char *line, const char *nick, const char *address) { GSList *list; char *event, *args; g_return_if_fail(line != NULL); if (!IS_IRC_SERVER(server)) return; /* get command.. */ event = g_strconcat("event ", line, NULL); args = strchr(event+6, ' '); if (args != NULL) *args++ = '\0'; else args = ""; while (*args == ' ') args++; list = server_redirect_getqueue(SERVER(server), event, args); if (list != NULL) { /* we want to send this to one client (or proxy itself) only */ REDIRECT_REC *rec; void *handle; rec = list->data; if (g_strncasecmp(rec->name, "proxy ", 6) != 0) { /* proxy only */ g_free(event); return; } if (sscanf(rec->name+6, "%p", &handle) == 1) { /* send it to specific client only */ server_redirect_remove_next(SERVER(server), event, list); net_transmit(handle, next_line->str, next_line->len); g_free(event); signal_stop(); return; } } if (g_strcasecmp(event, "event ping") == 0 || (g_strcasecmp(event, "event privmsg") == 0 && strstr(args, " :\001") != NULL && strstr(args, " :\001ACTION") == NULL) || (g_strcasecmp(event, "event notice") == 0 && strstr(args, " :\001IRSSILAG") != NULL)) { /* We want to answer ourself to PINGs and CTCPs, also don't let clients see replies to IRSSILAG requests */ g_free(event); return; } /* send the data to clients.. */ proxy_outdata_all(server, "%s", next_line->str); g_free(event); } static void event_connected(IRC_SERVER_REC *server) { GSList *tmp; if (!IS_IRC_SERVER(server) || server->connrec->chatnet == NULL) return; for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { CLIENT_REC *rec = tmp->data; if (rec->connected && rec->server == NULL && g_strcasecmp(server->connrec->chatnet, rec->listen->ircnet) == 0) { proxy_outdata(rec, ":%s NOTICE %s :Connected to server", rec->proxy_address, rec->nick); rec->server = server; } } } static void proxy_server_disconnected(CLIENT_REC *client, IRC_SERVER_REC *server) { GSList *tmp; proxy_outdata(client, ":%s NOTICE %s :Connection lost to server %s\n", client->proxy_address, client->nick, server->connrec->address); for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { IRC_CHANNEL_REC *rec = tmp->data; proxy_outserver(client, "PART %s :Connection lost to server", rec->name); } } static void sig_server_disconnected(IRC_SERVER_REC *server) { GSList *tmp; if (!IS_IRC_SERVER(server)) return; for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { CLIENT_REC *rec = tmp->data; if (rec->connected && rec->server == server) { proxy_server_disconnected(rec, server); rec->server = NULL; } } } static void event_nick(IRC_SERVER_REC *server, const char *data, const char *orignick) { GSList *tmp; if (g_strcasecmp(orignick, server->nick) != 0) return; if (*data == ':') data++; for (tmp = proxy_clients; tmp != NULL; tmp = tmp->next) { CLIENT_REC *rec = tmp->data; if (rec->connected && rec->server == server) { g_free(rec->nick); rec->nick = g_strdup(data); } } } static LISTEN_REC *find_listen(const char *ircnet, int port) { GSList *tmp; for (tmp = proxy_listens; tmp != NULL; tmp = tmp->next) { LISTEN_REC *rec = tmp->data; if (rec->port == port && g_strcasecmp(rec->ircnet, ircnet) == 0) return rec; } return NULL; } static void add_listen(const char *ircnet, int port) { LISTEN_REC *rec; if (port <= 0 || *ircnet == '\0') return; rec = g_new0(LISTEN_REC, 1); rec->ircnet = g_strdup(ircnet); rec->port = port; /* start listening */ rec->handle = net_listen(NULL, &rec->port); if (rec->handle == NULL) { printtext(NULL, NULL, MSGLEVEL_CLIENTERROR, "Proxy: Listen in port %d failed: %s", rec->port, g_strerror(errno)); return; } rec->tag = g_input_add(rec->handle, G_INPUT_READ, (GInputFunction) sig_listen, rec); proxy_listens = g_slist_append(proxy_listens, rec); } static void remove_listen(LISTEN_REC *rec) { proxy_listens = g_slist_remove(proxy_listens, rec); net_disconnect(rec->handle); g_source_remove(rec->tag); g_free(rec->ircnet); g_free(rec); } static void read_settings(void) { LISTEN_REC *rec; GSList *remove_listens; char **ports, **tmp, *ircnet, *port; int portnum; remove_listens = g_slist_copy(proxy_listens); ports = g_strsplit(settings_get_str("irssiproxy_ports"), " ", -1); for (tmp = ports; *tmp != NULL; tmp++) { ircnet = *tmp; port = strchr(ircnet, '='); if (port == NULL) continue; *port++ = '\0'; portnum = atoi(port); if (portnum <= 0) continue; rec = find_listen(ircnet, portnum); if (rec == NULL) add_listen(ircnet, portnum); else remove_listens = g_slist_remove(remove_listens, rec); } g_strfreev(ports); while (remove_listens != NULL) { remove_listen(remove_listens->data); g_slist_remove(remove_listens, remove_listens->data); } } void plugin_proxy_listen_init(void) { next_line = g_string_new(NULL); proxy_clients = NULL; proxy_listens = NULL; read_settings(); signal_add("server incoming", (SIGNAL_FUNC) sig_incoming); signal_add("server event", (SIGNAL_FUNC) sig_server_event); signal_add("event connected", (SIGNAL_FUNC) event_connected); signal_add("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); signal_add("event nick", (SIGNAL_FUNC) event_nick); signal_add("setup changed", (SIGNAL_FUNC) read_settings); } void plugin_proxy_listen_deinit(void) { while (proxy_clients != NULL) remove_client(proxy_clients->data); while (proxy_listens != NULL) remove_listen(proxy_listens->data); g_string_free(next_line, TRUE); signal_remove("server incoming", (SIGNAL_FUNC) sig_incoming); signal_remove("server event", (SIGNAL_FUNC) sig_server_event); signal_remove("event connected", (SIGNAL_FUNC) event_connected); signal_remove("server disconnected", (SIGNAL_FUNC) sig_server_disconnected); signal_remove("event nick", (SIGNAL_FUNC) event_nick); signal_remove("setup changed", (SIGNAL_FUNC) read_settings); }