diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c index e7f4af341..b8ada4214 100644 --- a/common/simple-pwquery.c +++ b/common/simple-pwquery.c @@ -1,494 +1,495 @@ /* simple-pwquery.c - A simple password query client for gpg-agent * Copyright (C) 2002, 2004, 2007 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 3 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, see . */ /* This module is intended as a simple client implementation to gpg-agent's GET_PASSPHRASE command. It 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 #ifdef HAVE_W32_SYSTEM #include #else #include #include #endif #ifdef HAVE_LOCALE_H #include #endif #define GNUPG_COMMON_NEED_AFLOCAL #include "../common/mischelp.h" #include "sysutils.h" #include "membuf.h" #define SIMPLE_PWQUERY_IMPLEMENTATION 1 #include "simple-pwquery.h" #define SPWQ_OUT_OF_CORE gpg_error_from_errno (ENOMEM) #define SPWQ_IO_ERROR gpg_error_from_errno (EIO) #define SPWQ_PROTOCOL_ERROR gpg_error (GPG_ERR_PROTOCOL_VIOLATION) #define SPWQ_ERR_RESPONSE gpg_error (GPG_ERR_INV_RESPONSE) #define SPWQ_NO_AGENT gpg_error (GPG_ERR_NO_AGENT) #define SPWQ_SYS_ERROR gpg_error_from_syserror () #define SPWQ_GENERAL_ERROR gpg_error (GPG_ERR_GENERAL) #define SPWQ_NO_PIN_ENTRY gpg_error (GPG_ERR_NO_PIN_ENTRY) #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 /* Name of the socket to be used. This is a kludge to keep on using the existsing code despite that we only support a standard socket. */ static char *default_gpg_agent_info; #ifndef HAVE_STPCPY static char * my_stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #define stpcpy(a,b) my_stpcpy((a), (b)) #endif /* Send an option to the agent */ static int agent_send_option (assuan_context_t ctx, const char *name, const char *value) { int err; char *line; 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"); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); spwq_free (line); return err; } /* Send all available options to the agent. */ static int agent_send_all_options (assuan_context_t ctx) { char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; char *dft_xauthority = NULL; char *dft_pinentry_user_data = NULL; int rc = 0; dft_display = getenv ("DISPLAY"); if (dft_display) { if ((rc = agent_send_option (ctx, "display", dft_display))) return rc; } dft_ttyname = getenv ("GPG_TTY"); #if !defined(HAVE_W32_SYSTEM) && !defined(HAVE_BROKEN_TTYNAME) if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); #endif if (dft_ttyname && *dft_ttyname) { if ((rc=agent_send_option (ctx, "ttyname", dft_ttyname))) return rc; } dft_ttytype = getenv ("TERM"); if (dft_ttyname && dft_ttytype) { if ((rc = agent_send_option (ctx, "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 (ctx, "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 (ctx, "lc-messages", dft_lc); if (old_lc) { setlocale (LC_MESSAGES, old_lc); spwq_free (old_lc); } if (rc) return rc; #endif } #endif /*HAVE_SETLOCALE*/ /* Send the XAUTHORITY variable. */ dft_xauthority = getenv ("XAUTHORITY"); if (dft_xauthority) { /* We ignore errors here because older gpg-agents don't support this option. */ agent_send_option (ctx, "xauthority", dft_xauthority); } /* Send the PINENTRY_USER_DATA variable. */ dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA"); if (dft_pinentry_user_data) { /* We ignore errors here because older gpg-agents don't support this option. */ agent_send_option (ctx, "pinentry-user-data", dft_pinentry_user_data); } /* Tell the agent that we support Pinentry notifications. No error checking so that it will work with older agents. */ assuan_transact (ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); 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 (assuan_context_t *ctx) { int rc; char *infostr; infostr = default_gpg_agent_info; if ( !infostr || !*infostr ) { #ifdef SPWQ_USE_LOGGING log_error (_("no gpg-agent running in this session\n")); #endif + *ctx = NULL; return SPWQ_NO_AGENT; } rc = assuan_new (ctx); if (rc) return rc; rc = assuan_socket_connect (*ctx, infostr, 0, 0); if (rc) { #ifdef SPWQ_USE_LOGGING log_error (_("can't connect to '%s': %s\n"), infostr, gpg_strerror (rc)); #endif goto errout; } rc = agent_send_all_options (*ctx); if (rc) { #ifdef SPWQ_USE_LOGGING log_error (_("problem setting the gpg-agent options\n")); #endif goto errout; } return 0; errout: assuan_release (*ctx); *ctx = NULL; return rc; } /* Copy text to BUFFER and escape as required. Return a pointer 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; const unsigned char *s = (unsigned char *)text; char *p = buffer; for (i=0; s[i]; i++) { if (s[i] < ' ' || s[i] == '+') { sprintf (p, "%%%02X", s[i]); p += 3; } else if (s[i] == ' ') *p++ = '+'; else *p++ = s[i]; } return p; } /* Set the name of the default socket to NAME. */ int simple_pw_set_socket (const char *name) { spwq_free (default_gpg_agent_info); default_gpg_agent_info = NULL; if (name) { default_gpg_agent_info = spwq_malloc (strlen (name) + 1); if (!default_gpg_agent_info) return SPWQ_OUT_OF_CORE; strcpy (default_gpg_agent_info, name); } return 0; } /* This is the default inquiry callback. It merely handles the Pinentry notification. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { (void)opaque; if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) { gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10)); /* We do not return errors to avoid breaking other code. */ } else { #ifdef SPWQ_USE_LOGGING log_debug ("ignoring gpg-agent inquiry '%s'\n", line); #endif } return 0; } /* Ask the gpg-agent for a passphrase and present the user with a DESCRIPTION, a PROMPT and optionally with a TRYAGAIN extra text. If a CACHEID is not NULL it is used to locate the passphrase in the cache and store it under this ID. If OPT_CHECK is true gpg-agent is asked to apply some checks on the passphrase security. If ERRORCODE is not NULL it should point a variable receiving an errorcode; this error code 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 opt_check, int *errorcode) { int rc; assuan_context_t ctx; membuf_t data; char *result = NULL; char *pw = NULL; char *p; size_t n; rc = agent_open (&ctx); 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 + 10 + 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; if (opt_check) p = stpcpy (p, "--check "); 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'; init_membuf_secure (&data, 64); rc = assuan_transact (ctx, line, put_membuf_cb, &data, default_inq_cb, NULL, NULL, NULL); spwq_free (line); /* Older Pinentries return the old assuan error code for canceled which gets translated by libassuan to GPG_ERR_ASS_CANCELED and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) { p = get_membuf (&data, &n); if (p) wipememory (p, n); spwq_free (p); } else { put_membuf (&data, "", 1); result = get_membuf (&data, NULL); if (pw == NULL) rc = gpg_error_from_syserror (); } } leave: if (errorcode) *errorcode = rc; assuan_release (ctx); return result; } /* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */ int simple_pwclear (const char *cacheid) { char line[500]; char *p; /* We need not more than 50 characters for the command and the terminating nul. */ if (strlen (cacheid) * 3 > sizeof (line) - 50) return SPWQ_PROTOCOL_ERROR; strcpy (line, "CLEAR_PASSPHRASE "); p = line + 17; p = copy_and_escape (p, cacheid); *p++ = '\n'; *p++ = '\0'; return simple_query (line); } /* Perform the simple query QUERY (which must be new-line and 0 terminated) and return the error code. */ int simple_query (const char *query) { assuan_context_t ctx; int rc; rc = agent_open (&ctx); if (rc) return rc; rc = assuan_transact (ctx, query, NULL, NULL, NULL, NULL, NULL, NULL); assuan_release (ctx); return rc; }