From a5d3f8a6e78844505d9b59f5c41d71d266e4581b Mon Sep 17 00:00:00 2001 From: Moritz Schulte Date: Wed, 26 Jan 2005 22:20:21 +0000 Subject: [PATCH] 2005-01-26 Moritz Schulte * command-ssh.c: New file. * Makefile.am (gpg_agent_SOURCES): New source file: command-ssh.c. * findkey.c (modify_description): New function. (agent_key_from_file): Support comment field in key s-expressions. * gpg-agent.c (enum cmd_and_opt_values): New item: oSSHSupport. (opts) New entry for oSSHSupport. New variable: socket_name_ssh. (cleanup_do): New function based on cleanup(). (cleanup): Use cleanup_do() for socket_name and socket_name_ssh. (main): New switch case for oSSHSupport. (main): Move socket name creation code to ... (create_socket_name): ... this new function. (main): Use create_socket_name() for creating socket names for socket_name and for socket_name_ssh in case ssh support is enabled. Move socket creation code to ... (create_server_socket): ... this new function. (main): Use create_server_socket() for creating sockets. In case standard_socket is set, do not only store a socket name in socket_name, but also in socket_name_ssh. Generate additional environment info strings for ssh support. Pass additional ssh socket argument to handle_connections. (start_connection_thread_ssh): New function. (handle_connections): Use select to multiplex between gpg-agent and ssh-agent protocol. * agent.h (struct opt): New member: ssh_support. Declare function: start_command_handler_ssh. --- agent/ChangeLog | 33 ++++ agent/Makefile.am | 4 +- agent/agent.h | 6 +- agent/findkey.c | 144 +++++++++++++++- agent/gpg-agent.c | 407 ++++++++++++++++++++++++++++++++-------------- 5 files changed, 468 insertions(+), 126 deletions(-) diff --git a/agent/ChangeLog b/agent/ChangeLog index c790482b4..051ed911e 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,36 @@ +2005-01-26 Moritz Schulte + + * command-ssh.c: New file. + * Makefile.am (gpg_agent_SOURCES): New source file: command-ssh.c. + + * findkey.c (modify_description): New function. + (agent_key_from_file): Support comment field in key s-expressions. + + * gpg-agent.c (enum cmd_and_opt_values): New item: oSSHSupport. + (opts) New entry for oSSHSupport. + New variable: socket_name_ssh. + (cleanup_do): New function based on cleanup(). + (cleanup): Use cleanup_do() for socket_name and socket_name_ssh. + (main): New switch case for oSSHSupport. + (main): Move socket name creation code to ... + (create_socket_name): ... this new function. + (main): Use create_socket_name() for creating socket names for + socket_name and for socket_name_ssh in case ssh support is + enabled. + Move socket creation code to ... + (create_server_socket): ... this new function. + (main): Use create_server_socket() for creating sockets. + In case standard_socket is set, do not only store a socket name in + socket_name, but also in socket_name_ssh. + Generate additional environment info strings for ssh support. + Pass additional ssh socket argument to handle_connections. + (start_connection_thread_ssh): New function. + (handle_connections): Use select to multiplex between gpg-agent + and ssh-agent protocol. + + * agent.h (struct opt): New member: ssh_support. + Declare function: start_command_handler_ssh. + 2005-01-04 Werner Koch * trustlist.c (agent_marktrusted): Use "Cancel" for the first diff --git a/agent/Makefile.am b/agent/Makefile.am index 4cedbe74e..df8ec322c 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -1,4 +1,4 @@ -# Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc. +# Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc. # # This file is part of GnuPG. # @@ -29,7 +29,7 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS) gpg_agent_SOURCES = \ gpg-agent.c agent.h \ - command.c \ + command.c command-ssh.c \ query.c \ cache.c \ trans.c \ diff --git a/agent/agent.h b/agent/agent.h index 7d6bf9f47..8afda6463 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,5 +1,5 @@ /* agent.h - Global definitions for the agent - * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -66,6 +66,7 @@ struct { int allow_preset_passphrase; int keep_tty; /* don't switch the TTY (for pinentry) on request */ int keep_display; /* don't switch the DISPLAY (for pinentry) on request */ + int ssh_support; /* Enable ssh-agent emulation. */ } opt; @@ -136,6 +137,9 @@ void agent_init_default_ctrl (struct server_control_s *ctrl); /*-- command.c --*/ void start_command_handler (int, int); +/*-- command-ssh.c --*/ +void start_command_handler_ssh (int); + /*-- findkey.c --*/ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); diff --git a/agent/findkey.c b/agent/findkey.c index b54528295..d39d3aae3 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,5 +1,5 @@ /* findkey.c - locate the secret key - * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -140,6 +140,108 @@ try_unprotect_cb (struct pin_entry_info_s *pi) } +/* Modify a Key description, replacing certain special format + characters. List of currently supported replacements: + + %% -> % + %c -> . */ +static int +modify_description (const char *description, + const char *comment, size_t comment_length, + char **description_modified) +{ + size_t description_length; + size_t description_new_length; + gpg_error_t err; + char *description_new; + unsigned int i, j; + unsigned int special; + + description_length = strlen (description); + description_new_length = description_length; + description_new = NULL; + + /* Calculate length. */ + special = 0; + for (i = 0; i < description_length; i++) + { + if (description[i] == '%') + special = 1; + else + { + if (special) + { + description_new_length -= 2; + switch (description[i]) + { + case 'c': + /* Comment. */ + description_new_length += comment_length; + break; + + case '%': + description_new_length += 1; + break; + } + special = 0; + } + } + } + + /* Allocate. */ + description_new = xtrymalloc (description_new_length + 1); + if (! description_new) + { + err = gpg_error_from_errno (errno); + goto out; + } + + /* Fill. */ + for (i = j = 0; i < description_length; i++) + { + if (description[i] == '%') + special = 1; + else + { + if (special) + { + switch (description[i]) + { + case 'c': + /* Comment. */ + if (comment) + { + strncpy (description_new + j, comment, comment_length); + j += comment_length; + } + break; + + case '%': + description_new[j] = '%'; + j++; + break; + } + special = 0; + } + else + { + description_new[j] = description[i]; + j++; + } + } + } + + description_new[j] = 0; + *description_modified = description_new; + err = 0; + + out: + + return err; +} + + + /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default @@ -292,10 +394,42 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_PROTECTED: - rc = unprotect (ctrl, desc_text, &buf, grip, ignore_cache); - if (rc) - log_error ("failed to unprotect the secret key: %s\n", - gpg_strerror (rc)); + { + gcry_sexp_t comment_sexp; + size_t comment_length; + char *desc_text_final; + const char *comment; + + comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); + if (comment_sexp) + comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length); + else + { + comment = NULL; + comment_length = 0; + } + + if (desc_text) + { + rc = modify_description (desc_text, + comment, comment_length, &desc_text_final); + if (rc) + log_error ("failed to modify description: %s\n", gpg_strerror (rc)); + } + else + desc_text_final = NULL; + + if (! rc) + { + rc = unprotect (ctrl, desc_text_final, &buf, grip, ignore_cache); + if (rc) + log_error ("failed to unprotect the secret key: %s\n", + gpg_strerror (rc)); + } + + gcry_sexp_release (comment_sexp); + xfree (desc_text_final); + } break; case PRIVATE_KEY_SHADOWED: if (shadow_info) diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index e76623f75..9f97fb6d7 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,5 +1,6 @@ /* gpg-agent.c - The GnuPG Agent - * Copyright (C) 2000, 2001, 2002, 2003 Free Software Foundation, Inc. + * Copyright (C) 2000, 2001, 2002, 2003, + * 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -91,7 +92,8 @@ enum cmd_and_opt_values oAllowMarkTrusted, oAllowPresetPassphrase, oKeepTTY, - oKeepDISPLAY + oKeepDISPLAY, + oSSHSupport }; @@ -144,6 +146,7 @@ static ARGPARSE_OPTS opts[] = { N_("allow clients to mark keys as \"trusted\"")}, { oAllowPresetPassphrase, "allow-preset-passphrase", 0, N_("allow presetting passphrase")}, + { oSSHSupport, "ssh-support", 0, "Enable SSH-Agent emulation" }, {0} }; @@ -163,6 +166,9 @@ static int maybe_setuid = 1; /* Name of the communication socket */ static char *socket_name; +/* Name of the communication socket used for ssh-agent-emulation. */ +static char *socket_name_ssh; + /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; @@ -183,7 +189,7 @@ static char *current_logfile; /* Local prototypes. */ static void create_directories (void); #ifdef USE_GNU_PTH -static void handle_connections (int listen_fd); +static void handle_connections (int listen_fd, int listen_fd_ssh); /* Pth wrapper function definitions. */ GCRY_THREAD_OPTION_PTH_IMPL; #endif /*USE_GNU_PTH*/ @@ -297,22 +303,29 @@ set_debug (void) static void -cleanup (void) +cleanup_do (char *name) { - if (socket_name && *socket_name) + if (name && *name) { char *p; - remove (socket_name); - p = strrchr (socket_name, '/'); + remove (name); + p = strrchr (name, '/'); if (p) - { - *p = 0; - rmdir (socket_name); - *p = '/'; - } - *socket_name = 0; + { + *p = 0; + rmdir (name); + *p = '/'; + } + *name = 0; } +} + +static void +cleanup (void) +{ + cleanup_do (socket_name); + cleanup_do (socket_name_ssh); } @@ -404,6 +417,105 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) } +static void +create_socket_name (char **name, int standard_socket, + struct sockaddr_un *serv_addr, + char *standard_identifier, char *identifier) +{ + char *p; + + if (standard_socket) + *name = make_filename (opt.homedir, standard_identifier, NULL); + else + { + *name = xstrdup (identifier); + p = strrchr (*name, '/'); + if (! p) + BUG (); + *p = 0; + if (!mkdtemp (*name)) + { + log_error (_("can't create directory `%s': %s\n"), + *name, strerror (errno)); + exit (1); + } + *p = '/'; + } + + if (strchr (*name, PATHSEP_C)) + { + log_error ("`%s' are not allowed in the socket name\n", PATHSEP_S); + exit (1); + } + if (strlen (*name) + 1 >= sizeof serv_addr->sun_path) + { + log_error ("name of socket too long\n"); + exit (1); + } +} + +static int +create_server_socket (struct sockaddr_un *serv_addr, + int standard_socket, const char *name) +{ + socklen_t len; + int fd; + int rc; + +#ifdef HAVE_W32_SYSTEM + fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); +#else + fd = socket (AF_UNIX, SOCK_STREAM, 0); +#endif + if (fd == -1) + { + log_error ("can't create socket: %s\n", strerror (errno)); + exit (1); + } + + memset (serv_addr, 0, sizeof *serv_addr); + serv_addr->sun_family = AF_UNIX; + strcpy (serv_addr->sun_path, name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen (serv_addr->sun_path) + 1); + +#ifdef HAVE_W32_SYSTEM + rc = _w32_sock_bind (fd, (struct sockaddr*) serv_addr, len); + if ((rc == -1) && standard_socket) + { + remove (name); + rc = bind (fd, (struct sockaddr*) serv_addr, len); + } +#else + rc = bind (fd, (struct sockaddr*) serv_addr, len); + if ((rc == -1) && standard_socket && (errno == EADDRINUSE)) + { + remove (name); + rc = bind (fd, (struct sockaddr*) serv_addr, len); + } +#endif + if (rc == -1) + { + log_error ("error binding socket to `%s': %s\n", + serv_addr->sun_path, strerror (errno)); + close (fd); + exit (1); + } + + if (listen (fd, 5 ) == -1) + { + log_error ("listen() failed: %s\n", strerror (errno)); + close (fd); + exit (1); + } + + if (opt.verbose) + log_info ("listening on socket `%s'\n", socket_name); + + return fd; +} + + int main (int argc, char **argv ) { @@ -596,6 +708,12 @@ main (int argc, char **argv ) case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; + case oSSHSupport: + opt.ssh_support = 1; + opt.keep_tty = 1; + opt.keep_display = 1; + break; + default : pargs.err = configfp? 1:2; break; } } @@ -745,11 +863,10 @@ main (int argc, char **argv ) else { /* Regular server mode */ int fd; - int rc; + int fd_ssh; pid_t pid; - int len; struct sockaddr_un serv_addr; - char *p; + struct sockaddr_un serv_addr_ssh; /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default @@ -761,90 +878,26 @@ main (int argc, char **argv ) #endif /* Create the socket name . */ - if (standard_socket) - socket_name = make_filename (opt.homedir, "S.gpg-agent", NULL); + create_socket_name (&socket_name, standard_socket, &serv_addr, + "S.gpg-agent", "/tmp/gpg-XXXXXX/S.gpg-agent"); + if (opt.ssh_support) + create_socket_name (&socket_name_ssh, standard_socket, &serv_addr_ssh, + "S.gpg-agent.ssh", "/tmp/gpg-XXXXXX/S.gpg-agent.ssh"); + + fd = create_server_socket (&serv_addr, + standard_socket, socket_name); + if (opt.ssh_support) + fd_ssh = create_server_socket (&serv_addr_ssh, + standard_socket, socket_name_ssh); else - { - socket_name = xstrdup ("/tmp/gpg-XXXXXX/S.gpg-agent"); - p = strrchr (socket_name, '/'); - if (!p) - BUG (); - *p = 0;; - if (!mkdtemp(socket_name)) - { - log_error (_("can't create directory `%s': %s\n"), - socket_name, strerror(errno) ); - exit (1); - } - *p = '/'; - } - - if (strchr (socket_name, PATHSEP_C) ) - { - log_error ("`%s' are not allowed in the socket name\n", PATHSEP_S); - exit (1); - } - if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) - { - log_error ("name of socket too long\n"); - exit (1); - } - -#ifdef HAVE_W32_SYSTEM - fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0); -#else - fd = socket (AF_UNIX, SOCK_STREAM, 0); -#endif - if (fd == -1) - { - log_error ("can't create socket: %s\n", strerror(errno) ); - exit (1); - } - - memset (&serv_addr, 0, sizeof serv_addr); - serv_addr.sun_family = AF_UNIX; - strcpy (serv_addr.sun_path, socket_name); - len = (offsetof (struct sockaddr_un, sun_path) - + strlen(serv_addr.sun_path) + 1); - -#ifdef HAVE_W32_SYSTEM - rc = _w32_sock_bind (fd, (struct sockaddr*)&serv_addr, len); - if (rc == -1 && standard_socket) - { - remove (socket_name); - rc = bind (fd, (struct sockaddr*)&serv_addr, len); - } -#else - rc = bind (fd, (struct sockaddr*)&serv_addr, len); - if (rc == -1 && standard_socket && errno == EADDRINUSE) - { - remove (socket_name); - rc = bind (fd, (struct sockaddr*)&serv_addr, len); - } -#endif - if (rc == -1) - { - log_error ("error binding socket to `%s': %s\n", - serv_addr.sun_path, strerror (errno) ); - close (fd); - exit (1); - } - - if (listen (fd, 5 ) == -1) - { - log_error ("listen() failed: %s\n", strerror (errno)); - close (fd); - exit (1); - } - - if (opt.verbose) - log_info ("listening on socket `%s'\n", socket_name ); - + /* Make the compiler happy. */ + fd_ssh = -1; fflush (NULL); #ifdef HAVE_W32_SYSTEM pid = getpid (); printf ("set GPG_AGENT_INFO=%s;%lu;1\n", socket_name, (ulong)pid); + printf ("set GPG_AGENT_INFO=%s;%lu;1\n", socket_name, (ulong)pid); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) @@ -854,7 +907,7 @@ main (int argc, char **argv ) } else if (pid) { /* We are the parent */ - char *infostr; + char *infostr, *infostr_ssh_sock, *infostr_ssh_pid; close (fd); @@ -866,8 +919,29 @@ main (int argc, char **argv ) kill (pid, SIGTERM); exit (1); } + if (opt.ssh_support) + { + if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", + socket_name_ssh) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + if (asprintf (&infostr_ssh_pid, "SSH_AGENT_PID=%u", + pid) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + } + *socket_name = 0; /* don't let cleanup() remove the socket - the child should do this from now on */ + if (opt.ssh_support) + *socket_name_ssh = 0; + if (argc) { /* run the program given on the commandline */ if (putenv (infostr)) @@ -877,6 +951,20 @@ main (int argc, char **argv ) kill (pid, SIGTERM ); exit (1); } + if (putenv (infostr_ssh_sock)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + if (putenv (infostr_ssh_pid)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); @@ -890,12 +978,29 @@ main (int argc, char **argv ) { *strchr (infostr, '=') = ' '; printf ( "setenv %s\n", infostr); + if (opt.ssh_support) + { + *strchr (infostr_ssh_sock, '=') = ' '; + printf ( "setenv %s\n", infostr_ssh_sock); + *strchr (infostr_ssh_pid, '=') = ' '; + printf ( "setenv %s\n", infostr_ssh_pid); + } } else { printf ( "%s; export GPG_AGENT_INFO;\n", infostr); + if (opt.ssh_support) + { + printf ( "%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); + printf ( "%s; export SSH_AGENT_PID;\n", infostr_ssh_pid); + } } free (infostr); + if (opt.ssh_support) + { + free (infostr_ssh_sock); + free (infostr_ssh_pid); + } exit (0); } /*NEVER REACHED*/ @@ -949,7 +1054,7 @@ main (int argc, char **argv ) sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); #endif - handle_connections (fd); + handle_connections (fd, opt.ssh_support ? fd_ssh : -1); } else #endif /*!USE_GNU_PTH*/ @@ -1230,16 +1335,37 @@ start_connection_thread (void *arg) return NULL; } +static void * +start_connection_thread_ssh (void *arg) +{ + int fd = (int)arg; + + if (opt.verbose) + log_info ("ssh handler for fd %d started\n", fd); + + /* FIXME: Move this housekeeping into a ticker function. Calling it + for each connection should work but won't work anymore if our + cleints start to keep connections. */ + agent_trustlist_housekeeping (); + + start_command_handler_ssh (fd); + if (opt.verbose) + log_info ("ssh handler for fd %d terminated\n", fd); + + return NULL; +} static void -handle_connections (int listen_fd) +handle_connections (int listen_fd, int listen_fd_ssh) { pth_attr_t tattr; pth_event_t ev; sigset_t sigs; int signo; struct sockaddr_un paddr; - socklen_t plen = sizeof( paddr ); + socklen_t plen = sizeof ( paddr ); + fd_set fdset, read_fdset; + int ret; int fd; tattr = pth_attr_new(); @@ -1259,6 +1385,11 @@ handle_connections (int listen_fd) ev = NULL; #endif + FD_ZERO (&fdset); + FD_SET (listen_fd, &fdset); + if (listen_fd_ssh != -1) + FD_SET (listen_fd_ssh, &fdset); + for (;;) { if (shutdown_pending) @@ -1275,28 +1406,68 @@ handle_connections (int listen_fd) continue; } - fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev); - if (fd == -1) - { -#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ - if (pth_event_status (ev) == PTH_STATUS_OCCURRED) -#else - if (pth_event_occurred (ev)) -#endif - { - handle_signal (signo); - continue; - } - log_error ("accept failed: %s - waiting 1s\n", strerror (errno)); - pth_sleep(1); - continue; + read_fdset = fdset; + ret = pth_select (FD_SETSIZE, &read_fdset, NULL, NULL, NULL); + if (ret == -1) + { + log_error ("pth_select failed: %s - waiting 1s\n", + strerror (errno)); + pth_sleep (1); + continue; } + - if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) - { - log_error ("error spawning connection handler: %s\n", - strerror (errno) ); - close (fd); + if (FD_ISSET (listen_fd, &read_fdset)) + { + fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev); + if (fd == -1) + { +#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ + if (pth_event_status (ev) == PTH_STATUS_OCCURRED) +#else + if (pth_event_occurred (ev)) +#endif + { + handle_signal (signo); + continue; + } + log_error ("accept failed: %s - waiting 1s\n", strerror (errno)); + pth_sleep(1); + continue; + } + + if (!pth_spawn (tattr, start_connection_thread, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } + } + else if ((listen_fd_ssh != -1) && FD_ISSET (listen_fd_ssh, &read_fdset)) + { + fd = pth_accept_ev (listen_fd_ssh, (struct sockaddr *)&paddr, &plen, ev); + if (fd == -1) + { +#ifdef PTH_STATUS_OCCURRED /* This is Pth 2 */ + if (pth_event_status (ev) == PTH_STATUS_OCCURRED) +#else + if (pth_event_occurred (ev)) +#endif + { + handle_signal (signo); + continue; + } + log_error ("accept failed: %s - waiting 1s\n", strerror (errno)); + pth_sleep(1); + continue; + } + + if (!pth_spawn (tattr, start_connection_thread_ssh, (void*)fd)) + { + log_error ("error spawning connection handler: %s\n", + strerror (errno) ); + close (fd); + } } }