/* * IRC - Internet Relay Chat, ircd/m_whois.c * Copyright (C) 1990 Jarkko Oikarinen and * University of Oulu, Computing Center * * See file AUTHORS in IRC package for additional names of * the programmers. * * 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: m_whois.c 1667 2006-06-09 02:16:17Z entrope $ */ /* * m_functions execute protocol messages on this server: * * cptr is always NON-NULL, pointing to a *LOCAL* client * structure (with an open socket connected!). This * identifies the physical socket where the message * originated (or which caused the m_function to be * executed--some m_functions may call others...). * * sptr is the source of the message, defined by the * prefix part of the message if present. If not * or prefix not found, then sptr==cptr. * * (!IsServer(cptr)) => (cptr == sptr), because * prefixes are taken *only* from servers... * * (IsServer(cptr)) * (sptr == cptr) => the message didn't * have the prefix. * * (sptr != cptr && IsServer(sptr) means * the prefix specified servername. (?) * * (sptr != cptr && !IsServer(sptr) means * that message originated from a remote * user (not local). * * combining * * (!IsServer(sptr)) means that, sptr can safely * taken as defining the target structure of the * message in this server. * * *Always* true (if 'parse' and others are working correct): * * 1) sptr->from == cptr (note: cptr->from == cptr) * * 2) MyConnect(sptr) <=> sptr == cptr (e.g. sptr * *cannot* be a local connection, unless it's * actually cptr!). [MyConnect(x) should probably * be defined as (x == x->from) --msa ] * * parc number of variable parameter strings (if zero, * parv is allowed to be NULL) * * parv a NULL terminated list of parameter pointers, * * parv[0], sender (prefix string), if not present * this points to an empty string. * parv[1]...parv[parc-1] * pointers to additional parameters * parv[parc] == NULL, *always* * * note: it is guaranteed that parv[0]..parv[parc-1] are all * non-NULL pointers. */ #include "config.h" #include "channel.h" #include "client.h" #include "hash.h" #include "ircd.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_reply.h" #include "ircd_string.h" #include "list.h" #include "match.h" #include "msg.h" #include "numeric.h" #include "numnicks.h" #include "s_user.h" #include "send.h" #include "whocmds.h" /* #include -- Now using assert in ircd_log.h */ #include /* * 2000-07-01: Isomer * * Rewritten to make this understandable * * You can no longer /whois unregistered clients. * * * General rules: * /whois nick always shows the nick. * /whois wild* shows the nick if: * * they aren't +i and aren't on any channels. * * they are on a common channel. * * they aren't +i and are on a public channel. (not +p and not +s) * * they aren't +i and are on a private channel. (+p but not +s) * Or to look at it another way (I think): * * +i users are only shown if your on a common channel. * * users on +s channels only aren't shown. * * whois doesn't show what channels a +k client is on, for the reason that * /whois X or /whois W floods a user off the server. :) * * nb: if the code and this comment disagree, the codes right and I screwed * up. */ /* * Send whois reply containing the marks for client. */ void client_whois_marks(struct Client *client, struct Client *replyto, const char *name) { static char markbufp[BUFSIZE] = ""; struct SLink* dp = NULL; if (!IsMarked(client)) return; memset(&markbufp, 0, BUFSIZE); for (dp = cli_marks(client); dp; dp = dp->next) { if (strlen(markbufp) + strlen(dp->value.cp) + 4 > 70) { send_reply(replyto, RPL_WHOISMARKS, name, markbufp); memset(&markbufp, 0, BUFSIZE); } /* Security fix: Replace strcat cu strncat pentru protectie buffer overflow */ if (markbufp[0]) { size_t remaining = BUFSIZE - strlen(markbufp) - 1; if (remaining > 2) { strncat(markbufp, ", ", remaining); } } size_t remaining = BUFSIZE - strlen(markbufp) - 1; if (remaining > 0 && dp->value.cp) { strncat(markbufp, dp->value.cp, remaining); } } if (markbufp[0]) { send_reply(replyto, RPL_WHOISMARKS, name, markbufp); } } /* * Send whois information for acptr to sptr */ static void do_whois(struct Client* sptr, struct Client *acptr, int parc) { struct Client *a2cptr=0; struct Channel *chptr=0; int mlen; int len; static char buf[512]; const struct User* user = cli_user(acptr); const char* name = (!*(cli_name(acptr))) ? "?" : cli_name(acptr); a2cptr = feature_bool(FEAT_HIS_WHOIS_SERVERNAME) && !IsAnOper(sptr) && sptr != acptr ? &his : user->server; assert(user); send_reply(sptr, RPL_WHOISUSER, name, user->username, user->host, cli_info(acptr)); /* Display the channels this user is on. * Ascunde canalele dacă: * - Target e channel service SAU * - Target are NoChan (+n) SAU * - Target are PRIV_HIDE_CHANNELS (hide_channels = yes în config) * EXCEPȚIE: Arată dacă target e tu însuți SAU cine face WHOIS e oper */ int hide_target_channels = (IsChannelService(acptr) || IsNoChan(acptr) || HasPriv(acptr, PRIV_HIDE_CHANNELS)); if (!hide_target_channels || (acptr==sptr) || IsAnOper(sptr)) { struct Membership* chan; mlen = strlen(cli_name(&me)) + strlen(cli_name(sptr)) + 12 + strlen(name); len = 0; *buf = '\0'; for (chan = user->channel; chan; chan = chan->next_channel) { chptr = chan->channel; if (!ShowChannel(sptr, chptr) && !(IsOper(sptr) && IsLocalChannel(chptr->chname))) continue; if (acptr != sptr && IsZombie(chan)) continue; /* Don't show local channels when HIS is defined, unless it's a * remote WHOIS --ULtimaTe_ */ if (IsLocalChannel(chptr->chname) && (acptr != sptr) && (parc == 2) && feature_bool(FEAT_HIS_WHOIS_LOCALCHAN) && !IsAnOper(sptr)) continue; if (len+strlen(chptr->chname) + mlen > BUFSIZE - 5) { send_reply(sptr, SND_EXPLICIT | RPL_WHOISCHANNELS, "%s :%s", name, buf); *buf = '\0'; len = 0; } if (IsDeaf(acptr)) *(buf + len++) = '-'; if (!ShowChannel(sptr, chptr) || (IsNoChan(acptr) && IsAnOper(sptr) && (acptr != sptr))) *(buf + len++) = '*'; if (IsDelayedJoin(chan) && (sptr != acptr)) *(buf + len++) = '<'; else if (IsChanOp(chan)) *(buf + len++) = '@'; else if (IsHalfOp(chan)) *(buf + len++) = '%'; else if (HasVoice(chan)) *(buf + len++) = '+'; else if (IsZombie(chan)) *(buf + len++) = '!'; if (len) *(buf + len) = '\0'; /* Security fix: Replace strcpy/strcat cu operații sigure */ size_t buf_remaining = sizeof(buf) - len - 1; if (buf_remaining > strlen(chptr->chname)) { strcpy(buf + len, chptr->chname); len += strlen(chptr->chname); if (len < sizeof(buf) - 2) { buf[len++] = ' '; buf[len] = '\0'; } } } if (buf[0] != '\0') send_reply(sptr, RPL_WHOISCHANNELS, name, buf); } send_reply(sptr, RPL_WHOISSERVER, name, cli_name(a2cptr), cli_info(a2cptr)); if (user) { if (user->away) send_reply(sptr, RPL_AWAY, name, user->away); /* Verifică dacă operul e vizibil în WHOIS * IsHideOper() = TRUE când hide_oper = yes în config * Pentru stealth oper (hide_oper = yes), NU afișa mesajul * Permite investigații undercover fără să fie detectat */ /* DEBUG: Log pentru verificare IsHideOper */ if (IsAnOper(acptr)) { log_write(LS_DEBUG, L_INFO, 0, "WHOIS DEBUG: %s - SeeOper=%d IsHideOper=%d IsAdmin=%d", name, SeeOper(sptr,acptr), IsHideOper(acptr), IsAdmin(acptr)); } if (SeeOper(sptr,acptr) && !IsHideOper(acptr)) { if (IsAdmin(acptr)) send_reply(sptr, RPL_WHOISOPERATOR, name, feature_str(FEAT_WHOIS_ADMIN)); else send_reply(sptr, RPL_WHOISOPERATOR, name, feature_str(FEAT_WHOIS_OPER)); } if (IsAccount(acptr)) send_reply(sptr, RPL_WHOISACCOUNT, name, user->account); if (IsHiddenHost(acptr) && (IsAnOper(sptr) || acptr == sptr)) send_reply(sptr, RPL_WHOISACTUALLY, name, user->username, user->realhost, ircd_ntoa(&cli_ip(acptr))); /* Hint: if your looking to add more flags to a user, eg +h, here's * probably a good place to add them :) */ if (IsChannelService(acptr)) send_reply(sptr, RPL_WHOISSERVICE, name, feature_str(FEAT_WHOIS_SERVICE)); if (IsAccountOnly(acptr) && !IsPrivDeaf(acptr)) send_reply(sptr, RPL_WHOISSPECIAL, name, "only accepts messages from registered users"); if (IsPrivDeaf(acptr)) send_reply(sptr, RPL_WHOISPRIVDEAF, name); if (IsBot(acptr)) send_reply(sptr, RPL_WHOISBOT, name); if (IsMarked(acptr)) client_whois_marks(acptr, sptr, name); if (cli_killmark(acptr) && !EmptyString(cli_killmark(acptr))) send_reply(sptr, RPL_WHOISKILL, name, cli_killmark(acptr)); if (IsSSL(acptr)) { send_reply(sptr, RPL_WHOISSSL, name); if (cli_sslclifp(acptr) && !EmptyString(cli_sslclifp(acptr))) send_reply(sptr, RPL_WHOISSSLFP, name, cli_sslclifp(acptr)); } if (!EmptyString(user->swhois)) send_reply(sptr, RPL_WHOISSPECIAL, name, user->swhois); if (cli_webirc(acptr) && !EmptyString(cli_webirc(acptr))) send_reply(sptr, RPL_WHOISWEBIRC, name, cli_webirc(acptr)); if (MyConnect(acptr) && (IsAnOper(sptr) || (!IsNoIdle(acptr) && (!feature_bool(FEAT_HIS_WHOIS_IDLETIME) || sptr == acptr || parc >= 3)))) send_reply(sptr, RPL_WHOISIDLE, name, CurrentTime - user->last, cli_firsttime(acptr)); if (IsOper(acptr) && IsWhoisNotice(acptr) && (sptr != acptr)) sendcmdto_one(&me, CMD_NOTICE, acptr, "%C :*** Notice -- %s (%s@%s) did a /whois on you.", acptr, cli_name(sptr), cli_user(sptr)->username, cli_user(sptr)->host); } } /* * Search and return as many people as matched by the wild 'nick'. * returns the number of people found (or, obviously, 0, if none where * found). */ static int do_wilds(struct Client* sptr, char *nick, int count, int parc) { struct Client *acptr; /* Current client we're considering */ struct User *user; /* the user portion of the client */ struct Membership* chan; int invis; /* does +i apply? */ int member; /* Is this user on any channels? */ int showperson; /* Should we show this person? */ int found = 0 ; /* How many were found? */ /* Ech! This is hideous! */ for (acptr = GlobalClientList; (acptr = next_client(acptr, nick)); acptr = cli_next(acptr)) { if (!IsRegistered(acptr)) continue; if (IsServer(acptr)) continue; /* * I'm always last :-) and acptr->next == 0!! * * Isomer: Does this strike anyone else as being a horrible hideous * hack? */ if (IsMe(acptr)) { assert(!cli_next(acptr)); break; } /* * 'Rules' established for sending a WHOIS reply: * * - if wildcards are being used don't send a reply if * the querier isn't any common channels and the * client in question is invisible. * * - only send replies about common or public channels * the target user(s) are on; */ user = cli_user(acptr); assert(user); invis = (acptr != sptr) && IsInvisible(acptr); member = (user && user->channel) ? 1 : 0; showperson = !invis && !member; /* Should we show this person now? */ if (showperson) { found++; do_whois(sptr, acptr, parc); if (count+found>MAX_WHOIS_LINES) return found; continue; } /* Step through the channels this user is on */ for (chan = user->channel; chan; chan = chan->next_channel) { struct Channel *chptr = chan->channel; /* If this is a public channel, show the person */ if (!invis && PubChannel(chptr)) { showperson = 1; break; } /* if this channel is +p and not +s, show them */ if (!invis && HiddenChannel(chptr) && !SecretChannel(chptr)) { showperson = 1; break; } member = find_channel_member(sptr, chptr) ? 1 : 0; if (invis && !member) continue; /* If sptr isn't really on this channel, skip it */ if (IsZombie(chan)) continue; /* Is this a common channel? */ if (member) { showperson = 1; break; } } /* of for (chan in channels) */ /* Don't show this person */ if (!showperson) continue; do_whois(sptr, acptr, parc); found++; if (count+found>MAX_WHOIS_LINES) return found; } /* of global client list */ return found; } /* * m_whois - generic message handler * * parv[0] = sender prefix * parv[1] = nickname masklist * * or * * parv[1] = target server, or a nickname representing a server to target. * parv[2] = nickname masklist */ int m_whois(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char* nick; char* tmp; char* p = 0; int found = 0; int total = 0; int wildscount = 0; if (parc < 2) { send_reply(sptr, ERR_NONICKNAMEGIVEN); return 0; } if (parc > 2) { /* For convenience: Accept a nickname as first parameter, by replacing * it with the correct servername - as is needed by hunt_server(). * This is the secret behind the /whois nick nick trick. */ if (feature_int(FEAT_HIS_REMOTE)) { /* If remote queries are disabled, then use the *second* parameter of * of whois, so /whois nick nick still works. */ if (!IsAnOper(sptr)) { if (!FindUser(parv[2])) { send_reply(sptr, ERR_NOSUCHNICK, parv[2]); send_reply(sptr, RPL_ENDOFWHOIS, parv[2]); return 0; } parv[1] = parv[2]; } } if (hunt_server_cmd(sptr, CMD_WHOIS, cptr, 0, "%C :%s", 1, parc, parv) != HUNTED_ISME) return 0; parv[1] = parv[2]; } for (tmp = parv[1]; (nick = ircd_strtok(&p, tmp, ",")); tmp = 0) { int wilds; found = 0; collapse(nick); wilds = (strchr(nick, '?') || strchr(nick, '*')); if (!wilds) { struct Client *acptr = 0; /* No wildcards */ acptr = FindUser(nick); if (acptr && !IsServer(acptr)) { do_whois(sptr, acptr, parc); found = 1; } } else /* wilds */ { if (++wildscount > 3) { send_reply(sptr, ERR_QUERYTOOLONG, parv[1]); break; } found=do_wilds(sptr, nick, total, parc); } if (!found) send_reply(sptr, ERR_NOSUCHNICK, nick); total+=found; if (total >= MAX_WHOIS_LINES) { send_reply(sptr, ERR_QUERYTOOLONG, parv[1]); break; } if (p) p[-1] = ','; } /* of tokenised parm[1] */ send_reply(sptr, RPL_ENDOFWHOIS, parv[1]); return 0; } /* * ms_whois - server message handler * * parv[0] = sender prefix * parv[1] = nickname masklist * * or * * parv[1] = target server, or a nickname representing a server to target. * parv[2] = nickname masklist */ int ms_whois(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) { char* nick; char* tmp; char* p = 0; int found = 0; int total = 0; if (parc < 2) { send_reply(sptr, ERR_NONICKNAMEGIVEN); return 0; } if (parc > 2) { if (hunt_server_cmd(sptr, CMD_WHOIS, cptr, 0, "%C :%s", 1, parc, parv) != HUNTED_ISME) return 0; parv[1] = parv[2]; } total = 0; for (tmp = parv[1]; (nick = ircd_strtok(&p, tmp, ",")); tmp = 0) { struct Client *acptr = 0; found = 0; collapse(nick); acptr = FindUser(nick); if (acptr && !IsServer(acptr)) { found++; do_whois(sptr, acptr, parc); } if (!found) send_reply(sptr, ERR_NOSUCHNICK, nick); total+=found; if (total >= MAX_WHOIS_LINES) { send_reply(sptr, ERR_QUERYTOOLONG, parv[1]); break; } if (p) p[-1] = ','; } /* of tokenised parm[1] */ send_reply(sptr, RPL_ENDOFWHOIS, parv[1]); return 0; }