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