degesch: use the new configuration
This is a simple, almost 1:1 conversion. Needs further unfucking.
This commit is contained in:
parent
3b8e8cc97f
commit
c23898166c
642
degesch.c
642
degesch.c
|
@ -67,41 +67,6 @@ enum
|
||||||
#include <readline/readline.h>
|
#include <readline/readline.h>
|
||||||
#include <readline/history.h>
|
#include <readline/history.h>
|
||||||
|
|
||||||
// --- Configuration (application-specific) ------------------------------------
|
|
||||||
|
|
||||||
// TODO: reject all junk present in the configuration; there can be newlines
|
|
||||||
|
|
||||||
static struct config_item g_config_table[] =
|
|
||||||
{
|
|
||||||
{ "nickname", NULL, "IRC nickname" },
|
|
||||||
{ "username", NULL, "IRC user name" },
|
|
||||||
{ "realname", NULL, "IRC real name/e-mail" },
|
|
||||||
|
|
||||||
{ "irc_host", NULL, "Address of the IRC server" },
|
|
||||||
{ "irc_port", "6667", "Port of the IRC server" },
|
|
||||||
{ "ssl", "off", "Whether to use SSL" },
|
|
||||||
{ "ssl_cert", NULL, "Client SSL certificate (PEM)" },
|
|
||||||
{ "ssl_verify", "on", "Whether to verify certificates" },
|
|
||||||
{ "ssl_ca_file", NULL, "OpenSSL CA bundle file" },
|
|
||||||
{ "ssl_ca_path", NULL, "OpenSSL CA bundle path" },
|
|
||||||
{ "autojoin", NULL, "Channels to join on start" },
|
|
||||||
{ "reconnect", "on", "Whether to reconnect on error" },
|
|
||||||
{ "reconnect_delay", "5", "Time between reconnecting" },
|
|
||||||
|
|
||||||
{ "socks_host", NULL, "Address of a SOCKS 4a/5 proxy" },
|
|
||||||
{ "socks_port", "1080", "SOCKS port number" },
|
|
||||||
{ "socks_username", NULL, "SOCKS auth. username" },
|
|
||||||
{ "socks_password", NULL, "SOCKS auth. password" },
|
|
||||||
|
|
||||||
{ "isolate_buffers", "off", "Isolate global/server buffers" },
|
|
||||||
|
|
||||||
#define XX(x, y, z) { "attr_" y, NULL, z },
|
|
||||||
ATTR_TABLE (XX)
|
|
||||||
#undef XX
|
|
||||||
|
|
||||||
{ NULL, NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Application data --------------------------------------------------------
|
// --- Application data --------------------------------------------------------
|
||||||
|
|
||||||
// All text stored in our data structures is encoded in UTF-8.
|
// All text stored in our data structures is encoded in UTF-8.
|
||||||
|
@ -492,7 +457,7 @@ struct app_context
|
||||||
{
|
{
|
||||||
// Configuration:
|
// Configuration:
|
||||||
|
|
||||||
struct str_map config; ///< User configuration
|
struct config config; ///< Program configuration
|
||||||
char *attrs[ATTR_COUNT]; ///< Terminal attributes
|
char *attrs[ATTR_COUNT]; ///< Terminal attributes
|
||||||
bool no_colors; ///< Colour output mode
|
bool no_colors; ///< Colour output mode
|
||||||
bool reconnect; ///< Whether to reconnect on conn. fail.
|
bool reconnect; ///< Whether to reconnect on conn. fail.
|
||||||
|
@ -546,10 +511,7 @@ app_context_init (struct app_context *self)
|
||||||
{
|
{
|
||||||
memset (self, 0, sizeof *self);
|
memset (self, 0, sizeof *self);
|
||||||
|
|
||||||
str_map_init (&self->config);
|
config_init (&self->config);
|
||||||
self->config.free = free;
|
|
||||||
load_config_defaults (&self->config, g_config_table);
|
|
||||||
|
|
||||||
poller_init (&self->poller);
|
poller_init (&self->poller);
|
||||||
|
|
||||||
server_init (&self->server, &self->poller);
|
server_init (&self->server, &self->poller);
|
||||||
|
@ -582,7 +544,7 @@ app_context_init (struct app_context *self)
|
||||||
static void
|
static void
|
||||||
app_context_free (struct app_context *self)
|
app_context_free (struct app_context *self)
|
||||||
{
|
{
|
||||||
str_map_free (&self->config);
|
config_free (&self->config);
|
||||||
for (size_t i = 0; i < ATTR_COUNT; i++)
|
for (size_t i = 0; i < ATTR_COUNT; i++)
|
||||||
free (self->attrs[i]);
|
free (self->attrs[i]);
|
||||||
|
|
||||||
|
@ -605,6 +567,224 @@ static void refresh_prompt (struct app_context *ctx);
|
||||||
static char *irc_cut_nickname (const char *prefix);
|
static char *irc_cut_nickname (const char *prefix);
|
||||||
static const char *irc_find_userhost (const char *prefix);
|
static const char *irc_find_userhost (const char *prefix);
|
||||||
|
|
||||||
|
// --- Configuration -----------------------------------------------------------
|
||||||
|
|
||||||
|
// TODO: eventually add "on_change" callbacks
|
||||||
|
|
||||||
|
static bool
|
||||||
|
config_validate_nonjunk_string
|
||||||
|
(const struct config_item_ *item, struct error **e)
|
||||||
|
{
|
||||||
|
if (item->type == CONFIG_ITEM_NULL)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
hard_assert (config_item_type_is_string (item->type));
|
||||||
|
for (size_t i = 0; i < item->value.string.len; i++)
|
||||||
|
{
|
||||||
|
// Not even a tabulator
|
||||||
|
unsigned char c = item->value.string.str[i];
|
||||||
|
if (c < 32)
|
||||||
|
{
|
||||||
|
error_set (e, "control characters are not allowed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
config_validate_nonnegative
|
||||||
|
(const struct config_item_ *item, struct error **e)
|
||||||
|
{
|
||||||
|
if (item->type == CONFIG_ITEM_NULL)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
hard_assert (item->type == CONFIG_ITEM_INTEGER);
|
||||||
|
if (item->value.integer >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_set (e, "must be non-negative");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct config_schema g_config_server[] =
|
||||||
|
{
|
||||||
|
{ .name = "nickname",
|
||||||
|
.comment = "IRC nickname",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
{ .name = "username",
|
||||||
|
.comment = "IRC user name",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
{ .name = "realname",
|
||||||
|
.comment = "IRC real name/e-mail",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
|
||||||
|
{ .name = "irc_host",
|
||||||
|
.comment = "Address of the IRC server",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
{ .name = "irc_port",
|
||||||
|
.comment = "Port of the IRC server",
|
||||||
|
.type = CONFIG_ITEM_INTEGER,
|
||||||
|
.validate = config_validate_nonnegative,
|
||||||
|
.default_ = "6667" },
|
||||||
|
|
||||||
|
{ .name = "ssl",
|
||||||
|
.comment = "Whether to use SSL/TLS",
|
||||||
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
|
.default_ = "off" },
|
||||||
|
{ .name = "ssl_cert",
|
||||||
|
.comment = "Client SSL certificate (PEM)",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
|
{ .name = "ssl_verify",
|
||||||
|
.comment = "Whether to verify certificates",
|
||||||
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
|
.default_ = "on" },
|
||||||
|
{ .name = "ssl_ca_file",
|
||||||
|
.comment = "OpenSSL CA bundle file",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
|
{ .name = "ssl_ca_path",
|
||||||
|
.comment = "OpenSSL CA bundle path",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
|
|
||||||
|
{ .name = "autojoin",
|
||||||
|
.comment = "Channels to join on start",
|
||||||
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
{ .name = "reconnect",
|
||||||
|
.comment = "Whether to reconnect on error",
|
||||||
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
|
.default_ = "on" },
|
||||||
|
{ .name = "reconnect_delay",
|
||||||
|
.comment = "Time between reconnecting",
|
||||||
|
.type = CONFIG_ITEM_INTEGER,
|
||||||
|
.default_ = "5" },
|
||||||
|
|
||||||
|
{ .name = "socks_host",
|
||||||
|
.comment = "Address of a SOCKS 4a/5 proxy",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.validate = config_validate_nonjunk_string },
|
||||||
|
{ .name = "socks_port",
|
||||||
|
.comment = "SOCKS port number",
|
||||||
|
.type = CONFIG_ITEM_INTEGER,
|
||||||
|
.validate = config_validate_nonnegative,
|
||||||
|
.default_ = "1080" },
|
||||||
|
{ .name = "socks_username",
|
||||||
|
.comment = "SOCKS auth. username",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
|
{ .name = "socks_password",
|
||||||
|
.comment = "SOCKS auth. password",
|
||||||
|
.type = CONFIG_ITEM_STRING },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct config_schema g_config_behaviour[] =
|
||||||
|
{
|
||||||
|
{ .name = "isolate_buffers",
|
||||||
|
.comment = "Don't leak messages from the server and global buffers",
|
||||||
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
|
.default_ = "off" },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct config_schema g_config_attributes[] =
|
||||||
|
{
|
||||||
|
#define XX(x, y, z) { .name = y, .comment = z, .type = CONFIG_ITEM_STRING },
|
||||||
|
ATTR_TABLE (XX)
|
||||||
|
#undef XX
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_config_server (struct config_item_ *subtree, void *user_data)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
// This will eventually iterate over the object and create servers
|
||||||
|
config_schema_apply_to_object (g_config_server, subtree);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_config_behaviour (struct config_item_ *subtree, void *user_data)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
config_schema_apply_to_object (g_config_behaviour, subtree);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_config_attributes (struct config_item_ *subtree, void *user_data)
|
||||||
|
{
|
||||||
|
(void) user_data;
|
||||||
|
config_schema_apply_to_object (g_config_attributes, subtree);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
register_config_modules (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
struct config *config = &ctx->config;
|
||||||
|
config_register_module (config,
|
||||||
|
"server", load_config_server, ctx);
|
||||||
|
config_register_module (config,
|
||||||
|
"behaviour", load_config_behaviour, ctx);
|
||||||
|
config_register_module (config,
|
||||||
|
"attributes", load_config_attributes, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
get_config_string (struct app_context *ctx, const char *key)
|
||||||
|
{
|
||||||
|
struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
|
||||||
|
hard_assert (item);
|
||||||
|
if (item->type == CONFIG_ITEM_NULL)
|
||||||
|
return NULL;
|
||||||
|
hard_assert (config_item_type_is_string (item->type));
|
||||||
|
return item->value.string.str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
set_config_string (struct app_context *ctx, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
|
||||||
|
hard_assert (item);
|
||||||
|
|
||||||
|
struct str s;
|
||||||
|
str_init (&s);
|
||||||
|
str_append (&s, value);
|
||||||
|
struct config_item_ *new_ = config_item_string (&s);
|
||||||
|
str_free (&s);
|
||||||
|
|
||||||
|
struct error *e = NULL;
|
||||||
|
if (config_item_set_from (item, new_, &e))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
config_item_destroy (new_);
|
||||||
|
print_error ("couldn't set `%s' in configuration: %s", key, e->message);
|
||||||
|
error_free (e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t
|
||||||
|
get_config_integer (struct app_context *ctx, const char *key)
|
||||||
|
{
|
||||||
|
struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
|
||||||
|
hard_assert (item && item->type == CONFIG_ITEM_INTEGER);
|
||||||
|
return item->value.integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_config_boolean (struct app_context *ctx, const char *key)
|
||||||
|
{
|
||||||
|
struct config_item_ *item = config_item_get (ctx->config.root, key, NULL);
|
||||||
|
hard_assert (item && item->type == CONFIG_ITEM_BOOLEAN);
|
||||||
|
return item->value.boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Attributed output -------------------------------------------------------
|
// --- Attributed output -------------------------------------------------------
|
||||||
|
|
||||||
static struct
|
static struct
|
||||||
|
@ -775,12 +955,12 @@ init_attribute (struct app_context *ctx, int id, const char *default_)
|
||||||
{
|
{
|
||||||
static const char *table[ATTR_COUNT] =
|
static const char *table[ATTR_COUNT] =
|
||||||
{
|
{
|
||||||
#define XX(x, y, z) [ATTR_ ## x] = "attr_" y,
|
#define XX(x, y, z) [ATTR_ ## x] = "attributes." y,
|
||||||
ATTR_TABLE (XX)
|
ATTR_TABLE (XX)
|
||||||
#undef XX
|
#undef XX
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *user = str_map_find (&ctx->config, table[id]);
|
const char *user = get_config_string (ctx, table[id]);
|
||||||
if (user)
|
if (user)
|
||||||
ctx->attrs[id] = xstrdup (user);
|
ctx->attrs[id] = xstrdup (user);
|
||||||
else
|
else
|
||||||
|
@ -1633,7 +1813,7 @@ init_buffers (struct app_context *ctx)
|
||||||
global->name = xstrdup (PROGRAM_NAME);
|
global->name = xstrdup (PROGRAM_NAME);
|
||||||
|
|
||||||
server->type = BUFFER_SERVER;
|
server->type = BUFFER_SERVER;
|
||||||
server->name = xstrdup (str_map_find (&ctx->config, "irc_host"));
|
server->name = xstrdup (get_config_string (ctx, "server.irc_host"));
|
||||||
server->server = &ctx->server;
|
server->server = &ctx->server;
|
||||||
|
|
||||||
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, global);
|
LIST_APPEND_WITH_TAIL (ctx->buffers, ctx->buffers_tail, global);
|
||||||
|
@ -1893,34 +2073,17 @@ irc_send (struct server *s, const char *format, ...)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
irc_get_boolean_from_config
|
|
||||||
(struct app_context *ctx, const char *name, bool *value, struct error **e)
|
|
||||||
{
|
|
||||||
const char *str = str_map_find (&ctx->config, name);
|
|
||||||
hard_assert (str != NULL);
|
|
||||||
|
|
||||||
if (set_boolean_if_valid (value, str))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error_set (e, "invalid configuration value for `%s'", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
irc_initialize_ssl_ctx (struct server *s, struct error **e)
|
irc_initialize_ssl_ctx (struct server *s, struct error **e)
|
||||||
{
|
{
|
||||||
// XXX: maybe we should call SSL_CTX_set_options() for some workarounds
|
// XXX: maybe we should call SSL_CTX_set_options() for some workarounds
|
||||||
|
|
||||||
bool verify;
|
bool verify = get_config_boolean (s->ctx, "server.ssl_verify");
|
||||||
if (!irc_get_boolean_from_config (s->ctx, "ssl_verify", &verify, e))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!verify)
|
if (!verify)
|
||||||
SSL_CTX_set_verify (s->ssl_ctx, SSL_VERIFY_NONE, NULL);
|
SSL_CTX_set_verify (s->ssl_ctx, SSL_VERIFY_NONE, NULL);
|
||||||
|
|
||||||
const char *ca_file = str_map_find (&s->ctx->config, "ca_file");
|
const char *ca_file = get_config_string (s->ctx, "server.ca_file");
|
||||||
const char *ca_path = str_map_find (&s->ctx->config, "ca_path");
|
const char *ca_path = get_config_string (s->ctx, "server.ca_path");
|
||||||
|
|
||||||
struct error *error = NULL;
|
struct error *error = NULL;
|
||||||
if (ca_file || ca_path)
|
if (ca_file || ca_path)
|
||||||
|
@ -1970,7 +2133,7 @@ irc_initialize_ssl (struct server *s, struct error **e)
|
||||||
if (!s->ssl)
|
if (!s->ssl)
|
||||||
goto error_ssl_2;
|
goto error_ssl_2;
|
||||||
|
|
||||||
const char *ssl_cert = str_map_find (&s->ctx->config, "ssl_cert");
|
const char *ssl_cert = get_config_string (s->ctx, "server.ssl_cert");
|
||||||
if (ssl_cert)
|
if (ssl_cert)
|
||||||
{
|
{
|
||||||
char *path = resolve_config_filename (ssl_cert);
|
char *path = resolve_config_filename (ssl_cert);
|
||||||
|
@ -3147,7 +3310,7 @@ irc_process_message (const struct irc_message *msg,
|
||||||
// we can also use WHOIS if it's not supported (optional by RFC 2812)
|
// we can also use WHOIS if it's not supported (optional by RFC 2812)
|
||||||
irc_send (s, "USERHOST %s", s->irc_user->nickname);
|
irc_send (s, "USERHOST %s", s->irc_user->nickname);
|
||||||
|
|
||||||
const char *autojoin = str_map_find (&s->ctx->config, "autojoin");
|
const char *autojoin = get_config_string (s->ctx, "server.autojoin");
|
||||||
if (autojoin)
|
if (autojoin)
|
||||||
irc_send (s, "JOIN :%s", autojoin);
|
irc_send (s, "JOIN :%s", autojoin);
|
||||||
}
|
}
|
||||||
|
@ -4076,8 +4239,7 @@ on_irc_timeout (void *user_data)
|
||||||
{
|
{
|
||||||
// Provoke a response from the server
|
// Provoke a response from the server
|
||||||
struct server *s = user_data;
|
struct server *s = user_data;
|
||||||
irc_send (s, "PING :%s",
|
irc_send (s, "PING :%s", get_config_string (s->ctx, "server.nickname"));
|
||||||
(char *) str_map_find (&s->ctx->config, "nickname"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -4146,30 +4308,29 @@ irc_connect (struct server *s, struct error **e)
|
||||||
{
|
{
|
||||||
struct app_context *ctx = s->ctx;
|
struct app_context *ctx = s->ctx;
|
||||||
|
|
||||||
const char *irc_host = str_map_find (&ctx->config, "irc_host");
|
const char *irc_host = get_config_string (ctx, "server.irc_host");
|
||||||
const char *irc_port = str_map_find (&ctx->config, "irc_port");
|
int64_t irc_port_int = get_config_integer (ctx, "server.irc_port");
|
||||||
|
|
||||||
const char *socks_host = str_map_find (&ctx->config, "socks_host");
|
const char *socks_host = get_config_string (ctx, "server.socks_host");
|
||||||
const char *socks_port = str_map_find (&ctx->config, "socks_port");
|
int64_t socks_port_int = get_config_integer (ctx, "server.socks_port");
|
||||||
const char *socks_username = str_map_find (&ctx->config, "socks_username");
|
const char *socks_username =
|
||||||
const char *socks_password = str_map_find (&ctx->config, "socks_password");
|
get_config_string (ctx, "server.socks_username");
|
||||||
|
const char *socks_password =
|
||||||
|
get_config_string (ctx, "server.socks_password");
|
||||||
|
|
||||||
const char *nickname = str_map_find (&ctx->config, "nickname");
|
// FIXME: use it as a number everywhere, there's no need for named services
|
||||||
const char *username = str_map_find (&ctx->config, "username");
|
// FIXME: memory leak
|
||||||
const char *realname = str_map_find (&ctx->config, "realname");
|
char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int);
|
||||||
|
char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int);
|
||||||
|
|
||||||
// We have a default value for these
|
const char *nickname = get_config_string (ctx, "server.nickname");
|
||||||
hard_assert (irc_port && socks_port);
|
const char *username = get_config_string (ctx, "server.username");
|
||||||
|
const char *realname = get_config_string (ctx, "server.realname");
|
||||||
|
|
||||||
// These are filled automatically if needed
|
// These are filled automatically if needed
|
||||||
hard_assert (nickname && username && realname);
|
hard_assert (nickname && username && realname);
|
||||||
|
|
||||||
// TODO: again, get rid of `struct error' in here. The question is: how
|
bool use_ssl = get_config_boolean (ctx, "server.ssl");
|
||||||
// do we tell our caller that he should not try to reconnect?
|
|
||||||
bool use_ssl;
|
|
||||||
if (!irc_get_boolean_from_config (ctx, "ssl", &use_ssl, e))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (socks_host)
|
if (socks_host)
|
||||||
{
|
{
|
||||||
char *address = format_host_port_pair (irc_host, irc_port);
|
char *address = format_host_port_pair (irc_host, irc_port);
|
||||||
|
@ -4288,115 +4449,12 @@ on_readline_input (char *line)
|
||||||
|
|
||||||
// --- Configuration loading ---------------------------------------------------
|
// --- Configuration loading ---------------------------------------------------
|
||||||
|
|
||||||
static bool
|
|
||||||
read_hexa_escape (const char **cursor, struct str *output)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
char c, code = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < 2; i++)
|
|
||||||
{
|
|
||||||
c = tolower (*(*cursor));
|
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
code = (code << 4) | (c - '0');
|
|
||||||
else if (c >= 'a' && c <= 'f')
|
|
||||||
code = (code << 4) | (c - 'a' + 10);
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
|
|
||||||
(*cursor)++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!i)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
str_append_c (output, code);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
read_octal_escape (const char **cursor, struct str *output)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
char c, code = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
c = *(*cursor);
|
|
||||||
if (c < '0' || c > '7')
|
|
||||||
break;
|
|
||||||
|
|
||||||
code = (code << 3) | (c - '0');
|
|
||||||
(*cursor)++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!i)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
str_append_c (output, code);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
read_string_escape_sequence (const char **cursor,
|
|
||||||
struct str *output, struct error **e)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
switch ((c = *(*cursor)++))
|
|
||||||
{
|
|
||||||
case '?': str_append_c (output, '?'); break;
|
|
||||||
case '"': str_append_c (output, '"'); break;
|
|
||||||
case '\\': str_append_c (output, '\\'); break;
|
|
||||||
case 'a': str_append_c (output, '\a'); break;
|
|
||||||
case 'b': str_append_c (output, '\b'); break;
|
|
||||||
case 'f': str_append_c (output, '\f'); break;
|
|
||||||
case 'n': str_append_c (output, '\n'); break;
|
|
||||||
case 'r': str_append_c (output, '\r'); break;
|
|
||||||
case 't': str_append_c (output, '\t'); break;
|
|
||||||
case 'v': str_append_c (output, '\v'); break;
|
|
||||||
|
|
||||||
case 'e':
|
|
||||||
case 'E':
|
|
||||||
str_append_c (output, '\x1b');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
case 'X':
|
|
||||||
if (!read_hexa_escape (cursor, output))
|
|
||||||
FAIL ("invalid hexadecimal escape");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '\0':
|
|
||||||
FAIL ("premature end of escape sequence");
|
|
||||||
|
|
||||||
default:
|
|
||||||
(*cursor)--;
|
|
||||||
if (!read_octal_escape (cursor, output))
|
|
||||||
FAIL ("unknown escape sequence");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
unescape_string (const char *s, struct str *output, struct error **e)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
while ((c = *s++))
|
|
||||||
{
|
|
||||||
if (c != '\\')
|
|
||||||
str_append_c (output, c);
|
|
||||||
else if (!read_string_escape_sequence (&s, output, e))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
autofill_user_info (struct app_context *ctx, struct error **e)
|
autofill_user_info (struct app_context *ctx, struct error **e)
|
||||||
{
|
{
|
||||||
const char *nickname = str_map_find (&ctx->config, "nickname");
|
const char *nickname = get_config_string (ctx, "server.nickname");
|
||||||
const char *username = str_map_find (&ctx->config, "username");
|
const char *username = get_config_string (ctx, "server.username");
|
||||||
const char *realname = str_map_find (&ctx->config, "realname");
|
const char *realname = get_config_string (ctx, "server.realname");
|
||||||
|
|
||||||
if (nickname && username && realname)
|
if (nickname && username && realname)
|
||||||
return true;
|
return true;
|
||||||
|
@ -4407,9 +4465,9 @@ autofill_user_info (struct app_context *ctx, struct error **e)
|
||||||
FAIL ("cannot retrieve user information: %s", strerror (errno));
|
FAIL ("cannot retrieve user information: %s", strerror (errno));
|
||||||
|
|
||||||
if (!nickname)
|
if (!nickname)
|
||||||
str_map_set (&ctx->config, "nickname", xstrdup (pwd->pw_name));
|
set_config_string (ctx, "server.nickname", pwd->pw_name);
|
||||||
if (!username)
|
if (!username)
|
||||||
str_map_set (&ctx->config, "username", xstrdup (pwd->pw_name));
|
set_config_string (ctx, "server.username", pwd->pw_name);
|
||||||
|
|
||||||
// Not all systems have the GECOS field but the vast majority does
|
// Not all systems have the GECOS field but the vast majority does
|
||||||
if (!realname)
|
if (!realname)
|
||||||
|
@ -4421,76 +4479,174 @@ autofill_user_info (struct app_context *ctx, struct error **e)
|
||||||
if (comma)
|
if (comma)
|
||||||
*comma = '\0';
|
*comma = '\0';
|
||||||
|
|
||||||
str_map_set (&ctx->config, "realname", xstrdup (gecos));
|
set_config_string (ctx, "server.realname", gecos);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
unescape_config (struct str_map *input, struct str_map *output, struct error **e)
|
read_file (const char *filename, struct str *output, struct error **e)
|
||||||
{
|
{
|
||||||
|
FILE *fp = fopen (filename, "rb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
error_set (e, "could not open `%s' for reading: %s",
|
||||||
|
filename, strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
while ((len = fread (buf, 1, sizeof buf, fp)) == sizeof buf)
|
||||||
|
str_append_data (output, buf, len);
|
||||||
|
str_append_data (output, buf, len);
|
||||||
|
|
||||||
|
bool success = !ferror (fp);
|
||||||
|
fclose (fp);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_set (e, "error while reading `%s': %s",
|
||||||
|
filename, strerror (errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
load_configuration (struct app_context *ctx, struct error **e)
|
||||||
|
{
|
||||||
|
char *filename = resolve_config_filename (PROGRAM_NAME ".conf");
|
||||||
|
if (!filename)
|
||||||
|
{
|
||||||
|
error_set (e, "cannot find configuration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct str data;
|
||||||
|
str_init (&data);
|
||||||
|
bool success = read_file (filename, &data, e);
|
||||||
|
free (filename);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
str_free (&data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
struct error *error = NULL;
|
struct error *error = NULL;
|
||||||
struct str_map_iter iter;
|
struct config_item_ *root =
|
||||||
str_map_iter_init (&iter, input);
|
config_item_parse (data.str, data.len, false, &error);
|
||||||
while (str_map_iter_next (&iter))
|
str_free (&data);
|
||||||
|
if (!root)
|
||||||
{
|
{
|
||||||
struct str value;
|
error_set (e, "configuration parse error: %s", error->message);
|
||||||
str_init (&value);
|
|
||||||
if (!unescape_string (iter.link->data, &value, &error))
|
|
||||||
{
|
|
||||||
error_set (e, "error reading configuration: %s: %s",
|
|
||||||
iter.link->key, error->message);
|
|
||||||
error_free (error);
|
error_free (error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
str_map_set (output, iter.link->key, str_steal (&value));
|
config_load (&ctx->config, root);
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
if (!autofill_user_info (ctx, e))
|
||||||
load_config (struct app_context *ctx, struct error **e)
|
|
||||||
{
|
|
||||||
// TODO: employ a better configuration file format, so that we don't have
|
|
||||||
// to do this convoluted post-processing anymore.
|
|
||||||
|
|
||||||
struct str_map map;
|
|
||||||
str_map_init (&map);
|
|
||||||
map.free = free;
|
|
||||||
|
|
||||||
bool success = read_config_file (&map, e) &&
|
|
||||||
unescape_config (&map, &ctx->config, e) &&
|
|
||||||
autofill_user_info (ctx, e);
|
|
||||||
str_map_free (&map);
|
|
||||||
if (!success)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const char *irc_host = str_map_find (&ctx->config, "irc_host");
|
if (!get_config_string (ctx, "server.irc_host"))
|
||||||
if (!irc_host)
|
|
||||||
{
|
{
|
||||||
error_set (e, "no hostname specified in configuration");
|
error_set (e, "no hostname specified in configuration");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!irc_get_boolean_from_config (ctx,
|
ctx->reconnect =
|
||||||
"reconnect", &ctx->reconnect, e)
|
get_config_boolean (ctx, "server.reconnect");
|
||||||
|| !irc_get_boolean_from_config (ctx,
|
ctx->isolate_buffers =
|
||||||
"isolate_buffers", &ctx->isolate_buffers, e))
|
get_config_boolean (ctx, "behaviour.isolate_buffers");
|
||||||
return false;
|
ctx->reconnect_delay =
|
||||||
|
get_config_integer (ctx, "server.reconnect_delay");
|
||||||
const char *delay_str = str_map_find (&ctx->config, "reconnect_delay");
|
|
||||||
hard_assert (delay_str != NULL); // We have a default value for this
|
|
||||||
if (!xstrtoul (&ctx->reconnect_delay, delay_str, 10))
|
|
||||||
{
|
|
||||||
error_set (e, "invalid configuration value for `%s'",
|
|
||||||
"reconnect_delay");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
write_configuration_file (const struct str *data, struct error **e)
|
||||||
|
{
|
||||||
|
struct str path;
|
||||||
|
str_init (&path);
|
||||||
|
get_xdg_home_dir (&path, "XDG_CONFIG_HOME", ".config");
|
||||||
|
str_append (&path, "/" PROGRAM_NAME);
|
||||||
|
|
||||||
|
if (!mkdir_with_parents (path.str, e))
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
str_append (&path, "/" PROGRAM_NAME ".conf");
|
||||||
|
FILE *fp = fopen (path.str, "w");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
error_set (e, "could not open `%s' for writing: %s",
|
||||||
|
path.str, strerror (errno));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
fwrite (data->str, data->len, 1, fp);
|
||||||
|
fclose (fp);
|
||||||
|
|
||||||
|
if (errno)
|
||||||
|
{
|
||||||
|
error_set (e, "writing to `%s' failed: %s", path.str, strerror (errno));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
return str_steal (&path);
|
||||||
|
|
||||||
|
error:
|
||||||
|
str_free (&path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
serialize_configuration (struct app_context *ctx, struct str *output)
|
||||||
|
{
|
||||||
|
str_append (output,
|
||||||
|
"# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n"
|
||||||
|
"#\n"
|
||||||
|
"# Relative paths are searched for in ${XDG_CONFIG_HOME:-~/.config}\n"
|
||||||
|
"# /" PROGRAM_NAME " as well as in $XDG_CONFIG_DIRS/" PROGRAM_NAME "\n"
|
||||||
|
"#\n"
|
||||||
|
"# Everything is in UTF-8. Any custom comments will be overwritten.\n"
|
||||||
|
"\n");
|
||||||
|
|
||||||
|
config_item_write (ctx->config.root, true, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_default_configuration ()
|
||||||
|
{
|
||||||
|
// XXX: this is a hack before we remove this awkward functionality
|
||||||
|
// altogether; the user will want to do this from the user interface
|
||||||
|
|
||||||
|
struct app_context ctx = {};
|
||||||
|
config_init (&ctx.config);
|
||||||
|
register_config_modules (&ctx);
|
||||||
|
config_load (&ctx.config, config_item_object ());
|
||||||
|
|
||||||
|
struct str data;
|
||||||
|
str_init (&data);
|
||||||
|
serialize_configuration (&ctx, &data);
|
||||||
|
|
||||||
|
struct error *e = NULL;
|
||||||
|
char *filename = write_configuration_file (&data, &e);
|
||||||
|
str_free (&data);
|
||||||
|
config_free (&ctx.config);
|
||||||
|
|
||||||
|
if (!filename)
|
||||||
|
{
|
||||||
|
print_error ("%s", e->message);
|
||||||
|
error_free (e);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
print_status ("configuration written to `%s'", filename);
|
||||||
|
free (filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Main program ------------------------------------------------------------
|
// --- Main program ------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -4519,8 +4675,7 @@ main (int argc, char *argv[])
|
||||||
{ 'd', "debug", NULL, 0, "run in debug mode" },
|
{ 'd', "debug", NULL, 0, "run in debug mode" },
|
||||||
{ 'h', "help", NULL, 0, "display this help and exit" },
|
{ 'h', "help", NULL, 0, "display this help and exit" },
|
||||||
{ 'V', "version", NULL, 0, "output version information and exit" },
|
{ 'V', "version", NULL, 0, "output version information and exit" },
|
||||||
{ 'w', "write-default-cfg", "FILENAME",
|
{ 'w', "write-default-cfg", NULL, OPT_LONG_ONLY,
|
||||||
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
|
|
||||||
"write a default configuration file and exit" },
|
"write a default configuration file and exit" },
|
||||||
{ 0, NULL, NULL, 0, NULL }
|
{ 0, NULL, NULL, 0, NULL }
|
||||||
};
|
};
|
||||||
|
@ -4542,7 +4697,7 @@ main (int argc, char *argv[])
|
||||||
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
case 'w':
|
case 'w':
|
||||||
call_write_default_config (optarg, g_config_table);
|
write_default_configuration ();
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
default:
|
default:
|
||||||
print_error ("wrong options");
|
print_error ("wrong options");
|
||||||
|
@ -4571,9 +4726,10 @@ main (int argc, char *argv[])
|
||||||
stifle_history (HISTORY_LIMIT);
|
stifle_history (HISTORY_LIMIT);
|
||||||
|
|
||||||
setup_signal_handlers ();
|
setup_signal_handlers ();
|
||||||
|
register_config_modules (&ctx);
|
||||||
|
|
||||||
struct error *e = NULL;
|
struct error *e = NULL;
|
||||||
if (!load_config (&ctx, &e))
|
if (!load_configuration (&ctx, &e))
|
||||||
{
|
{
|
||||||
print_error ("%s", e->message);
|
print_error ("%s", e->message);
|
||||||
error_free (e);
|
error_free (e);
|
||||||
|
|
Loading…
Reference in New Issue