]> jfr.im git - irc/freenode/syn.git/blob - facilities.c
add licence info
[irc/freenode/syn.git] / facilities.c
1 /*
2 * syn: a utility bot to manage IRC network access
3 * Copyright (C) 2009-2016 Stephen Bennett
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19
20 #include "atheme.h"
21 #include "pmodule.h"
22
23 #include "syn.h"
24
25 void facility_newuser(hook_user_nick_t *data);
26
27 void syn_cmd_facility(sourceinfo_t *si, int parc, char **parv);
28 static void syn_facility_help(sourceinfo_t *si, const char *subcmd);
29
30 mowgli_patricia_t *syn_facility_cmds;
31
32 command_t syn_facility = { "FACILITY", N_("Inspects or modifies facility lists"), "syn:facility", 4, syn_cmd_facility, { .func = syn_facility_help } };
33
34 static void syn_cmd_facility_list(sourceinfo_t *si, int parc, char **parv);
35 static void syn_cmd_facility_add(sourceinfo_t *si, int parc, char **parv);
36 static void syn_cmd_facility_del(sourceinfo_t *si, int parc, char **parv);
37 static void syn_cmd_facility_set(sourceinfo_t *si, int parc, char **parv);
38 static void syn_cmd_facility_addbl(sourceinfo_t *si, int parc, char **parv);
39 static void syn_cmd_facility_rmbl(sourceinfo_t *si, int parc, char **parv);
40 static void syn_cmd_facility_show(sourceinfo_t *si, int parc, char **parv);
41
42 command_t syn_facility_list = { "LIST", N_("Displays defined facilities"), "syn:facility", 1, syn_cmd_facility_list, { .path = "syn/facility_list" } };
43 command_t syn_facility_add = { "ADD", N_("Configures a new facility"), "syn:facility:admin", 2, syn_cmd_facility_add, { .path = "syn/facility_add" } };
44 command_t syn_facility_del = { "DEL", N_("Removes a configured facility"), "syn:facility:admin", 1, syn_cmd_facility_del, { .path = "syn/facility_del" } };
45 command_t syn_facility_set = { "SET", N_("Modifies a configured facility"), "syn:facility:admin", 3, syn_cmd_facility_set, { .path = "syn/facility_set" } };
46 command_t syn_facility_addbl = { "ADDBL", N_("Adds a blacklist entry for a faciltiy"), "syn:facility", 2, syn_cmd_facility_addbl, { .path = "syn/facility_addbl" } };
47 command_t syn_facility_rmbl = { "RMBL", N_("Removes a blacklist entry from a facility"), "syn:facility", 2, syn_cmd_facility_rmbl, { .path = "syn/facility_rmbl" } };
48 command_t syn_facility_show = { "SHOW", N_("Displays information about a facility"), "syn:facility", 1, syn_cmd_facility_show, { .path = "syn/facility_show" } };
49
50 typedef enum
51 {
52 facility_cloak_undefined,
53 facility_cloak_none,
54 facility_cloak_random,
55 facility_cloak_hex_ident,
56 facility_cloak_ident,
57 facility_cloak_account,
58 } facility_cloak_type;
59
60 static struct {
61 const char *name;
62 facility_cloak_type value;
63 } cloak_type_map[] = {
64 { "undefined", facility_cloak_undefined },
65 { "none", facility_cloak_none },
66 { "random", facility_cloak_random },
67 { "hexip", facility_cloak_hex_ident },
68 { "ident", facility_cloak_ident },
69 { "account", facility_cloak_account },
70 { NULL, 0 }
71 };
72
73 static facility_cloak_type cloak_type_from_string(const char *name)
74 {
75 if (name == NULL)
76 return facility_cloak_undefined;
77
78 for (int i=0; cloak_type_map[i].name != NULL; ++i)
79 {
80 if (0 == strcmp(name, cloak_type_map[i].name))
81 return cloak_type_map[i].value;
82 }
83 return facility_cloak_undefined;
84 }
85
86 static const char *string_from_cloak_type(facility_cloak_type type)
87 {
88 for (int i=0; cloak_type_map[i].name != NULL; ++i)
89 {
90 if (type == cloak_type_map[i].value)
91 return cloak_type_map[i].name;
92 }
93 return "unknown";
94 }
95
96 typedef struct
97 {
98 char hostpart[HOSTLEN];
99
100 int blocked;
101 char *blockmessage;
102
103 char *throttlemessage;
104 int throttle[2];
105
106 facility_cloak_type cloaking;
107 int cloak_override;
108
109 mowgli_list_t blacklist;
110
111 time_t throttle_latest;
112 } facility_t;
113
114 typedef struct
115 {
116 char *regex;
117 atheme_regex_t *re;
118 } bl_entry_t;
119
120 mowgli_patricia_t *facilities;
121
122 unsigned int block_report_interval = 60;
123 time_t last_block_report = 0;
124
125 mowgli_heap_t *facility_heap, *blacklist_heap;
126
127 // Horrible hack to work around the race condition when
128 // NickServ and syn both cloak somebody.
129 static void on_host_change(void *vdata);
130
131 void free_facility(const char *key, void *facility, void *unused)
132 {
133 facility_t *f = facility;
134
135 if (f->blockmessage)
136 free(f->blockmessage);
137 if (f->throttlemessage)
138 free(f->throttlemessage);
139
140 mowgli_node_t *n, *tn;
141 MOWGLI_LIST_FOREACH_SAFE(n, tn, f->blacklist.head)
142 {
143 bl_entry_t *bl = n->data;
144 free(bl->regex);
145 regex_destroy(bl->re);
146
147 mowgli_heap_free(blacklist_heap, bl);
148
149 mowgli_node_delete(n, &f->blacklist);
150 mowgli_node_free(n);
151 }
152
153 mowgli_heap_free(facility_heap, f);
154 }
155
156 void load_facilities()
157 {
158 FILE *f = fopen(DATADIR "/facilities.db", "r");
159 if (!f)
160 {
161 slog(LG_DEBUG, "Couldn't open facilities list: %s", strerror(errno));
162 return;
163 }
164
165 facility_t *curr_facility = NULL;
166
167 char line[BUFSIZE];
168 while (fgets(line, BUFSIZE, f))
169 {
170 char *token = strtok(line, " ");
171 strip(token);
172 if (0 == strcmp(token, "F"))
173 {
174 curr_facility = mowgli_heap_alloc(facility_heap);
175 char *hostpart = strtok(NULL, " ");
176 char *cloaking = strtok(NULL, " ");
177 char *blocked = strtok(NULL, " ");
178 char *throttle0 = strtok(NULL, " ");
179 char *throttle1 = strtok(NULL, " ");
180
181 char *cloak_override = strtok(NULL, " ");
182
183 strncpy(curr_facility->hostpart, hostpart, HOSTLEN);
184 curr_facility->cloaking = cloak_type_from_string(cloaking);
185 curr_facility->blocked = atoi(blocked);
186 curr_facility->throttle[0] = atoi(throttle0);
187 curr_facility->throttle[1] = atoi(throttle1);
188 curr_facility->cloak_override = cloak_override ? atoi(cloak_override) : 0;
189
190 mowgli_patricia_add(facilities, curr_facility->hostpart, curr_facility);
191 continue;
192 }
193
194 if (curr_facility == NULL)
195 continue;
196
197 if (0 == strcmp(token, "BM"))
198 {
199 char *msg = strtok(NULL, "");
200 if (msg)
201 {
202 strip(msg);
203 curr_facility->blockmessage = sstrdup(msg);
204 }
205 }
206 else if (0 == strcmp(token, "TM"))
207 {
208 char *msg = strtok(NULL, "");
209 if (msg)
210 {
211 strip(msg);
212 curr_facility->throttlemessage = sstrdup(msg);
213 }
214 }
215 else if (0 == strcmp(token, "BL"))
216 {
217 char *regex = strtok(NULL, "");
218 if (!regex)
219 continue;
220
221 strip(regex);
222
223 bl_entry_t *bl = mowgli_heap_alloc(blacklist_heap);
224 bl->regex = sstrdup(regex);
225 bl->re = regex_create(bl->regex, AREGEX_ICASE | AREGEX_PCRE);
226 mowgli_node_add(bl, mowgli_node_create(), &curr_facility->blacklist);
227 }
228 }
229 fclose(f);
230 }
231
232 void save_facilities()
233 {
234 FILE *db = fopen(DATADIR "/facilities.db.tmp", "w");
235
236 if (!db)
237 {
238 slog(LG_ERROR, "save_facilities(): cannot open facilities database for writing: %s", strerror(errno));
239 return;
240 }
241
242 mowgli_patricia_iteration_state_t state;
243 facility_t *f;
244 MOWGLI_PATRICIA_FOREACH(f, &state, facilities)
245 {
246 fprintf(db, "F %s %s %d %d %d %d\n", f->hostpart, string_from_cloak_type(f->cloaking),
247 f->blocked, f->throttle[0], f->throttle[1], f->cloak_override);
248 if (f->blockmessage)
249 fprintf(db, "BM %s\n", f->blockmessage);
250 if (f->throttlemessage)
251 fprintf(db, "TM %s\n", f->throttlemessage);
252 mowgli_node_t *n;
253 MOWGLI_LIST_FOREACH(n, f->blacklist.head)
254 {
255 bl_entry_t *bl = n->data;
256 fprintf(db, "BL %s\n", bl->regex);
257 }
258 }
259 fclose(db);
260 if (rename(DATADIR "/facilities.db.tmp", DATADIR "/facilities.db") < 0)
261 {
262 slog(LG_ERROR, "Couldn't rename facilities.db.tmp to facilities.db: %s", strerror(errno));
263 }
264 }
265
266 static void mod_init(module_t *m)
267 {
268 use_syn_main_symbols(m);
269 use_syn_util_symbols(m);
270 use_syn_kline_symbols(m);
271
272 hook_add_event("user_add");
273 hook_add_user_add(facility_newuser);
274 hook_add_event("incoming_host_change");
275 hook_add_hook("incoming_host_change", on_host_change);
276
277 service_named_bind_command("syn", &syn_facility);
278
279 syn_facility_cmds = mowgli_patricia_create(strcasecanon);
280 command_add(&syn_facility_list, syn_facility_cmds);
281 command_add(&syn_facility_add, syn_facility_cmds);
282 command_add(&syn_facility_del, syn_facility_cmds);
283 command_add(&syn_facility_set, syn_facility_cmds);
284 command_add(&syn_facility_addbl, syn_facility_cmds);
285 command_add(&syn_facility_rmbl, syn_facility_cmds);
286 command_add(&syn_facility_show, syn_facility_cmds);
287
288 facility_heap = mowgli_heap_create(sizeof(facility_t), 64, BH_NOW);
289 blacklist_heap = mowgli_heap_create(sizeof(bl_entry_t), 64, BH_NOW);
290 facilities = mowgli_patricia_create(strcasecanon);
291
292 add_uint_conf_item("FACILITY_REPORT_RATE", &syn->conf_table, 0, &block_report_interval, 0, 3600, 60);
293
294 load_facilities();
295 }
296
297 static void mod_deinit(module_unload_intent_t intent)
298 {
299 save_facilities();
300
301 del_conf_item("FACILITY_REPORT_RATE", &syn->conf_table);
302
303 mowgli_patricia_destroy(facilities, free_facility, NULL);
304 mowgli_heap_destroy(facility_heap);
305 mowgli_heap_destroy(blacklist_heap);
306
307 mowgli_patricia_destroy(syn_facility_cmds, NULL, NULL);
308
309 service_named_unbind_command("syn", &syn_facility);
310
311 hook_del_user_add(facility_newuser);
312 hook_del_hook("incoming_host_change", on_host_change);
313 }
314
315 static void facility_set_cloak(user_t *u, const char * cloak, bool cloak_override)
316 {
317 metadata_add(u, "syn:facility-cloak", cloak);
318
319 if (cloak_override)
320 metadata_add(u, "syn:facility-cloak-override", "1");
321
322 // Check whether they've already been cloaked. If vhost != host, and
323 // vhost isn't unaffiliated/*, then they have a project cloak that we shouldn't override.
324 // If vhost != host, and vhost *is* unaffiliated but we don't override unaffiliated cloaks,
325 // don't do so either.
326 if ((strncmp(u->vhost, "unaffiliated/", 13) != 0 || !cloak_override) &&
327 strncmp(u->vhost, u->host, HOSTLEN) != 0)
328 return;
329
330 // Don't send out a no-op cloak change either
331 if (strcmp(u->vhost, cloak))
332 user_sethost(syn->me, u, cloak);
333 }
334
335 void facility_newuser(hook_user_nick_t *data)
336 {
337 user_t *u = data->u;
338 facility_t *f;
339 mowgli_patricia_iteration_state_t state;
340
341 /* If the user has already been killed, don't try to do anything */
342 if (!u)
343 return;
344
345 int blocked = 0, throttled = 0, blacklisted = 0, cloak_override = 0;
346 char *blockmessage = NULL, *throttlemessage = NULL;
347 facility_cloak_type cloak = facility_cloak_none;
348 facility_t *blocking_facility = NULL, *throttling_facility = NULL, *cloaking_facility = NULL;
349 char *blocking_regex = NULL;
350
351 int dospam = 0;
352
353 MOWGLI_PATRICIA_FOREACH(f, &state, facilities)
354 {
355 if (0 != strncasecmp(u->host, f->hostpart, strlen(f->hostpart)))
356 continue;
357
358 syn_debug(2, "User %s matches facility %s", u->nick, f->hostpart);
359 dospam = 1;
360 u->flags |= SYN_UF_FACILITY_USER;
361
362 if (f->blocked > 0)
363 {
364 blocked = 1;
365 blocking_facility = f;
366 }
367 if (f->blocked < 0)
368 blocked = 0;
369
370 if (f->blockmessage)
371 blockmessage = f->blockmessage;
372
373 if (f->throttle[0] > 0 && !me.bursting)
374 {
375 if (f->throttle_latest < CURRTIME)
376 f->throttle_latest = CURRTIME;
377
378 f->throttle_latest += f->throttle[0];
379
380 if (f->throttle_latest > (f->throttle[1] * f->throttle[0]) + CURRTIME)
381 {
382 throttled = 1;
383 throttling_facility = f;
384 throttlemessage = f->throttlemessage;
385 }
386 }
387
388 if (f->cloaking != facility_cloak_undefined)
389 {
390 cloak = f->cloaking;
391 cloaking_facility = f;
392 }
393
394 if (f->cloak_override)
395 cloak_override = f->cloak_override;
396
397 char nuh[NICKLEN+USERLEN+HOSTLEN+GECOSLEN];
398 snprintf(nuh, sizeof(nuh), "%s!%s@%s %s", u->nick, u->user, u->host, u->gecos);
399
400 mowgli_node_t *n;
401 MOWGLI_LIST_FOREACH(n, f->blacklist.head)
402 {
403 bl_entry_t *bl = n->data;
404 if (!bl->re)
405 continue;
406 if (regex_match(bl->re, nuh))
407 {
408 syn_debug(1, "User %s blacklisted in %s (%s)", u->nick, f->hostpart, bl->regex);
409 blocking_facility = f;
410 blocking_regex = bl->regex;
411 blacklisted = 1;
412 break;
413 }
414 }
415
416 if (blacklisted > 0)
417 break;
418 }
419
420 if (throttled)
421 {
422 if (last_block_report + block_report_interval < CURRTIME)
423 {
424 last_block_report = CURRTIME;
425 syn_report("Killing user %s due to throttle [%d,%d] on facility %s",
426 u->nick, throttling_facility->throttle[0], throttling_facility->throttle[1],
427 throttling_facility->hostpart);
428 }
429 syn_kill2(u, "Throttled", "%s", throttlemessage);
430 data->u = NULL;
431 return;
432 }
433
434 if (blocked)
435 {
436 if (last_block_report + block_report_interval < CURRTIME)
437 {
438 last_block_report = CURRTIME;
439 syn_report("Killing user %s; blocked by facility %s",
440 u->nick, blocking_facility ? blocking_facility->hostpart : "(unknown)");
441 }
442 syn_kill2(u, "Facility Blocked", "%s", blockmessage);
443 data->u = NULL;
444 return;
445 }
446
447 if (blacklisted)
448 {
449 if (last_block_report + block_report_interval < CURRTIME)
450 {
451 last_block_report = CURRTIME;
452 syn_report("Killing user %s; blacklisted in facility %s (%s)",
453 u->nick, blocking_facility->hostpart, blocking_regex);
454 }
455 syn_kill(u, "%s", blockmessage);
456 data->u = NULL;
457 return;
458 }
459
460 if (cloak_override > 0)
461 cloak_override = 1;
462 else
463 cloak_override = 0;
464
465 char new_vhost[HOSTLEN];
466 mowgli_strlcpy(new_vhost, u->host, HOSTLEN);
467 switch (cloak)
468 {
469 case facility_cloak_none:
470 case facility_cloak_undefined:
471 break;
472
473 case facility_cloak_account:
474 {
475 facility_set_cloak(u, u->host, cloak_override);
476 break;
477 }
478
479 case facility_cloak_hex_ident:
480 {
481 char *ipstart = strstr(new_vhost, "session");
482 if (ipstart == NULL)
483 {
484 syn_debug(2, "Hex IP cloaking used for %s, but I couldn't find a session marker in %s", u->nick, new_vhost);
485 break;
486 }
487 const char *ident = u->user;
488 if (*ident == '~')
489 ++ident;
490 const char *ip = decode_hex_ip(ident);
491
492 if (ip)
493 {
494 strncpy(ipstart, "ip.", new_vhost + HOSTLEN - ipstart);
495 ipstart += 3;
496 strncpy(ipstart, ip, new_vhost + HOSTLEN - ipstart);
497 facility_set_cloak(u, new_vhost, cloak_override);
498 }
499 else
500 {
501 syn_report("Killing user %s; facility %s requires hexip but none was found",
502 u->nick, cloaking_facility->hostpart);
503 // If we couldn't decode an IP, block the connection
504 syn_kill2(u, "No IP address supplied", "Your gateway requires an underlying IP address to be supplied, which could not be found.");
505 data->u = NULL;
506 return;
507 }
508 break;
509
510 }
511 case facility_cloak_ident:
512 {
513 char *identstart = strstr(new_vhost, "session");
514 if (identstart == NULL)
515 {
516 syn_debug(2, "Ident cloaking used for %s, but I couldn't find a session marker in %s", u->nick, new_vhost);
517 break;
518 }
519
520 const char *ident = u->user;
521 if (*ident == '~')
522 ++ident;
523
524 const char *suffix = encode_ident_for_host(ident);
525 if (!suffix)
526 {
527 syn_debug(2, "Cannot host-encode ident %s for user %s, giving up", ident, u->nick);
528 break;
529 }
530
531 strncpy(identstart, suffix, new_vhost + HOSTLEN - identstart);
532 facility_set_cloak(u, new_vhost, cloak_override);
533 break;
534 }
535 case facility_cloak_random:
536 {
537 char *randstart = strstr(new_vhost, "session");
538 if (randstart == NULL)
539 {
540 syn_debug(2, "Random cloaking used for %s, but I couldn't find a session marker in %s", u->nick, new_vhost);
541 break;
542 }
543 strncpy(randstart, get_random_host_part(u), new_vhost + HOSTLEN - randstart);
544 facility_set_cloak(u, new_vhost, cloak_override);
545 break;
546 }
547 }
548
549 if (dospam && !me.bursting)
550 syn_report2(2, "Allowed %s!%s@%s [%s]", u->nick, u->user, u->vhost, u->gecos);
551 }
552
553 static void syn_facility_help(sourceinfo_t *si, const char *subcmd)
554 {
555 if (!subcmd)
556 {
557 // This can't go into one big command_success_nodata with \n in it
558 // because syn defines its own sourceinfo vtable which doesn't bother
559 // to split on \n in its command_success_nodata implementation
560 command_success_nodata(si, _("***** \2%s Help\2 *****"), si->service->nick);
561 command_success_nodata(si, _("Help for \2FACILITY\2:"));
562 command_success_nodata(si, " ");
563 command_success_nodata(si, "The FACILITY command displays and manipulates facility");
564 command_success_nodata(si, "definitions.");
565 command_success_nodata(si, " ");
566 command_success_nodata(si, "A facility is roughly a related group of gateways, defined");
567 command_success_nodata(si, "by a host prefix. Any new client connecting is checked");
568 command_success_nodata(si, "against the list of facilities, and various actions may be");
569 command_success_nodata(si, "taken based on this.");
570 command_success_nodata(si, " ");
571 command_success_nodata(si, "Each facility has some or all of the following information");
572 command_success_nodata(si, "defined:");
573 command_success_nodata(si, " ");
574 command_success_nodata(si, " - The hostname prefix");
575 command_success_nodata(si, " - Whether this facility is currently blocked");
576 command_success_nodata(si, " - The message to send to clients blocked by this facility.");
577 command_success_nodata(si, " - A throttle limit. This is of the form x,y and translates");
578 command_success_nodata(si, " rougly to y clients per x*y seconds.");
579 command_success_nodata(si, " - The message to send to clients denied because of this");
580 command_success_nodata(si, " facility's throttle.");
581 command_success_nodata(si, " - The cloaking scheme applied to matching clients.");
582 command_success_nodata(si, " - Whether facility cloaks will override unaffiliated cloaks.");
583 command_success_nodata(si, " - A blacklist of regular expressions. If any of these match");
584 command_success_nodata(si, " a client that matches this facility, it will be denied.");
585 command_success_nodata(si, " ");
586 command_success_nodata(si, "Facilities are checked in order from least specific to most");
587 command_success_nodata(si, "specific. If any facility is blocked, the client is killed,");
588 command_success_nodata(si, "with the exception that a negative block value can override");
589 command_success_nodata(si, "a positive one from a more general facility. If the client");
590 command_success_nodata(si, "is determined to be blocked, the most specific configured");
591 command_success_nodata(si, "block message is used.");
592 command_success_nodata(si, " ");
593 command_success_nodata(si, "The throttle settings for every matching facility are");
594 command_success_nodata(si, "checked and updated, even if the client is blocked. If the");
595 command_success_nodata(si, "client is denied due to throttling, the most specific");
596 command_success_nodata(si, "configured throttle message is used.");
597 command_success_nodata(si, " ");
598 command_success_nodata(si, "For every matching facility, the client is checked against");
599 command_success_nodata(si, "that facility's blacklist. If any of those regular");
600 command_success_nodata(si, "expressions matches, then the client is denied, and the most");
601 command_success_nodata(si, "specific block message so far encountered is used.");
602 command_success_nodata(si, " ");
603 command_success_nodata(si, "If the client is allowed to connect, then the cloaking");
604 command_success_nodata(si, "setting is used to determine whether to modify the client's");
605 command_success_nodata(si, "visible host name. Again the most specific defined value");
606 command_success_nodata(si, "is used. Possible values are:");
607 command_success_nodata(si, " ");
608 command_success_nodata(si, " - none. Leave the client's host alone.");
609 command_success_nodata(si, " - random. A 'session' marker in the client's host is");
610 command_success_nodata(si, " replaced by a random text string.");
611 command_success_nodata(si, " - hexip. The user's ident is treated as a hex-encoded IP");
612 command_success_nodata(si, " address, as used by several web gateways. A 'session'");
613 command_success_nodata(si, " marker in the user's host is replaced by ");
614 command_success_nodata(si, " 'ip.<decoded ip>'. If the ident cannot be decoded this");
615 command_success_nodata(si, " way, it falls back to the random method.");
616 command_success_nodata(si, " - account. This is meant to be used with a sasl_usercloak");
617 command_success_nodata(si, " auth {} block and applies the host generated by the ircd");
618 command_success_nodata(si, " as gateway cloak, overriding unaffiliated cloaks.");
619 command_success_nodata(si, " ");
620 command_success_nodata(si, "If no matching facility has a defined cloaking method, then");
621 command_success_nodata(si, "the default is \2none\2.");
622 command_success_nodata(si, " ");
623 command_success_nodata(si, "As with the 'blocked' setting, the override_unaff setting");
624 command_success_nodata(si, "may be set to 1 to disallow unaffiliated cloaks or -1");
625 command_success_nodata(si, "to specifically allow them even though a more general");
626 command_success_nodata(si, "facility would not allow them. The default is to allow them.");
627 command_help(si, syn_facility_cmds);
628 command_success_nodata(si, " ");
629 command_success_nodata(si, _("For more information, use \2/msg %s HELP FACILITY \37command\37\2."), si->service->nick);
630 command_success_nodata(si, _("***** \2End of Help\2 *****"));
631 }
632 else
633 help_display_as_subcmd(si, si->service, "FACILITY", subcmd, syn_facility_cmds);
634 }
635
636 void syn_cmd_facility(sourceinfo_t *si, int parc, char **parv)
637 {
638 command_t *c;
639 char *cmd = parv[0];
640
641 if (!cmd)
642 {
643 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY");
644 command_fail(si, fault_needmoreparams, "Syntax: FACILITY LIST|ADD|DEL|SET|ADDBL|RMBL [parameters]");
645 return;
646 }
647
648 c = command_find(syn_facility_cmds, cmd);
649 if (c == NULL)
650 {
651 command_fail(si, fault_badparams, "Invalid command. Possible commands are LIST ADD DEL SET ADDBL RMBL");
652 return;
653 }
654
655 command_exec(si->service, si, c, parc - 1, parv + 1);
656 }
657
658 void syn_cmd_facility_list(sourceinfo_t *si, int parc, char **parv)
659 {
660 char *match = NULL;
661 if (parc > 0)
662 match = parv[0];
663
664 int count = 0;
665 facility_t *f;
666 mowgli_patricia_iteration_state_t state;
667 MOWGLI_PATRICIA_FOREACH(f, &state, facilities)
668 {
669 if (match && 0 != strncmp(match, f->hostpart, strlen(match)))
670 continue;
671
672 command_success_nodata(si, "[%d] %s (cloaking %s, %s, throttle %d/%d)",
673 ++count, f->hostpart, string_from_cloak_type(f->cloaking),
674 (f->blocked > 0 ? "blocked" : (f->blocked < 0 ? "unblocked" : "not blocked")),
675 f->throttle[0], f->throttle[1]);
676 }
677
678 command_success_nodata(si, "%d facilit%s configured", count, count == 1 ? "y" : "ies");
679 }
680
681 void syn_cmd_facility_add(sourceinfo_t *si, int parc, char **parv)
682 {
683 if (parc < 1)
684 {
685 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY ADD");
686 command_fail(si, fault_needmoreparams, "Syntax: FACILITY ADD <hostpart> [cloaktype]");
687 return;
688 }
689
690 const char *hostpart = parv[0];
691 facility_cloak_type cloak = cloak_type_from_string(parc > 1 ? parv[1] : NULL);
692
693 facility_t *f = mowgli_heap_alloc(facility_heap);
694 strncpy(f->hostpart, hostpart, HOSTLEN);
695 f->cloaking = cloak;
696
697 mowgli_patricia_add(facilities, f->hostpart, f);
698
699 syn_report("\002FACILITY ADD\002 %s by %s", f->hostpart, get_oper_name(si));
700
701 command_success_nodata(si, "Added facility %s", f->hostpart);
702
703 save_facilities();
704 }
705
706 void syn_cmd_facility_del(sourceinfo_t *si, int parc, char **parv)
707 {
708 if (parc < 1)
709 {
710 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY DEL");
711 command_fail(si, fault_needmoreparams, "Syntax: FACILITY DEL <hostpart>");
712 return;
713 }
714
715 facility_t *f = mowgli_patricia_retrieve(facilities, parv[0]);
716
717 if (f == NULL)
718 {
719 command_fail(si, fault_badparams, "No such facility %s was found.", parv[0]);
720 return;
721 }
722
723 free_facility(NULL, f, NULL);
724 mowgli_patricia_delete(facilities, parv[0]);
725
726 syn_report("\002FACILITY DEL\002 %s by %s", parv[0], get_oper_name(si));
727
728 command_success_nodata(si, "Facility %s deleted", parv[0]);
729
730 save_facilities();
731 }
732
733
734
735 void syn_cmd_facility_set(sourceinfo_t *si, int parc, char **parv)
736 {
737 if (parc < 3)
738 {
739 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY SET");
740 command_fail(si, fault_needmoreparams, "Syntax: FACILITY SET <hostpart> <setting> [arguments]");
741 return;
742 }
743
744 facility_t *f = mowgli_patricia_retrieve(facilities, parv[0]);
745 if (f== NULL)
746 {
747 command_fail(si, fault_badparams, "No such facility %s", parv[0]);
748 return;
749 }
750
751 if (0 == strcasecmp(parv[1], "cloaking"))
752 {
753 facility_cloak_type cloak = cloak_type_from_string(parv[2]);
754 f->cloaking = cloak;
755
756 syn_report("\002FACILITY SET\002 cloaking->%s for %s by %s",
757 string_from_cloak_type(cloak), f->hostpart, get_oper_name(si));
758 command_success_nodata(si, "Cloaking method for %s set to %s", f->hostpart, string_from_cloak_type(cloak));
759
760 save_facilities();
761 return;
762 }
763
764 if (0 == strcasecmp(parv[1], "override_unaff"))
765 {
766 if (parc < 3)
767 f->cloak_override = 0;
768 else
769 f->cloak_override = atoi(parv[2]);
770
771 syn_report("\002FACILITY SET\002 override_unaff->%d for %s by %s",
772 f->cloak_override, f->hostpart, get_oper_name(si));
773 command_success_nodata(si, "Overriding unaffiliated cloaks for %s was set to %d", f->hostpart, f->cloak_override);
774
775 save_facilities();
776 return;
777 }
778
779 if (0 == strcasecmp(parv[1], "blocked"))
780 {
781 if (parc < 3)
782 f->blocked = 0;
783 else
784 f->blocked = atoi(parv[2]);
785
786 syn_report("\002FACILITY SET\002 blocked->%d for %s by %s",
787 f->blocked, f->hostpart, get_oper_name(si));
788 command_success_nodata(si, "Blocked for %s was set to %d", f->hostpart, f->blocked);
789
790 save_facilities();
791 return;
792 }
793
794 if (0 == strcasecmp(parv[1], "throttle"))
795 {
796 char buf[32];
797 strncpy(buf, parv[2], 32);
798 char *p = strchr(buf, ',');
799
800 if (p == NULL)
801 {
802 command_fail(si, fault_badparams, STR_INVALID_PARAMS, "FACILITY SET THROTTLE");
803 command_fail(si, fault_badparams, "Syntax: FACILITY SET <name> THROTTLE n,m");
804 return;
805 }
806 *p++ = '\0';
807
808 f->throttle[0] = atoi(buf);
809 f->throttle[1] = atoi(p);
810
811 syn_report("\002FACILITY SET\002 throttle->%d/%d for %s by %s",
812 f->throttle[0], f->throttle[1], f->hostpart, get_oper_name(si));
813 command_success_nodata(si, "Throttle for %s was set to %d seconds, burst %d",
814 f->hostpart, f->throttle[0], f->throttle[1]);
815
816 save_facilities();
817 return;
818 }
819
820 if (0 == strcasecmp(parv[1], "blockmessage"))
821 {
822 if (f->blockmessage)
823 free(f->blockmessage);
824
825 if (0 == strcmp(parv[2], "-"))
826 f->blockmessage = NULL;
827 else
828 f->blockmessage = sstrdup(parv[2]);
829
830 syn_report("\002FACILITY SET\002 block message->%s for %s by %s",
831 f->blockmessage, f->hostpart, get_oper_name(si));
832 command_success_nodata(si, "Block message for %s was set to %s", f->hostpart, f->blockmessage);
833
834 save_facilities();
835 return;
836 }
837
838 if (0 == strcasecmp(parv[1], "throttlemessage"))
839 {
840 if (f->throttlemessage)
841 free(f->throttlemessage);
842
843 if (0 == strcmp(parv[2], "-"))
844 f->throttlemessage = NULL;
845 else
846 f->throttlemessage = sstrdup(parv[2]);
847
848 syn_report("\002FACILITY SET\002 throttle message->%s for %s by %s",
849 f->throttlemessage, f->hostpart, get_oper_name(si));
850 command_success_nodata(si, "Throttle message for %s was set to %s", f->hostpart, f->throttlemessage);
851
852 save_facilities();
853 return;
854 }
855
856 command_fail(si, fault_badparams, "Unknown setting name");
857 }
858
859
860
861 void syn_cmd_facility_addbl(sourceinfo_t *si, int parc, char **parv)
862 {
863 if (parc < 2)
864 {
865 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY ADDBL");
866 command_fail(si, fault_needmoreparams, "Syntax: FACILITY ADDBL <hostpart> <regex>");
867 return;
868 }
869
870 facility_t *f = mowgli_patricia_retrieve(facilities, parv[0]);
871 if (f== NULL)
872 {
873 command_fail(si, fault_badparams, "No such facility %s", parv[0]);
874 return;
875 }
876
877 bl_entry_t *bl = mowgli_heap_alloc(blacklist_heap);
878 bl->regex = sstrdup(parv[1]);
879 bl->re = regex_create(bl->regex, AREGEX_ICASE | AREGEX_PCRE);
880
881 if (! bl->re)
882 {
883 command_fail(si, fault_badparams, "Failed to compile regex \"%s\"", bl->regex);
884 return;
885 }
886
887 mowgli_node_add(bl, mowgli_node_create(), &f->blacklist);
888
889 syn_report("\002FACILITY ADDBL\002 %s to %s by %s", bl->regex, f->hostpart, get_oper_name(si));
890 command_success_nodata(si, "Added blacklist \"%s\" for %s", bl->regex, f->hostpart);
891
892 save_facilities();
893 }
894
895
896
897 void syn_cmd_facility_rmbl(sourceinfo_t *si, int parc, char **parv)
898 {
899 if (parc < 2)
900 {
901 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY RMBL");
902 command_fail(si, fault_needmoreparams, "Syntax: FACILITY RMBL <hostpart> <regex>");
903 return;
904 }
905
906 facility_t *f = mowgli_patricia_retrieve(facilities, parv[0]);
907 if (f== NULL)
908 {
909 command_fail(si, fault_badparams, "No such facility %s", parv[0]);
910 return;
911 }
912
913 mowgli_node_t *n, *tn;
914 MOWGLI_LIST_FOREACH_SAFE(n, tn, f->blacklist.head)
915 {
916 bl_entry_t *bl = n->data;
917 if (0 != strcmp(parv[1], bl->regex))
918 continue;
919
920 free(bl->regex);
921 regex_destroy(bl->re);
922
923 mowgli_heap_free(blacklist_heap, bl);
924
925 mowgli_node_delete(n, &f->blacklist);
926 mowgli_node_free(n);
927
928 syn_report("\002FACILITY RMBL\002 %s from %s by %s", parv[1], f->hostpart, get_oper_name(si));
929 command_success_nodata(si, "Removed blacklist \"%s\" from %s", parv[1], f->hostpart);
930 }
931
932 save_facilities();
933 }
934
935 void syn_cmd_facility_show(sourceinfo_t *si, int parc, char **parv)
936 {
937 if (parc < 1)
938 {
939 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "FACILITY SHOW");
940 command_fail(si, fault_needmoreparams, "Syntax: FACILITY SHOW <hostpart>");
941 return;
942 }
943
944 facility_t *f = mowgli_patricia_retrieve(facilities, parv[0]);
945 if (f== NULL)
946 {
947 command_fail(si, fault_badparams, "No such facility %s", parv[0]);
948 return;
949 }
950
951 command_success_nodata(si, "Facility %s:", f->hostpart);
952 command_success_nodata(si, " cloaking method: %s", string_from_cloak_type(f->cloaking));
953 command_success_nodata(si, " unaffiliated cloaks: %s",
954 f->cloak_override > 0 ? "disallowed" : (f->cloak_override < 0 ? "allowed" : "(see parent facility)"));
955 command_success_nodata(si, " %s, block message \"%s\"",
956 f->blocked > 0 ? "blocked" : ( f->blocked < 0 ? "unblocked" : "not blocked"),
957 f->blockmessage);
958 command_success_nodata(si, " Throttle rate %d/%d, throttle message \"%s\"",
959 f->throttle[0], f->throttle[1], f->throttlemessage);
960
961 command_success_nodata(si, "Blacklist:");
962
963 int count = 0;
964 mowgli_node_t *n;
965 MOWGLI_LIST_FOREACH(n, f->blacklist.head)
966 {
967 bl_entry_t *bl = n->data;
968 command_success_nodata(si, "[%d] %s", ++count, bl->regex);
969 }
970 command_success_nodata(si, "%d blacklist entries for %s", count, f->hostpart);
971 }
972
973 static void on_host_change(void *vdata)
974 {
975 hook_incoming_host_change_t *data = vdata;
976
977 metadata_t *md = metadata_find(data->user, "syn:facility-cloak");
978 if (!md)
979 return;
980
981 metadata_t *override = metadata_find(data->user, "syn:facility-cloak-override");
982
983 if ((0 == strncmp(data->user->vhost, "unaffiliated/", 13) && override) ||
984 0 == strncmp(data->user->vhost, data->user->host, HOSTLEN))
985 {
986 // Override the host change -- a facility cloak is being replaced by unaffiliated while we're disallowing it,
987 // or a facility by another facility (this happens when removing a nickserv account vhost while a gateway user is logged in)
988 strshare_unref(data->user->vhost);
989 data->user->vhost = strshare_get(md->value);
990 }
991 else
992 {
993 // Bounce the sethost, to fix the race condition where services and syn both set a vhost on connect.
994 }
995 sethost_sts(syn->me, data->user, data->user->vhost);
996 }
997
998 DECLARE_MODULE_V1
999 (
1000 "syn/facilities", false, mod_init, mod_deinit,
1001 "$Revision$",
1002 "Stephen Bennett <stephen -at- freenode.net>"
1003 );