|
1 #include "nexus.h" |
|
2 #include "lua_config.h" |
|
3 #include "sock.h" |
|
4 #include <lib/log.h> |
|
5 |
|
6 #include <stdlib.h> |
|
7 #include <stdbool.h> |
|
8 #include <stdio.h> |
|
9 #include <getopt.h> |
|
10 #include <signal.h> |
|
11 #include <string.h> |
|
12 |
|
13 /** |
|
14 * Command-line option codes |
|
15 */ |
|
16 enum option_code { |
|
17 OPT_HELP = 'h', |
|
18 OPT_DEFAULTS = 'D', |
|
19 OPT_NETWORK = 'n', |
|
20 OPT_MODULE = 'm', |
|
21 OPT_CONF = 'C', |
|
22 OPT_DEBUG = 'd', |
|
23 OPT_CONFIG = 'c', |
|
24 |
|
25 /** Options without short names */ |
|
26 _OPT_EXT_BEGIN = 0x00ff, |
|
27 |
|
28 OPT_CONSOLE, |
|
29 OPT_CHANNEL, |
|
30 }; |
|
31 |
|
32 /** |
|
33 * Command-line option definitions |
|
34 */ |
|
35 static struct option options[] = { |
|
36 {"help", 0, NULL, OPT_HELP }, |
|
37 {"defaults", 1, NULL, OPT_DEFAULTS }, |
|
38 {"network", 1, NULL, OPT_NETWORK }, |
|
39 {"channel", 1, NULL, OPT_CHANNEL }, |
|
40 {"module", 1, NULL, OPT_MODULE }, |
|
41 {"conf", 1, NULL, OPT_CONF }, |
|
42 {"debug", 0, NULL, OPT_DEBUG }, |
|
43 {"console", 0, NULL, OPT_CONSOLE }, |
|
44 {"config", 1, NULL, OPT_CONFIG }, |
|
45 {0, 0, 0, 0 }, |
|
46 }; |
|
47 |
|
48 /** |
|
49 * Short command-line option defintion |
|
50 */ |
|
51 const char *short_options = "hn:c:m:C:d"; |
|
52 |
|
53 /** |
|
54 * Display --help output on stdout. |
|
55 * |
|
56 * If nexus is given, --config options for loaded modules are also listed |
|
57 */ |
|
58 static void usage (struct nexus *nexus, const char *exe) |
|
59 { |
|
60 printf("Usage: %s [OPTIONS]\n", exe); |
|
61 printf("\n"); |
|
62 printf(" --help / -h display this message\n"); |
|
63 printf(" --defaults / -D set the IRC client default info using '<nickname>:<username>:<realname>'\n"); |
|
64 printf(" --network / -n add an IRC network using '<name>:<hostname>[:<port>[:ssl][:ssl_cafile=<path>][:ssl_verify][:ssl_cert=<path>][:ssl_pkey=<path>]]' format\n"); |
|
65 printf(" --channel add an IRC channel using '<network>:<channel>' format\n"); |
|
66 printf(" --module / -m add a module using '<name>:<path>' format\n"); |
|
67 printf(" --config / -C add a module configuration option using '<mod_name>:<name>[:<value>]' format\n"); |
|
68 printf(" --debug / -d set logging level to DEBUG\n"); |
|
69 printf(" --config / -c load a Lua config file from '<path>'\n"); |
|
70 printf(" --console use the interactive console\n"); |
|
71 |
|
72 // dump loaded module configs |
|
73 if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) { |
|
74 struct module *module; |
|
75 |
|
76 printf("\n"); |
|
77 printf("Module configuration options\n"); |
|
78 |
|
79 TAILQ_FOREACH(module, &nexus->modules->list, modules_list) { |
|
80 printf("\n"); |
|
81 printf("%s:\n", module->info.name); |
|
82 |
|
83 // XXX: shouldn't be accessing directly |
|
84 if (module->desc->config_options) { |
|
85 const struct config_option *opt; |
|
86 |
|
87 for (opt = module->desc->config_options; opt->name; opt++) { |
|
88 printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help); |
|
89 } |
|
90 |
|
91 } else { |
|
92 printf("\t???\n"); |
|
93 } |
|
94 } |
|
95 } |
|
96 } |
|
97 |
|
98 /** |
|
99 * Parse and apply a --defaults option, setting the resulting defaults to our irc_client |
|
100 */ |
|
101 static err_t apply_defaults (struct nexus *nexus, char *opt, error_t *err) |
|
102 { |
|
103 struct irc_client_defaults defaults = { |
|
104 .register_info = { NULL, NULL, NULL }, |
|
105 .service = IRC_PORT, |
|
106 .service_ssl = IRC_SSL_PORT, |
|
107 }; |
|
108 |
|
109 // parse the required fields |
|
110 if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL) |
|
111 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <nickname> field for --defaults"); |
|
112 |
|
113 if ((defaults.register_info.username = strsep(&opt, ":")) == NULL) |
|
114 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <username> field for --defaults"); |
|
115 |
|
116 if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL) |
|
117 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <realname> field for --defaults"); |
|
118 |
|
119 // trailing garbage? |
|
120 if (opt) |
|
121 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); |
|
122 |
|
123 // apply them |
|
124 log_info("using default nick/user/real-name: %s/%s/%s", |
|
125 defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname); |
|
126 |
|
127 irc_client_set_defaults(nexus->client, &defaults); |
|
128 |
|
129 // ok |
|
130 return SUCCESS; |
|
131 } |
|
132 |
|
133 /** |
|
134 * Parse and apply a --network option, adding the given network to our irc_client. |
|
135 */ |
|
136 static err_t apply_network (struct nexus *nexus, char *opt, error_t *err) |
|
137 { |
|
138 struct irc_net_info info; |
|
139 const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL; |
|
140 bool use_ssl = false, ssl_verify = false; |
|
141 |
|
142 // init to zero |
|
143 memset(&info, 0, sizeof(info)); |
|
144 |
|
145 // parse the required fields |
|
146 if ((info.network = strsep(&opt, ":")) == NULL) |
|
147 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --network"); |
|
148 |
|
149 if ((info.hostname = strsep(&opt, ":")) == NULL) |
|
150 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <hostname> field for --network"); |
|
151 |
|
152 // parse the optional fields |
|
153 if (opt) |
|
154 info.service = strsep(&opt, ":"); |
|
155 |
|
156 // parse any remaining flags |
|
157 while (opt) { |
|
158 char *flag_token = strsep(&opt, ":"); |
|
159 char *flag = strsep(&flag_token, "="); |
|
160 char *value = flag_token; |
|
161 |
|
162 if (strcmp(flag, "ssl") == 0) { |
|
163 use_ssl = true; |
|
164 |
|
165 } else if (strcmp(flag, "ssl_cafile") == 0) { |
|
166 if (!value) |
|
167 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cafile"); |
|
168 |
|
169 ssl_cafile = value; |
|
170 |
|
171 } else if (strcmp(flag, "ssl_verify") == 0) { |
|
172 ssl_verify = true; |
|
173 |
|
174 } else if (strcmp(flag, "ssl_cert") == 0) { |
|
175 if (!value) |
|
176 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cert"); |
|
177 |
|
178 ssl_cert = value; |
|
179 |
|
180 } else if (strcmp(flag, "ssl_pkey") == 0) { |
|
181 if (!value) |
|
182 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_pkey"); |
|
183 |
|
184 ssl_pkey = value; |
|
185 |
|
186 } else |
|
187 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unrecognized flag for --network"); |
|
188 } |
|
189 |
|
190 // SSL? |
|
191 if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) { |
|
192 // verify |
|
193 if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey)) |
|
194 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey"); |
|
195 |
|
196 // create |
|
197 if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err)) |
|
198 goto error; |
|
199 } |
|
200 |
|
201 // create the net |
|
202 log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service); |
|
203 |
|
204 if (irc_client_add_net(nexus->client, NULL, &info, err)) |
|
205 goto error; |
|
206 |
|
207 // ok |
|
208 return SUCCESS; |
|
209 |
|
210 error: |
|
211 // cleanup |
|
212 if (info.ssl_cred) |
|
213 ssl_client_cred_put(info.ssl_cred); |
|
214 |
|
215 return ERROR_CODE(err); |
|
216 } |
|
217 |
|
218 /** |
|
219 * Parse and apply a --channel option, adding the given channel to the given irc_net. |
|
220 */ |
|
221 static err_t apply_channel (struct nexus *nexus, char *opt, error_t *err) |
|
222 { |
|
223 const char *network = NULL; |
|
224 struct irc_net *net; |
|
225 struct irc_chan_info info = { |
|
226 .channel = NULL, |
|
227 }; |
|
228 |
|
229 // parse the required fields |
|
230 if ((network = strsep(&opt, ":")) == NULL) |
|
231 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <network> field for --channel"); |
|
232 |
|
233 if ((info.channel = strsep(&opt, ":")) == NULL) |
|
234 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <channel> field for --channel"); |
|
235 |
|
236 // trailing garbage? |
|
237 if (opt) |
|
238 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); |
|
239 |
|
240 // look up the net |
|
241 if ((net = irc_client_get_net(nexus->client, network)) == NULL) |
|
242 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown network for --channel"); |
|
243 |
|
244 // add the channel |
|
245 log_info("add channel '%s' on network '%s'", info.channel, net->info.network); |
|
246 |
|
247 if (irc_net_add_chan(net, NULL, &info, err)) |
|
248 return ERROR_CODE(err); |
|
249 |
|
250 // ok |
|
251 return SUCCESS; |
|
252 } |
|
253 |
|
254 /** |
|
255 * Parse and apply a --module option, loading the given module. |
|
256 */ |
|
257 static err_t apply_module (struct nexus *nexus, char *opt, error_t *err) |
|
258 { |
|
259 struct module_info info = { |
|
260 .name = NULL, |
|
261 .path = NULL, |
|
262 }; |
|
263 |
|
264 // parse the required fields |
|
265 if ((info.name = strsep(&opt, ":")) == NULL) |
|
266 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --module"); |
|
267 |
|
268 if ((info.path = strsep(&opt, ":")) == NULL) |
|
269 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <path> field for --module"); |
|
270 |
|
271 // trailing garbage? |
|
272 if (opt) |
|
273 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); |
|
274 |
|
275 // load it |
|
276 log_info("loading module '%s' from path '%s'", info.name, info.path); |
|
277 |
|
278 if (module_load(nexus->modules, NULL, &info, err)) |
|
279 return ERROR_CODE(err); |
|
280 |
|
281 // ok |
|
282 return SUCCESS; |
|
283 } |
|
284 |
|
285 /** |
|
286 * Parse and apply a --conf option, calling the module's conf func. |
|
287 */ |
|
288 static err_t apply_conf (struct nexus *nexus, char *opt, error_t *err) |
|
289 { |
|
290 struct module *module; |
|
291 const char *module_name, *conf_name; |
|
292 char *conf_value; |
|
293 |
|
294 // parse the required fields |
|
295 if ((module_name = strsep(&opt, ":")) == NULL) |
|
296 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <module> field for --config"); |
|
297 |
|
298 if ((conf_name = strsep(&opt, ":")) == NULL) |
|
299 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --config"); |
|
300 |
|
301 // value is the rest of the data, might be NULL |
|
302 conf_value = opt; |
|
303 |
|
304 // lookup the module |
|
305 if ((module = modules_get(nexus->modules, module_name)) == NULL) |
|
306 return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown module for --config"); |
|
307 |
|
308 // do the config |
|
309 log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value); |
|
310 |
|
311 if (module_conf_raw(module, conf_name, conf_value, err)) |
|
312 return ERROR_CODE(err); |
|
313 |
|
314 // ok |
|
315 return SUCCESS; |
|
316 } |
|
317 |
|
318 /** |
|
319 * Open the console |
|
320 */ |
|
321 static err_t apply_console (struct nexus *nexus, error_t *err) |
|
322 { |
|
323 struct console_config config = { |
|
324 .prompt = "> ", |
|
325 }; |
|
326 struct console *console; |
|
327 |
|
328 log_info("initializing the console"); |
|
329 |
|
330 // init the console |
|
331 if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err)) |
|
332 return ERROR_CODE(err); |
|
333 |
|
334 // set it as the log output handler |
|
335 console_set_log_output(console); |
|
336 |
|
337 // create the lua console on top of that |
|
338 if (lua_console_create(&nexus->lua_console, console, nexus->lua, err)) |
|
339 goto error; |
|
340 |
|
341 // ok |
|
342 return SUCCESS; |
|
343 |
|
344 error: |
|
345 console_destroy(console); |
|
346 |
|
347 return ERROR_CODE(err); |
|
348 } |
|
349 |
|
350 err_t nexus_load_config (struct nexus *nexus, const char *path, error_t *err) |
|
351 { |
|
352 return lua_config_load(nexus->lua, path, err); |
|
353 } |
|
354 |
|
355 /** |
|
356 * Load the lua config file from \a path |
|
357 */ |
|
358 static err_t apply_config (struct nexus *nexus, char *path, error_t *err) |
|
359 { |
|
360 log_info("loading lua config from %s", path); |
|
361 |
|
362 return nexus_load_config(nexus, path, err); |
|
363 } |
|
364 |
|
365 /** |
|
366 * Parse arguments and apply them to the given nexus |
|
367 */ |
|
368 static err_t parse_args (struct nexus *nexus, int argc, char **argv, error_t *err) |
|
369 { |
|
370 int opt, option_index; |
|
371 |
|
372 // parse options |
|
373 while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) { |
|
374 switch (opt) { |
|
375 case OPT_HELP: |
|
376 usage(nexus, argv[0]); |
|
377 |
|
378 // XXX: return instead |
|
379 exit(EXIT_SUCCESS); |
|
380 |
|
381 // XXX: come up with something nicer for parsing these command-line options |
|
382 case OPT_NETWORK: |
|
383 if (apply_network(nexus, optarg, err)) |
|
384 goto error; |
|
385 |
|
386 break; |
|
387 |
|
388 case OPT_DEFAULTS: |
|
389 if (apply_defaults(nexus, optarg, err)) |
|
390 goto error; |
|
391 |
|
392 break; |
|
393 |
|
394 case OPT_CHANNEL: |
|
395 if (apply_channel(nexus, optarg, err)) |
|
396 goto error; |
|
397 |
|
398 break; |
|
399 |
|
400 case OPT_MODULE: |
|
401 if (apply_module(nexus, optarg, err)) |
|
402 goto error; |
|
403 |
|
404 break; |
|
405 |
|
406 case OPT_CONF: |
|
407 if (apply_conf(nexus, optarg, err)) |
|
408 goto error; |
|
409 |
|
410 break; |
|
411 |
|
412 case OPT_DEBUG: |
|
413 set_log_level(LOG_DEBUG); |
|
414 break; |
|
415 |
|
416 case OPT_CONSOLE: |
|
417 if (apply_console(nexus, err)) |
|
418 goto error; |
|
419 |
|
420 break; |
|
421 |
|
422 case OPT_CONFIG: |
|
423 if (apply_config(nexus, optarg, err)) |
|
424 goto error; |
|
425 |
|
426 break; |
|
427 |
|
428 case '?': |
|
429 usage(nexus, argv[0]); |
|
430 return SET_ERROR(err, &general_errors, ERR_CMD_OPT); |
|
431 } |
|
432 } |
|
433 |
|
434 // ok |
|
435 return SUCCESS; |
|
436 |
|
437 error: |
|
438 return ERROR_CODE(err); |
|
439 } |
|
440 |
|
441 void nexus_shutdown (struct nexus *nexus) |
|
442 { |
|
443 // destroy the console |
|
444 if (nexus->lua_console) |
|
445 lua_console_destroy(nexus->lua_console); |
|
446 |
|
447 nexus->lua_console = NULL; |
|
448 |
|
449 // unload the modules |
|
450 if (nexus->modules) |
|
451 modules_unload(nexus->modules); |
|
452 |
|
453 // quit the irc client |
|
454 if (nexus->client) |
|
455 irc_client_quit(nexus->client, "Goodbye, cruel world ;("); |
|
456 |
|
457 // remove the signal handlers |
|
458 if (nexus->signals) |
|
459 signals_free(nexus->signals); |
|
460 |
|
461 nexus->signals = NULL; |
|
462 |
|
463 // now event_base_dispatch should return once everythings' shut down... |
|
464 nexus->shutdown = true; |
|
465 } |
|
466 |
|
467 void nexus_crash (struct nexus *nexus) |
|
468 { |
|
469 // force-quit the event loop |
|
470 event_base_loopbreak(nexus->ev_base); |
|
471 } |
|
472 |
|
473 void nexus_destroy (struct nexus *nexus) |
|
474 { |
|
475 // destroy the console |
|
476 if (nexus->lua_console) |
|
477 lua_console_destroy(nexus->lua_console); |
|
478 |
|
479 // destroy the lua state |
|
480 if (nexus->lua) |
|
481 nexus_lua_destroy(nexus->lua); |
|
482 |
|
483 // destroy the modules |
|
484 if (nexus->modules) |
|
485 modules_destroy(nexus->modules); |
|
486 |
|
487 // destroy the irc client |
|
488 if (nexus->client) |
|
489 irc_client_destroy(nexus->client); |
|
490 |
|
491 // remove the signal handlers |
|
492 if (nexus->signals) |
|
493 signals_free(nexus->signals); |
|
494 |
|
495 // finally, the libevent base |
|
496 if (nexus->ev_base) |
|
497 event_base_free(nexus->ev_base); |
|
498 |
|
499 // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :) |
|
500 memset(nexus, 0, sizeof(*nexus)); |
|
501 } |
|
502 |
|
503 static void on_sig_shutdown (evutil_socket_t sig, short what, void *arg) |
|
504 { |
|
505 struct nexus *nexus = arg; |
|
506 |
|
507 (void) sig; |
|
508 (void) what; |
|
509 |
|
510 if (!nexus->shutdown) { |
|
511 // shutdown |
|
512 log_info("Terminating..."); |
|
513 |
|
514 nexus_shutdown(nexus); |
|
515 |
|
516 } else { |
|
517 // already tried to shutdown |
|
518 log_info("Crashing!"); |
|
519 |
|
520 nexus_crash(nexus); |
|
521 } |
|
522 } |
|
523 |
|
524 int main (int argc, char **argv) |
|
525 { |
|
526 struct nexus _nexus, *nexus = &_nexus; |
|
527 error_t err; |
|
528 |
|
529 // zero nexus |
|
530 memset(nexus, 0, sizeof(*nexus)); |
|
531 |
|
532 // initialize libevent |
|
533 if ((nexus->ev_base = event_base_new()) == NULL) |
|
534 FATAL("event_base_new"); |
|
535 |
|
536 |
|
537 // initialize signal handlers |
|
538 if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base))) |
|
539 FATAL("signals_create"); |
|
540 |
|
541 // add our signal handlers |
|
542 if (signal_ignore(SIGPIPE, &err)) |
|
543 FATAL_ERROR(&err, "signals_ignore"); |
|
544 |
|
545 // add our SIGTERM handler before console_init()? |
|
546 if ( |
|
547 (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus)) |
|
548 || (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus)) |
|
549 ) |
|
550 FATAL_ERROR(&err, "signals_add"); |
|
551 |
|
552 // initialize sock module |
|
553 if (sock_init(nexus->ev_base, &err)) |
|
554 FATAL_ERROR(&err, "sock_init"); |
|
555 |
|
556 // modules |
|
557 if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus))) |
|
558 FATAL_ERROR(&err, "modules_create"); |
|
559 |
|
560 // the IRC client |
|
561 if (irc_client_create(&nexus->client, &err)) |
|
562 FATAL_ERROR(&err, "irc_client_create"); |
|
563 |
|
564 // lua state |
|
565 if (nexus_lua_create(&nexus->lua, nexus, &err)) |
|
566 FATAL_ERROR(&err, "nexus_lua_create"); |
|
567 |
|
568 |
|
569 // parse args |
|
570 if (parse_args(nexus, argc, argv, &err)) |
|
571 FATAL_ERROR(&err, "parse_args"); |
|
572 |
|
573 |
|
574 // run event loop |
|
575 log_info("entering event loop"); |
|
576 |
|
577 if (event_base_dispatch(nexus->ev_base)) { |
|
578 if (nexus->shutdown) { |
|
579 log_info("clean shutdown completed"); |
|
580 |
|
581 } else { |
|
582 FATAL("event_base_dispatch returned without shutdown"); |
|
583 |
|
584 } |
|
585 } else { |
|
586 log_warn("zero return from event_base_dispatch"); |
|
587 } |
|
588 |
|
589 // cleanup |
|
590 nexus_destroy(nexus); |
|
591 nexus = NULL; |
|
592 |
|
593 // ok |
|
594 return EXIT_SUCCESS; |
|
595 } |
|
596 |