--- a/libpurple/sslconn.c Tue Mar 29 22:51:43 2016 -0500 +++ b/libpurple/sslconn.c Wed Mar 30 01:20:31 2016 -0500 @@ -22,11 +22,11 @@ #include "internal.h" -#include "certificate.h" #include "debug.h" #include "plugins.h" #include "request.h" #include "sslconn.h" +#include "tls-certificate.h" static gboolean _ssl_initialized = FALSE; static PurpleSslOps *_ssl_ops = NULL; @@ -34,50 +34,103 @@ static gboolean ssl_init(void) { - PurplePlugin *plugin; - PurpleSslOps *ops; + return g_tls_backend_supports_tls(g_tls_backend_get_default()); +} - if (_ssl_initialized) - return FALSE; +static void +emit_error(PurpleSslConnection *gsc, int error_code) +{ + if (gsc->error_cb != NULL) + gsc->error_cb(gsc, error_code, gsc->connect_cb_data); +} - plugin = purple_plugins_find_plugin("core-ssl"); +static void +tls_handshake_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + PurpleSslConnection *gsc = user_data; + GError *error = NULL; - if (plugin && !purple_plugin_is_loaded(plugin)) - purple_plugin_load(plugin, NULL); + if (!g_tls_connection_handshake_finish(G_TLS_CONNECTION(source), res, + &error)) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + /* Connection already closed/freed. Escape. */ + return; + } else if (g_error_matches(error, G_TLS_ERROR, + G_TLS_ERROR_HANDSHAKE)) { + /* In Gio, a handshake error is because of the cert */ + emit_error(gsc, PURPLE_SSL_CERTIFICATE_INVALID); + } else { + /* Report any other errors as handshake failing */ + emit_error(gsc, PURPLE_SSL_HANDSHAKE_FAILED); + } - ops = purple_ssl_get_ops(); - if ((ops == NULL) || (ops->init == NULL) || (ops->uninit == NULL) || - (ops->connectfunc == NULL) || (ops->close == NULL) || - (ops->read == NULL) || (ops->write == NULL)) - { - return FALSE; + purple_ssl_close(gsc); + return; } - return (_ssl_initialized = ops->init()); + gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ); +} + +static void +tls_connect(PurpleSslConnection *gsc) +{ + GSocket *socket; + GSocketConnection *conn; + GSocketConnectable *identity; + GIOStream *tls_conn; + GError *error = NULL; + + g_return_if_fail(gsc->conn == NULL); + + socket = g_socket_new_from_fd(gsc->fd, &error); + if (socket == NULL) { + emit_error(gsc, PURPLE_SSL_CONNECT_FAILED); + purple_ssl_close(gsc); + return; + } + + conn = g_socket_connection_factory_create_connection(socket); + g_object_unref(socket); + + identity = g_network_address_new(gsc->host, gsc->port); + tls_conn = g_tls_client_connection_new(G_IO_STREAM(conn), identity, + &error); + g_object_unref(identity); + g_object_unref(conn); + + if (tls_conn == NULL) { + emit_error(gsc, PURPLE_SSL_CONNECT_FAILED); + purple_ssl_close(gsc); + return; + } + + gsc->conn = G_TLS_CONNECTION(tls_conn); + gsc->cancellable = g_cancellable_new(); + + purple_tls_certificate_attach_to_tls_connection(gsc->conn); + + g_tls_connection_handshake_async(gsc->conn, G_PRIORITY_DEFAULT, + gsc->cancellable, tls_handshake_cb, gsc); } static void purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message) { PurpleSslConnection *gsc; - PurpleSslOps *ops; gsc = data; gsc->connect_data = NULL; if (source < 0) { - if (gsc->error_cb != NULL) - gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data); - + emit_error(gsc, PURPLE_SSL_CONNECT_FAILED); purple_ssl_close(gsc); return; } gsc->fd = source; - ops = purple_ssl_get_ops(); - ops->connectfunc(gsc); + tls_connect(gsc); } PurpleSslConnection * @@ -115,9 +168,6 @@ gsc->connect_cb = func; gsc->error_cb = error_func; - /* TODO: Move this elsewhere */ - gsc->verifier = purple_certificate_find_verifier("x509","tls_cached"); - gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc); if (gsc->connect_data == NULL) @@ -131,33 +181,45 @@ return (PurpleSslConnection *)gsc; } -static void -recv_cb(gpointer data, gint source, PurpleInputCondition cond) +static gboolean +recv_cb(GObject *source, gpointer data) { PurpleSslConnection *gsc = data; - gsc->recv_cb(gsc->recv_cb_data, gsc, cond); + gsc->recv_cb(gsc->recv_cb_data, gsc, PURPLE_INPUT_READ); + + return TRUE; } void purple_ssl_input_add(PurpleSslConnection *gsc, PurpleSslInputFunction func, void *data) { + GInputStream *input; + GSource *source; + g_return_if_fail(func != NULL); + g_return_if_fail(gsc->conn != NULL); purple_ssl_input_remove(gsc); gsc->recv_cb_data = data; gsc->recv_cb = func; - gsc->inpa = purple_input_add(gsc->fd, PURPLE_INPUT_READ, recv_cb, gsc); + input = g_io_stream_get_input_stream(G_IO_STREAM(gsc->conn)); + /* Pass NULL for cancellable as we don't want it notified on cancel */ + source = g_pollable_input_stream_create_source( + G_POLLABLE_INPUT_STREAM(input), NULL); + g_source_set_callback(source, (GSourceFunc)recv_cb, gsc, NULL); + gsc->inpa = g_source_attach(source, NULL); + g_source_unref(source); } void purple_ssl_input_remove(PurpleSslConnection *gsc) { if (gsc->inpa > 0) { - purple_input_remove(gsc->inpa); + g_source_remove(gsc->inpa); gsc->inpa = 0; } } @@ -186,7 +248,6 @@ void *data) { PurpleSslConnection *gsc; - PurpleSslOps *ops; g_return_val_if_fail(fd != -1, NULL); g_return_val_if_fail(func != NULL, NULL); @@ -205,13 +266,9 @@ gsc->fd = fd; if(host) gsc->host = g_strdup(host); - - /* TODO: Move this elsewhere */ - gsc->verifier = purple_certificate_find_verifier("x509","tls_cached"); + gsc->cancellable = g_cancellable_new(); - - ops = purple_ssl_get_ops(); - ops->connectfunc(gsc); + tls_connect(gsc); return (PurpleSslConnection *)gsc; } @@ -219,24 +276,30 @@ void purple_ssl_close(PurpleSslConnection *gsc) { - PurpleSslOps *ops; - g_return_if_fail(gsc != NULL); purple_request_close_with_handle(gsc); purple_notify_close_with_handle(gsc); - ops = purple_ssl_get_ops(); - (ops->close)(gsc); - if (gsc->connect_data != NULL) purple_proxy_connect_cancel(gsc->connect_data); if (gsc->inpa > 0) purple_input_remove(gsc->inpa); - if (gsc->fd >= 0) - close(gsc->fd); + /* Stop any pending operations */ + if (G_IS_CANCELLABLE(gsc->cancellable)) { + g_cancellable_cancel(gsc->cancellable); + g_clear_object(&gsc->cancellable); + } + + if (gsc->conn != NULL) { + /* Close the stream. Shouldn't take long and it can't + * be further cancelled so don't pass a cancellable + */ + g_io_stream_close(G_IO_STREAM(gsc->conn), NULL, NULL); + g_clear_object(&gsc->conn); + } g_free(gsc->host); g_free(gsc); @@ -245,38 +308,71 @@ size_t purple_ssl_read(PurpleSslConnection *gsc, void *data, size_t len) { - PurpleSslOps *ops; + GInputStream *input; + gssize outlen; + GError *error = NULL; g_return_val_if_fail(gsc != NULL, 0); g_return_val_if_fail(data != NULL, 0); g_return_val_if_fail(len > 0, 0); + g_return_val_if_fail(gsc->conn != NULL, 0); - ops = purple_ssl_get_ops(); - return (ops->read)(gsc, data, len); + input = g_io_stream_get_input_stream(G_IO_STREAM(gsc->conn)); + outlen = g_pollable_input_stream_read_nonblocking( + G_POLLABLE_INPUT_STREAM(input), data, len, + gsc->cancellable, &error); + + if (outlen < 0) { + if (outlen == G_IO_ERROR_WOULD_BLOCK) { + errno = EAGAIN; + } + + g_clear_error(&error); + } + + return outlen; } size_t purple_ssl_write(PurpleSslConnection *gsc, const void *data, size_t len) { - PurpleSslOps *ops; + GOutputStream *output; + gssize outlen; + GError *error = NULL; g_return_val_if_fail(gsc != NULL, 0); g_return_val_if_fail(data != NULL, 0); g_return_val_if_fail(len > 0, 0); + g_return_val_if_fail(gsc->conn != NULL, 0); - ops = purple_ssl_get_ops(); - return (ops->write)(gsc, data, len); + output = g_io_stream_get_output_stream(G_IO_STREAM(gsc->conn)); + outlen = g_pollable_output_stream_write_nonblocking( + G_POLLABLE_OUTPUT_STREAM(output), data, len, + gsc->cancellable, &error); + + if (outlen < 0) { + if (g_error_matches(error, G_IO_ERROR, + G_IO_ERROR_WOULD_BLOCK)) { + errno = EAGAIN; + } + + g_clear_error(&error); + } + + return outlen; } GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc) { - PurpleSslOps *ops; + GTlsCertificate *certificate; g_return_val_if_fail(gsc != NULL, NULL); + g_return_val_if_fail(gsc->conn != NULL, NULL); - ops = purple_ssl_get_ops(); - return (ops->get_peer_certificates)(gsc); + certificate = g_tls_connection_get_peer_certificate(gsc->conn); + + return certificate != NULL ? g_list_append(NULL, certificate) : NULL; } void @@ -305,13 +401,8 @@ void purple_ssl_uninit(void) { - PurpleSslOps *ops; - if (!_ssl_initialized) return; - ops = purple_ssl_get_ops(); - ops->uninit(); - _ssl_initialized = FALSE; }