m_block_masshighlight.c 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. /* Copyright (C) All Rights Reserved
  2. ** Written by Gottem <support@gottem.nl>
  3. ** Website: https://gitgud.malvager.net/Wazakindjes/unrealircd_mods
  4. ** License: https://gitgud.malvager.net/Wazakindjes/unrealircd_mods/raw/master/LICENSE
  5. **
  6. ** Contains edits by k4be to make the threshold checking more robust
  7. */
  8. // One include for all cross-platform compatibility thangs
  9. #include "unrealircd.h"
  10. // Config block
  11. #define MYCONF "block_masshighlight"
  12. // Channel mode for exempting from highlight checks
  13. #define IsNocheckHighlights(chptr) (chptr->mode.extmode & EXTCMODE_NOCHECK_MASSHIGHLIGHT)
  14. #define CHMODE_CHAR 'H'
  15. // Dem macros yo
  16. #define IsMDErr(x, y, z) \
  17. do { \
  18. if(!(x)) { \
  19. config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER(y).name, ModuleGetErrorStr((z)->handle)); \
  20. return MOD_FAILED; \
  21. } \
  22. } while(0)
  23. // Quality fowod declarations
  24. int is_accessmode_exempt(aClient *sptr, aChannel *chptr);
  25. int extcmode_requireowner(aClient *sptr, aChannel *chptr, char mode, char *para, int checkt, int what);
  26. void doXLine(char flag, aClient *sptr);
  27. int masshighlight_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
  28. int masshighlight_configposttest(int *errs);
  29. int masshighlight_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
  30. void masshighlight_md_free(ModData *md);
  31. char *masshighlight_hook_prechanmsg(aClient *sptr, aChannel *chptr, char *text, int notice);
  32. // Muh globals
  33. int spamf_ugly_vchanoverride = 0; // For viruschan shit =]
  34. static ModuleInfo *massHLMI = NULL; // Store ModuleInfo so we can use it to check for errors in MOD_LOAD
  35. ModDataInfo *massHLMDI; // To store some shit with the channel ;]
  36. Cmode_t EXTCMODE_NOCHECK_MASSHIGHLIGHT; // For storing the exemption chanmode =]
  37. struct {
  38. unsigned short int maxnicks; // Maxnicks, unsigned cuz can't be negative anyways lol
  39. char *delimiters; // List of delimiters for splitting a sentence into "words"
  40. char action; // Simple char like 'g' for gline etc
  41. time_t duration; // How long to ban for
  42. char *reason; // Reason for X:Lines or for the notice to offending users
  43. unsigned short int snotice; // Whether to send snotices or n0, simply 0 or 1
  44. unsigned short int banident; // Whether to ban ident@host or simply *@host
  45. unsigned short int multiline; // Check over multiple lines or just the one ;]
  46. unsigned short int allow_authed; // Allow identified users to bypass this shit
  47. unsigned int allow_accessmode; // Lowest channel access mode privilege to bypass the limit
  48. unsigned short int percent; // How many characters in a message recognised as a nickname is enough for the message to be rejected
  49. unsigned short int show_opers_origmsg; // Display the suspicious message to operators
  50. // These are just for setting to 0 or 1 to see if we got em config directives ;]
  51. unsigned short int got_maxnicks;
  52. unsigned short int got_delimiters;
  53. unsigned short int got_action;
  54. unsigned short int got_duration;
  55. unsigned short int got_reason;
  56. unsigned short int got_snotice;
  57. unsigned short int got_banident;
  58. unsigned short int got_multiline;
  59. unsigned short int got_allow_authed;
  60. unsigned short int got_allow_accessmode;
  61. unsigned short int got_percent;
  62. unsigned short int got_show_opers_origmsg;
  63. } muhcfg;
  64. // Dat dere module header
  65. ModuleHeader MOD_HEADER(m_block_masshighlight) = {
  66. "m_block_masshighlight", // Module name
  67. "$Id: v1.07 2018/04/16 Gottem/k4be$", // Version
  68. "Prevent mass highlights network-wide", // Description
  69. "3.2-b8-1", // Modversion, not sure wat do
  70. NULL
  71. };
  72. // Configuration testing-related hewks go in testing phase obv
  73. MOD_TEST(m_masshighlight) {
  74. // We have our own config block so we need to checkem config obv m9
  75. // Priorities don't really matter here
  76. HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, masshighlight_configtest);
  77. HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, masshighlight_configposttest);
  78. return MOD_SUCCESS;
  79. }
  80. // Initialisation routine (register hooks, commands and modes or create structs etc)
  81. MOD_INIT(m_block_masshighlight) {
  82. massHLMI = modinfo;
  83. // Request moddata for storing the last counter etc
  84. ModDataInfo mreq;
  85. memset(&mreq, 0, sizeof(mreq));
  86. mreq.type = MODDATATYPE_MEMBERSHIP; // Apply to memberships only (look at the user and then iterate through the channel list)
  87. mreq.name = "masshighlight"; // Name it
  88. mreq.free = masshighlight_md_free; // Function to free 'em
  89. massHLMDI = ModDataAdd(modinfo->handle, mreq);
  90. IsMDErr(massHLMDI, m_block_masshighlight, modinfo);
  91. // Also request +H channel mode fam
  92. CmodeInfo req;
  93. memset(&req, 0, sizeof(req));
  94. req.paracount = 0; // No args required ;]
  95. req.flag = CHMODE_CHAR;
  96. req.is_ok = extcmode_requireowner; // Need owner privs to set em imo tbh
  97. CmodeAdd(modinfo->handle, req, &EXTCMODE_NOCHECK_MASSHIGHLIGHT);
  98. // Register hewks m8
  99. HookAddPChar(modinfo->handle, HOOKTYPE_PRE_CHANMSG, 0, masshighlight_hook_prechanmsg);
  100. HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, masshighlight_configrun);
  101. return MOD_SUCCESS; // Let MOD_LOAD handle errors
  102. }
  103. // Actually load the module here (also command overrides as they may not exist in MOD_INIT yet)
  104. MOD_LOAD(m_block_masshighlight) {
  105. // Did the module throw an error during initialisation?
  106. if(ModuleGetError(massHLMI->handle) != MODERR_NOERROR) {
  107. // Display error string kek
  108. config_error("A critical error occurred when loading module %s: %s", MOD_HEADER(m_block_masshighlight).name, ModuleGetErrorStr(massHLMI->handle));
  109. return MOD_FAILED; // No good
  110. }
  111. return MOD_SUCCESS; // We good
  112. }
  113. // Called on unload/rehash obv
  114. MOD_UNLOAD(m_block_masshighlight) {
  115. if(muhcfg.reason) // inb4rip
  116. free(muhcfg.reason); // Let's free this lol
  117. if(muhcfg.delimiters)
  118. free(muhcfg.delimiters);
  119. return MOD_SUCCESS; // We good
  120. }
  121. // Client exempted through allow_accessmode?
  122. int is_accessmode_exempt(aClient *sptr, aChannel *chptr) {
  123. Membership *lp; // For checkin' em list access level =]
  124. if(IsServer(sptr) || IsMe(sptr)) // Allow servers always lel
  125. return 1;
  126. if(!muhcfg.allow_accessmode) // Don't even bother ;]
  127. return 0;
  128. if(chptr) { // Sanity cheqq
  129. if((lp = find_membership_link(sptr->user->channel, chptr))) {
  130. if(lp->flags & muhcfg.allow_accessmode)
  131. return 1;
  132. }
  133. }
  134. return 0; // No valid channel/membership or doesn't have enough axx lol
  135. }
  136. // Testing for owner status on channel
  137. int extcmode_requireowner(aClient *sptr, aChannel *chptr, char mode, char *para, int checkt, int what) {
  138. if(IsPerson(sptr) && is_chanowner(sptr, chptr))
  139. return EX_ALLOW;
  140. if(checkt == EXCHK_ACCESS_ERR)
  141. sendto_one(sptr, err_str(ERR_CHANOWNPRIVNEEDED), me.name, sptr->name, chptr->chname);
  142. return EX_DENY;
  143. }
  144. // Not using place_host_ban() cuz I need more control over the mask to ban ;];;];]
  145. void doXLine(char flag, aClient *sptr) {
  146. // Double check for sptr existing, cuz inb4segfault
  147. if(sptr) {
  148. char setTime[100], expTime[100];
  149. ircsnprintf(setTime, sizeof(setTime), "%li", TStime());
  150. ircsnprintf(expTime, sizeof(expTime), "%li", TStime() + muhcfg.duration);
  151. char *tkltype = malloc(sizeof(char) * 2); // Convert the single char to a char *
  152. tkltype[0] = (flag == 's' ? flag : toupper(flag)); // Uppercase that shit if not shunning y0
  153. tkltype[1] = '\0';
  154. // Build TKL args
  155. char *tkllayer[9] = {
  156. // :SERVER +FLAG IDENT HOST SETBY EXPIRATION SETAT :REASON
  157. me.name,
  158. "+",
  159. tkltype,
  160. (muhcfg.banident ? sptr->user->username : "*"), // Wildcard ident if banident == 0
  161. (flag == 'z' ? GetIP(sptr) : sptr->user->realhost), // Let's use the IP in case of Z:Lines lel
  162. me.name,
  163. expTime,
  164. setTime,
  165. muhcfg.reason
  166. };
  167. m_tkl(&me, &me, 9, tkllayer); // Ban 'em
  168. free(tkltype); // Free that shit lol
  169. }
  170. }
  171. int masshighlight_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) {
  172. int errors = 0; // Error count
  173. int i; // Iterat0r
  174. int tmp; // f0 checking integer values
  175. ConfigEntry *cep; // To store the current variable/value pair etc
  176. // Since we'll add a top-level block to unrealircd.conf, need to filter on CONFIG_MAIN lmao
  177. if(type != CONFIG_MAIN)
  178. return 0; // Returning 0 means idgaf bout dis
  179. // Check for valid config entries first
  180. if(!ce || !ce->ce_varname)
  181. return 0;
  182. // If it isn't our block, idc
  183. if(strcmp(ce->ce_varname, MYCONF))
  184. return 0;
  185. // Loop dat shyte fam
  186. for(cep = ce->ce_entries; cep; cep = cep->ce_next) {
  187. // Do we even have a valid name l0l?
  188. if(!cep->ce_varname) {
  189. config_error("%s:%i: blank %s item", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF); // Rep0t error
  190. errors++; // Increment err0r count fam
  191. continue; // Next iteration imo tbh
  192. }
  193. if(!strcmp(cep->ce_varname, "action")) {
  194. muhcfg.got_action = 1;
  195. if(!cep->ce_vardata || !strlen(cep->ce_vardata)) {
  196. config_error("%s:%i: %s::%s must be non-empty fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  197. errors++; // Increment err0r count fam
  198. continue;
  199. }
  200. // Checkem valid actions
  201. if(strcmp(cep->ce_vardata, "drop") && strcmp(cep->ce_vardata, "notice") && strcmp(cep->ce_vardata, "gline") && strcmp(cep->ce_vardata, "zline") &&
  202. strcmp(cep->ce_vardata, "kill") && strcmp(cep->ce_vardata, "tempshun") && strcmp(cep->ce_vardata, "shun") && strcmp(cep->ce_vardata, "viruschan")) {
  203. config_error("%s:%i: %s::%s must be one of: drop, notice, gline, zline, shun, tempshun, kill, viruschan", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  204. errors++;
  205. }
  206. if(!errors)
  207. muhcfg.action = cep->ce_vardata[0]; // We need this value to be set in posttest
  208. continue;
  209. }
  210. if(!strcmp(cep->ce_varname, "reason")) {
  211. muhcfg.got_reason = 1;
  212. if(!cep->ce_vardata || strlen(cep->ce_vardata) < 4) {
  213. config_error("%s:%i: %s::%s must be at least 4 characters long", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  214. errors++; // Increment err0r count fam
  215. }
  216. continue;
  217. }
  218. if(!strcmp(cep->ce_varname, "delimiters")) {
  219. muhcfg.got_delimiters = 1;
  220. if(!cep->ce_vardata || !strlen(cep->ce_vardata)) {
  221. config_error("%s:%i: %s::%s must contain at least one character", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  222. errors++; // Increment err0r count fam
  223. }
  224. continue;
  225. }
  226. if(!strcmp(cep->ce_varname, "duration")) {
  227. muhcfg.got_duration = 1;
  228. // Should be a time string imo (7d10s etc, or just 20)
  229. if(!cep->ce_vardata || config_checkval(cep->ce_vardata, CFG_TIME) <= 0) {
  230. config_error("%s:%i: %s::%s must be a time string like '7d10m' or simply '20'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  231. errors++; // Increment err0r count fam
  232. }
  233. continue;
  234. }
  235. if(!strcmp(cep->ce_varname, "maxnicks")) {
  236. muhcfg.got_maxnicks = 1;
  237. // Should be an integer yo
  238. if(!cep->ce_vardata) {
  239. config_error("%s:%i: %s::%s must be an integer between 1 and 512 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  240. errors++; // Increment err0r count fam
  241. continue;
  242. }
  243. for(i = 0; cep->ce_vardata[i]; i++) {
  244. if(!isdigit(cep->ce_vardata[i])) {
  245. config_error("%s:%i: %s::%s must be an integer between 1 and 512 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  246. errors++; // Increment err0r count fam
  247. break;
  248. }
  249. }
  250. if(!errors) {
  251. tmp = atoi(cep->ce_vardata);
  252. if(tmp <= 0 || tmp > 512) {
  253. config_error("%s:%i: %s::%s must be an integer between 1 and 512 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  254. errors++; // Increment err0r count fam
  255. }
  256. }
  257. continue;
  258. }
  259. if(!strcmp(cep->ce_varname, "snotice")) {
  260. muhcfg.got_snotice = 1;
  261. if(!cep->ce_vardata || (strcmp(cep->ce_vardata, "0") && strcmp(cep->ce_vardata, "1"))) {
  262. config_error("%s:%i: %s::%s must be either 0 or 1 fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  263. errors++; // Increment err0r count fam
  264. }
  265. continue;
  266. }
  267. if(!strcmp(cep->ce_varname, "banident")) {
  268. muhcfg.got_banident = 1;
  269. if(!cep->ce_vardata || (strcmp(cep->ce_vardata, "0") && strcmp(cep->ce_vardata, "1"))) {
  270. config_error("%s:%i: %s::%s must be either 0 or 1 fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  271. errors++; // Increment err0r count fam
  272. }
  273. continue;
  274. }
  275. if(!strcmp(cep->ce_varname, "multiline")) {
  276. muhcfg.got_multiline = 1;
  277. if(!cep->ce_vardata || (strcmp(cep->ce_vardata, "0") && strcmp(cep->ce_vardata, "1"))) {
  278. config_error("%s:%i: %s::%s must be either 0 or 1 fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  279. errors++; // Increment err0r count fam
  280. }
  281. continue;
  282. }
  283. if(!strcmp(cep->ce_varname, "allow_authed")) {
  284. muhcfg.got_allow_authed = 1;
  285. if(!cep->ce_vardata || (strcmp(cep->ce_vardata, "0") && strcmp(cep->ce_vardata, "1"))) {
  286. config_error("%s:%i: %s::%s must be either 0 or 1 fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  287. errors++; // Increment err0r count fam
  288. }
  289. continue;
  290. }
  291. if(!strcmp(cep->ce_varname, "allow_accessmode")) {
  292. muhcfg.got_allow_accessmode = 1;
  293. if(!cep->ce_vardata || !strlen(cep->ce_vardata)) {
  294. config_error("%s:%i: %s::%s must be either non-empty or not specified at all", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  295. errors++; // Increment err0r count fam
  296. continue;
  297. }
  298. if(strlen(cep->ce_vardata) > 1) {
  299. config_error("%s:%i: %s::%s must be exactly one character (mode) in length", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  300. errors++; // Increment err0r count fam
  301. continue;
  302. }
  303. if(strcmp(cep->ce_vardata, "v") && strcmp(cep->ce_vardata, "h") && strcmp(cep->ce_vardata, "o")
  304. #ifdef PREFIX_AQ
  305. && strcmp(cep->ce_vardata, "a") && strcmp(cep->ce_vardata, "q")
  306. #endif
  307. ) {
  308. config_error("%s:%i: %s::%s must be one of: v, h, o"
  309. #ifdef PREFIX_AQ
  310. ", a, q"
  311. #endif
  312. , cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  313. errors++;
  314. }
  315. continue;
  316. }
  317. if(!strcmp(cep->ce_varname, "percent")) {
  318. muhcfg.got_percent = 1;
  319. // Should be an integer yo
  320. if(!cep->ce_vardata) {
  321. config_error("%s:%i: %s::%s must be an integer between 1 and 100 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  322. errors++; // Increment err0r count fam
  323. continue;
  324. }
  325. for(i = 0; cep->ce_vardata[i]; i++) {
  326. if(!isdigit(cep->ce_vardata[i])) {
  327. config_error("%s:%i: %s::%s must be an integer between 1 and 100 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  328. errors++; // Increment err0r count fam
  329. break;
  330. }
  331. }
  332. if(!errors) {
  333. tmp = atoi(cep->ce_vardata);
  334. if(tmp <= 0 || tmp > 100) {
  335. config_error("%s:%i: %s::%s must be an integer between 1 and 100 m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  336. errors++; // Increment err0r count fam
  337. }
  338. }
  339. continue;
  340. }
  341. if(!strcmp(cep->ce_varname, "show_opers_origmsg")) {
  342. muhcfg.got_show_opers_origmsg = 1;
  343. if(!cep->ce_vardata || (strcmp(cep->ce_vardata, "0") && strcmp(cep->ce_vardata, "1"))) {
  344. config_error("%s:%i: %s::%s must be either 0 or 1 fam", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  345. errors++; // Increment err0r count fam
  346. }
  347. continue;
  348. }
  349. // Anything else is unknown to us =]
  350. config_warn("%s:%i: unknown item %s::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname); // So display just a warning
  351. }
  352. *errs = errors;
  353. return errors ? -1 : 1; // Returning 1 means "all good", -1 means we shat our panties
  354. }
  355. int masshighlight_configposttest(int *errs) {
  356. int errors = 0;
  357. // Set defaults and display warnings where needed
  358. if(!muhcfg.got_maxnicks) {
  359. muhcfg.maxnicks = 5;
  360. config_warn("[block_masshighlight] Unable to find 'maxnicks' directive, defaulting to: %d", muhcfg.maxnicks);
  361. }
  362. if(!muhcfg.got_action) {
  363. muhcfg.action = 'g'; // Gline em by default lol
  364. config_warn("[block_masshighlight] Unable to find 'action' directive, defaulting to: gline");
  365. }
  366. if(strchr("gzs", muhcfg.action) && !muhcfg.got_duration) { // Duration is only required for X:Lines =]
  367. muhcfg.duration = 604800; // 7 days yo
  368. config_warn("[block_masshighlight] Unable to find 'duration' directive, defaulting to: %li seconds", muhcfg.duration);
  369. }
  370. if(muhcfg.action != 'd' && !muhcfg.got_reason) // For everything besides drop, we need a reason =]
  371. muhcfg.reason = strdup("No mass highlighting allowed"); // So it doesn't fuck with free(), also no need to display config_warn() imo tbh
  372. if(!muhcfg.got_delimiters)
  373. muhcfg.delimiters = strdup(" ,.-_/\\:;"); // Ditto =]
  374. if(!muhcfg.got_snotice)
  375. muhcfg.snotice = 1; // Show 'em, no need to display config_warn() imo tbh
  376. if(!muhcfg.got_banident) // Lazy mode, even though it's not required for all actions, set it anyways =]
  377. muhcfg.banident = 1; // Default to ident@host imo tbh
  378. if(!muhcfg.got_multiline)
  379. muhcfg.multiline = 0; // Default to single line imo
  380. if(!muhcfg.got_allow_authed)
  381. muhcfg.allow_authed = 0; // Default to n0 fam
  382. if(!muhcfg.got_allow_accessmode)
  383. muhcfg.allow_accessmode = 0; // None ;]
  384. if(!muhcfg.got_percent)
  385. muhcfg.percent = 1; // 1%, max sensitivity
  386. if(!muhcfg.got_show_opers_origmsg)
  387. muhcfg.show_opers_origmsg = 1; // Default to showing em
  388. *errs = errors;
  389. return errors ? -1 : 1;
  390. }
  391. // "Run" the config (everything should be valid at this point)
  392. int masshighlight_configrun(ConfigFile *cf, ConfigEntry *ce, int type) {
  393. ConfigEntry *cep; // To store the current variable/value pair etc
  394. // Since we'll add a top-level block to unrealircd.conf, need to filter on CONFIG_MAIN lmao
  395. if(type != CONFIG_MAIN)
  396. return 0; // Returning 0 means idgaf bout dis
  397. // Check for valid config entries first
  398. if(!ce || !ce->ce_varname)
  399. return 0;
  400. // If it isn't masshighlight, idc
  401. if(strcmp(ce->ce_varname, MYCONF))
  402. return 0;
  403. // Loop dat shyte fam
  404. for(cep = ce->ce_entries; cep; cep = cep->ce_next) {
  405. // Do we even have a valid name l0l?
  406. if(!cep->ce_varname)
  407. continue; // Next iteration imo tbh
  408. if(!strcmp(cep->ce_varname, "delimiters")) {
  409. if(muhcfg.delimiters) // Let's free this first lol (should never happen but let's just in case xd)
  410. free(muhcfg.delimiters);
  411. muhcfg.delimiters = strdup(cep->ce_vardata);
  412. continue;
  413. }
  414. if(!strcmp(cep->ce_varname, "reason")) {
  415. if(muhcfg.reason) // Let's free this first lol (should never happen but let's just in case xd)
  416. free(muhcfg.reason);
  417. muhcfg.reason = strdup(cep->ce_vardata);
  418. continue;
  419. }
  420. if(!strcmp(cep->ce_varname, "duration")) {
  421. muhcfg.duration = config_checkval(cep->ce_vardata, CFG_TIME);
  422. continue;
  423. }
  424. if(!strcmp(cep->ce_varname, "maxnicks")) {
  425. muhcfg.maxnicks = atoi(cep->ce_vardata);
  426. continue;
  427. }
  428. if(!strcmp(cep->ce_varname, "snotice")) {
  429. muhcfg.snotice = atoi(cep->ce_vardata);
  430. continue;
  431. }
  432. if(!strcmp(cep->ce_varname, "banident")) {
  433. muhcfg.banident = atoi(cep->ce_vardata);
  434. continue;
  435. }
  436. if(!strcmp(cep->ce_varname, "multiline")) {
  437. muhcfg.multiline = atoi(cep->ce_vardata);
  438. continue;
  439. }
  440. if(!strcmp(cep->ce_varname, "allow_authed")) {
  441. muhcfg.allow_authed = atoi(cep->ce_vardata);
  442. continue;
  443. }
  444. if(!strcmp(cep->ce_varname, "allow_accessmode")) {
  445. muhcfg.allow_accessmode = 0;
  446. switch(cep->ce_vardata[0]) {
  447. case 'v':
  448. muhcfg.allow_accessmode |= CHFL_VOICE;
  449. case 'h':
  450. muhcfg.allow_accessmode |= CHFL_HALFOP;
  451. case 'o':
  452. muhcfg.allow_accessmode |= CHFL_CHANOP;
  453. #ifdef PREFIX_AQ
  454. case 'a':
  455. muhcfg.allow_accessmode |= CHFL_CHANPROT;
  456. case 'q':
  457. muhcfg.allow_accessmode |= CHFL_CHANOWNER;
  458. #endif
  459. default:
  460. break;
  461. }
  462. continue;
  463. }
  464. if(!strcmp(cep->ce_varname, "percent")) {
  465. muhcfg.percent = atoi(cep->ce_vardata);
  466. continue;
  467. }
  468. if(!strcmp(cep->ce_varname, "show_opers_origmsg")) {
  469. muhcfg.show_opers_origmsg = atoi(cep->ce_vardata);
  470. continue;
  471. }
  472. }
  473. return 1; // We good
  474. }
  475. void masshighlight_md_free(ModData *md) {
  476. if(md && md->i) // Just in case kek, as this function is required
  477. md->i = 0;
  478. }
  479. char *masshighlight_hook_prechanmsg(aClient *sptr, aChannel *chptr, char *text, int notice) {
  480. /* Args:
  481. ** sptr: Client that sent the message
  482. ** chptr: Channel to which it was sent
  483. ** text: The message body obv fambi
  484. ** notice: Was it a NOTICE or simply PRIVMSG?
  485. */
  486. int hl_cur; // Current highlight count yo
  487. char *p; // For tokenising that shit
  488. char *werd; // Store current token etc
  489. char *cleantext; // Let's not modify char *text =]
  490. aClient *acptr; // Temporarily store a mentioned nick to get the membership link
  491. Membership *lp; // For actually storing the link
  492. MembershipL *lp2; // Required for moddata call apparently
  493. int blockem; // Need to block the message?
  494. int clearem; // For clearing the counter
  495. int ret; // For checking the JOIN return status for viruschan action
  496. char joinbuf[CHANNELLEN + 4]; // Also for viruschan, need to make room for "0,#viruschan" etc ;];]
  497. char *vcparv[3]; // Arguments for viruschan JOIN
  498. int bypass_nsauth; // If config var allow_authed is tr00 and user es logged in
  499. char gotnicks[1024]; // For storing nicks that were mentioned =]
  500. int hl_new; // Store highlight count for further processing
  501. size_t werdlen; // Word length ;]
  502. size_t hl_nickslen; // Amount of chars that are part of highlights
  503. size_t msglen; // Full message length (both of these are required for calculating percent etc), excludes delimiters
  504. // Initialise some shit lol
  505. bypass_nsauth = (muhcfg.allow_authed && IsPerson(sptr) && IsLoggedIn(sptr));
  506. blockem = 0;
  507. clearem = 1;
  508. p = NULL;
  509. cleantext = NULL;
  510. memset(gotnicks, '\0', sizeof(gotnicks));
  511. hl_new = 0;
  512. hl_nickslen = 0;
  513. msglen = 0;
  514. if(IsNocheckHighlights(chptr))
  515. return text; // if channelmode is set, allow without further checking
  516. // Some sanity + privilege checks ;];] (allow_accessmode, U:Lines and opers are exempt from this shit)
  517. if(text && IsPerson(sptr) && MyClient(sptr) && !bypass_nsauth && !is_accessmode_exempt(sptr, chptr) && !IsULine(sptr) && !IsOper(sptr)) {
  518. if(!(lp = find_membership_link(sptr->user->channel, chptr))) // Not even a member tho lol
  519. return text; // Process normally cuz can't do shit w/ moddata =]
  520. lp2 = (MembershipL *)lp; // Cast that shit
  521. hl_cur = moddata_membership(lp2, massHLMDI).i; // Get current count
  522. // In case someone tries some funny business =]
  523. if(!(cleantext = (char *)StripControlCodes(text)))
  524. return text;
  525. for(werd = strtoken(&p, cleantext, muhcfg.delimiters); werd; werd = strtoken(&p, NULL, muhcfg.delimiters)) { // Split that shit
  526. werdlen = strlen(werd);
  527. msglen += werdlen; // We do not count ze delimiters, otherwise the percents would get strangely low
  528. if((acptr = find_person(werd, NULL)) && (find_membership_link(acptr->user->channel, chptr))) { // User mentioned another user in this channel
  529. if(!(strstr(gotnicks, acptr->id))) { // Do not count the same nickname appended multiple times to a single message
  530. ircsnprintf(gotnicks, sizeof(gotnicks), "%s%s,", gotnicks, acptr->id);
  531. hl_new++; // Got another highlight this round
  532. hl_nickslen += werdlen; // Also add the nick's length
  533. }
  534. }
  535. }
  536. if(msglen && 100 * hl_nickslen / msglen > muhcfg.percent) { // Check if we exceed the max allowed percentage
  537. hl_cur += hl_new; // Set moddata counter to include the ones found this round
  538. clearem = 0; // And don't clear moddata
  539. if(hl_cur > muhcfg.maxnicks) // Check if we also exceed max allowed nicks
  540. blockem = 1; // Flip flag to blockin' dat shit
  541. }
  542. if(!muhcfg.multiline && !clearem) // If single line mode and found a nick
  543. clearem = 1; // Need to clear that shit always lol
  544. moddata_membership(lp2, massHLMDI).i = (clearem ? 0 : hl_cur); // Actually set the counter =]
  545. if(blockem) { // Need to bl0ck?
  546. switch(muhcfg.action) {
  547. case 'd': // Drop silently
  548. if(muhcfg.snotice)
  549. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, dropping silently", chptr->chname, sptr->name);
  550. break;
  551. case 'n': // Drop, but send notice
  552. if(muhcfg.snotice)
  553. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, dropping with a notice", chptr->chname, sptr->name);
  554. sendto_one(sptr, ":%s NOTICE %s :%s", me.name, chptr->chname, muhcfg.reason);
  555. break;
  556. case 'k': // Kill em all
  557. if(muhcfg.snotice)
  558. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, killing 'em", chptr->chname, sptr->name);
  559. dead_link(sptr, muhcfg.reason);
  560. break;
  561. case 't': // Tempshun kek
  562. if(muhcfg.snotice)
  563. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, setting tempshun", chptr->chname, sptr->name);
  564. // Nicked this from place_host_ban() tbh =]
  565. sendto_snomask(SNO_TKL, "Temporary shun added at user %s (%s@%s) [%s]", sptr->name,
  566. (sptr->user ? sptr->user->username : "unknown"),
  567. (sptr->user ? sptr->user->realhost : GetIP(sptr)),
  568. muhcfg.reason);
  569. SetShunned(sptr); // Ez m0de
  570. break;
  571. case 's': // Shun, ...
  572. case 'g': // ...G:Line and ...
  573. case 'z': // ...Z:Line share the same internal function ;];]
  574. if(muhcfg.snotice) {
  575. if(muhcfg.action == 's')
  576. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, shunning 'em", chptr->chname, sptr->name);
  577. else
  578. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, setting %c:Line", chptr->chname, sptr->name, toupper(muhcfg.action));
  579. }
  580. doXLine(muhcfg.action, sptr);
  581. break;
  582. case 'v': // Viruschan lol
  583. if(muhcfg.snotice)
  584. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] Detected highlight spam in %s by %s, following viruschan protocol", chptr->chname, sptr->name);
  585. // This bit is ripped from m_tkl with some logic changes to suit what we need to do =]
  586. snprintf(joinbuf, sizeof(joinbuf), "0,%s", SPAMFILTER_VIRUSCHAN);
  587. vcparv[0] = sptr->name;
  588. vcparv[1] = joinbuf;
  589. vcparv[2] = NULL;
  590. spamf_ugly_vchanoverride = 1;
  591. ret = do_cmd(sptr, sptr, "JOIN", 2, vcparv); // Need to get return value
  592. spamf_ugly_vchanoverride = 0;
  593. if(ret != FLUSH_BUFFER) { // In case something went horribly wrong lmao
  594. sendnotice(sptr, "You are now restricted to talking in %s: %s", SPAMFILTER_VIRUSCHAN, muhcfg.reason);
  595. SetVirus(sptr); // Ayy rekt
  596. }
  597. break;
  598. default:
  599. break;
  600. }
  601. if(muhcfg.show_opers_origmsg)
  602. sendto_snomask_global(SNO_EYES, "*** [block_masshighlight] The message was: %s", text);
  603. text = NULL; // NULL makes Unreal drop the message entirely afterwards ;3
  604. }
  605. }
  606. return text;
  607. }