m_websocket_restrict.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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. // Config bl0ck
  9. #define MYCONF "websocket_restrict"
  10. // Hewkerino
  11. #define MYHEWK HOOKTYPE_PRE_LOCAL_JOIN
  12. // Big hecks go here
  13. typedef struct t_chanstrukk muhchan;
  14. struct t_chanstrukk {
  15. char *name;
  16. muhchan *next;
  17. };
  18. // Quality fowod declarations
  19. void doGZLine(aClient *sptr, char *fullErr);
  20. int websocket_restrict_prelocalconnect(aClient *sptr);
  21. int websocket_restrict_prelocaljoin(aClient *sptr, aChannel *chptr, char *parv[]);
  22. int websocket_restrict_packet_in(aClient *sptr, char *readbuf, int *length);
  23. int websocket_restrict_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
  24. int websocket_restrict_configposttest(int *errs);
  25. int websocket_restrict_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
  26. int websocket_restrict_rehash(void);
  27. // Muh globals
  28. static ModuleInfo *WSRMI = NULL; // Store ModuleInfo so we can use it to check for errors in MOD_LOAD
  29. Hook *wsockHookPrejoin;
  30. // Set config defaults here
  31. int *WSOnlyPorts; // Dynamic array ;]
  32. int c_numPorts = 0; // Keep track of WS only ports
  33. int zlineTime = 60; // Default GZ:Line time is 60 seconds lol
  34. muhchan *chanList = NULL; // Channel restrictions
  35. int chanCount = 0;
  36. // Dat dere module header
  37. ModuleHeader MOD_HEADER(m_websocket_restrict) = {
  38. "m_websocket_restrict", // Module name
  39. "$Id: v1.02 2018/03/03 Gottem$", // Version
  40. "Impose restrictions on websocket connections", // Description
  41. "3.2-b8-1", // Modversion, not sure wat do
  42. NULL
  43. };
  44. // Configuration testing-related hewks go in testing phase obv
  45. // This function is entirely optional
  46. MOD_TEST(m_websocket_restrict) {
  47. // We have our own config block so we need to checkem config obv m9
  48. // Priorities don't really matter here
  49. HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, websocket_restrict_configtest);
  50. HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, websocket_restrict_configposttest);
  51. return MOD_SUCCESS;
  52. }
  53. // Initialisation routine (register hooks, commands and modes or create structs etc)
  54. MOD_INIT(m_websocket_restrict) {
  55. WSRMI = modinfo; // Store module info yo
  56. HookAdd(modinfo->handle, HOOKTYPE_REHASH, 0, websocket_restrict_rehash);
  57. HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, websocket_restrict_configrun);
  58. HookAdd(modinfo->handle, HOOKTYPE_PRE_LOCAL_CONNECT, 0, websocket_restrict_prelocalconnect);
  59. HookAdd(modinfo->handle, HOOKTYPE_RAWPACKET_IN, -100, websocket_restrict_packet_in); // High priority so we go before the actual websocket module ;]
  60. wsockHookPrejoin = HookAdd(modinfo->handle, MYHEWK, 0, websocket_restrict_prelocaljoin);
  61. return MOD_SUCCESS; // Let MOD_LOAD handle errors and registering of overrides
  62. }
  63. MOD_LOAD(m_websocket_restrict) {
  64. // Check if module handle is available, also check for overrides/commands that weren't added for some raisin
  65. if(ModuleGetError(WSRMI->handle) != MODERR_NOERROR || !wsockHookPrejoin) {
  66. // Display error string kek
  67. config_error("A critical error occurred when loading module %s: %s", MOD_HEADER(m_websocket_restrict).name, ModuleGetErrorStr(WSRMI->handle));
  68. return MOD_FAILED; // No good
  69. }
  70. return MOD_SUCCESS; // We good
  71. }
  72. // Called on unload/rehash obv
  73. MOD_UNLOAD(m_websocket_restrict) {
  74. // Clean up shit here
  75. if(c_numPorts)
  76. free(WSOnlyPorts);
  77. if(chanList) {
  78. // This shit is a bit convoluted to prevent memory issues obv famalmalmalmlmalm
  79. muhchan *ch;
  80. while((ch = chanList) != NULL) {
  81. chanList = chanList->next;
  82. if(ch->name) free(ch->name);
  83. free(ch);
  84. }
  85. chanList = NULL;
  86. }
  87. chanCount = 0;
  88. return MOD_SUCCESS; // We good
  89. }
  90. void doGZLine(aClient *sptr, char *fullErr) {
  91. char setTime[100], expTime[100];
  92. ircsnprintf(setTime, sizeof(setTime), "%li", TStime());
  93. ircsnprintf(expTime, sizeof(expTime), "%li", TStime() + zlineTime);
  94. char *tkllayer[9] = {
  95. me.name,
  96. "+",
  97. "Z",
  98. "*",
  99. sptr->ip,
  100. me.name,
  101. expTime,
  102. setTime,
  103. fullErr
  104. };
  105. m_tkl(&me, &me, 9, tkllayer); // Ban 'em
  106. }
  107. // Check port restrictions for non-websocket users
  108. int websocket_restrict_prelocalconnect(aClient *sptr) {
  109. int ws_port = 0; // "Boolean"
  110. int i; // Iter8or lol
  111. ModDataInfo *websocket_md;
  112. if(!(websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT))) // Something went wrong getting ModDataInfo
  113. return 0; // Np
  114. if((moddata_client(sptr, websocket_md).ptr)) // If we found moddata, means this is a WS user, so gtfo
  115. return 0; // Np
  116. if(c_numPorts) { // If we found some valid ports in the config
  117. for(i = 0; WSOnlyPorts[i]; i++) { // Iter8 em
  118. if(sptr->local->listener->port == WSOnlyPorts[i]) { // User is connecting from a special designated port?
  119. ws_port = 1; // Flip boolean
  120. break; // Gtfo
  121. }
  122. }
  123. }
  124. if(ws_port) { // Regular user connecting to WS only p0t
  125. // Since we kinda __have__ to GZ:Line WS users, let's be consistent for non-WS users too ;]
  126. char fullErr[128];
  127. ircsnprintf(fullErr, sizeof(fullErr), "User is using a websocket-only port (%d)", sptr->local->listener->port); // Make error string
  128. doGZLine(sptr, fullErr); // Ban 'em
  129. return exit_client(sptr, sptr, sptr, fullErr); // Kbye
  130. }
  131. return 0; // We good
  132. }
  133. int websocket_restrict_prelocaljoin(aClient *sptr, aChannel *chptr, char *parv[]) {
  134. muhchan *chan;
  135. int found;
  136. ModDataInfo *websocket_md;
  137. if(!chanCount) // No restrictions to begin with =]
  138. return HOOK_CONTINUE;
  139. if(!(websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT))) // Something went wrong getting ModDataInfo
  140. return HOOK_CONTINUE; // Np
  141. if(!(moddata_client(sptr, websocket_md).ptr)) // If we didn't find moddata, means this is not a WS user, so gtfo
  142. return HOOK_CONTINUE; // Np
  143. found = 0;
  144. for(chan = chanList; chan; chan = chan->next) {
  145. if(!stricmp(chptr->chname, chan->name)) {
  146. found = 1;
  147. break;
  148. }
  149. }
  150. if(!found) {
  151. sendnotice(sptr, "[websocket_restrict] Not allowed to join %s", chptr->chname);
  152. return HOOK_DENY;
  153. }
  154. return HOOK_CONTINUE;
  155. }
  156. // Check restrictions for websocket users trying shit meant for non-websocket users
  157. int websocket_restrict_packet_in(aClient *sptr, char *readbuf, int *length) {
  158. /* Return values:
  159. *** -1: Don't touch this client anymore, it might have been killed lol
  160. *** 0: Don't process this data, but you can read another packet if you want
  161. *** > 0 means: Allow others to process this data still
  162. */
  163. if((sptr->local->receiveM == 0) && (*length > 8) && !strncmp(readbuf, "GET ", 4)) {
  164. ModDataInfo *websocket_md;
  165. if(!(websocket_md = findmoddata_byname("websocket", MODDATATYPE_CLIENT))) // Something went wrong getting ModDataInfo
  166. return 1; // Let others process the data
  167. // Check if user is connecting to a websocket-restricted port
  168. if(!(moddata_client(sptr, websocket_md).ptr)) { // If we DID find moddata, the client is already online and we can skip this part ;];]
  169. int ws_port = 0; // "Boolean"
  170. int i; // Iter8or lol
  171. if(c_numPorts) { // If we found some valid ports in the config
  172. for(i = 0; WSOnlyPorts[i]; i++) { // Iter8 em
  173. if(sptr->local->listener->port == WSOnlyPorts[i]) { // User is connecting from a special designated port?
  174. ws_port = 1; // Flip boolean
  175. break; // Gtfo
  176. }
  177. }
  178. }
  179. // If this module is loaded, it will always deny websocket connections if no special port was found
  180. if(!ws_port) {
  181. // Have to GZ:Line here to prevent duplicate notices lol (since client is not fully online, websocket.c won't create a proper frame)
  182. if(sptr->ip) { // IP may or may not be resolved yet (seems to be a race condition of sorts =])
  183. char fullErr[128];
  184. ircsnprintf(fullErr, sizeof(fullErr), "Websocket client using illegal port (%d)", sptr->local->listener->port); // Make error string
  185. doGZLine(sptr, fullErr); // Ban 'em
  186. }
  187. dead_link(sptr, "Illegal port used for websocket connections"); // Cuz can't use exit_client() from this hook apparently =]
  188. return -1; // Notify main loop of lost client
  189. }
  190. }
  191. }
  192. return 1; // Let others process the data
  193. }
  194. int websocket_restrict_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs) {
  195. int errors = 0; // Error count
  196. int pot; // For checkin' em p0t
  197. int i; // Iter8 em m8
  198. ConfigEntry *cep, *cep2; // To store the current variable/value pair etc, nested
  199. if(type != CONFIG_MAIN)
  200. return 0; // Returning 0 means idgaf bout dis
  201. // Check for valid config entries first
  202. if(!ce || !ce->ce_varname)
  203. return 0;
  204. // If it isn't our block, idc
  205. if(strcmp(ce->ce_varname, MYCONF))
  206. return 0;
  207. // Loop dat shyte fam
  208. for(cep = ce->ce_entries; cep; cep = cep->ce_next) {
  209. // Do we even have a valid name l0l?
  210. if(!cep || !cep->ce_varname)
  211. continue;
  212. // Checkem port restrictions
  213. if(!strcmp(cep->ce_varname, "port")) {
  214. if(!cep->ce_vardata) { // Sanity check lol
  215. config_error("%s:%i: no value specified for %s::port", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF);
  216. errors++; // Increment err0r count fam
  217. continue;
  218. }
  219. pot = atoi(cep->ce_vardata); // Convert em
  220. if(pot > 1024 && pot <= 65535) // Check port range lol
  221. c_numPorts++; // Got a valid port
  222. else {
  223. config_error("%s:%i: invalid %s::port '%s' (must be higher than 1024 and lower than or equal to 65535)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_vardata);
  224. errors++; // Increment err0r count fam
  225. }
  226. continue;
  227. }
  228. if(!strcmp(cep->ce_varname, "zlinetime")) {
  229. // Should be an integer yo
  230. if(!cep->ce_vardata) {
  231. config_error("%s:%i: %s::zlinetime must be an integer of 0 or larger m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF);
  232. errors++; // Increment err0r count fam
  233. continue;
  234. }
  235. for(i = 0; cep->ce_vardata[i]; i++) {
  236. if(!isdigit(cep->ce_vardata[i])) {
  237. config_error("%s:%i: %s::zlinetime must be an integer of 0 or larger m8", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF);
  238. errors++; // Increment err0r count fam
  239. break;
  240. }
  241. }
  242. continue;
  243. }
  244. // Here comes a nested block =]
  245. if(!strcmp(cep->ce_varname, "channels")) {
  246. // Loop 'em again
  247. for(cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) {
  248. if(!cep2->ce_varname || !strlen(cep2->ce_varname)) {
  249. config_error("%s:%i: blank %s::%s item", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, MYCONF, cep->ce_varname); // Rep0t error
  250. errors++; // Increment err0r count fam
  251. continue; // Next iteration imo tbh
  252. }
  253. if(cep2->ce_varname[0] != '#') {
  254. config_error("%s:%i: invalid channel name '%s': must start with #", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep2->ce_varname); // Rep0t error
  255. errors++; // Increment err0r count fam
  256. continue; // Next iteration imo tbh
  257. }
  258. if(strlen(cep2->ce_varname) > CHANNELLEN) {
  259. config_error("%s:%i: invalid channel name '%s': must not exceed %d characters in length", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep2->ce_varname, CHANNELLEN);
  260. errors++; // Increment err0r count fam
  261. continue; // Next iteration imo tbh
  262. }
  263. chanCount++;
  264. }
  265. continue;
  266. }
  267. config_warn("%s:%i: unknown directive %s::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, MYCONF, cep->ce_varname);
  268. }
  269. *errs = errors;
  270. return errors ? -1 : 1; // Returning 1 means "all good", -1 means we shat our panties
  271. }
  272. // Post test, check for missing shit here
  273. int websocket_restrict_configposttest(int *errs) {
  274. int errors = 0;
  275. if(!Module_Find("websocket")) {
  276. config_error("m_websocket_restrict was loaded but \002websocket\002 itself was not");
  277. *errs = 1;
  278. return -1;
  279. }
  280. if(!c_numPorts)
  281. config_warn("m_websocket_restrict was loaded but %s::port has no valid candidates, \002nobody using websocket clients will be able to connect\002", MYCONF); // Just a warning is enough lol
  282. else
  283. WSOnlyPorts = calloc(c_numPorts, sizeof(int)); // Allocate array and init to 0 ;]
  284. *errs = errors;
  285. return errors ? -1 : 1; // Returning 1 means "all good", -1 means we shat our panties
  286. }
  287. // "Run" the config (everything should be valid at this point)
  288. int websocket_restrict_configrun(ConfigFile *cf, ConfigEntry *ce, int type) {
  289. ConfigEntry *cep, *cep2; // To store the current variable/value pair etc, nested
  290. int pot; // For checkin' em p0t
  291. int i; // Iter8or for WSOnlyPorts[]
  292. muhchan *last = NULL; // Initialise to NULL so the loop requires minimal l0gic
  293. muhchan **chan = &chanList; // Hecks so the ->next chain stays intact
  294. if(type != CONFIG_MAIN)
  295. return 0; // Returning 0 means idgaf bout dis
  296. // Check for valid config entries first
  297. if(!ce || !ce->ce_varname)
  298. return 0;
  299. // If it isn't our block, idc
  300. if(strcmp(ce->ce_varname, MYCONF))
  301. return 0;
  302. i = 0;
  303. // Loop dat shyte fam
  304. for(cep = ce->ce_entries; cep; cep = cep->ce_next) {
  305. // Do we even have a valid name l0l?
  306. if(!cep || !cep->ce_varname)
  307. continue;
  308. if(!strcmp(cep->ce_varname, "port")) {
  309. if(!cep->ce_vardata)
  310. continue;
  311. pot = atoi(cep->ce_vardata);
  312. if(pot > 1024 && pot <= 65535) // Got a valid port
  313. WSOnlyPorts[i++] = pot;
  314. continue;
  315. }
  316. if(!strcmp(cep->ce_varname, "zlinetime")) {
  317. zlineTime = atoi(cep->ce_vardata);
  318. continue;
  319. }
  320. // Nesting
  321. if(!strcmp(cep->ce_varname, "channels")) {
  322. // Loop 'em
  323. for(cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) {
  324. if(!cep2->ce_varname)
  325. continue; // Next iteration imo tbh
  326. // Gotta get em length yo
  327. size_t namelen = sizeof(char) * (strlen(cep2->ce_varname) + 1);
  328. // Allocate mem0ry for the current entry
  329. *chan = malloc(sizeof(muhchan));
  330. // Allocate/initialise shit here
  331. (*chan)->name = malloc(namelen);
  332. (*chan)->next = NULL;
  333. // Copy that shit fam
  334. strncpy((*chan)->name, cep2->ce_varname, namelen);
  335. // Premium linked list fam
  336. if(last)
  337. last->next = *chan;
  338. last = *chan;
  339. chan = &(*chan)->next;
  340. }
  341. continue;
  342. }
  343. }
  344. return 1; // We good
  345. }
  346. int websocket_restrict_rehash(void) {
  347. // Reset config defaults
  348. c_numPorts = 0;
  349. zlineTime = 60;
  350. return HOOK_CONTINUE;
  351. }