#include "nexus.h"
#include "signals.h"
#include "log.h"
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#define DEFAULT_HOST "irc.fixme.fi"
#define DEFAULT_PORT "6667"
#define DEFAULT_PORT_SSL "6697"
/**
* Command-line option codes
*/
enum option_code {
OPT_HELP = 'h',
OPT_NETWORK = 'n',
OPT_CHANNEL = 'c',
OPT_MODULE = 'm',
OPT_CONFIG = 'C',
OPT_DEBUG = 'd',
/** Options without short names */
_OPT_EXT_BEGIN = 0x00ff,
};
/**
* Command-line option definitions
*/
static struct option options[] = {
{"help", 0, NULL, OPT_HELP },
{"network", 1, NULL, OPT_NETWORK },
{"channel", 1, NULL, OPT_CHANNEL },
{"module", 1, NULL, OPT_MODULE },
{"config", 1, NULL, OPT_CONFIG },
{"debug", 0, NULL, OPT_DEBUG },
{0, 0, 0, 0 },
};
/**
* 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(" --network / -n add an IRC network using '<name>:<hostname>[:<port>[:ssl]]' format\n");
printf(" --channel / -c 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");
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);
if (module->funcs->config_options) {
const struct config_option *opt;
for (opt = module->funcs->config_options; opt->name && opt->type; opt++) {
printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, opt->description, opt->help);
}
} else {
printf("\t???\n");
}
}
}
}
/**
* 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 = {
.network = NULL,
.hostname = NULL,
.service = DEFAULT_PORT,
.use_ssl = false,
.register_info = {
.nickname = "SpBotDev",
.username = "spbot-dev",
.realname = "SpBot (development version)"
}
};
// parse the required fields
if ((info.network = strsep(&opt, ":")) == NULL)
RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
if ((info.hostname = strsep(&opt, ":")) == NULL)
RETURN_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 = strsep(&opt, ":");
if (strcmp(flag, "ssl"))
info.use_ssl = true;
else
RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
}
// 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))
return ERROR_CODE(err);
// ok
return SET_ERROR(err, SUCCESS);
}
/**
* 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,
};
struct module *module;
// 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, &module, &info, err))
return ERROR_CODE(err);
// ok
return SUCCESS;
}
/**
* Parse and apply a --config option, calling the module's conf func.
*/
static err_t apply_config (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 = module_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(module, conf_name, conf_value, err))
return ERROR_CODE(err);
// ok
return SUCCESS;
}
/**
* 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, "hn:c:m:C:", 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_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_CONFIG:
if (apply_config(nexus, optarg, err))
return ERROR_CODE(err);
break;
case OPT_DEBUG:
set_log_level(LOG_DEBUG);
break;
case '?':
usage(nexus, argv[0]);
return SET_ERROR(err, ERR_CMD_OPT);
}
}
// ok
return SUCCESS;
}
static void on_sigint (evutil_socket_t sig, short what, void *arg)
{
struct nexus *ctx = arg;
(void) sig;
(void) what;
log_info("Quitting...");
// unload the modules
modules_unload(ctx->modules);
// quit the irc client
irc_client_quit(ctx->client, "Goodbye, cruel world ;(");
// remove the signal handlers (ourself)
signals_free(ctx->signals);
// now event_base_dispatch should return once everythings' shut down...
}
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(SIGPIPE)");
if ((ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, &on_sigint, nexus)))
FATAL_ERROR(&err, "signals_add(SIGINT)");
// 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");
// 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))
FATAL("event_base_dispatch");
// ok, no cleanup
return 0;
}