diff --git a/src/core/network-openssl.c b/src/core/network-openssl.c index 75d66050..d9bd948e 100644 --- a/src/core/network-openssl.c +++ b/src/core/network-openssl.c @@ -38,7 +38,6 @@ typedef struct GIOChannel *giochan; SSL *ssl; SSL_CTX *ctx; - unsigned int got_cert:1; unsigned int verify:1; } GIOSSLChannel; @@ -110,45 +109,11 @@ static GIOStatus ssl_errno(gint e) return G_IO_STATUS_ERROR; } -static GIOStatus irssi_ssl_cert_step(GIOSSLChannel *chan) -{ - X509 *cert; - gint err; - switch(err = SSL_do_handshake(chan->ssl)) - { - case 1: - if(!(cert = SSL_get_peer_certificate(chan->ssl))) - { - g_warning("SSL server supplied no certificate"); - return G_IO_STATUS_ERROR; - } - if (chan->verify && ! irssi_ssl_verify(chan->ssl, chan->ctx, cert)) { - X509_free(cert); - return G_IO_STATUS_ERROR; - } - X509_free(cert); - return G_IO_STATUS_NORMAL; - default: - if(SSL_get_error(chan->ssl, err) == SSL_ERROR_WANT_READ) - return G_IO_STATUS_AGAIN; - return ssl_errno(errno); - } - /*UNREACH*/ - return G_IO_STATUS_ERROR; -} - static GIOStatus irssi_ssl_read(GIOChannel *handle, gchar *buf, gsize len, gsize *ret, GError **gerr) { GIOSSLChannel *chan = (GIOSSLChannel *)handle; gint err; - if(! chan->got_cert) - { - gint cert_err = irssi_ssl_cert_step(chan); - if(cert_err != G_IO_STATUS_NORMAL) - return cert_err; - } - err = SSL_read(chan->ssl, buf, len); if(err < 0) { @@ -171,13 +136,6 @@ static GIOStatus irssi_ssl_write(GIOChannel *handle, const gchar *buf, gsize len GIOSSLChannel *chan = (GIOSSLChannel *)handle; gint err; - if(! chan->got_cert) - { - gint cert_err = irssi_ssl_cert_step(chan); - if(cert_err != G_IO_STATUS_NORMAL) - return cert_err; - } - err = SSL_write(chan->ssl, (const char *)buf, len); if(err < 0) { @@ -265,7 +223,6 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycer GIOChannel *gchan; int err, fd; SSL *ssl; - X509 *cert = NULL; SSL_CTX *ctx = NULL; g_return_val_if_fail(handle != NULL, NULL); @@ -336,47 +293,11 @@ static GIOChannel *irssi_ssl_get_iochannel(GIOChannel *handle, const char *mycer return NULL; } - if((err = SSL_connect(ssl)) <= 0) - { - switch(err = SSL_get_error(ssl, err)) - { - case SSL_ERROR_SYSCALL: - if(errno == EINTR || errno == EAGAIN) - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - break; - default: - SSL_free(ssl); - if (ctx != ssl_ctx) - SSL_CTX_free(ctx); - return NULL; - } - } - else if(!(cert = SSL_get_peer_certificate(ssl))) - { - g_warning("SSL server supplied no certificate"); - if (ctx != ssl_ctx) - SSL_CTX_free(ctx); - SSL_free(ssl); - return NULL; - } - else - { - if (verify && ! irssi_ssl_verify(ssl, ctx, cert)) { - SSL_free(ssl); - if (ctx != ssl_ctx) - SSL_CTX_free(ctx); - return NULL; - } - X509_free(cert); - } - chan = g_new0(GIOSSLChannel, 1); chan->fd = fd; chan->giochan = handle; chan->ssl = ssl; chan->ctx = ctx; - chan->got_cert = cert != NULL; chan->verify = verify; gchan = (GIOChannel *)chan; @@ -397,6 +318,32 @@ GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char * return ssl_handle; } +int irssi_ssl_handshake(GIOChannel *handle) +{ + GIOSSLChannel *chan = (GIOSSLChannel *)handle; + int ret, err; + X509 *cert; + + ret = SSL_connect(chan->ssl); + if (ret <= 0) { + err = SSL_get_error(chan->ssl, ret); + if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) { + g_warning(ERR_reason_error_string(ERR_get_error())); + return -1; + } + return err == SSL_ERROR_WANT_READ ? 1 : 3; + } + + cert = SSL_get_peer_certificate(chan->ssl); + if (cert == NULL) { + g_warning("SSL server supplied no certificate"); + return -1; + } + ret = !chan->verify || irssi_ssl_verify(chan->ssl, chan->ctx, cert); + X509_free(cert); + return ret ? 0 : -1; +} + #else /* HAVE_OPENSSL */ GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify) diff --git a/src/core/network.h b/src/core/network.h index 5e445e83..a3a4eb41 100644 --- a/src/core/network.h +++ b/src/core/network.h @@ -48,6 +48,7 @@ int net_ip_compare(IPADDR *ip1, IPADDR *ip2); GIOChannel *net_connect(const char *addr, int port, IPADDR *my_ip); /* Connect to socket with ip address and SSL*/ GIOChannel *net_connect_ip_ssl(IPADDR *ip, int port, IPADDR *my_ip, const char *cert, const char *pkey, const char *cafile, const char *capath, gboolean verify); +int irssi_ssl_handshake(GIOChannel *handle); /* Connect to socket with ip address */ GIOChannel *net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); /* Connect to named UNIX socket */ diff --git a/src/core/servers.c b/src/core/servers.c index 046fa072..2e9d11fc 100644 --- a/src/core/servers.c +++ b/src/core/servers.c @@ -167,6 +167,39 @@ static void server_connect_callback_init(SERVER_REC *server, GIOChannel *handle) server_connect_finished(server); } +#ifdef HAVE_OPENSSL +static void server_connect_callback_init_ssl(SERVER_REC *server, GIOChannel *handle) +{ + int error; + + g_return_if_fail(IS_SERVER(server)); + + error = irssi_ssl_handshake(handle); + if (error == -1) { + server->connection_lost = TRUE; + server_connect_failed(server, NULL); + return; + } + if (error & 1) { + if (server->connect_tag != -1) + g_source_remove(server->connect_tag); + server->connect_tag = g_input_add(handle, error == 1 ? G_INPUT_READ : G_INPUT_WRITE, + (GInputFunction) + server_connect_callback_init_ssl, + server); + return; + } + + lookup_servers = g_slist_remove(lookup_servers, server); + if (server->connect_tag != -1) { + g_source_remove(server->connect_tag); + server->connect_tag = -1; + } + + server_connect_finished(server); +} +#endif + static void server_real_connect(SERVER_REC *server, IPADDR *ip, const char *unix_socket) { @@ -218,6 +251,11 @@ server->connrec->ssl_cafile, server->connrec->ssl_capath, server->connrec->ssl_v g_free(errmsg2); } else { server->handle = net_sendbuffer_create(handle, 0); +#ifdef HAVE_OPENSSL + if (server->connrec->use_ssl) + server_connect_callback_init_ssl(server, handle); + else +#endif server->connect_tag = g_input_add(handle, G_INPUT_WRITE | G_INPUT_READ, (GInputFunction)