terom@53: #include "nexus.h" terom@106: #include "lua_config.h" terom@219: #include terom@217: #include terom@0: terom@0: #include terom@15: #include terom@0: #include terom@15: #include terom@48: #include terom@63: #include terom@0: terom@63: /** terom@63: * Command-line option codes terom@63: */ terom@23: enum option_code { terom@63: OPT_HELP = 'h', terom@98: OPT_DEFAULTS = 'D', terom@63: OPT_NETWORK = 'n', terom@63: OPT_MODULE = 'm', terom@106: OPT_CONF = 'C', terom@79: OPT_DEBUG = 'd', terom@106: OPT_CONFIG = 'c', terom@63: terom@63: /** Options without short names */ terom@63: _OPT_EXT_BEGIN = 0x00ff, terom@92: terom@92: OPT_CONSOLE, terom@106: OPT_CHANNEL, terom@23: }; terom@23: terom@63: /** terom@63: * Command-line option definitions terom@63: */ terom@15: static struct option options[] = { terom@63: {"help", 0, NULL, OPT_HELP }, terom@98: {"defaults", 1, NULL, OPT_DEFAULTS }, terom@63: {"network", 1, NULL, OPT_NETWORK }, terom@63: {"channel", 1, NULL, OPT_CHANNEL }, terom@63: {"module", 1, NULL, OPT_MODULE }, terom@106: {"conf", 1, NULL, OPT_CONF }, terom@79: {"debug", 0, NULL, OPT_DEBUG }, terom@92: {"console", 0, NULL, OPT_CONSOLE }, terom@106: {"config", 1, NULL, OPT_CONFIG }, terom@63: {0, 0, 0, 0 }, terom@15: }; terom@15: terom@63: /** terom@106: * Short command-line option defintion terom@106: */ terom@106: const char *short_options = "hn:c:m:C:d"; terom@106: terom@106: /** terom@83: * Display --help output on stdout. terom@83: * terom@83: * If nexus is given, --config options for loaded modules are also listed terom@63: */ terom@83: static void usage (struct nexus *nexus, const char *exe) terom@15: { terom@15: printf("Usage: %s [OPTIONS]\n", exe); terom@15: printf("\n"); terom@15: printf(" --help / -h display this message\n"); terom@98: printf(" --defaults / -D set the IRC client default info using '::'\n"); terom@140: printf(" --network / -n add an IRC network using ':[:[:ssl][:ssl_cafile=][:ssl_verify][:ssl_cert=][:ssl_pkey=]]' format\n"); terom@106: printf(" --channel add an IRC channel using ':' format\n"); terom@63: printf(" --module / -m add a module using ':' format\n"); terom@63: printf(" --config / -C add a module configuration option using ':[:]' format\n"); terom@79: printf(" --debug / -d set logging level to DEBUG\n"); terom@106: printf(" --config / -c load a Lua config file from ''\n"); terom@92: printf(" --console use the interactive console\n"); terom@92: terom@92: // dump loaded module configs terom@83: if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) { terom@83: struct module *module; terom@83: terom@83: printf("\n"); terom@83: printf("Module configuration options\n"); terom@83: terom@83: TAILQ_FOREACH(module, &nexus->modules->list, modules_list) { terom@83: printf("\n"); terom@83: printf("%s:\n", module->info.name); terom@83: terom@100: // XXX: shouldn't be accessing directly terom@100: if (module->desc->config_options) { terom@83: const struct config_option *opt; terom@83: terom@120: for (opt = module->desc->config_options; opt->name; opt++) { terom@120: printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help); terom@83: } terom@83: terom@83: } else { terom@83: printf("\t???\n"); terom@83: } terom@83: } terom@83: } terom@15: } terom@15: terom@63: /** terom@98: * Parse and apply a --defaults option, setting the resulting defaults to our irc_client terom@98: */ terom@217: static err_t apply_defaults (struct nexus *nexus, char *opt, error_t *err) terom@98: { terom@98: struct irc_client_defaults defaults = { terom@98: .register_info = { NULL, NULL, NULL }, terom@100: .service = IRC_PORT, terom@100: .service_ssl = IRC_SSL_PORT, terom@98: }; terom@98: terom@98: // parse the required fields terom@98: if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); terom@98: terom@98: if ((defaults.register_info.username = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); terom@98: terom@98: if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); terom@98: terom@98: // trailing garbage? terom@98: if (opt) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); terom@98: terom@98: // apply them terom@98: log_info("using default nick/user/real-name: %s/%s/%s", terom@98: defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname); terom@98: terom@98: irc_client_set_defaults(nexus->client, &defaults); terom@98: terom@98: // ok terom@217: return SUCCESS; terom@98: } terom@98: terom@98: /** terom@65: * Parse and apply a --network option, adding the given network to our irc_client. terom@63: */ terom@217: static err_t apply_network (struct nexus *nexus, char *opt, error_t *err) terom@63: { terom@98: struct irc_net_info info; terom@140: const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL; terom@140: bool use_ssl = false, ssl_verify = false; terom@98: terom@98: // init to zero terom@98: memset(&info, 0, sizeof(info)); terom@63: terom@63: // parse the required fields terom@63: if ((info.network = strsep(&opt, ":")) == NULL) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --network"); terom@63: terom@63: if ((info.hostname = strsep(&opt, ":")) == NULL) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --network"); terom@63: terom@63: // parse the optional fields terom@63: if (opt) terom@63: info.service = strsep(&opt, ":"); terom@63: terom@63: // parse any remaining flags terom@63: while (opt) { terom@140: char *flag_token = strsep(&opt, ":"); terom@140: char *flag = strsep(&flag_token, "="); terom@140: char *value = flag_token; terom@63: terom@140: if (strcmp(flag, "ssl") == 0) { terom@140: use_ssl = true; terom@63: terom@140: } else if (strcmp(flag, "ssl_cafile") == 0) { terom@140: if (!value) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cafile"); terom@140: terom@140: ssl_cafile = value; terom@140: terom@140: } else if (strcmp(flag, "ssl_verify") == 0) { terom@140: ssl_verify = true; terom@140: terom@140: } else if (strcmp(flag, "ssl_cert") == 0) { terom@140: if (!value) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cert"); terom@140: terom@140: ssl_cert = value; terom@140: terom@140: } else if (strcmp(flag, "ssl_pkey") == 0) { terom@140: if (!value) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_pkey"); terom@140: terom@140: ssl_pkey = value; terom@140: terom@140: } else terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unrecognized flag for --network"); terom@140: } terom@140: terom@140: // SSL? terom@140: if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) { terom@140: // verify terom@140: if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey)) terom@217: JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey"); terom@140: terom@140: // create terom@180: if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err)) terom@140: goto error; terom@63: } terom@63: terom@66: // create the net terom@65: log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service); terom@65: terom@63: if (irc_client_add_net(nexus->client, NULL, &info, err)) terom@140: goto error; terom@63: terom@63: // ok terom@140: return SUCCESS; terom@140: terom@140: error: terom@140: // cleanup terom@140: if (info.ssl_cred) terom@180: ssl_client_cred_put(info.ssl_cred); terom@140: terom@140: return ERROR_CODE(err); terom@63: } terom@63: terom@63: /** terom@65: * Parse and apply a --channel option, adding the given channel to the given irc_net. terom@63: */ terom@217: static err_t apply_channel (struct nexus *nexus, char *opt, error_t *err) terom@63: { terom@63: const char *network = NULL; terom@63: struct irc_net *net; terom@63: struct irc_chan_info info = { terom@63: .channel = NULL, terom@63: }; terom@63: terom@63: // parse the required fields terom@63: if ((network = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --channel"); terom@63: terom@63: if ((info.channel = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --channel"); terom@63: terom@63: // trailing garbage? terom@63: if (opt) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); terom@63: terom@63: // look up the net terom@63: if ((net = irc_client_get_net(nexus->client, network)) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown network for --channel"); terom@65: terom@66: // add the channel terom@65: log_info("add channel '%s' on network '%s'", info.channel, net->info.network); terom@63: terom@63: if (irc_net_add_chan(net, NULL, &info, err)) terom@63: return ERROR_CODE(err); terom@63: terom@63: // ok terom@63: return SUCCESS; terom@63: } terom@63: terom@63: /** terom@65: * Parse and apply a --module option, loading the given module. terom@65: */ terom@217: static err_t apply_module (struct nexus *nexus, char *opt, error_t *err) terom@65: { terom@65: struct module_info info = { terom@65: .name = NULL, terom@65: .path = NULL, terom@65: }; terom@65: terom@65: // parse the required fields terom@65: if ((info.name = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --module"); terom@65: terom@65: if ((info.path = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --module"); terom@65: terom@65: // trailing garbage? terom@65: if (opt) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel"); terom@65: terom@66: // load it terom@65: log_info("loading module '%s' from path '%s'", info.name, info.path); terom@65: terom@111: if (module_load(nexus->modules, NULL, &info, err)) terom@65: return ERROR_CODE(err); terom@65: terom@65: // ok terom@65: return SUCCESS; terom@65: } terom@65: terom@65: /** terom@106: * Parse and apply a --conf option, calling the module's conf func. terom@66: */ terom@217: static err_t apply_conf (struct nexus *nexus, char *opt, error_t *err) terom@66: { terom@66: struct module *module; terom@66: const char *module_name, *conf_name; terom@66: char *conf_value; terom@66: terom@66: // parse the required fields terom@66: if ((module_name = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --config"); terom@66: terom@66: if ((conf_name = strsep(&opt, ":")) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --config"); terom@66: terom@66: // value is the rest of the data, might be NULL terom@66: conf_value = opt; terom@66: terom@66: // lookup the module terom@134: if ((module = modules_get(nexus->modules, module_name)) == NULL) terom@217: return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown module for --config"); terom@66: terom@66: // do the config terom@66: log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value); terom@66: terom@100: if (module_conf_raw(module, conf_name, conf_value, err)) terom@66: return ERROR_CODE(err); terom@66: terom@66: // ok terom@66: return SUCCESS; terom@66: } terom@66: terom@92: /** terom@92: * Open the console terom@92: */ terom@217: static err_t apply_console (struct nexus *nexus, error_t *err) terom@92: { terom@92: struct console_config config = { terom@93: .prompt = "> ", terom@92: }; terom@105: struct console *console; terom@92: terom@92: log_info("initializing the console"); terom@92: terom@93: // init the console terom@105: if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err)) terom@93: return ERROR_CODE(err); terom@137: terom@137: // set it as the log output handler terom@137: console_set_log_output(console); terom@93: terom@93: // create the lua console on top of that terom@105: if (lua_console_create(&nexus->lua_console, console, nexus->lua, err)) terom@105: goto error; terom@93: terom@93: // ok terom@93: return SUCCESS; terom@105: terom@105: error: terom@105: console_destroy(console); terom@105: terom@105: return ERROR_CODE(err); terom@92: } terom@92: terom@217: err_t nexus_load_config (struct nexus *nexus, const char *path, error_t *err) terom@109: { terom@109: return lua_config_load(nexus->lua, path, err); terom@109: } terom@109: terom@66: /** terom@106: * Load the lua config file from \a path terom@106: */ terom@217: static err_t apply_config (struct nexus *nexus, char *path, error_t *err) terom@106: { terom@106: log_info("loading lua config from %s", path); terom@106: terom@109: return nexus_load_config(nexus, path, err); terom@106: } terom@106: terom@106: /** terom@63: * Parse arguments and apply them to the given nexus terom@63: */ terom@217: static err_t parse_args (struct nexus *nexus, int argc, char **argv, error_t *err) terom@63: { terom@63: int opt, option_index; terom@63: terom@63: // parse options terom@106: while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) { terom@63: switch (opt) { terom@63: case OPT_HELP: terom@83: usage(nexus, argv[0]); terom@63: terom@63: // XXX: return instead terom@63: exit(EXIT_SUCCESS); terom@63: terom@217: // XXX: come up with something nicer for parsing these command-line options terom@63: case OPT_NETWORK: terom@63: if (apply_network(nexus, optarg, err)) terom@217: goto error; terom@63: terom@63: break; terom@98: terom@98: case OPT_DEFAULTS: terom@98: if (apply_defaults(nexus, optarg, err)) terom@217: goto error; terom@98: terom@98: break; terom@63: terom@63: case OPT_CHANNEL: terom@63: if (apply_channel(nexus, optarg, err)) terom@217: goto error; terom@63: terom@63: break; terom@63: terom@63: case OPT_MODULE: terom@65: if (apply_module(nexus, optarg, err)) terom@217: goto error; terom@65: terom@65: break; terom@65: terom@106: case OPT_CONF: terom@106: if (apply_conf(nexus, optarg, err)) terom@217: goto error; terom@66: terom@66: break; terom@79: terom@79: case OPT_DEBUG: terom@79: set_log_level(LOG_DEBUG); terom@79: break; terom@92: terom@92: case OPT_CONSOLE: terom@92: if (apply_console(nexus, err)) terom@217: goto error; terom@92: terom@92: break; terom@106: terom@106: case OPT_CONFIG: terom@106: if (apply_config(nexus, optarg, err)) terom@217: goto error; terom@106: terom@106: break; terom@63: terom@63: case '?': terom@83: usage(nexus, argv[0]); terom@217: return SET_ERROR(err, &general_errors, ERR_CMD_OPT); terom@63: } terom@63: } terom@63: terom@63: // ok terom@63: return SUCCESS; terom@217: terom@217: error: terom@217: return ERROR_CODE(err); terom@63: } terom@63: terom@103: void nexus_shutdown (struct nexus *nexus) terom@103: { terom@103: // destroy the console terom@103: if (nexus->lua_console) terom@103: lua_console_destroy(nexus->lua_console); terom@103: terom@103: nexus->lua_console = NULL; terom@103: terom@103: // unload the modules terom@103: if (nexus->modules) terom@103: modules_unload(nexus->modules); terom@103: terom@103: // quit the irc client terom@103: if (nexus->client) terom@103: irc_client_quit(nexus->client, "Goodbye, cruel world ;("); terom@103: terom@103: // remove the signal handlers terom@103: if (nexus->signals) terom@103: signals_free(nexus->signals); terom@103: terom@103: nexus->signals = NULL; terom@103: terom@103: // now event_base_dispatch should return once everythings' shut down... terom@103: nexus->shutdown = true; terom@103: } terom@103: terom@109: void nexus_crash (struct nexus *nexus) terom@109: { terom@109: // force-quit the event loop terom@109: event_base_loopbreak(nexus->ev_base); terom@109: } terom@109: terom@103: void nexus_destroy (struct nexus *nexus) terom@103: { terom@109: // destroy the console terom@109: if (nexus->lua_console) terom@109: lua_console_destroy(nexus->lua_console); terom@109: terom@105: // destroy the lua state terom@105: if (nexus->lua) terom@105: nexus_lua_destroy(nexus->lua); terom@105: terom@103: // destroy the modules terom@103: if (nexus->modules) terom@103: modules_destroy(nexus->modules); terom@103: terom@103: // destroy the irc client terom@103: if (nexus->client) terom@103: irc_client_destroy(nexus->client); terom@103: terom@109: // remove the signal handlers terom@109: if (nexus->signals) terom@109: signals_free(nexus->signals); terom@110: terom@110: // finally, the libevent base terom@110: if (nexus->ev_base) terom@110: event_base_free(nexus->ev_base); terom@103: terom@110: // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :) terom@110: memset(nexus, 0, sizeof(*nexus)); terom@103: } terom@103: terom@200: static void on_sig_shutdown (evutil_socket_t sig, short what, void *arg) terom@48: { terom@103: struct nexus *nexus = arg; terom@48: terom@48: (void) sig; terom@48: (void) what; terom@48: terom@170: if (!nexus->shutdown) { terom@170: // shutdown terom@170: log_info("Terminating..."); terom@170: terom@170: nexus_shutdown(nexus); terom@170: terom@170: } else { terom@170: // already tried to shutdown terom@170: log_info("Crashing!"); terom@170: terom@170: nexus_crash(nexus); terom@170: } terom@48: } terom@48: terom@15: int main (int argc, char **argv) terom@15: { terom@70: struct nexus _nexus, *nexus = &_nexus; terom@217: error_t err; terom@0: terom@63: // zero nexus terom@63: memset(nexus, 0, sizeof(*nexus)); terom@15: terom@9: // initialize libevent terom@63: if ((nexus->ev_base = event_base_new()) == NULL) terom@21: FATAL("event_base_new"); terom@48: terom@92: terom@48: // initialize signal handlers terom@218: if (signals_create(&nexus->signals, nexus->ev_base, &err)) terom@48: FATAL("signals_create"); terom@92: terom@48: // add our signal handlers terom@71: if (signal_ignore(SIGPIPE, &err)) terom@170: FATAL_ERROR(&err, "signals_ignore"); terom@92: terom@170: // add our SIGTERM handler before console_init()? terom@200: if ( terom@200: (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus)) terom@200: || (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus)) terom@200: ) terom@170: FATAL_ERROR(&err, "signals_add"); terom@92: terom@63: // initialize sock module terom@63: if (sock_init(nexus->ev_base, &err)) terom@63: FATAL_ERROR(&err, "sock_init"); terom@55: terom@63: // modules terom@218: if (modules_create(&nexus->modules, nexus, &err)) terom@63: FATAL_ERROR(&err, "modules_create"); terom@63: terom@63: // the IRC client terom@63: if (irc_client_create(&nexus->client, &err)) terom@63: FATAL_ERROR(&err, "irc_client_create"); terom@105: terom@105: // lua state terom@105: if (nexus_lua_create(&nexus->lua, nexus, &err)) terom@105: FATAL_ERROR(&err, "nexus_lua_create"); terom@55: terom@105: terom@63: // parse args terom@63: if (parse_args(nexus, argc, argv, &err)) terom@63: FATAL_ERROR(&err, "parse_args"); terom@92: terom@92: terom@11: // run event loop terom@71: log_info("entering event loop"); terom@71: terom@103: if (event_base_dispatch(nexus->ev_base)) { terom@103: if (nexus->shutdown) { terom@103: log_info("clean shutdown completed"); terom@103: terom@103: } else { terom@103: FATAL("event_base_dispatch returned without shutdown"); terom@103: terom@103: } terom@170: } else { terom@170: log_warn("zero return from event_base_dispatch"); terom@103: } terom@103: terom@109: // cleanup terom@109: nexus_destroy(nexus); terom@109: nexus = NULL; terom@109: terom@109: // ok terom@109: return EXIT_SUCCESS; terom@0: } terom@0: