diff options
Diffstat (limited to '')
-rw-r--r-- | common/simple-pwquery.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/common/simple-pwquery.c b/common/simple-pwquery.c new file mode 100644 index 0000000..b8ada42 --- /dev/null +++ b/common/simple-pwquery.c @@ -0,0 +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 <https://www.gnu.org/licenses/>. + */ + +/* 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 <config.h> +#endif +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assuan.h> +#ifdef HAVE_W32_SYSTEM +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <sys/un.h> +#endif +#ifdef HAVE_LOCALE_H +#include <locale.h> +#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; +} |