/* irc-commands.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" #include "misc.h" #include "recode.h" #include "special-vars.h" #include "settings.h" #include "window-item-def.h" #include "servers-reconnect.h" #include "servers-redirect.h" #include "servers-setup.h" #include "nicklist.h" #include "bans.h" #include "irc-commands.h" #include "irc-servers.h" #include "irc-channels.h" #include "irc-queries.h" /* How often to check if there's anyone to be unbanned in knockout list */ #define KNOCKOUT_TIMECHECK 10000 /* /LIST: Max. number of channels in IRC network before -yes option is required */ #define LIST_MAX_CHANNELS_PASS 1000 /* When /PARTing a channel, if there's more messages in output queue than this, purge the output for channel. The idea behind this is that if you accidentally pasted some large text and /PART the channel, the text won't be fully pasted. Note that this counter is the whole size of the output queue, not channel specific.. */ #define MAX_COMMANDS_ON_PART_UNTIL_PURGE 10 typedef struct { IRC_CHANNEL_REC *channel; char *ban; time_t unban_time; } KNOCKOUT_REC; static GString *tmpstr; static int knockout_tag; /* SYNTAX: NOTICE */ static void cmd_notice(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { const char *target, *msg; char *recoded; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &target, &msg)) return; if (strcmp(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (*target == '\0' || *msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); recoded = recode_out(SERVER(server), msg, target); g_string_sprintf(tmpstr, "NOTICE %s :%s", target, recoded); g_free(recoded); irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); cmd_params_free(free_arg); } /* SYNTAX: CTCP [] */ static void cmd_ctcp(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { const char *target; char *ctcpcmd, *ctcpdata; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata)) return; if (strcmp(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); g_strup(ctcpcmd); if (*ctcpdata == '\0') g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s\001", target, ctcpcmd); else { char *recoded; recoded = recode_out(SERVER(server), ctcpdata, target); g_string_sprintf(tmpstr, "PRIVMSG %s :\001%s %s\001", target, ctcpcmd, recoded); g_free(recoded); } irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); cmd_params_free(free_arg); } /* SYNTAX: NCTCP [] */ static void cmd_nctcp(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { const char *target; char *ctcpcmd, *ctcpdata, *recoded; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST, &target, &ctcpcmd, &ctcpdata)) return; if (strcmp(target, "*") == 0) target = item == NULL ? NULL : window_item_get_target(item); if (*target == '\0' || *ctcpcmd == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); g_strup(ctcpcmd); recoded = recode_out(SERVER(server), ctcpdata, target); g_string_sprintf(tmpstr, "NOTICE %s :\001%s %s\001", target, ctcpcmd, recoded); g_free(recoded); irc_send_cmd_split(server, tmpstr->str, 2, server->max_msgs_in_cmd); cmd_params_free(free_arg); } /* SYNTAX: PART [] [] */ static void cmd_part(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *channame, *msg; char *recoded = NULL; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTCHAN, item, &channame, &msg)) return; if (*channame == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); if (*msg == '\0') msg = (char *) settings_get_str("part_message"); if (server->cmdcount > MAX_COMMANDS_ON_PART_UNTIL_PURGE) irc_server_purge_output(server, channame); if (*msg != '\0') recoded = recode_out(SERVER(server), msg, channame); irc_send_cmdv(server, ! recoded ? "PART %s" : "PART %s :%s", channame, recoded); g_free(recoded); cmd_params_free(free_arg); } /* SYNTAX: KICK [] [] */ static void cmd_kick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *channame, *nicks, *reason, *recoded; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTCHAN, item, &channame, &nicks, &reason)) return; if (*channame == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); if (!ischannel(*channame)) cmd_param_error(CMDERR_NOT_JOINED); recoded = recode_out(SERVER(server), reason, channame); g_string_sprintf(tmpstr, "KICK %s %s :%s", channame, nicks, recoded); g_free(recoded); irc_send_cmd_split(server, tmpstr->str, 3, server->max_kicks_in_cmd); cmd_params_free(free_arg); } /* SYNTAX: TOPIC [-delete] [] [] */ static void cmd_topic(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GHashTable *optlist; char *channame, *topic; char *recoded = NULL; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, item, "topic", &optlist, &channame, &topic)) return; if (*topic != '\0' || g_hash_table_lookup(optlist, "delete") != NULL) recoded = recode_out(SERVER(server), topic, channame); irc_send_cmdv(server, recoded == NULL ? "TOPIC %s" : "TOPIC %s :%s", channame, recoded); g_free(recoded); cmd_params_free(free_arg); } /* SYNTAX: INVITE [] */ static void cmd_invite(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *nick, *channame; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2, &nick, &channame)) return; if (*nick == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); if (*channame == '\0' || strcmp(channame, "*") == 0) { if (!IS_IRC_CHANNEL(item)) cmd_param_error(CMDERR_NOT_JOINED); channame = IRC_CHANNEL(item)->name; } irc_send_cmdv(server, "INVITE %s %s", nick, channame); cmd_params_free(free_arg); } /* SYNTAX: LIST [-yes] [] */ static void cmd_list(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GHashTable *optlist; char *str; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "list", &optlist, &str)) return; if (*str == '\0' && g_hash_table_lookup(optlist, "yes") == NULL && (server->channels_formed <= 0 || server->channels_formed > LIST_MAX_CHANNELS_PASS)) cmd_param_error(CMDERR_NOT_GOOD_IDEA); irc_send_cmdv(server, "LIST %s", str); cmd_params_free(free_arg); } /* SYNTAX: WHO [ | | **] */ static void cmd_who(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *channel, *rest; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_GETREST, &channel, &rest)) return; if (strcmp(channel, "*") == 0 || *channel == '\0') { if (!IS_IRC_CHANNEL(item)) cmd_param_error(CMDERR_NOT_JOINED); channel = IRC_CHANNEL(item)->name; } if (strcmp(channel, "**") == 0) { /* ** displays all nicks.. */ *channel = '\0'; } irc_send_cmdv(server, *rest == '\0' ? "WHO %s" : "WHO %s %s", channel, rest); cmd_params_free(free_arg); } static void cmd_names(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GHashTable *optlist; char *channel; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "names", &optlist, &channel)) return; if (strcmp(channel, "*") == 0 || *channel == '\0') { if (!IS_IRC_CHANNEL(item)) cmd_param_error(CMDERR_NOT_JOINED); channel = IRC_CHANNEL(item)->name; } if (strcmp(channel, "**") == 0) { /* ** displays all nicks.. */ irc_send_cmd(server, "NAMES"); } else { irc_send_cmdv(server, "NAMES %s", channel); } cmd_params_free(free_arg); } /* SYNTAX: NICK */ static void cmd_nick(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *nick; void *free_arg; g_return_if_fail(data != NULL); CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1, &nick)) return; g_free(server->last_nick); server->last_nick = g_strdup(nick); irc_send_cmdv(server, "NICK %s", nick); cmd_params_free(free_arg); } static char *get_redirect_nicklist(const char *nicks, int *free) { char *str, *ret; if (strchr(nicks, ',') == NULL) { *free = FALSE; return (char *) nicks; } *free = TRUE; str = g_strdup(nicks); g_strdelimit(str, ",", ' '); ret = g_strconcat(str, " ", nicks, NULL); g_free(str); return ret; } /* SYNTAX: WHOIS [-] [] [] */ static void cmd_whois(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GHashTable *optlist; char *qserver, *query, *event_402, *str; void *free_arg; int free_nick; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS, "whois", &optlist, &qserver, &query)) return; /* - */ server = IRC_SERVER(cmd_options_get_server("whois", optlist, SERVER(server))); if (server == NULL) { cmd_params_free(free_arg); return; } if (*query == '\0') { query = qserver; qserver = ""; } if (*query == '\0') { QUERY_REC *queryitem = QUERY(item); if (queryitem == NULL) query = server->nick; else query = qserver = queryitem->name; } if (strcmp(query, "*") == 0 && g_hash_table_lookup(optlist, "yes") == NULL) cmd_param_error(CMDERR_NOT_GOOD_IDEA); event_402 = "event 402"; if (*qserver == '\0') g_string_sprintf(tmpstr, "WHOIS %s", query); else { g_string_sprintf(tmpstr, "WHOIS %s %s", qserver, query); if (g_strcasecmp(qserver, query) == 0) event_402 = "whois event noserver"; } query = get_redirect_nicklist(query, &free_nick); str = g_strconcat(qserver, " ", query, NULL); if (settings_get_bool("auto_whowas")) { /* do automatic /WHOWAS if any of the nicks wasn't found */ server_redirect_event(server, "whois", 1, str, TRUE, NULL, "event 318", "whois end", "event 402", event_402, "event 301", "whois away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ "event 313", "whois oper", "event 401", "whois not found", "event 311", "whois event", "", "whois default event", NULL); } else { server_redirect_event(server, "whois", 1, str, TRUE, NULL, "event 318", "whois end", "event 301", "whois away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ "event 313", "whois oper", "event 311", "whois event", "", "whois default event", NULL); } g_free(str); server->whois_found = FALSE; irc_send_cmd_split(server, tmpstr->str, 2, server->max_whois_in_cmd); if (free_nick) g_free(query); cmd_params_free(free_arg); } static void event_whois(IRC_SERVER_REC *server, const char *data, const char *nick, const char *addr) { server->whois_found = TRUE; signal_emit("event 311", 4, server, data, nick, addr); } static void sig_whois_not_found(IRC_SERVER_REC *server, const char *data) { char *params, *nick; g_return_if_fail(data != NULL); params = event_get_params(data, 2, NULL, &nick); server->whowas_found = FALSE; server_redirect_event(server, "whowas", 1, nick, -1, NULL, "event 314", "whowas event", "event 369", "whowas event end", "event 406", "event empty", NULL); irc_send_cmdv(server, "WHOWAS %s 1", nick); g_free(params); } static void event_end_of_whois(IRC_SERVER_REC *server, const char *data, const char *nick, const char *addr) { signal_emit("event 318", 4, server, data, nick, addr); server->whois_found = FALSE; } static void event_whowas(IRC_SERVER_REC *server, const char *data, const char *nick, const char *addr) { server->whowas_found = TRUE; signal_emit("event 314", 4, server, data, nick, addr); } /* SYNTAX: WHOWAS [ []] */ static void cmd_whowas(const char *data, IRC_SERVER_REC *server) { char *nicks, *count, *nicks_redir; void *free_arg; int free_nick; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2, &nicks, &count)) return; if (*nicks == '\0') nicks = server->nick; nicks_redir = get_redirect_nicklist(nicks, &free_nick); server_redirect_event(server, "whowas", 1, nicks_redir, -1, NULL, "event 301", "whowas away", /* 301 can come as a reply to /MSG, /WHOIS or /WHOWAS */ "event 314", "whowas event", NULL); if (free_nick) g_free(nicks_redir); server->whowas_found = FALSE; irc_send_cmdv(server, *count == '\0' ? "WHOWAS %s" : "WHOWAS %s %s", nicks, count); cmd_params_free(free_arg); } /* SYNTAX: PING */ static void cmd_ping(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { GTimeVal tv; char *str; CMD_IRC_SERVER(server); if (*data == '\0' || strcmp(data, "*") == 0) { if (!IS_IRC_ITEM(item)) cmd_return_error(CMDERR_NOT_JOINED); data = window_item_get_target(item); } g_get_current_time(&tv); str = g_strdup_printf("%s PING %ld %ld", data, tv.tv_sec, tv.tv_usec); signal_emit("command ctcp", 3, str, server, item); g_free(str); } static void server_send_away(IRC_SERVER_REC *server, const char *reason) { if (!IS_IRC_SERVER(server)) return; if (*reason != '\0' || server->usermode_away) { g_free_and_null(server->away_reason); if (*reason != '\0') server->away_reason = g_strdup(reason); irc_send_cmdv(server, "AWAY :%s", reason); } } /* SYNTAX: AWAY [-one | -all] [] */ static void cmd_away(const char *data, IRC_SERVER_REC *server) { GHashTable *optlist; char *reason; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_GETREST, "away", &optlist, &reason)) return; if (g_hash_table_lookup(optlist, "one") != NULL) server_send_away(server, reason); else g_slist_foreach(servers, (GFunc) server_send_away, reason); cmd_params_free(free_arg); } /* SYNTAX: SCONNECT [[] ] */ static void cmd_sconnect(const char *data, IRC_SERVER_REC *server) { CMD_IRC_SERVER(server); if (*data == '\0') cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS); irc_send_cmdv(server, "CONNECT %s", data); } /* SYNTAX: QUOTE */ static void cmd_quote(const char *data, IRC_SERVER_REC *server) { if (server != NULL && !IS_IRC_SERVER(server)) return; if (server == NULL || server->connect_time == 0) cmd_return_error(CMDERR_NOT_CONNECTED); irc_send_cmd(server, data); } /* SYNTAX: RAWQUOTE */ static void cmd_rawquote(const char *data, IRC_SERVER_REC *server) { if (server != NULL && !IS_IRC_SERVER(server)) return; if (server == NULL || server->connect_time == 0) cmd_return_error(CMDERR_NOT_CONNECTED); irc_send_cmd_full(server, data, FALSE, FALSE, TRUE); } static void cmd_wall_hash(gpointer key, NICK_REC *nick, GSList **nicks) { if (nick->op) *nicks = g_slist_append(*nicks, nick); } /* SYNTAX: WAIT [-] */ static void cmd_wait(const char *data, IRC_SERVER_REC *server) { GHashTable *optlist; char *msecs; void *free_arg; int n; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST, NULL, &optlist, &msecs)) return; if (*msecs == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); /* - */ server = IRC_SERVER(cmd_options_get_server(NULL, optlist, SERVER(server))); n = atoi(msecs); if (server != NULL && n > 0) { g_get_current_time(&server->wait_cmd); server->wait_cmd.tv_sec += n/1000; server->wait_cmd.tv_usec += n%1000; if (server->wait_cmd.tv_usec >= 1000) { server->wait_cmd.tv_sec++; server->wait_cmd.tv_usec -= 1000; } } cmd_params_free(free_arg); } /* SYNTAX: WALL [] */ static void cmd_wall(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *channame, *msg, *args, *recoded; void *free_arg; IRC_CHANNEL_REC *chanrec; GSList *tmp, *nicks; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg)) return; if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); chanrec = irc_channel_find(server, channame); if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); /* See if the server has advertised support of wallchops */ if (g_hash_table_lookup(chanrec->server->isupport, "statusmsg") || g_hash_table_lookup(chanrec->server->isupport, "wallchops")) irc_send_cmdv(server, "NOTICE @%s :%s", chanrec->name, msg); else { /* Fall back to manually noticing each op */ nicks = NULL; g_hash_table_foreach(chanrec->nicks, (GHFunc) cmd_wall_hash, &nicks); args = g_strconcat(chanrec->name, " ", msg, NULL); msg = parse_special_string(settings_get_str("wall_format"), SERVER(server), item, args, NULL, 0); g_free(args); recoded = recode_out(SERVER(server), msg, channame); for (tmp = nicks; tmp != NULL; tmp = tmp->next) { NICK_REC *rec = tmp->data; if (rec != chanrec->ownnick) { irc_send_cmdv(server, "NOTICE %s :%s", rec->nick, recoded); } } g_free(recoded); g_free(msg); g_slist_free(nicks); } cmd_params_free(free_arg); } /* SYNTAX: WALLCHOPS */ /* ircu is the only major server i can see which supports this and it supports NOTICE @#channel anyway */ static void cmd_wallchops(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { char *channame, *msg, *recoded; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channame, &msg)) return; if (*msg == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); recoded = recode_out(SERVER(server), msg, channame); irc_send_cmdv(server, "WALLCHOPS %s :%s", channame, recoded); g_free(recoded); cmd_params_free(free_arg); } /* SYNTAX: KICKBAN [] */ static void cmd_kickban(const char *data, IRC_SERVER_REC *server, WI_ITEM_REC *item) { IRC_CHANNEL_REC *chanrec; char *channel, *nicks, *reason, *kickcmd, *bancmd, *recoded; char **nicklist, *spacenicks; void *free_arg; CMD_IRC_SERVER(server); if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTCHAN | PARAM_FLAG_GETREST, item, &channel, &nicks, &reason)) return; if (*channel == '\0' || *nicks == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); chanrec = irc_channel_find(server, channel); if (chanrec == NULL) cmd_param_error(CMDERR_CHAN_NOT_FOUND); if (!chanrec->wholist) cmd_param_error(CMDERR_CHAN_NOT_SYNCED); nicklist = g_strsplit(nicks, ",", -1); spacenicks = g_strjoinv(" ", nicklist); g_strfreev(nicklist); recoded = recode_out(SERVER(server), reason, channel); kickcmd = g_strdup_printf("%s %s %s", chanrec->name, nicks, recoded); g_free(recoded); bancmd = g_strdup_printf("%s %s", chanrec->name, spacenicks); g_free(spacenicks); if (settings_get_bool("kick_first_on_kickban")) { signal_emit("command kick", 3, kickcmd, server, chanrec); signal_emit("command ban", 3, bancmd, server, chanrec); } else { signal_emit("command ban", 3, bancmd, server, chanrec); signal_emit("command kick", 3, kickcmd, server, chanrec); } g_free(kickcmd); g_free(bancmd); cmd_params_free(free_arg); } static void knockout_destroy(IRC_SERVER_REC *server, KNOCKOUT_REC *rec) { server->knockoutlist = g_slist_remove(server->knockoutlist, rec); g_free(rec->ban); g_free(rec); } /* timeout function: knockout */ static void knockout_timeout_server(IRC_SERVER_REC *server) { GSList *tmp, *next; time_t now; g_return_if_fail(server != NULL); if (!IS_IRC_SERVER(server)) return; now = time(NULL); for (tmp = server->knockoutlist; tmp != NULL; tmp = next) { KNOCKOUT_REC *rec = tmp->data; next = tmp->next; if (rec->unban_time <= now) { /* timeout, unban. */ ban_remove(rec->channel, rec->ban); knockout_destroy(server, rec); } } } static int knockout_timeout(void) { g_slist_foreach(servers, (GFunc) knockout_timeout_server, NULL); return 1; } /* SYNTAX: KNOCKOUT [