diff --git a/CREDITS b/CREDITS index d8b548419..5f616b7d0 100644 --- a/CREDITS +++ b/CREDITS @@ -3,12 +3,12 @@ Solanum is based on Charybdis, which was based on ircd-ratbox. Development is led by a group of representatives from Libera Chat and OFTC: +amdj, Aaron Jones dwfreed, Doug Freed -edk, Ed Kellett -glguy, Eric Mertens ilbelkyr, Nicole Kleinhoff mcintosh, Richie McIntosh Myon, Christoph Berg +spb, Stephen Bennet tomaw, Tom Wesley The Charybdis team was: diff --git a/doc/connecting-servers.rst b/doc/connecting-servers.rst index c97b6176b..3ca80009b 100644 --- a/doc/connecting-servers.rst +++ b/doc/connecting-servers.rst @@ -112,7 +112,7 @@ hostname. For example, with atheme:: - loadmodule "modules/protocol/charybdis"; + loadmodule "modules/protocol/solanum"; uplink "a.example.org" { host = "localhost"; diff --git a/doc/features/filter.txt b/doc/features/filter.txt new file mode 100644 index 000000000..6a4050614 --- /dev/null +++ b/doc/features/filter.txt @@ -0,0 +1,59 @@ +extensions/filter module documentation +-------------------------------------- + +The filter extension implements message content filtering using +solanum's hook framework and Intel's Hyperscan regular expression +matching library. + +It requires an x86_64 processor with SSSE3 extensions. + +To operate, the filter requires a database of regular expessions +that have been compiled using the Hyperscan library's +hs_compile_multi() or hs_compile_ext_multi() functions. + +The command SETFILTER is used to manage operation of the filter and to +load compiled Hyperscan databases. + +General documenation of SETFILTER is available using the 'HELP SETFILTER' +command. + +For each expression in the database, the three least significant bits +of the expression ID are used to indicate which action the ircd should +take in the event of a match: + +001 (1) DROP - The message will be dropped and the client will be sent + an ERR_CANNOTSENDTOCHAN message. +010 (2) KILL - The connection from which the message was recevied will + be closed. +100 (4) ALARM - A Server Notice will be generated indicating that an + expression was matched. The nick, user, hostname and + IP address will be reported. For privacy, the expression + that has been matched will not be disclosed. + +Messages are passed to the filter module in a format similar to an +IRC messages: + +0:nick!user@host#1 PRIVMSG #help :hello! + +The number at the start of the line indicates the scanning pass: +Messages are scanned twice, once as they were received (0), and once +with any formatting or unprintable characters stripped (1). + +By default, 'nick', 'user' and 'host' will contain *. This behaviour +can be changed at build time if filtering on these fields is required. + +The number after the # will be 0 or 1 depending on whether the sending +client was identified to a NickServ account. + +The process for loading filters is as follows: + +1. The Hyperscan database is serialized using hs_serialize_database(). +2. A 'SETFILTER NEW' command is sent. +3. The serialized data is split into chunks and base64 encoded. + The chunk size needs to be chosen to ensure that the resuliting + strings are short enough to fit into a 510 byte IRC line, taking + into account space needed for the 'SETFILTER +' command, check field, + server mask, and base64 overhead. +4. The encoded chunks are sent using 'SETFILTER +' commands +5. Once the entire database has been sent, a 'SETFILTER APPLY' command + is sent to commit it. diff --git a/doc/reference.conf b/doc/reference.conf index a68379a4f..58b5fed1f 100644 --- a/doc/reference.conf +++ b/doc/reference.conf @@ -479,6 +479,7 @@ privset "local_op" { * usermode:helpops allows setting +h (from extensions/helpops) * auspex:usertimes: * allows viewing user idle/connect times even when +I is set (from extensions/umode_hide_idle_time) + * oper:shedding: allows the SHEDDING command (from extensions/m_shedding) */ privs = oper:general, oper:privs, oper:testline, oper:kill, oper:operwall, oper:message, usermode:servnotice, auspex:oper, auspex:hostname, auspex:umodes, auspex:cmodes; @@ -1314,6 +1315,13 @@ general { */ ping_cookie = no; + /* ping warn time: how long to wait after pinging a server before starting + * to complain it is unresponsive. Note that the ping check interval is 30 + * seconds, so the first complaint will come at the next check after this + * time has passed. + */ + ping_warn_time = 15 seconds; + /* connect timeout: sets how long we should wait for a connection * request to succeed */ @@ -1454,6 +1462,9 @@ general { /* hide_opers_in_whois: if set to YES, then oper status will be hidden in /WHOIS output. */ hide_opers_in_whois = no; + + /* hide_opers: Hide all opers from unprivileged users */ + hide_opers = no; /* tls_ciphers_oper_only: show the TLS cipher string in /WHOIS only to opers and self */ tls_ciphers_oper_only = no; diff --git a/extensions/Makefile.am b/extensions/Makefile.am index e404a9eea..b09e18241 100644 --- a/extensions/Makefile.am +++ b/extensions/Makefile.am @@ -19,6 +19,7 @@ extension_LTLIBRARIES = \ extb_account.la \ extb_canjoin.la \ extb_channel.la \ + extb_guest.la \ extb_hostmask.la \ extb_oper.la \ extb_server.la \ @@ -55,6 +56,7 @@ extension_LTLIBRARIES = \ m_omode.la \ m_opme.la \ m_sendbans.la \ + m_shedding.la \ m_webirc.la \ m_remove.la \ hide_uncommon_channels.la \ diff --git a/extensions/README b/extensions/README index 358b9dcc5..16b107ce6 100644 --- a/extensions/README +++ b/extensions/README @@ -79,6 +79,7 @@ extb_account.so - Account bans (+b $a[:mask]) extb_canjoin.so - Banned from another channel (+b $j:mask) extb_channel.so - Other-channel bans (+b $c:mask) extb_extgecos.so - Extended ban (+b $x:mask) +extb_guest.so - Unidentified bans (+b $g:mask) extb_oper.so - Oper bans (+b $o) extb_realname.so - Realname (gecos) bans (+b $r:mask) extb_server.so - Server bans (+b $s:mask) diff --git a/extensions/extb_account.c b/extensions/extb_account.c index 82af58899..a0fceb935 100644 --- a/extensions/extb_account.c +++ b/extensions/extb_account.c @@ -7,6 +7,7 @@ #include "modules.h" #include "client.h" #include "ircd.h" +#include "supported.h" static const char extb_desc[] = "Account ($a) extban type"; @@ -20,6 +21,7 @@ static int _modinit(void) { extban_table['a'] = eb_account; + add_isupport("ACCOUNTEXTBAN", isupport_string, "a"); return 0; } @@ -28,6 +30,7 @@ static void _moddeinit(void) { extban_table['a'] = NULL; + delete_isupport("ACCOUNTEXTBAN"); } static int eb_account(const char *data, struct Client *client_p, diff --git a/extensions/extb_extgecos.c b/extensions/extb_extgecos.c index 11d06b011..f636774e1 100644 --- a/extensions/extb_extgecos.c +++ b/extensions/extb_extgecos.c @@ -34,15 +34,42 @@ _moddeinit(void) static int eb_extended(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type) { - char buf[BUFSIZE]; - (void)chptr; if (data == NULL) return EXTBAN_INVALID; - snprintf(buf, sizeof buf, "%s!%s@%s#%s", - client_p->name, client_p->username, client_p->host, client_p->info); + const char *idx = strchr(data, '#'); + + if (idx != NULL && idx[1] == '\0') + /* Users cannot have empty realnames, + * so don't let a ban be set matching one + */ + return EXTBAN_INVALID; + + char buf[BUFSIZE]; + + if (idx != NULL) + { + // Copy the nick!user@host part of the ban + memcpy(buf, data, (idx - data)); + buf[(idx - data)] = '\0'; + + // Advance to the realname part of the ban + idx++; + + if (client_matches_mask(client_p, buf) && match(idx, client_p->info)) + return EXTBAN_MATCH; + } + else + { + // Treat data as a pattern to match against the full nick!user@host#gecos. + snprintf(buf, sizeof buf, "%s!%s@%s#%s", + client_p->name, client_p->username, client_p->host, client_p->info); + + if (match(data, buf)) + return EXTBAN_MATCH; + } - return match(data, buf) ? EXTBAN_MATCH : EXTBAN_NOMATCH; + return EXTBAN_NOMATCH; } diff --git a/extensions/extb_guest.c b/extensions/extb_guest.c new file mode 100644 index 000000000..22a1e4368 --- /dev/null +++ b/extensions/extb_guest.c @@ -0,0 +1,73 @@ +/* + * Guest extban type: bans unidentified users matching nick!user@host. + * -- TheDaemoness + */ + +#include "stdinc.h" +#include "modules.h" +#include "client.h" +#include "ircd.h" + +static const char extb_desc[] = "Guest ($g) extban type - bans unidentified users matching nick!user@host"; + +static int _modinit(void); +static void _moddeinit(void); +static int eb_guest(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type); + +DECLARE_MODULE_AV2(extb_guest, _modinit, _moddeinit, NULL, NULL, NULL, NULL, NULL, extb_desc); + +static int +_modinit(void) +{ + extban_table['g'] = eb_guest; + + return 0; +} + +static void +_moddeinit(void) +{ + extban_table['g'] = NULL; +} + +static int eb_guest(const char *data, struct Client *client_p, + struct Channel *chptr, long mode_type) +{ + (void)chptr; + + if (data == NULL) + return EXTBAN_INVALID; + + const char *idx = strchr(data, '#'); + + if (idx != NULL && idx[1] == '\0') + /* Users cannot have empty realnames, + * so don't let a ban be set matching one + */ + return EXTBAN_INVALID; + + if (!EmptyString(client_p->user->suser)) + return EXTBAN_NOMATCH; + + if (idx != NULL) + { + char buf[BUFSIZE]; + + // Copy the nick!user@host part of the ban + memcpy(buf, data, (idx - data)); + buf[(idx - data)] = '\0'; + + // Advance to the realname part of the ban + idx++; + + if (client_matches_mask(client_p, buf) && match(idx, client_p->info)) + return EXTBAN_MATCH; + + return EXTBAN_NOMATCH; + } + + if (client_matches_mask(client_p, data)) + return EXTBAN_MATCH; + + return EXTBAN_NOMATCH; +} diff --git a/extensions/m_shedding.c b/extensions/m_shedding.c new file mode 100644 index 000000000..a5cdc19d9 --- /dev/null +++ b/extensions/m_shedding.c @@ -0,0 +1,176 @@ +/* + * Solanum: a slightly advanced ircd + * shedding.c: Enables/disables user shedding. + * + * Based on oftc-hybrid's m_shedding.c + * + * Copyright (C) 2021 David Schultz + * Copyright (C) 2002 by the past and present ircd coders, and others. + * + * 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 "stdinc.h" +#include "modules.h" +#include "hook.h" +#include "client.h" +#include "ircd.h" +#include "send.h" +#include "s_conf.h" +#include "s_serv.h" +#include "s_newconf.h" +#include "messages.h" +#include "numeric.h" + +#define SHED_RATE_MIN 5 + +static struct ev_entry *user_shedding_ev = NULL; + +static const char shed_desc[] = "Enables/disables user shedding."; + +static void mo_shedding(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]); +static void me_shedding(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]); +static void do_user_shedding(void *unused); + +static struct Message shedding_msgtab = { + "SHEDDING", 0, 0, 0, 0, + {mg_unreg, mg_not_oper, mg_ignore, mg_ignore, {me_shedding, 2}, {mo_shedding, 3}} +}; + +mapi_clist_av1 shedding_clist[] = { &shedding_msgtab, NULL }; + +static void +moddeinit(void) +{ + rb_event_delete(user_shedding_ev); +} + +DECLARE_MODULE_AV2(shed, NULL, moddeinit, shedding_clist, NULL, NULL, NULL, NULL, shed_desc); + +static void +set_shedding_state(struct Client *source_p, const char *chr, const char *reason) +{ + int rate; + if (irccmp(chr, "OFF") == 0) + { + // disable shedding + sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE, "%s disabled user shedding", get_oper_name(source_p)); + rb_event_delete(user_shedding_ev); + user_shedding_ev = NULL; + return; + } + + rate = atoi(chr); + + if(rate < SHED_RATE_MIN) + { + sendto_one_notice(source_p, "Shedding rate must be at least %d", SHED_RATE_MIN); + return; + } + + sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE, "%s enabled user shedding (interval: %d seconds, reason: %s)", + get_oper_name(source_p), rate, reason); + + rb_event_delete(user_shedding_ev); + user_shedding_ev = NULL; + user_shedding_ev = rb_event_add("user shedding event", do_user_shedding, NULL, rate); +} + +/* + * mo_shedding + * + * inputs - pointer to server + * - pointer to client + * - parameter count + * - parameter list + * output - + * side effects - user shedding is enabled or disabled + * + * SHEDDING OFF - disable shedding + * SHEDDING : + * (parv[#] 1 2 3) + * + */ +static void +mo_shedding(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) +{ + if (!HasPrivilege(source_p, "oper:shedding")) + { + sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "SHEDDING"); + return; + } + + /* I can think of a thousand ways this could go wrong... */ + if (strchr(parv[1], '*') != NULL) + { + sendto_one_notice(source_p, "Wildcards are not permitted for shedding targets"); + return; + } + + if (parc != 4 && !(parc == 3 && irccmp(parv[2], "OFF") == 0)) + { + sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS), + me.name, source_p->name, "SHEDDING"); + return; + } + + if (irccmp(parv[1], me.name) != 0) { + /* it's not for us, pass it around */ + if (irccmp(parv[2], "OFF") == 0) + sendto_match_servs(source_p, parv[1], + CAP_ENCAP, NOCAPS, + "ENCAP %s SHEDDING OFF", parv[1]); + else + sendto_match_servs(source_p, parv[1], + CAP_ENCAP, NOCAPS, + "ENCAP %s SHEDDING %s :%s", + parv[1], parv[2], parv[3]); + return; + } + + set_shedding_state(source_p, parv[2], parv[3]); +} + +static void +me_shedding(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) +{ + if(!IsPerson(source_p)) + return; + + set_shedding_state(source_p, parv[1], parv[2]); +} + + +static void +do_user_shedding(void *unused) +{ + rb_dlink_node *ptr; + struct Client *client_p; + + RB_DLINK_FOREACH_PREV(ptr, lclient_list.tail) + { + client_p = ptr->data; + + if (!IsClient(client_p)) /* It could be servers */ + continue; + if (IsExemptKline(client_p)) + continue; + exit_client(client_p, client_p, &me, "Server closed connection"); + break; + } +} diff --git a/help/opers/cmode b/help/opers/cmode index e6ba9d0a4..fa2a64132 100644 --- a/help/opers/cmode +++ b/help/opers/cmode @@ -37,6 +37,8 @@ NO PARAMETERS: +C - Disable CTCP. All CTCP messages to the channel, except ACTION, are disallowed. ? +O - IRC Operator only channel. + ? +M - IRC Operators can not be kicked. Only settable by opers. Only + viewable by opers. ? +A - IRC server administrator only channel. ? +T - No NOTICEs allowed in the channel. ? +S - Only users connected via SSL/TLS may join the channel while this diff --git a/help/opers/extban b/help/opers/extban index 5a0e8e68b..73a979dd7 100644 --- a/help/opers/extban +++ b/help/opers/extban @@ -19,6 +19,7 @@ Unless noted below, all types can be used with +b, +q, +e and +I. $a: - Matches users logged in with a username matching the mask (* and ? wildcards) $c: - Matches users who are on the given channel + $g: - Matches as a normal ban but excludes logged in users $o - Matches opers (most useful with +I) $r: - Matches users with a realname (gecos) matching the mask (* and ? wildcards); this can only be used with +b and +q diff --git a/help/opers/setfilter b/help/opers/setfilter new file mode 100644 index 000000000..651668305 --- /dev/null +++ b/help/opers/setfilter @@ -0,0 +1,27 @@ +SETFILTER * ENABLE +SETFILTER * DISABLE +SETFILTER * DROP +SETFILTER * ABORT +SETFILTER [server-mask] { NEW | APPLY | + } + +Manages Hyperscan message filtering. + +ENABLE activates filtering. + +DISABLE deactivates filtering. It can be re-enabled with ENABLE. + +DROP unloads the currently loaded Hyperscan database, if any. + +ABORT cancels a database load operation started with NEW. + +NEW prepares a buffer to accept a new Hyperscan database. + + is a base64 encoded chunk of a serialized hyperscan database. + +APPLY deserialises the buffer and sets the resulting hyperscan database +as the one to use for filtering. + + can be any string and must be the same for all NEW, +, and +APPLY commands for a single hyperscan database. + +Requires Oper Priv: oper:admin diff --git a/help/opers/shedding b/help/opers/shedding new file mode 100644 index 000000000..e7dfa5c1b --- /dev/null +++ b/help/opers/shedding @@ -0,0 +1,9 @@ +SHEDDING : +SHEDDING OFF + +Turns user shedding on or off on the specified server. When a server is +shedding, a client will be disconnected from the server at a time period +defined by rate, until shedding is turned off. Clients with kline_exempt +are never affected by this feature. + +- Requires Oper Priv: oper:shedding diff --git a/help/users/cmode b/help/users/cmode new file mode 100644 index 000000000..e6ba9d0a4 --- /dev/null +++ b/help/users/cmode @@ -0,0 +1,72 @@ +MODE <+|-> [parameters] + +? designates that the cmode is provided by an extension +and may not be present on this server. + +CHANNELMODE - DESCRIPTION +------------------------------------------------------------------------ +NO PARAMETERS: + +n - No external messages. Only channel members may talk in + the channel. + +t - Ops Topic. Only opped (+o) users may set the topic. + +s - Secret. Channel will not be shown in /whois and /list etc. + +p - Private. Disables /knock to the channel. + +m - Moderated. Only opped/voiced users may talk in channel. + +i - Invite only. Users need to be invited or match a +I to + join the channel. + +r - Registered users only. Only users identified to services + may join. + ? +R - Blocks messages from unregistered users. Only users + identified to services may talk in the channel. + +c - No color. All markup (color, bold, underline, etc.) in + messages is stripped. + +g - Free invite. Everyone may invite users. Significantly + weakens +i control. + ? +u - Unfiltered. Receive messages that would otherwise be filtered + server side based on content. + +z - Op moderated. Messages blocked by +m, +b and +q are instead + sent to ops. + +L - Large ban list. Increase maximum number of +beIq entries. + Only settable by opers. + +P - Permanent. Channel does not disappear when empty. Only + settable by opers. + +F - Free target. Anyone may set forwards to this (otherwise + ops are necessary). + +Q - Disable forward. Users cannot be forwarded to the channel + (however, new forwards can still be set subject to +F). + +C - Disable CTCP. All CTCP messages to the channel, except ACTION, + are disallowed. + ? +O - IRC Operator only channel. + ? +A - IRC server administrator only channel. + ? +T - No NOTICEs allowed in the channel. + ? +S - Only users connected via SSL/TLS may join the channel while this + mode is set. Users already in the channel are not affected. + +WITH PARAMETERS: + +f - Forward. Forwards users who cannot join because of +i, + +j, +l or +r. + PARAMS: /mode #channel +f #channel2 + +j - Join throttle. Limits number of joins to the channel per time. + PARAMS: /mode #channel +j count:time + +k - Key. Requires users to issue /join #channel KEY to join. + PARAMS: /mode #channel +k key + +l - Limit. Impose a maximum number of LIMIT people in the channel. + PARAMS: /mode #channel +l limit + +v - Voice. Allows a user to talk in a +m channel. Noted by +nick. + PARAMS: /mode #channel +v nick + +o - Op. Allows a user full control over the channel. + PARAMS: /mode #channel +o nick + +b - Ban. Prevents a user from entering the channel, and from + sending or changing nick if they are on it, based on a + nick!ident@host match. + PARAMS: /mode #channel +b nick!user@host + +q - Quiet. Prevents a user from sending to the channel or changing + nick, based on a nick!ident@host match. + PARAMS: /mode #channel +q nick!user@host + +e - Exempt. Allows a user to join a channel and send to it even if + they are banned (+b) or quieted (+q), based on a nick!ident@host + match. + PARAMS: /mode #channel +e nick!user@host + +I - Invite Exempt. Allows a user to join a +i channel without an + invite, based on a nick!user@host match. + PARAMS: /mode #channel +I nick!user@host diff --git a/include/client.h b/include/client.h index 7dfc8f14f..bf7ca79b5 100644 --- a/include/client.h +++ b/include/client.h @@ -419,6 +419,7 @@ struct ListClient #define FLAGS_EXEMPTSHIDE 0x04000000 #define FLAGS_EXEMPTJUPE 0x08000000 #define FLAGS_IDENTIFIED 0x10000000 /* owns their current nick */ +#define FLAGS_PINGWARN 0x20000000 /* whether we've warned about this client being unresponsive */ /* flags for local clients, this needs stuff moved from above to here at some point */ @@ -602,7 +603,7 @@ extern struct Client *find_named_person(const char *); extern struct Client *next_client(struct Client *, const char *); #define accept_message(s, t) ((s) == (t) || (rb_dlinkFind((s), &((t)->localClient->allow_list)))) -extern void del_all_accepts(struct Client *client_p); +extern void del_all_accepts(struct Client *client_p, bool self_too); extern void dead_link(struct Client *client_p, int sendqex); extern int show_ip(struct Client *source_p, struct Client *target_p); diff --git a/include/messages.h b/include/messages.h index 0130111e9..a50d9a6db 100644 --- a/include/messages.h +++ b/include/messages.h @@ -96,6 +96,7 @@ #define NUMERIC_STR_317 "%s %ld %lu :seconds idle, signon time" #define NUMERIC_STR_318 "%s :End of /WHOIS list." #define NUMERIC_STR_319 ":%s 319 %s %s :" +#define NUMERIC_STR_320 "%s :%s" #define NUMERIC_STR_321 ":%s 321 %s Channel :Users Name" #define NUMERIC_STR_322 ":%s 322 %s %s%s %lu :%s" #define NUMERIC_STR_323 ":%s 323 %s :End of /LIST" @@ -106,7 +107,6 @@ #define NUMERIC_STR_331 ":%s 331 %s %s :No topic is set." #define NUMERIC_STR_332 ":%s 332 %s %s :%s" #define NUMERIC_STR_333 ":%s 333 %s %s %s %lld" -#define NUMERIC_STR_337 "%s :%s" #define NUMERIC_STR_338 "%s %s :actually using host" #define NUMERIC_STR_341 ":%s 341 %s %s %s" #define NUMERIC_STR_346 ":%s 346 %s %s %s %s %lu" diff --git a/include/numeric.h b/include/numeric.h index dcabff612..9132b7d1c 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -144,6 +144,7 @@ #define RPL_ENDOFWHOIS 318 #define RPL_WHOISCHANNELS 319 +#define RPL_WHOISSPECIAL 320 #define RPL_LISTSTART 321 #define RPL_LIST 322 @@ -159,7 +160,6 @@ #define RPL_NOTOPIC 331 #define RPL_TOPIC 332 #define RPL_TOPICWHOTIME 333 -#define RPL_WHOISTEXT 337 #define RPL_WHOISACTUALLY 338 #define RPL_INVITING 341 diff --git a/include/reject.h b/include/reject.h index 2ca378fe8..5ed023427 100644 --- a/include/reject.h +++ b/include/reject.h @@ -28,7 +28,7 @@ #define DELAYED_EXIT_TIME 10 void init_reject(void); -int check_reject(rb_fde_t *F, struct sockaddr *addr); +int check_reject(rb_fde_t *F, struct sockaddr *addr, bool ssl); void add_reject(struct Client *, const char *mask1, const char *mask2, struct ConfItem *aconf, const char *reason); int is_reject_ip(struct sockaddr *addr); void flush_reject(void); diff --git a/include/s_conf.h b/include/s_conf.h index acc4f1b82..53233ee1c 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -204,6 +204,7 @@ struct config_file_entry int operspy_admin_only; int pace_wait; int pace_wait_simple; + int ping_warn_time; int short_motd; int no_oper_flood; int hide_server; diff --git a/ircd/client.c b/ircd/client.c index 24738ae01..9482b37c5 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -420,6 +420,27 @@ check_pings_list(rb_dlink_list * list) client_p->localClient->lasttime = rb_current_time() - ping; sendto_one(client_p, "PING :%s", me.name); } + else if (ConfigFileEntry.ping_warn_time > 0 && (IsServer(client_p) || IsHandshake(client_p)) && + (rb_current_time() - client_p->localClient->lasttime) >= (ping + ConfigFileEntry.ping_warn_time)) + { + /* + * if we haven't heard from a server in a while, + * warn opers that something could be wrong... + * + * we'll do this about every 30 seconds until + * the server either becomes responsive or + * pings out. whichever comes first. + */ + client_p->flags |= FLAGS_PINGWARN; + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "Warning: No response from %s for %ld seconds", + client_p->name, + (rb_current_time() - client_p->localClient->lasttime - ping)); + ilog(L_SERVER, + "Warning: No response from %s for %ld seconds", + log_client_name(client_p, HIDE_IP), + (rb_current_time() - client_p->localClient->lasttime - ping)); + } } /* ping_timeout: */ @@ -1364,7 +1385,7 @@ exit_generic_client(struct Client *client_p, struct Client *source_p, struct Cli } /* Clean up allow lists */ - del_all_accepts(source_p); + del_all_accepts(source_p, true); whowas_add_history(source_p, 0); whowas_off_history(source_p); @@ -1772,19 +1793,19 @@ count_remote_client_memory(size_t * count, size_t * remote_client_memory_used) /* * del_all_accepts * - * inputs - pointer to exiting client + * inputs - pointer to exiting client, flag to include own allow_list * output - NONE * side effects - Walk through given clients allow_list and on_allow_list * remove all references to this client */ void -del_all_accepts(struct Client *client_p) +del_all_accepts(struct Client *client_p, bool self_too) { rb_dlink_node *ptr; rb_dlink_node *next_ptr; struct Client *target_p; - if(MyClient(client_p) && client_p->localClient->allow_list.head) + if(self_too && MyClient(client_p) && client_p->localClient->allow_list.head) { /* clear this clients accept list, and remove them from * everyones on_accept_list @@ -1792,6 +1813,7 @@ del_all_accepts(struct Client *client_p) RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client_p->localClient->allow_list.head) { target_p = ptr->data; + rb_dlinkFindDestroy(client_p, &target_p->on_allow_list); rb_dlinkDestroy(ptr, &client_p->localClient->allow_list); } @@ -1801,6 +1823,13 @@ del_all_accepts(struct Client *client_p) RB_DLINK_FOREACH_SAFE(ptr, next_ptr, client_p->on_allow_list.head) { target_p = ptr->data; + + /* If we're not doing our own, we're doing this because of a nick change. + * Skip those that would see the nick change anyway + */ + if(!self_too && has_common_channel(client_p, target_p)) + continue; + rb_dlinkFindDestroy(client_p, &target_p->localClient->allow_list); rb_dlinkDestroy(ptr, &client_p->on_allow_list); } diff --git a/ircd/listener.c b/ircd/listener.c index 80947af85..02b6dabdc 100644 --- a/ircd/listener.c +++ b/ircd/listener.c @@ -581,13 +581,19 @@ accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, voi static time_t last_oper_notice = 0; int len; + static const char *allinuse = "ERROR :All connections in use\r\n"; static const char *toofast = "ERROR :Reconnecting too fast, throttled.\r\n"; - static const unsigned char sslerrcode[] = { + static const unsigned char ssldeniederrcode[] = { // SSLv3.0 Fatal Alert: Access Denied 0x15, 0x03, 0x00, 0x00, 0x02, 0x02, 0x31 }; + static const unsigned char sslinternalerrcode[] = { + // SSLv3.0 Fatal Alert: Internal Error + 0x15, 0x03, 0x00, 0x00, 0x02, 0x02, 0x50 + }; + if(listener->ssl && (!ircd_ssl_ok || !get_ssld_count())) { rb_close(F); @@ -608,7 +614,11 @@ accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, voi last_oper_notice = rb_current_time(); } - rb_write(F, "ERROR :All connections in use\r\n", 31); + if(listener->ssl) + rb_write(F, sslinternalerrcode, sizeof(sslinternalerrcode)); + else + rb_write(F, allinuse, strlen(allinuse)); + rb_close(F); return 0; } @@ -625,7 +635,7 @@ accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, voi if(listener->ssl) { - rb_write(F, sslerrcode, sizeof(sslerrcode)); + rb_write(F, ssldeniederrcode, sizeof(ssldeniederrcode)); } else if(ConfigFileEntry.dline_with_reason) { @@ -648,7 +658,7 @@ accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, voi return 0; } - if(check_reject(F, addr)) { + if(check_reject(F, addr, listener->ssl)) { /* Reject the connection without closing the socket * because it is now on the delay_exit list. */ return 0; @@ -656,7 +666,11 @@ accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, voi if(throttle_add(addr)) { - rb_write(F, toofast, strlen(toofast)); + if(listener->ssl) + rb_write(F, ssldeniederrcode, sizeof(ssldeniederrcode)); + else + rb_write(F, toofast, strlen(toofast)); + rb_close(F); return 0; } diff --git a/ircd/modules.c b/ircd/modules.c index bf010cb27..93e43acca 100644 --- a/ircd/modules.c +++ b/ircd/modules.c @@ -77,6 +77,9 @@ init_modules(void) exit(EXIT_FAILURE); } + memset(&module_list, 0, sizeof(module_list)); + memset(&mod_paths, 0, sizeof(mod_paths)); + /* Add the default paths we look in to the module system --nenolod */ mod_add_path(ircd_paths[IRCD_PATH_MODULES]); mod_add_path(ircd_paths[IRCD_PATH_AUTOLOAD_MODULES]); @@ -462,6 +465,10 @@ load_a_module(const char *path, bool warn, int origin, bool core) if((c = rb_strcasestr(mod_displayname, LT_MODULE_EXT)) != NULL) *c = '\0'; + /* Quietly succeed if the module is already loaded */ + if(findmodule_byname(mod_displayname) != NULL) + return true; + tmpptr = lt_dlopenext(path); if(tmpptr == NULL) diff --git a/ircd/newconf.c b/ircd/newconf.c index d5c072582..33bfd4d1a 100644 --- a/ircd/newconf.c +++ b/ircd/newconf.c @@ -2751,6 +2751,7 @@ static struct ConfEntry conf_general_table[] = { "pace_wait", CF_TIME, NULL, 0, &ConfigFileEntry.pace_wait }, { "pace_wait_simple", CF_TIME, NULL, 0, &ConfigFileEntry.pace_wait_simple }, { "ping_cookie", CF_YESNO, NULL, 0, &ConfigFileEntry.ping_cookie }, + { "ping_warn_time", CF_TIME, NULL, 0, &ConfigFileEntry.ping_warn_time }, { "reject_after_count", CF_INT, NULL, 0, &ConfigFileEntry.reject_after_count }, { "reject_ban_time", CF_TIME, NULL, 0, &ConfigFileEntry.reject_ban_time }, { "reject_duration", CF_TIME, NULL, 0, &ConfigFileEntry.reject_duration }, diff --git a/ircd/packet.c b/ircd/packet.c index ee8429556..f0350d5ea 100644 --- a/ircd/packet.c +++ b/ircd/packet.c @@ -272,6 +272,23 @@ read_packet(rb_fde_t * F, void *data) client_p->localClient->lasttime = rb_current_time(); client_p->flags &= ~FLAGS_PINGSENT; + if (client_p->flags & FLAGS_PINGWARN) + { + /* + * if we warned about this server being unresponsive + * before, let's let everyone know there's no need + * to panic + */ + client_p->flags &= ~FLAGS_PINGWARN; + sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, + "Received response from previously unresponsive link %s", + client_p->name); + ilog(L_SERVER, + "Received response from previously unresponsive link %s", + log_client_name(client_p, HIDE_IP)); + } + + /* * Before we even think of parsing what we just read, stick * it on the end of the receive queue and do it when its diff --git a/ircd/reject.c b/ircd/reject.c index 801af5ffc..767dc0873 100644 --- a/ircd/reject.c +++ b/ircd/reject.c @@ -58,6 +58,7 @@ typedef struct _delay_data rb_fde_t *F; struct ConfItem *aconf; const char *reason; + bool ssl; } delay_t; typedef struct _throttle @@ -92,28 +93,39 @@ reject_exit(void *unused) delay_t *ddata; static const char *errbuf = "ERROR :Closing Link: (*** Banned (cache))\r\n"; + static const unsigned char ssldeniederrcode[] = { + // SSLv3.0 Fatal Alert: Access Denied + 0x15, 0x03, 0x00, 0x00, 0x02, 0x02, 0x31 + }; + RB_DLINK_FOREACH_SAFE(ptr, ptr_next, delay_exit.head) { ddata = ptr->data; - *dynamic_reason = '\0'; - - if (ddata->aconf) + if (ddata->ssl) { - snprintf(dynamic_reason, sizeof dynamic_reason, form_str(ERR_YOUREBANNEDCREEP) "\r\n", - me.name, "*", get_user_ban_reason(ddata->aconf)); - rb_write(ddata->F, dynamic_reason, strlen(dynamic_reason)); - - deref_conf(ddata->aconf); + rb_write(ddata->F, ssldeniederrcode, sizeof(ssldeniederrcode)); } - else if (ddata->reason) + else { - snprintf(dynamic_reason, sizeof dynamic_reason, ":%s 465 %s :%s\r\n", - me.name, "*", ddata->reason); - rb_write(ddata->F, dynamic_reason, strlen(dynamic_reason)); + *dynamic_reason = '\0'; + + if (ddata->aconf) + snprintf(dynamic_reason, sizeof dynamic_reason, form_str(ERR_YOUREBANNEDCREEP) "\r\n", + me.name, "*", get_user_ban_reason(ddata->aconf)); + else if (ddata->reason) + snprintf(dynamic_reason, sizeof dynamic_reason, ":%s 465 %s :%s\r\n", + me.name, "*", ddata->reason); + + if (*dynamic_reason) + rb_write(ddata->F, dynamic_reason, strlen(dynamic_reason)); + + rb_write(ddata->F, errbuf, strlen(errbuf)); } - rb_write(ddata->F, errbuf, strlen(errbuf)); + if (ddata->aconf) + deref_conf(ddata->aconf); + rb_close(ddata->F); rb_free(ddata); } @@ -228,7 +240,7 @@ add_reject(struct Client *client_p, const char *mask1, const char *mask2, struct } int -check_reject(rb_fde_t *F, struct sockaddr *addr) +check_reject(rb_fde_t *F, struct sockaddr *addr, bool ssl) { rb_patricia_node_t *pnode; reject_t *rdata; @@ -276,6 +288,7 @@ check_reject(rb_fde_t *F, struct sockaddr *addr) ddata->reason = NULL; } ddata->F = F; + ddata->ssl = ssl; rb_dlinkAdd(ddata, &ddata->node, &delay_exit); return 1; } diff --git a/ircd/s_conf.c b/ircd/s_conf.c index cb86811ff..dadaf7a41 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -642,16 +642,18 @@ attach_conf(struct Client *client_p, struct ConfItem *aconf) return (0); } -/* - * rehash - * - * Actual REHASH service routine. Called with sig == 0 if it has been called - * as a result of an operator issuing this command, else assume it has been - * called as a result of the server receiving a HUP signal. - */ -bool -rehash(bool sig) +struct rehash_data { + bool sig; +}; + +static void +service_rehash(void *data_) { + struct rehash_data *data = data_; + bool sig = data->sig; + + rb_free(data); + rb_dlink_node *n; hook_data_rehash hdata = { sig }; @@ -684,6 +686,21 @@ rehash(bool sig) privilegeset_cleanup_rehash(); call_hook(h_rehash, &hdata); +} + +/* + * rehash + * + * Called with sig == 0 if it has been called as a result of an operator + * issuing this command, else assume it has been called as a result of the + * server receiving a HUP signal. + */ +bool +rehash(bool sig) +{ + struct rehash_data *data = rb_malloc(sizeof *data); + data->sig = sig; + rb_defer(service_rehash, data); return false; } diff --git a/ircd/s_serv.c b/ircd/s_serv.c index dca66b2b8..92539389c 100644 --- a/ircd/s_serv.c +++ b/ircd/s_serv.c @@ -81,6 +81,7 @@ unsigned int CAP_ENCAP; unsigned int CAP_TS6; unsigned int CAP_SERVICE; unsigned int CAP_RSFNC; +unsigned int CAP_RSFNCF; unsigned int CAP_SAVE; unsigned int CAP_EUID; unsigned int CAP_EOPMOD; @@ -122,6 +123,7 @@ init_builtin_capabs(void) CAP_ENCAP = capability_put(serv_capindex, "ENCAP", NULL); CAP_SERVICE = capability_put(serv_capindex, "SERVICES", NULL); CAP_RSFNC = capability_put(serv_capindex, "RSFNC", NULL); + CAP_RSFNCF = capability_put(serv_capindex, "RSFNCF", NULL); CAP_SAVE = capability_put(serv_capindex, "SAVE", NULL); CAP_EUID = capability_put(serv_capindex, "EUID", NULL); CAP_EOPMOD = capability_put(serv_capindex, "EOPMOD", NULL); diff --git a/ircd/s_user.c b/ircd/s_user.c index 48acff5ba..94fee03c2 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -1725,7 +1725,7 @@ change_nick_user_host(struct Client *target_p, const char *nick, const char *use if(changed) { monitor_signon(target_p); - del_all_accepts(target_p); + del_all_accepts(target_p, false); } } diff --git a/librb/include/rb_commio.h b/librb/include/rb_commio.h index dde5ca9c7..79d88461c 100644 --- a/librb/include/rb_commio.h +++ b/librb/include/rb_commio.h @@ -159,6 +159,7 @@ int rb_ignore_errno(int); void rb_setselect(rb_fde_t *, unsigned int type, PF * handler, void *client_data); void rb_init_netio(void); int rb_select(unsigned long); +void rb_defer(void (*)(void *), void *); int rb_fd_ssl(rb_fde_t *F); int rb_get_fd(rb_fde_t *F); const char *rb_get_ssl_strerror(rb_fde_t *F); diff --git a/librb/src/commio.c b/librb/src/commio.c index 490706f9e..47a042c5e 100644 --- a/librb/src/commio.c +++ b/librb/src/commio.c @@ -51,6 +51,14 @@ static rb_bh *fd_heap; static rb_dlink_list timeout_list; static rb_dlink_list closed_list; +struct defer +{ + rb_dlink_node node; + void (*fn)(void *); + void *data; +}; +static rb_dlink_list defer_list; + static struct ev_entry *rb_timeout_ev; @@ -2015,10 +2023,27 @@ rb_setselect(rb_fde_t *F, unsigned int type, PF * handler, void *client_data) setselect_handler(F, type, handler, client_data); } +void +rb_defer(void (*fn)(void *), void *data) +{ + struct defer *defer = rb_malloc(sizeof *defer); + defer->fn = fn; + defer->data = data; + rb_dlinkAdd(defer, &defer->node, &defer_list); +} + int rb_select(unsigned long timeout) { int ret = select_handler(timeout); + rb_dlink_node *ptr, *next; + RB_DLINK_FOREACH_SAFE(ptr, next, defer_list.head) + { + struct defer *defer = ptr->data; + defer->fn(defer->data); + rb_dlinkDelete(ptr, &defer_list); + rb_free(defer); + } rb_close_pending_fds(); return ret; } diff --git a/librb/src/export-syms.txt b/librb/src/export-syms.txt index e6ec7af53..6d9a0b899 100644 --- a/librb/src/export-syms.txt +++ b/librb/src/export-syms.txt @@ -27,6 +27,7 @@ rb_ctime rb_current_time rb_current_time_tv rb_date +rb_defer rb_destroy_patricia rb_dictionary_add rb_dictionary_create diff --git a/modules/core/m_nick.c b/modules/core/m_nick.c index 890d0c2b3..dcdd48371 100644 --- a/modules/core/m_nick.c +++ b/modules/core/m_nick.c @@ -619,8 +619,6 @@ static void change_local_nick(struct Client *client_p, struct Client *source_p, char *nick, int dosend) { - struct Client *target_p; - rb_dlink_node *ptr, *next_ptr; struct Channel *chptr; char note[NICKLEN + 10]; int samenick; @@ -704,20 +702,7 @@ change_local_nick(struct Client *client_p, struct Client *source_p, /* Make sure everyone that has this client on its accept list * loses that reference. */ - /* we used to call del_all_accepts() here, but theres no real reason - * to clear a clients own list of accepted clients. So just remove - * them from everyone elses list --anfl - */ - RB_DLINK_FOREACH_SAFE(ptr, next_ptr, source_p->on_allow_list.head) - { - target_p = ptr->data; - - if (!has_common_channel(source_p, target_p)) - { - rb_dlinkFindDestroy(source_p, &target_p->localClient->allow_list); - rb_dlinkDestroy(ptr, &source_p->on_allow_list); - } - } + del_all_accepts(source_p, false); snprintf(note, sizeof(note), "Nick: %s", nick); rb_note(client_p->localClient->F, note); diff --git a/modules/m_challenge.c b/modules/m_challenge.c index ff390deee..6ea23e943 100644 --- a/modules/m_challenge.c +++ b/modules/m_challenge.c @@ -188,7 +188,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou if(oper_p == NULL) { sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST)); - ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)", + ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)", source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); @@ -206,7 +206,7 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou oper_up(source_p, oper_p); - ilog(L_OPERED, "OPER %s by %s!%s@%s (%s)", + ilog(L_OPERED, "CHALLENGE %s by %s!%s@%s (%s)", source_p->user->opername, source_p->name, source_p->username, source_p->host, source_p->sockhost); return; @@ -220,14 +220,14 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou if(oper_p == NULL) { sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST)); - ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)", + ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)", parv[1], source_p->name, source_p->username, source_p->host, source_p->sockhost); if(ConfigFileEntry.failed_oper_notice) sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, - "Failed CHALLENGE attempt - host mismatch by %s (%s@%s)", - source_p->name, source_p->username, source_p->host); + "Failed CHALLENGE attempt - user@host mismatch or no operator block for %s by %s (%s@%s)", + parv[1], source_p->name, source_p->username, source_p->host); return; } @@ -258,14 +258,14 @@ m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *sou if (source_p->certfp == NULL || rb_strcasecmp(source_p->certfp, oper_p->certfp)) { sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST)); - ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s) -- client certificate fingerprint mismatch", + ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s) -- client certificate fingerprint mismatch", parv[1], source_p->name, source_p->username, source_p->host, source_p->sockhost); if(ConfigFileEntry.failed_oper_notice) { sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, - "Failed OPER attempt - client certificate fingerprint mismatch by %s (%s@%s)", + "Failed CHALLENGE attempt - client certificate fingerprint mismatch by %s (%s@%s)", source_p->name, source_p->username, source_p->host); } return; diff --git a/modules/m_info.c b/modules/m_info.c index f6d03659b..6742bafb1 100644 --- a/modules/m_info.c +++ b/modules/m_info.c @@ -421,6 +421,11 @@ static struct InfoStruct info_table[] = { "Require ping cookies to connect", INFO_INTBOOL(&ConfigFileEntry.ping_cookie), }, + { + "ping_warn_time", + "Amount of time between warnings about unresponsive servers", + INFO_DECIMAL(&ConfigFileEntry.ping_warn_time), + }, { "reject_after_count", "Client rejection threshold setting", diff --git a/modules/m_oper.c b/modules/m_oper.c index 4c734f7c9..58fb9eaa1 100644 --- a/modules/m_oper.c +++ b/modules/m_oper.c @@ -107,8 +107,8 @@ m_oper(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p if(ConfigFileEntry.failed_oper_notice) { sendto_realops_snomask(SNO_GENERAL, L_NETWIDE, - "Failed OPER attempt - host mismatch by %s (%s@%s)", - source_p->name, source_p->username, source_p->host); + "Failed OPER attempt - user@host mismatch or no operator block for %s by %s (%s@%s)", + name, source_p->name, source_p->username, source_p->host); } return; diff --git a/modules/m_services.c b/modules/m_services.c index 768df637d..bf378600b 100644 --- a/modules/m_services.c +++ b/modules/m_services.c @@ -163,6 +163,13 @@ me_login(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source rb_strlcpy(source_p->user->suser, parv[1], sizeof(source_p->user->suser)); } +/* me_rsfnc() + * parv[1] = current user nickname + * parv[2] = target nickname + * parv[3] = new nickts + * parv[4] = current nickts + * parv[5] = optional; 0 (don't override RESVs) or 1 (override RESVs) + */ static void me_rsfnc(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) @@ -198,6 +205,12 @@ me_rsfnc(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source if(target_p->tsinfo != curts) return; + /* received a non-forced RSFNC for a nickname that is RESV. + * silently ignore it. ~jess + */ + if(parc > 5 && atoi(parv[5]) == 0 && find_nick_resv(parv[2])) + return; + if((exist_p = find_named_client(parv[2]))) { char buf[BUFSIZE]; @@ -267,7 +280,10 @@ me_rsfnc(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source monitor_signon(target_p); - del_all_accepts(target_p); + /* Make sure everyone that has this client on its accept list + * loses that reference. + */ + del_all_accepts(target_p, false); snprintf(note, sizeof(note), "Nick: %s", target_p->name); rb_note(target_p->localClient->F, note); diff --git a/modules/m_whois.c b/modules/m_whois.c index 7ed576434..bb37a5ba3 100644 --- a/modules/m_whois.c +++ b/modules/m_whois.c @@ -370,7 +370,7 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy) { rb_inet_ntop_sock((struct sockaddr *)&ip4, buf, sizeof buf); - sendto_one_numeric(source_p, RPL_WHOISTEXT, + sendto_one_numeric(source_p, RPL_WHOISSPECIAL, "%s :Underlying IPv4 is %s", target_p->name, buf); } @@ -386,15 +386,28 @@ single_whois(struct Client *source_p, struct Client *target_p, int operspy) sendto_one_numeric(source_p, RPL_WHOISIDLE, form_str(RPL_WHOISIDLE), target_p->name, - hdata_showidle.approved ? (long)(rb_current_time() - target_p->localClient->last) : 0, + hdata_showidle.approved != WHOIS_IDLE_HIDE ? (long)(rb_current_time() - target_p->localClient->last) : 0, (unsigned long)target_p->localClient->firsttime); - if (hdata_showidle.approved == WHOIS_IDLE_HIDE && (source_p->umodes & user_modes['I'])) - /* if the source has hidden their idle time, notify the source that they can't view others' idle times either */ - sendto_one_numeric(source_p, RPL_WHOISTEXT, form_str(RPL_WHOISTEXT), target_p->name, "has a hidden idle time because your own idle time is hidden"); - else if (hdata_showidle.approved != WHOIS_IDLE_SHOW) - /* if the target has hidden their idle time, notify the source */ - sendto_one_numeric(source_p, RPL_WHOISTEXT, form_str(RPL_WHOISTEXT), target_p->name, "is hiding their idle time"); + if (hdata_showidle.approved != WHOIS_IDLE_SHOW) + { + if (target_p->umodes & user_modes['I']) + { + if (hdata_showidle.approved == WHOIS_IDLE_HIDE) + /* if the target has hidden their idle time, notify the source */ + sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL), target_p->name, "is hiding their idle time"); + else + /* if the target has hidden their idle time, notify the source */ + sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL), target_p->name, "is hiding their idle time, but you have auspex"); + } + else if (hdata_showidle.approved == WHOIS_IDLE_HIDE) + /* if the source has hidden their idle time, notify the source that they can't view others' idle times either */ + sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL), target_p->name, "has a hidden idle time because your own idle time is hidden"); + else + /* client has auspex to be able to see idle time, but make sure they know that's why they're seeing it */ + sendto_one_numeric(source_p, RPL_WHOISSPECIAL, form_str(RPL_WHOISSPECIAL), target_p->name, + "has a hidden idle time because your own idle time is hidden, but you have auspex"); + } } else {