m_mshun.c 23 KB

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