diff --git a/src/core/chat-commands.c b/src/core/chat-commands.c index ae44ef63..8c70bfc6 100644 --- a/src/core/chat-commands.c +++ b/src/core/chat-commands.c @@ -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)); diff --git a/src/core/servers-setup.c b/src/core/servers-setup.c index 43653c43..ea15e2a8 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,7 @@ 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_server(SERVER_CONNECT_REC *conn, @@ -219,10 +219,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 +249,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) : @@ -279,9 +278,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 +306,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 +323,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 +332,13 @@ 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) +{ + return server_create_conn_opt(chat_type, dest, port, chatnet, password, nick, NULL); } /* Find matching server from setup. Try to find record with a same port, 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/fe-common/core/fe-server.c b/src/fe-common/core/fe-server.c index 5ed67170..0bf9a104 100644 --- a/src/fe-common/core/fe-server.c +++ b/src/fe-common/core/fe-server.c @@ -246,7 +246,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..b5697c96 100644 --- a/src/fe-common/irc/fe-irc-server.c +++ b/src/fe-common/irc/fe-irc-server.c @@ -51,12 +51,12 @@ 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] [-tls_cert ] [-tls_pkey ] + [-tls_pass ] [-tls_verify] [-tls_cafile ] + [-tls_capath ] [-tls_ciphers ] [-starttls | -nostarttls] [-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 +85,12 @@ 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, "nonostarttls")) + rec->starttls = -1; + if (g_hash_table_lookup(optlist, "nostarttls")) + rec->starttls = 0; + if (g_hash_table_lookup(optlist, "starttls")) + rec->starttls = 1; } /* SYNTAX: SERVER LIST */ @@ -108,29 +114,31 @@ 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->starttls >= 0) + g_string_append_printf(str, "%sstarttls, ", rec->starttls ? "" : "no"); + 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, "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->max_cmds_at_once > 0) g_string_append_printf(str, "cmdmax: %d, ", rec->max_cmds_at_once); if (rec->cmd_queue_speed > 0) @@ -155,7 +163,12 @@ void fe_irc_server_init(void) signal_add("server add fill", (SIGNAL_FUNC) sig_server_add_fill); 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 nonostarttls"); + command_set_options( + "server modify", + "-ircnet -network -cmdspeed -cmdmax -querychans starttls nostarttls nonostarttls"); } void fe_irc_server_deinit(void) 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..8c8179e0 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 nostarttls"); 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..4a8d2908 100644 --- a/src/irc/core/irc-servers-reconnect.c +++ b/src/irc/core/irc-servers-reconnect.c @@ -51,6 +51,8 @@ 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->no_starttls = src->no_starttls; + rec->starttls = src->starttls; *dest = (SERVER_CONNECT_REC *) rec; } diff --git a/src/irc/core/irc-servers-setup.c b/src/irc/core/irc-servers-setup.c index 5f1290a2..74edb47b 100644 --- a/src/irc/core/irc-servers-setup.c +++ b/src/irc/core/irc-servers-setup.c @@ -44,9 +44,13 @@ 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 == 0) + conn->no_starttls = 1; + else if (sserver->starttls == 1) + conn->starttls = 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; @@ -60,6 +64,11 @@ static void sig_server_setup_fill_connect(IRC_SERVER_CONNECT_REC *conn) value = settings_get_str("usermode"); conn->usermode = (value != NULL && *value != '\0') ? g_strdup(value) : NULL; + + if (g_hash_table_lookup(optlist, "starttls") != NULL) + conn->starttls = 1; + else if (g_hash_table_lookup(optlist, "nostarttls") != NULL) + conn->no_starttls = 1; } static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn, @@ -174,6 +183,7 @@ 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", -1); } static void sig_server_setup_saved(IRC_SERVER_SETUP_REC *rec, @@ -188,6 +198,10 @@ 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 >= 0) + iconfig_node_set_bool(node, "starttls", rec->starttls); + else + iconfig_node_set_str(node, "starttls", NULL); } void irc_servers_setup_init(void) diff --git a/src/irc/core/irc-servers-setup.h b/src/irc/core/irc-servers-setup.h index d3bafdef..8fbb311e 100644 --- a/src/irc/core/irc-servers-setup.h +++ b/src/irc/core/irc-servers-setup.h @@ -18,6 +18,7 @@ typedef struct { int max_cmds_at_once; int cmd_queue_speed; int max_query_chans; + int starttls; } 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..29b389f4 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,17 +208,17 @@ 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); +static void server_init_1(IRC_SERVER_REC *server) { IRC_SERVER_CONNECT_REC *conn; - char *address, *ptr, *username, *cmd; + char *cmd; g_return_if_fail(server != NULL); conn = server->connrec; - if (conn->proxy != NULL && conn->proxy_password != NULL && - *conn->proxy_password != '\0') { + 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 +244,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->no_starttls)) { + irc_cap_toggle(server, CAP_STARTTLS, TRUE); } server->isupport = g_hash_table_new((GHashFunc) i_istr_hash, (GCompareFunc) i_istr_equal); @@ -296,6 +260,163 @@ 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; + + 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); +} + +static void init_ssl_loop(IRC_SERVER_REC *server, GIOChannel *handle) +{ + int error; + + 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); + } + + server->connrec->starttls = 1; + 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 = 1; + 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("Now attempting STARTTLS"); + 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); +} + +static void server_init_2(IRC_SERVER_REC *server) +{ + IRC_SERVER_CONNECT_REC *conn; + char *address, *ptr, *username, *cmd; + + g_return_if_fail(server != NULL); + + conn = server->connrec; + + 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); + } } SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn) @@ -420,7 +541,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 +550,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; @@ -1048,6 +1180,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 +1192,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 +1224,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..753666ff 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,10 @@ struct _IRC_SERVER_CONNECT_REC { int max_query_chans; int max_kicks, max_msgs, max_modes, max_whois; + int no_starttls:1; + int starttls:1; }; +/* clang-format on */ #define STRUCT_SERVER_CONNECT_REC IRC_SERVER_CONNECT_REC struct _IRC_SERVER_REC { @@ -136,6 +141,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; @@ -155,6 +161,7 @@ void irc_server_purge_output(IRC_SERVER_REC *server, const char *target); 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); +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.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); }