diff --git a/degesch.c b/degesch.c index f3ad894..5b54e0d 100644 --- a/degesch.c +++ b/degesch.c @@ -1249,6 +1249,29 @@ config_validate_nonjunk_string return true; } +static bool +config_validate_addresses + (const struct config_item_ *item, struct error **e) +{ + if (item->type == CONFIG_ITEM_NULL) + return true; + if (!config_validate_nonjunk_string (item, e)) + return false; + + // Comma-separated list of "host[:port]" pairs + regex_t re; + int err = regcomp (&re, "^([^/:,]+(:[^/:,]+)?)?" + "(,([^/:,]+(:[^/:,]+)?)?)*$", REG_EXTENDED | REG_NOSUB); + hard_assert (!err); + + bool result = !regexec (&re, item->value.string.str, 0, NULL, 0); + if (!result) + error_set (e, "invalid address list string"); + + regfree (&re); + return result; +} + static bool config_validate_nonnegative (const struct config_item_ *item, struct error **e) @@ -1279,15 +1302,10 @@ static struct config_schema g_config_server[] = .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 = "addresses", + .comment = "Addresses of the IRC network (e.g. \"irc.net:6667\")", + .type = CONFIG_ITEM_STRING_ARRAY, + .validate = config_validate_addresses }, { .name = "ssl", .comment = "Whether to use SSL/TLS", @@ -3303,9 +3321,24 @@ irc_on_connector_connected (void *user_data, int socket) irc_finish_connection (s, socket); } +static void +irc_split_host_port (char *s, char **host, char **port) +{ + char *colon = strchr (s, ':'); + if (colon) + { + *colon = '\0'; + *port = ++colon; + } + else + *port = "6667"; + + *host = s; +} + static bool irc_setup_connector (struct server *s, - const char *host, const char *port, struct error **e) + const struct str_vector *addresses, struct error **e) { struct connector *connector = xmalloc (sizeof *connector); connector_init (connector, &s->ctx->poller); @@ -3319,33 +3352,28 @@ irc_setup_connector (struct server *s, s->state = IRC_CONNECTING; s->connector = connector; - if (!connector_add_target (connector, host, port, e)) + for (size_t i = 0; i < addresses->len; i++) { - irc_destroy_connector (s); - return false; + char *host, *port; + irc_split_host_port (addresses->vector[i], &host, &port); + + if (!connector_add_target (connector, host, port, e)) + { + irc_destroy_connector (s); + return false; + } } connector_step (connector); return true; } -static void -irc_initiate_connect (struct server *s) +static bool +irc_initiate_connect_socks (struct server *s, + const struct str_vector *addresses, struct error **e) { - hard_assert (s->state == IRC_DISCONNECTED); struct app_context *ctx = s->ctx; - const char *irc_host = get_config_string (ctx, "server.irc_host"); - int64_t irc_port_int = get_config_integer (ctx, "server.irc_port"); - - if (!get_config_string (ctx, "server.irc_host")) - { - // No sense in trying to reconnect - buffer_send_error (ctx, s->buffer, - "No hostname specified in configuration"); - return; - } - const char *socks_host = get_config_string (ctx, "server.socks_host"); int64_t socks_port_int = get_config_integer (ctx, "server.socks_port"); @@ -3354,36 +3382,65 @@ irc_initiate_connect (struct server *s) const char *socks_password = get_config_string (ctx, "server.socks_password"); - char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int); + if (!socks_host) + return false; + + // FIXME: we only try the first address (still better than nothing) + char *irc_host, *irc_port; + irc_split_host_port (addresses->vector[0], &irc_host, &irc_port); + char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int); - // TODO: the SOCKS code needs a rewrite so that we don't block on it either - struct error *e = NULL; - if (socks_host) - { - char *address = format_host_port_pair (irc_host, irc_port); - char *socks_address = format_host_port_pair (socks_host, socks_port); - buffer_send_status (ctx, s->buffer, - "Connecting to %s via %s...", address, socks_address); - free (socks_address); - free (address); + char *address = format_host_port_pair (irc_host, irc_port); + char *socks_address = format_host_port_pair (socks_host, socks_port); + buffer_send_status (ctx, s->buffer, + "Connecting to %s via %s...", address, socks_address); + free (socks_address); + free (address); - struct error *error = NULL; - int fd = socks_connect (socks_host, socks_port, irc_host, irc_port, - socks_username, socks_password, &error); - if (fd != -1) - irc_finish_connection (s, fd); - else - { - error_set (&e, "%s: %s", "SOCKS connection failed", error->message); - error_free (error); - } - } + // TODO: the SOCKS code needs a rewrite so that we don't block on it either; + // perhaps it could act as a special kind of connector + struct error *error = NULL; + bool result = true; + int fd = socks_connect (socks_host, socks_port, irc_host, irc_port, + socks_username, socks_password, &error); + if (fd != -1) + irc_finish_connection (s, fd); else - irc_setup_connector (s, irc_host, irc_port, &e); + { + error_set (e, "%s: %s", "SOCKS connection failed", error->message); + error_free (error); + result = false; + } - free (irc_port); free (socks_port); + return true; +} + +static void +irc_initiate_connect (struct server *s) +{ + hard_assert (s->state == IRC_DISCONNECTED); + struct app_context *ctx = s->ctx; + + const char *addresses = get_config_string (ctx, "server.addresses"); + if (!addresses || !addresses[strspn (addresses, ",")]) + { + // No sense in trying to reconnect + buffer_send_error (ctx, s->buffer, + "No addresses specified in configuration"); + return; + } + + struct str_vector servers; + str_vector_init (&servers); + split_str_ignore_empty (addresses, ',', &servers); + + struct error *e = NULL; + if (!irc_initiate_connect_socks (s, &servers, &e) && !e) + irc_setup_connector (s, &servers, &e); + + str_vector_free (&servers); if (e) { diff --git a/test b/test index 38227aa..daef9f8 100755 --- a/test +++ b/test @@ -20,7 +20,7 @@ expect_after { } # Connect to the daemon -send "/set server.irc_host = \"localhost\"\n" +send "/set server.addresses = \"localhost\"\n" expect "Option changed" send "/disconnect\n" expect "]"