diff --git a/doc/technical/protoctl.txt b/doc/technical/protoctl.txt index 3209a2f27..ae6e30ffa 100644 --- a/doc/technical/protoctl.txt +++ b/doc/technical/protoctl.txt @@ -98,6 +98,10 @@ SJ3 Notifies the server that the SJOIN command with SJ3 syntax is TKLEXT This allows 10 instead of 8 parameters in TKL's for spamfilter, see s_kline.c function m_tkl for more info on this (added in 3.2RC2). +TKLEXT2 This allows 11 instead of 8 or 10 parameters in TKL's for spamfilter, + see s_kline.c function m_tkl for more info on this (added in 3.4-alpha3). + Note that TKLEXT2 implies TKLEXT support as well. + NICKIP This token indicates that a (standard) base64 encoded IP address is included in the NICK command. The IP is in binary network byte order formated and encoded using the standard base64 algorithm. '*' is used if no IP is available. diff --git a/help.conf b/help.conf index 4534e165d..c6e150f3b 100644 --- a/help.conf +++ b/help.conf @@ -1349,7 +1349,11 @@ help Spamfilter { " This command adds/removes global spam filters."; " Spamfilters can be used to get rid of spam, advertising, bots, etc."; " -"; - " Use: /spamfilter [add|del|remove|+|-] [type] [action] [tkltime] [reason] [regex]"; + " Use: /spamfilter [add|del|remove|+|-] [method] [type] [action] [tkltime] [reason] [string]"; + " [method] Matching method, must be one of:"; + " -regex (PCRE fast regex),"; + " -posix (old 3.2.x POSIX regex), or"; + " -simple (fastest but only supports ? and * wildcards)"; " [type] specifies the target type, you can specify multiple targets:"; " 'c' channel msg, 'p' private msg, 'n' private notice,"; " 'N' channel notice, 'P' part msg, 'q' quit msg, 'd' dcc,"; @@ -1359,22 +1363,22 @@ help Spamfilter { " 'kline', 'gline', 'zline', 'gzline', 'block' (blocks the msg),"; " 'dccblock' (unable to send any dccs), 'viruschan' (part all channels"; " and join the virus help chan), 'warn' (warn for IRC Operators)."; - " [regex] this is the actual regex where we should block on"; + " [string] this is the actual string that should be blocked (regex or simple pattern)"; " [tkltime] the duration of the *LINEs placed by action (use '-' to use the default"; " set::spamfilter::ban-time, this value is ignored for block/tempshun');"; " [reason] the reason for the *LINE or blockmsg, CANNOT CONTAIN SPACES,"; " '_' will be translated to spaces. Again, if you use '-' for this"; " the default (set::spamfilter::ban-reason) is used."; " - "; - " A few examples (note they will probably linewrap!):"; - " /spamfilter add p block - - Come watch me on my webcam"; - " /spamfilter add p block - Possible_virus_detected,_join_#help Come watch me on my webcam"; - " /spamfilter add p tempshun - - You_are_infected me\.mpg"; - " /spamfilter add p gline - - Come watch me on my webcam"; - " /spamfilter add p gline 3h Please_go_to_www.viruscan.xx/nicepage/virus=blah Come watch me on my webcam"; - " /spamfilter add p kill - Please_go_to_www.viruscan.xx/nicepage/virus=blah Come watch me on my webcam"; - " /spamfilter del p block - - Come watch me on my webcam*"; - " /spamfilter add cN gzline 1d No_advertising_please come to irc\..+\..+"; + " A few examples (note they will probably line-wrap!):"; + " /spamfilter add -simple p block - - Come watch me on my webcam"; + " /spamfilter add -simple p block - Possible_virus_detected,_join_#help Come watch me on my webcam"; + " /spamfilter add -simple p tempshun - - You_are_infected me.mpg"; + " /spamfilter add -simple p gline - - Come watch me on my webcam"; + " /spamfilter add -simple p gline 3h Please_go_to_www.viruscan.xx/nicepage/virus=blah Come watch me on my webcam"; + " /spamfilter add -simple p kill - Please_go_to_www.viruscan.xx/nicepage/virus=blah Come watch me on my webcam"; + " /spamfilter del -simple p block - - Come watch me on my webcam*"; + " /spamfilter add -regex cN gzline 1d No_advertising_please /come to irc\..+\..+/"; }; help Tempshun { diff --git a/include/common.h b/include/common.h index 6d4dc8cab..bfbc17db7 100644 --- a/include/common.h +++ b/include/common.h @@ -239,6 +239,8 @@ static char *StsMalloc(size_t size, char *file, long line) #define ircstrdup(x,y) do { if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y); } while(0) #define ircfree(x) do { if (x) MyFree(x); x = NULL; } while(0) +#define safefree ircfree +#define safestrdup ircstrdup extern struct SLink *find_user_link( /* struct SLink *, struct Client * */ ); @@ -279,6 +281,7 @@ extern struct SLink *find_user_link( /* struct SLink *, struct Client * */ ); " VL" \ " SJ3" \ " TKLEXT" \ + " TKLEXT2" \ " NICKIP" \ " ESVID" diff --git a/include/config.h b/include/config.h index 374a6295f..a6006054b 100644 --- a/include/config.h +++ b/include/config.h @@ -467,6 +467,9 @@ #define SPAMFILTER_DETECTSLOW #endif +/* Use TRE Regex Library (as well) ? */ +#define USE_TRE + /* The 3.4-alpha* series, especially the first few, are highly experimental. * If EXPERIMENTAL is #define'd then all users will receive a notice about * this when they connect, along with a pointer to bugs.unrealircd.org where diff --git a/include/h.h b/include/h.h index 23218452c..f6950ee83 100644 --- a/include/h.h +++ b/include/h.h @@ -517,7 +517,7 @@ extern void count_memory(aClient *cptr, char *nick); extern void list_scache(aClient *sptr); extern char *oflagstr(long oflag); extern int rehash(aClient *cptr, aClient *sptr, int sig); -extern int _match(char *mask, char *name); +extern int _match(const char *mask, const char *name); extern void outofmemory(void); extern int add_listener2(ConfigItem_listen *conf); extern void link_cleanup(ConfigItem_link *link_ptr); @@ -659,7 +659,7 @@ extern MODVAR int (*register_user)(aClient *cptr, aClient *sptr, char *nick, cha extern MODVAR int (*tkl_hash)(unsigned int c); extern MODVAR char (*tkl_typetochar)(int type); extern MODVAR aTKline *(*tkl_add_line)(int type, char *usermask, char *hostmask, char *reason, char *setby, - TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason); + TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason, MatchType match_type); extern MODVAR aTKline *(*tkl_del_line)(aTKline *tkl); extern MODVAR void (*tkl_check_local_remove_shun)(aTKline *tmp); extern MODVAR aTKline *(*tkl_expire)(aTKline * tmp); @@ -751,3 +751,8 @@ extern void send_moddata_members(aClient *srv); extern int ssl_used_in_config_but_unavail(void); extern void config_report_ssl_error(void); extern int dead_link(aClient *to, char *notice); +extern aMatch *unreal_create_match(MatchType type, char *str, char **error); +extern void unreal_delete_match(aMatch *m); +extern int unreal_match(aMatch *m, char *str); +extern int unreal_match_method_strtoval(char *str); +extern char *unreal_match_method_valtostr(int val); diff --git a/include/struct.h b/include/struct.h index 9d9b1ba65..34e945f9a 100644 --- a/include/struct.h +++ b/include/struct.h @@ -316,6 +316,7 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define PROTO_NICKv2 0x0008 /* Negotiated NICKv2 protocol */ #define PROTO_SJOIN2 0x0010 /* Negotiated SJOIN2 protocol */ #define PROTO_UMODE2 0x0020 /* Negotiated UMODE2 protocol */ +#define PROTO_TKLEXT2 0x0040 /* TKL extension 2: 11 parameters instead of 8 or 10 */ #define PROTO_INVITENOTIFY 0x0080 /* client supports invite-notify */ #define PROTO_VL 0x0100 /* Negotiated VL protocol */ #define PROTO_SJ3 0x0200 /* Negotiated SJ3 protocol */ @@ -465,6 +466,7 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define SupportSJ3(x) (CHECKPROTO(x, PROTO_SJ3)) #define SupportVHP(x) (CHECKPROTO(x, PROTO_VHP)) #define SupportTKLEXT(x) (CHECKPROTO(x, PROTO_TKLEXT)) +#define SupportTKLEXT2(x) (CHECKPROTO(x, PROTO_TKLEXT2)) #define SupportNAMESX(x) (CHECKPROTO(x, PROTO_NAMESX)) #define SupportCLK(x) (CHECKPROTO(x, PROTO_CLK)) #define SupportUHNAMES(x) (CHECKPROTO(x, PROTO_UHNAMES)) @@ -479,6 +481,7 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define SetSJ3(x) ((x)->proto |= PROTO_SJ3) #define SetVHP(x) ((x)->proto |= PROTO_VHP) #define SetTKLEXT(x) ((x)->proto |= PROTO_TKLEXT) +#define SetTKLEXT2(x) ((x)->proto |= PROTO_TKLEXT2) #define SetNAMESX(x) ((x)->proto |= PROTO_NAMESX) #define SetCLK(x) ((x)->proto |= PROTO_CLK) #define SetUHNAMES(x) ((x)->proto |= PROTO_UHNAMES) @@ -491,6 +494,8 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define ClearVL(x) ((x)->proto &= ~PROTO_VL) #define ClearVHP(x) ((x)->proto &= ~PROTO_VHP) #define ClearSJ3(x) ((x)->proto &= ~PROTO_SJ3) +#define ClearTKLEXT(x) ((x)->proto &= ~PROTO_TKLEXT) +#define ClearTKLEXT2(x) ((x)->proto &= ~PROTO_TKLEXT2) /* * defined operator access levels @@ -724,6 +729,27 @@ struct aloopStruct { int rehash_save_sig; }; +/** Matching types for aMatch.type */ +typedef enum { + MATCH_SIMPLE=1, /**< Simple pattern with * and ? */ + MATCH_PCRE_REGEX=2, /**< PCRE2 Perl-like regex (new) */ +#ifdef USE_TRE + MATCH_TRE_REGEX=3, /**< TRE POSIX regex (old, unreal3.2.x) */ +#endif +} MatchType; + +/** Match struct, which allows various matching styles, see MATCH_* */ +typedef struct _match { + char *str; /**< Text of the glob/regex/whatever. Always set. */ + MatchType type; + union { +// pcre2_code *pcre_expr; /**< PCRE2 Perl-like Regex (New) */ +#ifdef USE_TRE + regex_t *tre_expr; /**< TRE POSIX Regex (Old) */ +#endif + } ext; +} aMatch; + typedef struct Whowas { int hashv; char *name; @@ -845,7 +871,7 @@ struct Server { struct _spamfilter { unsigned short action; /* see BAN_ACT* */ - regex_t expr; + aMatch *expr; char *tkl_reason; /* spamfilter reason field [escaped by unreal_encodespace()!] */ TS tkl_duration; }; @@ -1359,7 +1385,7 @@ struct _configitem_alias_format { char *nick; AliasType type; char *format, *parameters; - regex_t expr; + aMatch *expr; }; /** diff --git a/src/match.c b/src/match.c index 680d4bd3a..14224561e 100644 --- a/src/match.c +++ b/src/match.c @@ -21,6 +21,7 @@ #include "struct.h" #include "common.h" #include "sys.h" +#include "h.h" ID_Copyright("(C) 1990 Jarkko Oikarinen"); @@ -427,3 +428,119 @@ int match(const char *mask, const char *name) { } return match2(mask,name); } + +/** Free up all resources of an aMatch entry (including the struct itself). + * NOTE: this function may (also) be called for aMatch structs that have only been + * setup half-way, so use special care when accessing members (NULL checks!) + */ +void unreal_delete_match(aMatch *m) +{ + safefree(m->str); +#ifdef USE_TRE + if (m->type == MATCH_TRE_REGEX) + { + if (m->ext.tre_expr) + regfree(m->ext.tre_expr); + } +#endif + // TODO: PCRE2 !! + MyFree(m); +} + +aMatch *unreal_create_match(MatchType type, char *str, char **error) +{ + aMatch *m = MyMallocEx(sizeof(aMatch)); + static char errorbuf[512]; + + m->str = strdup(str); + m->type = type; + + if (m->type == MATCH_SIMPLE) + { + /* Nothing to do */ + } + else if (m->type == MATCH_PCRE_REGEX) + { + /* TODO */ + } +#ifdef USE_TRE + else if (m->type == MATCH_TRE_REGEX) + { + int errorcode; + + m->ext.tre_expr = MyMallocEx(sizeof(regex_t)); + errorcode = regcomp(m->ext.tre_expr, str, REG_ICASE|REG_EXTENDED|REG_NOSUB); + if (errorcode > 0) + { + int errorbufsize = 512; + char *errtmp = MyMallocEx(errorbufsize); + regerror(errorcode, m->ext.tre_expr, errtmp, errorbufsize); + strlcpy(errorbuf, errtmp, sizeof(errorbuf)); + MyFree(errtmp); + if (error) + *error = errorbuf; + goto fail; + } + } +#endif + else { + abort(); /* unknown type, how did that happen ? */ + } + return m; + +fail: + unreal_delete_match(m); + return NULL; +} + +/** Try to match an aMatch entry ('m') against a string ('str'). + * @returns 1 if matched, 0 if not. + * @notes These (more logical) return values are opposite to the match() function. + */ +int unreal_match(aMatch *m, char *str) +{ + if (m->type == MATCH_SIMPLE) + { + if (_match(m->str, str) == 0) + return 1; + return 0; + } + + if (m->type == MATCH_PCRE_REGEX) + { + // todo + return 0; + } + +#ifdef USE_TRE + if (m->type == MATCH_TRE_REGEX) + { + if (regexec(m->ext.tre_expr, str, 0, NULL, 0) == 0) + return 1; + return 0; + } +#endif +} + +int unreal_match_method_strtoval(char *str) +{ + if (!strcmp(str, "regex") || !strcmp(str, "pcre")) + return MATCH_PCRE_REGEX; + if (!strcmp(str, "posix") || !strcmp(str, "tre")) + return MATCH_TRE_REGEX; + if (!strcmp(str, "simple") || !strcmp(str, "glob")) + return MATCH_SIMPLE; + return 0; +} + +char *unreal_match_method_valtostr(int val) +{ + if (val == MATCH_PCRE_REGEX) + return "regex"; + if (val == MATCH_TRE_REGEX) + return "posix"; + if (val == MATCH_SIMPLE) + return "simple"; + + return "unknown"; +} diff --git a/src/modules.c b/src/modules.c index 4ad04131c..71e7b5581 100644 --- a/src/modules.c +++ b/src/modules.c @@ -91,7 +91,7 @@ int (*register_user)(aClient *cptr, aClient *sptr, char *nick, char *username, c int (*tkl_hash)(unsigned int c); char (*tkl_typetochar)(int type); aTKline *(*tkl_add_line)(int type, char *usermask, char *hostmask, char *reason, char *setby, - TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason); + TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason, MatchType match_type); aTKline *(*tkl_del_line)(aTKline *tkl); void (*tkl_check_local_remove_shun)(aTKline *tmp); aTKline *(*tkl_expire)(aTKline * tmp); diff --git a/src/modules/m_protoctl.c b/src/modules/m_protoctl.c index c7c9d6186..7439c390e 100644 --- a/src/modules/m_protoctl.c +++ b/src/modules/m_protoctl.c @@ -267,6 +267,12 @@ CMD_FUNC(m_protoctl) Debug((DEBUG_ERROR, "Chose protocol %s for link %s", proto, cptr->name)); SetTKLEXT(cptr); } + else if (strcmp(s, "TKLEXT2") == 0) + { + Debug((DEBUG_ERROR, "Chose protocol %s for link %s", proto, cptr->name)); + SetTKLEXT2(cptr); + SetTKLEXT(cptr); /* TKLEXT is implied as well. always. */ + } else if (strcmp(s, "NICKIP") == 0) { Debug((DEBUG_ERROR, "Chose protocol %s for link %s", proto, cptr->name)); diff --git a/src/modules/m_tkl.c b/src/modules/m_tkl.c index 8868866dd..ca5b5685f 100644 --- a/src/modules/m_tkl.c +++ b/src/modules/m_tkl.c @@ -76,7 +76,7 @@ DLLFUNC int m_spamfilter(aClient *cptr, aClient *sptr, int parc, char *parv[]); int _tkl_hash(unsigned int c); char _tkl_typetochar(int type); aTKline *_tkl_add_line(int type, char *usermask, char *hostmask, char *reason, char *setby, - TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason); + TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason, MatchType match_type); aTKline *_tkl_del_line(aTKline *tkl); static void _tkl_check_local_remove_shun(aTKline *tmp); aTKline *_tkl_expire(aTKline * tmp); @@ -160,7 +160,7 @@ DLLFUNC int MOD_INIT(m_tkl)(ModuleInfo *modinfo) CommandAdd(modinfo->handle, MSG_ZLINE, m_tzline, 3, M_OPER); CommandAdd(modinfo->handle, MSG_KLINE, m_tkline, 3, M_OPER); CommandAdd(modinfo->handle, MSG_GZLINE, m_gzline, 3, M_OPER); - CommandAdd(modinfo->handle, MSG_SPAMFILTER, m_spamfilter, 6, M_OPER); + CommandAdd(modinfo->handle, MSG_SPAMFILTER, m_spamfilter, 7, M_OPER); CommandAdd(modinfo->handle, MSG_TKL, _m_tkl, MAXPARA, M_OPER|M_SERVER); MARK_AS_OFFICIAL_MODULE(modinfo); return MOD_SUCCESS; @@ -480,16 +480,17 @@ DLLFUNC int m_tkl_line(aClient *cptr, aClient *sptr, int parc, char *parv[], ch char *mask = NULL; char mo[1024], mo2[1024]; char *p, *usermask, *hostmask; - char *tkllayer[9] = { - me.name, /*0 server.name */ - NULL, /*1 +|- */ - NULL, /*2 G */ - NULL, /*3 user */ - NULL, /*4 host */ - NULL, /*5 setby */ - "0", /*6 expire_at */ - NULL, /*7 set_at */ - "no reason" /*8 reason */ + char *tkllayer[10] = { + me.name, /*0 server.name */ + NULL, /*1 +|- */ + NULL, /*2 G */ + NULL, /*3 user */ + NULL, /*4 host */ + NULL, /*5 setby */ + "0", /*6 expire_at */ + NULL, /*7 set_at */ + "no reason", /*8 reason */ + NULL }; struct tm *t; @@ -713,17 +714,19 @@ DLLFUNC int m_tkl_line(aClient *cptr, aClient *sptr, int parc, char *parv[], ch int spamfilter_usage(aClient *sptr) { - sendnotice(sptr, "Use: /spamfilter [add|del|remove|+|-] [type] [action] [tkltime] [tklreason] [regex]"); + sendnotice(sptr, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex|-posix] [type] [action] [tkltime] [tklreason] [regex]"); sendnotice(sptr, "See '/helpop ?spamfilter' for more information."); return 0; } +/** /spamfilter [add|del|remove|+|-] [match-type] [type] [action] [tkltime] [reason] [regex] + * 1 2 3 4 5 6 7 + */ DLLFUNC int m_spamfilter(aClient *cptr, aClient *sptr, int parc, char *parv[]) { int whattodo = 0; /* 0 = add 1 = del */ char mo[32], mo2[32]; -char *p; -char *tkllayer[11] = { +char *tkllayer[13] = { me.name, /* 0 server.name */ NULL, /* 1 +|- */ "F", /* 2 F */ @@ -734,12 +737,17 @@ char *tkllayer[11] = { "0", /* 7 set_at */ "", /* 8 tkl time */ "", /* 9 tkl reason */ - "" /* 10 regex */ + "", /* 10 match method */ + "", /* 11 regex */ + NULL }; int targets = 0, action = 0; char targetbuf[64], actionbuf[2]; char reason[512]; int n; +aMatch *m; +int match_type = 0; +char *err = NULL; if (IsServer(sptr)) return 0; @@ -759,15 +767,20 @@ int n; sptr->name, sptr->user->username, GetHost(sptr)); return 0; } - if ((parc < 7) || BadPtr(parv[4])) + + if ((parc == 7) && (*parv[2] != '-')) + goto new_spamfilter_syntax; + + if ((parc < 8) || BadPtr(parv[7])) return spamfilter_usage(sptr); /* parv[1]: [add|del|+|-] - * parv[2]: type - * parv[3]: action - * parv[4]: tkl time - * parv[5]: tkl reason (or block reason..) - * parv[6]: regex + * parv[2]: match-type + * parv[3]: type + * parv[4]: action + * parv[5]: tkl time + * parv[6]: tkl reason (or block reason..) + * parv[7]: regex */ if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+")) whattodo = 0; @@ -779,50 +792,66 @@ int n; return spamfilter_usage(sptr); } - targets = spamfilter_gettargets(parv[2], sptr); + match_type = unreal_match_method_strtoval(parv[2]+1); + if (!match_type) + { +new_spamfilter_syntax: + sendnotice(sptr, "Unknown match-type '%s'. Must be one of: -regex (new fast PCRE regexes), " + "-posix (old unreal 3.2.x posix regexes) or " + "-simple (simple text with ? and * wildcards)", + parv[2]); + if (*parv[2] != '-') + sendnotice(sptr, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-posix/-simple field!!"); + + return spamfilter_usage(cptr); + } + + targets = spamfilter_gettargets(parv[3], sptr); if (!targets) return spamfilter_usage(sptr); strlcpy(targetbuf, spamfilter_target_inttostring(targets), sizeof(targetbuf)); - action = banact_stringtoval(parv[3]); + action = banact_stringtoval(parv[4]); if (!action) { - sendto_one(sptr, ":%s NOTICE %s :Invalid 'action' field (%s)", me.name, sptr->name, parv[3]); + sendto_one(sptr, ":%s NOTICE %s :Invalid 'action' field (%s)", me.name, sptr->name, parv[4]); return spamfilter_usage(sptr); } actionbuf[0] = banact_valtochar(action); actionbuf[1] = '\0'; - /* now check the regex... */ - p = unreal_checkregex(parv[6],0,1); - if (p) + /* now check the regex / match field... */ + m = unreal_create_match(match_type, reason, &err); + if (!m) { sendto_one(sptr, ":%s NOTICE %s :Error in regex '%s': %s", - me.name, sptr->name, parv[6], p); + me.name, sptr->name, parv[7], err); return 0; } + unreal_delete_match(m); tkllayer[1] = whattodo ? "-" : "+"; tkllayer[3] = targetbuf; tkllayer[4] = actionbuf; tkllayer[5] = make_nick_user_host(sptr->name, sptr->user->username, GetHost(sptr)); - if (parv[4][0] == '-') + if (parv[5][0] == '-') { ircsnprintf(mo, sizeof(mo), "%li", SPAMFILTER_BAN_TIME); tkllayer[8] = mo; } else - tkllayer[8] = parv[4]; + tkllayer[8] = parv[5]; - if (parv[5][0] == '-') + if (parv[6][0] == '-') strlcpy(reason, unreal_encodespace(SPAMFILTER_BAN_REASON), sizeof(reason)); else - strlcpy(reason, parv[5], sizeof(reason)); + strlcpy(reason, parv[6], sizeof(reason)); tkllayer[9] = reason; - tkllayer[10] = parv[6]; + tkllayer[10] = parv[2]+1; /* +1 to skip the '-' */ + tkllayer[11] = parv[7]; /* SPAMFILTER LENGTH CHECK. * We try to limit it here so '/stats f' output shows ok, output of that is: @@ -832,7 +861,7 @@ int n; * We also do >500 instead of >510, since that looks cleaner ;).. so actually we count * on 50 characters for the rest... -- Syzop */ - n = strlen(reason) + strlen(parv[6]) + strlen(tkllayer[5]) + (NICKLEN * 2) + 40; + n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40; if ((n > 500) && (whattodo == 0)) { sendnotice(sptr, "Sorry, spamfilter too long. You'll either have to trim down the " @@ -846,7 +875,7 @@ int n; tkllayer[7] = mo2; } - m_tkl(&me, &me, 11, tkllayer); + m_tkl(&me, &me, 12, tkllayer); return 0; } @@ -921,10 +950,11 @@ char _tkl_typetochar(int type) */ aTKline *_tkl_add_line(int type, char *usermask, char *hostmask, char *reason, char *setby, - TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason) + TS expire_at, TS set_at, TS spamf_tkl_duration, char *spamf_tkl_reason, MatchType match_type) { aTKline *nl; int index; + aMatch *m; /* Pre-allocate etc check for spamfilters that fail to compile. * This could happen if for example TRE supports a feature on server X, but not @@ -932,11 +962,12 @@ aTKline *_tkl_add_line(int type, char *usermask, char *hostmask, char *reason, c */ if (type & TKL_SPAMF) { - char *myerr = unreal_checkregex(reason, 0, 0); - if (myerr) + char *err = NULL; + m = unreal_create_match(match_type, reason, &err); + if (!m) { sendto_realops("[TKL ERROR] ERROR: Spamfilter was added but did not compile. ERROR='%s', Spamfilter='%s'", - myerr, reason); + err, reason); return NULL; } } @@ -957,7 +988,8 @@ aTKline *_tkl_add_line(int type, char *usermask, char *hostmask, char *reason, c { /* Need to set some additional flags like 'targets' and 'action'.. */ nl->subtype = spamfilter_gettargets(usermask, NULL); - nl->ptr.spamf = unreal_buildspamfilter(reason); + nl->ptr.spamf = MyMallocEx(sizeof(Spamfilter)); + nl->ptr.spamf->expr = m; nl->ptr.spamf->action = banact_chartoval(*hostmask); nl->expire_at = 0; /* temporary spamfilters are NOT supported! (makes no sense) */ if (!spamf_tkl_reason) @@ -1005,7 +1037,7 @@ aTKline *_tkl_del_line(aTKline *tkl) MyFree(p->setby); if (p->type & TKL_SPAMF && p->ptr.spamf) { - regfree(&p->ptr.spamf->expr); + unreal_delete_match(p->ptr.spamf->expr); if (p->ptr.spamf->tkl_reason) MyFree(p->ptr.spamf->tkl_reason); MyFree(p->ptr.spamf); @@ -1435,7 +1467,7 @@ aClient *acptr; if (MyClient(acptr)) { spamfilter_build_user_string(spamfilter_user, acptr->name, acptr); - if (regexec(&tk->ptr.spamf->expr, spamfilter_user, 0, NULL, 0)) + if (!unreal_match(tk->ptr.spamf->expr, spamfilter_user)) continue; /* No match */ /* matched! */ @@ -1465,7 +1497,7 @@ aClient *acptr; if (IsPerson(acptr)) { spamfilter_build_user_string(spamfilter_user, acptr->name, acptr); - if (regexec(&tk->ptr.spamf->expr, spamfilter_user, 0, NULL, 0)) + if (!unreal_match(tk->ptr.spamf->expr, spamfilter_user)) continue; /* No match */ /* matched! */ @@ -1783,6 +1815,7 @@ void _tkl_stats(aClient *cptr, int type, char *para) sendto_one(cptr, rpl_str(RPL_STATSSPAMF), me.name, cptr->name, (tk->type & TKL_GLOBAL) ? 'F' : 'f', + unreal_match_method_valtostr(tk->ptr.spamf->expr->type), spamfilter_target_inttostring(tk->subtype), banact_valtostring(tk->ptr.spamf->action), (tk->expire_at != 0) ? (tk->expire_at - curtime) : 0, @@ -1850,21 +1883,23 @@ void _tkl_synch(aClient *sptr) * This routine is used both internally by the ircd (to * for example add local klines, zlines, etc) and over the * network (glines, gzlines, spamfilter, etc). - * add: remove: spamfilter: spamfilter+TKLEXT sqline: - * parv[ 1]: + - +/- + +/- - * parv[ 2]: type type type type type - * parv[ 3]: user user target target hold - * parv[ 4]: host host action action host - * parv[ 5]: setby removedby (un)setby setby setby - * parv[ 6]: expire_at expire_at (0) expire_at (0) expire_at - * parv[ 7]: set_at set_at set_at set_at - * parv[ 8]: reason regex tkl duration reason - * parv[ 9]: tkl reason [A] - * parv[10]: regex + * add: remove: spamfilter: spamfilter+TKLEXT spamfilter+TKLEXT2 sqline: + * parv[ 1]: + - +/- + + +/- + * parv[ 2]: type type type type type type + * parv[ 3]: user user target target target hold + * parv[ 4]: host host action action action host + * parv[ 5]: setby removedby (un)setby setby setby setby + * parv[ 6]: expire_at expire_at (0) expire_at (0) expire_at (0) expire_at + * parv[ 7]: set_at set_at set_at set_at set_at + * parv[ 8]: reason regex tkl duration tkl duration reason + * parv[ 9]: tkl reason [A] tkl reason [A] + * parv[10]: regex match-type [B] + * parv[11]: match-string [C] * * [A] tkl reason field must be escaped by caller [eg: use unreal_encodespace() * if m_tkl is called internally]. - * + * [B] match-type must be one of: regex, simple, posix. + * [C] Could be a regex or a regular string with wildcards, depending on [B] */ int _m_tkl(aClient *cptr, aClient *sptr, int parc, char *parv[]) { @@ -1874,6 +1909,7 @@ int _m_tkl(aClient *cptr, aClient *sptr, int parc, char *parv[]) char gmt[256], gmt2[256]; char txt[256]; TS expiry_1, setat_1, spamf_tklduration = 0; + MatchType spamf_match_method = MATCH_TRE_REGEX; /* (if unspecified, default to this) */ char *reason = NULL, *timeret; if (!IsServer(sptr) && !IsOper(sptr) && !IsMe(sptr)) @@ -1935,7 +1971,27 @@ int _m_tkl(aClient *cptr, aClient *sptr, int parc, char *parv[]) found = 0; if ((type & TKL_SPAMF) && (parc >= 11)) { - reason = parv[10]; + if (parc >= 12) + { + reason = parv[11]; + spamf_match_method = unreal_match_method_strtoval(parv[10]); + if (spamf_match_method == 0) + { + sendto_realops("Ignoring spamfilter from %s with unknown match type '%s'", + sptr->name, parv[10]); + return 0; + } + } else { + reason = parv[10]; +#ifdef USE_TRE + spamf_match_method = MATCH_TRE_REGEX; +#else + sendto_realops("Ignoring spamfilter from %s. Spamfilter is of type 'posix' (TRE) and this " + "build was compiled without TRE support. Suggestion: upgrade the other server", + sptr->name); + return 0; +#endif + } spamf_tklduration = config_checkval(parv[8], CFG_TIME); /* was: atol(parv[8]); */ } for (tk = tklines[tkl_hash(parv[2][0])]; tk; tk = tk->next) @@ -2030,10 +2086,10 @@ int _m_tkl(aClient *cptr, aClient *sptr, int parc, char *parv[]) /* there is something fucked here? */ if ((type & TKL_SPAMF) && (parc >= 11)) tk = tkl_add_line(type, parv[3], parv[4], reason, parv[5], - expiry_1, setat_1, spamf_tklduration, parv[9]); + expiry_1, setat_1, spamf_tklduration, parv[9], spamf_match_method); else tk = tkl_add_line(type, parv[3], parv[4], reason, parv[5], - expiry_1, setat_1, 0, NULL); + expiry_1, setat_1, 0, NULL, 0); if (!tk) return 0; /* ERROR on allocate or something else... */ @@ -2137,6 +2193,22 @@ int _m_tkl(aClient *cptr, aClient *sptr, int parc, char *parv[]) if (type & TKL_GLOBAL) { + if ((parc == 12) && (type & TKL_SPAMF)) + { + /* Oooooh.. so many flavours ! */ + sendto_server(cptr, PROTO_TKLEXT2, 0, + ":%s TKL %s %s %s %s %s %s %s %s %s %s :%s", sptr->name, + parv[1], parv[2], parv[3], parv[4], parv[5], + parv[6], parv[7], parv[8], parv[9], parv[10], parv[11]); + sendto_server(cptr, PROTO_TKLEXT, PROTO_TKLEXT2, + ":%s TKL %s %s %s %s %s %s %s %s %s :%s", sptr->name, + parv[1], parv[2], parv[3], parv[4], parv[5], + parv[6], parv[7], parv[8], parv[9], parv[11]); /* parv[11] = regex */ + sendto_server(cptr, 0, PROTO_TKLEXT, + ":%s TKL %s %s %s %s %s %s %s :%s", sptr->name, + parv[1], parv[2], parv[3], parv[4], parv[5], + parv[6], parv[7], parv[10]); + } else if ((parc == 11) && (type & TKL_SPAMF)) { sendto_server(cptr, PROTO_TKLEXT, 0, @@ -2542,7 +2614,7 @@ long ms_past; getrusage(RUSAGE_SELF, &rprev); #endif - ret = regexec(&tk->ptr.spamf->expr, str, 0, NULL, 0); + ret = unreal_match(tk->ptr.spamf->expr, str); #ifdef SPAMFILTER_DETECTSLOW getrusage(RUSAGE_SELF, &rnow); @@ -2564,7 +2636,7 @@ long ms_past; } #endif - if (ret == 0) + if (ret) { /* We have a match! */ char buf[1024]; diff --git a/src/s_conf.c b/src/s_conf.c index 60cf75b36..92b8a6af9 100644 --- a/src/s_conf.c +++ b/src/s_conf.c @@ -2140,7 +2140,7 @@ void config_rehash() ircfree(fmt->format); ircfree(fmt->nick); ircfree(fmt->parameters); - regfree(&fmt->expr); + unreal_delete_match(fmt->expr); DelListItem(fmt, alias_ptr->format); MyFree(fmt); } @@ -5634,10 +5634,11 @@ int _conf_spamfilter(ConfigFile *conf, ConfigEntry *ce) char *word = NULL, *reason = NULL, *bantime = NULL; int action = 0, target = 0; char has_reason = 0, has_bantime = 0; + int match_type = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { - if (!strcmp(cep->ce_varname, "regex")) + if (!strcmp(cep->ce_varname, "match")) { nl->reason = strdup(cep->ce_vardata); @@ -5668,6 +5669,10 @@ int _conf_spamfilter(ConfigFile *conf, ConfigEntry *ce) has_bantime = 1; bantime = cep->ce_vardata; } + else if (!strcmp(cep->ce_varname, "match-type")) + { + match_type = unreal_match_method_strtoval(cep->ce_vardata); + } } nl->type = TKL_SPAMF; nl->expire_at = 0; @@ -5677,7 +5682,8 @@ int _conf_spamfilter(ConfigFile *conf, ConfigEntry *ce) nl->subtype = target; nl->setby = BadPtr(me.name) ? NULL : strdup(me.name); /* Hmm! */ - nl->ptr.spamf = unreal_buildspamfilter(word); + nl->ptr.spamf = MyMallocEx(sizeof(Spamfilter)); + nl->ptr.spamf->expr = unreal_create_match(match_type, word, NULL); nl->ptr.spamf->action = action; if (has_reason && reason) @@ -5698,8 +5704,9 @@ int _test_spamfilter(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp; int errors = 0; - char *regex = NULL, *reason = NULL; - char has_target = 0, has_regex = 0, has_action = 0, has_reason = 0, has_bantime = 0; + char *match = NULL, *reason = NULL; + char has_target = 0, has_match = 0, has_action = 0, has_reason = 0, has_bantime = 0, has_match_type = 0; + int match_type = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { @@ -5775,26 +5782,16 @@ int _test_spamfilter(ConfigFile *conf, ConfigEntry *ce) has_reason = 1; reason = cep->ce_vardata; } - else if (!strcmp(cep->ce_varname, "regex")) + else if (!strcmp(cep->ce_varname, "match")) { - char *errbuf; - if (has_regex) + if (has_match) { config_warn_duplicate(cep->ce_fileptr->cf_filename, - cep->ce_varlinenum, "spamfilter::regex"); + cep->ce_varlinenum, "spamfilter::match"); continue; } - has_regex = 1; - if ((errbuf = unreal_checkregex(cep->ce_vardata,0,0))) - { - config_error("%s:%i: spamfilter::regex contains an invalid regex: %s", - cep->ce_fileptr->cf_filename, - cep->ce_varlinenum, - errbuf); - errors++; - continue; - } - regex = cep->ce_vardata; + has_match = 1; + match = cep->ce_vardata; } else if (!strcmp(cep->ce_varname, "action")) { @@ -5822,6 +5819,26 @@ int _test_spamfilter(ConfigFile *conf, ConfigEntry *ce) } has_bantime = 1; } + else if (!strcmp(cep->ce_varname, "match-type")) + { + if (has_match_type) + { + config_warn_duplicate(cep->ce_fileptr->cf_filename, + cep->ce_varlinenum, "spamfilter::match-type"); + continue; + } + match_type = unreal_match_method_strtoval(cep->ce_vardata); + if (match_type == 0) + { + config_error("%s:%i: spamfilter::match-type: unknown match type '%s', " + "should be one of: 'simple', 'regex' or 'posix'", + cep->ce_fileptr->cf_filename, cep->ce_varlinenum, + cep->ce_vardata); + errors++; + continue; + } + has_match_type = 1; + } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, @@ -5831,10 +5848,26 @@ int _test_spamfilter(ConfigFile *conf, ConfigEntry *ce) } } - if (!has_regex) + if (match && match_type) + { + aMatch *m; + char *err; + + m = unreal_create_match(match_type, match, &err); + if (!m) + { + config_error("%s:%i: spamfilter::match contains an invalid regex: %s", + cep->ce_fileptr->cf_filename, + cep->ce_varlinenum, + err); + errors++; + } + } + + if (!has_match) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, - "spamfilter::regex"); + "spamfilter::match"); errors++; } if (!has_target) @@ -5849,13 +5882,25 @@ int _test_spamfilter(ConfigFile *conf, ConfigEntry *ce) "spamfilter::action"); errors++; } - if (regex && reason && (strlen(regex) + strlen(reason) > 505)) + if (match && reason && (strlen(match) + strlen(reason) > 505)) { - config_error("%s:%i: spamfilter block problem: regex + reason field are together over 505 bytes, " + config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, " "please choose a shorter regex or reason", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } + if (!has_match_type) + { + config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, + "spamfilter::match-type"); + errors++; + } + + if (!has_match_type && !has_match && has_action && has_target) + { + config_error("Upgrading from 3.2.x to 3.4.x? Your spamfilter { } blocks need to be converted. " + "See https://www.unrealircd.org/docs/Upgrading_from_3.2.x#Spamfilter"); + } return errors; } @@ -8392,7 +8437,9 @@ int _conf_alias(ConfigFile *conf, ConfigEntry *ce) if (!strcmp(cep->ce_varname, "format")) { format = MyMallocEx(sizeof(ConfigItem_alias_format)); ircstrdup(format->format, cep->ce_vardata); - regcomp(&format->expr, cep->ce_vardata, REG_ICASE|REG_EXTENDED); + format->expr = unreal_create_match(MATCH_PCRE_REGEX, cep->ce_vardata, NULL); + if (!format->expr) + abort(); /* Impossible due to _test_alias earlier */ for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "nick") || !strcmp(cepp->ce_varname, "target") || @@ -8477,26 +8524,22 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) { continue; } if (!strcmp(cep->ce_varname, "format")) { - int errorcode, errorbufsize; - char *errorbuf; - regex_t expr; + char *err = NULL; + aMatch *expr; char has_type = 0, has_target = 0, has_parameters = 0; has_format = 1; - errorcode = regcomp(&expr, cep->ce_vardata, REG_ICASE|REG_EXTENDED); - if (errorcode > 0) - { - errorbufsize = regerror(errorcode, &expr, NULL, 0)+1; - errorbuf = MyMalloc(errorbufsize); - regerror(errorcode, &expr, errorbuf, errorbufsize); - config_error("%s:%i: alias::format contains an invalid regex: %s", - cep->ce_fileptr->cf_filename, - cep->ce_varlinenum, - errorbuf); - errors++; - free(errorbuf); - } - regfree(&expr); + expr = unreal_create_match(MATCH_PCRE_REGEX, cep->ce_vardata, &err); + if (!expr) + { + config_error("%s:%i: alias::format contains an invalid regex: %s", + cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err); + config_error("Upgrading from 3.2.x to 3.4.x? Note that regex changed from POSIX Regex " + "to PCRE Regex!"); /* TODO: refer to some url ? */ + } else { + unreal_delete_match(expr); + } + for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (config_is_blankorempty(cepp, "alias::format")) { diff --git a/src/s_err.c b/src/s_err.c index 087ec4562..ff167dde1 100644 --- a/src/s_err.c +++ b/src/s_err.c @@ -262,7 +262,7 @@ static char *replies[] = { /* 226 RPL_STATSNLINE */ ":%s 226 %s n %s %s", /* 227 RPL_STATSVLINE */ ":%s 227 %s v %s %s %s", /* 228 RPL_STATSBANVER */ ":%s 228 %s %s %s", -/* 229 RPL_STATSSPAMF */ ":%s 229 %s %c %s %s %li %li %li %s %s :%s", +/* 229 RPL_STATSSPAMF */ ":%s 229 %s %c %s %s %s %li %li %li %s %s :%s", /* 230 RPL_STATSEXCEPTTKL */ ":%s 230 %s %c %s", /* 231 */ NULL, /* rfc1459 */ /* 232 RPL_RULES */ ":%s 232 %s :- %s", diff --git a/src/s_misc.c b/src/s_misc.c index 25ce38a9e..878abfbdb 100644 --- a/src/s_misc.c +++ b/src/s_misc.c @@ -847,26 +847,8 @@ Ilovegotos: return NULL; } - - -#define SPF_REGEX_FLAGS (REG_ICASE|REG_EXTENDED|REG_NOSUB) - -/** Allocates a new Spamfilter entry and compiles/fills in the info. - * NOTE: originally I wanted to integrate both badwords and spamfilter - * into one function, but that was quickly getting ugly :(. - */ -Spamfilter *unreal_buildspamfilter(char *s) -{ -Spamfilter *e = MyMallocEx(sizeof(Spamfilter)); - - regcomp(&e->expr, s, SPF_REGEX_FLAGS); - return e; -} - - /*|| BAN ACTION ROUTINES FOLLOW ||*/ - /** Converts a banaction string (eg: "kill") to an integer value (eg: BAN_ACT_KILL) */ int banact_stringtoval(char *s) {