/* * Unreal Internet Relay Chat Daemon, src/modules/whois.c * (C) 2000-2001 Carsten V. Munk and the UnrealIRCd Team * (C) 2003-2021 Bram Matthys and the UnrealIRCd team * Moved to modules by Fish (Justin Hammond) in 2001 * * 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. */ #include "unrealircd.h" /* Structs */ ModuleHeader MOD_HEADER = { "whois", /* Name of module */ "5.0", /* Version */ "command /whois", /* Short description of module */ "UnrealIRCd Team", "unrealircd-6", }; typedef enum WhoisConfigUser { WHOIS_CONFIG_USER_EVERYONE = 1, WHOIS_CONFIG_USER_SELF = 2, WHOIS_CONFIG_USER_OPER = 3, } WhoisConfigUser; #define HIGHEST_WHOIS_CONFIG_USER_VALUE 3 /* adjust this if you edit the enum above !! */ //this one is in include/struct.h because it needs full API exposure: //typedef enum WhoisConfigDetails { // ... //} WhoisConfigDetails; // typedef struct WhoisConfig WhoisConfig; struct WhoisConfig { WhoisConfig *prev, *next; char *name; WhoisConfigDetails permissions[HIGHEST_WHOIS_CONFIG_USER_VALUE+1]; }; /* Global variables */ WhoisConfig *whoisconfig = NULL; /* Forward declarations */ WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name); CMD_FUNC(cmd_whois); static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type); static void whois_config_setdefaults(void); MOD_TEST() { MARK_AS_OFFICIAL_MODULE(modinfo); EfunctionAdd(modinfo->handle, EFUNC_WHOIS_GET_POLICY, TO_INTFUNC(_whois_get_policy)); HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, whois_config_test); return MOD_SUCCESS; } MOD_INIT() { MARK_AS_OFFICIAL_MODULE(modinfo); CommandAdd(modinfo->handle, "WHOIS", cmd_whois, MAXPARA, CMD_USER); HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, whois_config_run); whois_config_setdefaults(); return MOD_SUCCESS; } MOD_LOAD() { return MOD_SUCCESS; } MOD_UNLOAD() { return MOD_SUCCESS; } static WhoisConfig *find_whois_config(const char *name) { WhoisConfig *w; for (w = whoisconfig; w; w = w->next) if (!strcmp(w->name, name)) return w; return NULL; } /* Lazy helper for whois_config_setdefaults */ static void whois_config_add(const char *name, WhoisConfigUser user, WhoisConfigDetails details) { WhoisConfig *w = find_whois_config(name); if (!w) { /* New one */ w = safe_alloc(sizeof(WhoisConfig)); safe_strdup(w->name, name); AddListItem(w, whoisconfig); } w->permissions[user] = details; } static void whois_config_setdefaults(void) { whois_config_add("basic", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("modes", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("modes", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("realhost", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("realhost", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("registered-nick", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("channels", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED); whois_config_add("channels", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("channels", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("server", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("away", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("oper", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED); whois_config_add("oper", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("oper", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("secure", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED); whois_config_add("secure", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("secure", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("bot", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("services", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("reputation", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("security-groups", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("geo", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("certfp", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("shunned", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("account", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("swhois", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("idle", WHOIS_CONFIG_USER_EVERYONE, WHOIS_CONFIG_DETAILS_LIMITED); whois_config_add("idle", WHOIS_CONFIG_USER_SELF, WHOIS_CONFIG_DETAILS_FULL); whois_config_add("idle", WHOIS_CONFIG_USER_OPER, WHOIS_CONFIG_DETAILS_FULL); } static void whois_free_config(void) { } static WhoisConfigUser whois_config_user_strtovalue(const char *str) { if (!strcmp(str, "everyone")) return WHOIS_CONFIG_USER_EVERYONE; if (!strcmp(str, "self")) return WHOIS_CONFIG_USER_SELF; if (!strcmp(str, "oper")) return WHOIS_CONFIG_USER_OPER; return 0; } static WhoisConfigDetails whois_config_details_strtovalue(const char *str) { if (!strcmp(str, "full")) return WHOIS_CONFIG_DETAILS_FULL; if (!strcmp(str, "limited")) return WHOIS_CONFIG_DETAILS_LIMITED; if (!strcmp(str, "none")) return WHOIS_CONFIG_DETAILS_NONE; return 0; } static int whois_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) { int errors = 0; ConfigEntry *cep, *cepp; if (type != CONFIG_SET) return 0; /* We are only interrested in set::whois-details.. */ if (!ce || strcmp(ce->name, "whois-details")) return 0; for (cep = ce->items; cep; cep = cep->next) { if (cep->value) { config_error("%s:%i: set::whois-details::%s item has a value, which is unexpected. Check your syntax!", cep->file->filename, cep->line_number, cep->name); errors++; continue; } for (cepp = cep->items; cepp; cepp = cepp->next) { if (!whois_config_user_strtovalue(cepp->name)) { config_error("%s:%i: set::whois-details::%s contains unknown user category called '%s', must be one of: everyone, self, ircop", cepp->file->filename, cepp->line_number, cep->name, cepp->name); errors++; continue; } else if (!cepp->value || !whois_config_details_strtovalue(cepp->value)) { config_error("%s:%i: set::whois-details::%s contains unknown details type '%s', must be one of: full, limited, none", cepp->file->filename, cepp->line_number, cep->name, cepp->name); errors++; continue; } /* else it is good */ } } *errs = errors; return errors ? -1 : 1; } static int whois_config_run(ConfigFile *cf, ConfigEntry *ce, int type) { ConfigEntry *cep, *cepp; if (type != CONFIG_SET) return 0; /* We are only interrested in set::whois-details.. */ if (!ce || strcmp(ce->name, "whois-details")) return 0; for (cep = ce->items; cep; cep = cep->next) { WhoisConfig *w = find_whois_config(cep->name); if (!w) { /* New one */ w = safe_alloc(sizeof(WhoisConfig)); safe_strdup(w->name, cep->name); AddListItem(w, whoisconfig); } for (cepp = cep->items; cepp; cepp = cepp->next) { WhoisConfigUser user = whois_config_user_strtovalue(cepp->name); WhoisConfigDetails details = whois_config_details_strtovalue(cepp->value); w->permissions[user] = details; } } return 1; } /** Get set::whois-details policy for an item. * @param client The client doing the /WHOIS * @param target The client being whoised, so the one to show all details for * @param name The name of the whois item (eg "modes") */ WhoisConfigDetails _whois_get_policy(Client *client, Client *target, const char *name) { WhoisConfig *w = find_whois_config(name); if (!w) return WHOIS_CONFIG_DETAILS_DEFAULT; if ((client == target) && (w->permissions[WHOIS_CONFIG_USER_SELF] > 0)) return w->permissions[WHOIS_CONFIG_USER_SELF]; if (IsOper(client) && (w->permissions[WHOIS_CONFIG_USER_OPER] > 0)) return w->permissions[WHOIS_CONFIG_USER_OPER]; if (w->permissions[WHOIS_CONFIG_USER_EVERYONE] > 0) return w->permissions[WHOIS_CONFIG_USER_EVERYONE]; return WHOIS_CONFIG_DETAILS_NONE; } /* WHOIS command. * parv[1] = list of nicks (comma separated) */ CMD_FUNC(cmd_whois) { Membership *lp; Client *target; Channel *channel; char *nick, *tmp; char *p = NULL; int len, mlen; char querybuf[BUFSIZE]; char buf[BUFSIZE]; int ntargets = 0; int maxtargets = max_targets_for_command("WHOIS"); if (parc < 2) { sendnumeric(client, ERR_NONICKNAMEGIVEN); return; } if (parc > 2) { if (hunt_server(client, recv_mtags, "WHOIS", 1, parc, parv) != HUNTED_ISME) return; parv[1] = parv[2]; } strlcpy(querybuf, parv[1], sizeof(querybuf)); for (tmp = canonize(parv[1]); (nick = strtoken(&p, tmp, ",")); tmp = NULL) { unsigned char showchannel, wilds, hideoper; /* <- these are all boolean-alike */ NameValuePrioList *list = NULL, *e; int policy; /* for temporary stuff */ if (MyUser(client) && (++ntargets > maxtargets)) { sendnumeric(client, ERR_TOOMANYTARGETS, nick, maxtargets, "WHOIS"); break; } /* We do not support "WHOIS *" */ wilds = (strchr(nick, '?') || strchr(nick, '*')); if (wilds) continue; target = find_user(nick, NULL); if (!target) { sendnumeric(client, ERR_NOSUCHNICK, nick); continue; } /* Ok, from this point we are going to proceed with the WHOIS. * The idea here is NOT to send any lines, so don't call sendto functions. * Instead, use add_nvplist_numeric() and add_nvplist_numeric_fmt() * to add items to the whois list. * Then at the end of this loop we call modules who can also add/remove * whois lines, and only after that we FINALLY send all the whois lines * in one go. */ hideoper = 0; if (IsHideOper(target) && (target != client) && !IsOper(client)) hideoper = 1; if (whois_get_policy(client, target, "basic") > WHOIS_CONFIG_DETAILS_NONE) { add_nvplist_numeric(&list, -1000000, "basic", client, RPL_WHOISUSER, target->name, target->user->username, IsHidden(target) ? target->user->virthost : target->user->realhost, target->info); } if (whois_get_policy(client, target, "modes") > WHOIS_CONFIG_DETAILS_NONE) { add_nvplist_numeric(&list, -100000, "modes", client, RPL_WHOISMODES, target->name, get_usermode_string(target), target->user->snomask ? target->user->snomask : ""); } if (whois_get_policy(client, target, "realhost") > WHOIS_CONFIG_DETAILS_NONE) { add_nvplist_numeric(&list, -90000, "realhost", client, RPL_WHOISHOST, target->name, (MyConnect(target) && strcmp(target->ident, "unknown")) ? target->ident : "*", target->user->realhost, target->ip ? target->ip : ""); } if (IsRegNick(target) && (whois_get_policy(client, target, "registered-nick") > WHOIS_CONFIG_DETAILS_NONE)) { add_nvplist_numeric(&list, -80000, "registered-nick", client, RPL_WHOISREGNICK, target->name); } /* The following code deals with channels */ policy = whois_get_policy(client, target, "channels"); if (policy > WHOIS_CONFIG_DETAILS_NONE) { int channel_whois_lines = 0; mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name); for (len = 0, *buf = '\0', lp = target->user->channel; lp; lp = lp->next) { Hook *h; int ret = EX_ALLOW; int operoverride = 0; channel = lp->channel; showchannel = 0; if (ShowChannel(client, channel)) showchannel = 1; for (h = Hooks[HOOKTYPE_SEE_CHANNEL_IN_WHOIS]; h; h = h->next) { int n = (*(h->func.intfunc))(client, target, channel); /* Hook return values: * EX_ALLOW means 'yes is ok, as far as modules are concerned' * EX_DENY means 'hide this channel, unless oper overriding' * EX_ALWAYS_DENY means 'hide this channel, always' * ... with the exception that we always show the channel if you /WHOIS yourself */ if (n == EX_DENY) { ret = EX_DENY; } else if (n == EX_ALWAYS_DENY) { ret = EX_ALWAYS_DENY; break; } } if (ret == EX_DENY) showchannel = 0; /* If the channel is normally hidden, but the user is an IRCOp, * and has the channel:see:whois privilege, * and set::whois-details for 'channels' has 'oper full', * then show it: */ if (!showchannel && (ValidatePermissionsForPath("channel:see:whois",client,NULL,channel,NULL)) && (policy == WHOIS_CONFIG_DETAILS_FULL)) { showchannel = 1; /* OperOverride */ operoverride = 1; } if ((ret == EX_ALWAYS_DENY) && (target != client)) continue; /* a module asked us to really not expose this channel, so we don't (except target==ourselves). */ /* This deals with target==client but also for unusual set::whois-details overrides * such as 'everyone full' */ if (policy == WHOIS_CONFIG_DETAILS_FULL) showchannel = 1; if (showchannel) { if (len + strlen(channel->name) > (size_t)BUFSIZE - 4 - mlen) { add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS, "%s :%s", target->name, buf); channel_whois_lines++; *buf = '\0'; len = 0; } if (operoverride) { /* '?' and '!' both mean we can see the channel in /WHOIS and normally wouldn't, * but there's still a slight difference between the two... */ if (!PubChannel(channel)) { /* '?' means it's a secret/private channel (too) */ *(buf + len++) = '?'; } else { /* public channel but hidden in WHOIS (umode +p, service bot, etc) */ *(buf + len++) = '!'; } } if (!MyUser(client) || !HasCapability(client, "multi-prefix")) { /* Standard NAMES reply (single character) */ char c = mode_to_prefix(*lp->member_modes); if (c) *(buf + len++) = c; } else { /* NAMES reply with all rights included (multi-prefix / NAMESX) */ strcpy(buf + len, modes_to_prefix(lp->member_modes)); len += strlen(buf + len); } if (len) *(buf + len) = '\0'; strcpy(buf + len, channel->name); len += strlen(channel->name); strcat(buf + len, " "); len++; } } if (buf[0] != '\0') { add_nvplist_numeric_fmt(&list, -70500-channel_whois_lines, "channels", client, RPL_WHOISCHANNELS, "%s :%s", target->name, buf); channel_whois_lines++; } } if (!(IsULine(target) && !IsOper(client) && HIDE_ULINES) && whois_get_policy(client, target, "server") > WHOIS_CONFIG_DETAILS_NONE) { add_nvplist_numeric(&list, -60000, "server", client, RPL_WHOISSERVER, target->name, target->user->server, target->uplink->info); } if (target->user->away && (whois_get_policy(client, target, "away") > WHOIS_CONFIG_DETAILS_NONE)) { add_nvplist_numeric(&list, -50000, "away", client, RPL_AWAY, target->name, target->user->away); } if (IsOper(target) && !hideoper) { policy = whois_get_policy(client, target, "oper"); if (policy == WHOIS_CONFIG_DETAILS_FULL) { const char *operlogin = get_operlogin(target); const char *operclass = get_operclass(target); if (operlogin && operclass) { add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR, "%s :is %s (%s) [%s]", target->name, "an IRC Operator", operlogin, operclass); } else if (operlogin) { add_nvplist_numeric_fmt(&list, -40000, "oper", client, RPL_WHOISOPERATOR, "%s :is %s (%s)", target->name, "an IRC Operator", operlogin); } else { add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR, target->name, "an IRC Operator"); } } else if (policy == WHOIS_CONFIG_DETAILS_LIMITED) { add_nvplist_numeric(&list, -40000, "oper", client, RPL_WHOISOPERATOR, target->name, "an IRC Operator"); } } if (target->umodes & UMODE_SECURE) { policy = whois_get_policy(client, target, "secure"); if (policy == WHOIS_CONFIG_DETAILS_LIMITED) { add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE, target->name, "is using a Secure Connection"); } else if (policy == WHOIS_CONFIG_DETAILS_FULL) { const char *ciphers = tls_get_cipher(target); if (ciphers) { add_nvplist_numeric_fmt(&list, -30000, "secure", client, RPL_WHOISSECURE, "%s :is using a Secure Connection [%s]", target->name, ciphers); } else { add_nvplist_numeric(&list, -30000, "secure", client, RPL_WHOISSECURE, target->name, "is using a Secure Connection"); } } } /* The following code deals with security-groups */ policy = whois_get_policy(client, target, "security-groups"); if ((policy > WHOIS_CONFIG_DETAILS_NONE) && !IsULine(target)) { SecurityGroup *s; int security_groups_whois_lines = 0; mlen = strlen(me.name) + strlen(client->name) + 10 + strlen(target->name) + strlen("is in security-groups: "); if (user_allowed_by_security_group_name(target, "known-users")) strlcpy(buf, "known-users,", sizeof(buf)); else strlcpy(buf, "unknown-users,", sizeof(buf)); len = strlen(buf); for (s = securitygroups; s; s = s->next) { if (len + strlen(s->name) > (size_t)BUFSIZE - 4 - mlen) { buf[len-1] = '\0'; add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups", target, RPL_WHOISSPECIAL, "%s :is in security-groups: %s", target->name, buf); security_groups_whois_lines++; *buf = '\0'; len = 0; } if (strcmp(s->name, "known-users") && user_allowed_by_security_group(target, s)) { strcpy(buf + len, s->name); len += strlen(buf+len); strcpy(buf + len, ","); len++; } } if (*buf) { buf[len-1] = '\0'; add_nvplist_numeric_fmt(&list, -15000-security_groups_whois_lines, "security-groups", client, RPL_WHOISSPECIAL, "%s :is in security-groups: %s", target->name, buf); security_groups_whois_lines++; } } if (MyUser(target) && IsShunned(target) && (whois_get_policy(client, target, "shunned") > WHOIS_CONFIG_DETAILS_NONE)) { add_nvplist_numeric(&list, -20000, "shunned", client, RPL_WHOISSPECIAL, target->name, "is shunned"); } if (target->user->swhois && (whois_get_policy(client, target, "swhois") > WHOIS_CONFIG_DETAILS_NONE)) { SWhois *s; int swhois_lines = 0; for (s = target->user->swhois; s; s = s->next) { if (hideoper && !IsOper(client) && s->setby && !strcmp(s->setby, "oper")) continue; /* hide oper-based swhois entries */ add_nvplist_numeric(&list, 100000+swhois_lines, "swhois", client, RPL_WHOISSPECIAL, target->name, s->line); swhois_lines++; } } /* TODO: hmm.. this should be a bit more towards the beginning of the whois, no ? */ if (IsLoggedIn(target) && (whois_get_policy(client, target, "account") > WHOIS_CONFIG_DETAILS_NONE)) { add_nvplist_numeric(&list, 200000, "account", client, RPL_WHOISLOGGEDIN, target->name, target->user->account); } if (MyConnect(target)) { policy = whois_get_policy(client, target, "idle"); /* If the policy is 'full' then show the idle time. * If the policy is 'limited then show the idle time according to the +I rules */ if ((policy == WHOIS_CONFIG_DETAILS_FULL) || ((policy == WHOIS_CONFIG_DETAILS_LIMITED) && !hide_idle_time(client, target))) { add_nvplist_numeric(&list, 500000, "idle", client, RPL_WHOISIDLE, target->name, (long long)(TStime() - target->local->idle_since), (long long)target->local->creationtime); } } RunHook(HOOKTYPE_WHOIS, client, target, &list); for (e = list; e; e = e->next) sendto_one(client, NULL, "%s", e->value); free_nvplist(list); } sendnumeric(client, RPL_ENDOFWHOIS, querybuf); }