/* * who.c -- The WHO queue. The ISON queue. The USERHOST queue. * * Written by Jeremy Nelson * Copyright 1996, 1997 EPIC Software Labs */ #include "irc.h" static char cvsrevision[] = "$Id$"; CVS_REVISION(who_c) #include "struct.h" #include "commands.h" #include "ircaux.h" #include "who.h" #include "server.h" #include "window.h" #include "vars.h" #include "hook.h" #include "output.h" #include "numbers.h" #include "parse.h" #include "if.h" #include "names.h" #include "misc.h" #define MAIN_SOURCE #include "modval.h" /* * * * * WHO QUEUE * * * */ /* flags used by who queue */ #define WHO_OPS 0x0001 #define WHO_NAME 0x0002 #define WHO_ZERO 0x0004 #define WHO_CHOPS 0x0008 #define WHO_FILE 0x0010 #define WHO_HOST 0x0020 #define WHO_SERVER 0x0040 #define WHO_HERE 0x0080 #define WHO_AWAY 0x0100 #define WHO_NICK 0x0200 #define WHO_LUSERS 0x0400 #define WHO_REAL 0x0800 #define WHO_NOCHOPS 0x1000 #define WHO_INVISIBLE 0x2000 /* * This is tricky -- this doesnt get the LAST one, it gets the * next to the last one. Why? Because the LAST one is the one * asking, and they want to know who is LAST (before them) * So it sucks. Sue me. */ static WhoEntry *who_previous_query (WhoEntry *me) { WhoEntry *what = who_queue_top(from_server); while (what && what->next != me) what = what->next; return what; } static void who_queue_add (WhoEntry *item) { WhoEntry *bottom = who_queue_top(from_server); while (bottom && bottom->next) bottom = bottom->next; if (!bottom) set_who_queue_top(from_server, item); else bottom->next = item; return; } static void delete_who_item (WhoEntry *save) { new_free(&save->who_target); new_free(&save->who_name); new_free(&save->who_host); new_free(&save->who_server); new_free(&save->who_nick); new_free(&save->who_real); new_free(&save->who_stuff); new_free(&save->who_end); new_free(&save->who_buff); new_free(&save->who_args); new_free((char **)&save); } static void who_queue_pop (void) { WhoEntry *save; int piggyback; do { if (!(save = who_queue_top(from_server))) break; piggyback = save->piggyback; set_who_queue_top(from_server, save->next); delete_who_item(save); } while (piggyback); return; } static WhoEntry *get_new_who_entry (void) { WhoEntry *new_w = (WhoEntry *)new_malloc(sizeof(WhoEntry)); #if 0 no need to do this new_w->dirty = 0; new_w->piggyback = 0; new_w->who_mask = 0; new_w->who_target = NULL; new_w->who_host = NULL; new_w->who_name = NULL; new_w->who_server = NULL; new_w->who_nick = NULL; new_w->who_real = NULL; new_w->who_stuff = NULL; new_w->who_end = NULL; new_w->next = NULL; #endif return new_w; } static void who_queue_list (void) { WhoEntry *item = who_queue_top(from_server); int count = 0; while (item) { yell("[%d] [%d] [%s] [%s] [%s]", count, item->who_mask, item->who_nick ? item->who_nick : empty_string, item->who_stuff ? item->who_stuff : empty_string, item->who_end ? item->who_end : empty_string); count++; item = item->next; } } static void who_queue_flush (void) { WhoEntry *item; while ((item = who_queue_top(from_server))) who_queue_pop(); yell("Who queue for server [%d] purged", from_server); } /* * who: the /WHO command. Parses the who switches and sets the who_mask and * whoo_stuff accordingly. Who_mask and who_stuff are used in whoreply() in * parse.c */ BUILT_IN_COMMAND(whocmd) { whobase(args, NULL, NULL, NULL); } /* * whobase: What does all the work. */ void BX_whobase(char *args, void (*line) (WhoEntry *, char *, char **), void (*end) (WhoEntry *, char *, char **), char *format, ...) { char *arg, *channel = NULL; int len; WhoEntry *new_w, *old; int done = 0; /* Maybe should output a warning? */ if (from_server <= -1 || !is_server_connected(from_server)) return; new_w = get_new_who_entry(); new_w->line = line; new_w->end = end; while ((arg = next_arg(args, &args)) != NULL && !done) { lower(arg); if (*arg == '-' || *arg == '/') { arg++; if ((len = strlen(arg)) == 0) { say("Unknown or missing flag"); return; } if (!strncmp(arg, "line", 4)) /* LINE */ { char *line; if ((line = next_expr(&args, '{'))) malloc_strcpy(&new_w->who_stuff, line); else say("Need {...} argument for -LINE argument."); } else if (!strncmp(arg, "end", 3)) /* END */ { char *line; if ((line = next_expr(&args, '{'))) malloc_strcpy(&new_w->who_end, line); else say("Need {...} argument for -END argument."); } else if (!strncmp(arg, "raw", 3)) /* RAW */ { m_s3cat(&new_w->who_args, " ", args); done = 1; } else if (!strncmp(arg, "o", 1)) /* OPS */ new_w->who_mask |= WHO_OPS; else if (!strncmp(arg, "lu", 2)) /* LUSERS */ new_w->who_mask |= WHO_LUSERS; else if (!strncmp(arg, "ch", 2)) /* CHOPS */ new_w->who_mask |= WHO_CHOPS; else if (!strncmp(arg, "no", 2)) /* NOCHOPS */ new_w->who_mask |= WHO_NOCHOPS; else if (!strncmp(arg, "u-i", 3)) /* INVISIBLE */ new_w->who_mask |= WHO_INVISIBLE; else if (!strncmp(arg, "ho", 2)) /* HOSTS */ { if ((arg = next_arg(args, &args)) == NULL) { say("WHO -HOST: missing argument"); return; } new_w->who_mask |= WHO_HOST; malloc_strcpy(&new_w->who_host, arg); channel = new_w->who_host; } else if (!strncmp(arg, "he", 2)) /* here */ new_w->who_mask |= WHO_HERE; else if (!strncmp(arg, "a", 1)) /* away */ new_w->who_mask |= WHO_AWAY; else if (!strncmp(arg, "s", 1)) /* servers */ { if ((arg = next_arg(args, &args)) == NULL) { say("WHO -SERVER: missing arguement"); return; } new_w->who_mask |= WHO_SERVER; malloc_strcpy(&new_w->who_server, arg); channel = new_w->who_server; } else if (!strncmp(arg, "na", 2)) { if ((arg = next_arg(args, &args)) == NULL) { say("WHO -NAME: missing arguement"); return; } new_w->who_mask |= WHO_NAME; malloc_strcpy(&new_w->who_name, arg); channel = new_w->who_name; } else if (!strncmp(arg, "re", 2)) { if ((arg = next_arg(args, &args)) == NULL) { say("WHO -REALNAME: missing arguement"); return; } new_w->who_mask |= WHO_REAL; malloc_strcpy(&new_w->who_real, arg); channel = new_w->who_real; } else if (!strncmp(arg, "ni", 2)) { if ((arg = next_arg(args, &args)) == NULL) { say("WHO -NICK: missing arguement"); return; } new_w->who_mask |= WHO_NICK; malloc_strcpy(&new_w->who_nick, arg); channel = new_w->who_nick; } else if (!strncmp(arg, "d", 1)) { who_queue_list(); delete_who_item(new_w); return; } else if (!strncmp(arg, "f", 1)) { who_queue_flush(); delete_who_item(new_w); return; } else m_s3cat(&new_w->who_args, " ", arg); } else if (strcmp(arg, "*") == 0) { channel = get_current_channel_by_refnum(0); if (!channel || !*channel) { say("You are not on a channel. Use /WHO ** to see everybody."); delete_who_item(new_w); return; } } else channel = arg; } if (!channel && (new_w->who_mask & WHO_OPS)) channel = "*.*"; if (!channel || !*channel) { channel = get_current_channel_by_refnum(0); if (!channel || !*channel) { say("You are not on a channel. Use /WHO ** to see everybody."); delete_who_item(new_w); return; } } who_queue_add(new_w); new_w->who_target = m_strdup(channel); if (format) { va_list arg; char buffer[BIG_BUFFER_SIZE+1]; *buffer = 0; va_start(arg, format); vsnprintf(buffer, BIG_BUFFER_SIZE, format, arg); va_end(arg); new_w->who_buff = m_strdup(buffer); } /* * Check to see if we can piggyback */ old = who_previous_query(new_w); if (old && !old->dirty && old->who_target && channel && !strcmp(old->who_target, channel)) { old->piggyback = 1; if (x_debug & DEBUG_OUTBOUND) yell("Piggybacking this WHO onto last one."); } else send_to_server("WHO %s %s%s%s", new_w->who_target, (new_w->who_mask & WHO_OPS) ? "o" : empty_string, (new_w->who_mask & WHO_INVISIBLE) ? "x": empty_string, new_w->who_args ? new_w->who_args : empty_string); } void quote_whine(char *type) { yell("### Please dont do /QUOTE %s. Use /%s instead", type, type); return; } static int who_whine = 0; void whoreply (char *from, char **ArgList) { int ok = 1; int voice = 0, opped = 0; char *channel, *user, *host, *server, *nick, *stat, *name; ChannelList *chan = NULL; char buf_data[BIG_BUFFER_SIZE+1]; WhoEntry *new_w = who_queue_top(from_server); if (!ArgList[5]) return; /* Fake! */ if (!new_w && !who_whine) { who_whine = 1; channel = ArgList[0]; user = ArgList[1]; host = ArgList[2]; server = ArgList[3]; nick = ArgList[4]; stat = ArgList[5]; PasteArgs(ArgList, 6); name = ArgList[6]; voice = (strchr(stat, '+') != NULL); opped = (strchr(stat, '@') != NULL); set_display_target(channel, LOG_CRAP); if (do_hook(WHO_LIST, "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name)) put_it("%s",convert_output_format(fget_string_var(FORMAT_WHO_FSET), "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name)); reset_display_target(); return; } do { /* * this can happen in a very likely situation. The timing is critical. * a user joins a channel. We send a userhost, but before the server * responds, we are kicked and rejoin the channel. A reply is still * coming from the server but the channel sync hasn't finished, * which means we are in limbo. This should also fix the core when * /quote who is done. */ if (!new_w) break; /* * We have recieved a reply to this query -- its too late to * piggyback it now! */ new_w->dirty = 1; /* * We dont always want to use this function. * If another function is supposed to do the work for us, * we yield to them. */ if (new_w->line) { new_w->line(new_w, from, ArgList); continue; } channel = ArgList[0]; user = ArgList[1]; host = ArgList[2]; server = ArgList[3]; nick = ArgList[4]; stat = ArgList[5]; PasteArgs(ArgList, 6); name = ArgList[6]; voice = (strchr(stat, '+') != NULL); opped = (strchr(stat, '@') != NULL); *buf_data = 0; strmopencat(buf_data, BIG_BUFFER_SIZE, user, "@", host, NULL); if (*stat == 'S') /* this only true for the header WHOREPLY */ { char buffer[1024]; channel = "Channel"; snprintf(buffer, 1024, "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name); set_display_target(channel, LOG_CRAP); if (new_w->who_stuff) ; /* munch it */ else if (do_hook(WHO_LIST, "%s", buffer)) put_it("%s",convert_output_format(fget_string_var(FORMAT_WHO_FSET), "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name)); reset_display_target(); return; } if (new_w && new_w->who_mask) { if (new_w->who_mask & WHO_HERE) ok = ok && (*stat == 'H'); if (new_w->who_mask & WHO_AWAY) ok = ok && (*stat == 'G'); if (new_w->who_mask & WHO_OPS) ok = ok && (*(stat + 1) == '*'); if (new_w->who_mask & WHO_LUSERS) ok = ok && (*(stat + 1) != '*'); if (new_w->who_mask & WHO_CHOPS) ok = ok && ((*(stat + 1) == '@') || (*(stat + 2) == '@')); if (new_w->who_mask & WHO_NOCHOPS) ok = ok && ((*(stat + 1) != '@') && (*(stat + 2) != '@') && (*(stat + 3) != '@')); if (new_w->who_mask & WHO_NAME) ok = ok && wild_match(new_w->who_name, user); if (new_w->who_mask & WHO_NICK) ok = ok && wild_match(new_w->who_nick, nick); if (new_w->who_mask & WHO_HOST) ok = ok && wild_match(new_w->who_host, host); if (new_w->who_mask & WHO_REAL) ok = ok && wild_match(new_w->who_real, name); if (new_w->who_mask & WHO_SERVER) ok = ok && wild_match(new_w->who_server, server); } if (ok) { char buffer[1024]; snprintf(buffer, 1023, "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name); set_display_target(channel, LOG_CRAP); chan = add_to_channel(channel, nick, from_server, opped, voice, buf_data, server, stat, 0, my_atol(name)); if (new_w->who_stuff) parse_line(NULL, new_w->who_stuff, buffer, 0, 0, 1); else if (!in_join_list(channel, from_server) && do_hook(WHO_LIST, "%s", buffer)) { if (do_hook(current_numeric, "%s", buffer)) { if (!get_int_var(SHOW_WHO_HOPCOUNT_VAR)) next_arg(name, &name); put_it("%s",convert_output_format(fget_string_var(FORMAT_WHO_FSET), "%s %s %s %s %s %s %s", channel, nick, stat, user, host, server, name)); } } #if WANT_NSLOOKUP if (get_int_var(AUTO_NSLOOKUP_VAR)) do_nslookup(host, nick, user, channel, from_server, auto_nslookup, NULL); #endif reset_display_target(); } } while (new_w->piggyback && (new_w = new_w->next)); } void who_end (char *from, char **ArgList) { WhoEntry *new_w = who_queue_top(from_server); char buffer[1025]; if (who_whine) who_whine = 0; if (!new_w) return; do { /* * Defer to another function, if neccesary. */ if (new_w->end) new_w->end(new_w, from, ArgList); else { snprintf(buffer, 1024, "%s %s %s", from, ArgList[0], ArgList[1]); if (new_w->who_end) parse_line(NULL, new_w->who_end, buffer, 0, 0, 1); else if (get_int_var(SHOW_END_OF_MSGS_VAR)) if (do_hook(current_numeric, "%s", buffer)) put_it("%s %s", numeric_banner(), buffer); } } while (new_w->piggyback && (new_w = new_w->next)); who_queue_pop(); } /* * * * * ISON QUEUE * * * */ static void ison_queue_add (IsonEntry *item) { IsonEntry *bottom = ison_queue_top(from_server); while (bottom && bottom->next) bottom = bottom->next; if (!bottom) set_ison_queue_top(from_server, item); else bottom->next = item; return; } static void ison_queue_pop (void) { IsonEntry *save = ison_queue_top(from_server); if (save) { set_ison_queue_top(from_server, save->next); new_free(&save->ison_asked); new_free(&save->ison_got); new_free((char **)&save); } return; } static IsonEntry *get_new_ison_entry (void) { IsonEntry *new_w = (IsonEntry *)new_malloc(sizeof(IsonEntry)); #if 0 new_w->ison_asked = NULL; new_w->ison_got = NULL; new_w->next = NULL; new_w->line = NULL; #endif ison_queue_add(new_w); return new_w; } #if 0 static void ison_queue_list (void) { IsonEntry *item = ison_queue_top(from_server); int count = 0; while (item) { yell("[%d] [%s] [%#x]", count, ison_asked, line); count++; item = item->next; } } #endif BUILT_IN_COMMAND(isoncmd) { if (!args || !*args) args = LOCAL_COPY(get_server_nickname(from_server)); isonbase(args, NULL); } void BX_isonbase (char *args, void (*line) (char *, char *)) { IsonEntry *new_i; char *next = args; /* Maybe should output a warning? */ if (from_server <= -1 || !is_server_connected(from_server)) return; while ((args = next)) { new_i = get_new_ison_entry(); new_i->line = line; if (strlen(args) > 500) { next = args + 500; while (!isspace((unsigned char)*next)) next--; *next++ = 0; } else next = NULL; malloc_strcpy(&new_i->ison_asked, args); send_to_server("ISON %s", new_i->ison_asked); } } /* * ison_returned: this is called when numeric 303 is received in * numbers.c. ISON must always be the property of the WHOIS queue. * Although we will check first that the top element expected is * actually an ISON. */ void ison_returned (char *from, char **ArgList) { IsonEntry *new_i = ison_queue_top(from_server); if (!new_i) { quote_whine("ISON"); return; } PasteArgs(ArgList, 0); if (new_i->line) new_i->line(new_i->ison_asked, ArgList[0]); else { if (do_hook(current_numeric, "%s", ArgList[0])) put_it("%s Currently online: %s", numeric_banner(), ArgList[0]); } ison_queue_pop(); return; } /* * * * * * USERHOST QUEUE * * * * */ static void userhost_queue_add (UserhostEntry *item) { UserhostEntry *bottom = userhost_queue_top(from_server); while (bottom && bottom->next) bottom = bottom->next; if (!bottom) set_userhost_queue_top(from_server, item); else bottom->next = item; return; } static void userhost_queue_pop (void) { UserhostEntry *save = userhost_queue_top(from_server); set_userhost_queue_top(from_server, save->next); new_free(&save->userhost_asked); new_free(&save->text); new_free((char **)&save); return; } static UserhostEntry *get_new_userhost_entry (void) { UserhostEntry *new_u = (UserhostEntry *)new_malloc(sizeof(UserhostEntry)); userhost_queue_add(new_u); return new_u; } /* * userhost: Does the USERHOST command. Need to split up the queries, * since the server will only reply to 5 at a time. */ BUILT_IN_COMMAND(userhostcmd) { userhostbase(args, NULL, 1, NULL); } BUILT_IN_COMMAND(useripcmd) { userhostbase(args, NULL, 0, NULL); } BUILT_IN_COMMAND(usripcmd) { userhostbase(args, NULL, 2, NULL); } void BX_userhostbase(char *args, void (*line) (UserhostItem *, char *, char *), int userhost, char *format, ...) { int total = 0, userhost_cmd = 0, server_query_reqd = 0; char *nick, *ptr, *next_ptr, *body = NULL; va_list ap; char buffer[BIG_BUFFER_SIZE], text[BIG_BUFFER_SIZE]; if (from_server < 0 || !is_server_connected(from_server)) return; if (format) { va_start(ap, format); vsnprintf(text, sizeof text, format, ap); va_end(ap); } else *text = 0; *buffer = 0; while ((nick = next_arg(args, &args)) != NULL) { if (check_nickname(nick)) { total++; if (!fetch_userhost(from_server, nick)) server_query_reqd++; if (*buffer) strlcat(buffer, space, sizeof buffer); strlcat(buffer, nick, sizeof buffer); } else if (!my_strnicmp(nick, "-cmd", 2)) { if (!total) { say("%s -cmd with no nicks specified", userhost == 1 ? "USERHOST" : userhost == 0 ? "USERIP" : "USRIP"); return; } while (my_isspace(*args)) args++; if (!(body = next_expr_failok(&args, '{'))) body = args; userhost_cmd = 1; break; } else if (!my_strnicmp(nick, "-direct", 2)) server_query_reqd++; } if (!userhost_cmd && !total) { server_query_reqd++; strlcpy(buffer, get_server_nickname(from_server), sizeof buffer); } ptr = buffer; if (server_query_reqd || (!line && !userhost_cmd)) { while (ptr && *ptr) { UserhostEntry *new_uh = get_new_userhost_entry(); move_to_abs_word(ptr, &next_ptr, 5); if (next_ptr && *next_ptr && next_ptr > ptr) next_ptr[-1] = 0; new_uh->userhost_asked = m_strdup(ptr); send_to_server("%s %s", userhost == 1 ? "USERHOST" : !userhost ? "USERIP" : "USRIP", new_uh->userhost_asked); if (userhost_cmd) new_uh->text = m_strdup(body); else if (*text) new_uh->text = m_strdup(text); if (line) new_uh->func = line; else if (userhost_cmd) new_uh->func = userhost_cmd_returned; else new_uh->func = NULL; ptr = next_ptr; } } else { while (ptr && *ptr) { char *uh, *nick = next_arg(ptr, &ptr); const char *old_uh = fetch_userhost(from_server, nick); UserhostItem item = { 0 }; uh = LOCAL_COPY(old_uh); item.nick = nick; item.oper = 0; item.connected = 1; item.away = 0; item.user = uh; item.host = strchr(uh, '@'); if (item.host) *item.host++ = 0; else item.host = ""; if (line) line(&item, nick, body ? body : *text ? text : NULL); else if (userhost_cmd) userhost_cmd_returned(&item, nick, body); else yell("Yowza! I don't know what to do here!"); } } } /* * userhost_returned: this is called when numeric 302 is received in * numbers.c. USERHOST must always remain the property of the userhost * queue. Sending out USERHOST requests to the server without going * through this queue will cause it to be corrupted and the client will * go higgledy-piggledy. */ void userhost_returned (char *from, char **ArgList) { UserhostEntry *top = userhost_queue_top(from_server); char *ptr; if (!top) { quote_whine("USERHOST"); return; } ptr = top->userhost_asked; /* * Go through the nicknames that were requested... */ while (ptr && *ptr) { /* * Grab the next nickname */ char *cnick = next_arg(ptr, &ptr); int len = strlen(cnick); /* * Now either it is present at the next argument * or its not. If it is, it will match the first * part of ArgList, and the following char will * either be a * or an = (eg, nick*= or nick=) */ if (ArgList) { while (*(*ArgList) == ' ') (*ArgList)++; } if (ArgList && *ArgList && (!my_strnicmp(cnick, *ArgList, len) && ((*ArgList)[len] == '*' || (*ArgList)[len] == '='))) { UserhostItem item; /* Extract all the interesting info */ item.connected = 1; item.nick = next_arg(*ArgList, ArgList); item.user = strchr(item.nick, '='); if (item.user[-1] == '*') { item.user[-1] = 0; item.oper = 1; } else item.oper = 0; if (item.user[1] == '+') item.away = 0; else item.away = 1; *item.user++ = 0; item.user++; item.host = strchr(item.user, '@'); *item.host++ = 0; /* * If the user wanted a callback, then * feed the callback with the info. */ if (top->func) top->func(&item, cnick, top->text); /* * Otherwise, the user just did /userhost, * so we offer the numeric, and if the user * doesnt bite, we output to the screen. */ else if (do_hook(current_numeric, "%s %s %s %s %s", item.nick, item.oper ? "+" : "-", item.away ? "-" : "+", item.user, item.host)) put_it("%s %s is %s@%s%s%s", numeric_banner(), item.nick, item.user, item.host, item.oper ? " (Is an IRC operator)" : empty_string, item.away ? " (away)" : empty_string); } /* * If ArgList isnt the current nick, then the current nick * must not be on irc. So we whip up a dummy UserhostItem * and send it on its way. We DO NOT HOOK the 302 numeric * with this bogus entry, because thats the historical * behavior. This can cause a problem if you do a USERHOST * and wait on the 302 numeric. I think waiting on the 302 * numeric is stupid, anyhow. */ else { /* * Of course, only if the user asked for a callback * via /userhost -cmd or a direct call to userhostbase. * If the user just did /userhost, and the nicks arent * on, then we just dont display anything. */ if (top->func) { UserhostItem item; item.nick = cnick; item.user = item.host = ""; item.oper = item.away = 0; item.connected = 1; top->func(&item, cnick, top->text); } } } userhost_queue_pop(); } void userhost_cmd_returned (UserhostItem *stuff, char *nick, char *text) { char args[BIG_BUFFER_SIZE + 1]; strcpy(args, stuff->nick ? stuff->nick : empty_string); strcat(args, stuff->oper ? " + " : " - "); strcat(args, stuff->away ? "+ " : "- "); strcat(args, stuff->user ? stuff->user : empty_string); strcat(args, space); strcat(args, stuff->host ? stuff->host : empty_string); parse_line(NULL, text, args, 0, 0, 1); } void clean_server_queues (int i) { int old_from_server = from_server; if (i == -1 || !get_server_list() || !is_server_connected(i)) return; /* Whatever */ from_server = i; while (who_queue_top(i)) who_queue_pop(); while (ison_queue_top(i)) ison_queue_pop(); while (userhost_queue_top(i)) userhost_queue_pop(); from_server = old_from_server; }