diff --git a/agent/ChangeLog b/agent/ChangeLog index 3a37ec80b..cd1f04f5a 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,30 @@ +2002-06-27 Werner Koch + + * Makefile.am (pkglib_PROGRAMS): Put protect-tool there. + + * findkey.c (agent_write_private_key,agent_key_from_file) + (agent_key_available): Use GNUPG_PRIVATE_KEYS_DIR constant. + * gpg-agent.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * protect-tool.c (store_private_key): New. + (import_p12_file): Store the new file if requested. + (main): New options --force and --store. + + * gpg-agent.c (main): Set a global flag when runing detached. + * query.c (start_pinentry): Pass the list of FD to keep in the + child when not running detached. + * call-scd.c (start_scd): Ditto. + +2002-06-26 Werner Koch + + * command.c (cmd_istrusted, cmd_listtrusted, cmd_marktrusted) + (cmd_pksign, cmd_pkdecrypt, cmd_genkey, cmd_get_passphrase) + (cmd_learn): Print an error message for a failed operation. + + * simple-pwquery.c, simple-pwquery.h: New. + * protect-tool. (get_passphrase): New, used to ge a passphrase + from the agent if none was given on the command line. + 2002-06-25 Werner Koch * protect-tool.c (rsa_key_check): New. diff --git a/agent/Makefile.am b/agent/Makefile.am index baeab5d65..c814dd995 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -19,7 +19,7 @@ ## Process this file with automake to produce Makefile.in bin_PROGRAMS = gpg-agent -noinst_PROGRAMS = protect-tool +pkglib_PROGRAMS = protect-tool AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBGCRYPT_CFLAGS) $(PTH_CFLAGS) LDFLAGS = @LDFLAGS@ @@ -48,8 +48,8 @@ gpg_agent_LDADD = ../jnlib/libjnlib.a ../assuan/libassuan.a \ protect_tool_SOURCES = \ protect-tool.c \ protect.c \ - minip12.c minip12.h - + minip12.c minip12.h \ + simple-pwquery.c simple-pwquery.h protect_tool_LDADD = ../jnlib/libjnlib.a \ ../common/libcommon.a $(LIBGCRYPT_LIBS) diff --git a/agent/agent.h b/agent/agent.h index 75683a0ad..ba6934366 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -45,6 +45,8 @@ struct { int no_grab; /* don't let the pinentry grab the keyboard */ unsigned long def_cache_ttl; + int running_detached; /* we are running detached from the tty. */ + } opt; diff --git a/agent/call-scd.c b/agent/call-scd.c index 8b79e81cf..5f7de0578 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -156,6 +156,8 @@ start_scd (void) const char *pgmname; ASSUAN_CONTEXT ctx; const char *argv[3]; + int no_close_list[3]; + int i; #ifdef USE_GNU_PTH if (!pth_mutex_acquire (&scd_lock, 0, NULL)) @@ -191,8 +193,18 @@ start_scd (void) argv[1] = "--server"; argv[2] = NULL; + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + } + no_close_list[i] = -1; + /* connect to the pinentry and perform initial handshaking */ - rc = assuan_pipe_connect (&ctx, opt.scdaemon_program, (char**)argv, 0); + rc = assuan_pipe_connect (&ctx, opt.scdaemon_program, (char**)argv, + no_close_list); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", diff --git a/agent/command.c b/agent/command.c index 387eef417..b98ec25ff 100644 --- a/agent/command.c +++ b/agent/command.c @@ -110,7 +110,10 @@ cmd_istrusted (ASSUAN_CONTEXT ctx, char *line) else if (rc == -1) return ASSUAN_Not_Trusted; else - return map_to_assuan_status (rc); + { + log_error ("command is_trusted failed: %s\n", gnupg_strerror (rc)); + return map_to_assuan_status (rc); + } } /* LISTTRUSTED @@ -119,7 +122,10 @@ cmd_istrusted (ASSUAN_CONTEXT ctx, char *line) static int cmd_listtrusted (ASSUAN_CONTEXT ctx, char *line) { - return map_to_assuan_status (agent_listtrusted (ctx)); + int rc = agent_listtrusted (ctx); + if (rc) + log_error ("command listtrusted failed: %s\n", gnupg_strerror (rc)); + return map_to_assuan_status (rc); } @@ -158,6 +164,8 @@ cmd_marktrusted (ASSUAN_CONTEXT ctx, char *line) p++; rc = agent_marktrusted (p, fpr, flag); + if (rc) + log_error ("command marktrusted failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } @@ -281,6 +289,8 @@ cmd_pksign (ASSUAN_CONTEXT ctx, char *line) CTRL ctrl = assuan_get_pointer (ctx); rc = agent_pksign (ctrl, assuan_get_data_fp (ctx)); + if (rc) + log_error ("command pksign failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } @@ -304,6 +314,8 @@ cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) rc = agent_pkdecrypt (ctrl, value, valuelen, assuan_get_data_fp (ctx)); xfree (value); + if (rc) + log_error ("command pkdecrypt failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } @@ -337,6 +349,8 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) rc = agent_genkey (ctrl, value, valuelen, assuan_get_data_fp (ctx)); xfree (value); + if (rc) + log_error ("command genkey failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } @@ -449,6 +463,8 @@ cmd_get_passphrase (ASSUAN_CONTEXT ctx, char *line) } } + if (rc) + log_error ("command get_passphrase failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } @@ -491,7 +507,7 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line) rc = agent_handle_learn (has_option (line, "--send")? ctx : NULL); if (rc) - log_error ("agent_handle_learn failed: %s\n", gnupg_strerror (rc)); + log_error ("command learn failed: %s\n", gnupg_strerror (rc)); return map_to_assuan_status (rc); } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index f2a26acc7..dfc10087a 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -173,7 +173,6 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf) { struct pin_entry_info_s *pi; int rc; - int tries = 0; assert (!opaque); diff --git a/agent/findkey.c b/agent/findkey.c index 3a9dd53b8..fb3b36e4c 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -52,7 +52,7 @@ agent_write_private_key (const unsigned char *grip, sprintf (hexgrip+2*i, "%02X", grip[i]); strcpy (hexgrip+40, ".key"); - fname = make_filename (opt.homedir, "private-keys-v1.d", hexgrip, NULL); + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); if (force) fp = fopen (fname, "wb"); else @@ -194,7 +194,7 @@ agent_key_from_file (const unsigned char *grip, unsigned char **shadow_info) sprintf (hexgrip+2*i, "%02X", grip[i]); strcpy (hexgrip+40, ".key"); - fname = make_filename (opt.homedir, "private-keys-v1.d", hexgrip, NULL); + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); fp = fopen (fname, "rb"); if (!fp) { @@ -318,7 +318,7 @@ agent_key_available (const unsigned char *grip) sprintf (hexgrip+2*i, "%02X", grip[i]); strcpy (hexgrip+40, ".key"); - fname = make_filename (opt.homedir, "private-keys-v1.d", hexgrip, NULL); + fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); i = !access (fname, R_OK)? 0 : -1; xfree (fname); return i; diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 7742e1b85..4441cf557 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -295,13 +295,7 @@ main (int argc, char **argv ) opt.homedir = getenv("GNUPGHOME"); if (!opt.homedir || !*opt.homedir) - { -#ifdef HAVE_DRIVE_LETTERS - opt.homedir = "c:/gnupg-test"; -#else - opt.homedir = "~/.gnupg-test"; -#endif - } + opt.homedir = GNUPG_DEFAULT_HOMEDIR; opt.def_cache_ttl = 10*60; /* default to 10 minutes */ @@ -473,7 +467,6 @@ main (int argc, char **argv ) { /* regular server mode */ int fd; pid_t pid; - int i; int len; struct sockaddr_un serv_addr; char *p; @@ -599,7 +592,10 @@ main (int argc, char **argv ) /* detach from tty and put process into a new session */ if (!nodetach ) - { /* close stdin, stdout and stderr unless it is the log stream */ + { + int i; + + /* close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if ( log_get_fd () != i) @@ -611,6 +607,7 @@ main (int argc, char **argv ) cleanup (); exit (1); } + opt.running_detached = 1; } if (chdir("/")) diff --git a/agent/minip12.c b/agent/minip12.c index 29531b23b..6fe9d2c6c 100644 --- a/agent/minip12.c +++ b/agent/minip12.c @@ -660,7 +660,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw) bagseqlength = ti.length; while (bagseqlength) { - log_error ( "at offset %u\n", (p - buffer)); + /*log_debug ( "at offset %u\n", (p - buffer));*/ where = "bag-sequence"; if (parse_tag (&p, &n, &ti)) goto bailout; diff --git a/agent/protect-tool.c b/agent/protect-tool.c index f53342595..4efe497a7 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -35,6 +35,7 @@ #define JNLIB_NEED_LOG_LOGV #include "agent.h" #include "minip12.h" +#include "simple-pwquery.h" #define N_(a) a #define _(a) a @@ -55,6 +56,8 @@ enum cmd_and_opt_values oShowKeygrip, oP12Import, + oStore, + oForce, aTest }; @@ -70,7 +73,14 @@ struct rsa_secret_key_s static int opt_armor; -static const char *passphrase = "abc"; +static int opt_store; +static int opt_force; +static const char *passphrase; + +static const char *get_passphrase (void); +static int store_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force); + static ARGPARSE_OPTS opts[] = { @@ -86,6 +96,8 @@ static ARGPARSE_OPTS opts[] = { { oShowKeygrip, "show-keygrip", 256, "show the \"keygrip\""}, { oP12Import, "p12-import", 256, "import a PKCS-12 encoded private key"}, + { oStore, "store", 0, "store the created key in the appropriate place"}, + { oForce, "force", 0, "force overwriting"}, {0} }; @@ -283,7 +295,7 @@ read_and_protect (const char *fname) if (!key) return; - rc = agent_protect (key, passphrase, &result, &resultlen); + rc = agent_protect (key, get_passphrase (), &result, &resultlen); xfree (key); if (rc) { @@ -318,7 +330,7 @@ read_and_unprotect (const char *fname) if (!key) return; - rc = agent_unprotect (key, passphrase, &result, &resultlen); + rc = agent_unprotect (key, get_passphrase (), &result, &resultlen); xfree (key); if (rc) { @@ -564,6 +576,7 @@ import_p12_file (const char *fname) struct rsa_secret_key_s sk; GcrySexp s_key; unsigned char *key; + unsigned char grip[20]; /* fixme: we should release some stuff on error */ @@ -571,7 +584,7 @@ import_p12_file (const char *fname) if (!buf) return; - kparms = p12_parse (buf, buflen, passphrase); + kparms = p12_parse (buf, buflen, get_passphrase ()); xfree (buf); if (!kparms) { @@ -626,18 +639,15 @@ import_p12_file (const char *fname) } /* Compute the keygrip. */ - { - unsigned char grip[20]; - if (!gcry_pk_get_keygrip (s_key, grip)) - { - log_error ("can't calculate keygrip\n"); - return; - } - log_info ("keygrip: "); - for (i=0; i < 20; i++) - log_printf ("%02X", grip[i]); - log_printf ("\n"); - } + if (!gcry_pk_get_keygrip (s_key, grip)) + { + log_error ("can't calculate keygrip\n"); + return; + } + log_info ("keygrip: "); + for (i=0; i < 20; i++) + log_printf ("%02X", grip[i]); + log_printf ("\n"); /* convert to canonical encoding */ buflen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_CANON, NULL, 0); @@ -648,7 +658,7 @@ import_p12_file (const char *fname) gcry_sexp_release (s_key); - rc = agent_protect (key, passphrase, &result, &resultlen); + rc = agent_protect (key, get_passphrase (), &result, &resultlen); xfree (key); if (rc) { @@ -666,7 +676,11 @@ import_p12_file (const char *fname) resultlen = strlen (p); } - fwrite (result, resultlen, 1, stdout); + if (opt_store) + store_private_key (grip, result, resultlen, opt_force); + else + fwrite (result, resultlen, 1, stdout); + xfree (result); } @@ -711,6 +725,8 @@ main (int argc, char **argv ) case oP12Import: cmd = oP12Import; break; case oPassphrase: passphrase = pargs.r.ret_str; break; + case oStore: opt_store = 1; break; + case oForce: opt_force = 1; break; default : pargs.err = 2; break; } @@ -736,7 +752,8 @@ main (int argc, char **argv ) else show_file (*argv); - return 0; + agent_exit (0); + return 8; /*NOTREACHED*/ } void @@ -745,3 +762,95 @@ agent_exit (int rc) rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } + + +/* Return the passphrase string and ask the agent if it has not been + set from the command line. */ +static const char * +get_passphrase (void) +{ + char *pw; + int err; + + if (passphrase) + return passphrase; + + pw = simple_pwquery (NULL,NULL, + _("Enter passphrase:"), + _("Please enter the passphrase or the PIN\n" + "needed to complete this operation."), + &err); + if (!pw) + { + if (err) + log_error ("error while asking for the passphrase\n"); + else + log_info ("cancelled\n"); + agent_exit (0); + } + passphrase = pw; + return passphrase; +} + + +static int +store_private_key (const unsigned char *grip, + const void *buffer, size_t length, int force) +{ + int i; + const char *homedir; + char *fname; + FILE *fp; + char hexgrip[40+4+1]; + + for (i=0; i < 20; i++) + sprintf (hexgrip+2*i, "%02X", grip[i]); + strcpy (hexgrip+40, ".key"); + + homedir = getenv("GNUPGHOME"); + if (!homedir || !*homedir) + homedir = GNUPG_DEFAULT_HOMEDIR; + + fname = make_filename (homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); + if (force) + fp = fopen (fname, "wb"); + else + { + if (!access (fname, F_OK)) + { + log_error ("secret key file `%s' already exists\n", fname); + xfree (fname); + return -1; + } + fp = fopen (fname, "wbx"); /* FIXME: the x is a GNU extension - let + configure check whether this actually + works */ + } + + if (!fp) + { + log_error ("can't create `%s': %s\n", fname, strerror (errno)); + xfree (fname); + return -1; + } + + if (fwrite (buffer, length, 1, fp) != 1) + { + log_error ("error writing `%s': %s\n", fname, strerror (errno)); + fclose (fp); + remove (fname); + xfree (fname); + return -1; + } + if ( fclose (fp) ) + { + log_error ("error closing `%s': %s\n", fname, strerror (errno)); + remove (fname); + xfree (fname); + return -1; + } + log_info ("secret key stored as `%s'\n", fname); + + xfree (fname); + return 0; +} diff --git a/agent/query.c b/agent/query.c index 724bbd57d..c7cba5e93 100644 --- a/agent/query.c +++ b/agent/query.c @@ -80,6 +80,8 @@ start_pinentry (void) const char *pgmname; ASSUAN_CONTEXT ctx; const char *argv[5]; + int no_close_list[3]; + int i; #ifdef USE_GNU_PTH if (!pth_mutex_acquire (&entry_lock, 0, NULL)) @@ -119,9 +121,19 @@ start_pinentry (void) } else argv[1] = NULL; + + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + } + no_close_list[i] = -1; /* connect to the pinentry and perform initial handshaking */ - rc = assuan_pipe_connect (&ctx, opt.pinentry_program, (char**)argv, 0); + rc = assuan_pipe_connect (&ctx, opt.pinentry_program, (char**)argv, + no_close_list); if (rc) { log_error ("can't connect to the PIN entry module: %s\n", diff --git a/agent/simple-pwquery.c b/agent/simple-pwquery.c new file mode 100644 index 000000000..5bb08afec --- /dev/null +++ b/agent/simple-pwquery.c @@ -0,0 +1,485 @@ +/* simple-pwquery.c - A simple password query cleint for gpg-agent + * Copyright (C) 2002 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 + */ + +/* This module is intended as a standalone client implementation to + gpg-agent's GET_PASSPHRASE command. In particular it does not use + the Assuan library and can only cope with an already running + gpg-agent. Some stuff is configurable in the header file. */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif + +#define SIMPLE_PWQUERY_IMPLEMENTATION 1 +#include "simple-pwquery.h" + +#if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING) +# undef SPWQ_USE_LOGGING +#endif + +#ifndef _ +#define _(a) (a) +#endif + +#if !defined (hexdigitp) && !defined (xtoi_2) +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) +#endif + + +/* Write NBYTES of BUF to file descriptor FD. */ +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + + while (nleft > 0) + { + nwritten = write( fd, buf, nleft ); + if (nwritten < 0) + { + if (errno == EINTR) + nwritten = 0; + else { +#ifdef SPWQ_USE_LOGGING + log_error ("write failed: %s\n", strerror (errno)); +#endif + return SPWQ_IO_ERROR; + } + } + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + + return 0; +} + + +/* Read an entire line and return number of bytes read. */ +static int +readline (int fd, char *buf, size_t buflen) +{ + size_t nleft = buflen; + char *p; + int nread = 0; + + while (nleft > 0) + { + int n = read (fd, buf, nleft); + if (n < 0) + { + if (errno == EINTR) + continue; + return -(SPWQ_IO_ERROR); + } + else if (!n) + { + return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */ + } + p = buf; + nleft -= n; + buf += n; + nread += n; + + for (; n && *p != '\n'; n--, p++) + ; + if (n) + { + break; /* at least one full line available - that's enough. + This function is just a simple implementation, so + it is okay to forget about pending bytes */ + } + } + + return nread; +} + + +/* Send an option to the agent */ +static int +agent_send_option (int fd, const char *name, const char *value) +{ + char buf[200]; + int nread; + char *line; + int i; + + line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2); + if (!line) + return SPWQ_OUT_OF_CORE; + strcpy (stpcpy (stpcpy (stpcpy ( + stpcpy (line, "OPTION "), name), "="), value), "\n"); + i = writen (fd, line, strlen (line)); + spwq_free (line); + if (i) + return i; + + /* get response */ + nread = readline (fd, buf, DIM(buf)-1); + if (nread < 0) + return -nread; + if (nread < 3) + return SPWQ_PROTOCOL_ERROR; + + if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n')) + return 0; /* okay */ + + return SPWQ_ERR_RESPONSE; +} + + +/* Send all available options to the agent. */ +static int +agent_send_all_options (int fd) +{ + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + int rc = 0; + + dft_display = getenv ("DISPLAY"); + if (dft_display) + { + if ((rc = agent_send_option (fd, "display", dft_display))) + return rc; + } + + if (ttyname (1)) + dft_ttyname = ttyname (1); + if (dft_ttyname) + { + if ((rc=agent_send_option (fd, "ttyname", dft_ttyname))) + return rc; + } + + dft_ttytype = getenv ("TERM"); + if (dft_ttyname && dft_ttytype) + { + if ((rc = agent_send_option (fd, "ttytype", dft_ttytype))) + return rc; + } + +#if defined(HAVE_SETLOCALE) + { + char *old_lc = NULL; + char *dft_lc = NULL; + +#if defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_CTYPE, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-ctype", dft_lc); + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + +#if defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + { + char *p = spwq_malloc (strlen (old_lc)+1); + if (!p) + return SPWQ_OUT_OF_CORE; + strcpy (p, old_lc); + old_lc = p; + } + dft_lc = setlocale (LC_MESSAGES, ""); + if (dft_ttyname && dft_lc) + rc = agent_send_option (fd, "lc-messages", dft_lc); + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + spwq_free (old_lc); + } + if (rc) + return rc; +#endif + } +#endif /*HAVE_SETLOCALE*/ + + return 0; +} + + + +/* Try to open a connection to the agent, send all options and return + the file descriptor for the connection. Return -1 in case of + error. */ +static int +agent_open (int *rfd) +{ + int rc; + int fd; + char *infostr, *p; + struct sockaddr_un client_addr; + size_t len; + int prot; + char line[200]; + int nread; + + *rfd = -1; + infostr = getenv ( "GPG_AGENT_INFO" ); + if ( !infostr ) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent is not available in this session\n")); +#endif + return SPWQ_NO_AGENT; + } + + if ( !(p = strchr ( infostr, ':')) || p == infostr + || (p-infostr)+1 >= sizeof client_addr.sun_path ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("malformed GPG_AGENT_INFO environment variable\n")); +#endif + return SPWQ_NO_AGENT; + } + *p++ = 0; + + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if ( prot != 1) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("gpg-agent protocol version %d is not supported\n"),prot); +#endif + return SPWQ_PROTOCOL_ERROR; + } + + if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) + { +#ifdef SPWQ_USE_LOGGING + log_error ("can't create socket: %s\n", strerror(errno) ); +#endif + return SPWQ_SYS_ERROR; + } + + memset (&client_addr, 0, sizeof client_addr); + client_addr.sun_family = AF_UNIX; + strcpy (client_addr.sun_path, infostr); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(client_addr.sun_path) + 1); + + if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno)); +#endif + close (fd ); + return SPWQ_IO_ERROR; + } + + nread = readline (fd, line, DIM(line)); + if (nread < 3 || !(line[0] == 'O' && line[1] == 'K' + && (line[2] == '\n' || line[2] == ' ')) ) + { +#ifdef SPWQ_USE_LOGGING + log_error ( _("communication problem with gpg-agent\n")); +#endif + close (fd ); + return SPWQ_PROTOCOL_ERROR; + } + + rc = agent_send_all_options (fd); + if (rc) + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem setting the gpg-agent options\n")); +#endif + close (fd); + return rc; + } + + *rfd = fd; + return 0; +} + + +/* Copy text to BUFFER and escape as required. Return a poiinter to + the end of the new buffer. NOte that BUFFER must be large enough + to keep the entire text; allocataing it 3 times the size of TEXT + is sufficient. */ +static char * +copy_and_escape (char *buffer, const char *text) +{ + int i; + char *p = buffer; + + for (i=0; text[i]; i++) + { + if (text[i] < ' ' || text[i] == '+') + { + sprintf (p, "%%%02X", text[i]); + p += 3; + } + else if (text[i] == ' ') + *p++ = '+'; + else + *p++ = text[i]; + } + return p; +} + + +/* Ask the gpg-agent for a passphrase and present the user with a + DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. + If a CACHEID is not NULL it is used to locate the passphrase in in + the cache and store it under this ID. If ERRORCODE is not NULL it + should point a variable receiving an errorcode; thsi errocode might + be 0 if the user canceled the operation. The function returns NULL + to indicate an error. */ +char * +simple_pwquery (const char *cacheid, + const char *tryagain, + const char *prompt, + const char *description, + int *errorcode) +{ + int fd = -1; + int nread; + char *result = NULL; + char *pw = NULL; + char *p; + int rc, i; + + rc = agent_open (&fd); + if (rc) + goto leave; + + if (!cacheid) + cacheid = "X"; + if (!tryagain) + tryagain = "X"; + if (!prompt) + prompt = "X"; + if (!description) + description = "X"; + + { + char *line; + /* We allocate 3 times the needed space so that there is enough + space for escaping. */ + line = spwq_malloc (15 + + 3*strlen (cacheid) + 1 + + 3*strlen (tryagain) + 1 + + 3*strlen (prompt) + 1 + + 3*strlen (description) + 1 + + 2); + if (!line) + { + rc = SPWQ_OUT_OF_CORE; + goto leave; + } + strcpy (line, "GET_PASSPHRASE "); + p = line+15; + p = copy_and_escape (p, cacheid); + *p++ = ' '; + p = copy_and_escape (p, tryagain); + *p++ = ' '; + p = copy_and_escape (p, prompt); + *p++ = ' '; + p = copy_and_escape (p, description); + *p++ = '\n'; + rc = writen (fd, line, p - line); + spwq_free (line); + if (rc) + goto leave; + } + + /* get response */ + pw = spwq_secure_malloc (500); + nread = readline (fd, pw, 499); + if (nread < 0) + { + rc = -nread; + goto leave; + } + if (nread < 3) + { + rc = SPWQ_PROTOCOL_ERROR; + goto leave; + } + + if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ') + { /* we got a passphrase - convert it back from hex */ + size_t pwlen = 0; + + for (i=3; i < nread && hexdigitp (pw+i); i+=2) + pw[pwlen++] = xtoi_2 (pw+i); + pw[pwlen] = 0; /* make a C String */ + result = pw; + pw = NULL; + } + else if (nread > 7 && !memcmp (pw, "ERR 111", 7) + && (pw[7] == ' ' || pw[7] == '\n') ) + { +#ifdef SPWQ_USE_LOGGING + log_info (_("canceled by user\n") ); +#endif + *errorcode = 0; /* canceled */ + } + else + { +#ifdef SPWQ_USE_LOGGING + log_error (_("problem with the agent\n")); +#endif + rc = SPWQ_ERR_RESPONSE; + } + + leave: + if (errorcode) + *errorcode = rc; + if (fd != -1) + close (fd); + if (pw) + spwq_free (pw); + return result; +} diff --git a/agent/simple-pwquery.h b/agent/simple-pwquery.h new file mode 100644 index 000000000..a1b276ff6 --- /dev/null +++ b/agent/simple-pwquery.h @@ -0,0 +1,69 @@ +/* simple-pwquery.c - A simple password query cleint for gpg-agent + * Copyright (C) 2002 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 + */ + +#ifndef SIMPLE_PWQUERY_H +#define SIMPLE_PWQUERY_H + +#ifdef SIMPLE_PWQUERY_IMPLEMENTATION /* Begin configuration stuff. */ + +/* Include whatever files you need. */ +#include +#include "../jnlib/logging.h" + +/* Try to write error message using the standard log mechanism. The + current implementation requires that the HAVE_JNLIB_LOGGING is also + defined. */ +#define SPWQ_USE_LOGGING 1 + +/* Memory allocation functions used by the implementation. Note, that + the returned value is expected to be freed with + spwq_secure_free. */ +#define spwq_malloc(a) gcry_malloc (a) +#define spwq_free(a) gcry_free (a) +#define spwq_secure_malloc(a) gcry_malloc_secure (a) +#define spwq_secure_free(a) gcry_free (a) + + +#endif /*SIMPLE_PWQUERY_IMPLEMENTATION*/ /* End configuration stuff. */ + + +/* Ask the gpg-agent for a passphrase and present the user with a + DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. + If a CACHEID is not NULL it is used to locate the passphrase in in + the cache and store it under this ID. If ERRORCODE is not NULL it + should point a variable receiving an errorcode; thsi errocode might + be 0 if the user canceled the operation. The function returns NULL + to indicate an error. */ +char *simple_pwquery (const char *cacheid, + const char *tryagain, + const char *prompt, + const char *description, + int *errorcode); + + +#define SPWQ_OUT_OF_CORE 1 +#define SPWQ_IO_ERROR 2 +#define SPWQ_PROTOCOL_ERROR 3 +#define SPWQ_ERR_RESPONSE 4 +#define SPWQ_NO_AGENT 5 +#define SPWQ_SYS_ERROR 6 +#define SPWQ_GENERAL_ERROR 7 + +#endif /*SIMPLE_PWQUERY_H*/