diff --git a/irssi.conf b/irssi.conf index 174d0552..80c2b9c3 100644 --- a/irssi.conf +++ b/irssi.conf @@ -1,10 +1,10 @@ servers = ( { address = "irc.dal.net"; chatnet = "DALnet"; port = "6667"; }, - { address = "ssl.efnet.org"; chatnet = "EFNet"; port = "9999"; use_tls = "yes"; }, + { address = "ssl.efnet.org"; chatnet = "EFNet"; port = "9999"; use_tls = "yes"; tls_verify = "no"; }, { address = "irc.esper.net"; chatnet = "EsperNet"; port = "6697"; use_tls = "yes"; tls_verify = "yes"; }, { address = "chat.freenode.net"; chatnet = "Freenode"; port = "6697"; use_tls = "yes"; tls_verify = "yes"; }, { address = "irc.gamesurge.net"; chatnet = "GameSurge"; port = "6667"; }, - { address = "eu.irc6.net"; chatnet = "IRCnet"; port = "6667"; use_tls = "yes"; }, + { address = "ssl.ircnet.ovh"; chatnet = "IRCnet"; port = "6697"; use_tls = "yes"; tls_verify = "yes"; }, { address = "open.ircnet.net"; chatnet = "IRCnet"; port = "6667"; }, { address = "irc.ircsource.net"; chatnet = "IRCSource"; port = "6667"; }, { address = "irc.netfuze.net"; chatnet = "NetFuze"; port = "6667"; }, diff --git a/src/common.h b/src/common.h index ccd000d5..bdbafca7 100644 --- a/src/common.h +++ b/src/common.h @@ -6,7 +6,7 @@ #define IRSSI_GLOBAL_CONFIG "irssi.conf" /* config file name in /etc/ */ #define IRSSI_HOME_CONFIG "config" /* config file name in ~/.irssi/ */ -#define IRSSI_ABI_VERSION 35 +#define IRSSI_ABI_VERSION 36 #define DEFAULT_SERVER_ADD_PORT 6667 #define DEFAULT_SERVER_ADD_TLS_PORT 6697 diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index ae44ef63..dd8dbe75 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -40,7 +40,7 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, CHAT_PROTOCOL_REC *proto; SERVER_CONNECT_REC *conn; GHashTable *optlist; - char *addr, *portstr, *password, *nick, *chatnet, *host, *tmp; + char *addr, *portstr, *password, *nick, *chatnet, *host; void *free_arg; g_return_val_if_fail(data != NULL, NULL); @@ -71,8 +71,8 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, if (chatnet == NULL) chatnet = g_hash_table_lookup(optlist, "network"); - conn = server_create_conn(proto != NULL ? proto->id : -1, addr, - atoi(portstr), chatnet, password, nick); + conn = server_create_conn_opt(proto != NULL ? proto->id : -1, addr, atoi(portstr), chatnet, + password, nick, optlist); if (conn == NULL) { signal_emit("error command", 1, GINT_TO_POINTER(CMDERR_NO_SERVER_DEFINED)); @@ -94,46 +94,7 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, if (strchr(addr, '/') != NULL) conn->unix_socket = TRUE; - if (g_hash_table_lookup(optlist, "6") != NULL) - conn->family = AF_INET6; - else if (g_hash_table_lookup(optlist, "4") != NULL) - conn->family = AF_INET; - - if (g_hash_table_lookup(optlist, "tls") != NULL || g_hash_table_lookup(optlist, "ssl") != NULL) - conn->use_tls = TRUE; - if ((tmp = g_hash_table_lookup(optlist, "tls_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL) - conn->tls_cert = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_pkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL) - conn->tls_pkey = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_pass")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL) - conn->tls_pass = g_strdup(tmp); - if (g_hash_table_lookup(optlist, "tls_verify") != NULL || g_hash_table_lookup(optlist, "ssl_verify") != NULL) - conn->tls_verify = TRUE; - if ((tmp = g_hash_table_lookup(optlist, "tls_cafile")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL) - conn->tls_cafile = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_capath")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL) - conn->tls_capath = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL) - conn->tls_ciphers = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL) - conn->tls_pinned_cert = g_strdup(tmp); - if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL || (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL) - conn->tls_pinned_pubkey = g_strdup(tmp); - if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0') - || (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0')) - conn->tls_verify = TRUE; - if ((conn->tls_cert != NULL && conn->tls_cert[0] != '\0') || conn->tls_verify) - conn->use_tls = TRUE; - - if (g_hash_table_lookup(optlist, "!") != NULL) - conn->no_autojoin_channels = TRUE; - - if (g_hash_table_lookup(optlist, "noautosendcmd") != NULL) - conn->no_autosendcmd = TRUE; - - if (g_hash_table_lookup(optlist, "noproxy") != NULL) - g_free_and_null(conn->proxy); - + /* TLS options are handled in server_create_conn_opt ... -> server_setup_fill_optlist */ *rawlog_file = g_strdup(g_hash_table_lookup(optlist, "rawlog")); @@ -149,13 +110,13 @@ static SERVER_CONNECT_REC *get_server_connect(const char *data, int *plus_addr, return conn; } -/* SYNTAX: CONNECT [-4 | -6] [-tls] [-tls_cert ] [-tls_pkey ] [-tls_pass ] +/* SYNTAX: CONNECT [-4 | -6] [-tls_cert ] [-tls_pkey ] [-tls_pass ] [-tls_verify] [-tls_cafile ] [-tls_capath ] - [-tls_ciphers ] [-tls_pinned_cert ] [-tls_pinned_pubkey ] - [-!] [-noautosendcmd] - [-noproxy] [-network ] [-host ] - [-rawlog ] -
| [ [ []]] */ + [-tls_ciphers ] [-tls_pinned_cert ] + [-tls_pinned_pubkey ] [-!] [-noautosendcmd] [-tls | -notls] + [-starttls | -disallow_starttls] [-noproxy] [-network ] + [-host ] [-rawlog ] +
| [ [ []]] */ /* NOTE: -network replaces the old -ircnet flag. */ static void cmd_connect(const char *data) { @@ -520,9 +481,9 @@ void chat_commands_init(void) command_set_options( "connect", "4 6 !! -network ~ssl ~+ssl_cert ~+ssl_pkey ~+ssl_pass ~ssl_verify ~+ssl_cafile " - "~+ssl_capath ~+ssl_ciphers ~+ssl_pinned_cert ~+ssl_pinned_pubkey tls +tls_cert " - "+tls_pkey +tls_pass tls_verify +tls_cafile +tls_capath +tls_ciphers +tls_pinned_cert " - "+tls_pinned_pubkey +host noproxy -rawlog noautosendcmd"); + "~+ssl_capath ~+ssl_ciphers ~+ssl_pinned_cert ~+ssl_pinned_pubkey tls notls +tls_cert " + "+tls_pkey +tls_pass tls_verify notls_verify +tls_cafile +tls_capath +tls_ciphers " + "+tls_pinned_cert +tls_pinned_pubkey +host noproxy -rawlog noautosendcmd"); command_set_options("msg", "channel nick"); } diff --git a/src/core/servers-reconnect.c b/src/core/servers-reconnect.c index 0cfac2ec..a9d9422b 100644 --- a/src/core/servers-reconnect.c +++ b/src/core/servers-reconnect.c @@ -239,8 +239,11 @@ static void sig_reconnect(SERVER_REC *server) if (reconnect_time == -1 || !server_should_reconnect(server)) return; - conn = server_connect_copy_skeleton(server->connrec, FALSE); - g_return_if_fail(conn != NULL); + sserver = server_setup_find(server->connrec->address, server->connrec->port, + server->connrec->chatnet); + + conn = server_connect_copy_skeleton(server->connrec, sserver == NULL); + g_return_if_fail(conn != NULL); /* save the server status */ if (server->connected) { @@ -249,10 +252,6 @@ static void sig_reconnect(SERVER_REC *server) reconnect_save_status(conn, server); } - sserver = server_setup_find(server->connrec->address, - server->connrec->port, - server->connrec->chatnet); - if (sserver != NULL) { /* save the last connection time/status */ sserver->last_connect = server->connect_time == 0 ? diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index 43653c43..357e8ebe 100644 --- a/src/core/servers-setup.c +++ b/src/core/servers-setup.c @@ -140,8 +140,8 @@ void server_setup_fill_reconn(SERVER_CONNECT_REC *conn, signal_emit("server setup fill reconn", 2, conn, sserver); } -static void server_setup_fill(SERVER_CONNECT_REC *conn, - const char *address, int port) +static void server_setup_fill(SERVER_CONNECT_REC *conn, const char *address, int port, + GHashTable *optlist) { g_return_if_fail(conn != NULL); g_return_if_fail(address != NULL); @@ -177,7 +177,69 @@ static void server_setup_fill(SERVER_CONNECT_REC *conn, memcpy(conn->own_ip6, source_host_ip6, sizeof(IPADDR)); } - signal_emit("server setup fill connect", 1, conn); + signal_emit("server setup fill connect", 2, conn, optlist); +} + +static void server_setup_fill_optlist(SERVER_CONNECT_REC *conn, GHashTable *optlist) +{ + char *tmp; + + if (g_hash_table_lookup(optlist, "6") != NULL) + conn->family = AF_INET6; + else if (g_hash_table_lookup(optlist, "4") != NULL) + conn->family = AF_INET; + + /* ad-hoc TLS settings from command optlist */ + if ((tmp = g_hash_table_lookup(optlist, "tls_cert")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_cert")) != NULL) + conn->tls_cert = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pkey")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_pkey")) != NULL) + conn->tls_pkey = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pass")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_pass")) != NULL) + conn->tls_pass = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_cafile")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_cafile")) != NULL) + conn->tls_cafile = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_capath")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_capath")) != NULL) + conn->tls_capath = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_ciphers")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_ciphers")) != NULL) + conn->tls_ciphers = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_cert")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_pinned_cert")) != NULL) + conn->tls_pinned_cert = g_strdup(tmp); + if ((tmp = g_hash_table_lookup(optlist, "tls_pinned_pubkey")) != NULL || + (tmp = g_hash_table_lookup(optlist, "ssl_pinned_pubkey")) != NULL) + conn->tls_pinned_pubkey = g_strdup(tmp); + if ((conn->tls_capath != NULL && conn->tls_capath[0] != '\0') || + (conn->tls_cafile != NULL && conn->tls_cafile[0] != '\0')) + conn->tls_verify = TRUE; + if (g_hash_table_lookup(optlist, "notls_verify") != NULL) + conn->tls_verify = FALSE; + if (g_hash_table_lookup(optlist, "tls_verify") != NULL || + g_hash_table_lookup(optlist, "ssl_verify") != NULL) + conn->tls_verify = TRUE; + if ((conn->tls_cert != NULL && conn->tls_cert[0] != '\0') || conn->tls_verify) + conn->use_tls = TRUE; + if (g_hash_table_lookup(optlist, "notls") != NULL) + conn->use_tls = FALSE; + if (g_hash_table_lookup(optlist, "tls") != NULL || + g_hash_table_lookup(optlist, "ssl") != NULL) + conn->use_tls = TRUE; + + if (g_hash_table_lookup(optlist, "!") != NULL) + conn->no_autojoin_channels = TRUE; + + if (g_hash_table_lookup(optlist, "noautosendcmd") != NULL) + conn->no_autosendcmd = TRUE; + + if (g_hash_table_lookup(optlist, "noproxy") != NULL) + g_free_and_null(conn->proxy); + + signal_emit("server setup fill optlist", 2, conn, optlist); } static void server_setup_fill_server(SERVER_CONNECT_REC *conn, @@ -219,10 +281,9 @@ static void server_setup_fill_chatnet(SERVER_CONNECT_REC *conn, signal_emit("server setup fill chatnet", 2, conn, chatnet); } -static SERVER_CONNECT_REC * -create_addr_conn(int chat_type, const char *address, int port, - const char *chatnet, const char *password, - const char *nick) +static SERVER_CONNECT_REC *create_addr_conn(int chat_type, const char *address, int port, + const char *chatnet, const char *password, + const char *nick, GHashTable *optlist) { CHAT_PROTOCOL_REC *proto; SERVER_CONNECT_REC *conn; @@ -250,7 +311,7 @@ create_addr_conn(int chat_type, const char *address, int port, conn->chatnet = g_strdup(chatnet); /* fill in the defaults */ - server_setup_fill(conn, address, port); + server_setup_fill(conn, address, port, optlist); /* fill the rest from chat network settings */ chatnetrec = chatnet != NULL ? chatnet_find(chatnet) : @@ -263,6 +324,10 @@ create_addr_conn(int chat_type, const char *address, int port, if (sserver != NULL) server_setup_fill_server(conn, sserver); + /* fill the optlist overrides */ + if (g_hash_table_size(optlist)) + server_setup_fill_optlist(conn, optlist); + /* nick / password given in command line overrides all settings */ if (password && *password) { g_free_not_null(conn->password); @@ -279,9 +344,8 @@ create_addr_conn(int chat_type, const char *address, int port, /* Connect to server where last connect succeeded (or we haven't tried to connect yet). If there's no such server, connect to server where we haven't connected for the longest time */ -static SERVER_CONNECT_REC * -create_chatnet_conn(const char *dest, int port, - const char *password, const char *nick) +static SERVER_CONNECT_REC *create_chatnet_conn(const char *dest, int port, const char *password, + const char *nick, GHashTable *optlist) { SERVER_SETUP_REC *bestrec; GSList *tmp; @@ -308,16 +372,15 @@ create_chatnet_conn(const char *dest, int port, } return bestrec == NULL ? NULL : - create_addr_conn(bestrec->chat_type, bestrec->address, 0, - dest, NULL, nick); + create_addr_conn(bestrec->chat_type, bestrec->address, 0, dest, + NULL, nick, optlist); } /* Create server connection record. `dest' is required, rest can be NULL. `dest' is either a server address or chat network */ -SERVER_CONNECT_REC * -server_create_conn(int chat_type, const char *dest, int port, - const char *chatnet, const char *password, - const char *nick) +SERVER_CONNECT_REC *server_create_conn_opt(int chat_type, const char *dest, int port, + const char *chatnet, const char *password, + const char *nick, GHashTable *optlist) { SERVER_CONNECT_REC *rec; CHATNET_REC *chatrec; @@ -326,7 +389,7 @@ server_create_conn(int chat_type, const char *dest, int port, chatrec = chatnet_find(dest); if (chatrec != NULL) { - rec = create_chatnet_conn(chatrec->name, port, password, nick); + rec = create_chatnet_conn(chatrec->name, port, password, nick, optlist); /* If rec is NULL the chatnet has no url to connect to */ return rec; } @@ -335,8 +398,20 @@ server_create_conn(int chat_type, const char *dest, int port, if (chatrec != NULL) chatnet = chatrec->name; - return create_addr_conn(chat_type, dest, port, - chatnet, password, nick); + return create_addr_conn(chat_type, dest, port, chatnet, password, nick, optlist); +} + +SERVER_CONNECT_REC *server_create_conn(int chat_type, const char *dest, int port, + const char *chatnet, const char *password, const char *nick) +{ + SERVER_CONNECT_REC *ret; + GHashTable *opt; + + opt = g_hash_table_new(NULL, NULL); + ret = server_create_conn_opt(chat_type, dest, port, chatnet, password, nick, opt); + g_hash_table_destroy(opt); + + return ret; } /* Find matching server from setup. Try to find record with a same port, @@ -409,7 +484,8 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) rec->password = g_strdup(config_node_get_str(node, "password", NULL)); rec->use_tls = config_node_get_bool(node, "use_tls", FALSE) || config_node_get_bool(node, "use_ssl", FALSE); - rec->tls_verify = config_node_get_bool(node, "tls_verify", FALSE) || config_node_get_bool(node, "ssl_verify", FALSE); + rec->tls_verify = config_node_get_bool(node, "tls_verify", TRUE) || + config_node_get_bool(node, "ssl_verify", FALSE); value = config_node_get_str(node, "tls_cert", NULL); if (value == NULL) @@ -451,11 +527,6 @@ static SERVER_SETUP_REC *server_setup_read(CONFIG_NODE *node) value = config_node_get_str(node, "ssl_pinned_pubkey", NULL); rec->tls_pinned_pubkey = g_strdup(value); - if (rec->tls_cafile || rec->tls_capath) - rec->tls_verify = TRUE; - if (rec->tls_cert != NULL || rec->tls_verify) - rec->use_tls = TRUE; - rec->port = port; rec->autoconnect = config_node_get_bool(node, "autoconnect", FALSE); rec->no_proxy = config_node_get_bool(node, "no_proxy", FALSE); diff --git a/src/core/servers-setup.h b/src/core/servers-setup.h index 296a94fa..f35c3873 100644 --- a/src/core/servers-setup.h +++ b/src/core/servers-setup.h @@ -31,6 +31,10 @@ server_create_conn(int chat_type, const char *dest, int port, const char *chatnet, const char *password, const char *nick); +SERVER_CONNECT_REC *server_create_conn_opt(int chat_type, const char *dest, int port, + const char *chatnet, const char *password, + const char *nick, GHashTable *optlist); + /* Find matching server from setup. Try to find record with a same port, but fallback to any server with the same address. */ SERVER_SETUP_REC *server_setup_find(const char *address, int port, diff --git a/src/core/session.c b/src/core/session.c index 6f3908e5..5fe481fb 100644 --- a/src/core/session.c +++ b/src/core/session.c @@ -254,23 +254,35 @@ static void session_restore_server(CONFIG_NODE *node) proto = chat_protocol_find(chat_type); if (proto == NULL || proto->not_initialized) { - if (handle < 0) close(handle); + if (handle >= 0) + close(handle); return; } conn = server_create_conn(proto->id, address, port, chatnet, password, nick); - if (conn != NULL) { - conn->reconnection = TRUE; - conn->connect_handle = i_io_channel_new(handle); + if (conn == NULL) + return; - server = proto->server_init_connect(conn); - server->version = g_strdup(config_node_get_str(node, "version", NULL)); - server->session_reconnect = TRUE; - signal_emit("session restore server", 2, server, node); + conn->use_tls = config_node_get_bool(node, "use_tls", FALSE); + conn->tls_cert = g_strdup(config_node_get_str(node, "tls_cert", NULL)); + conn->tls_pkey = g_strdup(config_node_get_str(node, "tls_pkey", NULL)); + conn->tls_verify = config_node_get_bool(node, "tls_verify", TRUE); + conn->tls_cafile = g_strdup(config_node_get_str(node, "tls_cafile", NULL)); + conn->tls_capath = g_strdup(config_node_get_str(node, "tls_capath", NULL)); + conn->tls_ciphers = g_strdup(config_node_get_str(node, "tls_ciphers", NULL)); + conn->tls_pinned_cert = g_strdup(config_node_get_str(node, "tls_pinned_cert", NULL)); + conn->tls_pinned_pubkey = g_strdup(config_node_get_str(node, "tls_pinned_pubkey", NULL)); - proto->server_connect(server); - } + conn->reconnection = TRUE; + conn->connect_handle = i_io_channel_new(handle); + + server = proto->server_init_connect(conn); + server->version = g_strdup(config_node_get_str(node, "version", NULL)); + server->session_reconnect = TRUE; + signal_emit("session restore server", 2, server, node); + + proto->server_connect(server); } static void sig_session_save(CONFIG_REC *config) diff --git a/src/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c index 5ed67170..e0100618 100644 --- a/src/fe-common/core/fe-server.c +++ b/src/fe-common/core/fe-server.c @@ -101,6 +101,7 @@ static SERVER_SETUP_REC *create_server_setup(GHashTable *optlist) server = rec->create_server_setup(); server->chat_type = rec->id; + server->tls_verify = TRUE; return server; } @@ -110,6 +111,7 @@ static void cmd_server_add_modify(const char *data, gboolean add) SERVER_SETUP_REC *rec; char *addr, *portstr, *password, *value, *chatnet; void *free_arg; + gboolean newrec; int port; if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS, @@ -135,6 +137,7 @@ static void cmd_server_add_modify(const char *data, gboolean add) rec = server_setup_find(addr, port, chatnet); if (rec == NULL) { + newrec = TRUE; if (add == FALSE) { printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_SETUPSERVER_NOT_FOUND, addr, port); @@ -150,6 +153,7 @@ static void cmd_server_add_modify(const char *data, gboolean add) rec->address = g_strdup(addr); rec->port = port; } else { + newrec = FALSE; if (*portstr != '\0' || g_hash_table_lookup(optlist, "port")) rec->port = port; @@ -165,20 +169,17 @@ static void cmd_server_add_modify(const char *data, gboolean add) else if (g_hash_table_lookup(optlist, "4")) rec->family = AF_INET; - if (g_hash_table_lookup(optlist, "tls") || g_hash_table_lookup(optlist, "ssl")) { - rec->use_tls = TRUE; - } - else if (g_hash_table_lookup(optlist, "notls") || g_hash_table_lookup(optlist, "nossl")) { - rec->use_tls = FALSE; - /* tls_verify implies use_tls, disable it explicitly */ - rec->tls_verify = FALSE; - } - value = g_hash_table_lookup(optlist, "tls_cert"); if (value == NULL) value = g_hash_table_lookup(optlist, "ssl_cert"); - if (value != NULL && *value != '\0') + if (value != NULL && *value != '\0') { rec->tls_cert = g_strdup(value); + if (newrec) { + /* convenience and backward compatibility, turn on tls if tls_cert is given + */ + rec->use_tls = TRUE; + } + } value = g_hash_table_lookup(optlist, "tls_pkey"); if (value == NULL) @@ -192,11 +193,6 @@ static void cmd_server_add_modify(const char *data, gboolean add) if (value != NULL && *value != '\0') rec->tls_pass = g_strdup(value); - if (g_hash_table_lookup(optlist, "tls_verify") || g_hash_table_lookup(optlist, "ssl_verify")) - rec->tls_verify = TRUE; - else if (g_hash_table_lookup(optlist, "notls_verify") || g_hash_table_lookup(optlist, "nossl_verify")) - rec->tls_verify = FALSE; - value = g_hash_table_lookup(optlist, "tls_cafile"); if (value == NULL) value = g_hash_table_lookup(optlist, "ssl_cafile"); @@ -231,8 +227,23 @@ static void cmd_server_add_modify(const char *data, gboolean add) || (rec->tls_capath != NULL && rec->tls_capath[0] != '\0')) rec->tls_verify = TRUE; - if ((rec->tls_cert != NULL && rec->tls_cert[0] != '\0') || rec->tls_verify == TRUE) + if (g_hash_table_lookup(optlist, "tls_verify") || + g_hash_table_lookup(optlist, "ssl_verify")) { + rec->tls_verify = TRUE; + if (newrec) { + /* convenience and backward compatibility, turn on tls if tls_verify is + * given */ + rec->use_tls = TRUE; + } + } else if (g_hash_table_lookup(optlist, "notls_verify") || + g_hash_table_lookup(optlist, "nossl_verify")) { + rec->tls_verify = FALSE; + } + + if (g_hash_table_lookup(optlist, "tls") || g_hash_table_lookup(optlist, "ssl")) rec->use_tls = TRUE; + else if (g_hash_table_lookup(optlist, "notls") || g_hash_table_lookup(optlist, "nossl")) + rec->use_tls = FALSE; if (g_hash_table_lookup(optlist, "auto")) rec->autoconnect = TRUE; if (g_hash_table_lookup(optlist, "noauto")) rec->autoconnect = FALSE; @@ -246,7 +257,7 @@ static void cmd_server_add_modify(const char *data, gboolean add) rec->own_ip4 = rec->own_ip6 = NULL; } - signal_emit("server add fill", 2, rec, optlist); + signal_emit("server add fill", 3, rec, optlist, GINT_TO_POINTER(add)); server_setup_add(rec); printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, diff --git a/src/fe-common/irc/fe-irc-server.c b/src/fe-common/irc/fe-irc-server.c index 7e3a6300..fbd70404 100644 --- a/src/fe-common/irc/fe-irc-server.c +++ b/src/fe-common/irc/fe-irc-server.c @@ -51,12 +51,13 @@ const char *get_visible_target(IRC_SERVER_REC *server, const char *target) return target; } -/* SYNTAX: SERVER ADD|MODIFY [-4 | -6] [-tls] [-tls_cert ] [-tls_pkey ] [-tls_pass ] - [-tls_verify] [-tls_cafile ] [-tls_capath ] - [-tls_ciphers ] +/* SYNTAX: SERVER ADD|MODIFY [-4 | -6] [-tls_cert ] [-tls_pkey ] + [-tls_pass ] [-tls_verify] [-tls_cafile ] + [-tls_capath ] [-tls_ciphers ] [-tls | -notls] + [-starttls | -nostarttls | -disallow_starttls | -nodisallow_starttls] [-auto | -noauto] [-network ] [-host ] - [-cmdspeed ] [-cmdmax ] [-port ] -
[ []] */ + [-cmdspeed ] [-cmdmax ] [-port ]
[ + []] */ /* NOTE: -network replaces the old -ircnet flag. */ static void sig_server_add_fill(IRC_SERVER_SETUP_REC *rec, GHashTable *optlist) @@ -85,6 +86,28 @@ static void sig_server_add_fill(IRC_SERVER_SETUP_REC *rec, if (value != NULL && *value != '\0') rec->max_cmds_at_once = atoi(value); value = g_hash_table_lookup(optlist, "querychans"); if (value != NULL && *value != '\0') rec->max_query_chans = atoi(value); + if (g_hash_table_lookup(optlist, "nodisallow_starttls") || + g_hash_table_lookup(optlist, "nostarttls")) + rec->starttls = STARTTLS_NOTSET; + if (g_hash_table_lookup(optlist, "disallow_starttls")) + rec->starttls = STARTTLS_DISALLOW; + if (g_hash_table_lookup(optlist, "starttls")) { + rec->starttls = STARTTLS_ENABLED; + rec->use_tls = 0; + } + if (g_hash_table_lookup(optlist, "nocap")) + rec->no_cap = 1; + if (g_hash_table_lookup(optlist, "cap")) + rec->no_cap = 0; +} + +static void sig_server_waiting_info(IRC_SERVER_REC *server, const char *version) +{ + if (!IS_IRC_SERVER(server)) + return; + + printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_SERVER_WAITING_CAP_LS, server, + version); } /* SYNTAX: SERVER LIST */ @@ -108,29 +131,35 @@ static void cmd_server_list(const char *data) g_string_append(str, "autoconnect, "); if (rec->no_proxy) g_string_append(str, "noproxy, "); - if (rec->use_tls) { + if (rec->no_cap) + g_string_append(str, "nocap, "); + if (rec->starttls == STARTTLS_DISALLOW) + g_string_append(str, "disallow_starttls, "); + if (rec->starttls == STARTTLS_ENABLED) + g_string_append(str, "starttls, "); + if (rec->use_tls) g_string_append(str, "tls, "); - if (rec->tls_cert) { - g_string_append_printf(str, "tls_cert: %s, ", rec->tls_cert); - if (rec->tls_pkey) - g_string_append_printf(str, "tls_pkey: %s, ", rec->tls_pkey); - if (rec->tls_pass) - g_string_append_printf(str, "(pass), "); - } - if (rec->tls_verify) - g_string_append(str, "tls_verify, "); - if (rec->tls_cafile) - g_string_append_printf(str, "tls_cafile: %s, ", rec->tls_cafile); - if (rec->tls_capath) - g_string_append_printf(str, "tls_capath: %s, ", rec->tls_capath); - if (rec->tls_ciphers) - g_string_append_printf(str, "tls_ciphers: %s, ", rec->tls_ciphers); - if (rec->tls_pinned_cert) - g_string_append_printf(str, "tls_pinned_cert: %s, ", rec->tls_pinned_cert); - if (rec->tls_pinned_pubkey) - g_string_append_printf(str, "tls_pinned_pubkey: %s, ", rec->tls_pinned_pubkey); - + if (rec->tls_cert) { + g_string_append_printf(str, "tls_cert: %s, ", rec->tls_cert); + if (rec->tls_pkey) + g_string_append_printf(str, "tls_pkey: %s, ", rec->tls_pkey); + if (rec->tls_pass) + g_string_append_printf(str, "(pass), "); } + if (!rec->tls_verify) + g_string_append(str, "notls_verify, "); + if (rec->tls_cafile) + g_string_append_printf(str, "tls_cafile: %s, ", rec->tls_cafile); + if (rec->tls_capath) + g_string_append_printf(str, "tls_capath: %s, ", rec->tls_capath); + if (rec->tls_ciphers) + g_string_append_printf(str, "tls_ciphers: %s, ", rec->tls_ciphers); + if (rec->tls_pinned_cert) + g_string_append_printf(str, "tls_pinned_cert: %s, ", rec->tls_pinned_cert); + if (rec->tls_pinned_pubkey) + g_string_append_printf(str, "tls_pinned_pubkey: %s, ", + rec->tls_pinned_pubkey); + if (rec->max_cmds_at_once > 0) g_string_append_printf(str, "cmdmax: %d, ", rec->max_cmds_at_once); if (rec->cmd_queue_speed > 0) @@ -153,13 +182,20 @@ static void cmd_server_list(const char *data) void fe_irc_server_init(void) { signal_add("server add fill", (SIGNAL_FUNC) sig_server_add_fill); + signal_add("server waiting cap ls", (SIGNAL_FUNC) sig_server_waiting_info); command_bind("server list", NULL, (SIGNAL_FUNC) cmd_server_list); - command_set_options("server add", "-ircnet -network -cmdspeed -cmdmax -querychans"); + command_set_options("server add", + "-ircnet -network -cmdspeed -cmdmax -querychans starttls " + "nostarttls disallow_starttls nodisallow_starttls cap nocap"); + command_set_options("server modify", + "-ircnet -network -cmdspeed -cmdmax -querychans starttls nostarttls " + "disallow_starttls nodisallow_starttls cap nocap"); } void fe_irc_server_deinit(void) { signal_remove("server add fill", (SIGNAL_FUNC) sig_server_add_fill); + signal_remove("server waiting cap ls", (SIGNAL_FUNC) sig_server_waiting_info); command_unbind("server list", (SIGNAL_FUNC) cmd_server_list); } diff --git a/src/fe-common/irc/module-formats.c b/src/fe-common/irc/module-formats.c index d4c00f25..1a96cbce 100644 --- a/src/fe-common/irc/module-formats.c +++ b/src/fe-common/irc/module-formats.c @@ -45,6 +45,7 @@ FORMAT_REC fecommon_irc_formats[] = { { "setupserver_header", "%#Server Port Network Settings", 0 }, { "setupserver_line", "%#%|$[!20]0 $[5]1 $[10]2 $3", 4, { 0, 1, 0, 0 } }, { "setupserver_footer", "", 0 }, + { "server_waiting_cap_ls", "Waiting for CAP LS response...", 2, { 0, 0 } }, { "sasl_success", "SASL authentication succeeded", 0 }, { "sasl_error", "Cannot authenticate via SASL ($0)", 1, { 0 } }, { "cap_req", "Capabilities requested: $0", 1, { 0 } }, diff --git a/src/fe-common/irc/module-formats.h b/src/fe-common/irc/module-formats.h index 892b77c8..5abd04a4 100644 --- a/src/fe-common/irc/module-formats.h +++ b/src/fe-common/irc/module-formats.h @@ -23,6 +23,7 @@ enum { IRCTXT_SETUPSERVER_HEADER, IRCTXT_SETUPSERVER_LINE, IRCTXT_SETUPSERVER_FOOTER, + IRCTXT_SERVER_WAITING_CAP_LS, IRCTXT_SASL_SUCCESS, IRCTXT_SASL_ERROR, IRCTXT_CAP_REQ, diff --git a/src/fe-fuzz/server.c b/src/fe-fuzz/server.c index a444494d..a2ffb9e9 100644 --- a/src/fe-fuzz/server.c +++ b/src/fe-fuzz/server.c @@ -177,12 +177,23 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { for (; *lines != NULL; lines++) { gchar *prefixedLine; + int disconnected; if (prefixedChoice) { prefixedLine = g_strdup_printf(":user %s\n", *lines); } else { prefixedLine = g_strdup_printf("%s\n", *lines); } + server_ref(server); signal_emit("server incoming", 2, server, prefixedLine); + disconnected = server->disconnected; + if (disconnected) { + server_connect_unref(server->connrec); + } + server_unref(server); + if (disconnected) { + /* reconnect */ + test_server(); + } g_free(prefixedLine); } diff --git a/src/irc/core/channels-query.c b/src/irc/core/channels-query.c index 97e982b7..6cf3dd2b 100644 --- a/src/irc/core/channels-query.c +++ b/src/irc/core/channels-query.c @@ -90,6 +90,8 @@ static void sig_disconnected(IRC_SERVER_REC *server) return; rec = server->chanqueries; + if (rec == NULL) + return; g_return_if_fail(rec != NULL); g_hash_table_destroy(rec->accountqueries); diff --git a/src/irc/core/irc-cap.c b/src/irc/core/irc-cap.c index 61a80066..ce7e1616 100644 --- a/src/irc/core/irc-cap.c +++ b/src/irc/core/irc-cap.c @@ -104,12 +104,54 @@ static gboolean parse_cap_name(char *name, char **key, char **val) return TRUE; } +static void cap_process_request_queue(IRC_SERVER_REC *server) +{ + /* No CAP has been requested */ + if (server->cap_queue == NULL) { + irc_cap_finish_negotiation(server); + } else { + GSList *tmp; + GString *cmd; + int avail_caps = 0; + + cmd = g_string_new("CAP REQ :"); + + /* To process the queue in order, we need to reverse the stack once */ + server->cap_queue = g_slist_reverse(server->cap_queue); + + /* Check whether the cap is supported by the server */ + for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { + if (g_hash_table_lookup_extended(server->cap_supported, tmp->data, NULL, + NULL)) { + if (avail_caps > 0) + g_string_append_c(cmd, ' '); + g_string_append(cmd, tmp->data); + + avail_caps++; + } + } + + /* Clear the queue here */ + i_slist_free_full(server->cap_queue, (GDestroyNotify) g_free); + server->cap_queue = NULL; + + /* If the server doesn't support any cap we requested close the negotiation here */ + if (avail_caps > 0) { + signal_emit("server cap req", 2, server, + cmd->str + sizeof("CAP REQ :") - 1); + irc_send_cmd_now(server, cmd->str); + } else { + irc_cap_finish_negotiation(server); + } + + g_string_free(cmd, TRUE); + } +} + static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *address) { - GSList *tmp; - GString *cmd; char *params, *evt, *list, *star, **caps; - int i, caps_length, disable, avail_caps, multiline; + int i, caps_length, disable, multiline; params = event_get_params(args, 4, NULL, &evt, &star, &list); if (params == NULL) @@ -174,42 +216,20 @@ static void event_cap (IRC_SERVER_REC *server, char *args, char *nick, char *add /* A multiline response is always terminated by a normal one, * wait until we receive that one to require any CAP */ if (multiline == FALSE) { - /* No CAP has been requested */ - if (server->cap_queue == NULL) { - irc_cap_finish_negotiation(server); - } - else { - cmd = g_string_new("CAP REQ :"); - - avail_caps = 0; - - /* To process the queue in order, we need to reverse the stack once */ - server->cap_queue = g_slist_reverse(server->cap_queue); - - /* Check whether the cap is supported by the server */ - for (tmp = server->cap_queue; tmp != NULL; tmp = tmp->next) { - if (g_hash_table_lookup_extended(server->cap_supported, tmp->data, NULL, NULL)) { - if (avail_caps > 0) - g_string_append_c(cmd, ' '); - g_string_append(cmd, tmp->data); - - avail_caps++; - } - } - - /* Clear the queue here */ - i_slist_free_full(server->cap_queue, (GDestroyNotify) g_free); - server->cap_queue = NULL; - - /* If the server doesn't support any cap we requested close the negotiation here */ - if (avail_caps > 0) { - signal_emit("server cap req", 2, server, cmd->str + sizeof("CAP REQ :") - 1); - irc_send_cmd_now(server, cmd->str); - } else { - irc_cap_finish_negotiation(server); - } - - g_string_free(cmd, TRUE); + gboolean want_starttls = + i_slist_find_string(server->cap_queue, CAP_STARTTLS) != NULL; + server->cap_queue = + i_slist_delete_string(server->cap_queue, CAP_STARTTLS, g_free); + if (server->connrec->starttls) { + /* the connection has requested starttls, + no more data must be sent now */ + } else if (want_starttls && + g_hash_table_lookup_extended(server->cap_supported, CAP_STARTTLS, + NULL, NULL)) { + irc_server_send_starttls(server); + /* no more data must be sent now */ + } else { + cap_process_request_queue(server); } } } @@ -301,12 +321,14 @@ static void event_invalid_cap (IRC_SERVER_REC *server, const char *data, const c void irc_cap_init (void) { + signal_add_last("server cap continue", (SIGNAL_FUNC) cap_process_request_queue); signal_add_first("event cap", (SIGNAL_FUNC) event_cap); signal_add_first("event 410", (SIGNAL_FUNC) event_invalid_cap); } void irc_cap_deinit (void) { + signal_remove("server cap continue", (SIGNAL_FUNC) cap_process_request_queue); signal_remove("event cap", (SIGNAL_FUNC) event_cap); signal_remove("event 410", (SIGNAL_FUNC) event_invalid_cap); } diff --git a/src/irc/core/irc-commands.c b/src/irc/core/irc-commands.c index 935c6b42..cba909c1 100644 --- a/src/irc/core/irc-commands.c +++ b/src/irc/core/irc-commands.c @@ -1053,7 +1053,7 @@ void irc_commands_init(void) signal_add("whois end", (SIGNAL_FUNC) event_end_of_whois); signal_add("whowas event", (SIGNAL_FUNC) event_whowas); - command_set_options("connect", "+ircnet"); + command_set_options("connect", "+ircnet starttls disallow_starttls nocap"); command_set_options("topic", "delete"); command_set_options("list", "yes"); command_set_options("away", "one all"); diff --git a/src/irc/core/irc-servers-reconnect.c b/src/irc/core/irc-servers-reconnect.c index cfe28a1a..e18a5a9b 100644 --- a/src/irc/core/irc-servers-reconnect.c +++ b/src/irc/core/irc-servers-reconnect.c @@ -51,6 +51,9 @@ static void sig_server_connect_copy(SERVER_CONNECT_REC **dest, rec->sasl_mechanism = src->sasl_mechanism; rec->sasl_username = g_strdup(src->sasl_username); rec->sasl_password = g_strdup(src->sasl_password); + rec->disallow_starttls = src->disallow_starttls; + rec->starttls = src->starttls; + rec->no_cap = src->no_cap; *dest = (SERVER_CONNECT_REC *) rec; } @@ -62,7 +65,8 @@ static void sig_server_reconnect_save_status(IRC_SERVER_CONNECT_REC *conn, return; g_free_not_null(conn->channels); - conn->channels = irc_server_get_channels(server); + conn->channels = + irc_server_get_channels(server, settings_get_choice("rejoin_channels_on_reconnect")); g_free_not_null(conn->usermode); conn->usermode = g_strdup(server->wanted_usermode); diff --git a/src/irc/core/irc-servers-setup.c b/src/irc/core/irc-servers-setup.c index 5f1290a2..5299ef04 100644 --- a/src/irc/core/irc-servers-setup.c +++ b/src/irc/core/irc-servers-setup.c @@ -44,9 +44,15 @@ static void sig_server_setup_fill_reconn(IRC_SERVER_CONNECT_REC *conn, conn->max_cmds_at_once = sserver->max_cmds_at_once; if (sserver->max_query_chans > 0) conn->max_query_chans = sserver->max_query_chans; + if (sserver->starttls == STARTTLS_DISALLOW) + conn->disallow_starttls = 1; + else if (sserver->starttls == STARTTLS_ENABLED) + conn->starttls = 1; + if (sserver->no_cap) + conn->no_cap = 1; } -static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn) +static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn, GHashTable *optlist) { const char *value; @@ -62,6 +68,23 @@ static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn) g_strdup(value) : NULL; } +static void sig_server_setup_fill_optlist(IRC_SERVER_CONNECT_REC *conn, GHashTable *optlist) +{ + if (!IS_IRC_SERVER_CONNECT(conn)) + return; + + if (g_hash_table_lookup(optlist, "starttls") != NULL) { + conn->starttls = 1; + conn->use_tls = 0; + } else if (g_hash_table_lookup(optlist, "disallow_starttls") != NULL) { + conn->disallow_starttls = 1; + } + if (g_hash_table_lookup(optlist, "nocap")) + conn->no_cap = 1; + if (g_hash_table_lookup(optlist, "cap")) + conn->no_cap = 0; +} + static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, IRC_CHATNET_REC *ircnet) { @@ -174,6 +197,11 @@ static void sig_server_setup_read(IRC_SERVER_SETUP_REC *rec, CONFIG_NODE *node) rec->max_cmds_at_once = config_node_get_int(node, "cmds_max_at_once", 0); rec->cmd_queue_speed = config_node_get_int(node, "cmd_queue_speed", 0); rec->max_query_chans = config_node_get_int(node, "max_query_chans", 0); + rec->starttls = config_node_get_bool(node, "starttls", STARTTLS_NOTSET); + if (rec->starttls == STARTTLS_ENABLED) { + rec->use_tls = 0; + } + rec->no_cap = config_node_get_bool(node, "no_cap", FALSE); } static void sig_server_setup_saved(IRC_SERVER_SETUP_REC *rec, @@ -188,6 +216,12 @@ static void sig_server_setup_saved(IRC_SERVER_SETUP_REC *rec, iconfig_node_set_int(node, "cmd_queue_speed", rec->cmd_queue_speed); if (rec->max_query_chans > 0) iconfig_node_set_int(node, "max_query_chans", rec->max_query_chans); + if (rec->starttls != STARTTLS_NOTSET) + iconfig_node_set_bool(node, "starttls", rec->starttls); + else + iconfig_node_set_str(node, "starttls", NULL); + if (rec->no_cap) + iconfig_node_set_bool(node, "no_cap", TRUE); } void irc_servers_setup_init(void) @@ -199,6 +233,7 @@ void irc_servers_setup_init(void) signal_add("server setup fill reconn", (SIGNAL_FUNC) sig_server_setup_fill_reconn); signal_add("server setup fill connect", (SIGNAL_FUNC) sig_server_setup_fill_connect); signal_add("server setup fill chatnet", (SIGNAL_FUNC) sig_server_setup_fill_chatnet); + signal_add("server setup fill optlist", (SIGNAL_FUNC) sig_server_setup_fill_optlist); signal_add("server setup read", (SIGNAL_FUNC) sig_server_setup_read); signal_add("server setup saved", (SIGNAL_FUNC) sig_server_setup_saved); } @@ -208,6 +243,7 @@ void irc_servers_setup_deinit(void) signal_remove("server setup fill reconn", (SIGNAL_FUNC) sig_server_setup_fill_reconn); signal_remove("server setup fill connect", (SIGNAL_FUNC) sig_server_setup_fill_connect); signal_remove("server setup fill chatnet", (SIGNAL_FUNC) sig_server_setup_fill_chatnet); + signal_remove("server setup fill optlist", (SIGNAL_FUNC) sig_server_setup_fill_optlist); signal_remove("server setup read", (SIGNAL_FUNC) sig_server_setup_read); signal_remove("server setup saved", (SIGNAL_FUNC) sig_server_setup_saved); } diff --git a/src/irc/core/irc-servers-setup.h b/src/irc/core/irc-servers-setup.h index d3bafdef..7b11a2fe 100644 --- a/src/irc/core/irc-servers-setup.h +++ b/src/irc/core/irc-servers-setup.h @@ -11,6 +11,12 @@ #define IS_IRC_SERVER_SETUP(server) \ (IRC_SERVER_SETUP(server) ? TRUE : FALSE) +enum { + STARTTLS_DISALLOW = -1, /* */ + STARTTLS_NOTSET = 0, + STARTTLS_ENABLED = 1 +}; + typedef struct { #include @@ -18,6 +24,8 @@ typedef struct { int max_cmds_at_once; int cmd_queue_speed; int max_query_chans; + int starttls; + int no_cap : 1; } IRC_SERVER_SETUP_REC; void irc_servers_setup_init(void); diff --git a/src/irc/core/irc-servers.c b/src/irc/core/irc-servers.c index f29a8f91..06f3d3c8 100644 --- a/src/irc/core/irc-servers.c +++ b/src/irc/core/irc-servers.c @@ -20,10 +20,11 @@ #include "module.h" -#include -#include -#include #include +#include +#include +#include +#include #include #include @@ -207,7 +208,7 @@ static char **split_message(SERVER_REC *server, const char *target, strlen(target)); } -static void server_init(IRC_SERVER_REC *server) +static void server_init_2(IRC_SERVER_REC *server) { IRC_SERVER_CONNECT_REC *conn; char *address, *ptr, *username, *cmd; @@ -216,8 +217,56 @@ static void server_init(IRC_SERVER_REC *server) conn = server->connrec; - if (conn->proxy != NULL && conn->proxy_password != NULL && - *conn->proxy_password != '\0') { + if (conn->password != NULL && *conn->password != '\0') { + /* send password */ + cmd = g_strdup_printf("PASS %s", conn->password); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } + + /* send nick */ + cmd = g_strdup_printf("NICK %s", conn->nick); + irc_send_cmd_now(server, cmd); + g_free(cmd); + + /* send user/realname */ + address = server->connrec->address; + ptr = strrchr(address, ':'); + if (ptr != NULL) { + /* IPv6 address .. doesn't work here, use the string after + the last : char */ + address = ptr + 1; + if (*address == '\0') + address = "x"; + } + + username = g_strdup(conn->username); + ptr = strchr(username, ' '); + if (ptr != NULL) + *ptr = '\0'; + + cmd = g_strdup_printf("USER %s %s %s :%s", username, username, address, conn->realname); + irc_send_cmd_now(server, cmd); + g_free(cmd); + g_free(username); + + if (conn->proxy != NULL && conn->proxy_string_after != NULL) { + cmd = g_strdup_printf(conn->proxy_string_after, conn->address, conn->port); + irc_send_cmd_now(server, cmd); + g_free(cmd); + } +} + +static void server_init_1(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + char *cmd; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + if (conn->proxy != NULL && conn->proxy_password != NULL && *conn->proxy_password != '\0') { cmd = g_strdup_printf("PASS %s", conn->proxy_password); irc_send_cmd_now(server, cmd); g_free(cmd); @@ -243,45 +292,8 @@ static void server_init(IRC_SERVER_REC *server) irc_cap_toggle(server, CAP_ACCOUNT_NOTIFY, TRUE); irc_cap_toggle(server, CAP_SELF_MESSAGE, TRUE); irc_cap_toggle(server, CAP_SERVER_TIME, TRUE); - - irc_send_cmd_now(server, "CAP LS " CAP_LS_VERSION); - - if (conn->password != NULL && *conn->password != '\0') { - /* send password */ - cmd = g_strdup_printf("PASS %s", conn->password); - irc_send_cmd_now(server, cmd); - g_free(cmd); - } - - /* send nick */ - cmd = g_strdup_printf("NICK %s", conn->nick); - irc_send_cmd_now(server, cmd); - g_free(cmd); - - /* send user/realname */ - address = server->connrec->address; - ptr = strrchr(address, ':'); - if (ptr != NULL) { - /* IPv6 address .. doesn't work here, use the string after - the last : char */ - address = ptr+1; - if (*address == '\0') - address = "x"; - } - - username = g_strdup(conn->username); - ptr = strchr(username, ' '); - if (ptr != NULL) *ptr = '\0'; - - cmd = g_strdup_printf("USER %s %s %s :%s", username, username, address, conn->realname); - irc_send_cmd_now(server, cmd); - g_free(cmd); - g_free(username); - - if (conn->proxy != NULL && conn->proxy_string_after != NULL) { - cmd = g_strdup_printf(conn->proxy_string_after, conn->address, conn->port); - irc_send_cmd_now(server, cmd); - g_free(cmd); + if (!conn->use_tls && (conn->starttls || !conn->disallow_starttls)) { + irc_cap_toggle(server, CAP_STARTTLS, TRUE); } server->isupport = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); @@ -296,6 +308,119 @@ static void server_init(IRC_SERVER_REC *server) /* this will reset to 1 sec after we get the 001 event */ server->wait_cmd = g_get_real_time(); server->wait_cmd += 120 * G_USEC_PER_SEC; + + if (!conn->no_cap) { + signal_emit("server waiting cap ls", 2, server, CAP_LS_VERSION); + irc_send_cmd_now(server, "CAP LS " CAP_LS_VERSION); + /* to detect non-CAP servers, send this bogus join */ + irc_send_cmd_now(server, "JOIN "); + } + if (conn->starttls) + irc_server_send_starttls(server); + else if (conn->no_cap) + server_init_2(server); +} + +static void init_ssl_loop(IRC_SERVER_REC *server, GIOChannel *handle) +{ + int error; + server->connrec->starttls = 1; + + if (server->starttls_tag) { + g_source_remove(server->starttls_tag); + server->starttls_tag = 0; + } + + error = irssi_ssl_handshake(handle); + if (error == -1) { + server->connection_lost = TRUE; + server_disconnect((SERVER_REC *) server); + return; + } + if (error & 1) { /* wait */ + server->starttls_tag = + i_input_add(handle, error == 1 ? I_INPUT_READ : I_INPUT_WRITE, + (GInputFunction) init_ssl_loop, server); + return; + } + /* continue */ + rawlog_redirect(server->rawlog, "Now talking encrypted"); + signal_emit("server connection switched", 1, server); + if (!server->cap_supported) { + server_init_2(server); + } else { + signal_emit("server cap continue", 1, server); + } + + if (settings_get_bool("starttls_sts")) { + IRC_SERVER_SETUP_REC *ssetup = IRC_SERVER_SETUP(server_setup_find( + server->connrec->address, server->connrec->port, server->connrec->chatnet)); + if (ssetup != NULL) { + ssetup->starttls = STARTTLS_ENABLED; + server_setup_add((SERVER_SETUP_REC *) ssetup); + } + } +} + +#include +void irc_server_send_starttls(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + g_warning("[%s] Now attempting STARTTLS", server->tag); + irc_send_cmd_now(server, "STARTTLS"); +} + +static void event_starttls(IRC_SERVER_REC *server, const char *data) +{ + GIOChannel *ssl_handle; + + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->handle->readbuffer != NULL && + !line_split_is_empty(server->handle->readbuffer)) { + char *str; + line_split("", -1, &str, &server->handle->readbuffer); + } + ssl_handle = net_start_ssl((SERVER_REC *) server); + if (ssl_handle != NULL) { + g_source_remove(server->readtag); + server->readtag = -1; + server->handle->handle = ssl_handle; + init_ssl_loop(server, server->handle->handle); + } else { + g_warning("net_start_ssl failed"); + } +} + +static void event_registerfirst(IRC_SERVER_REC *server, const char *data) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->connected) + return; + + if (!server->cap_supported && !server->connrec->starttls) + server_init_2(server); +} + +static void event_capend(IRC_SERVER_REC *server) +{ + g_return_if_fail(server != NULL); + + if (!IS_IRC_SERVER(server)) + return; + + if (server->connected) + return; + + server_init_2(server); } SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) @@ -348,6 +473,27 @@ void irc_server_connect(SERVER_REC *server) { g_return_if_fail(server != NULL); + if (server->connrec->connect_handle != NULL) { + /* an existing handle from upgrade */ + IRC_SERVER_CONNECT_REC *conn; + int tls_disconnect; + + conn = ((IRC_SERVER_REC *) server)->connrec; + tls_disconnect = conn->use_tls || conn->starttls; + + if (tls_disconnect) { + /* we cannot use it, it is encrypted. force a reconnect */ + g_io_channel_unref(conn->connect_handle); + conn->connect_handle = NULL; + server->session_reconnect = FALSE; + server_connect_ref((SERVER_CONNECT_REC *) conn); + server_disconnect(server); + server_connect((SERVER_CONNECT_REC *) conn); + server_connect_unref((SERVER_CONNECT_REC *) conn); + return; + } + } + if (!server_start_connect(server)) { server_connect_unref(server->connrec); g_free(server); @@ -420,7 +566,7 @@ static void sig_connected(IRC_SERVER_REC *server) server->splits = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); if (!server->session_reconnect) - server_init(server); + server_init_1(server); } static void isupport_destroy_hash(void *key, void *value) @@ -429,6 +575,17 @@ static void isupport_destroy_hash(void *key, void *value) g_free(value); } +static void sig_disconnected(IRC_SERVER_REC *server) +{ + if (!IS_IRC_SERVER(server)) + return; + + if (server->starttls_tag) { + g_source_remove(server->starttls_tag); + server->starttls_tag = 0; + } +} + static void sig_destroyed(IRC_SERVER_REC *server) { GSList *tmp; @@ -650,20 +807,17 @@ void irc_servers_start_cmd_timeout(void) /* Return a string of all channels (and keys, if any have them) in server, like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ -char *irc_server_get_channels(IRC_SERVER_REC *server) +char *irc_server_get_channels(IRC_SERVER_REC *server, int rejoin_channels_mode) { GSList *tmp; GString *chans, *keys; char *ret; int use_keys; - int rejoin_channels_mode; g_return_val_if_fail(server != NULL, FALSE); - rejoin_channels_mode = settings_get_choice("rejoin_channels_on_reconnect"); - /* do we want to rejoin channels in the first place? */ - if(rejoin_channels_mode == 0) + if (rejoin_channels_mode == REJOIN_CHANNELS_MODE_OFF) return g_strdup(""); chans = g_string_new(NULL); @@ -674,7 +828,9 @@ char *irc_server_get_channels(IRC_SERVER_REC *server) for (tmp = server->channels; tmp != NULL; tmp = tmp->next) { CHANNEL_REC *channel = tmp->data; CHANNEL_SETUP_REC *setup = channel_setup_find(channel->name, channel->server->connrec->chatnet); - if ((setup != NULL && setup->autojoin && rejoin_channels_mode == 2) || rejoin_channels_mode == 1) { + if ((setup != NULL && setup->autojoin && + rejoin_channels_mode == REJOIN_CHANNELS_MODE_AUTO) || + rejoin_channels_mode == REJOIN_CHANNELS_MODE_ON) { g_string_append_printf(chans, "%s,", channel->name); g_string_append_printf(keys, "%s,", channel->key == NULL ? "x" : channel->key); if (channel->key != NULL) @@ -687,7 +843,9 @@ char *irc_server_get_channels(IRC_SERVER_REC *server) REJOIN_REC *rec = tmp->data; CHANNEL_SETUP_REC *setup = channel_setup_find(rec->channel, server->tag); - if ((setup != NULL && setup->autojoin && rejoin_channels_mode == 2) || rejoin_channels_mode == 1) { + if ((setup != NULL && setup->autojoin && + rejoin_channels_mode == REJOIN_CHANNELS_MODE_AUTO) || + rejoin_channels_mode == REJOIN_CHANNELS_MODE_ON) { g_string_append_printf(chans, "%s,", rec->channel); g_string_append_printf(keys, "%s,", rec->key == NULL ? "x" : rec->key); @@ -1048,6 +1206,7 @@ void irc_server_init_isupport(IRC_SERVER_REC *server) void irc_servers_init(void) { + settings_add_bool("servers", "starttls_sts", TRUE); settings_add_choice("servers", "rejoin_channels_on_reconnect", 1, "off;on;auto"); settings_add_str("misc", "usermode", DEFAULT_USER_MODE); settings_add_str("misc", "split_line_start", ""); @@ -1059,9 +1218,13 @@ void irc_servers_init(void) cmd_tag = -1; signal_add_first("server connected", (SIGNAL_FUNC) sig_connected); + signal_add_first("server disconnected", (SIGNAL_FUNC) sig_disconnected); signal_add_last("server destroyed", (SIGNAL_FUNC) sig_destroyed); signal_add_last("server quit", (SIGNAL_FUNC) sig_server_quit); signal_add("server cap ack " CAP_MAXLINE, (SIGNAL_FUNC) cap_maxline); + signal_add("event 670", (SIGNAL_FUNC) event_starttls); + signal_add("event 451", (SIGNAL_FUNC) event_registerfirst); + signal_add("server cap end", (SIGNAL_FUNC) event_capend); signal_add("event 001", (SIGNAL_FUNC) event_connected); signal_add("event 004", (SIGNAL_FUNC) event_server_info); signal_add("event 005", (SIGNAL_FUNC) event_isupport); @@ -1087,9 +1250,13 @@ void irc_servers_deinit(void) g_source_remove(cmd_tag); signal_remove("server connected", (SIGNAL_FUNC) sig_connected); + signal_remove("server disconnected", (SIGNAL_FUNC) sig_disconnected); signal_remove("server destroyed", (SIGNAL_FUNC) sig_destroyed); signal_remove("server quit", (SIGNAL_FUNC) sig_server_quit); signal_remove("server cap ack " CAP_MAXLINE, (SIGNAL_FUNC) cap_maxline); + signal_remove("event 670", (SIGNAL_FUNC) event_starttls); + signal_remove("event 451", (SIGNAL_FUNC) event_registerfirst); + signal_remove("server cap end", (SIGNAL_FUNC) event_capend); signal_remove("event 001", (SIGNAL_FUNC) event_connected); signal_remove("event 004", (SIGNAL_FUNC) event_server_info); signal_remove("event 005", (SIGNAL_FUNC) event_isupport); diff --git a/src/irc/core/irc-servers.h b/src/irc/core/irc-servers.h index 14560611..544baba9 100644 --- a/src/irc/core/irc-servers.h +++ b/src/irc/core/irc-servers.h @@ -27,6 +27,7 @@ #define CAP_ACCOUNT_NOTIFY "account-notify" #define CAP_SELF_MESSAGE "znc.in/self-message" #define CAP_SERVER_TIME "server-time" +#define CAP_STARTTLS "tls" /* returns IRC_SERVER_REC if it's IRC server, NULL if it isn't */ #define IRC_SERVER(server) \ @@ -42,6 +43,7 @@ #define IS_IRC_SERVER_CONNECT(conn) \ (IRC_SERVER_CONNECT(conn) ? TRUE : FALSE) +/* clang-format off */ /* all strings should be either NULL or dynamically allocated */ /* address and nick are mandatory, rest are optional */ struct _IRC_SERVER_CONNECT_REC { @@ -59,7 +61,11 @@ struct _IRC_SERVER_CONNECT_REC { int max_query_chans; int max_kicks, max_msgs, max_modes, max_whois; + int disallow_starttls:1; + int starttls:1; + int no_cap:1; }; +/* clang-format on */ #define STRUCT_SERVER_CONNECT_REC IRC_SERVER_CONNECT_REC struct _IRC_SERVER_REC { @@ -136,6 +142,7 @@ struct _IRC_SERVER_REC { GSList *rejoin_channels; /* try to join to these channels after a while - channels go here if they're "temporarily unavailable" because of netsplits */ + guint starttls_tag; /* Holds the source id of the running timeout */ struct _SERVER_QUERY_REC *chanqueries; GHashTable *isupport; @@ -151,10 +158,17 @@ void irc_server_connect(SERVER_REC *server); /* Purge server output, either all or for specified target */ void irc_server_purge_output(IRC_SERVER_REC *server, const char *target); +enum { + REJOIN_CHANNELS_MODE_OFF = 0, /* */ + REJOIN_CHANNELS_MODE_ON, + REJOIN_CHANNELS_MODE_AUTO +}; + /* Return a string of all channels (and keys, if any have them) in server, like "#a,#b,#c,#d x,b_chan_key,x,x" or just "#e,#f,#g" */ -char *irc_server_get_channels(IRC_SERVER_REC *server); +char *irc_server_get_channels(IRC_SERVER_REC *server, int rejoin_channels_mode); +void irc_server_send_starttls(IRC_SERVER_REC *server); /* INTERNAL: */ void irc_server_send_action(IRC_SERVER_REC *server, const char *target, const char *data); diff --git a/src/irc/core/irc-session.c b/src/irc/core/irc-session.c index 6b817099..49d9a3f2 100644 --- a/src/irc/core/irc-session.c +++ b/src/irc/core/irc-session.c @@ -23,8 +23,10 @@ #include #include #include +#include #include +#include #include #include @@ -43,6 +45,7 @@ static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, GSList *tmp; CONFIG_NODE *isupport; struct _isupport_data isupport_data; + int tls_disconnect; if (!IS_IRC_SERVER(server)) return; @@ -58,7 +61,15 @@ static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, break; } } - net_sendbuffer_flush(server->handle); + /* we cannot upgrade TLS (yet?) */ + tls_disconnect = server->connrec->use_tls || server->connrec->starttls; + if (tls_disconnect) { + config_node_set_str(config, node, "rejoin_channels", + irc_server_get_channels(server, REJOIN_CHANNELS_MODE_ON)); + irc_send_cmd_now(server, "QUIT :[TLS] Client upgrade"); + } + + net_sendbuffer_flush(server->handle); config_node_set_str(config, node, "real_address", server->real_address); config_node_set_str(config, node, "userhost", server->userhost); @@ -71,18 +82,27 @@ static void sig_session_save_server(IRC_SERVER_REC *server, CONFIG_REC *config, config_node_set_str(config, node, "sasl_username", server->connrec->sasl_username); config_node_set_str(config, node, "sasl_password", server->connrec->sasl_password); + config_node_set_int(config, node, "starttls", + server->connrec->disallow_starttls ? STARTTLS_DISALLOW : + server->connrec->starttls ? STARTTLS_ENABLED : + STARTTLS_NOTSET); + + config_node_set_bool(config, node, "no_cap", server->connrec->no_cap); config_node_set_bool(config, node, "isupport_sent", server->isupport_sent); isupport = config_node_section(config, node, "isupport", NODE_TYPE_BLOCK); isupport_data.config = config; isupport_data.node = isupport; g_hash_table_foreach(server->isupport, (GHFunc) session_isupport_foreach, &isupport_data); + + /* we have to defer the disconnect to irc_server_connect */ } static void sig_session_restore_server(IRC_SERVER_REC *server, CONFIG_NODE *node) { GSList *tmp; + int starttls_mode; if (!IS_IRC_SERVER(server)) return; @@ -96,6 +116,7 @@ static void sig_session_restore_server(IRC_SERVER_REC *server, server->emode_known = config_node_get_bool(node, "emode_known", FALSE); server->isupport_sent = config_node_get_bool(node, "isupport_sent", FALSE); + server->connrec->no_cap = config_node_get_bool(node, "no_cap", FALSE); server->connrec->sasl_mechanism = config_node_get_int(node, "sasl_mechanism", SASL_MECHANISM_NONE); /* The fields below might have been filled when loading the chatnet * description from the config and we favor the content that's been saved @@ -105,6 +126,16 @@ static void sig_session_restore_server(IRC_SERVER_REC *server, g_free(server->connrec->sasl_password); server->connrec->sasl_password = g_strdup(config_node_get_str(node, "sasl_password", NULL)); + server->connrec->channels = g_strdup(config_node_get_str(node, "rejoin_channels", NULL)); + + starttls_mode = config_node_get_int(node, "starttls", STARTTLS_NOTSET); + if (starttls_mode == STARTTLS_DISALLOW) + server->connrec->disallow_starttls = 1; + if (starttls_mode == STARTTLS_ENABLED) { + server->connrec->starttls = 1; + server->connrec->use_tls = 0; + } + if (server->isupport == NULL) { server->isupport = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); @@ -123,6 +154,7 @@ static void sig_session_restore_server(IRC_SERVER_REC *server, } irc_server_init_isupport(server); + /* we will reconnect in irc_server_connect if the connection was TLS */ } static void sig_session_restore_nick(IRC_CHANNEL_REC *channel, diff --git a/src/irc/core/irc.c b/src/irc/core/irc.c index 8ecda85b..628d7130 100644 --- a/src/irc/core/irc.c +++ b/src/irc/core/irc.c @@ -579,6 +579,7 @@ void irc_irc_init(void) signal_add("server event", (SIGNAL_FUNC) irc_server_event); signal_add("server event tags", (SIGNAL_FUNC) irc_server_event_tags); signal_add("server connected", (SIGNAL_FUNC) irc_init_server); + signal_add("server connection switched", (SIGNAL_FUNC) irc_init_server); signal_add("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); current_server_event = NULL; @@ -593,5 +594,6 @@ void irc_irc_deinit(void) signal_remove("server event", (SIGNAL_FUNC) irc_server_event); signal_remove("server event tags", (SIGNAL_FUNC) irc_server_event_tags); signal_remove("server connected", (SIGNAL_FUNC) irc_init_server); + signal_remove("server connection switched", (SIGNAL_FUNC) irc_init_server); signal_remove("server incoming", (SIGNAL_FUNC) irc_parse_incoming_line); } diff --git a/src/irc/core/netsplit.c b/src/irc/core/netsplit.c index 1463f962..38b63fe5 100644 --- a/src/irc/core/netsplit.c +++ b/src/irc/core/netsplit.c @@ -381,6 +381,9 @@ static void sig_disconnected(IRC_SERVER_REC *server) if (!IS_IRC_SERVER(server)) return; + if (server->splits == NULL) + return; + g_hash_table_foreach(server->splits, (GHFunc) netsplit_destroy_hash, server); g_hash_table_destroy(server->splits); diff --git a/src/irc/notifylist/notifylist.c b/src/irc/notifylist/notifylist.c index 158b4dea..73737b37 100644 --- a/src/irc/notifylist/notifylist.c +++ b/src/irc/notifylist/notifylist.c @@ -209,6 +209,9 @@ static void notifylist_deinit_server(IRC_SERVER_REC *server) return; mserver = MODULE_DATA(server); + if (!mserver) + return; + while (mserver->notify_users != NULL) { rec = mserver->notify_users->data; diff --git a/src/perl/irc/Server.xs b/src/perl/irc/Server.xs index 54361ec9..fef3698f 100644 --- a/src/perl/irc/Server.xs +++ b/src/perl/irc/Server.xs @@ -1,5 +1,6 @@ #define PERL_NO_GET_CONTEXT #include "module.h" +#include static GSList *register_hash2list(HV *hv) { @@ -47,12 +48,20 @@ MODULE = Irssi::Irc::Server PACKAGE = Irssi::Irc::Server PREFIX = irc_server_ PROTOTYPES: ENABLE void -irc_server_get_channels(server) +irc_server_get_channels(server, rejoin_channels_mode = "") Irssi::Irc::Server server + char *rejoin_channels_mode PREINIT: char *ret; + int mode; + SETTINGS_REC *setting; PPCODE: - ret = irc_server_get_channels(server); + setting = settings_get_record("rejoin_channels_on_reconnect"); + mode = strarray_find(setting->choices, rejoin_channels_mode); + if (mode < 0) + mode = setting->default_value.v_int; + + ret = irc_server_get_channels(server, mode); XPUSHs(sv_2mortal(new_pv(ret))); g_free(ret);