Our server pr0vider carried out maintenance on 15 January 2020 but it corrupted some files in the process. If you notice anything out of the ordinary (partial files, pages not loading, that kind of shit) then let us know at:

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. }