libpurple/sslconn.c

Sun, 15 May 2016 15:32:05 -0300

author
Florian Quèze <florian@instantbird.org>
date
Sun, 15 May 2016 15:32:05 -0300
changeset 37679
6ff708c38c04
parent 37635
e6ccacaa6322
child 37682
c49f5f42b10e
permissions
-rw-r--r--

Add "account-status-changing" signal from instantbird

It's like "account-status-changed", but emitted before the status change
instead of after.

Instantbird ticket: https://bugzilla.mozilla.org/show_bug.cgi?id=954403

/* purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */
#define _PURPLE_SSLCONN_C_

#include "internal.h"

#include "debug.h"
#include "plugins.h"
#include "request.h"
#include "sslconn.h"
#include "tls-certificate.h"

static void
emit_error(PurpleSslConnection *gsc, int error_code)
{
	if (gsc->error_cb != NULL)
		gsc->error_cb(gsc, error_code, gsc->connect_cb_data);
}

static void
tls_handshake_cb(GObject *source, GAsyncResult *res, gpointer user_data)
{
	PurpleSslConnection *gsc = user_data;
	GError *error = 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);
		}

		purple_ssl_close(gsc);
		return;
	}

	gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
}

static gboolean
tls_connect(PurpleSslConnection *gsc)
{
	GSocket *socket;
	GSocketConnection *conn;
	GSocketConnectable *identity;
	GIOStream *tls_conn;
	GError *error = NULL;

	g_return_val_if_fail(gsc->conn == NULL, FALSE);

	socket = g_socket_new_from_fd(gsc->fd, &error);
	if (socket == NULL) {
		purple_debug_warning("sslconn",
				"Error creating socket from fd (%u): %s",
				gsc->fd, error->message);
		g_clear_error(&error);
		return FALSE;
	}

	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) {
		purple_debug_warning("sslconn",
				"Error creating TLS client connection: %s",
				error->message);
		g_clear_error(&error);
		return FALSE;
	}

	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);

	return TRUE;
}

static void
purple_ssl_connect_cb(gpointer data, gint source, const gchar *error_message)
{
	PurpleSslConnection *gsc;

	gsc = data;
	gsc->connect_data = NULL;

	if (source < 0)
	{
		emit_error(gsc, PURPLE_SSL_CONNECT_FAILED);
		purple_ssl_close(gsc);
		return;
	}

	gsc->fd = source;

	if (!tls_connect(gsc)) {
		emit_error(gsc, PURPLE_SSL_CONNECT_FAILED);
		purple_ssl_close(gsc);
	}
}

PurpleSslConnection *
purple_ssl_connect(PurpleAccount *account, const char *host, int port,
				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
				 void *data)
{
	return purple_ssl_connect_with_ssl_cn(account, host, port, func, error_func,
	                                  NULL, data);
}

PurpleSslConnection *
purple_ssl_connect_with_ssl_cn(PurpleAccount *account, const char *host, int port,
				 PurpleSslInputFunction func, PurpleSslErrorFunction error_func,
				 const char *ssl_cn, void *data)
{
	PurpleSslConnection *gsc;

	g_return_val_if_fail(host != NULL,            NULL);
	g_return_val_if_fail(port != 0 && port != -1, NULL);
	g_return_val_if_fail(func != NULL,            NULL);

	gsc = g_new0(PurpleSslConnection, 1);

	gsc->fd              = -1;
	gsc->host            = ssl_cn ? g_strdup(ssl_cn) : g_strdup(host);
	gsc->port            = port;
	gsc->connect_cb_data = data;
	gsc->connect_cb      = func;
	gsc->error_cb        = error_func;

	gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc);

	if (gsc->connect_data == NULL)
	{
		g_free(gsc->host);
		g_free(gsc);

		return NULL;
	}

	return (PurpleSslConnection *)gsc;
}

static gboolean
recv_cb(GObject *source, gpointer data)
{
	PurpleSslConnection *gsc = data;

	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;

	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) {
		g_source_remove(gsc->inpa);
		gsc->inpa = 0;
	}
}

const gchar *
purple_ssl_strerror(PurpleSslErrorType error)
{
	switch(error) {
		case PURPLE_SSL_CONNECT_FAILED:
			return _("SSL Connection Failed");
		case PURPLE_SSL_HANDSHAKE_FAILED:
			return _("SSL Handshake Failed");
		case PURPLE_SSL_CERTIFICATE_INVALID:
			return _("SSL peer presented an invalid certificate");
		default:
			purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error);
			return _("Unknown SSL error");
	}
}

PurpleSslConnection *
purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd,
                      PurpleSslInputFunction func,
                      PurpleSslErrorFunction error_func,
                      const char *host,
                      void *data)
{
	PurpleSslConnection *gsc;

	g_return_val_if_fail(fd != -1,                NULL);
	g_return_val_if_fail(func != NULL,            NULL);

	gsc = g_new0(PurpleSslConnection, 1);

	gsc->connect_cb_data = data;
	gsc->connect_cb      = func;
	gsc->error_cb        = error_func;
	gsc->fd              = fd;
    if(host)
        gsc->host            = g_strdup(host);
	gsc->cancellable     = g_cancellable_new();

	if (!tls_connect(gsc)) {
		emit_error(gsc, PURPLE_SSL_CONNECT_FAILED);
		g_clear_pointer(&gsc, purple_ssl_close);
	}

	return (PurpleSslConnection *)gsc;
}

void
purple_ssl_close(PurpleSslConnection *gsc)
{
	g_return_if_fail(gsc != NULL);

	purple_request_close_with_handle(gsc);
	purple_notify_close_with_handle(gsc);

	if (gsc->connect_data != NULL)
		purple_proxy_connect_cancel(gsc->connect_data);

	if (gsc->inpa > 0)
		purple_input_remove(gsc->inpa);

	/* 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);
}

size_t
purple_ssl_read(PurpleSslConnection *gsc, void *data, size_t len)
{
	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);

	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 (g_error_matches(error, G_IO_ERROR,
				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)
{
	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);

	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)
{
	GTlsCertificate *certificate;

	g_return_val_if_fail(gsc != NULL, NULL);
	g_return_val_if_fail(gsc->conn != NULL, NULL);

	certificate = g_tls_connection_get_peer_certificate(gsc->conn);

	return certificate != NULL ? g_list_append(NULL, certificate) : NULL;
}

mercurial