glib/gio/tests/socket-client.c
Stef Walter 0f99cfa882 GTlsDatabase and related objects
The database is an abstract object implemented by the various TLS
backends, which is used by GTlsConnection to lookup certificates
and keys, as well as verify certificate chains.

Also add GTlsInteraction, which can be used to prompt the user
for a password or PIN (used with the database).

https://bugzilla.gnome.org/show_bug.cgi?id=636572
2011-08-04 08:54:55 +02:00

435 lines
11 KiB
C

#include <gio/gio.h>
#include <gio/gunixsocketaddress.h>
#include <glib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gtlsconsoleinteraction.h"
GMainLoop *loop;
gboolean verbose = FALSE;
gboolean non_blocking = FALSE;
gboolean use_udp = FALSE;
int cancel_timeout = 0;
int read_timeout = 0;
gboolean unix_socket = FALSE;
gboolean tls = FALSE;
static GOptionEntry cmd_entries[] = {
{"cancel", 'c', 0, G_OPTION_ARG_INT, &cancel_timeout,
"Cancel any op after the specified amount of seconds", NULL},
{"udp", 'u', 0, G_OPTION_ARG_NONE, &use_udp,
"Use udp instead of tcp", NULL},
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
"Be verbose", NULL},
{"non-blocking", 'n', 0, G_OPTION_ARG_NONE, &non_blocking,
"Enable non-blocking i/o", NULL},
#ifdef G_OS_UNIX
{"unix", 'U', 0, G_OPTION_ARG_NONE, &unix_socket,
"Use a unix socket instead of IP", NULL},
#endif
{"timeout", 't', 0, G_OPTION_ARG_INT, &read_timeout,
"Time out reads after the specified number of seconds", NULL},
{"tls", 'T', 0, G_OPTION_ARG_NONE, &tls,
"Use TLS (SSL)", NULL},
{NULL}
};
#include "socket-common.c"
static gboolean
accept_certificate (GTlsClientConnection *conn, GTlsCertificate *cert,
GTlsCertificateFlags errors, gpointer user_data)
{
g_print ("Certificate would have been rejected ( ");
if (errors & G_TLS_CERTIFICATE_UNKNOWN_CA)
g_print ("unknown-ca ");
if (errors & G_TLS_CERTIFICATE_BAD_IDENTITY)
g_print ("bad-identity ");
if (errors & G_TLS_CERTIFICATE_NOT_ACTIVATED)
g_print ("not-activated ");
if (errors & G_TLS_CERTIFICATE_EXPIRED)
g_print ("expired ");
if (errors & G_TLS_CERTIFICATE_REVOKED)
g_print ("revoked ");
if (errors & G_TLS_CERTIFICATE_INSECURE)
g_print ("insecure ");
g_print (") but accepting anyway.\n");
return TRUE;
}
static GTlsCertificate *
lookup_client_certificate (GTlsClientConnection *conn, GError **error)
{
GList *l, *accepted;
GList *c, *certificates;
GTlsDatabase *database;
GTlsCertificate *certificate = NULL;
GTlsConnection *base;
accepted = g_tls_client_connection_get_accepted_cas (conn);
for (l = accepted; l != NULL; l = g_list_next (l))
{
base = G_TLS_CONNECTION (conn);
database = g_tls_connection_get_database (base);
certificates = g_tls_database_lookup_certificates_issued_by (database, l->data,
g_tls_connection_get_interaction (base),
G_TLS_DATABASE_LOOKUP_KEYPAIR,
NULL, error);
if (error && *error)
break;
if (certificates)
certificate = g_object_ref (certificates->data);
for (c = certificates; c != NULL; c = g_list_next (c))
g_object_unref (c->data);
g_list_free (certificates);
}
for (l = accepted; l != NULL; l = g_list_next (l))
g_byte_array_unref (l->data);
g_list_free (accepted);
if (certificate == NULL && error && !*error)
g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED,
"Server requested a certificate, but could not find relevant certificate in database.");
return certificate;
}
static gboolean
make_connection (const char *argument, GTlsCertificate *certificate, GCancellable *cancellable,
GSocket **socket, GSocketAddress **address, GIOStream **connection,
GInputStream **istream, GOutputStream **ostream, GError **error)
{
GSocketType socket_type;
GSocketFamily socket_family;
GSocketAddressEnumerator *enumerator;
GSocketConnectable *connectable;
GSocketAddress *src_address;
GTlsInteraction *interaction;
GError *err = NULL;
if (use_udp)
socket_type = G_SOCKET_TYPE_DATAGRAM;
else
socket_type = G_SOCKET_TYPE_STREAM;
if (unix_socket)
socket_family = G_SOCKET_FAMILY_UNIX;
else
socket_family = G_SOCKET_FAMILY_IPV4;
*socket = g_socket_new (socket_family, socket_type, 0, error);
if (*socket == NULL)
return FALSE;
if (read_timeout)
g_socket_set_timeout (*socket, read_timeout);
if (unix_socket)
{
GSocketAddress *addr;
addr = socket_address_from_string (argument);
if (addr == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not parse '%s' as unix socket name", argument);
return FALSE;
}
connectable = G_SOCKET_CONNECTABLE (addr);
}
else
{
connectable = g_network_address_parse (argument, 7777, error);
if (connectable == NULL)
return FALSE;
}
enumerator = g_socket_connectable_enumerate (connectable);
while (TRUE)
{
*address = g_socket_address_enumerator_next (enumerator, cancellable, error);
if (*address == NULL)
{
if (error == NULL)
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"No more addresses to try");
return FALSE;
}
if (g_socket_connect (*socket, *address, cancellable, &err))
break;
g_message ("Connection to %s failed: %s, trying next\n", socket_address_to_string (*address), err->message);
g_clear_error (&err);
g_object_unref (*address);
}
g_object_unref (enumerator);
g_print ("Connected to %s\n",
socket_address_to_string (*address));
src_address = g_socket_get_local_address (*socket, error);
if (!src_address)
{
g_prefix_error (error, "Error getting local address: ");
return FALSE;
}
g_print ("local address: %s\n",
socket_address_to_string (src_address));
g_object_unref (src_address);
if (use_udp)
{
*connection = NULL;
*istream = NULL;
*ostream = NULL;
}
else
*connection = G_IO_STREAM (g_socket_connection_factory_create_connection (*socket));
if (tls)
{
GIOStream *tls_conn;
tls_conn = g_tls_client_connection_new (*connection, connectable, error);
if (!tls_conn)
{
g_prefix_error (error, "Could not create TLS connection: ");
return FALSE;
}
g_signal_connect (tls_conn, "accept-certificate",
G_CALLBACK (accept_certificate), NULL);
interaction = g_tls_console_interaction_new ();
g_tls_connection_set_interaction (G_TLS_CONNECTION (tls_conn), interaction);
g_object_unref (interaction);
if (certificate)
g_tls_connection_set_certificate (G_TLS_CONNECTION (tls_conn), certificate);
g_object_unref (*connection);
*connection = G_IO_STREAM (tls_conn);
if (!g_tls_connection_handshake (G_TLS_CONNECTION (tls_conn),
cancellable, error))
{
g_prefix_error (error, "Error during TLS handshake: ");
return FALSE;
}
}
g_object_unref (connectable);
if (*connection)
{
*istream = g_io_stream_get_input_stream (*connection);
*ostream = g_io_stream_get_output_stream (*connection);
}
return TRUE;
}
int
main (int argc,
char *argv[])
{
GSocket *socket;
GSocketAddress *address;
GError *error = NULL;
GOptionContext *context;
GCancellable *cancellable;
GIOStream *connection;
GInputStream *istream;
GOutputStream *ostream;
GSocketAddress *src_address;
GTlsCertificate *certificate = NULL;
gint i;
g_thread_init (NULL);
g_type_init ();
context = g_option_context_new (" <hostname>[:port] - Test GSocket client stuff");
g_option_context_add_main_entries (context, cmd_entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("%s: %s\n", argv[0], error->message);
return 1;
}
if (argc != 2)
{
g_printerr ("%s: %s\n", argv[0], "Need to specify hostname / unix socket name");
return 1;
}
if (use_udp && tls)
{
g_printerr ("DTLS (TLS over UDP) is not supported");
return 1;
}
if (cancel_timeout)
{
cancellable = g_cancellable_new ();
g_thread_create (cancel_thread, cancellable, FALSE, NULL);
}
else
{
cancellable = NULL;
}
loop = g_main_loop_new (NULL, FALSE);
for (i = 0; i < 2; i++)
{
if (make_connection (argv[1], certificate, cancellable, &socket, &address,
&connection, &istream, &ostream, &error))
break;
if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_CERTIFICATE_REQUIRED))
{
g_clear_error (&error);
certificate = lookup_client_certificate (G_TLS_CLIENT_CONNECTION (connection), &error);
if (certificate != NULL)
continue;
}
g_printerr ("%s: %s", argv[0], error->message);
return 1;
}
/* TODO: Test non-blocking connect/handshake */
if (non_blocking)
g_socket_set_blocking (socket, FALSE);
while (TRUE)
{
gchar buffer[4096];
gssize size;
gsize to_send;
if (fgets (buffer, sizeof buffer, stdin) == NULL)
break;
to_send = strlen (buffer);
while (to_send > 0)
{
if (use_udp)
{
ensure_socket_condition (socket, G_IO_OUT, cancellable);
size = g_socket_send_to (socket, address,
buffer, to_send,
cancellable, &error);
}
else
{
ensure_connection_condition (connection, G_IO_OUT, cancellable);
size = g_output_stream_write (ostream,
buffer, to_send,
cancellable, &error);
}
if (size < 0)
{
if (g_error_matches (error,
G_IO_ERROR,
G_IO_ERROR_WOULD_BLOCK))
{
g_print ("socket send would block, handling\n");
g_error_free (error);
error = NULL;
continue;
}
else
{
g_printerr ("Error sending to socket: %s\n",
error->message);
return 1;
}
}
g_print ("sent %" G_GSSIZE_FORMAT " bytes of data\n", size);
if (size == 0)
{
g_printerr ("Unexpected short write\n");
return 1;
}
to_send -= size;
}
if (use_udp)
{
ensure_socket_condition (socket, G_IO_IN, cancellable);
size = g_socket_receive_from (socket, &src_address,
buffer, sizeof buffer,
cancellable, &error);
}
else
{
ensure_connection_condition (connection, G_IO_IN, cancellable);
size = g_input_stream_read (istream,
buffer, sizeof buffer,
cancellable, &error);
}
if (size < 0)
{
g_printerr ("Error receiving from socket: %s\n",
error->message);
return 1;
}
if (size == 0)
break;
g_print ("received %" G_GSSIZE_FORMAT " bytes of data", size);
if (use_udp)
g_print (" from %s", socket_address_to_string (src_address));
g_print ("\n");
if (verbose)
g_print ("-------------------------\n"
"%.*s"
"-------------------------\n",
(int)size, buffer);
}
g_print ("closing socket\n");
if (connection)
{
if (!g_io_stream_close (connection, cancellable, &error))
{
g_printerr ("Error closing connection: %s\n",
error->message);
return 1;
}
g_object_unref (connection);
}
else
{
if (!g_socket_close (socket, &error))
{
g_printerr ("Error closing master socket: %s\n",
error->message);
return 1;
}
}
g_object_unref (socket);
g_object_unref (address);
return 0;
}