/* irc.c : 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "module.h" #include #include #include #include #include #include #include #include char *current_server_event; static int signal_default_event; static int signal_server_event; static int signal_server_event_tags; static int signal_server_incoming; #ifdef BLOCKING_SOCKETS # define MAX_SOCKET_READS 1 #else # define MAX_SOCKET_READS 5 #endif static void strip_params_colon(char *const); /* The core of the irc_send_cmd* functions. If `raw' is TRUE, the `cmd' won't be checked at all if it's 512 bytes or not, or if it contains line feeds or not. Use with extreme caution! */ void irc_send_cmd_full(IRC_SERVER_REC *server, const char *cmd, int send_now, int immediate, int raw) { GString *str; int len; gboolean server_supports_tag; g_return_if_fail(server != NULL); g_return_if_fail(cmd != NULL); if (server->connection_lost) return; str = g_string_sized_new(MAX_IRC_USER_TAGS_LEN + 2 /* `@'+SPACE */ + server->max_message_len + 2 /* CR+LF */ + 1 /* `\0' */); if (server->cmdcount == 0) irc_servers_start_cmd_timeout(); server->cmdcount++; if (!raw) { const char *tmp = cmd; server_supports_tag = server->cap_supported != NULL && g_hash_table_lookup_extended(server->cap_supported, CAP_MESSAGE_TAGS, NULL, NULL); if (*cmd == '@' && server_supports_tag) { const char *end; while (*tmp != ' ' && *tmp != '\0') tmp++; end = tmp; if (tmp - cmd > MAX_IRC_USER_TAGS_LEN) { g_warning("irc_send_cmd_full(); tags too long(%ld)", tmp - cmd); while (tmp - cmd > MAX_IRC_USER_TAGS_LEN && cmd != tmp - 1) tmp--; while (*tmp != ',' && cmd != tmp - 1) tmp--; } if (cmd != tmp) g_string_append_len(str, cmd, tmp - cmd); tmp = end; while (*tmp == ' ') tmp++; if (*tmp != '\0' && str->len > 0) g_string_append_c(str, ' '); } len = strlen(tmp); /* check that we don't send any longer commands than 510 bytes (2 bytes for CR+LF) */ g_string_append_len(str, tmp, len > server->max_message_len ? server->max_message_len : len); } else { g_string_append(str, cmd); } if (send_now) { rawlog_output(server->rawlog, str->str); server_redirect_command(server, str->str, server->redirect_next); server->redirect_next = NULL; } if (!raw) { /* Add CR+LF to command */ g_string_append_c(str, 13); g_string_append_c(str, 10); } if (send_now) { irc_server_send_data(server, str->str, str->len); g_string_free(str, TRUE); } else { /* add to queue */ if (immediate) { server->cmdqueue = g_slist_prepend(server->cmdqueue, server->redirect_next); server->cmdqueue = g_slist_prepend(server->cmdqueue, g_string_free(str, FALSE)); } else { server->cmdqueue = g_slist_append(server->cmdqueue, g_string_free(str, FALSE)); server->cmdqueue = g_slist_append(server->cmdqueue, server->redirect_next); } } server->redirect_next = NULL; } /* Send command to IRC server */ void irc_send_cmd(IRC_SERVER_REC *server, const char *cmd) { gint64 now; int send_now; now = g_get_real_time(); send_now = now >= server->wait_cmd && (server->cmdcount < server->max_cmds_at_once || server->cmd_queue_speed <= 0); irc_send_cmd_full(server, cmd, send_now, FALSE, FALSE); } /* Send command to IRC server */ void irc_send_cmdv(IRC_SERVER_REC *server, const char *cmd, ...) { va_list args; char *str; va_start(args, cmd); str = g_strdup_vprintf(cmd, args); irc_send_cmd(server, str); g_free(str); va_end(args); } /* Send command to server immediately bypassing all flood protections and queues. */ void irc_send_cmd_now(IRC_SERVER_REC *server, const char *cmd) { g_return_if_fail(cmd != NULL); irc_send_cmd_full(server, cmd, TRUE, TRUE, FALSE); } /* Send command to server putting it at the beginning of the queue of commands to send -- it will go out as soon as possible in accordance to the flood protection settings. */ void irc_send_cmd_first(IRC_SERVER_REC *server, const char *cmd) { g_return_if_fail(cmd != NULL); irc_send_cmd_full(server, cmd, FALSE, TRUE, FALSE); } static char *split_nicks(const char *cmd, char **pre, char **nicks, char **post, int arg) { char *p; *pre = g_strdup(cmd); *post = *nicks = NULL; if (**pre == '@') { /* the message-tags "add" one space separated argument in front of the non message-tagged IRC commands. So the nicks are now off-set by one to the right. */ arg++; } for (p = *pre; *p != '\0'; p++) { if (*p != ' ') continue; if (arg == 1) { /* text after nicks */ *p++ = '\0'; while (*p == ' ') p++; *post = p; break; } /* find nicks */ while (p[1] == ' ') p++; if (--arg == 1) { *p = '\0'; *nicks = p+1; } } return *pre; } void irc_send_cmd_split(IRC_SERVER_REC *server, const char *cmd, int nickarg, int max_nicks) { char *str, *pre, *post, *nicks; char **nicklist, **tmp; GString *nickstr; int count; g_return_if_fail(server != NULL); g_return_if_fail(cmd != NULL); str = split_nicks(cmd, &pre, &nicks, &post, nickarg); if (nicks == NULL) { /* no nicks given? */ g_free(str); return; } /* split the nicks */ nickstr = g_string_new(NULL); nicklist = g_strsplit(nicks, ",", -1); count = 0; tmp = nicklist; for (;; tmp++) { if (*tmp != NULL) { g_string_append_printf(nickstr, "%s,", *tmp); if (++count < max_nicks) continue; } count = 0; if (nickstr->len > 0) g_string_truncate(nickstr, nickstr->len-1); if (post == NULL) irc_send_cmdv(server, "%s %s", pre, nickstr->str); else irc_send_cmdv(server, "%s %s %s", pre, nickstr->str, post); g_string_truncate(nickstr, 0); if (*tmp == NULL || tmp[1] == NULL) break; } g_strfreev(nicklist); g_string_free(nickstr, TRUE); g_free(str); } /* Get next parameter */ char *event_get_param(char **data) { char *pos; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(*data != NULL, NULL); if (**data == ':') { /* last parameter */ pos = *data; *data += strlen(*data); return pos+1; } pos = *data; while (**data != '\0' && **data != ' ') (*data)++; if (**data == ' ') *(*data)++ = '\0'; return pos; } /* Get count parameters from data */ char *event_get_params(const char *data, int count, ...) { char **str, *tmp, *duprec, *datad; gboolean rest; va_list args; g_return_val_if_fail(data != NULL, NULL); va_start(args, count); duprec = datad = g_strdup(data); rest = count & PARAM_FLAG_GETREST; count = PARAM_WITHOUT_FLAGS(count); while (count-- > 0) { str = (char **) va_arg(args, char **); if (count == 0 && rest) { /* Put the rest into the last parameter. */ strip_params_colon(datad); tmp = datad; } else { tmp = event_get_param(&datad); } if (str != NULL) *str = tmp; } va_end(args); return duprec; } /* Given a string containing , strip any colon prefixing . */ static void strip_params_colon(char *const params) { char *s; if (params == NULL) { return; } s = params; while (*s != '\0') { if (*s == ':') { memmove(s, s+1, strlen(s+1)+1); return; } s = strchr(s, ' '); if (s == NULL) { return; } while (*s == ' ') { s++; } } } static void irc_server_event(IRC_SERVER_REC *server, const char *line, const char *nick, const char *address) { const char *signal; char *event, *args; g_return_if_fail(line != NULL); /* split event / args */ event = g_strconcat("event ", line, NULL); args = strchr(event+6, ' '); if (args != NULL) *args++ = '\0'; else args = ""; while (*args == ' ') args++; ascii_strdown(event); /* check if event needs to be redirected */ signal = server_redirect_get_signal(server, nick, event, args); if (signal == NULL) signal = event; else rawlog_redirect(server->rawlog, signal); /* emit it */ current_server_event = event+6; if (!signal_emit(signal, 4, server, args, nick, address)) signal_emit_id(signal_default_event, 4, server, line, nick, address); current_server_event = NULL; g_free(event); } static void irc_server_event_tags(IRC_SERVER_REC *server, const char *line, const char *nick, const char *address, const char *tags) { if (*line != '\0') signal_emit_id(signal_server_event, 4, server, line, nick, address); } static char *irc_parse_prefix(char *line, char **nick, char **address, char **tags) { char *p; *nick = *address = *tags = NULL; /* ["@" SPACE] : [["!" ] "@" ] SPACE */ if (*line == '@') { *tags = ++line; while (*line != '\0' && *line != ' ') { line++; } if (*line == ' ') { *line++ = '\0'; while (*line == ' ') line++; } } if (*line != ':') return line; *nick = ++line; p = NULL; while (*line != '\0' && *line != ' ') { if (*line == '!' || *line == '@') { p = line; if (*line == '!') break; } line++; } if (p != NULL) { line = p; *line++ = '\0'; *address = line; while (*line != '\0' && *line != ' ') line++; } if (*line == ' ') { *line++ = '\0'; while (*line == ' ') line++; } return line; } /* Parse command line sent by server */ static void irc_parse_incoming_line(IRC_SERVER_REC *server, char *line) { char *nick, *address, *tags; g_return_if_fail(server != NULL); g_return_if_fail(line != NULL); line = irc_parse_prefix(line, &nick, &address, &tags); if (*line != '\0' || tags != NULL) signal_emit_id(signal_server_event_tags, 5, server, line, nick, address, tags); } /* input function: handle incoming server messages */ static void irc_parse_incoming(SERVER_REC *server) { char *str; int count; int ret; g_return_if_fail(server != NULL); /* Some commands can send huge replies and irssi might handle them too slowly, so read only a few times from the socket before letting other tasks to run. */ count = 0; ret = 0; server_ref(server); while (!server->disconnected && (ret = net_sendbuffer_receive_line(server->handle, &str, count < MAX_SOCKET_READS)) > 0) { rawlog_input(server->rawlog, str); signal_emit_id(signal_server_incoming, 2, server, str); if (server->connection_lost) server_disconnect(server); count++; } if (ret == -1) { /* connection lost */ server->connection_lost = TRUE; server_disconnect(server); } server_unref(server); } static void irc_init_server(IRC_SERVER_REC *server) { g_return_if_fail(server != NULL); if (!IS_IRC_SERVER(server)) return; server->readtag = g_input_add(net_sendbuffer_handle(server->handle), G_INPUT_READ, (GInputFunction) irc_parse_incoming, server); } void irc_irc_init(void) { signal_add("server event", (SIGNAL_FUNC) irc_server_event); signal_add("server event tags", (SIGNAL_FUNC) irc_server_event_tags); signal_add("server connected", (SIGNAL_FUNC) irc_init_server); signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); current_server_event = NULL; signal_default_event = signal_get_uniq_id("default event"); signal_server_event = signal_get_uniq_id("server event"); signal_server_event_tags = signal_get_uniq_id("server event tags"); signal_server_incoming = signal_get_uniq_id("server incoming"); } void irc_irc_deinit(void) { signal_remove("server event", (SIGNAL_FUNC) irc_server_event); signal_remove("server event tags", (SIGNAL_FUNC) irc_server_event_tags); signal_remove("server connected", (SIGNAL_FUNC) irc_init_server); signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); }