summaryrefslogtreecommitdiffstats
path: root/common/simple-pwquery.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--common/simple-pwquery.c495
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;
+}