m_textshun.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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. // One include for all cross-platform compatibility thangs
  7. #include "unrealircd.h"
  8. // Command strings
  9. #define MSG_TEXTSHUN "TEXTSHUN"
  10. #define MSG_TEXTSHUN_SHORT "TS"
  11. #define MSG_TEXTSHUN_ALT "TLINE"
  12. // Hewktypez
  13. #define SCONNECT_HOOK HOOKTYPE_SERVER_CONNECT
  14. #define PRCHANMSG_HOOK HOOKTYPE_PRE_CHANMSG
  15. #define PRUSERMSG_HOOK HOOKTYPE_PRE_USERMSG
  16. // Big hecks go here
  17. typedef struct t_tline TLine;
  18. struct t_tline {
  19. char *nickrgx;
  20. char *bodyrgx;
  21. time_t set;
  22. time_t expire;
  23. char *raisin;
  24. char *setby;
  25. TLine *next;
  26. };
  27. // Dem macros yo
  28. CMD_FUNC(m_textshun); // Register command function
  29. #define IsMDErr(x, y, z) \
  30. do { \
  31. if(!(x)) { \
  32. config_error("A critical error occurred when registering ModData for %s: %s", MOD_HEADER(y).name, ModuleGetErrorStr((z)->handle)); \
  33. return MOD_FAILED; \
  34. } \
  35. } while(0)
  36. // Quality fowod declarations
  37. static int dumpit(aClient *sptr, char **p);
  38. EVENT(textshun_event); // For expiring that shit fam
  39. void textshun_moddata_free(ModData *md);
  40. void check_tlines(void);
  41. void add_tline(TLine *newtl);
  42. void del_tline(TLine *muhtl);
  43. TLine *get_tlines(void);
  44. TLine *find_tline(char *nickrgx, char *bodyrgx);
  45. TLine *match_tline(aClient *sptr, char *text);
  46. int textshun_hook_serverconnect(aClient *sptr);
  47. char *_check_premsg(aClient *sptr, char *text);
  48. char *textshun_hook_prechanmsg(aClient *sptr, aChannel *chptr, char *text, int notice);
  49. char *textshun_hook_preusermsg(aClient *sptr, aClient *to, char *text, int notice);
  50. // Muh globals
  51. ModDataInfo *textshunMDI; // To store the T:Lines with &me lol (hack so we don't have to use a .db file or some shit)
  52. static ModuleInfo *textshunMI = NULL; // Store ModuleInfo so we can use it to check for errors in MOD_LOAD
  53. Command *textshunCmd, *textshunCmdShort, *textshunCmdAlt; // Pointers to the commands we're gonna add
  54. Hook *serverConnectHook, *preChanMsgHook, *preUserMsgHook; // Dem hewks lol
  55. int TLC; // A counter for T:Lines so we can change the moddata back to NULL
  56. // Help string in case someone does just /TEXTSHUN
  57. static char *muhhalp[] = {
  58. /* Special characters:
  59. ** \002 = bold -- \x02
  60. ** \037 = underlined -- \x1F
  61. */
  62. "*** \002Help on /TEXTSHUN\002 ***",
  63. "Enables opers to drop messages based on nick and body regexes (T:Lines).",
  64. "It only supports (PCRE) regexes because regular wildcards seem",
  65. "ineffective to me. ;] Also, you can't have spaces so you",
  66. "should simply use \\s. Also supports the aliases TS and TLINE.",
  67. "It's all case-insensitive by default. It also tells you if your",
  68. "regex is wrong (and what). The lines are network-wide.",
  69. "Servers, U:Lines and opers are exempt for obvious reasons.",
  70. "The nick regex is matched against both n!u@realhost and n!u@vhost masks.",
  71. " ",
  72. "Syntax:",
  73. " \002/TEXTSHUN\002 \037ADD/DEL\037 \037nickrgx\037 \037bodyrgx\037 [\037expiration\037] \037reason\037",
  74. " ",
  75. "Examples:",
  76. " \002/tline add guest.+ h[o0]+m[o0]+ 0 nope\002",
  77. " \002/textshun add guest.+ h[o0]+m[o0]+ nope\002",
  78. " \002/ts del guest.+ h[o0]+m[o0]+\002",
  79. " Adds/deletes the same T:Line, with no expiration",
  80. " \002/tline add guest.+ h[o0]+m[o0]+ 3600 ain't gonna happen\002",
  81. " \002/tline add guest.+ h[o0]+m[o0]+ 1h ain't gonna happen\002",
  82. " Add a T:Line that expires in an hour",
  83. " \002/tline\002",
  84. " Show all T:Lines",
  85. NULL
  86. };
  87. // Dat dere module header
  88. ModuleHeader MOD_HEADER(m_textshun) = {
  89. "m_textshun", // Module name
  90. "$Id: v1.07 2019/01/24 Gottem$", // Version
  91. "Drop messages based on nick and body", // Description
  92. "3.2-b8-1", // Modversion, not sure wat do
  93. NULL
  94. };
  95. // Initialisation routine (register hooks, commands and modes or create structs etc)
  96. MOD_INIT(m_textshun) {
  97. TLine *TLineList, *tEntry; // To initialise the TLC counter imo tbh fam
  98. // If command(s) already exist(s) for some reason, bail out
  99. if(CommandExists(MSG_TEXTSHUN)) {
  100. config_error("Command %s already exists", MSG_TEXTSHUN);
  101. return MOD_FAILED;
  102. }
  103. if(CommandExists(MSG_TEXTSHUN_SHORT)) {
  104. config_error("Command %s already exists", MSG_TEXTSHUN_SHORT);
  105. return MOD_FAILED;
  106. }
  107. if(CommandExists(MSG_TEXTSHUN_ALT)) {
  108. config_error("Command %s already exists", MSG_TEXTSHUN_ALT);
  109. return MOD_FAILED;
  110. }
  111. TLC = 0; // Start with 0 obv lmao
  112. if(!(textshunMDI = findmoddata_byname("textshun_list", MODDATATYPE_CLIENT))) { // Attempt to find active moddata (like in case of a rehash)
  113. ModDataInfo mreq; // No moddata, let's request that shit
  114. memset(&mreq, 0, sizeof(mreq)); // Set 'em lol
  115. mreq.type = MODDATATYPE_CLIENT; // Apply to servers only (CLIENT actually includes users but we'll disregard that =])
  116. mreq.name = "textshun_list"; // Name it
  117. mreq.free = textshun_moddata_free; // Function to free 'em
  118. mreq.serialize = NULL;
  119. mreq.unserialize = NULL;
  120. mreq.sync = 0;
  121. textshunMDI = ModDataAdd(modinfo->handle, mreq); // Add 'em yo
  122. IsMDErr(textshunMDI, m_textshun, modinfo);
  123. }
  124. else { // We did get moddata
  125. if((TLineList = get_tlines())) { // So load 'em
  126. for(tEntry = TLineList; tEntry; tEntry = tEntry->next) // and iter8 m8
  127. TLC++; // Ayyy premium countur
  128. }
  129. }
  130. // Add muh hooks with (mostly) high prio lol
  131. serverConnectHook = HookAdd(modinfo->handle, SCONNECT_HOOK, 0, textshun_hook_serverconnect);
  132. preChanMsgHook = HookAddPChar(modinfo->handle, PRCHANMSG_HOOK, -100, textshun_hook_prechanmsg);
  133. preUserMsgHook = HookAddPChar(modinfo->handle, PRUSERMSG_HOOK, -100, textshun_hook_preusermsg);
  134. // Dem commands fam
  135. textshunCmd = CommandAdd(modinfo->handle, MSG_TEXTSHUN, m_textshun, MAXPARA, M_SERVER | M_USER);
  136. textshunCmdShort = CommandAdd(modinfo->handle, MSG_TEXTSHUN_SHORT, m_textshun, MAXPARA, M_SERVER | M_USER);
  137. textshunCmdAlt = CommandAdd(modinfo->handle, MSG_TEXTSHUN_ALT, m_textshun, MAXPARA, M_SERVER | M_USER);
  138. textshunMI = modinfo; // Store module info yo
  139. return MOD_SUCCESS; // Let MOD_LOAD handle module errors
  140. }
  141. // Actually load the module here (also command overrides as they may not exist in MOD_INIT yet)
  142. MOD_LOAD(m_textshun) {
  143. EventAddEx(textshunMI->handle, "textshun_event", 10, 0, textshun_event, NULL); // Run event every 10 seconds, indefinitely and without any additional data (void *NULL etc)
  144. // Did the module throw an error during initialisation, or is one of the h00k/command pointers null even?
  145. if(ModuleGetError(textshunMI->handle) != MODERR_NOERROR || !serverConnectHook || !preChanMsgHook || !preUserMsgHook || !textshunCmd || !textshunCmdShort || !textshunCmdAlt) {
  146. // Display error string kek
  147. config_error("A critical error occurred when loading module %s: %s", MOD_HEADER(m_textshun).name, ModuleGetErrorStr(textshunMI->handle));
  148. return MOD_FAILED; // No good
  149. }
  150. return MOD_SUCCESS; // We good
  151. }
  152. // Called on unload/rehash obv
  153. MOD_UNLOAD(m_textshun) {
  154. // Not clearing the moddata structs here so we can re-use them easily ;];]
  155. return MOD_SUCCESS; // We good
  156. }
  157. // Dump a NULL-terminated array of strings to user sptr using the numeric rplnum, and then return 0 (taken from DarkFire IRCd)
  158. static int dumpit(aClient *sptr, char **p) {
  159. if(IsServer(sptr)) // Bail out early and silently if it's a server =]
  160. return 0;
  161. for(; *p != NULL; p++)
  162. sendto_one(sptr, ":%s %03d %s :%s", me.name, RPL_TEXT, sptr->name, *p);
  163. // Let user take 8 seconds to read it
  164. sptr->local->since += 8;
  165. return 0;
  166. }
  167. EVENT(textshun_event) {
  168. check_tlines(); // Checkem and expirem
  169. }
  170. // Probably never called but it's a required function
  171. // The free shit here normally only happens when the client attached to the moddata quits (afaik), but that's us =]
  172. void textshun_moddata_free(ModData *md) {
  173. if(md->ptr) { // r u insaiyan?
  174. TLine *tEntry = md->ptr; // Cast em
  175. if(tEntry->nickrgx) free(tEntry->nickrgx); // Gotta
  176. if(tEntry->bodyrgx) free(tEntry->bodyrgx); // free
  177. if(tEntry->raisin) free(tEntry->raisin); // 'em
  178. if(tEntry->setby) free(tEntry->setby); // all
  179. tEntry->set = 0; // Just in case lol
  180. tEntry->expire = 0L; // ditt0
  181. md->ptr = NULL; // d-d-ditt0
  182. }
  183. }
  184. // Check for expiring T:Lines
  185. void check_tlines(void) {
  186. TLine *TLineList, *head, *last, **tEntry;
  187. char gmt[256]; // For a pretty timestamp instead of UNIX time lol
  188. char *timeret; // Ditto
  189. TS setat; // For use with the pretty timestamp
  190. if(!(TLineList = get_tlines())) // Ayyy no T:Lines known
  191. return;
  192. tEntry = &TLineList; // Hecks so the ->next chain stays intact
  193. head = TLineList;
  194. while(*tEntry) { // Loop while we have entries obv
  195. if((*tEntry)->expire > 0 && TStime() > ((*tEntry)->set + (*tEntry)->expire)) { // Do we need to expire it?
  196. last = *tEntry; // Get the entry pointur
  197. *tEntry = last->next; // Set the iterat0r to the next one
  198. if(last == head) { // If it's the first entry, need to take special precautions ;]
  199. moddata_client((&me), textshunMDI).ptr = *tEntry; // Cuz shit rips if we don't do dis
  200. head = *tEntry; // Move head up
  201. }
  202. // Get pretty timestamp =]
  203. setat = last->set;
  204. timeret = asctime(gmtime((TS *)&setat));
  205. strlcpy(gmt, timeret, sizeof(gmt));
  206. iCstrip(gmt);
  207. // Send expiration notice to all _local_ opers lol (every server checks expirations itself newaysz y0)
  208. sendto_snomask(SNO_TKL, "*** Expiring T:Line set by %s at %s GMT for nick /%s/ and body /%s/ [reason: %s]", last->setby, gmt, last->nickrgx, last->bodyrgx, last->raisin);
  209. if(last->nickrgx) free(last->nickrgx); // Gotta
  210. if(last->bodyrgx) free(last->bodyrgx); // free
  211. if(last->raisin) free(last->raisin); // em
  212. if(last->setby) free(last->setby); // all
  213. free(last); // lol
  214. TLC--;
  215. }
  216. else {
  217. tEntry = &(*tEntry)->next; // No need for expiration, go to the next one
  218. }
  219. }
  220. if(TLC <= 0) // Cuz shit rips if we don't do dis
  221. moddata_client((&me), textshunMDI).ptr = NULL;
  222. }
  223. // Add new T:Line obv fam
  224. void add_tline(TLine *newtl) {
  225. TLine *TLineList, *tEntry; // Head + iter8or imo tbh
  226. TLC++; // Always increment count
  227. if(!(TLineList = get_tlines())) { // If TLineList is NULL...
  228. TLineList = newtl; // ...simply have it point to the newly alloc8ed entry
  229. moddata_client((&me), textshunMDI).ptr = TLineList; // And st0re em
  230. return;
  231. }
  232. for(tEntry = TLineList; tEntry && tEntry->next; tEntry = tEntry->next) { } // Dirty shit to get teh last entray
  233. tEntry->next = newtl; // Append lol
  234. }
  235. // Delete em fam
  236. void del_tline(TLine *muhtl) {
  237. TLine *TLineList, *last, **tEntry;
  238. if(!(TLineList = get_tlines())) // Ayyy no T:Lines known
  239. return;
  240. tEntry = &TLineList; // Hecks so the ->next chain stays intact
  241. if(*tEntry == muhtl) { // If it's the first entry, need to take special precautions ;]
  242. last = *tEntry; // Get the entry pointur
  243. *tEntry = last->next; // Set the iterat0r to the next one
  244. if(last->nickrgx) free(last->nickrgx); // Gotta
  245. if(last->bodyrgx) free(last->bodyrgx); // free
  246. if(last->raisin) free(last->raisin); // em
  247. if(last->setby) free(last->setby); // all
  248. free(last); // lol
  249. moddata_client((&me), textshunMDI).ptr = *tEntry; // Cuz shit rips if we don't do dis
  250. TLC--;
  251. return;
  252. }
  253. while(*tEntry) { // Loop while we have entries obv
  254. if(*tEntry == muhtl) { // Do we need to delete em?
  255. last = *tEntry; // Get the entry pointur
  256. *tEntry = last->next; // Set the iterat0r to the next one
  257. if(last->nickrgx) free(last->nickrgx); // Gotta
  258. if(last->bodyrgx) free(last->bodyrgx); // free
  259. if(last->raisin) free(last->raisin); // em
  260. if(last->setby) free(last->setby); // all
  261. free(last); // lol
  262. TLC--;
  263. break;
  264. }
  265. else {
  266. tEntry = &(*tEntry)->next; // No need, go to the next one
  267. }
  268. }
  269. if(TLC <= 0) // Cuz shit rips if we don't do dis
  270. moddata_client((&me), textshunMDI).ptr = NULL;
  271. }
  272. // Get (head of) the T:Line list
  273. TLine *get_tlines(void) {
  274. TLine *TLineList = moddata_client((&me), textshunMDI).ptr; // Get mod data
  275. // Sanity check lol
  276. if(TLineList && TLineList->nickrgx)
  277. return TLineList;
  278. return NULL;
  279. }
  280. // Find a specific T:Line (based on nick and body regex lol)
  281. TLine *find_tline(char *nickrgx, char *bodyrgx) {
  282. TLine *TLineList, *tEntry; // Head and iter8or fam
  283. if((TLineList = get_tlines())) { // Check if the list even has entries kek
  284. for(tEntry = TLineList; tEntry; tEntry = tEntry->next) { // Iter8 em
  285. // The regex match itself (aMatch *) is case-insensitive anyways, so let's do stricmp() here =]
  286. if(!stricmp(tEntry->nickrgx, nickrgx) && !stricmp(tEntry->bodyrgx, bodyrgx))
  287. return tEntry;
  288. }
  289. }
  290. return NULL; // Not found m8
  291. }
  292. // For matching a user and string to a T:Line
  293. TLine *match_tline(aClient *sptr, char *text) {
  294. char *mask = make_nick_user_host(sptr->name, sptr->user->username, sptr->user->realhost); // Get nick!user@host with the real hostnaem
  295. char *vmask = (sptr->user->virthost ? make_nick_user_host(sptr->name, sptr->user->username, sptr->user->virthost) : NULL); // Also virthost, if any
  296. aMatch *exprNick, *exprBody; // For creating the actual match struct pointer thingy
  297. int nickmatch, bodmatch; // Did we get een match?
  298. TLine *TLineList, *tEntry; // Head and iter8or fam
  299. if(!text || !mask) // r u insaiyan lol?
  300. return NULL;
  301. if((TLineList = get_tlines())) { // Check if the list even has entries kek
  302. for(tEntry = TLineList; tEntry; tEntry = tEntry->next) {
  303. nickmatch = bodmatch = 0;
  304. exprNick = unreal_create_match(MATCH_PCRE_REGEX, tEntry->nickrgx, NULL); // Create match struct for nikk regex
  305. exprBody = unreal_create_match(MATCH_PCRE_REGEX, tEntry->bodyrgx, NULL); // Also for body
  306. if(!exprNick || !exprBody) // If either failed, gtfo
  307. continue;
  308. if(vmask) // If virthost exists
  309. nickmatch = (unreal_match(exprNick, mask) || unreal_match(exprNick, vmask)); // Check if it either matches obv
  310. else // If it doesn't (no umode +x, no NickServ vhost, etc)
  311. nickmatch = unreal_match(exprNick, mask); // Matchem real host only
  312. bodmatch = unreal_match(exprBody, text);
  313. unreal_delete_match(exprNick); // Cleanup
  314. unreal_delete_match(exprBody); // lol
  315. if(nickmatch && bodmatch)
  316. return tEntry;
  317. }
  318. }
  319. return NULL; // rip
  320. }
  321. // Internal function called by the pre*msg hooks ;];]
  322. char *_check_premsg(aClient *sptr, char *text) {
  323. TLine *tEntry; // Iter8or
  324. char *body;
  325. if(!MyConnect(sptr)) // No need to check if it's not our client =]
  326. return text;
  327. // Strip all markup shit (bold, italikk etc) and colours
  328. if(!(body = (char *)StripControlCodes(text)))
  329. return text;
  330. if(!IsServer(sptr) && !IsMe(sptr) && !IsULine(sptr) && !IsOper(sptr) && (tEntry = match_tline(sptr, body))) { // Servers, U:Lines and opers are exempt for obv raisins
  331. // If match, send notices to all other servers/opers =]
  332. sendto_snomask(SNO_TKL, "*** T:Line for nick /%s/ and body /%s/ matched by %s [body: %s]", tEntry->nickrgx, tEntry->bodyrgx, sptr->name, body);
  333. sendto_server(&me, 0, 0, ":%s SENDSNO G :*** T:Line for nick /%s/ and body /%s/ matched by %s [body: %s]", me.name, tEntry->nickrgx, tEntry->bodyrgx, sptr->name, body);
  334. return NULL; // And return NULL to discard the entire message (quality shun imo tbh)
  335. }
  336. return text; // Allowed, return original text
  337. }
  338. // Server connect hewk familia
  339. int textshun_hook_serverconnect(aClient *sptr) {
  340. // Sync T:Lines fam
  341. TLine *TLineList, *tEntry; // Head and iter8or ;];]
  342. if((TLineList = get_tlines())) { // Gettem list
  343. for(tEntry = TLineList; tEntry; tEntry = tEntry->next) {
  344. if(!tEntry || !tEntry->nickrgx) // Sanity check imo ;]
  345. continue;
  346. // Syntax for servers is a bit different (namely the setby arg and the : before reason (makes the entire string after be considered one arg ;];])
  347. sendto_one(sptr, ":%s TLINE ADD %s %s %ld %ld %s :%s", me.name, tEntry->nickrgx, tEntry->bodyrgx, tEntry->set, tEntry->expire, tEntry->setby, tEntry->raisin);
  348. }
  349. }
  350. return HOOK_CONTINUE;
  351. }
  352. // Pre message hewks lol
  353. char *textshun_hook_prechanmsg(aClient *sptr, aChannel *chptr, char *text, int notice) {
  354. return _check_premsg(sptr, text);
  355. }
  356. char *textshun_hook_preusermsg(aClient *sptr, aClient *to, char *text, int notice) {
  357. return _check_premsg(sptr, text);
  358. }
  359. // Function for /TLINE etc
  360. CMD_FUNC(m_textshun) {
  361. /* Gets args: aClient *cptr, aClient *sptr, int parc, char *parv[]
  362. **
  363. ** cptr: Pointer to directly attached client -- if remote user this is the remote server instead
  364. ** sptr: Pointer to user executing command
  365. ** parc: Amount of arguments (also includes the command in the count)
  366. ** parv: Contains the actual args, first one starts at parv[1]
  367. **
  368. ** So "TEXTSHUN test" would result in parc = 2 and parv[1] = "test"
  369. ** Also, parv[0] seems to always be NULL, so better not rely on it fam
  370. */
  371. aMatch *exprNick, *exprBody; // For verifying the regexes
  372. char *regexerr, *regexerr_nick, *regexerr_body; // Error pointers (regexerr is static so better strdup em)
  373. TLine *TLineList, *newtl, *tEntry; // Quality struct pointers
  374. char *nickrgx, *bodyrgx, *exptmp, *setby; // Muh args
  375. char raisin[BUFSIZE]; // Reasons may or may not be pretty long
  376. char gmt[256], gmt2[256]; // For a pretty timestamp instead of UNIX time lol
  377. char *timeret; // Ditto
  378. char cur, prev, prev2; // For checking time strings
  379. long setat, expire; // After how many seconds the T:Line should expire
  380. TS expiry; // For use with the pretty timestamps
  381. int i, rindex, del, nickrgx_ok, bodyrgx_ok; // Iterat0rs and "booleans" =]
  382. // Gotta be at least a server, U:Line or oper with correct privs lol
  383. if((!IsServer(sptr) && !IsMe(sptr) && !IsULine(sptr) && !IsOper(sptr)) || !ValidatePermissionsForPath("textshun", sptr, NULL, NULL, NULL)) {
  384. sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, sptr->name); // Check ur privilege fam
  385. return -1; // Ain't gonna happen lol
  386. }
  387. // If no args given (or we got /tline list)
  388. if(BadPtr(parv[1]) || !stricmp(parv[1], "list")) {
  389. if(IsServer(cptr)) // No need to list shit for servers =]
  390. return 0;
  391. if(!(TLineList = get_tlines())) // Attempt to get list
  392. sendnotice(sptr, "*** No T:Lines found");
  393. else {
  394. for(tEntry = TLineList; tEntry; tEntry = tEntry->next) {
  395. timeret = asctime(gmtime((TS *)&tEntry->set));
  396. strlcpy(gmt2, timeret, sizeof(gmt2));
  397. iCstrip(gmt2);
  398. if(tEntry->expire == 0) // Let's show "permanent" for permanent T:Lines, n0? =]
  399. sendnotice(sptr, "*** Permanent T:Line set by %s at %s GMT for nick /%s/ and body /%s/ [reason: %s]", tEntry->setby, gmt2, tEntry->nickrgx, tEntry->bodyrgx, tEntry->raisin);
  400. else {
  401. // Get pretty timestamp for expiring lines =]
  402. expiry = tEntry->set + tEntry->expire;
  403. timeret = asctime(gmtime((TS *)&expiry));
  404. strlcpy(gmt, timeret, sizeof(gmt));
  405. iCstrip(gmt);
  406. sendnotice(sptr, "*** T:Line set by %s at %s GMT for nick /%s/ and body /%s/, expiring at %s GMT [reason: %s]", tEntry->setby, gmt2, tEntry->nickrgx, tEntry->bodyrgx, gmt, tEntry->raisin);
  407. }
  408. }
  409. }
  410. return 0;
  411. }
  412. // Need at least 4 args lol
  413. if(!stricmp(parv[1], "help") || !stricmp(parv[1], "halp"))
  414. return dumpit(sptr, muhhalp); // Return help string instead
  415. del = (!stricmp(parv[1], "del") ? 1 : 0); // Are we deleting?
  416. if((!del && parc < 5) || (del && parc < 4)) { // Delete doesn't require the expire and reason fields
  417. sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, sptr->name, "TEXTSHUN"); // Need m0ar lol
  418. return -1;
  419. }
  420. if(stricmp(parv[1], "add") && stricmp(parv[1], "del")) { // If first arg is neither add nor del, fuck off
  421. sendnotice(sptr, "*** [textshun] First arg must be either ADD or DEL");
  422. return -1;
  423. }
  424. // Extra args required for servers (setby and setat fields, used during linking yo)
  425. if(IsServer(cptr) && parc <= 7)
  426. return -1; // Return silently
  427. // Initialise a bunch of shit
  428. memset(raisin, '\0', sizeof(raisin));
  429. exptmp = regexerr_nick = regexerr_body = NULL;
  430. expire = DEFAULT_BANTIME;
  431. setat = TStime();
  432. rindex = nickrgx_ok = bodyrgx_ok = 0;
  433. nickrgx = parv[2];
  434. bodyrgx = parv[3];
  435. exptmp = parv[4];
  436. setby = sptr->name;
  437. exprNick = unreal_create_match(MATCH_PCRE_REGEX, nickrgx, &regexerr); // Attempt to create match struct
  438. if(!exprNick && regexerr && !IsServer(cptr)) { // Servers don't need to get a notice for invalid shit
  439. regexerr_nick = strdup(regexerr); // Must dup regexerr here ;]
  440. regexerr = NULL; // Nullify just to be shur
  441. }
  442. exprBody = unreal_create_match(MATCH_PCRE_REGEX, bodyrgx, &regexerr); // Attempt to create match struct
  443. if(!exprBody && regexerr && !IsServer(cptr)) { // Servers don't need to get a notice for invalid shit
  444. regexerr_body = strdup(regexerr);
  445. regexerr = NULL; // Nullify just to be shur
  446. }
  447. // We good?
  448. nickrgx_ok = (exprNick ? 1 : 0);
  449. bodyrgx_ok = (exprBody ? 1 : 0);
  450. if(exprNick) unreal_delete_match(exprNick); // Cleanup
  451. if(exprBody) unreal_delete_match(exprBody); // lol
  452. // Most shit is silent for servers
  453. if(IsServer(cptr)) {
  454. if(!nickrgx_ok || !bodyrgx_ok) // Both should be sane obv
  455. return -1; // Return silently
  456. tEntry = find_tline(nickrgx, bodyrgx); // Attempt to find existing T:Line
  457. if(!del && tEntry) // Adding a T:Line but it already exists
  458. return -1; // Return silently
  459. else if(del && !tEntry) // Deleting but doesn't exist
  460. return -1; // Return silently
  461. strlcpy(raisin, parv[7], sizeof(raisin)); // Copy the reason field imo tbh
  462. setat = atol(parv[4]); // Extra arg yo
  463. expire = atol(parv[5]); // Set expiration
  464. setby = parv[6]; // Extra arg yo
  465. if(setat <= 0) // Some error occured lol
  466. return -1; // Gtfo silently
  467. }
  468. // Command came from a user
  469. else {
  470. if(!nickrgx_ok || !bodyrgx_ok) { // Both need to be sane obv
  471. if(!nickrgx_ok) sendnotice(sptr, "*** [textshun] Invalid nick regex /%s/ [err: %s]", nickrgx, regexerr_nick); // Report regex error for nikk
  472. if(!bodyrgx_ok) sendnotice(sptr, "*** [textshun] Invalid body regex /%s/ [err: %s]", bodyrgx, regexerr_body); // For body too
  473. if(regexerr_nick) free(regexerr_nick); // Free if exists
  474. if(regexerr_body) free(regexerr_body); // Ditto
  475. return -1; // Lolnope
  476. }
  477. // Just in case ;]
  478. if(regexerr_nick) free(regexerr_nick);
  479. if(regexerr_body) free(regexerr_body);
  480. tEntry = find_tline(nickrgx, bodyrgx); // Attempt to find existing T:Line
  481. if(!del && tEntry) { // Adding a T:Line but it already exists
  482. sendnotice(sptr, "*** T:Line for nick /%s/ and body /%s/ already exists", nickrgx, bodyrgx);
  483. return -1; // Lolnope
  484. }
  485. else if(del && !tEntry) { // Deleting but doesn't exist
  486. sendnotice(sptr, "*** T:Line for nick /%s/ and body /%s/ doesn't exist", nickrgx, bodyrgx);
  487. return -1; // Lolnope
  488. }
  489. // If adding, check for expiration and reason fields
  490. if(!del) {
  491. // Let's check for a time string (3600, 1h, 2w3d, etc)
  492. for(i = 0; exptmp[i] != 0; i++) {
  493. cur = exptmp[i];
  494. if(!isdigit(cur)) { // No digit, check for the 'h' in '1h' etc
  495. prev = (i >= 1 ? exptmp[i - 1] : 0);
  496. prev2 = (i >= 2 ? exptmp[i - 2] : 0);
  497. if((prev && prev2 && isdigit(prev2) && prev == 'm' && cur == 'o') || (prev && isdigit(prev) && strchr("smhdw", cur))) // Check for allowed combos
  498. continue;
  499. exptmp = NULL; // Fuck off
  500. rindex = 4; // Reason index for parv[] is 4
  501. break; // Only one mismatch is enough
  502. }
  503. }
  504. if(exptmp) { // If the for() loop didn't pass over the innermost if(), expire field is sane
  505. expire = config_checkval(exptmp, CFG_TIME); // So get a long from the (possible) time string
  506. rindex = 5; // And set reason index for parv[] to 5
  507. }
  508. if(!rindex || BadPtr(parv[rindex]) || !strlen(parv[rindex])) { // If rindex is 0 it means the arg is missing
  509. sendnotice(sptr, "*** [textshun] The reason field is required");
  510. return -1; // No good fam
  511. }
  512. // Now start from rindex and copy dem remaining args
  513. for(i = rindex; parv[i] != NULL; i++) {
  514. if(i == rindex)
  515. strlcpy(raisin, parv[i], sizeof(raisin));
  516. else {
  517. strlcat(raisin, " ", sizeof(raisin));
  518. strlcat(raisin, parv[i], sizeof(raisin));
  519. }
  520. }
  521. }
  522. }
  523. // For both servers and users ;]
  524. if(!del) {
  525. // Allocate/initialise mem0ry for the new entry
  526. newtl = malloc(sizeof(TLine));
  527. newtl->nickrgx = strdup(nickrgx);
  528. newtl->bodyrgx = strdup(bodyrgx);
  529. newtl->set = setat;
  530. newtl->expire = expire;
  531. newtl->raisin = strdup(raisin);
  532. newtl->setby = strdup(setby);
  533. newtl->next = NULL;
  534. tEntry = newtl;
  535. add_tline(newtl); // Add em
  536. }
  537. // Propagate the T:Line to other servers if it came from a user only (when servers link they sync it themselves)
  538. if(!IsServer(sptr))
  539. sendto_server(&me, 0, 0, ":%s TLINE %s %s %s %ld %ld %s :%s", sptr->name, (del ? "DEL" : "ADD"), nickrgx, bodyrgx, tEntry->set, tEntry->expire, setby, tEntry->raisin); // Muh raw command
  540. else { // If it did come from a server, let's make a "set at" timestamp =]
  541. timeret = asctime(gmtime((TS *)&tEntry->set));
  542. strlcpy(gmt2, timeret, sizeof(gmt2));
  543. iCstrip(gmt2);
  544. }
  545. // Also send snomask notices to all local opers =]
  546. if(tEntry->expire == 0) { // Permanent lol
  547. if(IsServer(sptr)) // Show "set at" during sync phase ;]
  548. sendto_snomask(SNO_TKL, "*** Permanent T:Line %sed by %s at %s GMT for nick /%s/ and body /%s/ [reason: %s]", (del ? "delet" : "add"), setby, gmt2, nickrgx, bodyrgx, tEntry->raisin);
  549. else
  550. sendto_snomask(SNO_TKL, "*** Permanent T:Line %sed by %s for nick /%s/ and body /%s/ [reason: %s]", (del ? "delet" : "add"), setby, nickrgx, bodyrgx, tEntry->raisin);
  551. }
  552. else {
  553. // Make pretty expiration timestamp if not a permanent T:Line
  554. expiry = tEntry->set + tEntry->expire;
  555. timeret = asctime(gmtime((TS *)&expiry));
  556. strlcpy(gmt, timeret, sizeof(gmt));
  557. iCstrip(gmt);
  558. if(IsServer(sptr)) // Show "set at" during sync phase ;]
  559. sendto_snomask(SNO_TKL, "*** T:Line %sed by %s at %s GMT for nick /%s/ and body /%s/, expiring at %s GMT [reason: %s]", (del ? "delet" : "add"), setby, gmt2, nickrgx, bodyrgx, gmt, tEntry->raisin);
  560. else
  561. sendto_snomask(SNO_TKL, "*** T:Line %sed by %s for nick /%s/ and body /%s/, expiring at %s GMT [reason: %s]", (del ? "delet" : "add"), setby, nickrgx, bodyrgx, gmt, tEntry->raisin);
  562. }
  563. // Delete em famamlamlamlmal
  564. if(del)
  565. del_tline(tEntry);
  566. return 0; // All good
  567. }