mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 00:12:19 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			459 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			12 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},
 | |
|   G_OPTION_ENTRY_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 GSocket *
 | |
| make_socket (GSocketFamily socket_family, GSocketType socket_type, GError **error)
 | |
| {
 | |
|   GSocket *socket = g_socket_new (socket_family, socket_type, 0, error);
 | |
| 
 | |
|   if (socket && read_timeout)
 | |
|     g_socket_set_timeout (socket, read_timeout);
 | |
| 
 | |
|   return socket;
 | |
| }
 | |
| 
 | |
| 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;
 | |
|   GSocketAddressEnumerator *enumerator;
 | |
|   GSocketConnectable *connectable;
 | |
|   GSocketAddress *src_address;
 | |
|   GTlsInteraction *interaction;
 | |
|   GError *err = NULL;
 | |
|   char *socket_string;
 | |
| 
 | |
|   if (use_udp)
 | |
|     socket_type = G_SOCKET_TYPE_DATAGRAM;
 | |
|   else
 | |
|     socket_type = G_SOCKET_TYPE_STREAM;
 | |
| 
 | |
| 
 | |
|   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 && *error == NULL)
 | |
|             g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
 | |
|                                  "No more addresses to try");
 | |
|           return FALSE;
 | |
|         }
 | |
| 
 | |
|       *socket = make_socket (unix_socket ? G_SOCKET_FAMILY_UNIX : g_socket_address_get_family (*address),
 | |
|                              socket_type, error);
 | |
|       if (*socket == NULL)
 | |
|         {
 | |
|           g_object_unref (*address);
 | |
|           g_object_unref (enumerator);
 | |
|           return FALSE;
 | |
|         }
 | |
| 
 | |
|       if (g_socket_connect (*socket, *address, cancellable, &err))
 | |
|         break;
 | |
|       socket_string = socket_address_to_string (*address);
 | |
|       g_message ("Connection to %s failed: %s, trying next", socket_string, err->message);
 | |
|       g_free (socket_string);
 | |
|       g_clear_error (&err);
 | |
| 
 | |
|       g_object_unref (*socket);
 | |
|       g_object_unref (*address);
 | |
|     }
 | |
|   g_object_unref (enumerator);
 | |
| 
 | |
|   socket_string = socket_address_to_string (*address);
 | |
|   g_print ("Connected to %s\n", socket_string);
 | |
|   g_free (socket_string);
 | |
| 
 | |
|   src_address = g_socket_get_local_address (*socket, error);
 | |
|   if (!src_address)
 | |
|     {
 | |
|       g_prefix_error (error, "Error getting local address: ");
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   socket_string = socket_address_to_string (src_address);
 | |
|   g_print ("local address: %s\n", socket_string);
 | |
|   g_free (socket_string);
 | |
|   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 = NULL;
 | |
|   GSocketAddress *address = NULL;
 | |
|   GError *error = NULL;
 | |
|   GOptionContext *context;
 | |
|   GCancellable *cancellable;
 | |
|   GIOStream *connection = NULL;
 | |
|   GInputStream *istream = NULL;
 | |
|   GOutputStream *ostream = NULL;
 | |
|   GSocketAddress *src_address = NULL;
 | |
|   GTlsCertificate *certificate = NULL;
 | |
|   gint i;
 | |
| 
 | |
|   address = NULL;
 | |
|   connection = NULL;
 | |
| 
 | |
|   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)
 | |
|     {
 | |
|       GThread *thread;
 | |
|       cancellable = g_cancellable_new ();
 | |
|       thread = g_thread_new ("cancel", cancel_thread, cancellable);
 | |
|       g_thread_unref (thread);
 | |
|     }
 | |
|   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 socket: %s\n",
 | |
| 		      error->message);
 | |
| 	  return 1;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|   g_object_unref (socket);
 | |
|   g_object_unref (address);
 | |
| 
 | |
|   return 0;
 | |
| }
 |