fix possible crash on user deletion
[srvx.git] / src / main-common.c
1 extern FILE *replay_file;
2
3 unsigned long boot_time;
4 unsigned long burst_begin;
5 unsigned long now;
6 unsigned long burst_length;
7 struct log_type *MAIN_LOG;
8
9 int quit_services;
10 int max_cycles;
11
12 char *services_config = "srvx.conf";
13
14 char **services_argv;
15 int services_argc;
16
17 struct cManagerNode cManager;
18
19 struct policer_params *oper_policer_params, *luser_policer_params, *god_policer_params;
20
21 static const struct message_entry msgtab[] = {
22     { "MSG_NONE", "None" },
23     { "MSG_ON", "On" },
24     { "MSG_OFF", "Off" },
25     { "MSG_NEVER", "Never" },
26     { "MSG_SERVICE_IMMUNE", "$b%s$b may not be kicked, killed, banned, or deopped." },
27     { "MSG_SERVICE_PRIVILEGED", "$b%s$b is a privileged service." },
28     { "MSG_NOT_A_SERVICE", "$b%s$b is not a service bot." },
29     { "MSG_COMMAND_UNKNOWN", "$b%s$b is an unknown command." },
30     { "MSG_COMMAND_PRIVILEGED", "$b%s$b is a privileged command." },
31     { "MSG_COMMAND_DISABLED", "$b%s$b is a disabled command." },
32     { "MSG_SETTING_PRIVILEGED", "$b%s$b is a privileged setting." },
33     { "MSG_AUTHENTICATE", "You must first authenticate with $b$N$b." },
34     { "MSG_USER_AUTHENTICATE", "%s must first authenticate with $b$N$b." },
35     { "MSG_SET_EMAIL_ADDR", "You must first set your account's email address.  (Contact network staff if you cannot auth to your account.)" },
36     { "MSG_HANDLE_UNKNOWN", "Account $b%s$b has not been registered." },
37     { "MSG_NICK_UNKNOWN", "User with nick $b%s$b does not exist." },
38     { "MSG_CHANNEL_UNKNOWN", "Channel with name $b%s$b does not exist." },
39     { "MSG_SERVER_UNKNOWN", "Server with name $b%s$b does not exist or is not linked." },
40     { "MSG_MODULE_UNKNOWN", "No module has been registered with name $b%s$b." },
41     { "MSG_INVALID_MODES", "$b%s$b is an invalid set of channel modes." },
42     { "MSG_INVALID_GLINE", "Invalid G-line '%s'." },
43     { "MSG_INVALID_DURATION", "Invalid time span '%s'." },
44     { "MSG_NOT_TARGET_NAME", "You must provide the name of a channel or user." },
45     { "MSG_NOT_CHANNEL_NAME", "You must provide a valid channel name." },
46     { "MSG_INVALID_CHANNEL", "You must provide the name of a channel that exists." },
47     { "MSG_CHANNEL_ABSENT", "You aren't currently in $b%s$b." },
48     { "MSG_CHANNEL_USER_ABSENT", "$b%s$b isn't currently in $b%s$b." },
49     { "MSG_MISSING_PARAMS", "$b%s$b requires more parameters." },
50     { "MSG_DEPRECATED_COMMAND", "The $b%s$b command has been deprecated, and will be removed in the future; please use $b%s$b instead." },
51     { "MSG_OPER_SUSPENDED", "Your $b$O$b access has been suspended." },
52     { "MSG_USER_OUTRANKED", "$b%s$b outranks you (command has no effect)." },
53     { "MSG_STUPID_ACCESS_CHANGE", "Please ask someone $belse$b to demote you." },
54     { "MSG_NO_SEARCH_ACCESS", "You do not have enough access to search based on $b%s$b." },
55     { "MSG_INVALID_CRITERIA", "$b%s$b is an invalid search criteria." },
56     { "MSG_INVALID_FIELD", "$b%s$b is an invalid search field." },
57     { "MSG_MATCH_COUNT", "Found $b%u$b matches." },
58     { "MSG_NO_MATCHES", "Nothing matched the criteria of your search." },
59     { "MSG_TOPIC_UNKNOWN", "No help on that topic." },
60     { "MSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
61     { "MSG_INTERNAL_FAILURE", "Your command could not be processed due to an internal failure." },
62     { "MSG_DB_UNKNOWN", "I do not know of a database named %s." },
63     { "MSG_DB_IS_MONDO", "Database %s is in the \"mondo\" database and cannot be written separately." },
64     { "MSG_DB_WRITE_ERROR", "Error while writing database %s." },
65     { "MSG_DB_WROTE_DB", "Wrote database %s (in %lu.%06lu seconds)." },
66     { "MSG_DB_WROTE_ALL", "Wrote all databases (in %lu.%06lu seconds)." },
67     { "MSG_AND", "and" },
68     { "MSG_0_SECONDS", "0 seconds" },
69     { "MSG_YEAR", "year" },
70     { "MSG_YEARS", "years" },
71     { "MSG_WEEK", "week" },
72     { "MSG_WEEKS", "weeks" },
73     { "MSG_DAY", "day" },
74     { "MSG_DAYS", "days" },
75     { "MSG_HOUR", "hour" },
76     { "MSG_HOURS", "hours" },
77     { "MSG_MINUTE", "minute" },
78     { "MSG_MINUTES", "minutes" },
79     { "MSG_SECOND", "second" },
80     { "MSG_SECONDS", "seconds" },
81     { NULL, NULL }
82 };
83
84 void uplink_select(char *name);
85
86 static int
87 uplink_insert(const char *key, void *data, UNUSED_ARG(void *extra))
88 {
89     struct uplinkNode *uplink = malloc(sizeof(struct uplinkNode));
90     struct record_data *rd = data;
91     struct addrinfo hints, *ai;
92     int enabled = 1;
93     char *str;
94
95     if(!uplink)
96     {
97         return 0;
98     }
99
100     uplink->name = (char *)key;
101     uplink->host = database_get_data(rd->d.object, "address", RECDB_QSTRING);
102
103     str = database_get_data(rd->d.object, "port", RECDB_QSTRING);
104     uplink->port = str ? atoi(str) : 6667;
105     uplink->password = database_get_data(rd->d.object, "password", RECDB_QSTRING);
106     uplink->their_password = database_get_data(rd->d.object, "uplink_password", RECDB_QSTRING);
107
108     str = database_get_data(rd->d.object, "enabled", RECDB_QSTRING);
109     if(str)
110     {
111         enabled = atoi(str) ? 1 : 0;
112     }
113
114     cManager.enabled += enabled;
115
116     str = database_get_data(rd->d.object, "max_tries", RECDB_QSTRING);
117     uplink->max_tries = str ? atoi(str) : 3;
118     uplink->flags = enabled ? 0 : UPLINK_UNAVAILABLE;
119     uplink->state = DISCONNECTED;
120     uplink->tries = 0;
121
122     str = database_get_data(rd->d.object, "bind_address", RECDB_QSTRING);
123     memset(&hints, 0, sizeof(hints));
124     hints.ai_flags = AI_PASSIVE;
125     hints.ai_socktype = SOCK_STREAM;
126     if (!getaddrinfo(str, NULL, &hints, &ai))
127     {
128         uplink->bind_addr_len = ai->ai_addrlen;
129         uplink->bind_addr = calloc(1, ai->ai_addrlen);
130         memcpy(uplink->bind_addr, ai->ai_addr, ai->ai_addrlen);
131         freeaddrinfo(ai);
132     }
133     else
134     {
135         uplink->bind_addr = NULL;
136         uplink->bind_addr_len = 0;
137     }
138
139     uplink->next = cManager.uplinks;
140     uplink->prev = NULL;
141
142     if(cManager.uplinks)
143     {
144         cManager.uplinks->prev = uplink;
145     }
146
147     cManager.uplinks = uplink;
148
149     /* If the configuration is being reloaded, set the current uplink
150        to the reloaded equivalent, if possible. */
151     if(cManager.uplink
152        && enabled
153        && !irccasecmp(uplink->host, cManager.uplink->host)
154        && uplink->port == cManager.uplink->port)
155     {
156         uplink->state = cManager.uplink->state;
157         uplink->tries = cManager.uplink->tries;
158         cManager.uplink = uplink;
159     }
160
161     return 0;
162 }
163
164 void
165 uplink_compile(void)
166 {
167     const char *cycles;
168     dict_t conf_node;
169     struct uplinkNode *oldUplinks = NULL, *oldUplink = NULL;
170
171     /* Save the old uplinks, we'll remove them later. */
172     oldUplink = cManager.uplink;
173     oldUplinks = cManager.uplinks;
174
175     cycles = conf_get_data("server/max_cycles", RECDB_QSTRING);
176     max_cycles = cycles ? atoi(cycles) : 30;
177     if(!(conf_node = conf_get_data("uplinks", RECDB_OBJECT)))
178     {
179         log_module(MAIN_LOG, LOG_FATAL, "No uplinks configured; giving up.");
180         exit(1);
181     }
182
183     cManager.enabled = 0;
184     dict_foreach(conf_node, uplink_insert, NULL);
185
186     /* Remove the old uplinks, if any. It doesn't matter if oldUplink (below)
187        is a reference to one of these, because it won't get dereferenced. */
188     if(oldUplinks)
189     {
190         struct uplinkNode *uplink, *next;
191
192         oldUplinks->prev->next = NULL;
193
194         for(uplink = oldUplinks; uplink; uplink = next)
195         {
196             next = uplink->next;
197             free(uplink->bind_addr);
198             free(uplink);
199         }
200     }
201
202     /* If the uplink hasn't changed, it's either NULL or pointing at
203        an uplink that was just deleted, select a new one. */
204     if(cManager.uplink == oldUplink)
205     {
206         if(oldUplink)
207         {
208             irc_squit(self, "Uplinks updated; selecting new uplink.", NULL);
209         }
210
211         cManager.uplink = NULL;
212         uplink_select(NULL);
213     }
214 }
215
216 struct uplinkNode *
217 uplink_find(char *name)
218 {
219     struct uplinkNode *uplink;
220
221     if(!cManager.enabled || !cManager.uplinks)
222     {
223         return NULL;
224     }
225
226     for(uplink = cManager.uplinks; uplink; uplink = uplink->next)
227     {
228         if(!strcasecmp(uplink->name, name))
229         {
230             return uplink;
231         }
232     }
233
234     return NULL;
235 }
236
237 void
238 uplink_select(char *name)
239 {
240     struct uplinkNode *start, *uplink, *next;
241     int stop;
242
243     if(!cManager.enabled || !cManager.uplinks)
244     {
245         log_module(MAIN_LOG, LOG_FATAL, "No uplinks enabled; giving up.");
246         exit(1);
247     }
248
249     if(!cManager.uplink)
250     {
251         start = cManager.uplinks;
252     }
253     else
254     {
255         start = cManager.uplink->next;
256         if(!start)
257         {
258             start = cManager.uplinks;
259         }
260     }
261
262     stop = 0;
263     for(uplink = start; uplink; uplink = next)
264     {
265         next = uplink->next ? uplink->next : cManager.uplinks;
266
267         if(stop)
268         {
269             uplink = NULL;
270             break;
271         }
272
273         /* We've wrapped around the list. */
274         if(next == start)
275         {
276             sleep((cManager.cycles >> 1) * 5);
277             cManager.cycles++;
278
279             if(max_cycles && (cManager.cycles >= max_cycles))
280             {
281                 log_module(MAIN_LOG, LOG_ERROR, "Maximum uplink list cycles exceeded; giving up.");
282                 exit(1);
283             }
284
285             /* Give the uplink currently in 'uplink' consideration,
286                and if not selected, break on the next iteration. */
287             stop = 1;
288         }
289
290         /* Skip bad uplinks. */
291         if(uplink->flags & UPLINK_UNAVAILABLE)
292         {
293             continue;
294         }
295
296         if(name && irccasecmp(uplink->name, name))
297         {
298             /* If we were told to connect to a specific uplink, don't stop
299                until we find it.
300             */
301             continue;
302         }
303
304         /* It would be possible to track uplink health through a variety
305            of statistics and only break on the best uplink. For now, break
306            on the first available one.
307         */
308
309         break;
310     }
311
312     if(!uplink)
313     {
314         /* We are shit outta luck if every single uplink has been passed
315            over. Use the current uplink if possible. */
316         if(!cManager.uplink || cManager.uplink->flags & UPLINK_UNAVAILABLE)
317         {
318             log_module(MAIN_LOG, LOG_ERROR, "All available uplinks exhausted; giving up.");
319             exit(1);
320         }
321
322         return;
323     }
324
325     cManager.uplink = uplink;
326 }
327
328 int
329 uplink_connect(void)
330 {
331     struct uplinkNode *uplink = cManager.uplink;
332
333     if(uplink->state != DISCONNECTED)
334     {
335         return 0;
336     }
337
338     if(uplink->flags & UPLINK_UNAVAILABLE)
339     {
340         uplink_select(NULL);
341         uplink = cManager.uplink;
342     }
343
344     if(uplink->tries)
345     {
346         /* This delay could scale with the number of tries. */
347         sleep(2);
348     }
349
350     if(!create_socket_client(uplink))
351     {
352         if(uplink->max_tries && (uplink->tries >= uplink->max_tries))
353         {
354             /* This is a bad uplink, move on. */
355             uplink->flags |= UPLINK_UNAVAILABLE;
356             uplink_select(NULL);
357         }
358
359         return 0;
360     }
361     else
362     {
363         uplink->state = AUTHENTICATING;
364         irc_introduce(uplink->password);
365     }
366
367     return 1;
368 }
369
370 void
371 received_ping(void)
372 {
373     /* This function is called when a ping is received. Take it as
374        a sign of link health and reset the connection manager
375        information. */
376
377     cManager.cycles = 0;
378 }
379
380 static exit_func_t *ef_list;
381 static unsigned int ef_size = 0, ef_used = 0;
382
383 void reg_exit_func(exit_func_t handler)
384 {
385     if (ef_used == ef_size) {
386         if (ef_size) {
387             ef_size <<= 1;
388             ef_list = realloc(ef_list, ef_size*sizeof(exit_func_t));
389         } else {
390             ef_size = 8;
391             ef_list = malloc(ef_size*sizeof(exit_func_t));
392         }
393     }
394     ef_list[ef_used++] = handler;
395 }
396
397 void call_exit_funcs(void)
398 {
399     unsigned int n = ef_used;
400
401     /* Call them in reverse order because we initialize logs, then
402      * nickserv, then chanserv, etc., and they register their exit
403      * funcs in that order, and there are some dependencies (for
404      * example, ChanServ requires NickServ to not have cleaned up).
405      */
406
407     while (n > 0) {
408         ef_list[--n]();
409     }
410     free(ef_list);
411     ef_used = ef_size = 0;
412 }
413
414 int
415 set_policer_param(const char *param, void *data, void *extra)
416 {
417     struct record_data *rd = data;
418     const char *str = GET_RECORD_QSTRING(rd);
419     if (str) {
420         policer_params_set(extra, param, str);
421     }
422     return 0;
423 }
424
425 static void
426 conf_globals(void)
427 {
428     const char *info;
429     dict_t dict;
430
431     info = conf_get_data("services/global/nick", RECDB_QSTRING);
432     if (info && (info[0] == '.'))
433         info = NULL;
434     init_global(info);
435
436     info = conf_get_data("services/nickserv/nick", RECDB_QSTRING);
437     if (info && (info[0] == '.'))
438         info = NULL;
439     init_nickserv(info);
440
441     info = conf_get_data("services/chanserv/nick", RECDB_QSTRING);
442     if (info && (info[0] == '.'))
443         info = NULL;
444     init_chanserv(info);
445     
446     info = conf_get_data("services/spamserv/nick", RECDB_QSTRING);
447     if (info && (info[0] == '.'))
448         info = NULL;
449     init_spamserv(info);
450
451     god_policer_params = policer_params_new();
452     if ((dict = conf_get_data("policers/commands-god", RECDB_OBJECT))) {
453         dict_foreach(dict, set_policer_param, god_policer_params);
454     } else {
455         policer_params_set(god_policer_params, "size", "30");
456         policer_params_set(god_policer_params, "drain-rate", "1");
457     }
458     oper_policer_params = policer_params_new();
459     if ((dict = conf_get_data("policers/commands-oper", RECDB_OBJECT))) {
460         dict_foreach(dict, set_policer_param, oper_policer_params);
461     } else {
462         policer_params_set(oper_policer_params, "size", "10");
463         policer_params_set(oper_policer_params, "drain-rate", "1");
464     }
465     luser_policer_params = policer_params_new();
466     if ((dict = conf_get_data("policers/commands-luser", RECDB_OBJECT))) {
467         dict_foreach(dict, set_policer_param, luser_policer_params);
468     } else {
469         policer_params_set(luser_policer_params, "size", "5");
470         policer_params_set(luser_policer_params, "drain-rate", "0.50");
471     }
472
473     info = conf_get_data("services/opserv/nick", RECDB_QSTRING);
474     if (info && (info[0] == '.'))
475         info = NULL;
476     init_opserv(info);
477 }
478
479 #ifdef HAVE_SYS_RESOURCE_H
480
481 static int
482 set_item_rlimit(const char *name, void *data, void *extra)
483 {
484     long rsrc;
485     int found;
486     struct record_data *rd = data;
487     struct rlimit rlim;
488     const char *str;
489
490     rsrc = (long)dict_find(extra, name, &found);
491     if (!found) {
492         log_module(MAIN_LOG, LOG_ERROR, "Invalid rlimit \"%s\" in rlimits section.", name);
493         return 0;
494     }
495     if (!(str = GET_RECORD_QSTRING(rd))) {
496         log_module(MAIN_LOG, LOG_ERROR, "Missing or invalid parameter type for rlimit \"%s\".", name);
497         return 0;
498     }
499     if (getrlimit(rsrc, &rlim) < 0) {
500         log_module(MAIN_LOG, LOG_ERROR, "Couldn't get rlimit \"%s\": errno %d: %s", name, errno, strerror(errno));
501         return 0;
502     }
503     rlim.rlim_cur = ParseVolume(str);
504     if (setrlimit(rsrc, &rlim) < 0) {
505         log_module(MAIN_LOG, LOG_ERROR, "Couldn't set rlimit \"%s\": errno %d: %s", name, errno, strerror(errno));
506     }
507     return 0;
508 }
509
510 static void
511 conf_rlimits(void)
512 {
513     dict_t dict, values;
514
515     values = dict_new();
516     dict_insert(values, "data", (void*)RLIMIT_DATA);
517     dict_insert(values, "stack", (void*)RLIMIT_STACK);
518 #ifdef RLIMIT_VMEM
519     dict_insert(values, "vmem", (void*)RLIMIT_VMEM);
520 #else
521 #ifdef RLIMIT_AS
522     dict_insert(values, "vmem", (void*)RLIMIT_AS);
523 #endif
524 #endif
525     if ((dict = conf_get_data("rlimits", RECDB_OBJECT))) {
526         dict_foreach(dict, set_item_rlimit, values);
527     }
528     dict_delete(values);
529 }
530
531 #else
532
533 static void
534 conf_rlimits(void)
535 {
536 }
537
538 #endif
539
540 static void
541 usage(char *exe_name)
542 {
543     /* We can assume we have getopt_long(). */
544     printf("Usage: %s [-c config] [-r log] [-d] [-f] [-v|-h]\n"
545            " -c, --config         selects a different configuration file.\n"
546            " -d, --debug          enables debug mode.\n"
547            " -f, --foreground     run srvx in the foreground.\n"
548            " -h, --help           prints this usage message.\n"
549            " -k, --check          checks the configuration file's syntax.\n"
550            " -r, --replay         replay a log file (for debugging).\n"
551            " -v, --version        prints this program's version.\n"
552            , exe_name);
553 }
554
555 static void
556 version()
557 {
558     printf("    --------------------------------------------------\n"
559            "    - "PACKAGE_STRING" ("CODENAME"), Built: " __DATE__ ", " __TIME__".\n"
560            "    - Copyright (C) 2000 - 2007, srvx Development Team\n"
561            "    - Version tag %s\n"
562            "    --------------------------------------------------\n",
563            git_version);
564 }
565
566 static void
567 license()
568 {
569     printf("\n"
570            "This program is free software; you can redistribute it and/or modify\n"
571            "it under the terms of the GNU General Public License as published by\n"
572            "the Free Software Foundation; either version 2 of the License, or\n"
573            "(at your option) any later version.\n"
574            "\n"
575            "This program is distributed in the hope that it will be useful,\n"
576            "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
577            "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
578            "GNU General Public License for more details.\n"
579            "\n"
580            "You should have received a copy of the GNU General Public License\n"
581            "along with this program; if not, write to the Free Software\n"
582            "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\n");
583 }
584
585 void main_shutdown(void)
586 {
587     struct uplinkNode *ul, *ul_next;
588     ioset_cleanup();
589     for (ul = cManager.uplinks; ul; ul = ul_next) {
590         ul_next = ul->next;
591         free(ul->bind_addr);
592         free(ul);
593     }
594     tools_cleanup();
595     conf_close();
596 #if defined(PID_FILE)
597     remove(PID_FILE);
598 #endif
599     policer_params_delete(god_policer_params);
600     policer_params_delete(oper_policer_params);
601     policer_params_delete(luser_policer_params);
602     if (replay_file)
603         fclose(replay_file);
604 }