From 15664a8598bdd0428ccffcdfe624f751e263cce3 Mon Sep 17 00:00:00 2001 From: Moritz Schulte Date: Mon, 19 Jul 2004 15:54:11 +0000 Subject: [PATCH] 2004-07-19 Moritz Schulte * Makefile.am (gpg_agent_SOURCES): Adding: gpg-stream.c, gpg-stream.h, buffer.c, buffer.h, command-ssh.c. * pksign.c (agent_pksign_do): New function, based on code ripped out from agent_pksign. (agent_pksign): Use agent_pksign_do. * query.c (start_pinentry): Accept CTRL being NULL. * agent.h (start_command_handler_ssh): Declare function. (agent_pksign_do): Declare function. (opt): New member: ssh_support. * gpg-agent.c: Include . New configuration option: ssh-support. (socket_name_ssh): New variabel. (handle_connections): Additional argument: listen_fd_ssh. Accept connections on both sockets, call start_connection_thread_ssh for connections on listen_fd_ssh. (start_connection_thread_ssh): New function. (cleanup_do): New functions, basically old cleanup function. (cleanup): Call cleanup_do for socket_name and socket_name_ssh. (server_socket_create): New function ... (main): ... use it. (main): Generate environment entries for ssh. * command-ssh.c: New file, implementing the ssh-agent protocol. * gpg-stream.c, gpg-stream.h, buffer.c, buffer.h: Merged Libgpg-stream. --- agent/ChangeLog | 32 + agent/Makefile.am | 4 +- agent/agent.h | 6 + agent/buffer.c | 413 +++++++++++ agent/buffer.h | 102 +++ agent/command-ssh.c | 1694 +++++++++++++++++++++++++++++++++++++++++++ agent/gpg-agent.c | 374 +++++++--- agent/gpg-stream.c | 914 +++++++++++++++++++++++ agent/gpg-stream.h | 148 ++++ agent/pksign.c | 87 ++- agent/query.c | 10 +- 11 files changed, 3643 insertions(+), 141 deletions(-) create mode 100644 agent/buffer.c create mode 100644 agent/buffer.h create mode 100644 agent/command-ssh.c create mode 100644 agent/gpg-stream.c create mode 100644 agent/gpg-stream.h diff --git a/agent/ChangeLog b/agent/ChangeLog index 929959d85..7293e23ce 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,35 @@ +2004-07-19 Moritz Schulte + + * Makefile.am (gpg_agent_SOURCES): Adding: gpg-stream.c, + gpg-stream.h, buffer.c, buffer.h, command-ssh.c. + + * pksign.c (agent_pksign_do): New function, based on code ripped + out from agent_pksign. + (agent_pksign): Use agent_pksign_do. + + * query.c (start_pinentry): Accept CTRL being NULL. + + * agent.h (start_command_handler_ssh): Declare function. + (agent_pksign_do): Declare function. + (opt): New member: ssh_support. + + * gpg-agent.c: Include . + New configuration option: ssh-support. + (socket_name_ssh): New variabel. + (handle_connections): Additional argument: listen_fd_ssh. Accept + connections on both sockets, call start_connection_thread_ssh for + connections on listen_fd_ssh. + (start_connection_thread_ssh): New function. + (cleanup_do): New functions, basically old cleanup function. + (cleanup): Call cleanup_do for socket_name and socket_name_ssh. + (server_socket_create): New function ... + (main): ... use it. + (main): Generate environment entries for ssh. + + * command-ssh.c: New file, implementing the ssh-agent protocol. + * gpg-stream.c, gpg-stream.h, buffer.c, buffer.h: Merged + Libgpg-stream. + 2004-06-20 Moritz Schulte * gpg-agent.c: Include (build fix for BSD). diff --git a/agent/Makefile.am b/agent/Makefile.am index e83311fb4..406b8f33c 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -29,7 +29,9 @@ AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(PTH_CFLAGS) gpg_agent_SOURCES = \ gpg-agent.c agent.h \ - command.c \ + gpg-stream.c gpg-stream.h gpg-stream-config.h \ + buffer.c buffer.h \ + command.c command-ssh.c \ query.c \ cache.c \ trans.c \ diff --git a/agent/agent.h b/agent/agent.h index 6b7821e30..d307a6d6c 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -61,6 +61,7 @@ struct { int allow_mark_trusted; 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; @@ -130,6 +131,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); @@ -158,6 +162,8 @@ void agent_unlock_cache_entry (void **cache_id); /*-- pksign.c --*/ +int agent_pksign_do (CTRL ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, int ignore_cache); int agent_pksign (ctrl_t ctrl, const char *desc_text, FILE *outfp, int ignore_cache); diff --git a/agent/buffer.c b/agent/buffer.c new file mode 100644 index 000000000..bd441060e --- /dev/null +++ b/agent/buffer.c @@ -0,0 +1,413 @@ +/* buffer.c - Buffer management layer + Copyright (C) 2004 g10 Code GmbH + + This file is part of libgpg-stream. + + libgpg-stream 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. + + libgpg-stream 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libgpg-stream; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#include +#include +#include +#include +#include + +#include + +#include "buffer.h" + + + +/* Buffer context. */ +struct buffer +{ + void *handle; /* Handle, passed to callbacks. */ + buffer_functions_t functions; /* Callback functions. */ + unsigned int flags; /* General flags. */ + struct buffer_in + { + char *container; /* Container holding data. */ + size_t container_size; /* Size of CONTAINER. */ + size_t data_size; /* Size of data in CONTAINER. */ + off_t data_offset; /* Offset inside of CONTAINER. */ + } buffer_in; + struct buffer_out + { + char *container; /* Container holding data. */ + size_t container_size; /* Size of CONTAINER. */ + size_t data_size; /* Size of data in CONTAINER. */ + off_t data_offset; /* Offset inside of CONTAINER. */ + size_t data_flushed; /* Amount of data already flushed. */ + } buffer_out; +}; + + + +/* Buffer contains unflushed data. */ +#define BUFFER_FLAG_DIRTY (1 << 0) + + + +/* Fill buffer. */ +static gpg_error_t +buffer_fill_do (buffer_t buffer) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + size_t bytes_read = 0; + + if (! buffer->functions.func_read) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else + { + buffer_func_read_t func_read = buffer->functions.func_read; + + err = (*func_read) (buffer->handle, + buffer->buffer_in.container, + buffer->buffer_in.container_size, + &bytes_read); + } + + buffer->buffer_in.data_offset = 0; + buffer->buffer_in.data_size = bytes_read; + + return err; +} + +/* Empty buffer input. */ +static gpg_error_t +buffer_empty (buffer_t buffer) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + buffer->buffer_in.data_size = 0; + buffer->buffer_in.data_offset = 0; + + return err; +} + +/* Flush data contained in buffer. */ +static gpg_error_t +buffer_flush_do (buffer_t buffer) +{ + buffer_func_write_t func_write = buffer->functions.func_write; + gpg_error_t err = GPG_ERR_NO_ERROR; + size_t bytes_written = 0; + + if (! func_write) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else if (buffer->flags & BUFFER_FLAG_DIRTY) + while ((buffer->buffer_out.data_size + - buffer->buffer_out.data_flushed) && (! err)) + { + + err = (*func_write) (buffer->handle, + buffer->buffer_out.container + + buffer->buffer_out.data_flushed, + buffer->buffer_out.data_size + - buffer->buffer_out.data_flushed, + &bytes_written); + if (! err) + { + buffer->buffer_out.data_size = 0; + buffer->buffer_out.data_offset = 0; + buffer->buffer_out.data_flushed = 0; + buffer->flags &= ~BUFFER_FLAG_DIRTY; + } + else + buffer->buffer_out.data_flushed += bytes_written; + } + + return err; +} + +static gpg_error_t +buffer_stat_do (buffer_t buffer, + size_t *size) +{ + buffer_func_stat_t func_stat = buffer->functions.func_stat; + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = (*func_stat) (buffer->handle, size); + + return err; +} + + + +/* Read from a buffer. */ +gpg_error_t +buffer_read (buffer_t buffer, + char *data, + size_t bytes_to_read, + size_t *bytes_read) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + size_t data_read = 0; + size_t data_to_copy = 0; + + while ((bytes_to_read - data_read) && (! err)) + { + if (buffer->buffer_in.data_offset == buffer->buffer_in.data_size) + { + /* Nothing more to read in current container, try to + fill container with new data. */ + err = buffer_fill_do (buffer); + if (! err) + if (! buffer->buffer_in.data_size) + /* Filling did not result in any data read. */ + break; + } + + if (! err) + { + if ((bytes_to_read + - data_read) > (buffer->buffer_in.data_size + - buffer->buffer_in.data_offset)) + data_to_copy = (buffer->buffer_in.data_size + - buffer->buffer_in.data_offset); + else + data_to_copy = bytes_to_read - data_read; + + memcpy (data + data_read, + buffer->buffer_in.container + buffer->buffer_in.data_offset, + data_to_copy); + buffer->buffer_in.data_offset += data_to_copy; + data_read += data_to_copy; + } + } + + if (bytes_read) + *bytes_read = data_read; + + return err; +} + +/* Write to a buffer. */ +gpg_error_t +buffer_write (buffer_t buffer, + const char *data, + size_t bytes_to_write, + size_t *bytes_written) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + size_t data_written = 0; + size_t data_to_copy = 0; + + while ((bytes_to_write - data_written) && (! err)) + { + if (buffer->buffer_out.data_offset == buffer->buffer_out.container_size) + /* Container full, flush buffer. */ + err = buffer_flush_do (buffer); + + if (! err) + { + if ((bytes_to_write + - data_written) > (buffer->buffer_out.container_size + - buffer->buffer_out.data_offset)) + data_to_copy = (buffer->buffer_out.container_size + - buffer->buffer_out.data_offset); + else + data_to_copy = bytes_to_write - data_written; + + memcpy (buffer->buffer_out.container + + buffer->buffer_out.data_offset, + data + data_written, + data_to_copy); + if ((buffer->buffer_out.data_offset + + data_to_copy) > buffer->buffer_out.data_size) + buffer->buffer_out.data_size = (buffer->buffer_out.data_offset + + data_to_copy); + buffer->buffer_out.data_offset += data_to_copy; + data_written += data_to_copy; + + if (data_written) + if (! (buffer->flags & BUFFER_FLAG_DIRTY)) + buffer->flags |= BUFFER_FLAG_DIRTY; + } + } + + if (bytes_written) + *bytes_written = data_written; + + return err; +} + +/* Seek in a buffer. */ +gpg_error_t +buffer_seek (buffer_t buffer, + off_t offset, + int whence) +{ + buffer_func_seek_t func_seek = buffer->functions.func_seek; + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (! func_seek) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else + { + if (buffer->flags & BUFFER_FLAG_DIRTY) + /* Flush data first in order to prevent flushing it to the + wrong offset. */ + err = buffer_flush_do (buffer); + + if (! err) + err = (*func_seek) (buffer->handle, offset, whence); + + if (! err) + err = buffer_empty (buffer); + } + + return err; +} + +/* Return the unread data contained in a buffer. */ +gpg_error_t +buffer_peek (buffer_t buffer, + char **data, + size_t *data_size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (buffer->buffer_in.data_offset == buffer->buffer_in.data_size) + /* Refill container. */ + err = buffer_fill_do (buffer); + + if (! err) + { + if (data) + *data = buffer->buffer_in.container + buffer->buffer_in.data_offset; + if (data_size) + *data_size = buffer->buffer_in.data_size - buffer->buffer_in.data_offset; + } + + return err; +} + +/* Skip SIZE bytes of input data contained in buffer. */ +gpg_error_t +buffer_skip (buffer_t buffer, + size_t size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (buffer->buffer_in.data_offset + size > buffer->buffer_in.data_size) + err = gpg_error (GPG_ERR_INV_ARG); + else + buffer->buffer_in.data_offset += size; + + return err; +} + + + +/* Create a new buffer. */ +gpg_error_t +buffer_create (buffer_t *buffer, + void *handle, + buffer_functions_t functions) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + /* Allocate memory, initialize. */ + + buffer_t buffer_new = NULL; + char *container_in_new = NULL; + char *container_out_new = NULL; + + buffer_new = malloc (sizeof (*buffer_new)); + if (! buffer_new) + err = gpg_error_from_errno (errno); + + if (! err) + { + container_in_new = malloc (BUFFER_BLOCK_SIZE); + if (! container_in_new) + err = gpg_error_from_errno (errno); + } + if (! err) + { + container_out_new = malloc (BUFFER_BLOCK_SIZE); + if (! container_out_new) + err = gpg_error_from_errno (errno); + } + + if (! err) + { + buffer_new->handle = handle; + buffer_new->flags = 0; + buffer_new->functions = functions; + buffer_new->buffer_in.container = container_in_new; + buffer_new->buffer_in.container_size = BUFFER_BLOCK_SIZE; + buffer_new->buffer_in.data_size = 0; + buffer_new->buffer_in.data_offset = 0; + buffer_new->buffer_out.container = container_out_new; + buffer_new->buffer_out.container_size = BUFFER_BLOCK_SIZE; + buffer_new->buffer_out.data_size = 0; + buffer_new->buffer_out.data_offset = 0; + buffer_new->buffer_out.data_flushed = 0; + *buffer = buffer_new; + } + else + { + if (container_in_new) + free (container_in_new); + if (container_out_new) + free (container_out_new); + if (buffer_new) + free (buffer_new); + } + + return err; +} + +/* Destroy a buffer. */ +gpg_error_t +buffer_destroy (buffer_t buffer) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (buffer) + { + err = buffer_flush_do (buffer); + free (buffer->buffer_in.container); + free (buffer->buffer_out.container); + free (buffer); + } + + return err; +} + +/* Write out unwritten data contained in buffer. */ +gpg_error_t +buffer_flush (buffer_t buffer) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_flush_do (buffer); + + return err; +} + +/* Stat buffer. */ +gpg_error_t +buffer_stat (buffer_t buffer, + size_t *size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_stat_do (buffer, size); + + return err; +} diff --git a/agent/buffer.h b/agent/buffer.h new file mode 100644 index 000000000..121ad0a31 --- /dev/null +++ b/agent/buffer.h @@ -0,0 +1,102 @@ +/* buffer.h - Buffer management layer + Copyright (C) 2004 g10 Code GmbH + + This file is part of libgpg-stream. + + libgpg-stream 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. + + libgpg-stream 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libgpg-stream; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#ifndef BUFFER_H +#define BUFFER_H + +#include +#include + +#include + + + +#define BUFFER_BLOCK_SIZE 1024 + + + +typedef struct buffer *buffer_t; + +/* Callbacks, necessary for filling/flushing/seeking. */ +typedef gpg_error_t (*buffer_func_read_t) (void *handle, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read); +typedef gpg_error_t (*buffer_func_write_t) (void *handle, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written); +typedef gpg_error_t (*buffer_func_seek_t) (void *handle, + off_t offset, + int whence); + +typedef gpg_error_t (*buffer_func_stat_t) (void *handle, + size_t *size); + +typedef struct buffer_functions +{ + buffer_func_read_t func_read; /* Read callback. */ + buffer_func_write_t func_write; /* Write callback. */ + buffer_func_seek_t func_seek; /* Seek callback. */ + buffer_func_stat_t func_stat; /* Stat callback. */ +} buffer_functions_t; + +/* Create a new buffer. */ +gpg_error_t buffer_create (buffer_t *buffer, + void *handle, + buffer_functions_t functions); + +/* Destroy a buffer. */ +gpg_error_t buffer_destroy (buffer_t buffer); + +/* Read from a buffer. */ +gpg_error_t buffer_read (buffer_t buffer, + char *data, + size_t bytes_to_read, + size_t *bytes_read); + +/* Write to a buffer. */ +gpg_error_t buffer_write (buffer_t buffer, + const char *data, + size_t bytes_to_write, + size_t *bytes_written); + +/* Seek in a buffer. */ +gpg_error_t buffer_seek (buffer_t buffer, + off_t offset, + int whence); + +/* Return the unread data contained in a buffer. */ +gpg_error_t buffer_peek (buffer_t buffer, + char **data, + size_t *data_size); + +/* Skip SIZE bytes of input data contained in buffer. */ +gpg_error_t buffer_skip (buffer_t buffer, + size_t size); + +/* Write out unwritten data contained in buffer. */ +gpg_error_t buffer_flush (buffer_t buffer); + +/* Stat buffer. */ +gpg_error_t buffer_stat (buffer_t buffer, + size_t *size); + +#endif diff --git a/agent/command-ssh.c b/agent/command-ssh.c new file mode 100644 index 000000000..cc8144e22 --- /dev/null +++ b/agent/command-ssh.c @@ -0,0 +1,1694 @@ +/* command-ssh.c - gpg-agent's ssh-agent emulation + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "agent.h" + +#include + +#include "gpg-stream.h" + + + +/* Request types. */ +#define SSH_REQUEST_REQUEST_IDENTITIES 11 +#define SSH_REQUEST_SIGN_REQUEST 13 +#define SSH_REQUEST_ADD_IDENTITY 17 +#define SSH_REQUEST_REMOVE_IDENTITY 18 +#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 +#define SSH_REQUEST_LOCK 22 +#define SSH_REQUEST_UNLOCK 23 +#define SSH_REQUEST_ADD_ID_CONSTRAINED 25 + +/* Options. */ +#define SSH_OPT_CONSTRAIN_LIFETIME 1 +#define SSH_OPT_CONSTRAIN_CONFIRM 2 + +/* Response types. */ +#define SSH_RESPONSE_SUCCESS 6 +#define SSH_RESPONSE_FAILURE 5 +#define SSH_RESPONSE_IDENTITIES_ANSWER 12 +#define SSH_RESPONSE_SIGN_RESPONSE 14 + + + +/* Basic types. */ + +/* A "byte". */ +typedef unsigned char byte_t; + +/* A "mpint". */ +typedef gcry_mpi_t mpint_t; + + + +/* SSH specific types. */ + +typedef byte_t ssh_request_type_t; +typedef byte_t ssh_response_type_t; + +/* A "Packet header"; part of a Request/Response. */ +typedef struct ssh_packet_header +{ + uint32_t length; + byte_t type; +} ssh_packet_header_t; + +/* A "Key type". */ +typedef enum ssh_key_type + { + SSH_KEY_TYPE_NONE, + SSH_KEY_TYPE_RSA, + } ssh_key_type_t; + +/* Type used for associating Key types with their string + representation. */ +typedef struct ssh_key_type_spec +{ + ssh_key_type_t type; + const char *name; +} ssh_key_type_spec_t; + +/* Secret RSA key material. */ +typedef struct ssh_key_secret_rsa +{ + mpint_t n; + mpint_t e; + mpint_t d; + mpint_t iqmp; + mpint_t p; + mpint_t q; +} ssh_key_secret_rsa_t; + +/* Public RSA key material. */ +typedef struct ssh_key_public_rsa +{ + mpint_t e; + mpint_t n; +} ssh_key_public_rsa_t; + +/* A secret key. */ +typedef struct ssh_key_secret +{ + ssh_key_type_t type; + union + { + ssh_key_secret_rsa_t rsa; + } material; +} ssh_key_secret_t; + +/* A public key. */ +typedef struct ssh_key_public +{ + ssh_key_type_t type; + union + { + ssh_key_public_rsa_t rsa; + } material; +} ssh_key_public_t; + +typedef void (*ssh_request_handler_t) (ctrl_t ctrl, + gpg_stream_t request, + gpg_stream_t response); + +typedef struct ssh_request_spec +{ + ssh_request_type_t type; + ssh_request_handler_t handler; +} ssh_request_spec_t; + + + +/* Table associating numeric key types with their string + representation. */ +static ssh_key_type_spec_t ssh_key_types[] = + { + { SSH_KEY_TYPE_RSA, "ssh-rsa" } + }; + +static uint32_t lifetime_default; + + + +/* Primitive I/O functions. */ + +static gpg_err_code_t +gpg_stream_copy (gpg_stream_t dest, gpg_stream_t src) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char buffer[STREAM_BLOCK_SIZE]; + size_t bytes_read = 0; + + while (1) + { + err = gpg_stream_read (src, buffer, sizeof (buffer), &bytes_read); + if (err || (! bytes_read)) + break; + + err = gpg_stream_write (dest, buffer, bytes_read, NULL); + if (err) + break; + } + + return err; +} + +static gpg_err_code_t +gpg_stream_read_byte (gpg_stream_t stream, byte_t *b) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char buffer[1]; + size_t bytes_read = 0; + + err = gpg_stream_read (stream, buffer, sizeof (buffer), &bytes_read); + if ((! err) && (bytes_read != sizeof (buffer))) + err = GPG_ERR_EOF; + + if (! err) + *b = buffer[0]; + + return err; +} + +static gpg_err_code_t +gpg_stream_write_byte (gpg_stream_t stream, byte_t b) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_write (stream, &b, sizeof (b), NULL); + + return err; +} + +static gpg_err_code_t +gpg_stream_read_uint32 (gpg_stream_t stream, uint32_t *uint32) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char buffer[4] = { 0 }; + size_t bytes_read = 0; + uint32_t n = 0; + + err = gpg_stream_read (stream, buffer, sizeof (buffer), &bytes_read); + if ((! err) && (bytes_read != sizeof (buffer))) + err = GPG_ERR_EOF; + + if (! err) + { + n = (0 + | ((uint32_t) (buffer[0] << 24)) + | ((uint32_t) (buffer[1] << 16)) + | ((uint32_t) (buffer[2] << 8)) + | ((uint32_t) (buffer[3] << 0))); + *uint32 = n; + } + + return err; +} + +static gpg_err_code_t +gpg_stream_write_uint32 (gpg_stream_t stream, uint32_t uint32) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char buffer[4] = { 0 }; + + buffer[0] = uint32 >> 24; + buffer[1] = uint32 >> 16; + buffer[2] = uint32 >> 8; + buffer[3] = uint32 >> 0; + + err = gpg_stream_write (stream, buffer, sizeof (buffer), NULL); + + return err; +} + +static gpg_err_code_t +gpg_stream_read_string (gpg_stream_t stream, + unsigned char **string, uint32_t *string_size) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *buffer = NULL; + size_t bytes_read = 0; + uint32_t length = 0; + + /* Read string length. */ + err = gpg_stream_read_uint32 (stream, &length); + if (err) + goto out; + + /* Allocate space. */ + buffer = malloc (length + 1); + if (! buffer) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + /* Read data. */ + err = gpg_stream_read (stream, buffer, length, &bytes_read); + if ((! err) && (bytes_read != length)) + err = GPG_ERR_EOF; + if (err) + goto out; + + /* Finalize string object. */ + buffer[length] = 0; + + out: + + if (! err) + { + *string = buffer; + if (string_size) + *string_size = length; + } + else + if (buffer) + free (buffer); + + return err; +} + +static gpg_err_code_t +gpg_stream_write_string (gpg_stream_t stream, + const unsigned char *string, uint32_t string_n) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_write_uint32 (stream, string_n); + if (err) + goto out; + + err = gpg_stream_write (stream, string, string_n, NULL); + if (err) + goto out; + + out: + + return err; +} + +static gpg_err_code_t +gpg_stream_write_cstring (gpg_stream_t stream, char *string) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_write_string (stream, (char *) string, strlen (string)); + + return err; +} + +static gpg_err_code_t +gpg_stream_read_mpint (gpg_stream_t stream, mpint_t *mpint, + unsigned int mpi_type) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *mpi_data = NULL; + uint32_t mpi_data_size = 0; + gcry_mpi_t mpi = NULL; + + if (! mpi_type) + mpi_type = GCRYMPI_FMT_STD; + + err = gpg_stream_read_string (stream, &mpi_data, &mpi_data_size); + if (err) + goto out; + + err = gcry_mpi_scan (&mpi, mpi_type, mpi_data, mpi_data_size, NULL); + if (err) + goto out; + + out: + + free (mpi_data); + + if (! err) + *mpint = mpi; + + return err; +} + +static gpg_err_code_t +gpg_stream_write_mpint (gpg_stream_t stream, mpint_t mpint, + unsigned int mpi_type) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *mpi_buffer = NULL; + size_t mpi_buffer_n = 0; + + if (! mpi_type) + mpi_type = GCRYMPI_FMT_STD; + + err = gcry_mpi_aprint (mpi_type, &mpi_buffer, &mpi_buffer_n, mpint); + if (err) + goto out; + + err = gpg_stream_write_string (stream, mpi_buffer, mpi_buffer_n); + if (err) + goto out; + + out: + + free (mpi_buffer); + + return err; +} + +static gpg_err_code_t +gpg_stream_read_file (const char *filename, + unsigned char **buffer, size_t *buffer_n) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *buffer_new = NULL; + size_t buffer_new_n = 0; + gpg_stream_t stream = NULL; + size_t bytes_read = 0; + + err = gpg_stream_create_file (&stream, filename, GPG_STREAM_FLAG_READ); + if (err) + goto out; + + err = gpg_stream_stat (stream, &buffer_new_n); + if (err) + goto out; + + buffer_new = malloc (buffer_new_n); + if (! buffer_new) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + err = gpg_stream_read (stream, buffer_new, buffer_new_n, &bytes_read); + if ((! err) && (bytes_read != buffer_new_n)) + err = GPG_ERR_INTERNAL; /* FIXME? */ + if (err) + goto out; + + out: + + gpg_stream_destroy (stream); + + if (! err) + { + *buffer = buffer_new; + *buffer_n = buffer_new_n; + } + else + { + free (buffer_new); + } + + return err; +} + + + +/* Key I/O. */ + +static gpg_err_code_t +ssh_key_type_lookup (const char *key_type_identifier, ssh_key_type_t *key_type) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned int i = 0; + + for (i = 0; i < DIM (ssh_key_types); i++) + if (! strcmp (key_type_identifier, ssh_key_types[i].name)) + break; + + if (i == DIM (ssh_key_types)) + err = GPG_ERR_NOT_FOUND; + else + *key_type = ssh_key_types[i].type; + + return err; +} + +static gpg_err_code_t +ssh_receive_key_secret (gpg_stream_t stream, ssh_key_secret_t *key_secret) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + ssh_key_secret_t key = { 0 }; + unsigned char *key_type = NULL; + + err = gpg_stream_read_string (stream, &key_type, NULL); + if (err) + goto out; + + err = ssh_key_type_lookup (key_type, &key.type); + if (err) + goto out; + + switch (key.type) + { + case SSH_KEY_TYPE_RSA: + err = gpg_stream_read_mpint (stream, &key.material.rsa.n, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.e, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.d, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.iqmp, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.p, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.q, 0); + if (err) + break; + + if (gcry_mpi_cmp (key.material.rsa.p, key.material.rsa.q)) + { + /* P shall be smaller then Q! */ + gcry_mpi_t tmp = NULL; + + tmp = key.material.rsa.p; + key.material.rsa.p = key.material.rsa.q; + key.material.rsa.q = tmp; + } + + break; + + case SSH_KEY_TYPE_NONE: + default: + err = GPG_ERR_INTERNAL; /* fixme: key type unsupported. */ + break; + } + if (err) + goto out; + + out: + + free (key_type); + + if (! err) + *key_secret = key; + else + { + gcry_mpi_release (key.material.rsa.n); + gcry_mpi_release (key.material.rsa.e); + gcry_mpi_release (key.material.rsa.d); + gcry_mpi_release (key.material.rsa.iqmp); + gcry_mpi_release (key.material.rsa.p); + gcry_mpi_release (key.material.rsa.q); + } + + return err; +} + +static gpg_err_code_t +ssh_send_key_public (gpg_stream_t stream, ssh_key_public_t *key_public) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + switch (key_public->type) + { + case SSH_KEY_TYPE_RSA: + { + err = gpg_stream_write_cstring (stream, "ssh-rsa"); + if (err) + goto out; + err = gpg_stream_write_mpint (stream, key_public->material.rsa.e, 0); + if (err) + goto out; + err = gpg_stream_write_mpint (stream, key_public->material.rsa.n, 0); + if (err) + goto out; + + break; + } + + case SSH_KEY_TYPE_NONE: + default: + err = GPG_ERR_INTERNAL; /* FIXME */ + } + + out: + + return err; +} + +static gpg_err_code_t +ssh_receive_key_public (gpg_stream_t stream, ssh_key_public_t *key_public) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + ssh_key_public_t key = { 0 }; + unsigned char *key_type = NULL; + + err = gpg_stream_read_string (stream, &key_type, NULL); + if (err) + goto out; + + err = ssh_key_type_lookup (key_type, &key.type); + if (err) + goto out; + + switch (key.type) + { + case SSH_KEY_TYPE_RSA: + { + err = gpg_stream_read_mpint (stream, &key.material.rsa.e, 0); + if (err) + break; + err = gpg_stream_read_mpint (stream, &key.material.rsa.n, 0); + if (err) + break; + break; + } + + case SSH_KEY_TYPE_NONE: + err = GPG_ERR_INTERNAL; /* fixme: key type unsupported. */ + break; + } + + if (err) + goto out; + + out: + + free (key_type); + + if (! err) + *key_public = key; + else + { + switch (key.type) + { + case SSH_KEY_TYPE_RSA: + gcry_mpi_release (key.material.rsa.e); + gcry_mpi_release (key.material.rsa.n); + break; + + case SSH_KEY_TYPE_NONE: + break; + } + } + + return err; +} + +static gpg_err_code_t +ssh_extract_key_public_from_blob (unsigned char *blob, size_t blob_size, + ssh_key_public_t *key_public) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gpg_stream_t blob_stream = NULL; + + err = gpg_stream_create (&blob_stream, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + goto out; + + err = gpg_stream_write (blob_stream, blob, blob_size, NULL); + if (err) + goto out; + + err = gpg_stream_flush (blob_stream); + if (err) + goto out; + + err = gpg_stream_seek (blob_stream, 0, SEEK_SET); + if (err) + goto out; + + err = ssh_receive_key_public (blob_stream, key_public); + if (err) + goto out; + + out: + + gpg_stream_destroy (blob_stream); + + return err; +} + +static gpg_err_code_t +ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, + ssh_key_public_t *key_public) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gpg_stream_t blob_stream = NULL; + unsigned char *blob_new = NULL; + size_t blob_new_size = 0; + size_t bytes_read = 0; + + err = gpg_stream_create (&blob_stream, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + goto out; + + err = ssh_send_key_public (blob_stream, key_public); + if (err) + goto out; + + err = gpg_stream_flush (blob_stream); + if (err) + goto out; + + err = gpg_stream_seek (blob_stream, 0, SEEK_SET); + if (err) + goto out; + + err = gpg_stream_stat (blob_stream, &blob_new_size); + if (err) + goto out; + + blob_new = malloc (blob_new_size); + if (! blob_new) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + err = gpg_stream_read (blob_stream, blob_new, blob_new_size, &bytes_read); + if ((! err) && (bytes_read != blob_new_size)) + err = GPG_ERR_INTERNAL; /* FIXME? */ + if (err) + goto out; + + out: + + gpg_stream_destroy (blob_stream); + + if (! err) + { + *blob = blob_new; + *blob_size = blob_new_size; + } + else + { + if (blob_new) + free (blob_new); + } + + return err; +} + + + +static gpg_err_code_t +ssh_key_grip (ssh_key_public_t *public, ssh_key_secret_t *secret, + unsigned char *buffer) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *ret = NULL; + gcry_sexp_t sexp = NULL; + + switch (public ? public->type : secret->type) + { + case SSH_KEY_TYPE_RSA: + err = gcry_sexp_build (&sexp, NULL, + "(public-key (rsa (n %m) (e %m)))", + public + ? public->material.rsa.n + : secret->material.rsa.n, + public + ? public->material.rsa.e + : secret->material.rsa.n); + break; + + case SSH_KEY_TYPE_NONE: + abort (); + break; + } + if (err) + goto out; + + ret = gcry_pk_get_keygrip (sexp, buffer); + if (! ret) + { + err = GPG_ERR_INTERNAL; /* FIXME? */ + goto out; + } + + out: + + gcry_sexp_release (sexp); + + return err; +} + +static gpg_err_code_t +ssh_key_public_from_stored_key (unsigned char *buffer, size_t buffer_n, + ssh_key_public_t *key) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gcry_sexp_t key_stored = NULL; + gcry_sexp_t key_data = NULL; + gcry_sexp_t value = NULL; + const char *identifier = NULL; + size_t identifier_n = 0; + + err = gcry_sexp_new (&key_stored, buffer, buffer_n, 1); + if (err) + goto out; + + identifier = gcry_sexp_nth_data (key_stored, 0, &identifier_n); + if (! identifier) + { + err = GPG_ERR_INTERNAL; + goto out; + } + + if ((identifier_n == 21) + && (! strncmp (identifier, "protected-private-key", identifier_n))) + { + key_data = gcry_sexp_cadr (key_stored); + if (! key_data) + { + err = GPG_ERR_INTERNAL; + goto out; + } + identifier = gcry_sexp_nth_data (key_data, 0, &identifier_n); + if (! identifier) + { + err = GPG_ERR_INTERNAL; + goto out; + } + + if ((identifier_n == 3) + && (! (strncmp (identifier, "rsa", identifier_n)))) + { + gcry_mpi_t mpi_n = NULL; + gcry_mpi_t mpi_e = NULL; + + value = gcry_sexp_find_token (key_data, "n", 0); + if (! value) + err = GPG_ERR_INTERNAL; + else + mpi_n = gcry_sexp_nth_mpi (value, 1, GCRYMPI_FMT_STD); + + if (! err) + { + value = gcry_sexp_find_token (key_data, "e", 0); + if (! value) + err = GPG_ERR_INTERNAL; + else + mpi_e = gcry_sexp_nth_mpi (value, 1, GCRYMPI_FMT_STD); + } + + if (! err) + { + key->type = SSH_KEY_TYPE_RSA; + key->material.rsa.e = mpi_e; + key->material.rsa.n = mpi_n; + } + else + { + gcry_mpi_release (mpi_n); + gcry_mpi_release (mpi_e); + } + } + } + + out: + + gcry_sexp_release (key_stored); + gcry_sexp_release (key_data); + gcry_sexp_release (value); + + return err; +} + + + +/* Request handler. */ + +static void +ssh_handler_request_identities (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gpg_err_code_t ret = GPG_ERR_NO_ERROR; + struct dirent *dir_entry = NULL; + char *key_directory = NULL; + size_t key_directory_n = 0; + char *key_path = NULL; + unsigned char *key_blob = NULL; + size_t key_blob_n = 0; + unsigned char *buffer = NULL; + size_t buffer_n = 0; + uint32_t key_counter = 0; + gpg_stream_t key_blobs = NULL; + ssh_key_public_t key = { SSH_KEY_TYPE_NONE }; + DIR *dir = NULL; + + /* Prepare buffer stream. */ + + err = gpg_stream_create (&key_blobs, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + goto out; + + /* Open key directory. */ + key_directory = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL); + if (! key_directory) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + key_directory_n = strlen (key_directory); + + key_path = malloc (key_directory_n + 46); + if (! key_path) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + sprintf (key_path, "%s/", key_directory); + sprintf (key_path + key_directory_n + 41, ".key"); + + dir = opendir (key_directory); + if (! dir) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + /* Iterate over key files. */ + + while (1) + { + dir_entry = readdir (dir); + if (dir_entry) + { + if ((dir_entry->d_namlen == 44) + && (! strncmp (dir_entry->d_name + 40, ".key", 4))) + { + strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); + + /* Read file content. */ + err = gpg_stream_read_file (key_path, &buffer, &buffer_n); + if (err) + goto out; + + /* Convert it into a public key. */ + err = ssh_key_public_from_stored_key (buffer, buffer_n, &key); + free (buffer); + buffer = NULL; + if (err) + goto out; + + /* Convert public key to key blob. */ + err = ssh_convert_key_to_blob (&key_blob, &key_blob_n, &key); + if (err) + goto out; + + /* Add key blob to buffer stream. */ + err = gpg_stream_write_string (key_blobs, key_blob, key_blob_n); + free (key_blob); + key_blob = NULL; + if (err) + goto out; + err = gpg_stream_write_cstring (key_blobs, ""); + if (err) + goto out; + + key_counter++; + } + } + else + break; + } + + err = gpg_stream_flush (key_blobs); + if (err) + goto out; + err = gpg_stream_seek (key_blobs, 0, SEEK_SET); + if (err) + goto out; + + out: + + /* Send response. */ + + ret = gpg_stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); + + if (! ret) + ret = gpg_stream_write_uint32 (response, err ? 0 : key_counter); + + if ((! ret) && (! err)) + gpg_stream_copy (response, key_blobs); + + gpg_stream_destroy (key_blobs); + closedir (dir); + free (key_directory); + free (key_path); + free (key_blob); +} + +static gpg_err_code_t +data_hash (unsigned char *data, size_t data_n, + int md_algorithm, unsigned char *hash) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + gcry_md_hash_buffer (md_algorithm, hash, data, data_n); + + return err; +} + +static gpg_err_code_t +data_sign (CTRL ctrl, unsigned char **sig, size_t *sig_n) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gcry_sexp_t signature_sexp = NULL; + gpg_stream_t stream = NULL; + gcry_sexp_t sublist = NULL; + unsigned char *signature = NULL; + size_t signature_n = 0; + gcry_mpi_t sig_value = NULL; + unsigned char *sig_blob = NULL; + size_t sig_blob_n = 0; + size_t bytes_read = 0; + char description[] = + "Please provide the passphrase for key " + "`0123456789012345678901234567890123456789':"; + char key_grip[41]; + unsigned int i = 0; + + for (i = 0; i < 20; i++) + sprintf (&key_grip[i * 2], "%02X", ctrl->keygrip[i]); + strncpy (strchr (description, '0'), key_grip, 40); + + err = agent_pksign_do (ctrl, description, &signature_sexp, 0); + if (err) + goto out; + + err = gpg_stream_create (&stream, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + goto out; + + /* FIXME */ + switch (1 /* rsa */) + { + case 1: + sublist = gcry_sexp_find_token (signature_sexp, "s", 0); + if (! sublist) + { + err = GPG_ERR_INTERNAL; + break; + } + + sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); + if (! sig_value) + { + err = GPG_ERR_INTERNAL; + break; + } + + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, + &signature, &signature_n, sig_value); + if (err) + break; + + err = gpg_stream_write_cstring (stream, "ssh-rsa"); + if (err) + break; + + err = gpg_stream_write_string (stream, signature, signature_n); + if (err) + break; + } + if (err) + goto out; + + err = gpg_stream_flush (stream); + if (err) + goto out; + + err = gpg_stream_seek (stream, 0, SEEK_SET); + if (err) + goto out; + + err = gpg_stream_stat (stream, &sig_blob_n); + if (err) + goto out; + + sig_blob = malloc (sig_blob_n); + if (! sig_blob) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + err = gpg_stream_read (stream, sig_blob, sig_blob_n, &bytes_read); + if ((! err) && (sig_blob_n != bytes_read)) + err = GPG_ERR_INTERNAL; /* violation */ + if (err) + goto out; + + out: + + gpg_stream_destroy (stream); + gcry_mpi_release (sig_value); + free (signature); + + if (! err) + { + *sig = sig_blob; + *sig_n = sig_blob_n; + } + else + { + gcry_sexp_release (signature_sexp); + gcry_sexp_release (sublist); + free (sig_blob); + } + + return err; +} + +static void +ssh_handler_sign_request (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + ssh_key_public_t key = { SSH_KEY_TYPE_NONE }; + unsigned char hash[MAX_DIGEST_LEN] = { 0 }; + unsigned int hash_n = 0; + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char key_grip[20] = { 0 }; + unsigned char *key_blob = NULL; + uint32_t key_blob_size = 0; + unsigned char *sig = NULL; + unsigned char *data = NULL; + uint32_t data_size = 0; + size_t sig_n = 0; + uint32_t flags = 0; + + log_debug ("[ssh-agent] sign request\n"); + + /* Receive key. */ + + err = gpg_stream_read_string (request, &key_blob, &key_blob_size); + if (err) + goto out; + + err = ssh_extract_key_public_from_blob (key_blob, key_blob_size, &key); + if (err) + goto out; + + /* Receive data to sign. */ + + err = gpg_stream_read_string (request, &data, &data_size); + if (err) + goto out; + + /* Read flags, FIXME? */ + + err = gpg_stream_read_uint32 (request, &flags); + if (err) + goto out; + + /* Hash data. */ + + hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1); + if (! hash_n) + { + err = GPG_ERR_INTERNAL; /* FIXME? */ + goto out; + } + + err = data_hash (data, data_size, GCRY_MD_SHA1, hash); + if (err) + goto out; + + /* Calculate key grip. */ + + err = ssh_key_grip (&key, NULL, key_grip); + if (err) + goto out; + + /* Fill control structure. */ + + ctrl->digest.algo = GCRY_MD_SHA1; + memcpy (ctrl->digest.value, hash, hash_n); + ctrl->digest.valuelen = hash_n; + ctrl->have_keygrip = 1; + memcpy (ctrl->keygrip, key_grip, 20); + + /* Sign data. */ + + err = data_sign (ctrl, &sig, &sig_n); + if (err) + goto out; + + out: + + /* Done. */ + + if (! err) + { + err = gpg_stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); + if (! err) + err = gpg_stream_write_string (response, sig, sig_n); + } + else + gpg_stream_write_byte (response, SSH_RESPONSE_FAILURE); + + free (key_blob); + free (data); + free (sig); +} + +static gpg_err_code_t +ssh_key_to_sexp_buffer (ssh_key_secret_t *key, const char *passphrase, + unsigned char **buffer, size_t *buffer_n) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char *buffer_new = NULL; + unsigned int buffer_new_n = 0; + gcry_sexp_t key_sexp = NULL; + + err = gcry_sexp_build (&key_sexp, NULL, + "(private-key" + " (rsa" + " (n %m)" + " (e %m)" + " (d %m)" + " (p %m)" + " (q %m)" + " (u %m)))", + key->material.rsa.n, + key->material.rsa.e, + key->material.rsa.d, + key->material.rsa.p, + key->material.rsa.q, + key->material.rsa.iqmp); + if (err) + goto out; + + buffer_new_n = gcry_sexp_sprint (key_sexp, GCRYSEXP_FMT_CANON, NULL, 0); + buffer_new = malloc (buffer_new_n); + if (! buffer_new) + { + err = gpg_err_code_from_errno (errno); + goto out; + } + + gcry_sexp_sprint (key_sexp, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n); + + err = agent_protect (buffer_new, passphrase, buffer, buffer_n); + if (err) + goto out; + + out: + + if (key_sexp) + gcry_sexp_release (key_sexp); + if (buffer_new) + free (buffer_new); + + return err; +} + +static gpg_err_code_t +get_passphrase (char *description, size_t passphrase_n, char *passphrase) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + struct pin_entry_info_s *pi = NULL; + + pi = gcry_calloc_secure (1, sizeof (*pi) + passphrase_n + 1); + if (! pi) + { + err = gpg_error (GPG_ERR_ENOMEM); + goto out; + } + + pi->min_digits = 0; /* We want a real passphrase. */ + pi->max_digits = 8; + pi->max_tries = 1; + pi->failed_tries = 0; + pi->check_cb = NULL; + pi->check_cb_arg = NULL; + pi->cb_errtext = NULL; + pi->max_length = 100; + + err = agent_askpin (NULL, description, NULL, pi); + if (err) + goto out; + + memcpy (passphrase, pi->pin, passphrase_n); + + out: + + return err; +} + +static gpg_err_code_t +ssh_identity_register (ssh_key_secret_t *key, int ttl) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char key_grip[21] = { 0 }; + unsigned char *buffer = NULL; + unsigned int buffer_n = 0; + char passphrase[100] = { 0 }; + int ret = 0; + + log_debug ("[ssh-agent] registering identity `%s'\n", key_grip); + + err = ssh_key_grip (NULL, key, key_grip); + if (err) + goto out; + + ret = agent_key_available (key_grip); + if (! ret) + goto out; + + err = get_passphrase ("foo", sizeof (passphrase), passphrase); + if (err) + goto out; + + err = ssh_key_to_sexp_buffer (key, passphrase, &buffer, &buffer_n); + if (err) + goto out; + + err = agent_write_private_key (key_grip, buffer, buffer_n, 0); + if (err) + goto out; + + err = agent_put_cache (key_grip, passphrase, ttl); + if (err) + goto out; + + out: + + if (passphrase) + gcry_free (passphrase); + if (buffer) + free (buffer); + + return err; +} + +static gpg_err_code_t +ssh_identity_drop (ssh_key_public_t *key) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned char key_grip[21] = { 0 }; + + err = ssh_key_grip (key, NULL, key_grip); + if (err) + goto out; + + /* FIXME */ + + log_debug ("[ssh-agent] dropping identity `%s'\n", key_grip); + + out: + + return err; +} + +static void +ssh_handler_add_identity (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + ssh_key_secret_t key = { 0 }; + unsigned char *comment = NULL; + byte_t b = 0; + int confirm = 0; + int death = 0; + + log_debug ("[ssh-agent] add identity\n"); + + err = ssh_receive_key_secret (request, &key); + if (err) + goto out; + + err = gpg_stream_read_string (request, &comment, NULL); + if (err) + goto out; + + while (1) + { + err = gpg_stream_read_byte (request, &b); + if (err) + { + err = GPG_ERR_NO_ERROR; + break; + } + + switch (b) + { + case SSH_OPT_CONSTRAIN_LIFETIME: + { + uint32_t n = 0; + + err = gpg_stream_read_uint32 (request, &n); + if (! err) + death = time (NULL) + n; + break; + } + + case SSH_OPT_CONSTRAIN_CONFIRM: + { + confirm = 1; + break; + } + + default: + break; + } + } + if (err) + goto out; + + if (lifetime_default && (! death)) + death = time (NULL) + lifetime_default; + + /* FIXME: are constraints used correctly? */ + + err = ssh_identity_register (&key, death); + if (err) + goto out; + + out: + + free (comment); + + //ssh_key_destroy (key); FIXME + + gpg_stream_write_byte (response, + err + ? SSH_RESPONSE_FAILURE + : SSH_RESPONSE_SUCCESS); +} + +static void +ssh_handler_remove_identity (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + ssh_key_public_t key = { SSH_KEY_TYPE_NONE }; + unsigned char *key_blob = NULL; + uint32_t key_blob_size = 0; + + /* Receive key. */ + + log_debug ("[ssh-agent] remove identity\n"); + + err = gpg_stream_read_string (request, &key_blob, NULL); + if (err) + goto out; + + err = ssh_extract_key_public_from_blob (key_blob, key_blob_size, &key); + if (err) + goto out; + + err = ssh_identity_drop (&key); + if (err) + goto out; + + out: + + free (key_blob); + + err = gpg_stream_write_byte (response, + err + ? SSH_RESPONSE_FAILURE + : SSH_RESPONSE_SUCCESS); +} + +static gpg_err_code_t +ssh_identities_remove_all (void) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + log_debug ("[ssh-agent] remove all identities\n"); + + /* FIXME: shall we remove _all_ cache entries or only those + registered through the ssh emulation? */ + + return err; +} + +static void +ssh_handler_remove_all_identities (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = ssh_identities_remove_all (); + + gpg_stream_write_byte (response, + err + ? SSH_RESPONSE_FAILURE + : SSH_RESPONSE_SUCCESS); +} + +static gpg_err_code_t +ssh_lock (void) +{ + gpg_err_code_t err = GPG_ERR_NOT_IMPLEMENTED; + + log_debug ("[ssh-agent] lock\n"); + + return err; +} + +static gpg_err_code_t +ssh_unlock (void) +{ + gpg_err_code_t err = GPG_ERR_NOT_IMPLEMENTED; + + log_debug ("[ssh-agent] unlock\n"); + + return err; +} + +static void +ssh_handler_lock (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = ssh_lock (); + + gpg_stream_write_byte (response, + err + ? SSH_RESPONSE_FAILURE + : SSH_RESPONSE_SUCCESS); +} + +static void +ssh_handler_unlock (ctrl_t ctrl, + gpg_stream_t request, gpg_stream_t response) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + + err = ssh_unlock (); + + gpg_stream_write_byte (response, + err + ? SSH_RESPONSE_FAILURE + : SSH_RESPONSE_SUCCESS); +} + + + +/* Associating request types with the corresponding request + handlers. */ + +static ssh_request_spec_t request_specs[] = + { + { SSH_REQUEST_REQUEST_IDENTITIES, ssh_handler_request_identities }, + { SSH_REQUEST_SIGN_REQUEST, ssh_handler_sign_request }, + { SSH_REQUEST_ADD_IDENTITY, ssh_handler_add_identity }, + { SSH_REQUEST_ADD_ID_CONSTRAINED, ssh_handler_add_identity }, + { SSH_REQUEST_REMOVE_IDENTITY, ssh_handler_remove_identity }, + { SSH_REQUEST_REMOVE_ALL_IDENTITIES, ssh_handler_remove_all_identities }, + { SSH_REQUEST_LOCK, ssh_handler_lock }, + { SSH_REQUEST_UNLOCK, ssh_handler_unlock }, + }; + + + +static gpg_err_code_t +ssh_request_process (ctrl_t ctrl, gpg_stream_t request, gpg_stream_t response) +{ + ssh_request_type_t request_type = 0; + gpg_err_code_t err = GPG_ERR_NO_ERROR; + unsigned int i = 0; + + err = gpg_stream_read_byte (request, &request_type); + if (err) + goto out; + + log_debug ("[ssh-agent] request: %u\n", request_type); + + for (i = 0; i < DIM (request_specs); i++) + if (request_specs[i].type == request_type) + break; + if (i == DIM (request_specs)) + { + err = gpg_stream_write_byte (response, SSH_RESPONSE_FAILURE); + goto out; + } + + (*request_specs[i].handler) (ctrl, request, response); + + out: + + return err; +} + +static gpg_err_code_t +gpg_stream_eof_p (gpg_stream_t stream, unsigned int *eof) +{ + gpg_err_code_t err = GPG_ERR_NO_ERROR; + size_t bytes_left = 0; + + err = gpg_stream_peek (stream, NULL, &bytes_left); + if (! err) + *eof = !bytes_left; + + return err; +} + +void +start_command_handler_ssh (int sock_client) +{ + struct server_control_s ctrl = { NULL }; + gpg_err_code_t err = GPG_ERR_NO_ERROR; + gpg_stream_t stream_sock = NULL; + gpg_stream_t stream_request = NULL; + gpg_stream_t stream_response = NULL; + unsigned char *request = NULL; + uint32_t request_size = 0; + unsigned int eof = 0; + size_t size = 0; + + /* Setup control structure. */ + + log_debug ("[ssh-agent] Starting command handler\n"); + + ctrl.connection_fd = sock_client; + + err = gpg_stream_create_fd (&stream_sock, sock_client, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE); + if (err) + goto out; + + while (1) + { + err = gpg_stream_eof_p (stream_sock, &eof); + if (err || eof) + break; + + /* Create memory streams for request/response data. */ + stream_request = NULL; + err = gpg_stream_create (&stream_request, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + break; + stream_response = NULL; + err = gpg_stream_create (&stream_response, NULL, + GPG_STREAM_FLAG_READ | GPG_STREAM_FLAG_WRITE, + gpg_stream_functions_mem); + if (err) + break; + + /* Retrieve request length. */ + free (request); + request = NULL; + err = gpg_stream_read_string (stream_sock, &request, &request_size); + if (err) + break; + + log_debug ("[ssh-agent] Received request of length: %u\n", request_size); + + /* Write request data to request stream. */ + err = gpg_stream_write (stream_request, request, request_size, NULL); + if (err) + break; + + err = gpg_stream_flush (stream_request); + if (err) + break; + + err = gpg_stream_seek (stream_request, 0, SEEK_SET); + if (err) + break; + + /* Process request. */ + err = ssh_request_process (&ctrl, stream_request, stream_response); + if (err) + break; + err = gpg_stream_flush (stream_response); + if (err) + break; + + /* Figure out size of response data. */ + err = gpg_stream_seek (stream_response, 0, SEEK_SET); + if (err) + break; + err = gpg_stream_peek (stream_response, NULL, &size); + if (err) + break; + + /* Write response data to socket stream. */ + err = gpg_stream_write_uint32 (stream_sock, size); + if (err) + break; + err = gpg_stream_copy (stream_sock, stream_response); + if (err) + break; + + err = gpg_stream_flush (stream_sock); + if (err) + break; + + }; + if (err) + goto out; + + + out: + + gpg_stream_destroy (stream_sock); + gpg_stream_destroy (stream_request); + gpg_stream_destroy (stream_response); + free (request); + + log_debug ("[ssh-agent] Leaving ssh command handler: %s\n", gpg_strerror (err)); + + /* fixme: make sure that stream_destroy closes client socket. */ +} diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 69a28e78b..5f7b6f843 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -84,8 +85,8 @@ enum cmd_and_opt_values oAllowMarkTrusted, oKeepTTY, oKeepDISPLAY, - -aTest }; + aTest, + oSSHSupport }; @@ -131,6 +132,8 @@ static ARGPARSE_OPTS opts[] = { N_("do not use the PIN cache when signing")}, { oAllowMarkTrusted, "allow-mark-trusted", 0, N_("allow clients to mark keys as \"trusted\"")}, + { oSSHSupport, "ssh-support", 0, "Enable SSH-Agent emulation" }, + {0} }; @@ -149,6 +152,9 @@ static int maybe_setuid = 1; /* Name of the communication socket */ static char socket_name[128]; +/* Name of the communication socket used for ssh-agent-emulation. */ +static char socket_name_ssh[128]; + /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; @@ -169,7 +175,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; @@ -280,25 +286,30 @@ set_debug (void) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); } - + +static void +cleanup_do (char *name) +{ + char *p = NULL; + + remove (name); + p = strrchr (name, '/'); + if (p) + { + *p = 0; + rmdir (name); + *p = '/'; + } + *name = 0; +} static void cleanup (void) { if (*socket_name) - { - char *p; - - remove (socket_name); - p = strrchr (socket_name, '/'); - if (p) - { - *p = 0; - rmdir (socket_name); - *p = '/'; - } - *socket_name = 0; - } + cleanup_do (socket_name); + if (*socket_name_ssh) + cleanup_do (socket_name_ssh); } @@ -383,6 +394,76 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) return 1; /* handled */ } +static void +server_socket_create (int *sock, + const char *identifier, char *name, int name_size) +{ + struct sockaddr_un serv_addr; + char *p = NULL; + int fd = -1; + socklen_t len; + + *name = 0; + snprintf (name, name_size - 1, + "/tmp/gpg-XXXXXX/S.gpg-agent%s", + identifier ? identifier : ""); + name[name_size - 1] = 0; + + if (strchr (name, ':')) + { + log_error ("colons are not allowed in the socket name\n"); + exit (1); + } + + 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 (strlen (name) + 1 >= sizeof (serv_addr.sun_path)) + { + log_error ("name of socket too long\n"); + exit (1); + } + + fd = socket (AF_UNIX, SOCK_STREAM, 0); + 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); + + if (bind (fd, (struct sockaddr*) &serv_addr, len) == -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); + } + + *sock = fd; +} + int main (int argc, char **argv ) @@ -568,6 +649,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; } } @@ -702,12 +789,10 @@ main (int argc, char **argv ) else if (!is_daemon) ; else - { /* regular server mode */ - int fd; + { /* regular server mode */ + int fd = -1, fd_ssh = -1; pid_t pid; - int len; - struct sockaddr_un serv_addr; - char *p; + //char *p; /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default @@ -716,65 +801,18 @@ main (int argc, char **argv ) if (!opt.keep_display) unsetenv ("DISPLAY"); - *socket_name = 0; - snprintf (socket_name, DIM(socket_name)-1, - "/tmp/gpg-XXXXXX/S.gpg-agent"); - socket_name[DIM(socket_name)-1] = 0; - 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 = '/'; + server_socket_create (&fd, "", socket_name, sizeof (socket_name)); + if (opt.ssh_support) + server_socket_create (&fd_ssh, "-ssh", + socket_name_ssh, sizeof (socket_name_ssh)); - if (strchr (socket_name, ':') ) - { - log_error ("colons are not allowed in the socket name\n"); - exit (1); - } - if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) - { - log_error ("name of socket too long\n"); - exit (1); - } - - - fd = socket (AF_UNIX, SOCK_STREAM, 0); - 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); - - if (bind (fd, (struct sockaddr*)&serv_addr, len) == -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 ); - + { + log_info ("listening on socket `%s'\n", socket_name); + if (opt.ssh_support) + log_info ("listening on socket `%s'\n", socket_name_ssh); + } fflush (NULL); pid = fork (); @@ -785,9 +823,11 @@ main (int argc, char **argv ) } else if (pid) { /* We are the parent */ - char *infostr; + char *infostr, *infostr_ssh_sock, *infostr_ssh_pid; close (fd); + if (opt.ssh_support) + close (fd_ssh); /* create the info string: :: */ if (asprintf (&infostr, "GPG_AGENT_INFO=%s:%lu:1", @@ -797,8 +837,28 @@ 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 */ + *socket_name_ssh = 0; + if (argc) { /* run the program given on the commandline */ if (putenv (infostr)) @@ -808,6 +868,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); @@ -821,12 +895,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*/ @@ -877,7 +968,7 @@ main (int argc, char **argv ) sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); - handle_connections (fd); + handle_connections (fd, opt.ssh_support ? fd_ssh : -1); } else #endif /*!USE_GNU_PTH*/ @@ -903,7 +994,7 @@ main (int argc, char **argv ) } close (fd); } - + return 0; } @@ -1144,24 +1235,40 @@ 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 ); + pth_attr_t tattr; + fd_set fdset, read_fdset; + int ret = -1; int fd; - tattr = pth_attr_new(); - pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); - pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024); - pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent"); - - sigemptyset (&sigs ); + sigemptyset (&sigs); sigaddset (&sigs, SIGHUP); sigaddset (&sigs, SIGUSR1); sigaddset (&sigs, SIGUSR2); @@ -1169,6 +1276,16 @@ handle_connections (int listen_fd) sigaddset (&sigs, SIGTERM); ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 32*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "gpg-agent"); + + FD_ZERO (&fdset); + FD_SET (listen_fd, &fdset); + if (listen_fd_ssh != -1) + FD_SET (listen_fd_ssh, &fdset); + for (;;) { if (shutdown_pending) @@ -1185,28 +1302,67 @@ 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); + } } } diff --git a/agent/gpg-stream.c b/agent/gpg-stream.c new file mode 100644 index 000000000..f8d34e1ff --- /dev/null +++ b/agent/gpg-stream.c @@ -0,0 +1,914 @@ +/* stream.c - Stream I/O/ layer + Copyright (C) 2004 g10 Code GmbH + + This file is part of libgpg-stream. + + libgpg-stream 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. + + libgpg-stream 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libgpg-stream; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gpg-stream.h" +#include "gpg-stream-config.h" +#include "buffer.h" + + + +/* A Stream Context. */ +struct gpg_stream +{ + void *handle; /* Handle. */ + unsigned int flags; /* Flags. */ + buffer_t buffer; /* Buffer used for I/O. */ + gpg_stream_functions_t functions; /* Callbacks. */ +}; + + + +/* Macros. */ + +/* Standard permissions used for creating new files. */ +#define GPG_STREAM_FILE_PERMISSIONS 0600 + +/* Evaluate EXPRESSION, setting VARIABLE to the return code, if + VARIABLE is zero. */ +#define SET_UNLESS_NONZERO(variable, tmp_variable, expression) \ + do \ + { \ + tmp_variable = expression; \ + if ((! variable) && tmp_variable) \ + variable = tmp_variable; \ + } \ + while (0) + +/* Implementation of Memory I/O. */ + +typedef struct gpg_stream_handle_mem +{ + char *memory; /* Data. */ + size_t memory_size; /* Size of MEMORY. */ + size_t data_size; /* Size of data in MEMORY. */ + unsigned int grow: 1; /* MEMORY is allowed to grow. */ + size_t offset; /* Current offset in MEMORY. */ +} *gpg_stream_handle_mem_t; + +static gpg_error_t +gpg_stream_func_mem_create (void **handle, + void *spec, + unsigned int flags) +{ + gpg_stream_handle_mem_t mem_handle = NULL; + gpg_stream_spec_mem_t *mem_spec = spec; + gpg_error_t err = GPG_ERR_NO_ERROR; + + mem_handle = malloc (sizeof (*mem_handle)); + if (! mem_handle) + err = gpg_error_from_errno (errno); + else + { + mem_handle->memory = mem_spec ? mem_spec->memory : 0; + mem_handle->memory_size = mem_spec ? mem_spec->memory_size : 0; + mem_handle->data_size = 0; + mem_handle->grow = mem_spec ? mem_spec->grow : 1; + mem_handle->offset = 0; + *handle = mem_handle; + } + + return err; +} + +static gpg_error_t +gpg_stream_func_mem_read (void *handle, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read) +{ + gpg_stream_handle_mem_t mem_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (bytes_to_read > mem_handle->data_size - mem_handle->offset) + bytes_to_read = mem_handle->data_size - mem_handle->offset; + + memcpy (buffer, mem_handle->memory + mem_handle->offset, + bytes_to_read); + mem_handle->offset += bytes_to_read; + *bytes_read = bytes_to_read; + + return err; +} + +static gpg_error_t +gpg_stream_func_mem_write (void *handle, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written) +{ + gpg_stream_handle_mem_t mem_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + char *memory_new = NULL; + + if (! mem_handle->grow) + if (bytes_to_write > mem_handle->memory_size - mem_handle->offset) + bytes_to_write = mem_handle->memory_size - mem_handle->offset; + + while (bytes_to_write > mem_handle->memory_size - mem_handle->offset) + { + memory_new = realloc (mem_handle->memory, + mem_handle->memory_size + BUFFER_BLOCK_SIZE); + if (! memory_new) + err = gpg_error_from_errno (errno); + else + { + if (mem_handle->memory != memory_new) + mem_handle->memory = memory_new; + mem_handle->memory_size += BUFFER_BLOCK_SIZE; + } + } + + if (! err) + { + memcpy (mem_handle->memory + mem_handle->offset, buffer, + bytes_to_write); + if (mem_handle->offset + bytes_to_write > mem_handle->data_size) + mem_handle->data_size = mem_handle->offset + bytes_to_write; + mem_handle->offset += bytes_to_write; + } + *bytes_written = bytes_to_write; + + return err; +} + +gpg_error_t +gpg_stream_func_mem_seek (void *handle, + off_t offset, + int whence) +{ + gpg_stream_handle_mem_t mem_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + + switch (whence) + { + case SEEK_SET: + if ((offset < 0) || (offset > mem_handle->data_size)) + err = gpg_error (GPG_ERR_INV_ARG); + else + mem_handle->offset = offset; + break; + + case SEEK_CUR: + if ((mem_handle->offset + offset < 0) + || (mem_handle->offset + offset > mem_handle->data_size)) + err = gpg_error (GPG_ERR_INV_ARG); + else + mem_handle->offset += offset; + break; + + case SEEK_END: + if ((mem_handle->data_size + offset < 0) + || (mem_handle->data_size + offset > mem_handle->data_size)) + err = gpg_error (GPG_ERR_INV_ARG); + else + mem_handle->offset += offset; + } + + return err; +} + +static gpg_error_t +gpg_stream_func_mem_stat (void *handle, + size_t *size) +{ + gpg_stream_handle_mem_t mem_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + + *size = mem_handle->data_size; + + return err; +} + +static gpg_error_t +gpg_stream_func_mem_destroy (void *handle) +{ + gpg_stream_handle_mem_t mem_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (mem_handle->memory) + free (mem_handle->memory); + free (mem_handle); + + return err; +} + +gpg_stream_functions_t gpg_stream_functions_mem = + { + gpg_stream_func_mem_create, + gpg_stream_func_mem_read, + gpg_stream_func_mem_write, + gpg_stream_func_mem_seek, + gpg_stream_func_mem_stat, + gpg_stream_func_mem_destroy + }; + +/* Implementation of FD I/O. */ + +typedef struct gpg_stream_handle_fd +{ + int fd; +} *gpg_stream_handle_fd_t; + +static gpg_error_t +gpg_stream_func_fd_create (void **handle, + void *spec, + unsigned int flags) +{ + gpg_stream_handle_fd_t fd_handle = NULL; + gpg_stream_spec_fd_t *fd_spec = spec; + gpg_error_t err = GPG_ERR_NO_ERROR; + + fd_handle = malloc (sizeof (*fd_handle)); + if (! fd_handle) + err = gpg_error_from_errno (errno); + else + { + fd_handle->fd = fd_spec->fd; + *handle = fd_handle; + } + + return err; +} + +static gpg_error_t +gpg_stream_func_fd_read (void *handle, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read) + +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + ssize_t ret = -1; + + ret = READ (file_handle->fd, buffer, bytes_to_read); + if (ret == -1) + err = gpg_error_from_errno (errno); + else + *bytes_read = ret; + + return err; +} + +static gpg_error_t +gpg_stream_func_fd_write (void *handle, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written) + +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + ssize_t ret = -1; + + ret = WRITE (file_handle->fd, buffer, bytes_to_write); + if (ret == -1) + err = gpg_error_from_errno (errno); + else + *bytes_written = ret; + + return err; +} + +static gpg_error_t +gpg_stream_func_fd_seek (void *handle, + off_t pos, + int whence) +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + off_t ret = -1; + + ret = lseek (file_handle->fd, pos, whence); + if (ret == -1) + err = gpg_error_from_errno (errno); + + return err; +} + +static gpg_error_t +gpg_stream_func_fd_stat (void *handle, + size_t *size) +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + struct stat statbuf; + int ret = 0; + + ret = fstat (file_handle->fd, &statbuf); + if (ret == -1) + err = gpg_error_from_errno (errno); + else + *size = statbuf.st_size; + + return err; +} + +static gpg_error_t +gpg_stream_func_fd_destroy (void *handle) +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + + free (file_handle); + + return err; +} + +gpg_stream_functions_t gpg_stream_functions_fd = + { + gpg_stream_func_fd_create, + gpg_stream_func_fd_read, + gpg_stream_func_fd_write, + gpg_stream_func_fd_seek, + gpg_stream_func_fd_stat, + gpg_stream_func_fd_destroy + }; + + +/* Implementation of File I/O. */ + +static gpg_error_t +gpg_stream_func_file_create (void **handle, + void *spec, + unsigned int flags) +{ + gpg_stream_handle_fd_t file_handle = NULL; + gpg_stream_spec_file_t *file_spec = spec; + gpg_error_t err = GPG_ERR_NO_ERROR; + int open_flags = 0; + int fd = -1; + + file_handle = malloc (sizeof (*file_handle)); + if (! file_handle) + err = gpg_error_from_errno (errno); + + if (! err) + { + struct flag_mapping + { + unsigned int gpg_stream; + unsigned int sys; + } flag_mappings[] = { { GPG_STREAM_FLAG_READ, + O_RDONLY }, + { GPG_STREAM_FLAG_WRITE, + O_WRONLY }, + { GPG_STREAM_FLAG_EXCLUSIVE, + O_EXCL }, + { GPG_STREAM_FLAG_APPEND, + O_APPEND }, + { GPG_STREAM_FLAG_CREATE, + O_CREAT }, + { GPG_STREAM_FLAG_NONBLOCK, + O_NONBLOCK }, + { GPG_STREAM_FLAG_TRUNCATE, + O_TRUNC } }; + unsigned int i = 0; + + for (i = 0; i < (sizeof (flag_mappings) / sizeof (*flag_mappings)); i++) + if (flags & flag_mappings[i].gpg_stream) + open_flags |= flag_mappings[i].sys; + + fd = open (file_spec->filename, open_flags, file_spec->mode); + if (fd == -1) + err = gpg_error_from_errno (errno); + } + + if (! err) + { + file_handle->fd = fd; + *handle = file_handle; + } + else + { + if (file_handle) + free (file_handle); + if (fd != -1) + close (fd); + } + + return err; +} + +static gpg_error_t +gpg_stream_func_file_destroy (void *handle) +{ + gpg_stream_handle_fd_t file_handle = handle; + gpg_error_t err = GPG_ERR_NO_ERROR; + int ret = 0; + + if (file_handle) + { + ret = close (file_handle->fd); + if (ret == -1) + err = gpg_error_from_errno (errno); + free (file_handle); + } + + return err; +} + +gpg_stream_functions_t gpg_stream_functions_file = + { + gpg_stream_func_file_create, + gpg_stream_func_fd_read, + gpg_stream_func_fd_write, + gpg_stream_func_fd_seek, + gpg_stream_func_fd_stat, + gpg_stream_func_file_destroy + }; + + + +static gpg_error_t +gpg_stream_create_do (gpg_stream_t *stream, + void *spec, + unsigned int flags, + gpg_stream_functions_t functions) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (! (1 + && (0 + || ((flags & GPG_STREAM_FLAG_READ) && functions.func_read) + || ((flags & GPG_STREAM_FLAG_WRITE) && functions.func_write)))) + err = gpg_error (GPG_ERR_INV_ARG); + else + { + buffer_functions_t buffer_fncs = { functions.func_read, + functions.func_write, + functions.func_seek, + functions.func_stat }; + gpg_stream_t stream_new = NULL; + buffer_t buffer = NULL; + void *handle = NULL; + + stream_new = malloc (sizeof (*stream_new)); + if (! stream_new) + err = gpg_error_from_errno (errno); + + if (! err) + if (functions.func_create) + err = (*functions.func_create) (&handle, spec, flags); + + if (! err) + err = buffer_create (&buffer, handle, buffer_fncs); + + if (! err) + { + stream_new->handle = handle; + stream_new->flags = flags; + stream_new->buffer = buffer; + stream_new->functions = functions; + *stream = stream_new; + } + else + { + if (functions.func_destroy) + (*functions.func_destroy) (handle); + if (buffer) + buffer_destroy (buffer); + } + } + + return err; +} + +gpg_error_t +gpg_stream_create (gpg_stream_t *stream, + void *spec, + unsigned int flags, + gpg_stream_functions_t functions) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_create_do (stream, spec, flags, functions); + + return err; +} + +gpg_error_t +gpg_stream_create_file (gpg_stream_t *stream, + const char *filename, + unsigned int flags) +{ + gpg_stream_spec_file_t spec = { filename, GPG_STREAM_FILE_PERMISSIONS }; + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_create_do (stream, &spec, flags, gpg_stream_functions_file); + + return err; +} + +gpg_error_t +gpg_stream_create_fd (gpg_stream_t *stream, + int fd, + unsigned int flags) +{ + gpg_stream_spec_fd_t spec = { fd }; + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_create_do (stream, &spec, flags, gpg_stream_functions_fd); + + return err; +} + + +gpg_error_t +gpg_stream_destroy (gpg_stream_t stream) +{ + gpg_error_t err = GPG_ERR_NO_ERROR, tmp_err = GPG_ERR_NO_ERROR; + + if (stream) + { + SET_UNLESS_NONZERO (err, tmp_err, buffer_destroy (stream->buffer)); + if (stream->functions.func_destroy) + SET_UNLESS_NONZERO (err, tmp_err, \ + (*stream->functions.func_destroy) (stream->handle)); + free (stream); + } + + return err; +} + +static gpg_error_t +gpg_stream_read_do (gpg_stream_t stream, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (! (stream->flags & GPG_STREAM_FLAG_READ)) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + err = buffer_read (stream->buffer, + buffer, bytes_to_read, bytes_read); + + return err; +} + +gpg_error_t +gpg_stream_read (gpg_stream_t stream, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_read_do (stream, buffer, bytes_to_read, bytes_read); + + return err; +} + +static gpg_error_t +gpg_stream_write_do (gpg_stream_t stream, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (! (stream->flags & GPG_STREAM_FLAG_WRITE)) + err = GPG_ERR_NOT_SUPPORTED; + else + err = buffer_write (stream->buffer, + buffer, bytes_to_write, bytes_written); + + return err; +} + +gpg_error_t +gpg_stream_write (gpg_stream_t stream, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_write_do (stream, buffer, bytes_to_write, bytes_written); + + return err; +} + +static gpg_error_t +gpg_stream_read_line_do (gpg_stream_t stream, + char **line, + size_t *line_length) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + if (! (stream->flags & GPG_STREAM_FLAG_READ)) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + { + buffer_functions_t buffer_fncs_mem = { gpg_stream_func_mem_read, + gpg_stream_func_mem_write, + gpg_stream_func_mem_seek }; + void *handle = NULL; + char *line_new = NULL; + buffer_t line_buffer = NULL; + char *newline = NULL; + size_t data_size = 0; + char *data = NULL; + size_t line_size = 0; + + err = gpg_stream_func_mem_create (&handle, NULL, 0); + if (! err) + err = buffer_create (&line_buffer, handle, buffer_fncs_mem); + if (! err) + do + { + err = buffer_peek (stream->buffer, &data, &data_size); + if (! err) + { + size_t bytes_written = 0; + + newline = memchr (data, '\n', data_size); + if (newline) + { + /* Write until newline. */ + line_size += newline - data + 1; + err = buffer_write (line_buffer, data, newline - data + 1, + &bytes_written); + if (! err) + err = buffer_skip (stream->buffer, bytes_written); + break; + } + else + { + /* Write whole block. */ + line_size += data_size; + err = buffer_write (line_buffer, data, data_size, + &bytes_written); + if (! err) + err = buffer_skip (stream->buffer, bytes_written); + } + } + } + while ((! err) && data_size); + + if (! err) + { + /* Complete line has been written to line_buffer. */ + if (line_size) + { + err = buffer_seek (line_buffer, 0, SEEK_SET); + if (! err) + { + line_new = malloc (line_size + 1); + if (! line_new) + err = gpg_error_from_errno (errno); + } + if (! err) + { + size_t bytes_written = 0, written = 0; + + while ((bytes_written < line_size) && (! err)) + { + err = buffer_read (line_buffer, line_new + bytes_written, + line_size - bytes_written, &written); + bytes_written += written; + } + if (! err) + line_new[line_size] = 0; + } + } + } + + if (line_buffer) + buffer_destroy (line_buffer); + if (handle) + gpg_stream_func_mem_destroy (handle); + + if (! err) + { + *line = line_new; + if (line_length) + *line_length = line_size; + } + else + { + if (line_new) + free (line_new); + } + } + + return err; +} + +gpg_error_t +gpg_stream_read_line (gpg_stream_t stream, + char **line, + size_t *line_length) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_read_line_do (stream, line, line_length); + + return err; +} + +static gpg_error_t +gpg_stream_print_va_do (gpg_stream_t stream, + const char *format, + va_list ap) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + FILE *tmp_stream = NULL; + int ret = 0; + + if (! (stream->flags & GPG_STREAM_FLAG_WRITE)) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + { + tmp_stream = tmpfile (); + if (! tmp_stream) + err = gpg_error_from_errno (errno); + + if (! err) + { + ret = vfprintf (tmp_stream, format, ap); + if (ret == -1) + err = gpg_error_from_errno (errno); + } + + if (! err) + { + ret = fseek (tmp_stream, 0, SEEK_SET); + if (ret == -1) + err = gpg_error_from_errno (errno); + } + + if (! err) + { + size_t bytes_read = 0, bytes_written = 0; + char data[BUFFER_BLOCK_SIZE]; + + while (! err) + { + bytes_read = fread (data, 1, sizeof (data), tmp_stream); + if (ferror (tmp_stream)) + err = gpg_error_from_errno (errno); + if (! err) + err = gpg_stream_write_do (stream, data, + bytes_read, &bytes_written); + if (! err) + if (feof (tmp_stream)) + break; + } + } + + if (tmp_stream) + fclose (tmp_stream); + } + + return err; +} + +gpg_error_t +gpg_stream_print_va (gpg_stream_t stream, + const char *format, + va_list ap) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_print_va_do (stream, format, ap); + + return err; +} + +gpg_error_t +gpg_stream_print (gpg_stream_t stream, + const char *format, + ...) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + va_list ap; + + va_start (ap, format); + err = gpg_stream_print_va (stream, format, ap); + va_end (ap); + + return err; +} + +static gpg_error_t +gpg_stream_flush_do (gpg_stream_t stream) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_flush (stream->buffer); + + return err; +} + +gpg_error_t +gpg_stream_flush (gpg_stream_t stream) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_flush_do (stream); + + return err; +} + +static gpg_error_t +gpg_stream_peek_do (gpg_stream_t stream, + char **buffer, + size_t *size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_peek (stream->buffer, buffer, size); + + return err; +} + +gpg_error_t +gpg_stream_peek (gpg_stream_t stream, + char **buffer, + size_t *size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_peek_do (stream, buffer, size); + + return err; +} + +static gpg_error_t +gpg_stream_seek_do (gpg_stream_t stream, + off_t offset, + int whence) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_seek (stream->buffer, offset, whence); + + return err; +} + +gpg_error_t +gpg_stream_seek (gpg_stream_t stream, + off_t offset, + int whence) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_seek_do (stream, offset, whence); + + return err; +} + +static gpg_error_t +gpg_stream_stat_do (gpg_stream_t stream, + size_t *size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = buffer_stat (stream->buffer, size); + + return err; +} + +gpg_error_t +gpg_stream_stat (gpg_stream_t stream, + size_t *size) +{ + gpg_error_t err = GPG_ERR_NO_ERROR; + + err = gpg_stream_stat_do (stream, size); + + return err; +} diff --git a/agent/gpg-stream.h b/agent/gpg-stream.h new file mode 100644 index 000000000..d9c30f8bf --- /dev/null +++ b/agent/gpg-stream.h @@ -0,0 +1,148 @@ +/* stream.h - Stream I/O layer + Copyright (C) 2004 g10 Code GmbH + + This file is part of libgpg-stream. + + libgpg-stream 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. + + libgpg-stream 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libgpg-stream; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#ifndef GPG_STREAM_H +#define GPG_STREAM_H + +#include +#include + +#include + + + +#define STREAM_BLOCK_SIZE 1024 + + + +typedef struct gpg_stream *gpg_stream_t; + +typedef gpg_error_t (*gpg_stream_func_create_t) (void **handle, + void *spec, + unsigned int flags); +typedef gpg_error_t (*gpg_stream_func_read_t) (void *handle, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read); +typedef gpg_error_t (*gpg_stream_func_write_t) (void *handle, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written); +typedef gpg_error_t (*gpg_stream_func_seek_t) (void *handle, + off_t pos, + int whence); +typedef gpg_error_t (*gpg_stream_func_stat_t) (void *handle, + size_t *size); +typedef gpg_error_t (*gpg_stream_func_destroy_t) (void *handle); + +typedef struct gpg_stream_functions +{ + gpg_stream_func_create_t func_create; + gpg_stream_func_read_t func_read; + gpg_stream_func_write_t func_write; + gpg_stream_func_seek_t func_seek; + gpg_stream_func_stat_t func_stat; + gpg_stream_func_destroy_t func_destroy; +} gpg_stream_functions_t; + +#define GPG_STREAM_FLAG_READ (1 << 0) +#define GPG_STREAM_FLAG_WRITE (1 << 1) +#define GPG_STREAM_FLAG_EXCLUSIVE (1 << 2) +#define GPG_STREAM_FLAG_APPEND (1 << 3) +#define GPG_STREAM_FLAG_CREATE (1 << 4) +#define GPG_STREAM_FLAG_NONBLOCK (1 << 5) +#define GPG_STREAM_FLAG_TRUNCATE (1 << 6) +#define GPG_STREAM_FLAG_BINARY (1 << 7) + +gpg_error_t gpg_stream_create (gpg_stream_t *stream, + void *spec, + unsigned int flags, + gpg_stream_functions_t functions); + +gpg_error_t gpg_stream_create_file (gpg_stream_t *stream, + const char *filename, + unsigned int flags); + +gpg_error_t gpg_stream_create_fd (gpg_stream_t *stream, + int fd, + unsigned int flags); + +gpg_error_t gpg_stream_destroy (gpg_stream_t stream); + +gpg_error_t gpg_stream_read (gpg_stream_t stream, + char *buffer, + size_t bytes_to_read, + size_t *bytes_read); + +gpg_error_t gpg_stream_write (gpg_stream_t stream, + const char *buffer, + size_t bytes_to_write, + size_t *bytes_written); + +gpg_error_t gpg_stream_read_line (gpg_stream_t stream, + char **line, + size_t *line_length); + +gpg_error_t gpg_stream_print_va (gpg_stream_t stream, + const char *format, + va_list ap); + +gpg_error_t gpg_stream_print (gpg_stream_t stream, + const char *format, + ...); + +gpg_error_t gpg_stream_flush (gpg_stream_t stream); + +gpg_error_t gpg_stream_peek (gpg_stream_t stream, + char **buffer, + size_t *size); + +gpg_error_t gpg_stream_seek (gpg_stream_t stream, + off_t offset, + int whence); + +gpg_error_t gpg_stream_stat (gpg_stream_t stream, + size_t *size); + +typedef struct gpg_stream_spec_mem +{ + char *memory; + size_t memory_size; + unsigned int grow: 1; +} gpg_stream_spec_mem_t; + +extern gpg_stream_functions_t gpg_stream_functions_mem; + +typedef struct gpg_stream_spec_file +{ + const char *filename; + mode_t mode; +} gpg_stream_spec_file_t; + +extern gpg_stream_functions_t gpg_stream_functions_file; + +typedef struct gpg_stream_spec_fd +{ + int fd; +} gpg_stream_spec_fd_t; + +extern gpg_stream_functions_t gpg_stream_functions_fd; + +#endif diff --git a/agent/pksign.c b/agent/pksign.c index acde66029..f2c61b43b 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -55,18 +55,17 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash) } -/* SIGN whatever information we have accumulated in CTRL and write it - back to OUTFP. */ +/* SIGN whatever information we have accumulated in CTRL and return + the signature S-Expression. */ int -agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) +agent_pksign_do (CTRL ctrl, const char *desc_text, + gcry_sexp_t *signature_sexp, int ignore_cache) { - gcry_sexp_t s_skey = NULL, s_hash = NULL, s_sig = NULL; + gcry_sexp_t s_skey = NULL, s_sig = NULL; unsigned char *shadow_info = NULL; - int rc; - char *buf = NULL; - size_t len; + unsigned int rc = 0; /* FIXME: gpg-error? */ - if (!ctrl->have_keygrip) + if (! ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); rc = agent_key_from_file (ctrl, desc_text, ctrl->keygrip, @@ -77,26 +76,40 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) goto leave; } - if (!s_skey) - { /* divert operation to the smartcard */ - unsigned char *sigbuf; + if (! s_skey) + { + /* divert operation to the smartcard */ + + unsigned char *buf = NULL; + size_t len = 0; rc = divert_pksign (ctrl, ctrl->digest.value, ctrl->digest.valuelen, ctrl->digest.algo, - shadow_info, &sigbuf); + shadow_info, &buf); if (rc) { log_error ("smartcard signing failed: %s\n", gpg_strerror (rc)); goto leave; } - len = gcry_sexp_canon_len (sigbuf, 0, NULL, NULL); + len = gcry_sexp_canon_len (buf, 0, NULL, NULL); assert (len); - buf = sigbuf; + + rc = gcry_sexp_sscan (&s_sig, NULL, buf, len); + xfree (buf); + if (rc) + { + log_error ("failed to convert sigbuf returned by divert_pksign " + "into S-Exp: %s", gpg_strerror (rc)); + goto leave; + } } else - { /* no smartcard, but a private key */ + { + /* no smartcard, but a private key */ + + gcry_sexp_t s_hash = NULL; /* put the hash into a sexp */ rc = do_encode_md (ctrl->digest.value, @@ -114,6 +127,7 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) /* sign */ rc = gcry_pk_sign (&s_sig, s_hash, s_skey); + gcry_sexp_release (s_hash); if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); @@ -125,26 +139,47 @@ agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) log_debug ("result: "); gcry_sexp_dump (s_sig); } - - len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); - assert (len); - buf = xmalloc (len); - len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); - assert (len); } + leave: + + if (! rc) + *signature_sexp = s_sig; + + gcry_sexp_release (s_skey); + xfree (shadow_info); + + return rc; +} + +/* SIGN whatever information we have accumulated in CTRL and write it + back to OUTFP. */ +int +agent_pksign (CTRL ctrl, const char *desc_text, FILE *outfp, int ignore_cache) +{ + gcry_sexp_t s_sig = NULL; + char *buf = NULL; + size_t len = 0; + int rc = 0; + + rc = agent_pksign_do (ctrl, desc_text, &s_sig, ignore_cache); + if (rc) + goto leave; + + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xmalloc (len); + len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + /* FIXME: we must make sure that no buffering takes place or we are in full control of the buffer memory (easy to do) - should go into assuan. */ fwrite (buf, 1, len, outfp); leave: - gcry_sexp_release (s_skey); - gcry_sexp_release (s_hash); gcry_sexp_release (s_sig); xfree (buf); - xfree (shadow_info); + return rc; } - - diff --git a/agent/query.c b/agent/query.c index 145aaca00..4fbe702c6 100644 --- a/agent/query.c +++ b/agent/query.c @@ -132,7 +132,7 @@ start_pinentry (CTRL ctrl) pgmname++; argv[0] = pgmname; - if (ctrl->display && !opt.keep_display) + if ((ctrl && ctrl->display) && !opt.keep_display) { argv[1] = "--display"; argv[2] = ctrl->display; @@ -169,7 +169,7 @@ start_pinentry (CTRL ctrl) NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (map_assuan_err (rc)); - if (ctrl->ttyname) + if (ctrl && ctrl->ttyname) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", ctrl->ttyname) < 0 ) @@ -180,7 +180,7 @@ start_pinentry (CTRL ctrl) if (rc) return unlock_pinentry (map_assuan_err (rc)); } - if (ctrl->ttytype) + if (ctrl && ctrl->ttytype) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", ctrl->ttytype) < 0 ) @@ -190,7 +190,7 @@ start_pinentry (CTRL ctrl) if (rc) return unlock_pinentry (map_assuan_err (rc)); } - if (ctrl->lc_ctype) + if (ctrl && ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) @@ -200,7 +200,7 @@ start_pinentry (CTRL ctrl) if (rc) return unlock_pinentry (map_assuan_err (rc)); } - if (ctrl->lc_messages) + if (ctrl && ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )