From 2eecf4f2da58798dee6962e49bff0c4f72df752c Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sat, 5 Oct 2019 12:27:49 +0200 Subject: [PATCH] Use generic numeric 531 (ERR_CANTSENDTOUSER) for all such cases and use hook CAN_SEND_TO_USER rather than HOOKTYPE_PRE_USERMSG (which is now removed). As for the numeric change: this makes it much easier for client devs. You rarely need to differentiate in the client code between the various causes. One only cares about detecting that the message was not sent and that the user needs to be informed. This replaces various NOTICEs, ERR_NOCTCP, ERR_NONONREG etc. with just the new numeric 531, which is taken from InspIRCd. The syntax is: :server 531 yourname targetname :reason for the block This makes it similar to numeric 404 (ERR_CANNOTSENDTOCHAN) that is used to indicate that a channel message was blocked. For module devs, the new hook CAN_SEND_TO_USER prototype is: int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); You can replace the text via this, by setting *text in your function. You can block the message, by returning HOOK_DENY. If doing so, then you must also set *errmsg to an appropriate value. Do not send any error message to the user! UnrealIRCd will take care of sending the error message for you, if you set *errmsg. Only if you need something special you could violate this rule, but preferably not! As you can see, CAN_SEND_TO_USER works just like CAN_SEND_TO_CHANNEL. --- doc/RELEASE-NOTES | 13 + include/h.h | 4 +- include/modules.h | 7 +- include/numeric.h | 1 + include/proto.h | 4 +- src/err.c | 2 +- src/modules/invite.c | 2 +- src/modules/message.c | 350 ++++++++++++++------------ src/modules/restrict-commands.c | 46 ++-- src/modules/tkl.c | 17 +- src/modules/usermodes/censor.c | 17 +- src/modules/usermodes/noctcp.c | 14 +- src/modules/usermodes/privdeaf.c | 14 +- src/modules/usermodes/regonlymsg.c | 15 +- src/modules/usermodes/secureonlymsg.c | 27 +- src/user.c | 8 +- 16 files changed, 297 insertions(+), 244 deletions(-) diff --git a/doc/RELEASE-NOTES b/doc/RELEASE-NOTES index fb2d79ba9..694fdcde7 100644 --- a/doc/RELEASE-NOTES +++ b/doc/RELEASE-NOTES @@ -360,3 +360,16 @@ Server protocol such as SID, NICKv2, TKLEXT2, etc. (more precise information will follow) In particular this means UnrealIRCd 3.2.x will not link with 5.x. + +Client protocol +---------------- +TODO: expand with other new things / changes +* When a message is blocked, for whatever reason, we now use a generic + numeric response: :server 531 yourname targetname :reason for the block + This replaces all the various NOTICEs, ERR_NOCTCP, ERR_NONONREG, etc. + with just one single numeric. + The only other numerics that you may still encounter when PM'ing are + ERR_NOSUCHNICK, ERR_TOOMANYTARGETS and ERR_TARGETTOOFAST, which are + generic errors to any command involving targets. And ERR_SERVICESDOWN. + Note that channel messages already had a generic numeric for signaling + blocked messages for a very long time, ERR_CANNOTSENDTOCHAN. diff --git a/include/h.h b/include/h.h index fde7262de..64b26c76e 100644 --- a/include/h.h +++ b/include/h.h @@ -284,7 +284,7 @@ extern void sendto_ops_and_log(FORMAT_STRING(const char *pattern), ...) __attrib extern MODVAR int writecalls, writeb[]; extern int deliver_it(Client *cptr, char *str, int len, int *want_read); -extern int check_for_target_limit(Client *client, void *target, const char *name); +extern int target_limit_exceeded(Client *client, void *target, const char *name); extern char *canonize(char *buffer); extern ConfigItem_deny_dcc *dcc_isforbidden(Client *client, char *filename); extern ConfigItem_deny_dcc *dcc_isdiscouraged(Client *client, char *filename); @@ -311,7 +311,7 @@ extern int umode_allow_all(Client *client, int what); extern int umode_allow_unset(Client *client, int what); extern int umode_allow_opers(Client *client, int what); extern int umode_allow_none(Client *client, int what); -extern int umode_delete(char ch, long val); +extern int umode_delete(char ch, long val); extern void build_umode_string(Client *client, long old, long sendmask, char *umode_buf); extern void send_umode_out(Client *client, int show_to_user, long old); diff --git a/include/modules.h b/include/modules.h index 0545bb380..353af2ae8 100644 --- a/include/modules.h +++ b/include/modules.h @@ -931,7 +931,6 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va #define HOOKTYPE_FREE_CLIENT 59 #define HOOKTYPE_FREE_USER 60 #define HOOKTYPE_PRE_CHANMSG 61 -#define HOOKTYPE_PRE_USERMSG 62 #define HOOKTYPE_KNOCK 63 #define HOOKTYPE_MODECHAR_ADD 64 #define HOOKTYPE_MODECHAR_DEL 65 @@ -973,6 +972,8 @@ extern void SavePersistentLongX(ModuleInfo *modinfo, char *varshortname, long va #define HOOKTYPE_PRE_LOCAL_QUIT_CHAN 102 #define HOOKTYPE_IDENT_LOOKUP 103 #define HOOKTYPE_CONFIGRUN_EX 104 +#define HOOKTYPE_CAN_SEND_TO_USER 105 +// FIXME: ^^ make this enums? or..? /* Adding a new hook here? * 1) Add the #define HOOKTYPE_.... with a new number @@ -1008,6 +1009,7 @@ int hooktype_remote_kick(Client *client, Client *victim, Channel *channel, Messa char *hooktype_pre_usermsg(Client *client, Client *to, char *text, int notice); int hooktype_usermsg(Client *client, Client *to, MessageTag *mtags, char *text, int notice); int hooktype_can_send_to_channel(Client *client, Channel *channel, Membership *member, char **text, char **errmsg, int notice); +int hooktype_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); int hooktype_pre_chanmsg(Client *client, Channel *channel, MessageTag *mtags, char *text, int notice); int hooktype_chanmsg(Client *client, Channel *channel, int sendflags, int prefix, char *target, MessageTag *mtags, char *text, int notice); char *hooktype_pre_local_topic(Client *client, Channel *channel, char *topic); @@ -1145,11 +1147,11 @@ _UNREAL_ERROR(_hook_error_incompatible, "Incompatible hook function. Check argum ((hooktype == HOOKTYPE_INVITE) && !ValidateHook(hooktype_invite, func)) || \ ((hooktype == HOOKTYPE_CAN_JOIN) && !ValidateHook(hooktype_can_join, func)) || \ ((hooktype == HOOKTYPE_CAN_SEND_TO_CHANNEL) && !ValidateHook(hooktype_can_send_to_channel, func)) || \ + ((hooktype == HOOKTYPE_CAN_SEND_TO_USER) && !ValidateHook(hooktype_can_send_to_user, func)) || \ ((hooktype == HOOKTYPE_CAN_KICK) && !ValidateHook(hooktype_can_kick, func)) || \ ((hooktype == HOOKTYPE_FREE_CLIENT) && !ValidateHook(hooktype_free_client, func)) || \ ((hooktype == HOOKTYPE_FREE_USER) && !ValidateHook(hooktype_free_user, func)) || \ ((hooktype == HOOKTYPE_PRE_CHANMSG) && !ValidateHook(hooktype_pre_chanmsg, func)) || \ - ((hooktype == HOOKTYPE_PRE_USERMSG) && !ValidateHook(hooktype_pre_usermsg, func)) || \ ((hooktype == HOOKTYPE_KNOCK) && !ValidateHook(hooktype_knock, func)) || \ ((hooktype == HOOKTYPE_MODECHAR_ADD) && !ValidateHook(hooktype_modechar_add, func)) || \ ((hooktype == HOOKTYPE_MODECHAR_DEL) && !ValidateHook(hooktype_modechar_del, func)) || \ @@ -1276,6 +1278,7 @@ enum EfunctionType { EFUNC_TKL_CHARTOTYPE, EFUNC_TKL_TYPE_STRING, EFUNC_CAN_SEND_TO_CHANNEL, + EFUNC_CAN_SEND_TO_USER, EFUNC_BROADCAST_MD_GLOBALVAR, EFUNC_BROADCAST_MD_GLOBALVAR_CMD, EFUNC_TKL_IP_HASH, diff --git a/include/numeric.h b/include/numeric.h index 40978313c..fce426860 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -148,6 +148,7 @@ #define ERR_OPERONLY 520 #define ERR_LISTSYNTAX 521 +#define ERR_CANTSENDTOUSER 531 /* * Numberic replies from server commands. * These are currently in the range 200-399. diff --git a/include/proto.h b/include/proto.h index c7c0ca07e..8d5ea7283 100644 --- a/include/proto.h +++ b/include/proto.h @@ -23,7 +23,7 @@ #ifndef proto_h #define proto_h /* channel.c */ -extern int sendmodeto_one(Client *cptr, char *from, char *name, char *mode, char *param, time_t creationtime); +extern int sendmodeto_one(Client *cptr, char *from, char *name, char *mode, char *param, time_t creationtime); /* lusers.c */ extern void init_irccounts(void); @@ -54,7 +54,7 @@ extern EVENT(save_tunefile); extern void read_motd(const char *filename, MOTDFile *motd); /* s_user.c */ -extern int check_for_target_limit(Client *client, void *target, const char *name); +extern int target_limit_exceeded(Client *client, void *target, const char *name); extern void make_umodestr(void); extern char *get_mode_str(Client *acptr); diff --git a/src/err.c b/src/err.c index aa857bacb..a47a92be4 100644 --- a/src/err.c +++ b/src/err.c @@ -570,7 +570,7 @@ static char *replies[] = { /* 528 */ NULL, /* 529 */ NULL, /* 530 */ NULL, -/* 531 */ NULL, +/* 531 ERR_CANTSENDTOUSER */ "%s :%s", /* 532 */ NULL, /* 533 */ NULL, /* 534 */ NULL, diff --git a/src/modules/invite.c b/src/modules/invite.c index a8c0ad1de..81adaf72c 100644 --- a/src/modules/invite.c +++ b/src/modules/invite.c @@ -162,7 +162,7 @@ CMD_FUNC(cmd_invite) if (MyConnect(client)) { - if (check_for_target_limit(client, target, target->name)) + if (target_limit_exceeded(client, target, target->name)) return; if (!ValidatePermissionsForPath("immune:invite-flood",client,NULL,NULL,NULL)) diff --git a/src/modules/message.c b/src/modules/message.c index 6080bc4dc..d4dd53c41 100644 --- a/src/modules/message.c +++ b/src/modules/message.c @@ -29,6 +29,7 @@ CMD_FUNC(cmd_private); CMD_FUNC(cmd_notice); void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], int notice); int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char **errmsg, int notice); +int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, int notice); /* Place includes here */ #define MSG_PRIVATE "PRIVMSG" /* PRIV */ @@ -73,8 +74,9 @@ MOD_UNLOAD() return MOD_SUCCESS; } -static int check_dcc(Client *client, char *target, Client *targetcli, char *text); -static int check_dcc_soft(Client *from, Client *to, char *text); +char *get_dcc_filename(const char *text); +static int can_dcc(Client *client, char *target, Client *targetcli, char *filename, char **errmsg); +static int can_dcc_soft(Client *from, Client *to, char *filename, char **errmsg); #define CANPRIVMSG_CONTINUE 100 #define CANPRIVMSG_SEND 101 @@ -84,63 +86,74 @@ static int check_dcc_soft(Client *from, Client *to, char *text); * notice: 1 if notice, 0 if privmsg * text: Pointer to a pointer to a text [in, out] * cmd: Pointer to a pointer which contains the command to use [in, out] - * - * RETURN VALUES: - * CANPRIVMSG_CONTINUE: issue a 'continue' in target nickname list (aka: skip further processing this target) - * CANPRIVMSG_SEND: send the message (use text/newcmd!) - * Other: return with this value (can be anything) */ -static int can_privmsg(Client *client, Client *target, int notice, char **text, char **cmd) +int can_send_to_user(Client *client, Client *target, char **msgtext, char **errmsg, int notice) { int ret; + Hook *h; + int n; + static char errbuf[256]; + + *errmsg = NULL; if (IsVirus(client)) { - sendnotice(client, "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN); - return CANPRIVMSG_CONTINUE; + ircsnprintf(errbuf, sizeof(errbuf), "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN); + *errmsg = errbuf; + return 0; } - if (MyUser(client) && !strncasecmp(*text, "\001DCC", 4)) + if (**msgtext == '\001') { - ret = check_dcc(client, target->name, target, *text); - if (IsDead(client)) - return 0; - if (ret == 0) - return CANPRIVMSG_CONTINUE; - } - if (MyUser(target) && !strncasecmp(*text, "\001DCC", 4) && - !check_dcc_soft(client, target, *text)) - return CANPRIVMSG_CONTINUE; - - if (MyUser(client) && check_for_target_limit(client, target, target->name)) - return CANPRIVMSG_CONTINUE; - - if (!is_silenced(client, target)) - { - Hook *tmphook; - - if (!notice && MyConnect(client) && - target->user && target->user->away) - sendnumeric(client, RPL_AWAY, target->name, - target->user->away); - - if (MyUser(client) && match_spamfilter(client, *text, (notice ? SPAMF_USERNOTICE : SPAMF_USERMSG), target->name, 0, NULL)) - return 0; - - for (tmphook = Hooks[HOOKTYPE_PRE_USERMSG]; tmphook; tmphook = tmphook->next) { - *text = (*(tmphook->func.pcharfunc))(client, target, *text, notice); - if (!*text) - break; + char *filename = get_dcc_filename(*msgtext); + if (filename) + { + if (MyUser(client) && !can_dcc(client, target->name, target, filename, errmsg)) + return 0; + if (MyUser(target) && !can_dcc_soft(client, target, filename, errmsg)) + return 0; } - if (!*text) - return CANPRIVMSG_CONTINUE; - - return CANPRIVMSG_SEND; - } else { - /* Silenced */ - RunHook3(HOOKTYPE_SILENCED, client, target, notice); } - return CANPRIVMSG_CONTINUE; + + if (MyUser(client) && target_limit_exceeded(client, target, target->name)) + { + /* target_limit_exceeded() is an exception, in the sense that + * it will send a different numeric. So we don't set errmsg. + */ + return 0; + } + + if (is_silenced(client, target)) + { + RunHook3(HOOKTYPE_SILENCED, client, target, notice); + /* Silently discarded, no error message */ + return 0; + } + + // Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric? + if (MyUser(client) && match_spamfilter(client, *msgtext, (notice ? SPAMF_USERNOTICE : SPAMF_USERMSG), target->name, 0, NULL)) + return 0; + + n = HOOK_CONTINUE; + for (h = Hooks[HOOKTYPE_CAN_SEND_TO_USER]; h; h = h->next) + { + n = (*(h->func.intfunc))(client, target, msgtext, errmsg, notice); + if (n == HOOK_DENY) + { + if (!*errmsg) + { + ircd_log(LOG_ERROR, "Module %s did not set errmsg!!!", h->owner->header->name); + abort(); + } + return 0; + } + } + + /* This may happen, if nothing is left to send anymore (don't send empty messages) */ + if (!*msgtext) + return 0; + + return 1; } /* @@ -158,7 +171,7 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], { Client *target; Channel *channel; - char *nick, *p, *p2, *pc, *text, *errmsg, *newcmd; + char *nick, *p, *p2, *pc, *text, *errmsg; int prefix = 0; char pfixchan[CHANNELLEN + 4]; int ret; @@ -297,13 +310,18 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], } } - if (MyUser(client) && (*parv[2] == 1)) + if (MyUser(client) && (*parv[2] == '\001')) { - ret = check_dcc(client, channel->chname, NULL, parv[2]); - if (IsDead(client)) - return; - if (ret == 0) + char *errmsg = NULL; + char *filename = get_dcc_filename(parv[2]); + if (filename && !can_dcc(client, channel->chname, NULL, filename, &errmsg)) + { + if (IsDead(client)) + return; + if (!IsDead(client) && !notice) + sendnumeric(client, ERR_CANNOTSENDTOCHAN, channel->chname, errmsg, p2); continue; + } } if (IsVirus(client) && strcasecmp(channel->chname, SPAMFILTER_VIRUSCHAN)) @@ -399,30 +417,37 @@ void cmd_message(Client *client, MessageTag *recv_mtags, int parc, char *parv[], target = hash_find_nickatserver(nick, NULL); if (target) { + char *errmsg = NULL; text = parv[2]; - newcmd = cmd; - ret = can_privmsg(client, target, notice, &text, &newcmd); - if (IsDead(client)) - return; - if (ret == CANPRIVMSG_SEND) + if (!can_send_to_user(client, target, &text, &errmsg, notice)) { + /* Message is discarded */ + if (IsDead(client)) + return; + if (!notice && errmsg) + sendnumeric(client, ERR_CANTSENDTOUSER, target->name, errmsg); + } else + { + /* We may send the message */ MessageTag *mtags = NULL; + + /* Inform sender that recipient is away, if this is so */ + if (!notice && MyConnect(client) && target->user && target->user->away) + sendnumeric(client, RPL_AWAY, target->name, target->user->away); + new_message(client, recv_mtags, &mtags); labeled_response_inhibit = 1; sendto_prefix_one(target, client, mtags, ":%s %s %s :%s", CHECKPROTO(target->direction, PROTO_SID) ? ID(client) : client->name, - newcmd, + cmd, (MyUser(target) ? target->name : nick), text); labeled_response_inhibit = 0; RunHook5(HOOKTYPE_USERMSG, client, target, mtags, text, notice); free_message_tags(mtags); continue; - } else - if (ret == CANPRIVMSG_CONTINUE) - continue; - else - return; + } + continue; /* Message has been delivered or rejected, continue with next target */ } /* If nick@server -and- the @server portion was set::services-server then send a special message */ @@ -468,9 +493,9 @@ CMD_FUNC(cmd_notice) */ char *dcc_displayfile(char *f) { -static char buf[512]; -char *i, *o = buf; -size_t n = strlen(f); + static char buf[512]; + char *i, *o = buf; + size_t n = strlen(f); if (n < 300) { @@ -500,80 +525,95 @@ size_t n = strlen(f); return buf; } +char *get_dcc_filename(const char *text) +{ + static char filename[BUFSIZE+1]; + char *end; + int size_string; + + if (*text != '\001') + return 0; + + if (!strncasecmp(text+1, "DCC SEND ", 9)) + text = text + 10; + else if (!strncasecmp(text+1, "DCC RESUME ", 11)) + text = text + 12; + else + return 0; + + for (; *text == ' '; text++); /* skip leading spaces */ + + if (*text == '"' && *(text+1)) + end = strchr(text+1, '"'); + else + end = strchr(text, ' '); + + if (!end || (end < text)) + return 0; + + size_string = (int)(end - text); + + if (!size_string || (size_string > (BUFSIZE - 1))) + return 0; + + strlcpy(filename, text, size_string+1); + return filename; +} + /** Checks if a DCC SEND is allowed. - * @param client Sending client + * @param client Sending client * @param target Target name (user or channel) * @param targetcli Target client (NULL in case of channel!) * @param text The entire message * @returns 1 if DCC SEND allowed, 0 if rejected */ -static int check_dcc(Client *client, char *target, Client *targetcli, char *text) +static int can_dcc(Client *client, char *target, Client *targetcli, char *filename, char **errmsg) { - char *ctcp; ConfigItem_deny_dcc *fl; - char *end, realfile[BUFSIZE]; + static char errbuf[256]; int size_string, ret; - if ((*text != 1) || ValidatePermissionsForPath("immune:dcc",client,targetcli,NULL,NULL) || (targetcli && ValidatePermissionsForPath("self:getbaddcc",targetcli,NULL,NULL,NULL))) + /* User (IRCOp) may bypass send restrictions */ + if (ValidatePermissionsForPath("immune:dcc",client,targetcli,NULL,NULL)) return 1; - ctcp = &text[1]; - /* Most likely a DCC send .. */ - if (!strncasecmp(ctcp, "DCC SEND ", 9)) - ctcp = text + 10; - else if (!strncasecmp(ctcp, "DCC RESUME ", 11)) - ctcp = text + 12; - else - return 1; /* something else, allow */ + /* User (IRCOp) likes to receive bad dcc's */ + if (targetcli && ValidatePermissionsForPath("self:getbaddcc",targetcli,NULL,NULL,NULL)) + return 1; + /* Check if user is already blocked (from the past) */ if (IsDCCBlock(client)) { - sendnotice(client, "*** You are blocked from sending files as you have tried to " - "send a forbidden file - reconnect to regain ability to send"); + *errmsg = "*** You are blocked from sending files as you have tried to " + "send a forbidden file - reconnect to regain ability to send"; return 0; } - for (; *ctcp == ' '; ctcp++); /* skip leading spaces */ - if (*ctcp == '"' && *(ctcp+1)) - end = strchr(ctcp+1, '"'); - else - end = strchr(ctcp, ' '); + if (match_spamfilter(client, filename, SPAMF_DCC, target, 0, NULL)) + return 0; - /* check if it was fake.. just pass it along then .. */ - if (!end || (end < ctcp)) - return 1; /* allow */ - - size_string = (int)(end - ctcp); - - if (!size_string || (size_string > (BUFSIZE - 1))) - return 1; /* allow */ - - strlcpy(realfile, ctcp, size_string+1); - - if (match_spamfilter(client, realfile, SPAMF_DCC, target, 0, NULL)) - return 0; /* deny */ - - if ((fl = dcc_isforbidden(client, realfile))) + if ((fl = dcc_isforbidden(client, filename))) { - char *displayfile = dcc_displayfile(realfile); - sendnumericfmt(client, - RPL_TEXT, "*** Cannot DCC SEND file %s to %s (%s)", displayfile, target, fl->reason); - sendnotice(client, "*** You have been blocked from sending files, reconnect to regain permission to send files"); + char *displayfile = dcc_displayfile(filename); + + RunHook5(HOOKTYPE_DCC_DENIED, client, target, filename, displayfile, fl); + + ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason); + *errmsg = errbuf; SetDCCBlock(client); - - RunHook5(HOOKTYPE_DCC_DENIED, client, target, realfile, displayfile, fl); - - return 0; /* block */ + return 0; } /* Channel dcc (???) and discouraged? just block */ - if (!targetcli && ((fl = dcc_isdiscouraged(client, realfile)))) + if (!targetcli && ((fl = dcc_isdiscouraged(client, filename)))) { - char *displayfile = dcc_displayfile(realfile); - sendnumericfmt(client, RPL_TEXT, "*** Cannot DCC SEND file %s to %s (%s)", displayfile, target, fl->reason); - return 0; /* block */ + ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason); + *errmsg = errbuf; + return 0; } - return 1; /* allowed */ + + /* If we get here, the file is allowed */ + return 1; } /** Checks if a DCC is allowed by DCCALLOW rules (only SOFT bans are checked). @@ -585,63 +625,46 @@ static int check_dcc(Client *client, char *target, Client *targetcli, char *text * 1: allowed * 0: block */ -static int check_dcc_soft(Client *from, Client *to, char *text) +static int can_dcc_soft(Client *from, Client *to, char *filename, char **errmsg) { - char *ctcp; ConfigItem_deny_dcc *fl; - char *end, realfile[BUFSIZE]; - int size_string; + char *displayfile; + static char errbuf[256]; - if ((*text != 1) || ValidatePermissionsForPath("immune:dcc",from,to,NULL,NULL)|| ValidatePermissionsForPath("self:getbaddcc",to,NULL,NULL,NULL)) + /* User (IRCOp) may bypass send restrictions */ + if (ValidatePermissionsForPath("immune:dcc",from,to,NULL,NULL)) return 1; - ctcp = &text[1]; - /* Most likely a DCC send .. */ - if (!strncasecmp(ctcp, "DCC SEND ", 9)) - ctcp = text + 10; - else - return 1; /* something else, allow */ + /* User (IRCOp) likes to receive bad dcc's */ + if (ValidatePermissionsForPath("self:getbaddcc",to,NULL,NULL,NULL)) + return 1; - if (*ctcp == '"' && *(ctcp+1)) - end = strchr(ctcp+1, '"'); - else - end = strchr(ctcp, ' '); + /* On the 'soft' blocklist ? */ + if (!(fl = dcc_isdiscouraged(from, filename))) + return 1; /* No, so is OK */ - /* check if it was fake.. just pass it along then .. */ - if (!end || (end < ctcp)) - return 1; /* allow */ + /* If on DCCALLOW list then the user is OK with it */ + if (on_dccallow_list(to, from)) + return 1; - size_string = (int)(end - ctcp); + /* Soft-blocked */ + displayfile = dcc_displayfile(filename); - if (!size_string || (size_string > (BUFSIZE - 1))) - return 1; /* allow */ + ircsnprintf(errbuf, sizeof(errbuf), "Cannot DCC SEND file: %s", fl->reason); + *errmsg = errbuf; - strlcpy(realfile, ctcp, size_string+1); - - if ((fl = dcc_isdiscouraged(from, realfile))) + /* Inform target ('to') about the /DCCALLOW functionality */ + sendnotice(to, "%s (%s@%s) tried to DCC SEND you a file named '%s', the request has been blocked.", + from->name, from->user->username, GetHost(from), displayfile); + if (!IsDCCNotice(to)) { - if (!on_dccallow_list(to, from)) - { - char *displayfile = dcc_displayfile(realfile); - sendnumericfmt(from, - RPL_TEXT, "*** Cannot DCC SEND file %s to %s (%s)", displayfile, to->name, fl->reason); - sendnotice(from, "User %s is currently not accepting DCC SENDs with such a filename/filetype from you. " - "Your file %s was not sent.", to->name, displayfile); - sendnotice(to, "%s (%s@%s) tried to DCC SEND you a file named '%s', the request has been blocked.", - from->name, from->user->username, GetHost(from), displayfile); - if (!IsDCCNotice(to)) - { - SetDCCNotice(to); - sendnotice(to, "Files like these might contain malicious content (viruses, trojans). " - "Therefore, you must explicitly allow anyone that tries to send you such files."); - sendnotice(to, "If you trust %s, and want him/her to send you this file, you may obtain " - "more information on using the dccallow system by typing '/DCCALLOW HELP'", from->name); - } - return 0; - } + SetDCCNotice(to); + sendnotice(to, "Files like these might contain malicious content (viruses, trojans). " + "Therefore, you must explicitly allow anyone that tries to send you such files."); + sendnotice(to, "If you trust %s, and want him/her to send you this file, you may obtain " + "more information on using the dccallow system by typing '/DCCALLOW HELP'", from->name); } - - return 1; /* allowed */ + return 0; } /* Taken from xchat by Peter Zelezny @@ -649,7 +672,8 @@ static int check_dcc_soft(Client *from, Client *to, char *text) * RGB color stripping support added -- codemastr */ -char *_StripColors(unsigned char *text) { +char *_StripColors(unsigned char *text) +{ int i = 0, len = strlen(text), save_len=0; char nc = 0, col = 0, rgb = 0, *save_text=NULL; static unsigned char new_str[4096]; @@ -906,13 +930,11 @@ int _can_send_to_channel(Client *client, Channel *channel, char **msgtext, char i = (*(h->func.intfunc))(client, channel, lp, msgtext, errmsg, notice); if (i != HOOK_CONTINUE) { -#ifdef DEBUGMODE if (!*errmsg) { ircd_log(LOG_ERROR, "Module %s did not set errmsg!!!", h->owner->header->name); abort(); } -#endif break; } } diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c index e2e89737a..a0537c7a8 100644 --- a/src/modules/restrict-commands.c +++ b/src/modules/restrict-commands.c @@ -52,8 +52,8 @@ RestrictedCommand *find_restrictions_byconftag(char *conftag); int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs); int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type); int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice); -char *rcmd_hook_preusermsg(Client *client, Client *to, char *text, int notice); -char *rcmd_hook_wrapper(Client *client, char *text, int notice, char *display, char *conftag); +int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); +int rcmd_block_message(Client *client, char *text, int notice, char **errmsg, char *display, char *conftag); CMD_OVERRIDE_FUNC(rcmd_override); // Globals @@ -82,7 +82,7 @@ MOD_INIT() // Due to the nature of PRIVMSG/NOTICE we're gonna need to hook into PRE_* stuff instead of using command overrides HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, -1000000, rcmd_can_send_to_channel); - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, -1000000, rcmd_hook_preusermsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, -1000000, rcmd_can_send_to_user); return MOD_SUCCESS; } @@ -294,7 +294,8 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type) return 1; } -int rcmd_canbypass(Client *client, RestrictedCommand *rcmd) { +int rcmd_canbypass(Client *client, RestrictedCommand *rcmd) +{ if (!client || !rcmd) return 1; if (rcmd->exempt_identified && IsLoggedIn(client)) @@ -308,45 +309,56 @@ int rcmd_canbypass(Client *client, RestrictedCommand *rcmd) { int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, int notice) { - if (!rcmd_hook_wrapper(client, *msg, notice, "channel", (notice ? "channel-notice" : "channel-message"))) + if (rcmd_block_message(client, *msg, notice, errmsg, "channel", (notice ? "channel-notice" : "channel-message"))) return HOOK_DENY; - // FIXME ^^^^ may not send notices from the wrapper, but will convert when preusermsg is done as well + return HOOK_CONTINUE; } -char *rcmd_hook_preusermsg(Client *client, Client *to, char *text, int notice) +int rcmd_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { // Need a few extra exceptions for user messages only =] - if ((client == to) || IsULine(to)) - return text; - return rcmd_hook_wrapper(client, text, notice, "user", (notice ? "private-notice" : "private-message")); + if ((client == target) || IsULine(target)) + return HOOK_CONTINUE; /* bypass/exempt */ + + if (rcmd_block_message(client, *text, notice, errmsg, "user", (notice ? "private-notice" : "private-message"))) + return HOOK_DENY; + + return HOOK_CONTINUE; } -char *rcmd_hook_wrapper(Client *client, char *text, int notice, char *display, char *conftag) +int rcmd_block_message(Client *client, char *text, int notice, char **errmsg, char *display, char *conftag) { RestrictedCommand *rcmd; + static char errbuf[256]; // Let's allow non-local users, opers and U:Lines early =] if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client)) - return text; + return 0; rcmd = find_restrictions_byconftag(conftag); if (rcmd) { if (rcmd->disable) { - sendnotice(client, "Sending of %ss to %ss been disabled by the network administrators", (notice ? "notice" : "message"), display); - return NULL; + ircsnprintf(errbuf, sizeof(errbuf), + "Sending of %ss to %ss been disabled by the network administrators", + (notice ? "notice" : "message"), display); + *errmsg = errbuf; + return 1; } if (!rcmd_canbypass(client, rcmd)) { - sendnotice(client, "You cannot send %ss to %ss until you've been connected for %ld seconds or more", (notice ? "notice" : "message"), display, rcmd->connect_delay); - return NULL; + ircsnprintf(errbuf, sizeof(errbuf), + "You cannot send %ss to %ss until you've been connected for %ld seconds or more", + (notice ? "notice" : "message"), display, rcmd->connect_delay); + *errmsg = errbuf; + return 1; } } // No restrictions apply, process command as normal =] - return text; + return 0; } CMD_OVERRIDE_FUNC(rcmd_override) diff --git a/src/modules/tkl.c b/src/modules/tkl.c index 3c29589b7..7ca894abe 100644 --- a/src/modules/tkl.c +++ b/src/modules/tkl.c @@ -4385,16 +4385,27 @@ int _match_spamfilter(Client *client, char *str_in, int target, char *destinatio { case SPAMF_USERMSG: case SPAMF_USERNOTICE: - sendnotice(client, "Message to %s blocked: %s", destination, reason); + { + char errmsg[512]; + ircsnprintf(errmsg, sizeof(errmsg), "Message blocked: %s", reason); + sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg); break; - case SPAMF_CHANMSG: + } case SPAMF_CHANNOTICE: + break; /* no replies to notices */ + case SPAMF_CHANMSG: + { sendto_one(client, NULL, ":%s 404 %s %s :Message blocked: %s", me.name, client->name, destination, reason); break; + } case SPAMF_DCC: - sendnotice(client, "DCC to %s blocked: %s", destination, reason); + { + char errmsg[512]; + ircsnprintf(errmsg, sizeof(errmsg), "DCC blocked: %s", reason); + sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg); break; + } case SPAMF_AWAY: /* hack to deal with 'after-away-was-set-filters' */ if (client->user->away && !strcmp(str_in, client->user->away)) diff --git a/src/modules/usermodes/censor.c b/src/modules/usermodes/censor.c index dba0f3742..68452abeb 100644 --- a/src/modules/usermodes/censor.c +++ b/src/modules/usermodes/censor.c @@ -20,7 +20,7 @@ long UMODE_CENSOR = 0L; #define IsCensored(x) (x->umodes & UMODE_CENSOR) -char *censor_pre_usermsg(Client *client, Client *target, char *text, int notice); +int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); int censor_config_test(ConfigFile *, ConfigEntry *, int, int *); int censor_config_run(ConfigFile *, ConfigEntry *, int); @@ -46,7 +46,7 @@ MOD_INIT() UmodeAdd(modinfo->handle, 'G', UMODE_GLOBAL, 0, NULL, &UMODE_CENSOR); - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, censor_pre_usermsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, censor_can_send_to_user); HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, censor_config_run); return MOD_SUCCESS; @@ -237,22 +237,21 @@ char *stripbadwords_message(char *str, int *blocked) return stripbadwords(str, conf_badword_message, blocked); } -char *censor_pre_usermsg(Client *client, Client *target, char *text, int notice) +int censor_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { -int blocked; + int blocked = 0; if (MyUser(client) && IsCensored(target)) { - text = stripbadwords_message(text, &blocked); + *text = stripbadwords_message(*text, &blocked); if (blocked) { - if (!notice) - sendnumeric(client, ERR_NOSWEAR, target->name); - return NULL; + *errmsg = "User does not accept private messages containing swearing"; + return HOOK_DENY; } } - return text; + return HOOK_CONTINUE; } // TODO: when stats is modular, make it call this for badwords diff --git a/src/modules/usermodes/noctcp.c b/src/modules/usermodes/noctcp.c index 0874aaa29..92498091a 100644 --- a/src/modules/usermodes/noctcp.c +++ b/src/modules/usermodes/noctcp.c @@ -34,7 +34,7 @@ long UMODE_NOCTCP = 0L; #define IsNoCTCP(client) (client->umodes & UMODE_NOCTCP) -char *noctcp_preusermsg(Client *client, Client *target, char *text, int notice); +int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); MOD_TEST() { @@ -47,7 +47,7 @@ CmodeInfo req; UmodeAdd(modinfo->handle, 'T', UMODE_GLOBAL, 0, NULL, &UMODE_NOCTCP); - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, noctcp_preusermsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, noctcp_can_send_to_user); MARK_AS_OFFICIAL_MODULE(modinfo); return MOD_SUCCESS; @@ -74,12 +74,12 @@ static int IsACTCP(char *s) return 0; } -char *noctcp_preusermsg(Client *client, Client *target, char *text, int notice) +int noctcp_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { - if (MyUser(client) && !notice && IsNoCTCP(target) && !IsOper(client) && IsACTCP(text)) + if (MyUser(client) && !notice && IsNoCTCP(target) && !IsOper(client) && IsACTCP(*text)) { - sendnumeric(client, ERR_NOCTCP, target->name); - return NULL; + *errmsg = "User does not accept CTCPs"; + return HOOK_DENY; } - return text; + return HOOK_CONTINUE; } diff --git a/src/modules/usermodes/privdeaf.c b/src/modules/usermodes/privdeaf.c index 625b098f8..a788c0053 100644 --- a/src/modules/usermodes/privdeaf.c +++ b/src/modules/usermodes/privdeaf.c @@ -17,7 +17,7 @@ ModuleHeader MOD_HEADER static long UMODE_PRIVDEAF = 0; static Umode *UmodePrivdeaf = NULL; -char *privdeaf_checkmsg(Client *, Client *, char *, int); +int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); MOD_INIT() { @@ -32,7 +32,7 @@ MOD_INIT() return MOD_FAILED; } - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, privdeaf_checkmsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, privdeaf_can_send_to_user); return MOD_SUCCESS; } @@ -47,13 +47,13 @@ MOD_UNLOAD() return MOD_SUCCESS; } -char *privdeaf_checkmsg(Client *client, Client *target, char *text, int notice) +int privdeaf_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { if ((target->umodes & UMODE_PRIVDEAF) && !IsOper(client) && !IsULine(client) && !IsServer(client) && (client != target)) { - sendnotice(client, "Message to '%s' not delivered: User does not accept private messages", target->name); - return NULL; - } else - return text; + *errmsg = "User does not accept private messages"; + return HOOK_DENY; + } + return HOOK_CONTINUE; } diff --git a/src/modules/usermodes/regonlymsg.c b/src/modules/usermodes/regonlymsg.c index 7109b55da..bc9cc7847 100644 --- a/src/modules/usermodes/regonlymsg.c +++ b/src/modules/usermodes/regonlymsg.c @@ -35,13 +35,13 @@ ModuleHeader MOD_HEADER long UMODE_REGONLYMSG = 0L; /* Forward declarations */ -char *regonlymsg_pre_usermsg(Client *client, Client *target, char *text, int notice); +int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); MOD_INIT() { UmodeAdd(modinfo->handle, 'R', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_REGONLYMSG); - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, regonlymsg_pre_usermsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, regonlymsg_can_send_to_user); MARK_AS_OFFICIAL_MODULE(modinfo); return MOD_SUCCESS; @@ -57,17 +57,16 @@ MOD_UNLOAD() return MOD_SUCCESS; } -char *regonlymsg_pre_usermsg(Client *client, Client *target, char *text, int notice) +int regonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { if (IsRegOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsLoggedIn(client)) { if (ValidatePermissionsForPath("client:override:message:regonlymsg",client,target,NULL,text)) - return text; /* TODO: this is actually an override */ + return HOOK_CONTINUE; /* bypass this restriction */ - sendnumeric(client, ERR_NONONREG, target->name); - - return NULL; /* Block the message */ + *errmsg = "You must identify to a registered nick to private message this user"; + return HOOK_DENY; } - return text; + return HOOK_CONTINUE; } diff --git a/src/modules/usermodes/secureonlymsg.c b/src/modules/usermodes/secureonlymsg.c index 0bf7a1d43..ecc818368 100644 --- a/src/modules/usermodes/secureonlymsg.c +++ b/src/modules/usermodes/secureonlymsg.c @@ -36,13 +36,13 @@ ModuleHeader MOD_HEADER long UMODE_SECUREONLYMSG = 0L; /* Forward declarations */ -char *secureonlymsg_pre_usermsg(Client *client, Client *target, char *text, int notice); +int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice); MOD_INIT() { UmodeAdd(modinfo->handle, 'Z', UMODE_GLOBAL, 0, umode_allow_all, &UMODE_SECUREONLYMSG); - HookAddPChar(modinfo->handle, HOOKTYPE_PRE_USERMSG, 0, secureonlymsg_pre_usermsg); + HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, secureonlymsg_can_send_to_user); MARK_AS_OFFICIAL_MODULE(modinfo); return MOD_SUCCESS; @@ -58,36 +58,29 @@ MOD_UNLOAD() return MOD_SUCCESS; } -char *secureonlymsg_pre_usermsg(Client *client, Client *target, char *text, int notice) +int secureonlymsg_can_send_to_user(Client *client, Client *target, char **text, char **errmsg, int notice) { if (IsSecureOnlyMsg(target) && !IsServer(client) && !IsULine(client) && !IsSecureConnect(client)) { if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text)) - return text; /* TODO: this is actually an override */ + return HOOK_CONTINUE; /* bypass this restriction */ - /* A numeric is preferred to indicate the user cannot message. - * 492 is ERR_NOCTCP but apparently is also used by some other ircd(s) as a - * general "cannot send message" numeric (similar to the generic - * ERR_CANNOTSENDTOCHAN for channel messaging). - */ - sendto_one(client, NULL, ":%s 492 %s :Cannot send to user %s (You must be connected via SSL/TLS to message this user)", - me.name, client->name, target->name); - - return NULL; /* Block the message */ + *errmsg = "You must be connected via SSL/TLS to message this user"; + return HOOK_DENY; } else if (IsSecureOnlyMsg(client) && !IsSecureConnect(target) && !IsULine(target)) { if (ValidatePermissionsForPath("client:override:message:secureonlymsg",client,target,NULL,text)) - return text; /* TODO: this is actually an override */ + return HOOK_CONTINUE; /* bypass this restriction */ /* Similar to above but in this case we are +Z and are trying to message * an SSL user (who does not have +Z set, note the 'else'). This does not * make sense since they could never message back to us. Better block the * message than leave the user confused. */ - sendto_one(client, NULL, ":%s 492 %s :Cannot send to user %s (You have user mode +Z set but are not connected via SSL/TLS)", - me.name, client->name, target->name); + *errmsg = "Recipient is not connected via SSL/TLS and you are +Z"; + return HOOK_DENY; } - return text; + return HOOK_CONTINUE; } diff --git a/src/user.c b/src/user.c index 2bb38cc76..be417f1cf 100644 --- a/src/user.c +++ b/src/user.c @@ -173,19 +173,20 @@ unsigned char hash_target(void *target) return (unsigned char)((v >> 17) ^ (v >> 9)); } -/** check_for_target_limit +/** target_limit_exceeded * @param client The client. * @param target The target client * @param name The name of the target client (used in the error message) * @retval Returns 1 if too many targets were addressed (do not send!), 0 if ok to send. */ -int check_for_target_limit(Client *client, void *target, const char *name) +int target_limit_exceeded(Client *client, void *target, const char *name) { u_char hash = hash_target(target); int i; if (ValidatePermissionsForPath("immune:target-limit",client,NULL,NULL,NULL)) return 0; + if (client->local->targets[0] == hash) return 0; @@ -206,8 +207,7 @@ int check_for_target_limit(Client *client, void *target, const char *name) client->local->nexttarget += 2; /* punish them some more */ client->local->since += 2; /* lag them up as well */ - sendnumeric(client, ERR_TARGETTOOFAST, - name, client->local->nexttarget - TStime()); + sendnumeric(client, ERR_TARGETTOOFAST, name, client->local->nexttarget - TStime()); return 1; }