diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c
index 2369dffc2..9d8d93aec 100644
--- a/agent/call-pinentry.c
+++ b/agent/call-pinentry.c
@@ -1,2196 +1,2196 @@
/* call-pinentry.c - Spawn the pinentry to query stuff from the user
* Copyright (C) 2001, 2002, 2004, 2007, 2008,
* 2010 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
# include
# include
# include
# include
#endif
#include
#include "agent.h"
#include
#include "../common/sysutils.h"
#include "../common/i18n.h"
#include "../common/zb32.h"
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
/* Because access to the pinentry must be serialized (it is and shall
be a global mutually exclusive dialog) we better timeout pending
requests after some time. 1 minute seem to be a reasonable
time. */
#define LOCK_TIMEOUT (1*60)
/* Define the number of bits to use for a generated pin. The
* passphrase will be rendered as zbase32 which results for 150 bits
* in a string of 30 characters. That fits nicely into the 5
* character blocking which pinentry can do. 128 bits would actually
* be sufficient but can't be formatted nicely. Please do not change
* this value because pattern check files may let such passwords
* always pass. */
#define DEFAULT_GENPIN_BITS 150
/* The assuan context of the current pinentry. */
static assuan_context_t entry_ctx;
/* A list of features of the current pinentry. */
static struct
{
/* The Pinentry support RS+US tabbing. This means that a RS (0x1e)
* starts a new tabbing block in which a US (0x1f) followed by a
* colon marks a colon. A pinentry can use this to pretty print
* name value pairs. */
unsigned int tabbing:1;
} entry_features;
/* A mutex used to serialize access to the pinentry. */
static npth_mutex_t entry_lock;
/* The thread ID of the popup working thread. */
static npth_t popup_tid;
/* A flag used in communication between the popup working thread and
its stop function. */
static int popup_finished;
/* Data to be passed to our callbacks, */
struct entry_parm_s
{
int lines;
size_t size;
unsigned char *buffer;
int status;
unsigned int constraints_flags;
};
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_call_pinentry (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&entry_lock, NULL);
if (err)
log_fatal ("error initializing mutex: %s\n", strerror (err));
initialized = 1;
}
}
/* This function may be called to print information pertaining to the
current state of this module to the log. */
void
agent_query_dump_state (void)
{
log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%lx\n",
entry_ctx, (long)assuan_get_pid (entry_ctx),
(unsigned long)popup_tid);
}
/* Called to make sure that a popup window owned by the current
connection gets closed. */
void
agent_reset_query (ctrl_t ctrl)
{
if (entry_ctx && popup_tid && ctrl->pinentry_active)
{
agent_popup_message_stop (ctrl);
}
}
/* Unlock the pinentry so that another thread can start one and
disconnect that pinentry - we do this after the unlock so that a
stalled pinentry does not block other threads. Fixme: We should
have a timeout in Assuan for the disconnect operation. */
static gpg_error_t
unlock_pinentry (ctrl_t ctrl, gpg_error_t rc)
{
assuan_context_t ctx = entry_ctx;
int err;
if (rc)
{
if (DBG_IPC)
log_debug ("error calling pinentry: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
/* Change the source of the error to pinentry so that the final
consumer of the error code knows that the problem is with
pinentry. For backward compatibility we do not do that for
some common error codes. */
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_PIN_ENTRY:
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
case GPG_ERR_ASS_UNKNOWN_INQUIRE:
case GPG_ERR_ASS_TOO_MUCH_DATA:
case GPG_ERR_NO_PASSPHRASE:
case GPG_ERR_BAD_PASSPHRASE:
case GPG_ERR_BAD_PIN:
break;
case GPG_ERR_CORRUPTED_PROTECTION:
/* This comes from gpg-agent. */
break;
default:
rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc));
break;
}
}
if (--ctrl->pinentry_active == 0)
{
entry_ctx = NULL;
err = npth_mutex_unlock (&entry_lock);
if (err)
{
log_error ("failed to release the entry lock: %s\n", strerror (err));
if (!rc)
rc = gpg_error_from_errno (err);
}
assuan_release (ctx);
}
return rc;
}
/* Helper for at_fork_cb which can also be called by the parent to
* show which envvars will be set. */
static void
atfork_core (ctrl_t ctrl, int debug_mode)
{
int iterator = 0;
const char *name, *assname, *value;
while ((name = session_env_list_stdenvnames (&iterator, &assname)))
{
/* For all new envvars (!ASSNAME) and the two medium old ones
* which do have an assuan name but are conveyed using
* environment variables, update the environment of the forked
* process. We also pass DISPLAY despite that --display is also
* used when exec-ing the pinentry. The reason is that for
* example the qt5ct tool does not have any arguments and thus
* relies on the DISPLAY envvar. The use case here is a global
* envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is
* useful when using the Qt pinentry under GNOME or XFCE.
*/
if (!assname
|| (!opt.keep_display && !strcmp (name, "DISPLAY"))
|| !strcmp (name, "XAUTHORITY")
|| !strcmp (name, "PINENTRY_USER_DATA"))
{
value = session_env_getenv (ctrl->session_env, name);
if (value)
{
if (debug_mode)
log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value);
else
gnupg_setenv (name, value, 1);
}
}
}
}
/* To make sure we leave no secrets in our image after forking of the
pinentry, we use this callback. */
static void
atfork_cb (void *opaque, int where)
{
ctrl_t ctrl = opaque;
if (!where)
{
gcry_control (GCRYCTL_TERM_SECMEM);
atfork_core (ctrl, 0);
}
}
/* Status line callback for the FEATURES status. */
static gpg_error_t
getinfo_features_cb (void *opaque, const char *line)
{
const char *args;
char **tokens;
int i;
(void)opaque;
if ((args = has_leading_keyword (line, "FEATURES")))
{
tokens = strtokenize (args, " ");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; tokens[i]; i++)
if (!strcmp (tokens[i], "tabbing"))
entry_features.tabbing = 1;
xfree (tokens);
}
return 0;
}
static gpg_error_t
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
{
unsigned long *pid = opaque;
char pidbuf[50];
/* There is only the pid in the server's response. */
if (length >= sizeof pidbuf)
length = sizeof pidbuf -1;
if (length)
{
strncpy (pidbuf, buffer, length);
pidbuf[length] = 0;
*pid = strtoul (pidbuf, NULL, 10);
}
return 0;
}
/* Fork off the pin entry if this has not already been done. Note,
that this function must always be used to acquire the lock for the
pinentry - we will serialize _all_ pinentry calls.
*/
static gpg_error_t
start_pinentry (ctrl_t ctrl)
{
int rc = 0;
const char *full_pgmname;
const char *pgmname;
assuan_context_t ctx;
const char *argv[5];
assuan_fd_t no_close_list[3];
int i;
const char *tmpstr;
unsigned long pinentry_pid;
const char *value;
struct timespec abstime;
char *flavor_version;
int err;
if (ctrl->pinentry_active)
{
/* It's trying to use pinentry recursively. In this situation,
the thread holds ENTRY_LOCK already. */
ctrl->pinentry_active++;
return 0;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += LOCK_TIMEOUT;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error_from_errno (rc);
log_error (_("failed to acquire the pinentry lock: %s\n"),
gpg_strerror (rc));
return rc;
}
if (entry_ctx)
return 0;
if (opt.verbose)
log_info ("starting a new PIN Entry\n");
#ifdef HAVE_W32_SYSTEM
fflush (stdout);
fflush (stderr);
#endif
if (fflush (NULL))
{
#ifndef HAVE_W32_SYSTEM
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
#endif
log_error ("error flushing pending output: %s\n", strerror (errno));
/* At least Windows XP fails here with EBADF. According to docs
and Wine an fflush(NULL) is the same as _flushall. However
the Wine implementation does not flush stdin,stdout and stderr
- see above. Let's try to ignore the error. */
#ifndef HAVE_W32_SYSTEM
return unlock_pinentry (ctrl, tmperr);
#endif
}
full_pgmname = opt.pinentry_program;
if (!full_pgmname || !*full_pgmname)
full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
if ( !(pgmname = strrchr (full_pgmname, '/')))
pgmname = full_pgmname;
else
pgmname++;
/* OS X needs the entire file name in argv[0], so that it can locate
the resource bundle. For other systems we stick to the usual
convention of supplying only the name of the program. */
#ifdef __APPLE__
argv[0] = full_pgmname;
#else /*!__APPLE__*/
argv[0] = pgmname;
#endif /*__APPLE__*/
if (!opt.keep_display
&& (value = session_env_getenv (ctrl->session_env, "DISPLAY")))
{
argv[1] = "--display";
argv[2] = value;
argv[3] = NULL;
}
else
argv[1] = NULL;
i=0;
if (!opt.running_detached)
no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
no_close_list[i] = ASSUAN_INVALID_FD;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
return rc;
}
ctrl->pinentry_active = 1;
entry_ctx = ctx;
/* We don't want to log the pinentry communication to make the logs
easier to read. We might want to add a new debug option to enable
pinentry logging. */
#ifdef ASSUAN_NO_LOGGING
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
#endif
/* Connect to the pinentry and perform initial handshaking. Note
that atfork is used to change the environment for pinentry. We
start the server in detached mode to suppress the console window
under Windows. */
rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv,
no_close_list, atfork_cb, ctrl,
ASSUAN_PIPE_CONNECT_DETACHED);
if (rc)
{
log_error ("can't connect to the PIN entry module '%s': %s\n",
full_pgmname, gpg_strerror (rc));
return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY));
}
if (DBG_IPC)
log_debug ("connection to PIN entry established\n");
if (opt.debug_pinentry)
atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */
value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA");
if (value != NULL)
{
char *optstr;
if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
rc = assuan_transact (entry_ctx,
opt.no_grab? "OPTION no-grab":"OPTION grab",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED
|| gpg_err_code (rc) == GPG_ERR_UNKNOWN_OPTION)
{
if (opt.verbose)
log_info ("Option no-grab/grab is ignored by pinentry.\n");
/* Keep going even if the feature is not supported. */
}
else
return unlock_pinentry (ctrl, rc);
}
value = session_env_getenv (ctrl->session_env, "GPG_TTY");
if (value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
value = session_env_getenv (ctrl->session_env, "TERM");
if (value && *value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_ctype)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_messages)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_external_cache)
{
/* Indicate to the pinentry that it may read from an external cache.
It is essential that the pinentry respect this. If the
cached password is not up to date and retry == 1, then, using
a version of GPG Agent that doesn't support this, won't issue
another pin request and the user won't get a chance to
correct the password. */
rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_emacs_pinentry)
{
/* Indicate to the pinentry that it may read passphrase through
Emacs minibuffer, if possible. */
rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
{
/* Provide a few default strings for use by the pinentries. This
* may help a pinentry to avoid implementing localization code.
* Note that gpg-agent has been set to utf-8 so that the strings
* are in the expected encoding. */
static const struct { const char *key, *value; int what; } tbl[] = {
/* TRANSLATORS: These are labels for buttons etc as used in
* Pinentries. In your translation copy the text before the
* second vertical bar verbatim; translate only the following
* text. An underscore indicates that the next letter should be
* used as an accelerator. Double the underscore to have
* pinentry display a literal underscore. */
{ "ok", N_("|pinentry-label|_OK") },
{ "cancel", N_("|pinentry-label|_Cancel") },
{ "yes", N_("|pinentry-label|_Yes") },
{ "no", N_("|pinentry-label|_No") },
{ "prompt", N_("|pinentry-label|PIN:") },
{ "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 },
{ "cf-visi",N_("Do you really want to make your "
"passphrase visible on the screen?") },
{ "tt-visi",N_("|pinentry-tt|Make passphrase visible") },
{ "tt-hide",N_("|pinentry-tt|Hide passphrase") },
{ "capshint", N_("Caps Lock is on") },
{ NULL, NULL}
};
char *optstr;
int idx;
const char *s, *s2;
for (idx=0; tbl[idx].key; idx++)
{
if (!opt.allow_external_cache && tbl[idx].what == 1)
continue; /* No need for it. */
s = L_(tbl[idx].value);
if (*s == '|' && (s2=strchr (s+1,'|')))
s = s2+1;
if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell the pinentry that we would prefer that the given character
is used as the invisible character by the entry widget. */
if (opt.pinentry_invisible_char)
{
char *optstr;
if ((optstr = xtryasprintf ("OPTION invisible-char=%s",
opt.pinentry_invisible_char)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
if (opt.pinentry_timeout)
{
char *optstr;
if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing. */
xfree (optstr);
}
}
/* Tell the pinentry the name of a file it shall touch after having
messed with the tty. This is optional and only supported by
newer pinentries and thus we do no error checking. */
tmpstr = opt.pinentry_touch_file;
if (tmpstr && !strcmp (tmpstr, "/dev/null"))
tmpstr = NULL;
else if (!tmpstr)
tmpstr = get_agent_socket_name ();
if (tmpstr)
{
char *optstr;
if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 )
;
else
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell Pinentry about our client. */
if (ctrl->client_pid)
{
char *optstr;
const char *nodename = "";
#ifndef HAVE_W32_SYSTEM
struct utsname utsbuf;
if (!uname (&utsbuf))
nodename = utsbuf.nodename;
#endif /*!HAVE_W32_SYSTEM*/
if ((optstr = xtryasprintf ("OPTION owner=%lu/%d %s",
ctrl->client_pid, ctrl->client_uid,
nodename)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
/* Ask the pinentry for its version and flavor and store that as a
* string in MB. This information is useful for helping users to
* figure out Pinentry problems. Note that "flavor" may also return
* a status line with the features; we use a dedicated handler for
* that. */
{
membuf_t mb;
init_membuf (&mb, 256);
if (assuan_transact (entry_ctx, "GETINFO flavor",
put_membuf_cb, &mb,
NULL, NULL,
getinfo_features_cb, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO version",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO ttyinfo",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "? ? ?");
put_membuf (&mb, "", 1);
flavor_version = get_membuf (&mb, NULL);
}
/* Now ask the Pinentry for its PID. If the Pinentry is new enough
it will send the pid back and we will use an inquire to notify
our client. The client may answer the inquiry either with END or
with CAN to cancel the pinentry. */
rc = assuan_transact (entry_ctx, "GETINFO pid",
getinfo_pid_cb, &pinentry_pid,
NULL, NULL, NULL, NULL);
if (rc)
{
log_info ("You may want to update to a newer pinentry\n");
rc = 0;
}
else if (!rc && pinentry_pid == (unsigned long)(-1L))
log_error ("pinentry did not return a PID\n");
else
{
rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version);
if (gpg_err_code (rc) == GPG_ERR_CANCELED
|| gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT,
gpg_err_code (rc)));
rc = 0;
}
xfree (flavor_version);
return rc;
}
/* Returns True if the pinentry is currently active. If WAITSECONDS is
greater than zero the function will wait for this many seconds
before returning. */
int
pinentry_active_p (ctrl_t ctrl, int waitseconds)
{
int err;
(void)ctrl;
if (waitseconds > 0)
{
struct timespec abstime;
int rc;
npth_clock_gettime (&abstime);
abstime.tv_sec += waitseconds;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error (GPG_ERR_INTERNAL);
return rc;
}
}
else
{
err = npth_mutex_trylock (&entry_lock);
if (err)
return gpg_error (GPG_ERR_LOCKED);
}
err = npth_mutex_unlock (&entry_lock);
if (err)
log_error ("failed to release the entry lock at %d: %s\n", __LINE__,
strerror (errno));
return 0;
}
static gpg_error_t
getpin_cb (void *opaque, const void *buffer, size_t length)
{
struct entry_parm_s *parm = opaque;
if (!buffer)
return 0;
/* we expect the pin to fit on one line */
if (parm->lines || length >= parm->size)
return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA);
/* fixme: we should make sure that the assuan buffer is allocated in
secure memory or read the response byte by byte */
memcpy (parm->buffer, buffer, length);
parm->buffer[length] = 0;
parm->lines++;
return 0;
}
static int
all_digitsp( const char *s)
{
for (; *s && *s >= '0' && *s <= '9'; s++)
;
return !*s;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary Nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. Parsing stops at the end of the string or
a white space character. */
static char *
unescape_passphrase_string (const unsigned char *s)
{
char *buffer, *d;
buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1);
if (!buffer)
return NULL;
while (*s && !spacep (s))
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d = xtoi_2 (s);
if (!*d)
*d = '\xff';
d++;
s += 2;
}
else if (*s == '+')
{
*d++ = ' ';
s++;
}
else
*d++ = *s++;
}
*d = 0;
return buffer;
}
/* Estimate the quality of the passphrase PW and return a value in the
range 0..100. */
static int
estimate_passphrase_quality (const char *pw)
{
int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3;
int length;
const char *s;
if (goodlength < 1)
return 0;
for (length = 0, s = pw; *s; s++)
if (!spacep (s))
length ++;
if (length > goodlength)
return 100;
return ((length*10) / goodlength)*10;
}
/* Generate a random passphrase in zBase32 encoding (RFC-6189) to be
* used by Pinentry to suggest a passphrase. Note that we have the
* same algorithm in gpg.c for --gen-random at level 30. It is
* important that we always output exactly 30 characters to match the
* special exception we have in the pattern file for symmetric
* encryption. */
static char *
generate_pin (void)
{
unsigned int nbits = DEFAULT_GENPIN_BITS;
size_t nbytes = nbytes = (nbits + 7) / 8;
void *rand;
char *generated;
rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM);
if (!rand)
{
log_error ("failed to generate random pin\n");
return NULL;
}
generated = zb32_encode (rand, nbits);
gcry_free (rand);
return generated;
}
/* Handle inquiries. */
struct inq_cb_parm_s
{
assuan_context_t ctx;
unsigned int flags; /* CHECK_CONSTRAINTS_... */
int genpinhash_valid;
char genpinhash[32]; /* Hash of the last generated pin. */
};
/* Return true if PIN is indentical to the last generated pin. */
static int
is_generated_pin (struct inq_cb_parm_s *parm, const char *pin)
{
char hashbuf[32];
if (!parm->genpinhash_valid)
return 0;
if (!*pin)
return 0;
/* Note that we compare the hash so that we do not need to save the
* generated PIN longer than needed. */
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin));
if (!memcmp (hashbuf, parm->genpinhash, 32))
return 1; /* yes, it is the same. */
return 0;
}
static gpg_error_t
inq_cb (void *opaque, const char *line)
{
struct inq_cb_parm_s *parm = opaque;
gpg_error_t err;
const char *s;
char *pin;
if ((s = has_leading_keyword (line, "QUALITY")))
{
char numbuf[20];
int percent;
pin = unescape_passphrase_string (s);
if (!pin)
err = gpg_error_from_syserror ();
else
{
percent = estimate_passphrase_quality (pin);
if (check_passphrase_constraints (NULL, pin, parm->flags, NULL))
percent = -percent;
snprintf (numbuf, sizeof numbuf, "%d", percent);
err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf));
xfree (pin);
}
}
else if ((s = has_leading_keyword (line, "CHECKPIN")))
{
char *errtext = NULL;
size_t errtextlen;
if (!opt.enforce_passphrase_constraints)
{
log_error ("unexpected inquiry 'CHECKPIN' without enforced "
"passphrase constraints\n");
err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD);
goto leave;
}
pin = unescape_passphrase_string (s);
if (!pin)
err = gpg_error_from_syserror ();
else
{
if (!is_generated_pin (parm, pin)
&& check_passphrase_constraints (NULL, pin,parm->flags, &errtext))
{
if (errtext)
{
/* Unescape the percent-escaped errtext because
assuan_send_data escapes it again. */
errtextlen = percent_unescape_inplace (errtext, 0);
err = assuan_send_data (parm->ctx, errtext, errtextlen);
}
else
{
log_error ("passphrase check failed without error text\n");
err = gpg_error (GPG_ERR_GENERAL);
}
}
else
{
err = assuan_send_data (parm->ctx, NULL, 0);
}
xfree (errtext);
xfree (pin);
}
}
else if ((s = has_leading_keyword (line, "GENPIN")))
{
int wasconf;
parm->genpinhash_valid = 0;
pin = generate_pin ();
if (!pin)
{
log_error ("failed to generate a passphrase\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, pin, strlen (pin));
if (!wasconf)
assuan_end_confidential (parm->ctx);
gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin));
parm->genpinhash_valid = 1;
xfree (pin);
}
else
{
log_error ("unsupported inquiry '%s' from pinentry\n", line);
err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
leave:
return err;
}
/* Helper to setup pinentry for genpin action. */
static gpg_error_t
setup_genpin (ctrl_t ctrl)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *tmpstr, *tmpstr2;
const char *tooltip;
(void)ctrl;
/* TRANSLATORS: This string is displayed by Pinentry as the label
for generating a passphrase. */
tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v");
snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:"");
xfree (tmpstr);
err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
|| gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old Pinentry versions. */
else if (err)
return err;
tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0);
if (tmpstr2)
tooltip = tmpstr2;
else
{
/* TRANSLATORS: This string is a tooltip, shown by pinentry when
hovering over the generate button. Please use an appropriate
string to describe what this is about. The length of the
tooltip is limited to about 900 characters. If you do not
translate this entry, a default English text (see source)
will be used. The strcmp thingy is there to detect a
non-translated string. */
tooltip = L_("pinentry.genpin.tooltip");
if (!strcmp ("pinentry.genpin.tooltip", tooltip))
tooltip = "Suggest a random passphrase.";
}
tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
xfree (tmpstr2);
snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:"");
xfree (tmpstr);
err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
|| gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old pinentry versions. */
else if (err)
return err;
return 0;
}
/* Helper to setup pinentry for formatted passphrase. */
static gpg_error_t
setup_formatted_passphrase (ctrl_t ctrl)
{
static const struct { const char *key, *help_id, *value; } tbl[] = {
{ "hint", "pinentry.formatted_passphrase.hint",
/* TRANSLATORS: This is a text shown by pinentry if the option
for formatted passphrase is enabled. The length is
limited to about 900 characters. */
N_("Note: The blanks are not part of the passphrase.") },
{ NULL, NULL }
};
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
int idx;
char *tmpstr;
const char *s;
char *escapedstr;
(void)ctrl;
if (opt.pinentry_formatted_passphrase)
{
snprintf (line, DIM(line), "OPTION formatted-passphrase");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
for (idx=0; tbl[idx].key; idx++)
{
tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
if (tmpstr)
s = tmpstr;
else
s = L_(tbl[idx].value);
escapedstr = try_percent_escape (s, "\t\r\n\f\v");
xfree (tmpstr);
if (escapedstr && *escapedstr)
{
snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s",
tbl[idx].key, escapedstr);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = 0;
xfree (escapedstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
}
}
return 0;
}
/* Helper to setup pinentry for enforced passphrase constraints. */
static gpg_error_t
setup_enforced_constraints (ctrl_t ctrl)
{
static const struct { const char *key, *help_id, *value; } tbl[] = {
{ "hint-short", "pinentry.constraints.hint.short", NULL },
{ "hint-long", "pinentry.constraints.hint.long", NULL },
/* TRANSLATORS: This is a text shown by pinentry as title of a dialog
telling the user that the entered new passphrase does not satisfy
the passphrase constraints. Please keep it short. */
{ "error-title", NULL, N_("Passphrase Not Allowed") },
{ NULL, NULL }
};
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
int idx;
char *tmpstr;
const char *s;
char *escapedstr;
(void)ctrl;
if (opt.enforce_passphrase_constraints)
{
snprintf (line, DIM(line), "OPTION constraints-enforce");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
for (idx=0; tbl[idx].key; idx++)
{
tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
if (tmpstr)
s = tmpstr;
else if (tbl[idx].value)
s = L_(tbl[idx].value);
else
{
log_error ("no help string found for %s\n", tbl[idx].help_id);
continue;
}
escapedstr = try_percent_escape (s, "\t\r\n\f\v");
xfree (tmpstr);
if (escapedstr && *escapedstr)
{
snprintf (line, DIM(line), "OPTION constraints-%s=%s",
tbl[idx].key, escapedstr);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = 0; /* Ignore an empty string (would give an IPC error). */
xfree (escapedstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
}
}
return 0;
}
/* Helper for agent_askpin and agent_get_passphrase. */
static gpg_error_t
setup_qualitybar (ctrl_t ctrl)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *tmpstr, *tmpstr2;
const char *tooltip;
(void)ctrl;
/* TRANSLATORS: This string is displayed by Pinentry as the label
for the quality bar. */
tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old Pinentry versions. */
else if (rc)
return rc;
tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
if (tmpstr2)
tooltip = tmpstr2;
else
{
/* TRANSLATORS: This string is a tooltip, shown by pinentry when
hovering over the quality bar. Please use an appropriate
string to describe what this is about. The length of the
tooltip is limited to about 900 characters. If you do not
translate this entry, a default english text (see source)
will be used. */
tooltip = L_("pinentry.qualitybar.tooltip");
if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
tooltip = ("The quality of the text entered above.\n"
"Please ask your administrator for "
"details about the criteria.");
}
tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
xfree (tmpstr2);
snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old pinentry versions. */
else if (rc)
return rc;
return 0;
}
/* Check the button_info line for a close action. Also check for the
PIN_REPEATED flag. */
static gpg_error_t
pinentry_status_cb (void *opaque, const char *line)
{
unsigned int *flag = opaque;
const char *args;
if ((args = has_leading_keyword (line, "BUTTON_INFO")))
{
if (!strcmp (args, "close"))
*flag |= PINENTRY_STATUS_CLOSE_BUTTON;
}
else if (has_leading_keyword (line, "PIN_REPEATED"))
{
*flag |= PINENTRY_STATUS_PIN_REPEATED;
}
else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
{
*flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
}
return 0;
}
/* Build a SETDESC command line. This is a dedicated function so that
* it can remove control characters which are not supported by the
* current Pinentry. */
static void
build_cmd_setdesc (char *line, size_t linelen, const char *desc)
{
char *src, *dst;
snprintf (line, linelen, "SETDESC %s", desc);
if (!entry_features.tabbing)
{
/* Remove RS and US. */
for (src=dst=line; *src; src++)
if (!strchr ("\x1e\x1f", *src))
*dst++ = *src;
*dst = 0;
}
}
/* Watch the socket's EOF condition, while checking finish of
foreground thread. When EOF condition is detected, terminate
the pinentry process behind the assuan pipe.
*/
static void *
watch_sock (void *arg)
{
pid_t pid = assuan_get_pid (entry_ctx);
while (1)
{
int err;
fd_set fdset;
struct timeval timeout = { 0, 500000 };
gnupg_fd_t sock = *(gnupg_fd_t *)arg;
if (sock == GNUPG_INVALID_FD)
return NULL;
FD_ZERO (&fdset);
FD_SET (FD2INT (sock), &fdset);
- err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout);
+ err = npth_select (FD2NUM (sock)+1, &fdset, NULL, NULL, &timeout);
if (err < 0)
{
if (errno == EINTR)
continue;
else
return NULL;
}
/* Possibly, it's EOF. */
if (err > 0)
break;
}
if (pid == (pid_t)(-1))
; /* No pid available can't send a kill. */
#ifdef HAVE_W32_SYSTEM
/* Older versions of assuan set PID to 0 on Windows to indicate an
invalid value. */
else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0)
TerminateProcess ((HANDLE)pid, 1);
#else
else if (pid > 0)
kill (pid, SIGINT);
#endif
return NULL;
}
static gpg_error_t
watch_sock_start (gnupg_fd_t *sock_p, npth_t *thread_p)
{
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
{
log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
err = npth_create (thread_p, &tattr, watch_sock, sock_p);
npth_attr_destroy (&tattr);
if (err)
{
log_error ("do_getpin: error spawning thread: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
return 0;
}
static void
watch_sock_end (gnupg_fd_t *sock_p, npth_t *thread_p)
{
int err;
*sock_p = GNUPG_INVALID_FD;
err = npth_join (*thread_p, NULL);
if (err)
log_error ("watch_sock_end: error joining thread: %s\n", strerror (err));
}
/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread
detecting the socket's EOF.
*/
static gpg_error_t
do_getpin (ctrl_t ctrl, struct entry_parm_s *parm)
{
gpg_error_t rc;
gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
npth_t thread;
int wasconf;
struct inq_cb_parm_s inq_cb_parm;
rc = watch_sock_start (&sock_watched, &thread);
if (rc)
return rc;
inq_cb_parm.ctx = entry_ctx;
inq_cb_parm.flags = parm->constraints_flags;
inq_cb_parm.genpinhash_valid = 0;
wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (entry_ctx);
rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm,
inq_cb, &inq_cb_parm,
pinentry_status_cb, &parm->status);
if (!wasconf)
assuan_end_confidential (entry_ctx);
if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer))
parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED;
else
parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED;
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error 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);
/* Change error code in case the window close button was clicked
to cancel the operation. */
if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON)
&& gpg_err_code (rc) == GPG_ERR_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
watch_sock_end (&sock_watched, &thread);
return rc;
}
/* Call the Entry and ask for the PIN. We do check for a valid PIN
number here and repeat it as long as we have invalid formed
numbers. KEYINFO and CACHE_MODE are used to tell pinentry something
about the key. */
gpg_error_t
agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *initial_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode)
{
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
const char *errtext = NULL;
int is_pin = 0;
int is_generated;
if (opt.batch)
return 0; /* fixme: we should return BAD PIN */
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
unsigned char *passphrase;
size_t size;
*pininfo->pin = 0; /* Reset the PIN. */
rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size,
pininfo->max_length - 1);
if (rc)
return rc;
memcpy(&pininfo->pin, passphrase, size);
xfree(passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
}
return rc;
}
return gpg_error(GPG_ERR_NO_PIN_ENTRY);
}
if (!pininfo || pininfo->max_length < 1)
return gpg_error (GPG_ERR_INV_VALUE);
if (!desc_text && pininfo->min_digits)
desc_text = L_("Please enter your PIN, so that the secret key "
"can be unlocked for this session");
else if (!desc_text)
desc_text = L_("Please enter your passphrase, so that the secret key "
"can be unlocked for this session");
if (prompt_text)
is_pin = !!strstr (prompt_text, "PIN");
else
is_pin = desc_text && strstr (desc_text, "PIN");
rc = start_pinentry (ctrl);
if (rc)
return rc;
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
build_cmd_setdesc (line, DIM(line), desc_text);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s",
prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
/* If a passphrase quality indicator has been requested and a
minimum passphrase length has not been disabled, send the command
to the pinentry. */
if (pininfo->with_qualitybar && opt.min_passphrase_len )
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (initial_errtext)
{
snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATERROR %s",
L_("does not match - try again"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
pininfo->with_repeat = 0; /* Pinentry does not support it. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATOK %s",
L_("Passphrases match."));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
rc = 0; /* Pinentry does not support it. */
}
}
pininfo->repeat_okay = 0;
pininfo->status = 0;
for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
{
memset (&parm, 0, sizeof parm);
parm.size = pininfo->max_length;
*pininfo->pin = 0; /* Reset the PIN. */
parm.buffer = (unsigned char*)pininfo->pin;
parm.constraints_flags = pininfo->constraints_flags;
if (errtext)
{
/* TRANSLATORS: The string is appended to an error message in
the pinentry. The %s is the actual error message, the
two %d give the current and maximum number of tries. */
snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
errtext, pininfo->failed_tries+1, pininfo->max_tries);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
errtext = NULL;
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = do_getpin (ctrl, &parm);
pininfo->status = parm.status;
is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
errtext = is_pin? L_("PIN too long")
: L_("Passphrase too long");
else if (rc)
return unlock_pinentry (ctrl, rc);
if (!errtext && pininfo->min_digits && !is_generated)
{
/* do some basic checks on the entered PIN. */
if (!all_digitsp (pininfo->pin))
errtext = L_("Invalid characters in PIN");
else if (pininfo->max_digits
&& strlen (pininfo->pin) > pininfo->max_digits)
errtext = L_("PIN too long");
else if (strlen (pininfo->pin) < pininfo->min_digits)
errtext = L_("PIN too short");
}
if (!errtext && pininfo->check_cb && !is_generated)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
/* When pinentry cache causes an error, return now. */
if (rc
&& (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
return unlock_pinentry (ctrl, rc);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{
if (pininfo->cb_errtext)
errtext = pininfo->cb_errtext;
else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
|| gpg_err_code (rc) == GPG_ERR_BAD_PIN)
errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
}
else if (rc)
return unlock_pinentry (ctrl, rc);
}
if (!errtext)
{
if (pininfo->with_repeat
&& (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
pininfo->repeat_okay = 1;
return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
}
if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
/* The password was read from the cache. Don't count this
against the retry count. */
pininfo->failed_tries --;
}
}
return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
: GPG_ERR_BAD_PASSPHRASE));
}
/* Ask for the passphrase using the supplied arguments. The returned
passphrase needs to be freed by the caller. PININFO is optional
and can be used to have constraints checking while the pinentry
dialog is open (like what we do in agent_askpin). This is very
similar to agent_askpin and we should eventually merge the two
functions. */
int
agent_get_passphrase (ctrl_t ctrl,
char **retpass, const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode,
struct pin_entry_info_s *pininfo)
{
int rc;
int is_pin;
int is_generated;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
*retpass = NULL;
if (opt.batch)
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
unsigned char *passphrase;
size_t size;
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo)
{
*pininfo->pin = 0; /* Reset the PIN. */
rc = pinentry_loopback (ctrl, "PASSPHRASE",
&passphrase, &size,
pininfo->max_length - 1);
if (rc)
return rc;
memcpy (&pininfo->pin, passphrase, size);
wipememory (passphrase, size);
xfree (passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
}
return rc;
}
else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
/* Legacy variant w/o PININFO. */
return pinentry_loopback (ctrl, "PASSPHRASE",
(unsigned char **)retpass, &size,
MAX_PASSPHRASE_LEN);
}
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
/* Set IS_PIN and if needed a default prompt. */
if (prompt)
is_pin = !!strstr (prompt, "PIN");
else
{
is_pin = desc && strstr (desc, "PIN");
prompt = is_pin? L_("PIN:"): L_("Passphrase:");
}
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s", prompt);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if ((with_qualitybar || (pininfo && pininfo->with_qualitybar))
&& opt.min_passphrase_len)
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (errtext)
{
snprintf (line, DIM(line), "SETERROR %s", errtext);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = setup_formatted_passphrase (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
if (!pininfo)
{
/* Legacy method without PININFO. */
memset (&parm, 0, sizeof parm);
parm.size = ASSUAN_LINELENGTH/2 - 5;
parm.buffer = gcry_malloc_secure (parm.size+10);
if (!parm.buffer)
return unlock_pinentry (ctrl, out_of_core ());
rc = do_getpin (ctrl, &parm);
if (rc)
xfree (parm.buffer);
else
*retpass = parm.buffer;
return unlock_pinentry (ctrl, rc);
}
/* We got PININFO. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATERROR %s",
L_("does not match - try again"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
pininfo->with_repeat = 0; /* Pinentry does not support it. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATOK %s",
L_("Passphrases match."));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
rc = 0; /* Pinentry does not support it. */
}
(void)setup_genpin (ctrl);
rc = setup_enforced_constraints (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
pininfo->repeat_okay = 0;
pininfo->status = 0;
for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
{
memset (&parm, 0, sizeof parm);
parm.constraints_flags = pininfo->constraints_flags;
parm.size = pininfo->max_length;
parm.buffer = (unsigned char*)pininfo->pin;
*pininfo->pin = 0; /* Reset the PIN. */
if (errtext)
{
/* TRANSLATORS: The string is appended to an error message in
the pinentry. The %s is the actual error message, the
two %d give the current and maximum number of tries. */
snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
errtext, pininfo->failed_tries+1, pininfo->max_tries);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
errtext = NULL;
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = do_getpin (ctrl, &parm);
pininfo->status = parm.status;
is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
errtext = is_pin? L_("PIN too long")
: L_("Passphrase too long");
else if (rc)
return unlock_pinentry (ctrl, rc);
if (!errtext && pininfo->min_digits && !is_generated)
{
/* do some basic checks on the entered PIN. */
if (!all_digitsp (pininfo->pin))
errtext = L_("Invalid characters in PIN");
else if (pininfo->max_digits
&& strlen (pininfo->pin) > pininfo->max_digits)
errtext = L_("PIN too long");
else if (strlen (pininfo->pin) < pininfo->min_digits)
errtext = L_("PIN too short");
}
if (!errtext && pininfo->check_cb && !is_generated)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
/* When pinentry cache causes an error, return now. */
if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
return unlock_pinentry (ctrl, rc);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{
if (pininfo->cb_errtext)
errtext = pininfo->cb_errtext;
else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
|| gpg_err_code (rc) == GPG_ERR_BAD_PIN)
errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
}
else if (rc)
return unlock_pinentry (ctrl, rc);
}
if (!errtext)
{
if (pininfo->with_repeat
&& (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
pininfo->repeat_okay = 1;
return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
}
if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
/* The password was read from the Pinentry's own cache.
Don't count this against the retry count. */
pininfo->failed_tries--;
}
}
return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
: GPG_ERR_BAD_PASSPHRASE));
}
/* Pop up the PIN-entry, display the text and the prompt and ask the
user to confirm this. We return 0 for success, ie. the user
confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an
other error. If WITH_CANCEL it true an extra cancel button is
displayed to allow the user to easily return a GPG_ERR_CANCELED.
if the Pinentry does not support this, the user can still cancel by
closing the Pinentry window. */
int
agent_get_confirmation (ctrl_t ctrl,
const char *desc, const char *ok,
const char *notok, int with_cancel)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return pinentry_loopback_confirm (ctrl, desc, 1, ok, notok);
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error 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)
return unlock_pinentry (ctrl, rc);
if (ok)
{
snprintf (line, DIM(line), "SETOK %s", ok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (notok)
{
/* Try to use the newer NOTOK feature if a cancel button is
requested. If no cancel button is requested we keep on using
the standard cancel. */
if (with_cancel)
{
snprintf (line, DIM(line), "SETNOTOK %s", notok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = GPG_ERR_ASS_UNKNOWN_CMD;
if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
{
snprintf (line, DIM(line), "SETCANCEL %s", notok);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
if (rc)
return unlock_pinentry (ctrl, rc);
}
{
gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
npth_t thread;
rc = watch_sock_start (&sock_watched, &thread);
if (!rc)
{
rc = assuan_transact (entry_ctx, "CONFIRM",
NULL, NULL, NULL, NULL, NULL, NULL);
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);
watch_sock_end (&sock_watched, &thread);
}
return unlock_pinentry (ctrl, rc);
}
}
/* The thread running the popup message. */
static void *
popup_message_thread (void *arg)
{
gpg_error_t rc;
gnupg_fd_t sock_watched = *(gnupg_fd_t *)arg;
npth_t thread;
rc = watch_sock_start (&sock_watched, &thread);
if (rc)
return NULL;
/* We use the --one-button hack instead of the MESSAGE command to
allow the use of old Pinentries. Those old Pinentries will then
show an additional Cancel button but that is mostly a visual
annoyance. */
assuan_transact (entry_ctx, "CONFIRM --one-button",
NULL, NULL, NULL, NULL, NULL, NULL);
watch_sock_end (&sock_watched, &thread);
popup_finished = 1;
return NULL;
}
/* Pop up a message window similar to the confirm one but keep it open
until agent_popup_message_stop has been called. It is crucial for
the caller to make sure that the stop function gets called as soon
as the message is not anymore required because the message is
system modal and all other attempts to use the pinentry will fail
(after a timeout). */
int
agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
{
int rc;
char line[ASSUAN_LINELENGTH];
npth_attr_t tattr;
int err;
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return pinentry_loopback_confirm (ctrl, desc, 0, ok_btn, NULL);
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if (ok_btn)
{
snprintf (line, DIM(line), "SETOK %s", ok_btn);
rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
err = npth_attr_init (&tattr);
if (err)
return unlock_pinentry (ctrl, gpg_error_from_errno (err));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
popup_finished = 0;
err = npth_create (&popup_tid, &tattr, popup_message_thread,
&ctrl->thread_startup.fd);
npth_attr_destroy (&tattr);
if (err)
{
rc = gpg_error_from_errno (err);
log_error ("error spawning popup message handler: %s\n",
strerror (err) );
return unlock_pinentry (ctrl, rc);
}
npth_setname_np (popup_tid, "popup-message");
return 0;
}
/* Close a popup window. */
void
agent_popup_message_stop (ctrl_t ctrl)
{
int rc;
pid_t pid;
(void)ctrl;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return;
if (!popup_tid || !entry_ctx)
{
log_debug ("agent_popup_message_stop called with no active popup\n");
return;
}
pid = assuan_get_pid (entry_ctx);
if (pid == (pid_t)(-1))
; /* No pid available can't send a kill. */
else if (popup_finished)
; /* Already finished and ready for joining. */
#ifdef HAVE_W32_SYSTEM
/* Older versions of assuan set PID to 0 on Windows to indicate an
invalid value. */
else if (pid != (pid_t) INVALID_HANDLE_VALUE
&& pid != 0)
{
HANDLE process = (HANDLE) pid;
/* Arbitrary error code. */
TerminateProcess (process, 1);
}
#else
else if (pid > 0)
kill (pid, SIGINT);
#endif
/* Now wait for the thread to terminate. */
rc = npth_join (popup_tid, NULL);
if (rc)
log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
strerror (rc));
/* Thread IDs are opaque, but we try our best here by resetting it
to the same content that a static global variable has. */
memset (&popup_tid, '\0', sizeof (popup_tid));
/* Now we can close the connection. */
unlock_pinentry (ctrl, 0);
}
int
agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH)))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
rc = start_pinentry (ctrl);
if (rc)
return rc;
snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return unlock_pinentry (ctrl, rc);
}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index de33d0b4e..335d389c6 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,3420 +1,3420 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000-2020 Free Software Foundation, Inc.
* Copyright (C) 2000-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* 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 .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# ifdef HAVE_WINSOCK2_H
# include
# endif
# include
# include
#else /*!HAVE_W32_SYSTEM*/
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "agent.h"
#include /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/asshelp.h"
#include "../common/comopt.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
aUseStandardSocketP,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugQuickRandom,
oDebugPinentry,
oNoOptions,
oHomedir,
oNoDetach,
oGrab,
oNoGrab,
oLogFile,
oServer,
oDaemon,
oSupervised,
oBatch,
oPinentryProgram,
oPinentryTouchFile,
oPinentryInvisibleChar,
oPinentryTimeout,
oPinentryFormattedPassphrase,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oScdaemonProgram,
oTpm2daemonProgram,
oDefCacheTTL,
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oCheckSymPassphrasePattern,
oMaxPassphraseDays,
oEnablePassphraseHistory,
oStealSocket,
oUseStandardSocket,
oNoUseStandardSocket,
oExtraSocket,
oBrowserSocket,
oFakedSystemTime,
oIgnoreCacheForSigning,
oAllowMarkTrusted,
oNoAllowMarkTrusted,
oNoUserTrustlist,
oSysTrustlistName,
oAllowPresetPassphrase,
oAllowLoopbackPinentry,
oNoAllowLoopbackPinentry,
oNoAllowExternalCache,
oAllowEmacsPinentry,
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
oSSHFingerprintDigest,
oPuttySupport,
oWin32OpenSSHSupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oS2KCount,
oS2KCalibration,
oAutoExpandSecmem,
oListenBacklog,
oInactivityTimeout,
oWriteEnvFile,
oNoop
};
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_s_n (oSupervised, "supervised", "@"),
#endif
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_s_i (oInactivityTimeout, "inactivity-timeout", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
/* */ N_("|FILE|write server mode logs to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
/* */ N_("do not use the SCdaemon") ),
ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
/* */ N_("|PGM|use PGM as the SCdaemon program") ),
ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program",
/* */ N_("|PGM|use PGM as the tpm2daemon program") ),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oExtraSocket, "extra-socket",
/* */ N_("|NAME|accept some commands via NAME")),
ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
ARGPARSE_s_n (oKeepTTY, "keep-tty",
/* */ N_("ignore requests to change the TTY")),
ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
/* */ N_("ignore requests to change the X display")),
ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
N_("|ALGO|use ALGO to show ssh fingerprints")),
ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable putty support")
#else
/* */ "@"
#endif
),
ARGPARSE_o_s (oWin32OpenSSHSupport, "enable-win32-openssh-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable Win32-OpenSSH support")
#else
/* */ "@"
#endif
),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_header ("Security", N_("Options controlling the security")),
ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
N_("|N|expire cached PINs after N seconds")),
ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh",
/* */ N_("|N|expire SSH keys after N seconds")),
ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl",
/* */ N_("|N|set maximum PIN cache lifetime to N seconds")),
ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh",
/* */ N_("|N|set maximum SSH key lifetime to N seconds")),
ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
/* */ N_("do not use the PIN cache when signing")),
ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
/* */ N_("disallow the use of an external password cache")),
ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
/* */ N_("disallow clients to mark keys as \"trusted\"")),
ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
ARGPARSE_s_n (oNoUserTrustlist, "no-user-trustlist", "@"),
ARGPARSE_s_s (oSysTrustlistName, "sys-trustlist-name", "@"),
ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
/* */ N_("allow presetting passphrase")),
ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"),
ARGPARSE_header ("Passphrase policy",
N_("Options enforcing a passphrase policy")),
ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
N_("do not allow bypassing the passphrase policy")),
ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len",
N_("|N|set minimal required length for new passphrases to N")),
ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha",
N_("|N|require at least N non-alpha"
" characters for a new passphrase")),
ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern",
N_("|FILE|check new passphrases against pattern in FILE")),
ARGPARSE_s_s (oCheckSymPassphrasePattern, "check-sym-passphrase-pattern",
"@"),
ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days",
N_("|N|expire the passphrase after N days")),
ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history",
N_("do not allow the reuse of old passphrases")),
ARGPARSE_header ("Pinentry", N_("Options controlling the PIN-Entry")),
ARGPARSE_s_n (oBatch, "batch", N_("never use the PIN-entry")),
ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
N_("disallow caller to override the pinentry")),
ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
ARGPARSE_s_n (oGrab, "grab", N_("let PIN-Entry grab keyboard and mouse")),
ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
N_("|PGM|use PGM as the PIN-Entry program")),
ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout",
N_("|N|set the Pinentry timeout to N seconds")),
ARGPARSE_s_n (oPinentryFormattedPassphrase, "pinentry-formatted-passphrase",
"@"),
ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
N_("allow passphrase to be prompted through Emacs")),
/* Dummy options for backward compatibility. */
ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
/* Dummy options. */
ARGPARSE_s_n (oNoop, "disable-extended-key-format", "@"),
ARGPARSE_s_n (oNoop, "enable-extended-key-format", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
#define MAX_PASSPHRASE_DAYS (0)
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms.
* CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#define TIMERTICK_INTERVAL (4)
#define CHECK_OWN_SOCKET_INTERVAL (60)
/* Flag indicating that the ssh-agent subsystem has been enabled. */
static int ssh_support;
#ifdef HAVE_W32_SYSTEM
/* Flag indicating that support for Putty has been enabled. */
static int putty_support;
/* A magic value used with WM_COPYDATA. */
#define PUTTY_IPC_MAGIC 0x804e50ba
/* To avoid surprises we limit the size of the mapped IPC file to this
value. Putty currently (0.62) uses 8k, thus 16k should be enough
for the foreseeable future. */
#define PUTTY_IPC_MAXLEN 16384
/* Path to the pipe, which handles requests from Win32-OpenSSH. */
static const char *win32_openssh_support;
#define W32_DEFAILT_AGENT_PIPE_NAME "\\\\.\\pipe\\openssh-ssh-agent"
#endif /*HAVE_W32_SYSTEM*/
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flags to indicate that check_own_socket shall not be called. */
static int disable_check_own_socket;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Name of the communication socket used for native gpg-agent
requests. The second variable is either NULL or a malloced string
with the real socket name in case it has been redirected. */
static char *socket_name;
static char *redir_socket_name;
/* Name of the optional extra socket used for native gpg-agent requests. */
static char *socket_name_extra;
static char *redir_socket_name_extra;
/* Name of the optional browser socket used for native gpg-agent requests. */
static char *socket_name_browser;
static char *redir_socket_name_browser;
/* Name of the communication socket used for ssh-agent protocol. */
static char *socket_name_ssh;
static char *redir_socket_name_ssh;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
static assuan_sock_nonce_t socket_nonce_extra;
static assuan_sock_nonce_t socket_nonce_browser;
static assuan_sock_nonce_t socket_nonce_ssh;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Default values for options passed to the pinentry. */
static char *default_display;
static char *default_ttyname;
static char *default_ttytype;
static char *default_lc_ctype;
static char *default_lc_messages;
static char *default_xauthority;
/* Name of a config file which was last read on startup or if missing
* the name of the standard config file. Any value here enabled the
* rereading of the standard config files on SIGHUP. */
static char *config_filename;
/* Helper to implement --debug-level */
static const char *debug_level;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* The handle_tick() function may test whether a parent is still
* running. We record the PID of the parent here or -1 if it should
* be watched. */
static pid_t parent_pid = (pid_t)(-1);
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how gpg-agent was started, the homedir inotify watch
* may not be reliable. This flag is set if we assume that inotify
* works reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void agent_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void agent_init_default_ctrl (ctrl_t ctrl);
static void agent_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh);
static void check_own_socket (void);
static int check_for_running_agent (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
This function is expected to be called only once. GETFNC is
expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
/* Return strings describing this program. The case values are
described in common/argparse.c:strusage. The values here override
the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPG_AGENT@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
only the active debug flags are propagated to the subsystems. With
DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
all flags already set. Note that we don't fail here, because it is
important to keep gpg-agent running even after re-reading the
options due to a SIGHUP. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
the corresponding real name if the socket has been redirected. */
static void
remove_socket (char *name, char *redir_name)
{
if (name && *name)
{
if (redir_name)
name = redir_name;
gnupg_remove (name);
*name = 0;
}
}
/* Discover which inherited file descriptors correspond to which
* services/sockets offered by gpg-agent, using the LISTEN_FDS and
* LISTEN_FDNAMES convention. The understood labels are "ssh",
* "extra", and "browser". "std" or other labels will be interpreted
* as the standard socket.
*
* This function is designed to log errors when the expected file
* descriptors don't make sense, but to do its best to continue to
* work even in the face of minor misconfigurations.
*
* For more information on the LISTEN_FDS convention, see
* sd_listen_fds(3) on certain Linux distributions.
*/
#ifndef HAVE_W32_SYSTEM
static void
map_supervised_sockets (gnupg_fd_t *r_fd,
gnupg_fd_t *r_fd_extra,
gnupg_fd_t *r_fd_browser,
gnupg_fd_t *r_fd_ssh)
{
struct {
const char *label;
int **fdaddr;
char **nameaddr;
} tbl[] = {
{ "ssh", &r_fd_ssh, &socket_name_ssh },
{ "browser", &r_fd_browser, &socket_name_browser },
{ "extra", &r_fd_extra, &socket_name_extra },
{ "std", &r_fd, &socket_name } /* (Must be the last item.) */
};
const char *envvar;
char **fdnames;
int nfdnames;
int fd_count;
*r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
/* Print a warning if LISTEN_PID does not match outr pid. */
envvar = getenv ("LISTEN_PID");
if (!envvar)
log_error ("no LISTEN_PID environment variable found in "
"--supervised mode (ignoring)\n");
else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
log_error ("environment variable LISTEN_PID (%lu) does not match"
" our pid (%lu) in --supervised mode (ignoring)\n",
(unsigned long)strtoul (envvar, NULL, 10),
(unsigned long)getpid ());
/* Parse LISTEN_FDNAMES into the array FDNAMES. */
envvar = getenv ("LISTEN_FDNAMES");
if (envvar)
{
fdnames = strtokenize (envvar, ":");
if (!fdnames)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
agent_exit (1);
}
for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
;
}
else
{
fdnames = NULL;
nfdnames = 0;
}
/* Parse LISTEN_FDS into fd_count or provide a replacement. */
envvar = getenv ("LISTEN_FDS");
if (envvar)
fd_count = atoi (envvar);
else if (fdnames)
{
log_error ("no LISTEN_FDS environment variable found in --supervised"
" mode (relying on LISTEN_FDNAMES instead)\n");
fd_count = nfdnames;
}
else
{
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
"found in --supervised mode"
" (assuming 1 active descriptor)\n");
fd_count = 1;
}
if (fd_count < 1)
{
log_error ("--supervised mode expects at least one file descriptor"
" (was told %d, carrying on as though it were 1)\n",
fd_count);
fd_count = 1;
}
/* Assign the descriptors to the return values. */
if (!fdnames)
{
struct stat statbuf;
if (fd_count != 1)
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
" in --supervised mode."
" (ignoring all sockets but the first one)\n",
fd_count);
if (fstat (3, &statbuf) == -1 && errno ==EBADF)
log_fatal ("file descriptor 3 must be valid in --supervised mode"
" if LISTEN_FDNAMES is not set\n");
*r_fd = 3;
socket_name = gnupg_get_socket_name (3);
}
else if (fd_count != nfdnames)
{
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
"LISTEN_FDS (%d) in --supervised mode\n",
nfdnames, fd_count);
}
else
{
int i, j, fd;
char *name;
for (i = 0; i < nfdnames; i++)
{
for (j = 0; j < DIM (tbl); j++)
{
if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
{
fd = 3 + i;
if (**tbl[j].fdaddr == -1)
{
name = gnupg_get_socket_name (fd);
if (name)
{
**tbl[j].fdaddr = fd;
*tbl[j].nameaddr = name;
log_info ("using fd %d for %s socket (%s)\n",
fd, tbl[j].label, name);
}
else
{
log_error ("cannot listen on fd %d for %s socket\n",
fd, tbl[j].label);
close (fd);
}
}
else
{
log_error ("cannot listen on more than one %s socket\n",
tbl[j].label);
close (fd);
}
break;
}
}
}
}
xfree (fdnames);
}
#endif /*!HAVE_W32_SYSTEM*/
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
deinitialize_module_cache ();
if (!is_supervised && !inhibit_socket_removal)
{
remove_socket (socket_name, redir_socket_name);
if (opt.extra_socket > 1)
remove_socket (socket_name_extra, redir_socket_name_extra);
if (opt.browser_socket > 1)
remove_socket (socket_name_browser, redir_socket_name_browser);
remove_socket (socket_name_ssh, redir_socket_name_ssh);
}
}
/* Handle options which are allowed to be reset after program start.
Return true when the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
int i;
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.no_grab = 1;
opt.debug_pinentry = 0;
opt.pinentry_program = NULL;
opt.pinentry_touch_file = NULL;
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = NULL;
opt.pinentry_timeout = 0;
opt.pinentry_formatted_passphrase = 0;
memset (opt.daemon_program, 0, sizeof opt.daemon_program);
opt.def_cache_ttl = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.check_sym_passphrase_pattern = NULL;
opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
opt.enable_passphrase_history = 0;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 1;
opt.sys_trustlist_name = NULL;
opt.allow_external_cache = 1;
opt.allow_loopback_pinentry = 1;
opt.allow_emacs_pinentry = 0;
memset (opt.disable_daemon, 0, sizeof opt.disable_daemon);
disable_check_own_socket = 0;
/* Note: When changing the next line, change also gpgconf_list. */
opt.ssh_fingerprint_digest = GCRY_MD_SHA256;
opt.s2k_count = 0;
set_s2k_calibration_time (0); /* Set to default. */
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oDebugPinentry: opt.debug_pinentry = 1; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oNoGrab: opt.no_grab |= 1; break;
case oGrab: opt.no_grab |= 2; break;
case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
case oPinentryInvisibleChar:
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
break;
case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
case oPinentryFormattedPassphrase:
opt.pinentry_formatted_passphrase = 1;
break;
case oTpm2daemonProgram:
opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str;
break;
case oScdaemonProgram:
opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str;
break;
case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oCheckSymPassphrasePattern:
opt.check_sym_passphrase_pattern = pargs->r.ret_str;
break;
case oMaxPassphraseDays:
opt.max_passphrase_days = pargs->r.ret_ulong;
break;
case oEnablePassphraseHistory:
opt.enable_passphrase_history = 1;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
case oNoUserTrustlist: opt.no_user_trustlist = 1; break;
case oSysTrustlistName: opt.sys_trustlist_name = pargs->r.ret_str; break;
case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
case oNoAllowExternalCache: opt.allow_external_cache = 0;
break;
case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
break;
case oSSHFingerprintDigest:
i = gcry_md_map_name (pargs->r.ret_str);
if (!i)
log_error (_("selected digest algorithm is invalid\n"));
else
opt.ssh_fingerprint_digest = i;
break;
case oS2KCount:
opt.s2k_count = pargs->r.ret_ulong;
break;
case oS2KCalibration:
set_s2k_calibration_time (pargs->r.ret_ulong);
break;
case oNoop: break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
/* Hack to allow --grab to override --no-grab. */
if ((opt.no_grab & 2))
opt.no_grab = 0;
/* With --no-user-trustlist it does not make sense to allow the mark
* trusted feature. */
if (opt.no_user_trustlist)
opt.allow_mark_trusted = 0;
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
initialize_module_cache ();
initialize_module_call_pinentry ();
initialize_module_daemon ();
initialize_module_trustlist ();
}
/* The main entry point. */
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
int debug_argparser = 0;
const char *shell;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
gpg_error_t err;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
file descriptors and the signal mask. This info is required to
do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
disable_core_dumps ();
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Record some of the original environment strings. */
{
const char *s;
int idx;
static const char *names[] =
{ "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
err = 0;
opt.startup_env = session_env_new ();
if (!opt.startup_env)
err = gpg_error_from_syserror ();
for (idx=0; !err && names[idx]; idx++)
{
s = getenv (names[idx]);
if (s)
err = session_env_setenv (opt.startup_env, names[idx], s);
}
if (!err)
{
s = gnupg_ttyname (0);
if (s)
err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
}
if (err)
log_fatal ("error recording startup environment: %s\n",
gpg_strerror (err));
/* Fixme: Better use the locale function here. */
opt.startup_lc_ctype = getenv ("LC_CTYPE");
if (opt.startup_lc_ctype)
opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
opt.startup_lc_messages = getenv ("LC_MESSAGES");
if (opt.startup_lc_messages)
opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
}
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
case oDebugQuickRandom:
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
maybe_setuid = 0;
/*
* Now we are now working under our real uid
*/
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
/* We are re-using the struct, thus the reset flag. We OR the
* flags so that the internal intialized flag won't be cleared. */
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, GPG_AGENT_NAME EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case aUseStandardSocketP: gpgconf_list = 3; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oStealSocket: steal_socket = 1; break;
case oSupervised: is_supervised = 1; break;
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
break;
case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
break;
case oUseStandardSocket:
case oNoUseStandardSocket:
obsolete_option (configname, pargs.lineno, "use-standard-socket");
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oKeepTTY: opt.keep_tty = 1; break;
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport:
ssh_support = 1;
break;
case oPuttySupport:
# ifdef HAVE_W32_SYSTEM
putty_support = 1;
# endif
break;
case oWin32OpenSSHSupport:
# ifdef HAVE_W32_SYSTEM
if (pargs.r_type)
win32_openssh_support = pargs.r.ret_str;
else
win32_openssh_support = W32_DEFAILT_AGENT_PIPE_NAME;
# endif
break;
case oExtraSocket:
opt.extra_socket = 1; /* (1 = points into argv) */
socket_name_extra = pargs.r.ret_str;
break;
case oBrowserSocket:
opt.browser_socket = 1; /* (1 = points into argv) */
socket_name_browser = pargs.r.ret_str;
break;
case oAutoExpandSecmem:
/* Try to enable this option. It will officially only be
* supported by Libgcrypt 1.9 but 1.8.2 already supports it
* on the quiet and thus we use the numeric value value. */
gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
(unsigned int)pargs.r.ret_ulong, 0);
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oDebugQuickRandom:
/* Only used by the first stage command line parser. */
break;
case oWriteEnvFile:
obsolete_option (configname, pargs.lineno, "write-env-file");
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
finalize_rereadable_options ();
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_AGENT, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
#ifdef ENABLE_NLS
/* gpg-agent usually does not output any messages because it runs in
the background. For log files it is acceptable to have messages
always encoded in utf-8. We switch here to utf-8, so that
commands like --help still give native messages. It is far
easier to switch only once instead of for every message and it
actually helps when more then one thread is active (avoids an
extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
{
/* We have been called without any command and thus we merely
check whether an agent is already running. We do this right
here so that we don't clobber a logfile with this check but
print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_agent (0);
agent_exit (0);
}
if (is_supervised && !opt.quiet)
log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised");
if (is_supervised)
;
else if (!opt.extra_socket)
opt.extra_socket = 1;
else if (socket_name_extra
&& (!strcmp (socket_name_extra, "none")
|| !strcmp (socket_name_extra, "/dev/null")))
{
/* User requested not to create this socket. */
opt.extra_socket = 0;
socket_name_extra = NULL;
}
if (is_supervised)
;
else if (!opt.browser_socket)
opt.browser_socket = 1;
else if (socket_name_browser
&& (!strcmp (socket_name_browser, "none")
|| !strcmp (socket_name_browser, "/dev/null")))
{
/* User requested not to create this socket. */
opt.browser_socket = 0;
socket_name_browser = NULL;
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
if (!gpgconf_list)
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 3)
{
/* We now use the standard socket always - return true for
backward compatibility. */
agent_exit (0);
}
else if (gpgconf_list == 2)
agent_exit (0);
else if (gpgconf_list)
{
/* Note: If an option is runtime changeable, please set the
* respective flag in the gpgconf-comp.c table. */
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL );
es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL_SSH );
es_printf ("max-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL );
es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL_SSH );
es_printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_LEN );
es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_NONALPHA);
es_printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT);
es_printf ("check-sym-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT);
es_printf ("max-passphrase-days:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_PASSPHRASE_DAYS);
es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, "sha256");
agent_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
/* Make sure that we have a default ttyname. */
if (!default_ttyname && gnupg_ttyname (1))
default_ttyname = xstrdup (gnupg_ttyname (1));
if (!default_ttytype && getenv ("TERM"))
default_ttytype = xstrdup (getenv ("TERM"));
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
agent_exit (1);
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
xfree (ctrl);
agent_exit (1);
}
agent_init_default_ctrl (ctrl);
start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (is_supervised && comopt.no_autostart)
{
/* If we are running on a server and the user has set
* no-autostart for gpg or gpgsm. gpg-agent would anyway be
* started by the supervisor which has the bad effect that it
* will steal the socket from a remote server. Note that
* systemd has no knowledge about the lock files we take during
* the start operation. */
log_info ("%s %s not starting in supervised mode due to no-autostart.\n",
gpgrt_strusage(11), gpgrt_strusage(13) );
}
else if (is_supervised)
{
#ifndef HAVE_W32_SYSTEM
gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
initialize_modules ();
/* when supervised and sending logs to stderr, the process
supervisor should handle log entry metadata (pid, name,
timestamp) */
if (!logfile)
log_set_prefix (NULL, 0);
log_info ("%s %s starting in supervised mode.\n",
gpgrt_strusage(11), gpgrt_strusage(13) );
/* See below in "regular server mode" on why we remove certain
* envvars. */
if (!opt.keep_display)
gnupg_unsetenv ("DISPLAY");
gnupg_unsetenv ("INSIDE_EMACS");
/* Virtually create the sockets. Note that we use -1 here
* because the whole thing works only on Unix. */
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
if (fd == -1)
log_fatal ("no standard socket provided\n");
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
fd, fd_extra, fd_browser, fd_ssh);
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
#endif /*!HAVE_W32_SYSTEM*/
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular server mode */
gnupg_fd_t fd;
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Remove the DISPLAY variable so that a pinentry does not
default to a specific display. There is still a default
display when gpg-agent was started using --display or a
client requested this using an OPTION command. Note, that we
don't do this when running in reverse daemon mode (i.e. when
exec the program given as arguments). */
#ifndef HAVE_W32_SYSTEM
if (!opt.keep_display && !argc)
gnupg_unsetenv ("DISPLAY");
#endif
/* Remove the INSIDE_EMACS variable so that a pinentry does not
always try to interact with Emacs. The variable is set when
a client requested this using an OPTION command. */
gnupg_unsetenv ("INSIDE_EMACS");
/* Create the sockets. */
socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 1, 0,
&redir_socket_name, &socket_nonce);
if (opt.extra_socket)
{
if (socket_name_extra)
socket_name_extra = create_socket_name (socket_name_extra, 0);
else
socket_name_extra = create_socket_name
/**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
opt.extra_socket = 2; /* Indicate that it has been malloced. */
fd_extra = create_server_socket (socket_name_extra, 0, 0,
&redir_socket_name_extra,
&socket_nonce_extra);
}
if (opt.browser_socket)
{
if (socket_name_browser)
socket_name_browser = create_socket_name (socket_name_browser, 0);
else
socket_name_browser= create_socket_name
/**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
opt.browser_socket = 2; /* Indicate that it has been malloced. */
fd_browser = create_server_socket (socket_name_browser, 0, 0,
&redir_socket_name_browser,
&socket_nonce_browser);
}
socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
&redir_socket_name_ssh,
&socket_nonce_ssh);
/* If we are going to exec a program in the parent, we record
the PID, so that the child may check whether the program is
still alive. */
if (argc)
parent_pid = getpid ();
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
char *infostr_ssh_sock, *infostr_ssh_valid;
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
we restore it. That is not strictly necessary but some
programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
/* Create the SSH info string if enabled. */
if (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_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
(unsigned long)getpid()) < 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 */
if (opt.extra_socket)
*socket_name_extra = 0;
if (opt.browser_socket)
*socket_name_browser = 0;
*socket_name_ssh = 0;
if (argc)
{ /* Run the program given on the commandline. */
if (ssh_support && (putenv (infostr_ssh_sock)
|| putenv (infostr_ssh_valid)))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
/* Close all the file descriptors except the standard
ones and those open at startup. We explicitly don't
close 0,1,2 in case something went wrong collecting
them at startup. */
close_all_fds (3, startup_fd_list);
/* Run the command. */
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
if (ssh_support)
{
*strchr (infostr_ssh_sock, '=') = ' ';
es_printf ("setenv %s;\n", infostr_ssh_sock);
}
}
else
{
if (ssh_support)
{
es_printf ("%s; export SSH_AUTH_SOCK;\n",
infostr_ssh_sock);
}
}
if (ssh_support)
{
xfree (infostr_ssh_sock);
xfree (infostr_ssh_valid);
}
exit (0);
}
/*NOTREACHED*/
} /* End parent */
/*
This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Unless we are running with a program given on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
if (!argc)
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13) );
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
agent_exit (int rc)
{
/*FIXME: update_random_seed_file();*/
/* We run our cleanup handler because that may close cipher contexts
stored in secure memory and thus this needs to be done before we
explicitly terminate secure memory. */
cleanup ();
#if 1
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
set once at startup and dispatches progress messages to the
corresponding threads of the agent. */
static void
agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
structure usually identified by an argument named CTRL. This
function is called immediately after allocating the control
structure. Its purpose is to setup the default values for that
structure. Note that some values may have already been set. */
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
log_assert (ctrl->session_env);
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to agent_init_default_ctrl. */
static void
agent_deinit_default_ctrl (ctrl_t ctrl)
{
unregister_progress_cb ();
session_env_release (ctrl->session_env);
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
}
/* Because the ssh protocol does not send us information about the
current TTY setting, we use this function to use those from startup
or those explicitly set. This is also used for the restricted mode
where we ignore requests to change the environment. */
gpg_error_t
agent_copy_startup_env (ctrl_t ctrl)
{
gpg_error_t err = 0;
int iterator = 0;
const char *name, *value;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
if ((value = session_env_getenv (opt.startup_env, name)))
err = session_env_setenv (ctrl->session_env, name, value);
}
if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
err = gpg_error_from_syserror ();
if (err)
log_error ("error setting default session environment: %s\n",
gpg_strerror (err));
return err;
}
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the PTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!config_filename)
return; /* No config file. */
twopart = strconcat (GPG_AGENT_NAME EXTSEP_S "conf" PATHSEP_S,
config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart) )
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
finalize_rereadable_options ();
set_debug ();
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_AGENT, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* Return the file name of the socket we are using for native
requests. */
const char *
get_agent_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the file name of the socket we are using for SSH
requests. */
const char *
get_agent_ssh_socket_name (void)
{
const char *s = socket_name_ssh;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_agent_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM)
void *
get_agent_daemon_notify_event (void)
{
static HANDLE the_event = INVALID_HANDLE_VALUE;
if (the_event == INVALID_HANDLE_VALUE)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd notify event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
the_event = h2;
}
}
return the_event;
}
#endif /*HAVE_W32_SYSTEM*/
/* Create a name for the socket in the home directory as using
STANDARD_NAME. We also check for valid characters as well as
against a maximum allowed length for a unix domain socket is done.
The function terminates the process in case of an error. Returns:
Pointer to an allocated string with the absolute name of the socket
used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
agent_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. Note that this
function needs to be used for the regular socket first (indicated
by PRIMARY) and only then for the extra and the ssh sockets. If
the socket has been redirected the name of the real socket is
stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
agent_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* At least our error code mapping on Windows-CE used to return
* EEXIST thus we better test for this on Windows . */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a gpg-agent is already running. We do this
test only if this is the primary socket. For secondary
sockets we assume that a test for gpg-agent has already been
done and reuse the requested socket. Testing the ssh-socket
is not possible because at this point, though we know the new
Assuan socket, the Assuan server and thus the ssh-agent
server is not yet operational; this would lead to a hang. */
if (primary && !check_for_running_agent (1))
{
if (steal_socket)
log_info (N_("trying to steal socket from running %s\n"),
"gpg-agent");
else
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a gpg-agent is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the private keys exists and
create it if not. This function won't fail as it is only a
convenience function and not strictly necessary. */
static void
create_private_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (gnupg_stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
if (gnupg_chmod (fname, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
}
else
{
/* The file exists or another error. Make sure we have sensible
* permissions. We enforce rwx for user but keep existing group
* permissions. Permissions for other are always cleared. */
if (gnupg_chmod (fname, "-rwx...---"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
}
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we compare only the suffix if we see that the default
homedir does start with a tilde. We don't stop here in case of
problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (gnupg_stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_private_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_private_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* If we are running as a child of another process, check whether
the parent is still alive and shutdown if not. */
#ifndef HAVE_W32_SYSTEM
if (parent_pid != (pid_t)(-1))
{
if (kill (parent_pid, 0))
{
shutdown_pending = 2;
log_info ("parent process died - shutting down\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Need to check for expired cache entries. */
agent_cache_housekeeping ();
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
other places too. This is only used when build for W32. */
void
agent_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
agent_flush_cache (0);
reread_configuration ();
agent_reload_trustlist ();
/* We flush the module name cache so that after installing a
"pinentry" binary that one can be used in case the
"pinentry-basic" fallback was in use. */
gnupg_module_name_flush_some ();
if (opt.disable_daemon[DAEMON_SCD])
agent_kill_daemon (DAEMON_SCD);
}
/* A helper function to handle SIGUSR2. */
static void
agent_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - updating card event counter\n");
/* Nothing to check right now. We only increment a counter. */
bump_card_eventcounter ();
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
agent_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
agent_query_dump_state ();
agent_daemon_dump_state ();
break;
case SIGUSR2:
agent_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
break;
#endif
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
#ifdef HAVE_W32_SYSTEM
/* The window message processing function for Putty. Warning: This
code runs as a native Windows thread. Use of our own functions
needs to be bracket with pth_leave/pth_enter. */
static LRESULT CALLBACK
putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int ret = 0;
int w32rc;
COPYDATASTRUCT *cds;
const char *mapfile;
HANDLE maphd;
PSID mysid = NULL;
PSID mapsid = NULL;
void *data = NULL;
PSECURITY_DESCRIPTOR psd = NULL;
ctrl_t ctrl = NULL;
if (msg != WM_COPYDATA)
{
return DefWindowProc (hwnd, msg, wparam, lparam);
}
cds = (COPYDATASTRUCT*)lparam;
if (cds->dwData != PUTTY_IPC_MAGIC)
return 0; /* Ignore data with the wrong magic. */
mapfile = cds->lpData;
if (!cds->cbData || mapfile[cds->cbData - 1])
return 0; /* Ignore empty and non-properly terminated strings. */
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map file '%s'", mapfile);
npth_unprotect ();
}
maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map handle %p\n", maphd);
npth_unprotect ();
}
if (!maphd || maphd == INVALID_HANDLE_VALUE)
return 0;
npth_protect ();
mysid = w32_get_user_sid ();
if (!mysid)
{
log_error ("error getting my sid\n");
goto leave;
}
w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION,
&mapsid, NULL, NULL, NULL,
&psd);
if (w32rc)
{
log_error ("error getting sid of ssh map file: rc=%d", w32rc);
goto leave;
}
if (DBG_IPC)
{
char *sidstr;
if (!ConvertSidToStringSid (mysid, &sidstr))
sidstr = NULL;
log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
if (!ConvertSidToStringSid (mapsid, &sidstr))
sidstr = NULL;
log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
}
if (!EqualSid (mysid, mapsid))
{
log_error ("ssh map file has a non-matching sid\n");
goto leave;
}
data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (DBG_IPC)
log_debug ("ssh IPC buffer at %p\n", data);
if (!data)
goto leave;
/* log_printhex ("request:", data, 20); */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
goto leave;
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
goto leave;
}
agent_init_default_ctrl (ctrl);
if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
ret = 1; /* Valid ssh message has been constructed. */
agent_deinit_default_ctrl (ctrl);
/* log_printhex (" reply:", data, 20); */
leave:
xfree (ctrl);
if (data)
UnmapViewOfFile (data);
xfree (mapsid);
if (psd)
LocalFree (psd);
xfree (mysid);
CloseHandle (maphd);
npth_unprotect ();
return ret;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* The thread handling Putty's IPC requests. */
static void *
putty_message_thread (void *arg)
{
WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
NULL, NULL, NULL, NULL, NULL, "Pageant"};
HWND hwnd;
MSG msg;
(void)arg;
if (opt.verbose)
log_info ("putty message loop thread started\n");
/* The message loop runs as thread independent from our nPth system.
This also means that we need to make sure that we switch back to
our system before calling any no-windows function. */
npth_unprotect ();
/* First create a window to make sure that a message queue exists
for this thread. */
if (!RegisterClass (&wndwclass))
{
npth_protect ();
log_error ("error registering Pageant window class");
return NULL;
}
hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
0, 0, 0, 0,
HWND_MESSAGE, /* hWndParent */
NULL, /* hWndMenu */
NULL, /* hInstance */
NULL); /* lpParm */
if (!hwnd)
{
npth_protect ();
log_error ("error creating Pageant window");
return NULL;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Back to nPth. */
npth_protect ();
if (opt.verbose)
log_info ("putty message loop thread stopped\n");
return NULL;
}
#define BUFSIZE (5 * 1024)
/* The thread handling Win32-OpenSSH requests through NamedPipe. */
static void *
win32_openssh_thread (void *arg)
{
HANDLE pipe;
(void)arg;
if (opt.verbose)
log_info ("Win32-OpenSSH thread started\n");
while (1)
{
ctrl_t ctrl = NULL;
estream_t ssh_stream = NULL;
es_syshd_t syshd;
npth_unprotect ();
pipe = CreateNamedPipeA (win32_openssh_support, PIPE_ACCESS_DUPLEX,
(PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
| PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS),
PIPE_UNLIMITED_INSTANCES,
BUFSIZE, BUFSIZE, 0, NULL);
if (pipe == INVALID_HANDLE_VALUE)
{
npth_protect ();
log_error ("cannot create pipe: %ld\n", GetLastError ());
break;
}
if (ConnectNamedPipe (pipe, NULL) == 0)
{
npth_protect ();
CloseHandle (pipe);
log_error ("Error at ConnectNamedPipe: %ld\n", GetLastError ());
break;
}
npth_protect ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
CloseHandle (pipe);
log_error ("error allocating connection control data: %s\n",
strerror (errno));
break;
}
#if _WIN32_WINNT >= 0x600
if (!GetNamedPipeClientProcessId (pipe, &ctrl->client_pid))
log_info ("failed to get client process id: %ld\n", GetLastError ());
else
ctrl->client_uid = -1;
#endif
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
break;
}
agent_init_default_ctrl (ctrl);
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = pipe;
ssh_stream = es_sysopen (&syshd, "r+b");
if (!ssh_stream)
{
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
break;
}
start_command_handler_ssh_stream (ctrl, ssh_stream);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
}
if (opt.verbose)
log_info ("Win32-OpenSSH thread stopped\n");
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
static void *
do_start_connection_thread (ctrl_t ctrl)
{
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose > 1 && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
if (opt.verbose > 1 && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread_std (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* This is the extra socket connection thread's main function. */
static void *
start_connection_thread_extra (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_extra))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 1;
return do_start_connection_thread (ctrl);
}
/* This is the browser socket connection thread's main function. */
static void *
start_connection_thread_browser (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_browser))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 2;
return do_start_connection_thread (ctrl);
}
/* This is the ssh connection thread's main function. */
static void *
start_connection_thread_ssh (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_ssh))
return NULL;
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread_std },
{ "extra", start_connection_thread_extra },
{ "browser", start_connection_thread_browser },
{ "ssh", start_connection_thread_ssh }
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n",
strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
events[0] = get_agent_daemon_notify_event ();
events[1] = INVALID_HANDLE_VALUE;
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
/* On Windows we need to fire up a separate thread to listen for
requests from Putty (an SSH client), so we can replace Putty's
Pageant (its ssh-agent implementation). */
#ifdef HAVE_W32_SYSTEM
if (putty_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
if (ret)
log_error ("error spawning putty message loop: %s\n", strerror (ret));
}
if (win32_openssh_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, win32_openssh_thread, NULL);
if (ret)
log_error ("error spawning Win32-OpenSSH loop: %s\n", strerror (ret));
}
#endif /*HAVE_W32_SYSTEM*/
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
- nfd = FD2INT (listen_fd);
+ nfd = FD2NUM (listen_fd);
if (listen_fd_extra != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_extra), &fdset);
if (FD2INT (listen_fd_extra) > nfd)
- nfd = FD2INT (listen_fd_extra);
+ nfd = FD2NUM (listen_fd_extra);
}
if (listen_fd_browser != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_browser), &fdset);
if (FD2INT (listen_fd_browser) > nfd)
- nfd = FD2INT (listen_fd_browser);
+ nfd = FD2NUM (listen_fd_browser);
}
if (listen_fd_ssh != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_ssh), &fdset);
if (FD2INT (listen_fd_ssh) > nfd)
- nfd = FD2INT (listen_fd_ssh);
+ nfd = FD2NUM (listen_fd_ssh);
}
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
listentbl[1].l_fd = listen_fd_extra;
listentbl[2].l_fd = listen_fd_browser;
listentbl[3].l_fd = listen_fd_ssh;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (active_connections == 0 || is_supervised)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if (events_set & 1)
agent_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else if ( !(ctrl->session_env = session_env_new ()))
{
log_error ("error allocating session env block for %s: %s\n",
listentbl[idx].name, strerror (errno) );
xfree (ctrl);
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
separate thread so that check_own_thread can be called from the
timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
{
xfree (sockname);
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether an agent is available and running. Prints an
error if not. If SILENT is true, no messages are printed.
Returns 0 if the agent is running. */
static int
check_for_running_agent (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no gpg-agent running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("gpg-agent running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/common/sysutils.h b/common/sysutils.h
index e34fff72f..dac2d9244 100644
--- a/common/sysutils.h
+++ b/common/sysutils.h
@@ -1,129 +1,131 @@
/* sysutils.h - System utility functions for Gnupg
* Copyright (C) 2002 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file 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 .
*/
#ifndef GNUPG_COMMON_SYSUTILS_H
#define GNUPG_COMMON_SYSUTILS_H
/* Because we use system handles and not libc low level file
descriptors on W32, we need to declare them as HANDLE (which
actually is a plain pointer). This is required to eventually
support 64 bits Windows systems. */
#ifdef HAVE_W32_SYSTEM
typedef void *gnupg_fd_t;
#define GNUPG_INVALID_FD ((void*)(-1))
#define INT2FD(s) ((void *)(s))
# ifdef _WIN64
# define FD2INT(h) ((intptr_t)(h))
# else
# define FD2INT(h) ((unsigned int)(h))
# endif
#define FD_DBG(h) ((int)(intptr_t)(h))
+#define FD2NUM(h) ((int)(intptr_t)(h))
#else
typedef int gnupg_fd_t;
#define GNUPG_INVALID_FD (-1)
#define INT2FD(s) (s)
#define FD2INT(h) (h)
#define FD_DBG(h) (h)
+#define FD2NUM(h) (h)
#endif
#ifdef HAVE_STAT
# include
#endif
struct gnupg_dir_s;
typedef struct gnupg_dir_s *gnupg_dir_t;
struct gnupg_dirent_s
{
/* We don't have a d_ino because that can't be used on Windows
* anyway. D_NAME is a pointer into the gnupg_dir_s which has a
* static buffer or allocates sufficient space as needed. This is
* only valid after gnupg_readdir. */
char *d_name;
};
typedef struct gnupg_dirent_s *gnupg_dirent_t;
void trap_unaligned (void);
int disable_core_dumps (void);
int enable_core_dumps (void);
void enable_special_filenames (void);
const unsigned char *get_session_marker (size_t *rlen);
unsigned int get_uint_nonce (void);
/*int check_permissions (const char *path,int extension,int checkonly);*/
void gnupg_sleep (unsigned int seconds);
void gnupg_usleep (unsigned int usecs);
int translate_sys2libc_fd (gnupg_fd_t fd, int for_write);
int translate_sys2libc_fd_int (int fd, int for_write);
gpg_error_t gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd);
int check_special_filename (const char *fname, int for_write, int notranslate);
gnupg_fd_t gnupg_check_special_filename (const char *fname);
FILE *gnupg_tmpfile (void);
void gnupg_reopen_std (const char *pgmname);
void gnupg_inhibit_set_foregound_window (int yes);
void gnupg_allow_set_foregound_window (pid_t pid);
int gnupg_remove (const char *fname);
gpg_error_t gnupg_rename_file (const char *oldname, const char *newname,
int *block_signals);
int gnupg_mkdir (const char *name, const char *modestr);
int gnupg_chdir (const char *name);
int gnupg_rmdir (const char *name);
int gnupg_chmod (const char *name, const char *modestr);
char *gnupg_mkdtemp (char *template);
int gnupg_setenv (const char *name, const char *value, int overwrite);
int gnupg_unsetenv (const char *name);
char *gnupg_getcwd (void);
gpg_err_code_t gnupg_access (const char *name, int mode);
#ifdef HAVE_STAT
int gnupg_stat (const char *name, struct stat *statbuf);
#endif /*HAVE_STAT*/
int gnupg_open (const char *name, int flags, unsigned int mode);
gnupg_dir_t gnupg_opendir (const char *name);
gnupg_dirent_t gnupg_readdir (gnupg_dir_t gdir);
int gnupg_closedir (gnupg_dir_t gdir);
gpg_error_t gnupg_chuid (const char *user, int silent);
char *gnupg_get_socket_name (int fd);
int gnupg_fd_valid (int fd);
gpg_error_t gnupg_inotify_watch_delete_self (int *r_fd, const char *fname);
gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name);
int gnupg_inotify_has_name (int fd, const char *name);
estream_t open_stream_nc (gnupg_fd_t fd, const char *mode);
#ifdef HAVE_W32_SYSTEM
int gnupg_w32_set_errno (int ec);
void *w32_get_user_sid (void);
#include "../common/w32help.h"
#endif /*HAVE_W32_SYSTEM*/
#endif /*GNUPG_COMMON_SYSUTILS_H*/
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index 8b0fefc64..57f515b40 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1,2588 +1,2588 @@
/* dirmngr.c - Keyserver and X.509 LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003-2004, 2006-2007, 2008, 2010-2011, 2020 g10 Code GmbH
* Copyright (C) 2014 Werner Koch
*
* 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 .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
#include
#include
#endif
#include
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#ifdef HAVE_INOTIFY_INIT
# include
#endif /*HAVE_INOTIFY_INIT*/
#include
#include "dirmngr-err.h"
#if HTTP_USE_NTBTLS
# include
#elif HTTP_USE_GNUTLS
# include
#endif /*HTTP_USE_GNUTLS*/
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "dirmngr.h"
#include
#include "certcache.h"
#include "crlcache.h"
#include "crlfetch.h"
#include "misc.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "../common/asshelp.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "../common/comopt.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "dns-stuff.h"
#include "http-common.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values {
aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aServer,
aDaemon,
aSupervised,
aListCRLs,
aLoadCRL,
aFetchCRL,
aShutdown,
aFlush,
aGPGConfList,
aGPGConfTest,
aGPGConfVersions,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oDebugLevel,
oGnutlsDebug,
oDebugCacheExpiredCerts,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oBatch,
oDisableHTTP,
oDisableLDAP,
oDisableIPv4,
oDisableIPv6,
oIgnoreLDAPDP,
oIgnoreHTTPDP,
oIgnoreOCSPSvcUrl,
oHonorHTTPProxy,
oHTTPProxy,
oLDAPProxy,
oOnlyLDAPProxy,
oLDAPServer,
oLDAPFile,
oLDAPTimeout,
oLDAPAddServers,
oOCSPResponder,
oOCSPSigner,
oOCSPMaxClockSkew,
oOCSPMaxPeriod,
oOCSPCurrentPeriod,
oMaxReplies,
oHkpCaCert,
oFakedSystemTime,
oForce,
oAllowOCSP,
oAllowVersionCheck,
oStealSocket,
oSocketName,
oLDAPWrapperProgram,
oHTTPWrapperProgram,
oIgnoreCert,
oIgnoreCertExtension,
oIgnoreCRLExtension,
oUseTor,
oNoUseTor,
oKeyServer,
oNameServer,
oDisableCheckOwnSocket,
oStandardResolver,
oRecursiveResolver,
oResolverTimeout,
oConnectTimeout,
oConnectQuickTimeout,
oListenBacklog,
oFakeCRL,
oCompatibilityFlags,
aTest
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aGPGConfVersions, "gpgconf-versions", "@"),
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_c (aSupervised, "supervised", "@"),
#endif
ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check",
N_("allow online software version check")),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_s_i (oMaxReplies, "max-replies",
N_("|N|do not return more than N items in one query")),
ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oIgnoreCert,"ignore-cert", "@"),
ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
ARGPARSE_s_s (oIgnoreCRLExtension,"ignore-crl-extension", "@"),
ARGPARSE_header ("Network", N_("Network related options")),
ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")),
ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"),
ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"),
ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"),
ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"),
ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"),
ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"),
ARGPARSE_s_s (oNameServer, "nameserver", "@"),
ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"),
ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"),
ARGPARSE_header ("HTTP", N_("Configuration for HTTP servers")),
ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
N_("ignore HTTP CRL distribution points")),
ARGPARSE_s_s (oHTTPProxy, "http-proxy",
N_("|URL|redirect all HTTP requests to URL")),
ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy",
N_("use system's HTTP proxy setting")),
ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
ARGPARSE_header ("Keyserver", N_("Configuration for OpenPGP servers")),
ARGPARSE_s_s (oKeyServer, "keyserver",
N_("|URL|use keyserver at URL")),
ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
ARGPARSE_header ("LDAP", N_("Configuration for X.509 servers")),
ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
N_("ignore LDAP CRL distribution points")),
ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
N_("|HOST|use HOST for LDAP queries")),
ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
N_("do not use fallback hosts with --ldap-proxy")),
ARGPARSE_s_s (oLDAPServer, "ldapserver",
N_("|SPEC|use this keyserver to lookup keys")),
ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
N_("|FILE|read LDAP server list from FILE")),
ARGPARSE_s_n (oLDAPAddServers, "add-servers",
N_("add new servers discovered in CRL distribution"
" points to serverlist")),
ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
N_("|N|set LDAP timeout to N seconds")),
ARGPARSE_s_s (oFakeCRL, "fake-crl", "@"),
ARGPARSE_header ("OCSP", N_("Configuration for OCSP")),
ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
N_("ignore certificate contained OCSP service URLs")),
ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
N_("|URL|use OCSP responder at URL")),
ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
N_("|FPR|OCSP response signed by FPR")),
ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
ARGPARSE_header (NULL, N_("Other options")),
ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
ARGPARSE_s_n (oDebugCacheExpiredCerts, "debug-cache-expired-certs", "@"),
ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"),
ARGPARSE_header (NULL, ""), /* Stop the header group. */
/* Not yet used options. */
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
"of all commands and options)\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_DNS_VALUE , "dns" },
{ DBG_NETWORK_VALUE, "network" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ DBG_KEEPTMP_VALUE, "keeptmp" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* The list of compatibility flags. */
static struct compatibility_flags_s compatibility_flags [] =
{
{ COMPAT_RESTRICT_HTTP_REDIR, "restrict-http-redir" },
{ 0, NULL }
};
#define DEFAULT_MAX_REPLIES 10
#define DEFAULT_LDAP_TIMEOUT 15 /* seconds */
#define DEFAULT_CONNECT_TIMEOUT (15*1000) /* 15 seconds */
#define DEFAULT_CONNECT_QUICK_TIMEOUT ( 2*1000) /* 2 seconds */
/* For the cleanup handler we need to keep track of the socket's name. */
static const char *socket_name;
/* If the socket has been redirected, this is the name of the
redirected socket.. */
static const char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument.
* Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Only if this flag has been set will we remove the socket file. */
static int cleanup_socket;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Helper to implement --debug-level. */
static const char *debug_level;
/* Helper to set the NTBTLS or GNUTLS log level. */
static int opt_gnutls_debug = -1;
/* Flag indicating that a shutdown has been requested. */
static volatile int shutdown_pending;
/* Flags to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Flag to control the Tor mode. */
static enum
{ TOR_MODE_AUTO = 0, /* Switch to NO or YES */
TOR_MODE_NEVER, /* Never use Tor. */
TOR_MODE_NO, /* Do not use Tor */
TOR_MODE_YES, /* Use Tor */
TOR_MODE_FORCE /* Force using Tor */
} tor_mode;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Counter for the active connections. */
static int active_connections;
/* This flag is set by any network access and used by the housekeeping
* thread to run background network tasks. */
static int network_activity_seen;
/* A list of filenames registered with --hkp-cacert. */
static strlist_t hkp_cacert_filenames;
/* A flag used to clear the list of ldapservers iff --ldapserver is
* given on the command line or one of the conf files. In this case we
* want to clear all old specifications through the legacy
* dirmngr_ldapservers.conf. */
static int ldapserver_list_needs_reset;
/* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */
#define TIMERTICK_INTERVAL (60)
#define TIMERTICK_INTERVAL_SHUTDOWN (4)
/* How oft to run the housekeeping. */
#define HOUSEKEEPING_INTERVAL (600)
/* This union is used to avoid compiler warnings in case a pointer is
64 bit and an int 32 bit. We store an integer in a pointer and get
it back later (npth_getspecific et al.). */
union int_and_ptr_u
{
int aint;
assuan_fd_t afd;
void *aptr;
};
/* The key used to store the current file descriptor in the thread
local storage. We use this in conjunction with the
log_set_pid_suffix_cb feature. */
#ifndef HAVE_W32_SYSTEM
static npth_key_t my_tlskey_current_fd;
#endif
/* Prototypes. */
static void cleanup (void);
#if USE_LDAP
static ldap_server_t parse_ldapserver_file (const char* filename, int ienoent);
#endif /*USE_LDAP*/
static fingerprint_list_t parse_fingerprint_item (const char *string,
const char *optionname,
int want_binary);
static void netactivity_action (void);
static void handle_connections (assuan_fd_t listen_fd);
static void gpgconf_versions (void);
/* NPth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static const char *
my_strusage( int level )
{
const char *p;
switch ( level )
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@DIRMNGR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Callback from libksba to hash a provided buffer. Our current
implementation does only allow SHA-1 for hashing. This may be
extended by mapping the name, testing for algorithm availability
and adjust the length checks accordingly. */
static gpg_error_t
my_ksba_hash_buffer (void *arg, const char *oid,
const void *buffer, size_t length, size_t resultsize,
unsigned char *result, size_t *resultlen)
{
(void)arg;
if (oid && strcmp (oid, "1.3.14.3.2.26"))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (resultsize < 20)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
gcry_md_hash_buffer (2, result, buffer, length);
*resultlen = 20;
return 0;
}
/* GNUTLS log function callback. */
#ifdef HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
int n;
n = strlen (text);
while (n && text[n-1] == '\n')
n--;
log_debug ("gnutls:L%d: %.*s\n", level, n, text);
}
#endif /*HTTP_USE_GNUTLS*/
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE|DBG_KEEPTMP_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
log_info (_("valid debug levels are: %s\n"),
"none, basic, advanced, expert, guru");
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
{
opt.verbose = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
}
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
#if HTTP_USE_NTBTLS
if (opt_gnutls_debug >= 0)
{
ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
}
#elif HTTP_USE_GNUTLS
if (opt_gnutls_debug >= 0)
{
gnutls_global_set_log_function (my_gnutls_log);
gnutls_global_set_log_level (opt_gnutls_debug);
}
#endif /*HTTP_USE_GNUTLS*/
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_tor_mode (void)
{
if (dirmngr_use_tor ())
{
/* Enable Tor mode and when called again force a new circuit
* (e.g. on SIGHUP). */
enable_dns_tormode (1);
if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1))
{
log_error ("error enabling Tor mode: %s\n", strerror (errno));
log_info ("(is your Libassuan recent enough?)\n");
}
}
else
disable_dns_tormode ();
}
/* Return true if Tor shall be used. */
int
dirmngr_use_tor (void)
{
if (tor_mode == TOR_MODE_AUTO)
{
/* Figure out whether Tor is running. */
assuan_fd_t sock;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
tor_mode = TOR_MODE_NO;
else
{
tor_mode = TOR_MODE_YES;
assuan_sock_close (sock);
}
}
if (tor_mode == TOR_MODE_FORCE)
return 2; /* Use Tor (using 2 to indicate force mode) */
else if (tor_mode == TOR_MODE_YES)
return 1; /* Use Tor */
else
return 0; /* Do not use Tor. */
}
/* This is somewhat similar to dirmngr_use_tor but avoids a trial
* connect and may thus be faster for this special case. */
int
dirmngr_never_use_tor_p (void)
{
return tor_mode == TOR_MODE_NEVER;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
es_fputs (text, es_stderr);
es_putc ('\n', es_stderr);
dirmngr_exit (2);
}
/* Helper to stop the reaper thread for the ldap wrapper. */
static void
shutdown_reaper (void)
{
#if USE_LDAP
ldap_wrapper_wait_connections ();
#endif
}
/* Handle options which are allowed to be reset after program start.
Return true if the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
if (!pargs)
{ /* Reset mode. */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.ldap_wrapper_program = NULL;
opt.disable_http = 0;
opt.disable_ldap = 0;
opt.honor_http_proxy = 0;
opt.http_proxy = NULL;
opt.ldap_proxy = NULL;
opt.only_ldap_proxy = 0;
opt.ignore_http_dp = 0;
opt.ignore_ldap_dp = 0;
opt.ignore_ocsp_service_url = 0;
opt.allow_ocsp = 0;
opt.allow_version_check = 0;
opt.ocsp_responder = NULL;
opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
opt.ocsp_max_period = 90 * 86400; /* 90 days. */
opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
opt.max_replies = DEFAULT_MAX_REPLIES;
while (opt.ocsp_signer)
{
fingerprint_list_t tmp = opt.ocsp_signer->next;
xfree (opt.ocsp_signer);
opt.ocsp_signer = tmp;
}
while (opt.ignored_certs)
{
fingerprint_list_t tmp = opt.ignored_certs->next;
xfree (opt.ignored_certs);
opt.ignored_certs = tmp;
}
FREE_STRLIST (opt.ignored_cert_extensions);
FREE_STRLIST (opt.ignored_crl_extensions);
http_register_tls_ca (NULL);
FREE_STRLIST (hkp_cacert_filenames);
FREE_STRLIST (opt.keyserver);
/* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_AUTO;
disable_check_own_socket = 0;
enable_standard_resolver (0);
set_dns_timeout (0);
opt.connect_timeout = 0;
opt.connect_quick_timeout = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
ldapserver_list_needs_reset = 1;
opt.debug_cache_expired_certs = 0;
xfree (opt.fake_crl);
opt.fake_crl = NULL;
opt.compat_flags = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
case oLogFile:
if (!reread)
return 0; /* Not handled. */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oLDAPWrapperProgram:
opt.ldap_wrapper_program = pargs->r.ret_str;
break;
case oHTTPWrapperProgram:
opt.http_wrapper_program = pargs->r.ret_str;
break;
case oDisableHTTP: opt.disable_http = 1; break;
case oDisableLDAP: opt.disable_ldap = 1; break;
case oDisableIPv4: opt.disable_ipv4 = 1; break;
case oDisableIPv6: opt.disable_ipv6 = 1; break;
case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
case oAllowOCSP: opt.allow_ocsp = 1; break;
case oAllowVersionCheck: opt.allow_version_check = 1; break;
case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
case oOCSPSigner:
opt.ocsp_signer = parse_fingerprint_item (pargs->r.ret_str,
"--ocsp-signer", 0);
break;
case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
case oHkpCaCert:
{
/* We need to register the filenames with gnutls (http.c) and
* also for our own cert cache. */
char *tmpname;
/* Do tilde expansion and make path absolute. */
tmpname = make_absfilename (pargs->r.ret_str, NULL);
http_register_tls_ca (tmpname);
add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str);
xfree (tmpname);
}
break;
case oIgnoreCert:
{
fingerprint_list_t item, r;
item = parse_fingerprint_item (pargs->r.ret_str, "--ignore-cert", 20);
if (item)
{ /* Append */
if (!opt.ignored_certs)
opt.ignored_certs = item;
else
{
for (r = opt.ignored_certs; r->next; r = r->next)
;
r->next = item;
}
}
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
break;
case oIgnoreCRLExtension:
add_to_strlist (&opt.ignored_crl_extensions, pargs->r.ret_str);
break;
case oUseTor:
tor_mode = TOR_MODE_FORCE;
break;
case oNoUseTor:
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_NEVER;
break;
case oStandardResolver: enable_standard_resolver (1); break;
case oRecursiveResolver: enable_recursive_resolver (1); break;
case oLDAPServer:
#if USE_LDAP
{
ldap_server_t server;
char *p;
p = pargs->r.ret_str;
if (!strncmp (p, "ldap:", 5) && !(p[5] == '/' && p[6] == '/'))
p += 5;
server = ldapserver_parse_one (p, NULL, 0);
if (server)
{
if (ldapserver_list_needs_reset)
{
ldapserver_list_needs_reset = 0;
ldapserver_list_free (opt.ldapservers);
opt.ldapservers = NULL;
}
server->next = opt.ldapservers;
opt.ldapservers = server;
}
}
#endif
break;
case oKeyServer:
if (*pargs->r.ret_str)
add_to_strlist (&opt.keyserver, pargs->r.ret_str);
break;
case oNameServer:
set_dns_nameserver (pargs->r.ret_str);
break;
case oResolverTimeout:
set_dns_timeout (pargs->r.ret_int);
break;
case oConnectTimeout:
opt.connect_timeout = pargs->r.ret_ulong * 1000;
break;
case oConnectQuickTimeout:
opt.connect_quick_timeout = pargs->r.ret_ulong * 1000;
break;
case oLDAPTimeout:
opt.ldaptimeout = pargs->r.ret_int;
break;
case oDebugCacheExpiredCerts:
opt.debug_cache_expired_certs = 0;
break;
case oFakeCRL:
xfree (opt.fake_crl);
opt.fake_crl = *pargs->r.ret_str? xstrdup (pargs->r.ret_str) : NULL;
break;
case oCompatibilityFlags:
if (parse_compatibility_flags (pargs->r.ret_str, &opt.compat_flags,
compatibility_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_WARNING;
}
break;
default:
return 0; /* Not handled. */
}
set_dns_verbose (opt.verbose, !!DBG_DNS);
http_set_verbose (opt.verbose, !!DBG_NETWORK);
set_dns_disable_ipv4 (opt.disable_ipv4);
set_dns_disable_ipv6 (opt.disable_ipv6);
return 1; /* Handled. */
}
/* This function is called after option parsing to adjust some values
* and call option setup functions. */
static void
post_option_parsing (enum cmd_and_opt_values cmd)
{
/* It would be too surpirsing if the quick timeout is larger than
* the standard value. */
if (opt.connect_quick_timeout > opt.connect_timeout)
opt.connect_quick_timeout = opt.connect_timeout;
set_debug ();
/* For certain commands we do not want to set/test for Tor mode
* because that is somewhat expensive. */
switch (cmd)
{
case aGPGConfList:
case aGPGConfTest:
case aGPGConfVersions:
break;
default:
set_tor_mode ();
break;
}
}
#ifndef HAVE_W32_SYSTEM
static int
pid_suffix_callback (unsigned long *r_suffix)
{
union int_and_ptr_u value;
memset (&value, 0, sizeof value);
value.aptr = npth_getspecific (my_tlskey_current_fd);
*r_suffix = value.aint;
return (*r_suffix != -1); /* Use decimal representation. */
}
#endif /*!HAVE_W32_SYSTEM*/
#if HTTP_USE_NTBTLS
static void
my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv)
{
(void)opaque;
if (level == -1)
log_logv_prefix (GPGRT_LOGLVL_INFO, "ntbtls: ", fmt, argv);
else
{
char prefix[10+20];
snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level);
log_logv_prefix (GPGRT_LOGLVL_DEBUG, prefix, fmt, argv);
}
}
#endif
static void
thread_init (void)
{
npth_init ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now with NPth running we can set the logging callback. Our
windows implementation does not yet feature the NPth TLS
functions. */
#ifndef HAVE_W32_SYSTEM
if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
log_set_pid_suffix_cb (pid_suffix_callback);
#endif /*!HAVE_W32_SYSTEM*/
}
int
main (int argc, char **argv)
{
enum cmd_and_opt_values cmd = 0;
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int debug_argparser = 0;
int greeting = 0;
int nogreeting = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
#if USE_LDAP
char *ldapfile = NULL;
#endif /*USE_LDAP*/
int debug_wait = 0;
int rc;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
gpgrt_set_strusage (my_strusage);
log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the libraries. */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
/* Init TLS library. */
#if HTTP_USE_NTBTLS
if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
/* Init Assuan. */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor);
setup_libgcrypt_logging ();
#if HTTP_USE_NTBTLS
ntbtls_set_log_handler (my_ntbtls_log_handler, NULL);
#endif
/* Setup defaults. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Reset rereadable options to default values. */
parse_rereadable_options (NULL, 0);
/* Default TCP timeouts. */
opt.connect_timeout = DEFAULT_CONNECT_TIMEOUT;
opt.connect_quick_timeout = DEFAULT_CONNECT_QUICK_TIMEOUT;
/* LDAP defaults. */
opt.add_new_ldapservers = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
/* Other defaults. */
/* Check whether we have a config file given on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
socket_name = dirmngr_socket_name ();
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
* flags so that the internal intialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, DIRMNGR_NAME EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aServer:
case aDaemon:
case aSupervised:
case aShutdown:
case aFlush:
case aListCRLs:
case aLoadCRL:
case aFetchCRL:
case aGPGConfList:
case aGPGConfTest:
case aGPGConfVersions:
cmd = pargs.r_opt;
break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: /* Ignore this option here. */; break;
case oNoDetach: nodetach = 1; break;
case oStealSocket: steal_socket = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oLDAPFile:
# if USE_LDAP
ldapfile = pargs.r.ret_str;
# endif /*USE_LDAP*/
break;
case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
case oFakedSystemTime:
gnupg_set_time ((time_t)pargs.r.ret_ulong, 0);
break;
case oForce: opt.force = 1; break;
case oSocketName: socket_name = pargs.r.ret_str; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (),
DIRMNGR_NAME EXTSEP_S "conf",
NULL);
else
{
opt.config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_DIRMNGR, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
if (nogreeting )
greeting = 0;
if (!opt.homedir_cache)
opt.homedir_cache = xstrdup (gnupg_homedir ());
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14));
es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15));
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (!gnupg_access ("/etc/"DIRMNGR_NAME, F_OK)
&& !strncmp (gnupg_homedir (), "/etc/", 5))
log_info
("NOTE: DirMngr is now a proper part of %s. The configuration and"
" other directory names changed. Please check that no other version"
" of dirmngr is still installed. To disable this warning, remove the"
" directory '/etc/dirmngr'.\n", GNUPG_NAME);
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
post_option_parsing (cmd);
/* Get LDAP server list from file unless --ldapserver has been used. */
#if USE_LDAP
if (opt.ldapservers)
;
else if (!ldapfile)
{
ldapfile = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
opt.ldapservers = parse_ldapserver_file (ldapfile, 1);
xfree (ldapfile);
}
else
opt.ldapservers = parse_ldapserver_file (ldapfile, 0);
#endif /*USE_LDAP*/
#ifndef HAVE_W32_SYSTEM
/* We need to ignore the PIPE signal because the we might log to a
socket and that code handles EPIPE properly. The ldap wrapper
also requires us to ignore this silly signal. Assuan would set
this signal to ignore anyway.*/
signal (SIGPIPE, SIG_IGN);
#endif
/* Ready. Now to our duties. */
if (!cmd)
cmd = aServer;
rc = 0;
if (cmd == aServer)
{
/* Note that this server mode is mainly useful for debugging. */
if (argc)
wrong_args ("--server");
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
start_command_handler (ASSUAN_INVALID_FD, 0);
shutdown_reaper ();
}
#ifndef HAVE_W32_SYSTEM
else if (cmd == aSupervised)
{
struct stat statbuf;
if (!opt.quiet)
log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised");
is_supervised = 1;
/* In supervised mode, we expect file descriptor 3 to be an
already opened, listening socket.
We will also not detach from the controlling process or close
stderr; the supervisor should handle all of that. */
if (fstat (3, &statbuf) == -1 && errno == EBADF)
{
log_error ("file descriptor 3 must be validin --supervised mode\n");
dirmngr_exit (1);
}
socket_name = gnupg_get_socket_name (3);
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
else
log_set_prefix (NULL, 0);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (3);
shutdown_reaper ();
}
#endif /*HAVE_W32_SYSTEM*/
else if (cmd == aDaemon)
{
assuan_fd_t fd;
pid_t pid;
int len;
struct sockaddr_un serv_addr;
if (argc)
wrong_args ("--daemon");
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
#ifndef HAVE_W32_SYSTEM
if (strchr (socket_name, ':'))
{
log_error (_("colons are not allowed in the socket name\n"));
dirmngr_exit (1);
}
#endif
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
cleanup ();
dirmngr_exit (1);
}
{
int redirected;
if (assuan_sock_set_sockaddr_un (socket_name,
(struct sockaddr*)&serv_addr,
&redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), socket_name);
else
log_error ("error preparing socket '%s': %s\n",
socket_name,
gpg_strerror (gpg_error_from_syserror ()));
dirmngr_exit (1);
}
if (redirected)
{
redir_socket_name = xstrdup (serv_addr.sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n",
socket_name, redir_socket_name);
}
}
len = SUN_LEN (&serv_addr);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Fixme: We should actually test whether a dirmngr is
* already running. For now the steal option is a dummy. */
/* if (steal_socket) */
/* log_info (N_("trying to steal socket from running %s\n"), */
/* "dirmngr"); */
gnupg_remove (redir_socket_name? redir_socket_name : socket_name);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
}
if (rc != -1
&& (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
serv_addr.sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
dirmngr_exit (1);
}
cleanup_socket = 1;
if (gnupg_chmod (serv_addr.sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
serv_addr.sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
assuan_sock_close (fd);
dirmngr_exit (1);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), serv_addr.sun_path);
es_fflush (NULL);
/* Note: We keep the dirmngr_info output only for the sake of
existing scripts which might use this to detect a successful
start of the dirmngr. */
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
pid = getpid ();
es_printf ("set %s=%s;%lu;1\n",
DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
#else
pid = fork();
if (pid == (pid_t)-1)
{
log_fatal (_("error forking process: %s\n"), strerror (errno));
dirmngr_exit (1);
}
if (pid)
{ /* We are the parent */
char *infostr;
/* Don't let cleanup() remove the socket - the child is
responsible for doing that. */
cleanup_socket = 0;
close (fd);
/* Create the info string: :: */
if (asprintf (&infostr, "%s=%s:%lu:1",
DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0)
{
log_error (_("out of core\n"));
kill (pid, SIGTERM);
dirmngr_exit (1);
}
/* Print the environment string, so that the caller can use
shell's eval to set it. But see above. */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
}
free (infostr);
exit (0);
/*NEVER REACHED*/
} /* end parent */
/*
This is the child
*/
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
dirmngr_exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
dirmngr_exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
}
#endif
if (!nodetach )
{
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
dirmngr_exit (1);
}
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (fd);
shutdown_reaper ();
}
else if (cmd == aListCRLs)
{
/* Just list the CRL cache and exit. */
if (argc)
wrong_args ("--list-crls");
crl_cache_init ();
crl_cache_list (es_stdout);
}
else if (cmd == aLoadCRL)
{
struct server_control_s ctrlbuf;
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
if (!argc)
rc = crl_cache_load (&ctrlbuf, NULL);
else
{
for (; !rc && argc; argc--, argv++)
rc = crl_cache_load (&ctrlbuf, *argv);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFetchCRL)
{
ksba_reader_t reader;
struct server_control_s ctrlbuf;
if (argc != 1)
wrong_args ("--fetch-crl URL");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
rc = crl_fetch (&ctrlbuf, argv[0], &reader);
if (rc)
log_error (_("fetching CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
else
{
rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
if (rc)
log_error (_("processing CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
crl_close_reader (reader);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFlush)
{
/* Delete cache and exit. */
if (argc)
wrong_args ("--flush");
rc = crl_cache_flush();
}
else if (cmd == aGPGConfTest)
dirmngr_exit (0);
else if (cmd == aGPGConfList)
{
unsigned long flags = 0;
char *filename_esc;
es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
es_printf ("ldaptimeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
es_printf ("max-replies:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
filename_esc = percent_escape (get_default_keyserver (0), NULL);
es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
es_printf ("resolver-timeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, 0);
}
else if (cmd == aGPGConfVersions)
gpgconf_versions ();
cleanup ();
return !!rc;
}
static void
cleanup (void)
{
crl_cache_deinit ();
cert_cache_deinit (1);
reload_dns_stuff (1);
#if USE_LDAP
ldapserver_list_free (opt.ldapservers);
#endif /*USE_LDAP*/
opt.ldapservers = NULL;
if (cleanup_socket)
{
cleanup_socket = 0;
if (redir_socket_name)
gnupg_remove (redir_socket_name);
else if (socket_name && *socket_name)
gnupg_remove (socket_name);
}
}
void
dirmngr_exit (int rc)
{
cleanup ();
exit (rc);
}
void
dirmngr_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
if (opt.http_proxy)
ctrl->http_proxy = xstrdup (opt.http_proxy);
ctrl->http_no_crl = 1;
ctrl->timeout = opt.connect_timeout;
}
void
dirmngr_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
ctrl->magic = 0xdeadbeef;
xfree (ctrl->http_proxy);
ctrl->http_proxy = NULL;
nvc_release (ctrl->rootdse);
ctrl->rootdse = NULL;
}
/* Create a list of LDAP servers from the file FILENAME. Returns the
list or NULL in case of errors.
The format of such a file is line oriented where empty lines and
lines starting with a hash mark are ignored. All other lines are
assumed to be colon seprated with these fields:
1. field: Hostname
2. field: Portnumber
3. field: Username
4. field: Password
5. field: Base DN
*/
#if USE_LDAP
static ldap_server_t
parse_ldapserver_file (const char* filename, int ignore_enoent)
{
char buffer[1024];
char *p;
ldap_server_t server, serverstart, *serverend;
int c;
unsigned int lineno = 0;
estream_t fp;
fp = es_fopen (filename, "r");
if (!fp)
{
if (ignore_enoent && gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
;
else
log_info ("failed to open '%s': %s\n", filename, strerror (errno));
return NULL;
}
serverstart = NULL;
serverend = &serverstart;
while (es_fgets (buffer, sizeof buffer, fp))
{
lineno++;
if (!*buffer || buffer[strlen(buffer)-1] != '\n')
{
if (*buffer && es_feof (fp))
; /* Last line not terminated - continue. */
else
{
log_error (_("%s:%u: line too long - skipped\n"),
filename, lineno);
while ( (c=es_fgetc (fp)) != EOF && c != '\n')
; /* Skip until end of line. */
continue;
}
}
/* Skip empty and comment lines.*/
for (p=buffer; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
/* Parse the colon separated fields. */
server = ldapserver_parse_one (buffer, filename, lineno);
if (server)
{
*serverend = server;
serverend = &server->next;
}
}
if (es_ferror (fp))
log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
es_fclose (fp);
return serverstart;
}
#endif /*USE_LDAP*/
/* Parse a fingerprint entry as used by --ocsc-signer. OPTIONNAME as
* a description on the options used. WANT_BINARY requests to store a
* binary fingerprint. Returns NULL on error and logs that error. */
static fingerprint_list_t
parse_fingerprint_item (const char *string,
const char *optionname, int want_binary)
{
gpg_error_t err;
char *fname;
estream_t fp;
char line[256];
char *p;
fingerprint_list_t list, *list_tail, item;
unsigned int lnr = 0;
int c, i, j;
int errflag = 0;
/* Check whether this is not a filename and treat it as a direct
fingerprint specification. */
if (!strpbrk (string, "/.~\\"))
{
item = xcalloc (1, sizeof *item);
for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
if ( string[i] != ':' )
item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (string+i) || !string[i]))
{
log_error (_("%s:%u: invalid fingerprint detected\n"),
optionname, 0);
xfree (item);
return NULL;
}
if (want_binary)
{
item->binlen = 20;
hex2bin (item->hexfpr, item->hexfpr, 20);
}
return item;
}
/* Well, it is a filename. */
if (*string == '/' || (*string == '~' && string[1] == '/'))
fname = make_filename (string, NULL);
else
{
if (string[0] == '.' && string[1] == '/' )
string += 2;
fname = make_filename (gnupg_homedir (), string, NULL);
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
list = NULL;
list_tail = &list;
for (;;)
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
if (!es_feof (fp))
{
err = gpg_error_from_syserror ();
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
}
es_fclose (fp);
if (errflag)
{
while (list)
{
fingerprint_list_t tmp = list->next;
xfree (list);
list = tmp;
}
}
xfree (fname);
return list; /* Ready. */
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
/* */: GPG_ERR_INCOMPLETE_LINE);
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
continue;
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
item = xcalloc (1, sizeof *item);
*list_tail = item;
list_tail = &item->next;
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
errflag = 1;
}
else if (want_binary)
{
item->binlen = 20;
hex2bin (item->hexfpr, item->hexfpr, 20);
}
i++;
while (spacep (p+i))
i++;
if (p[i] && p[i] != '\n')
log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
}
/*NOTREACHED*/
}
/*
Stuff used in daemon mode.
*/
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the NPTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!opt.config_filename)
goto finish; /* No config file. */
twopart = strconcat (DIRMNGR_NAME EXTSEP_S "conf" PATHSEP_S,
opt.config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
post_option_parsing (0);
finish:
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_DIRMNGR, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* A global function which allows us to trigger the reload stuff from
other places. */
void
dirmngr_sighup_action (void)
{
log_info (_("SIGHUP received - "
"re-reading configuration and flushing caches\n"));
reread_configuration ();
set_tor_mode ();
cert_cache_deinit (0);
crl_cache_deinit ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
reload_dns_stuff (0);
ks_hkp_reload ();
}
/* This function is called if some network activity was done. At this
* point we know the we have a network and we can decide whether to
* run scheduled background tasks soon. The function should return
* quickly and only trigger actions for another thread. */
static void
netactivity_action (void)
{
network_activity_seen = 1;
}
/* The signal handler. */
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
dirmngr_sighup_action ();
break;
case SIGUSR1:
/* See also cmd_getinfo:"stats". */
cert_cache_print_stats (NULL);
domaininfo_print_stats (NULL);
break;
case SIGUSR2:
log_info (_("SIGUSR2 received - no action defined\n"));
break;
case SIGTERM:
if (!shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
else
log_info (_("SIGTERM received - still %d active connections\n"),
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
dirmngr_exit (0);
}
break;
case SIGINT:
log_info (_("SIGINT received - immediate shutdown\n"));
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
dirmngr_exit (0);
break;
default:
log_info (_("signal %d received - no action defined\n"), signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Thread to do the housekeeping. */
static void *
housekeeping_thread (void *arg)
{
static int sentinel;
time_t curtime;
struct server_control_s ctrlbuf;
(void)arg;
curtime = gnupg_get_time ();
if (sentinel)
{
log_info ("housekeeping is already going on\n");
return NULL;
}
sentinel++;
if (opt.verbose > 1)
log_info ("starting housekeeping\n");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
dns_stuff_housekeeping ();
ks_hkp_housekeeping (curtime);
if (network_activity_seen)
{
network_activity_seen = 0;
if (opt.allow_version_check)
dirmngr_load_swdb (&ctrlbuf, 0);
workqueue_run_global_tasks (&ctrlbuf, 1);
}
else
workqueue_run_global_tasks (&ctrlbuf, 0);
dirmngr_deinit_default_ctrl (&ctrlbuf);
if (opt.verbose > 1)
log_info ("ready with housekeeping\n");
sentinel--;
return NULL;
}
/* We try to enable correct overflow handling for signed int (commonly
* used for time_t). With gcc 4.2 -fno-strict-overflow was introduced
* and used here as a pragma. Later gcc versions (gcc 6?) removed
* this as a pragma and -fwrapv was then suggested as a replacement
* for -fno-strict-overflow. */
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC push_options
# pragma GCC optimize ("wrapv")
#endif
static int
time_for_housekeeping_p (time_t curtime)
{
static time_t last_housekeeping;
if (!last_housekeeping)
last_housekeeping = curtime;
if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
|| last_housekeeping > curtime /*(be prepared for y2038)*/)
{
last_housekeeping = curtime;
return 1;
}
return 0;
}
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC pop_options
#endif
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
struct stat statbuf;
if (time_for_housekeeping_p (gnupg_get_time ()))
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
log_error ("error preparing housekeeping thread: %s\n", strerror (err));
else
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
if (err)
log_error ("error spawning housekeeping thread: %s\n",
strerror (err));
npth_attr_destroy (&tattr);
}
}
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* Check the nonce on a new connection. This is a NOP unless we are
using our Unix domain socket emulation under Windows. */
static int
check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (fd), strerror (errno));
assuan_sock_close (fd);
return -1;
}
else
return 0;
}
/* Helper to call a connection's main function. */
static void *
start_connection_thread (void *arg)
{
static unsigned int last_session_id;
unsigned int session_id;
union int_and_ptr_u argval;
gnupg_fd_t fd;
memset (&argval, 0, sizeof argval);
argval.aptr = arg;
fd = argval.afd;
if (check_nonce (fd, &socket_nonce))
{
log_error ("handler nonce check FAILED\n");
return NULL;
}
#ifndef HAVE_W32_SYSTEM
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
active_connections++;
if (opt.verbose)
log_info (_("handler for fd %d started\n"), FD_DBG (fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
start_command_handler (fd, session_id);
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"), FD_DBG (fd));
active_connections--;
workqueue_run_post_session_tasks (session_id);
#ifndef HAVE_W32_SYSTEM
argval.afd = ASSUAN_INVALID_FD;
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
return NULL;
}
#ifdef HAVE_INOTIFY_INIT
/* Read an inotify event and return true if it matches NAME. */
static int
my_inotify_is_name (int fd, const char *name)
{
union {
struct inotify_event ev;
char _buf[sizeof (struct inotify_event) + 100 + 1];
} buf;
int n;
const char *s;
s = strrchr (name, '/');
if (s && s[1])
name = s + 1;
n = npth_read (fd, &buf, sizeof buf);
if (n < sizeof (struct inotify_event))
return 0;
if (buf.ev.len < strlen (name)+1)
return 0;
if (strcmp (buf.ev.name, name))
return 0; /* Not the desired file. */
return 1; /* Found. */
}
#endif /*HAVE_INOTIFY_INIT*/
/* Main loop in daemon mode. Note that LISTEN_FD will be owned by
* this function. */
static void
handle_connections (assuan_fd_t listen_fd)
{
npth_attr_t tattr;
#ifndef HAVE_W32_SYSTEM
int signo;
#endif
struct sockaddr_un paddr;
socklen_t plen = sizeof( paddr );
int nfd, ret;
fd_set fdset, read_fdset;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
int my_inotify_fd = -1;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM /* FIXME */
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
#ifdef HAVE_INOTIFY_INIT
if (disable_check_own_socket)
my_inotify_fd = -1;
else if ((my_inotify_fd = inotify_init ()) == -1)
log_info ("error enabling fast daemon termination: %s\n",
strerror (errno));
else
{
/* We need to watch the directory for the file because there
* won't be an IN_DELETE_SELF for a socket file. */
char *slash = strrchr (socket_name, '/');
log_assert (slash && slash[1]);
*slash = 0;
if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1)
{
close (my_inotify_fd);
my_inotify_fd = -1;
}
*slash = '/';
}
#endif /*HAVE_INOTIFY_INIT*/
/* Setup the fdset. It has only one member. This is because we use
pth_select instead of pth_accept to properly sync timeouts with
to full second. */
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
- nfd = FD2INT (listen_fd);
+ nfd = FD2NUM (listen_fd);
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
if (my_inotify_fd > nfd)
nfd = my_inotify_fd;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
/* Main loop. */
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections || is_supervised)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
/* assuan_sock_close (listen_fd); */
/* listen_fd = -1; */
FD_ZERO (&fdset);
nfd = -1;
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
nfd = my_inotify_fd;
}
}
/* Take a copy of the fdset. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. When a shutdown is pending we use a shorter
* interval to handle the shutdown more quickly. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += (shutdown_pending
? TIMERTICK_INTERVAL_SHUTDOWN
: TIMERTICK_INTERVAL);
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
if (shutdown_pending)
{
/* Do not anymore accept connections. */
continue;
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset)
&& my_inotify_is_name (my_inotify_fd, socket_name))
{
shutdown_pending = 1;
log_info ("socket file has been removed - shutting down\n");
}
#endif /*HAVE_INOTIFY_INIT*/
if (FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
gnupg_fd_t fd;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else
{
char threadname[50];
union int_and_ptr_u argval;
npth_t thread;
memset (&argval, 0, sizeof argval);
argval.afd = fd;
snprintf (threadname, sizeof threadname,
"conn fd=%d", FD_DBG (fd));
ret = npth_create (&thread, &tattr,
start_connection_thread, argval.aptr);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret) );
assuan_sock_close (fd);
}
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1)
close (my_inotify_fd);
#endif /*HAVE_INOTIFY_INIT*/
npth_attr_destroy (&tattr);
if (listen_fd != GNUPG_INVALID_FD)
assuan_sock_close (listen_fd);
cleanup ();
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
}
const char*
dirmngr_get_current_socket_name (void)
{
if (socket_name)
return socket_name;
else
return dirmngr_socket_name ();
}
/* Parse the revision part from the extended version blurb. */
static const char *
get_revision_from_blurb (const char *blurb, int *r_len)
{
const char *s = blurb? blurb : "";
int n;
for (; *s; s++)
if (*s == '\n' && s[1] == '(')
break;
if (*s)
{
s += 2;
for (n=0; s[n] && s[n] != ' '; n++)
;
}
else
{
s = "?";
n = 1;
}
*r_len = n;
return s;
}
/* Print versions of dirmngr and used libraries. This is used by
* "gpgconf --show-versions" so that there is no need to link gpgconf
* against all these libraries. This is an internal API and should
* not be relied upon. */
static void
gpgconf_versions (void)
{
const char *s;
int n;
/* Unfortunately Npth has no way to get the version. */
s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n",
assuan_check_version (NULL), n, s);
s = get_revision_from_blurb (ksba_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* KSBA %s (%.*s)\n\n",
ksba_check_version (NULL), n, s);
#ifdef HTTP_USE_NTBTLS
s = get_revision_from_blurb (ntbtls_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* NTBTLS %s (%.*s)\n\n",
ntbtls_check_version (NULL), n, s);
#elif HTTP_USE_GNUTLS
es_fprintf (es_stdout, "* GNUTLS %s\n\n",
gnutls_check_version (NULL));
#endif
}
diff --git a/dirmngr/http.c b/dirmngr/http.c
index 2345ce8c1..a7fd6ec26 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1,3836 +1,3836 @@
/* http.c - HTTP protocol handler
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, 2011, 2014 Werner Koch
* Copyright (C) 2015-2017, 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - 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.
*
* or both in parallel, as here.
*
* This file 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 .
*/
/* Simple HTTP client implementation. We try to keep the code as
self-contained as possible. There are some constraints however:
- estream is required. We now require estream because it provides a
very useful and portable asprintf implementation and the fopencookie
function.
- stpcpy is required
- fixme: list other requirements.
- Either HTTP_USE_NTBTLS or HTTP_USE_GNUTLS must be defined to select
which TLS library to use.
- With HTTP_NO_WSASTARTUP the socket initialization is not done
under Windows. This is useful if the socket layer has already
been initialized elsewhere. This also avoids the installation of
an exit handler to cleanup the socket layer.
*/
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include
# endif
# include
# ifndef EHOSTUNREACH
# define EHOSTUNREACH WSAEHOSTUNREACH
# endif
# ifndef EAFNOSUPPORT
# define EAFNOSUPPORT WSAEAFNOSUPPORT
# endif
#else /*!HAVE_W32_SYSTEM*/
# include
# include
# include
# include
# include
# include
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include
#endif
#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
#endif
#ifdef HTTP_USE_NTBTLS
# include
#elif HTTP_USE_GNUTLS
# include
# include
#endif /*HTTP_USE_GNUTLS*/
#include /* We need the socket wrapper. */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "dns-stuff.h"
#include "dirmngr-status.h" /* (dirmngr_status_printf) */
#include "http.h"
#include "http-common.h"
#ifdef USE_NPTH
# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) npth_accept ((a), (b), (c))
#else
# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) accept ((a), (b), (c))
#endif
#ifdef HAVE_W32_SYSTEM
#define sock_close(a) closesocket(a)
#else
#define sock_close(a) close(a)
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef INADDR_NONE /* Slowaris is missing that. */
#define INADDR_NONE ((unsigned long)(-1))
#endif /*INADDR_NONE*/
#define HTTP_PROXY_ENV "http_proxy"
#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890@" \
"!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
#if HTTP_USE_NTBTLS
typedef ntbtls_t tls_session_t;
#elif HTTP_USE_GNUTLS
typedef gnutls_session_t tls_session_t;
#else
# error building without TLS is not supported
#endif
static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls);
static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls);
static int remove_escapes (char *string);
static int insert_escapes (char *buffer, const char *string,
const char *special);
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (ctrl_t ctrl, http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag, unsigned int timeout,
strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (ctrl_t ctrl,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
unsigned int timeout, assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length);
static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
static gpgrt_ssize_t cookie_write (void *cookie,
const void *buffer, size_t size);
static int cookie_close (void *cookie);
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t simple_cookie_read (void *cookie,
void *buffer, size_t size);
static gpgrt_ssize_t simple_cookie_write (void *cookie,
const void *buffer, size_t size);
#endif
/* A socket object used to a allow ref counting of sockets. */
struct my_socket_s
{
assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */
int refcount; /* Number of references to this socket. */
};
typedef struct my_socket_s *my_socket_t;
/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
cookie_write,
NULL,
cookie_close
};
struct cookie_s
{
/* Socket object or NULL if already closed. */
my_socket_t sock;
/* The session object or NULL if not used. */
http_session_t session;
/* True if TLS is to be used. */
int use_tls;
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
};
typedef struct cookie_s *cookie_t;
/* Simple cookie functions. Here the cookie is an int with the
* socket. */
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static es_cookie_io_functions_t simple_cookie_functions =
{
simple_cookie_read,
simple_cookie_write,
NULL,
NULL
};
#endif
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
#else
# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */
#endif
/* The session object. */
struct http_session_s
{
unsigned long magic;
int refcount; /* Number of references to this object. */
tls_session_t tls_session;
struct {
int done; /* Verifciation has been done. */
int rc; /* TLS verification return code. */
unsigned int status; /* Verification status. */
} verify;
char *servername; /* Malloced server name. */
/* A callback function to log details of TLS certifciates. */
void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
const void **, size_t *);
/* The flags passed to the session object. */
unsigned int flags;
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
/* The connect timeout */
unsigned int connect_timeout;
#ifdef HTTP_USE_GNUTLS
gnutls_certificate_credentials_t certcred;
#endif /*HTTP_USE_GNUTLS*/
};
/* An object to save header lines. */
struct header_s
{
struct header_s *next;
char *value; /* The value of the header (malloced). */
char name[1]; /* The name of the header (canonicalized). */
};
typedef struct header_s *header_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
#else
# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */
#endif
/* Our handle context. */
struct http_context_s
{
unsigned long magic;
unsigned int status_code;
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
void *read_cookie;
http_session_t session;
parsed_uri_t uri;
http_req_t req_type;
char *buffer; /* Line buffer. */
size_t buffer_size;
unsigned int flags;
header_t headers; /* Received headers. */
};
/* Two flags to enable verbose and debug mode. Although currently not
* set-able a value > 1 for OPT_DEBUG enables debugging of the session
* reference counting. */
static int opt_verbose;
static int opt_debug;
/* The global callback for the verification function. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
/* The list of files with trusted CA certificates. */
static strlist_t tls_ca_certlist;
/* The list of files with extra trusted CA certificates. */
static strlist_t cfg_ca_certlist;
/* The global callback for net activity. */
static void (*netactivity_cb)(void);
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
#if GNUPG_MAJOR_VERSION == 1
#define REQ_WINSOCK_MAJOR 1
#define REQ_WINSOCK_MINOR 1
#else
#define REQ_WINSOCK_MAJOR 2
#define REQ_WINSOCK_MINOR 2
#endif
static void
deinit_sockets (void)
{
WSACleanup();
}
static void
init_sockets (void)
{
static int initialized;
static WSADATA wsdata;
if (initialized)
return;
if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
{
log_error ("error initializing socket library: ec=%d\n",
(int)WSAGetLastError () );
return;
}
if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
|| HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
{
log_error ("socket library version is %x.%x - but %d.%d needed\n",
LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
WSACleanup();
return;
}
atexit ( deinit_sockets );
initialized = 1;
}
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
/* Create a new socket object. Returns NULL and closes FD if not
enough memory is available. */
static my_socket_t
_my_socket_new (int lnr, assuan_fd_t fd)
{
my_socket_t so;
so = xtrymalloc (sizeof *so);
if (!so)
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return NULL;
}
so->fd = fd;
so->refcount = 1;
if (opt_debug)
log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
lnr, so, FD_DBG (so->fd));
return so;
}
#define my_socket_new(a) _my_socket_new (__LINE__, (a))
/* Bump up the reference counter for the socket object SO. */
static my_socket_t
_my_socket_ref (int lnr, my_socket_t so)
{
so->refcount++;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
lnr, so, FD_DBG (so->fd), so->refcount);
return so;
}
#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
/* Bump down the reference counter for the socket object SO. If SO
has no more references, close the socket and release the
object. */
static void
_my_socket_unref (int lnr, my_socket_t so,
void (*preclose)(void*), void *preclosearg)
{
if (so)
{
so->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
lnr, so, FD_DBG (so->fd), so->refcount);
if (!so->refcount)
{
if (preclose)
preclose (preclosearg);
assuan_sock_close (so->fd);
xfree (so);
}
}
}
#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
#ifdef HTTP_USE_GNUTLS
static ssize_t
my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
{
my_socket_t sock = ptr;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
int nread;
# if USE_NPTH
npth_unprotect ();
# endif
nread = recv (FD2INT (sock->fd), buffer, size, 0);
# if USE_NPTH
npth_protect ();
# endif
return nread;
#else /* !HAVE_W32_SYSTEM */
# if USE_NPTH
return npth_read (sock->fd, buffer, size);
# else
return read (sock->fd, buffer, size);
# endif
#endif
}
static ssize_t
my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
{
my_socket_t sock = ptr;
#ifdef HAVE_W32_SYSTEM
int nwritten;
# if USE_NPTH
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock->fd), buffer, size, 0);
# if USE_NPTH
npth_protect ();
# endif
return nwritten;
#else /*!HAVE_W32_SYSTEM*/
# if USE_NPTH
return npth_write (sock->fd, buffer, size);
# else
return write (sock->fd, buffer, size);
# endif
#endif
}
#endif /*HTTP_USE_GNUTLS*/
#ifdef HTTP_USE_NTBTLS
/* Connect the ntbls callback to our generic callback. */
static gpg_error_t
my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
{
http_t hd = opaque;
(void)verify_flags;
log_assert (hd && hd->session && hd->session->verify_cb);
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
return hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
tls);
}
#endif /*HTTP_USE_NTBTLS*/
/* This notification function is called by estream whenever stream is
closed. Its purpose is to mark the closing in the handle so
that a http_close won't accidentally close the estream. The function
http_close removes this notification so that it won't be called if
http_close was used before an es_fclose. */
static void
fp_onclose_notification (estream_t stream, void *opaque)
{
http_t hd = opaque;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
if (hd->fp_read && hd->fp_read == stream)
hd->fp_read = NULL;
else if (hd->fp_write && hd->fp_write == stream)
hd->fp_write = NULL;
}
/*
* Helper function to create an HTTP header with hex encoded data. A
* new buffer is returned. This buffer is the concatenation of the
* string PREFIX, the hex-encoded DATA of length LEN and the string
* SUFFIX. On error NULL is returned and ERRNO set.
*/
static char *
make_header_line (const char *prefix, const char *suffix,
const void *data, size_t len )
{
static unsigned char bintoasc[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const unsigned char *s = data;
char *buffer, *p;
buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
if (!buffer)
return NULL;
p = stpcpy (buffer, prefix);
for ( ; len >= 3 ; len -= 3, s += 3 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
*p++ = bintoasc[s[2]&077];
*p = 0;
}
if ( len == 2 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[((s[1]<<2)&074)];
*p++ = '=';
}
else if ( len == 1 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(s[0] <<4)&060];
*p++ = '=';
*p++ = '=';
}
*p = 0;
strcpy (p, suffix);
return buffer;
}
/* Set verbosity and debug mode for this module. */
void
http_set_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
always returns NULL. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
tls_callback = cb;
}
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
removed. */
void
http_register_tls_ca (const char *fname)
{
gpg_err_code_t ec;
strlist_t sl;
if (!fname)
{
free_strlist (tls_ca_certlist);
tls_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if ((ec = gnupg_access (fname, F_OK)))
log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec));
sl = add_to_strlist (&tls_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a CA certificate for future use. The certificate is
* expected to be in FNAME. PEM format is assume if FNAME has a
* suffix of ".pem". If FNAME is NULL the list of CA files is
* removed. This is a variant of http_register_tls_ca which puts the
* certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */
void
http_register_cfg_ca (const char *fname)
{
gpg_err_code_t ec;
strlist_t sl;
if (!fname)
{
free_strlist (cfg_ca_certlist);
cfg_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if ((ec = gnupg_access (fname, F_OK)))
log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec));
sl = add_to_strlist (&cfg_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a callback which is called every time the HTTP mode has
* made a successful connection to some server. */
void
http_register_netactivity_cb (void (*cb)(void))
{
netactivity_cb = cb;
}
/* Call the netactivity callback if any. */
static void
notify_netactivity (void)
{
if (netactivity_cb)
netactivity_cb ();
}
/* Free the TLS session associated with SESS, if any. */
static void
close_tls_session (http_session_t sess)
{
if (sess->tls_session)
{
#if HTTP_USE_NTBTLS
/* FIXME!!
Possibly, ntbtls_get_transport and close those streams.
Somehow get SOCK to call my_socket_unref.
*/
ntbtls_release (sess->tls_session);
#elif HTTP_USE_GNUTLS
my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
my_socket_unref (sock, NULL, NULL);
gnutls_deinit (sess->tls_session);
if (sess->certcred)
gnutls_certificate_free_credentials (sess->certcred);
#endif /*HTTP_USE_GNUTLS*/
xfree (sess->servername);
sess->tls_session = NULL;
}
}
/* Release a session. Take care not to release it while it is being
used by a http context object. */
static void
session_unref (int lnr, http_session_t sess)
{
if (!sess)
return;
log_assert (sess->magic == HTTP_SESSION_MAGIC);
sess->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
lnr, sess, sess->refcount);
if (sess->refcount)
return;
close_tls_session (sess);
sess->magic = 0xdeadbeef;
xfree (sess);
}
#define http_session_unref(a) session_unref (__LINE__, (a))
void
http_session_release (http_session_t sess)
{
http_session_unref (sess);
}
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
* HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
* HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
* HTTP_FLAG_TRUST_CFG - Also use CAs set with http_register_cfg_ca
* HTTP_FLAG_NO_CRL - Do not consult CRLs for https.
*/
gpg_error_t
http_session_new (http_session_t *r_session,
const char *intended_hostname, unsigned int flags,
http_verify_cb_t verify_cb, void *verify_cb_value)
{
gpg_error_t err;
http_session_t sess;
*r_session = NULL;
sess = xtrycalloc (1, sizeof *sess);
if (!sess)
return gpg_error_from_syserror ();
sess->magic = HTTP_SESSION_MAGIC;
sess->refcount = 1;
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
sess->connect_timeout = 0;
#if HTTP_USE_NTBTLS
{
(void)intended_hostname; /* Not needed because we do not preload
* certificates. */
err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
if (err)
{
log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
goto leave;
}
}
#elif HTTP_USE_GNUTLS
{
const char *errpos;
int rc;
strlist_t sl;
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Disabled for 2.3.2 to due problems with the standard hkps pool. */
/* is_hkps_pool = (intended_hostname */
/* && !ascii_strcasecmp (intended_hostname, */
/* get_default_keyserver (1))); */
is_hkps_pool = 0;
/* If we are looking for the hkps pool from sks-keyservers.net,
* then forcefully use its dedicated certificate authority. */
/* Disabled for 2.3.2 because the service had to be shutdown. */
/* if (is_hkps_pool) */
/* { */
/* char *pemname = make_filename_try (gnupg_datadir (), */
/* "sks-keyservers.netCA.pem", NULL); */
/* if (!pemname) */
/* { */
/* err = gpg_error_from_syserror (); */
/* log_error ("setting CA from file '%s' failed: %s\n", */
/* pemname, gpg_strerror (err)); */
/* } */
/* else */
/* { */
/* rc = gnutls_certificate_set_x509_trust_file */
/* (sess->certcred, pemname, GNUTLS_X509_FMT_PEM); */
/* if (rc < 0) */
/* log_info ("setting CA from file '%s' failed: %s\n", */
/* pemname, gnutls_strerror (rc)); */
/* xfree (pemname); */
/* } */
/* */
/* if (is_hkps_pool) */
/* add_system_cas = 0; */
/* } */
/* Add configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_DEF) && !is_hkps_pool)
{
for (sl = tls_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
/* If HKP trust is requested and there are no HKP certificates
* configured, also try the standard system certificates. */
if (!tls_ca_certlist)
add_system_cas = 1;
}
/* Add system certificates to the session. */
if (add_system_cas)
{
#if GNUTLS_VERSION_NUMBER >= 0x030014
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
if (rc < 0)
log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
else if (!shown)
{
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
#endif /* gnutls >= 3.0.20 */
}
/* Add other configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_CFG) && !is_hkps_pool)
{
for (sl = cfg_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting extra CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
}
rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
if (rc < 0)
{
log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* A new session has the transport ptr set to (void*(-1), we need
it to be NULL. */
gnutls_transport_set_ptr (sess->tls_session, NULL);
rc = gnutls_priority_set_direct (sess->tls_session,
"NORMAL",
&errpos);
if (rc < 0)
{
log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
errpos, gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnutls_credentials_set (sess->tls_session,
GNUTLS_CRD_CERTIFICATE, sess->certcred);
if (rc < 0)
{
log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
{
(void)intended_hostname;
(void)flags;
}
#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
if (opt_debug > 1)
log_debug ("http.c:session_new: sess %p created\n", sess);
err = 0;
leave:
if (err)
http_session_unref (sess);
else
*r_session = sess;
return err;
}
/* Increment the reference count for session SESS. Passing NULL for
SESS is allowed. */
http_session_t
http_session_ref (http_session_t sess)
{
if (sess)
{
sess->refcount++;
if (opt_debug > 1)
log_debug ("http.c:session_ref: sess %p ref now %d\n",
sess, sess->refcount);
}
return sess;
}
void
http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *hostname,
const void **certs, size_t *certlens))
{
sess->cert_log_cb = cb;
}
/* Set the TIMEOUT in milliseconds for the connection's connect
* calls. Using 0 disables the timeout. */
void
http_session_set_timeout (http_session_t sess, unsigned int timeout)
{
sess->connect_timeout = timeout;
}
/* Start a HTTP retrieval and on success store at R_HD a context
pointer for completing the request and to wait for the response.
If HTTPHOST is not NULL it is used for the Host header instead of a
Host header derived from the URL. */
gpg_error_t
http_open (ctrl_t ctrl, http_t *r_hd, http_req_t reqtype, const char *url,
const char *httphost,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
http_t hd;
*r_hd = NULL;
if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = reqtype;
hd->flags = flags;
hd->session = http_session_ref (session);
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (ctrl, hd, httphost, auth, proxy, srvtag,
hd->session? hd->session->connect_timeout : 0,
headers);
if (err)
{
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. TIMEOUT is in milliseconds. */
gpg_error_t
http_raw_connect (ctrl_t ctrl, http_t *r_hd,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout)
{
gpg_error_t err = 0;
http_t hd;
cookie_t cookie;
*r_hd = NULL;
if ((flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = HTTP_REQ_OPAQUE;
hd->flags = flags;
/* Connect. */
{
assuan_fd_t sock;
err = connect_server (ctrl, server, port,
hd->flags, srvtag, timeout, &sock);
if (err)
{
xfree (hd);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (hd);
return err;
}
}
/* Setup estreams for reading and writing. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
leave:
if (err)
{
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
my_socket_unref (hd->sock, NULL, NULL);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
void
http_start_data (http_t hd)
{
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string ("\r\n", "http.c:request-header:");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
}
else
es_fflush (hd->fp_write);
}
gpg_error_t
http_wait_response (http_t hd)
{
gpg_error_t err;
cookie_t cookie;
int use_tls;
/* Make sure that we are in the data. */
http_start_data (hd);
/* Close the write stream. Note that the reference counted socket
object keeps the actual system socket open. */
cookie = hd->write_cookie;
if (!cookie)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
use_tls = cookie->use_tls;
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it to NULL. */
hd->write_cookie = NULL;
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
if ((hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (FD2INT (hd->sock->fd), 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
cookie->sock = my_socket_ref (hd->sock);
cookie->session = http_session_ref (hd->session);
cookie->use_tls = use_tls;
hd->read_cookie = cookie;
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
http_session_unref (cookie->session);
xfree (cookie);
hd->read_cookie = NULL;
return err;
}
err = parse_response (hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
}
/* Convenience function to send a request and wait for the response.
Closes the handle on error. If PROXY is not NULL, this value will
be used as an HTTP proxy and any enabled $http_proxy gets
ignored. */
gpg_error_t
http_open_document (ctrl_t ctrl, http_t *r_hd, const char *document,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session,
const char *srvtag, strlist_t headers)
{
gpg_error_t err;
err = http_open (ctrl, r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
proxy, session, srvtag, headers);
if (err)
return err;
err = http_wait_response (*r_hd);
if (err)
http_close (*r_hd, 0);
return err;
}
void
http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
/* First remove the close notifications for the streams. */
if (hd->fp_read)
es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
if (hd->fp_write)
es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
/* Now we can close the streams. */
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
hd->magic = 0xdeadbeef;
http_release_parsed_uri (hd->uri);
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
xfree (hd->buffer);
xfree (hd);
}
estream_t
http_get_read_ptr (http_t hd)
{
return hd?hd->fp_read:NULL;
}
estream_t
http_get_write_ptr (http_t hd)
{
return hd?hd->fp_write:NULL;
}
unsigned int
http_get_status_code (http_t hd)
{
return hd?hd->status_code:0;
}
/* Return information pertaining to TLS. If TLS is not in use for HD,
NULL is returned. WHAT is used ask for specific information:
(NULL) := Only check whether TLS is in use. Returns an
unspecified string if TLS is in use. That string may
even be the empty string.
*/
const char *
http_get_tls_info (http_t hd, const char *what)
{
(void)what;
if (!hd)
return NULL;
return hd->uri->use_tls? "":NULL;
}
static gpg_error_t
parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls)
{
gpg_err_code_t ec;
*ret_uri = xtrycalloc (1, sizeof **ret_uri + 2 * strlen (uri) + 1);
if (!*ret_uri)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
strcpy ((*ret_uri)->buffer, uri);
strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri);
(*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1;
ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
if (ec)
{
http_release_parsed_uri (*ret_uri);
*ret_uri = NULL;
}
return gpg_err_make (default_errsource, ec);
}
/*
* Parse an URI and put the result into the newly allocated RET_URI.
* On success the caller must use http_release_parsed_uri() to
* releases the resources. If the HTTP_PARSE_NO_SCHEME_CHECK flag is
* set, the function tries to parse the URL in the same way it would
* do for an HTTP style URI. */
gpg_error_t
http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
unsigned int flags)
{
return parse_uri (ret_uri, uri, !!(flags & HTTP_PARSE_NO_SCHEME_CHECK), 0);
}
void
http_release_parsed_uri (parsed_uri_t uri)
{
if (uri)
{
uri_tuple_t r, r2;
for (r = uri->params; r; r = r2)
{
r2 = r->next;
xfree (r);
}
for (r = uri->query; r; r = r2)
{
r2 = r->next;
xfree (r);
}
xfree (uri);
}
}
static gpg_err_code_t
do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls)
{
uri_tuple_t *tail;
char *p, *p2, *p3, *pp;
int n;
p = uri->buffer;
n = strlen (uri->buffer);
/* Initialize all fields to an empty string or an empty list. */
uri->scheme = uri->host = uri->path = p + n;
uri->port = 0;
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
uri->is_ldap = 0;
uri->opaque = 0;
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
uri->off_host = 0;
uri->off_path = 0;
/* A quick validity check unless we have the opaque scheme. */
if (strspn (p, VALID_URI_CHARS) != n
&& strncmp (p, "opaque:", 7))
return GPG_ERR_BAD_URI; /* Invalid characters found. */
if (!only_local_part)
{
/* Find the scheme. */
if (!(p2 = strchr (p, ':')) || p2 == p)
return GPG_ERR_BAD_URI; /* No scheme. */
*p2++ = 0;
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
uri->scheme = p;
if (!strcmp (uri->scheme, "http") && !force_tls)
{
uri->port = 80;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "hkp") && !force_tls)
{
uri->port = 11371;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
|| (force_tls && (!strcmp (uri->scheme, "http")
|| !strcmp (uri->scheme,"hkp"))))
{
uri->port = 443;
uri->is_http = 1;
uri->use_tls = 1;
}
else if (!strcmp (uri->scheme, "opaque"))
{
uri->opaque = 1;
uri->path = p2;
return 0;
}
else if (!no_scheme_check)
return GPG_ERR_INV_URI; /* Not an http style scheme. */
else if (!strcmp (uri->scheme, "ldap") && !force_tls)
{
uri->port = 389;
uri->is_ldap = 1;
}
else if (!strcmp (uri->scheme, "ldaps")
|| (force_tls && (!strcmp (uri->scheme, "ldap"))))
{
uri->port = 636;
uri->is_ldap = 1;
uri->use_tls = 1;
}
else if (!strcmp (uri->scheme, "ldapi")) /* LDAP via IPC. */
{
uri->port = 0;
uri->is_ldap = 1;
}
p = p2;
if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
p += 2;
if ((p2 = strchr (p, '/')))
{
if (p2 - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = p2 - uri->buffer;
*p2++ = 0;
}
else
{
n = (p - uri->buffer) + strlen (p);
if (n > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = n;
}
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
{
uri->auth = p;
*p3++ = '\0';
p = p3;
}
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
/* Handle an IPv6 literal */
if( *p == '[' && (p3=strchr( p, ']' )) )
{
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = (p + 1) - uri->buffer;
uri->v6lit = 1;
p = p3;
}
else
{
uri->host = p;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = p - uri->buffer;
}
if ((p3 = strchr (p, ':')))
{
*p3++ = '\0';
uri->port = atoi (p3);
uri->explicit_port = 1;
}
if ((n = remove_escapes (uri->host)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (uri->host))
return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */
p = p2 ? p2 : NULL;
}
else if (!no_scheme_check && (uri->is_http || uri->is_ldap))
return GPG_ERR_INV_URI; /* HTTP or LDAP w/o leading double slash. */
else
{
uri->opaque = 1;
uri->path = p;
if (is_onion_address (uri->path))
uri->onion = 1;
return 0;
}
} /* End global URI part. */
/* Parse the pathname part if any. */
if (p && *p)
{
/* TODO: Here we have to check params. */
/* Do we have a query part? */
if ((p2 = strchr (p, '?')))
*p2++ = 0;
uri->path = p;
if ((n = remove_escapes (p)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (p))
return GPG_ERR_BAD_URI; /* Path includes a Nul. */
p = p2 ? p2 : NULL;
/* Parse a query string if any. */
if (p && *p)
{
tail = &uri->query;
for (;;)
{
uri_tuple_t elem;
if ((p2 = strchr (p, '&')))
*p2++ = 0;
if (!(elem = parse_tuple (p)))
return GPG_ERR_BAD_URI;
*tail = elem;
tail = &elem->next;
if (!p2)
break; /* Ready. */
p = p2;
}
}
}
if (is_onion_address (uri->host))
uri->onion = 1;
return 0;
}
/*
* Remove all %xx escapes; this is done in-place. Returns: New length
* of the string.
*/
static int
remove_escapes (char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = (unsigned char*)string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
{
s++;
*p = *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
*p <<= 4;
s++;
*p |= *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Make sure to keep a string terminator. */
return n;
}
/* If SPECIAL is NULL this function escapes in forms mode. */
static size_t
escape_data (char *buffer, const void *data, size_t datalen,
const char *special)
{
int forms = !special;
const unsigned char *s;
size_t n = 0;
if (forms)
special = "%;?&=";
for (s = data; datalen; s++, datalen--)
{
if (forms && *s == ' ')
{
if (buffer)
*buffer++ = '+';
n++;
}
else if (forms && *s == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
}
else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
s++;
datalen--;
}
else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
{
if (buffer)
*(unsigned char*)buffer++ = *s;
n++;
}
else
{
if (buffer)
{
snprintf (buffer, 4, "%%%02X", *s);
buffer += 3;
}
n += 3;
}
}
return n;
}
static int
insert_escapes (char *buffer, const char *string,
const char *special)
{
return escape_data (buffer, string, strlen (string), special);
}
/* Allocate a new string from STRING using standard HTTP escaping as
well as escaping of characters given in SPECIALS. A common pattern
for SPECIALS is "%;?&=". However it depends on the needs, for
example "+" and "/: often needs to be escaped too. Returns NULL on
failure and sets ERRNO. If SPECIAL is NULL a dedicated forms
encoding mode is used. */
char *
http_escape_string (const char *string, const char *specials)
{
int n;
char *buf;
n = insert_escapes (NULL, string, specials);
buf = xtrymalloc (n+1);
if (buf)
{
insert_escapes (buf, string, specials);
buf[n] = 0;
}
return buf;
}
/* Allocate a new string from {DATA,DATALEN} using standard HTTP
escaping as well as escaping of characters given in SPECIALS. A
common pattern for SPECIALS is "%;?&=". However it depends on the
needs, for example "+" and "/: often needs to be escaped too.
Returns NULL on failure and sets ERRNO. If SPECIAL is NULL a
dedicated forms encoding mode is used. */
char *
http_escape_data (const void *data, size_t datalen, const char *specials)
{
int n;
char *buf;
n = escape_data (NULL, data, datalen, specials);
buf = xtrymalloc (n+1);
if (buf)
{
escape_data (buf, data, datalen, specials);
buf[n] = 0;
}
return buf;
}
static uri_tuple_t
parse_tuple (char *string)
{
char *p = string;
char *p2;
int n;
uri_tuple_t tuple;
if ((p2 = strchr (p, '=')))
*p2++ = 0;
if ((n = remove_escapes (p)) < 0)
return NULL; /* Bad URI. */
if (n != strlen (p))
return NULL; /* Name with a Nul in it. */
tuple = xtrycalloc (1, sizeof *tuple);
if (!tuple)
return NULL; /* Out of core. */
tuple->name = p;
if (!p2) /* We have only the name, so we assume an empty value string. */
{
tuple->value = p + strlen (p);
tuple->valuelen = 0;
tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
}
else /* Name and value. */
{
if ((n = remove_escapes (p2)) < 0)
{
xfree (tuple);
return NULL; /* Bad URI. */
}
tuple->value = p2;
tuple->valuelen = n;
}
return tuple;
}
/* Return true if STRING is likely "hostname:port" or only "hostname". */
static int
is_hostname_port (const char *string)
{
int colons = 0;
if (!string || !*string)
return 0;
for (; *string; string++)
{
if (*string == ':')
{
if (colons)
return 0;
if (!string[1])
return 0;
colons++;
}
else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
return 0; /* Invalid characters in hostname. */
else if (colons && !digitp (string))
return 0; /* Not a digit in the port. */
}
return 1;
}
/*
* Send a HTTP request to the server
* Returns 0 if the request was successful
*/
static gpg_error_t
send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth,
const char *proxy, const char *srvtag, unsigned int timeout,
strlist_t headers)
{
gpg_error_t err;
const char *server;
char *request, *p;
unsigned short port;
const char *http_proxy = NULL;
char *proxy_authstr = NULL;
char *authstr = NULL;
assuan_fd_t sock;
int have_http_proxy = 0;
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no TLS context available\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
if (opt_debug)
log_debug ("Using TLS library: %s %s\n",
#if HTTP_USE_NTBTLS
"NTBTLS", ntbtls_check_version (NULL)
#elif HTTP_USE_GNUTLS
"GNUTLS", gnutls_check_version (NULL)
#endif /*HTTP_USE_GNUTLS*/
);
if ((hd->flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
server = *hd->uri->host ? hd->uri->host : "localhost";
port = hd->uri->port ? hd->uri->port : 80;
/* Try to use SNI. */
if (hd->uri->use_tls)
{
#if HTTP_USE_GNUTLS
int rc;
#endif
xfree (hd->session->servername);
hd->session->servername = xtrystrdup (httphost? httphost : server);
if (!hd->session->servername)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
return err;
}
#if HTTP_USE_NTBTLS
err = ntbtls_set_hostname (hd->session->tls_session,
hd->session->servername);
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
return err;
}
#elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
GNUTLS_NAME_DNS,
hd->session->servername,
strlen (hd->session->servername));
if (rc < 0)
log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
}
if ( (proxy && *proxy)
|| ( (hd->flags & HTTP_FLAG_TRY_PROXY)
&& (http_proxy = getenv (HTTP_PROXY_ENV))
&& *http_proxy ))
{
parsed_uri_t uri;
if (proxy)
http_proxy = proxy;
err = parse_uri (&uri, http_proxy, 0, 0);
if (gpg_err_code (err) == GPG_ERR_INV_URI
&& is_hostname_port (http_proxy))
{
/* Retry assuming a "hostname:port" string. */
char *tmpname = strconcat ("http://", http_proxy, NULL);
if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
err = 0;
xfree (tmpname);
}
if (err)
;
else if (!strcmp (uri->scheme, "http"))
have_http_proxy = 1;
else if (!strcmp (uri->scheme, "socks4")
|| !strcmp (uri->scheme, "socks5h"))
err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
else
err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
if (err)
{
log_error ("invalid HTTP proxy (%s): %s\n",
http_proxy, gpg_strerror (err));
return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
}
if (uri->auth)
{
remove_escapes (uri->auth);
proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
uri->auth, strlen(uri->auth));
if (!proxy_authstr)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
http_release_parsed_uri (uri);
return err;
}
}
err = connect_server (ctrl,
*uri->host ? uri->host : "localhost",
uri->port ? uri->port : 80,
hd->flags, NULL, timeout, &sock);
http_release_parsed_uri (uri);
}
else
{
err = connect_server (ctrl,
server, port, hd->flags, srvtag, timeout, &sock);
}
if (err)
{
xfree (proxy_authstr);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
if (have_http_proxy && hd->uri->use_tls)
{
int saved_flags;
cookie_t cookie;
/* Try to use the CONNECT method to proxy our TLS stream. */
request = es_bsprintf
("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
httphost ? httphost : server,
port,
httphost ? httphost : server,
port,
proxy_authstr ? proxy_authstr : "");
xfree (proxy_authstr);
proxy_authstr = NULL;
if (! request)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
cookie = xtrycalloc (1, sizeof *cookie);
if (! cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
return err;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (! hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
xfree (request);
hd->write_cookie = NULL;
return err;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
request = NULL;
/* Make sure http_wait_response doesn't close the stream. */
saved_flags = hd->flags;
hd->flags &= ~HTTP_FLAG_SHUTDOWN;
/* Get the response. */
err = http_wait_response (hd);
/* Restore flags, destroy stream. */
hd->flags = saved_flags;
es_fclose (hd->fp_read);
hd->fp_read = NULL;
hd->read_cookie = NULL;
/* Reset state. */
hd->in_data = 0;
if (err)
return err;
if (hd->status_code != 200)
{
request = es_bsprintf
("CONNECT %s:%hu",
httphost ? httphost : server,
port);
log_error (_("error accessing '%s': http status %u\n"),
request ? request : "out of core",
http_get_status_code (hd));
xfree (request);
return gpg_error (GPG_ERR_NO_DATA);
}
/* We are done with the proxy, the code below will establish a
* TLS session and talk directly to the target server. */
http_proxy = NULL;
}
#if HTTP_USE_NTBTLS
if (hd->uri->use_tls)
{
estream_t in, out;
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
* to use es_fopencookie. */
# ifdef HAVE_W32_SYSTEM
in = es_fopencookie (hd->sock->fd, "rb",
simple_cookie_functions);
# else
in = es_fdopen_nc (hd->sock->fd, "rb");
# endif
if (!in)
{
err = gpg_error_from_syserror ();
xfree (proxy_authstr);
return err;
}
# ifdef HAVE_W32_SYSTEM
out = es_fopencookie (hd->sock->fd, "wb",
simple_cookie_functions);
# else
out = es_fdopen_nc (hd->sock->fd, "wb");
# endif
if (!out)
{
err = gpg_error_from_syserror ();
es_fclose (in);
xfree (proxy_authstr);
return err;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
if (err)
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
my_ntbtls_verify_cb, hd);
if (err)
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
#if NTBTLS_VERSION_NUMBER >= 0x000200
unsigned int tlevel, ttype;
const char *s = ntbtls_get_last_alert (hd->session->tls_session,
&tlevel, &ttype);
if (s)
log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype);
#endif
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
hd->session->verify.done = 0;
/* Try the available verify callbacks until one returns success
* or a real error. Note that NTBTLS does the verification
* during the handshake via */
err = 0; /* Fixme check that the CB has been called. */
if (hd->session->verify_cb
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
hd->session->tls_session);
if (tls_callback
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = tls_callback (hd, hd->session, 0);
if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
#elif HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
int rc;
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
my_gnutls_read);
gnutls_transport_set_push_function (hd->session->tls_session,
my_gnutls_write);
handshake_again:
do
{
rc = gnutls_handshake (hd->session->tls_session);
}
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc < 0)
{
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
|| rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
gnutls_alert_description_t alertno;
const char *alertstr;
alertno = gnutls_alert_get (hd->session->tls_session);
alertstr = gnutls_alert_get_name (alertno);
log_info ("TLS handshake %s: %s (alert %d)\n",
rc == GNUTLS_E_WARNING_ALERT_RECEIVED
? "warning" : "failed",
alertstr, (int)alertno);
if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
log_info (" (sent server name '%s')\n", server);
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
goto handshake_again;
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
xfree (proxy_authstr);
return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
}
hd->session->verify.done = 0;
if (tls_callback)
err = tls_callback (hd, hd->session, 0);
else
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_GNUTLS*/
if (auth || hd->uri->auth)
{
char *myauth;
if (auth)
{
myauth = xtrystrdup (auth);
if (!myauth)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
remove_escapes (myauth);
}
else
{
remove_escapes (hd->uri->auth);
myauth = hd->uri->auth;
}
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
if (auth)
xfree (myauth);
if (!authstr)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
}
p = build_rel_path (hd->uri);
if (!p)
{
xfree (authstr);
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
if (http_proxy && *http_proxy)
{
request = es_bsprintf
("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
hd->uri->use_tls? "https" : "http",
httphost? httphost : server,
port, *p == '/' ? "" : "/", p,
authstr ? authstr : "",
proxy_authstr ? proxy_authstr : "");
}
else
{
char portstr[35];
if (port == (hd->uri->use_tls? 443 : 80))
*portstr = 0;
else
snprintf (portstr, sizeof portstr, ":%u", port);
request = es_bsprintf
("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
*p == '/' ? "" : "/", p,
httphost? httphost : server,
portstr,
authstr? authstr:"");
}
xfree (p);
if (!request)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (authstr);
xfree (proxy_authstr);
return err;
}
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
cookie->use_tls = hd->uri->use_tls;
cookie->session = http_session_ref (hd->session);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
hd->write_cookie = NULL;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
if (!err)
{
for (;headers; headers=headers->next)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (headers->d, "http.c:request-header:");
if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
|| (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
break;
}
}
}
}
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
return err;
}
/*
* Build the relative path from the parsed URI. Minimal
* implementation. May return NULL in case of memory failure; errno
* is then set accordingly.
*/
static char *
build_rel_path (parsed_uri_t uri)
{
uri_tuple_t r;
char *rel_path, *p;
int n;
/* Count the needed space. */
n = insert_escapes (NULL, uri->path, "%;?&");
/* TODO: build params. */
for (r = uri->query; r; r = r->next)
{
n++; /* '?'/'&' */
n += insert_escapes (NULL, r->name, "%;?&=");
if (!r->no_value)
{
n++; /* '=' */
n += insert_escapes (NULL, r->value, "%;?&=");
}
}
n++;
/* Now allocate and copy. */
p = rel_path = xtrymalloc (n);
if (!p)
return NULL;
n = insert_escapes (p, uri->path, "%;?&");
p += n;
/* TODO: add params. */
for (r = uri->query; r; r = r->next)
{
*p++ = r == uri->query ? '?' : '&';
n = insert_escapes (p, r->name, "%;?&=");
p += n;
if (!r->no_value)
{
*p++ = '=';
/* TODO: Use valuelen. */
n = insert_escapes (p, r->value, "%;?&=");
p += n;
}
}
*p = 0;
return rel_path;
}
/* Transform a header name into a standard capitalized format; e.g.
"Content-Type". Conversion stops at the colon. As usual we don't
use the localized versions of ctype.h. */
static void
capitalize_header_name (char *name)
{
int first = 1;
for (; *name && *name != ':'; name++)
{
if (*name == '-')
first = 1;
else if (first)
{
if (*name >= 'a' && *name <= 'z')
*name = *name - 'a' + 'A';
first = 0;
}
else if (*name >= 'A' && *name <= 'Z')
*name = *name - 'A' + 'a';
}
}
/* Store an HTTP header line in LINE away. Line continuation is
supported as well as merging of headers with the same name. This
function may modify LINE. */
static gpg_err_code_t
store_header (http_t hd, char *line)
{
size_t n;
char *p, *value;
header_t h;
n = strlen (line);
if (n && line[n-1] == '\n')
{
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
}
if (!n) /* we are never called to hit this. */
return GPG_ERR_BUG;
if (*line == ' ' || *line == '\t')
{
/* Continuation. This won't happen too often as it is not
recommended. We use a straightforward implementation. */
if (!hd->headers)
return GPG_ERR_PROTOCOL_VIOLATION;
n += strlen (hd->headers->value);
p = xtrymalloc (n+1);
if (!p)
return gpg_err_code_from_syserror ();
strcpy (stpcpy (p, hd->headers->value), line);
xfree (hd->headers->value);
hd->headers->value = p;
return 0;
}
capitalize_header_name (line);
p = strchr (line, ':');
if (!p)
return GPG_ERR_PROTOCOL_VIOLATION;
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, line) )
break;
if (h)
{
/* We have already seen a line with that name. Thus we assume
* it is a comma separated list and merge them. */
p = strconcat (h->value, ",", value, NULL);
if (!p)
return gpg_err_code_from_syserror ();
xfree (h->value);
h->value = p;
return 0;
}
/* Append a new header. */
h = xtrymalloc (sizeof *h + strlen (line));
if (!h)
return gpg_err_code_from_syserror ();
strcpy (h->name, line);
h->value = xtrymalloc (strlen (value)+1);
if (!h->value)
{
xfree (h);
return gpg_err_code_from_syserror ();
}
strcpy (h->value, value);
h->next = hd->headers;
hd->headers = h;
return 0;
}
/* Return the header NAME from the last response. The returned value
is valid as along as HD has not been closed and no other request
has been send. If the header was not found, NULL is returned. NAME
must be canonicalized, that is the first letter of each dash
delimited part must be uppercase and all other letters lowercase. */
const char *
http_get_header (http_t hd, const char *name)
{
header_t h;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, name) )
return h->value;
return NULL;
}
/* Return a newly allocated and NULL terminated array with pointers to
header names. The array must be released with xfree() and its
content is only values as long as no other request has been
send. */
const char **
http_get_header_names (http_t hd)
{
const char **array;
size_t n;
header_t h;
for (n=0, h = hd->headers; h; h = h->next)
n++;
array = xtrycalloc (n+1, sizeof *array);
if (array)
{
for (n=0, h = hd->headers; h; h = h->next)
array[n++] = h->name;
}
return array;
}
/*
* Parse the response from a server.
* Returns: Errorcode and sets some files in the handle
*/
static gpg_error_t
parse_response (http_t hd)
{
char *line, *p, *p2;
size_t maxlen, len;
cookie_t cookie = hd->read_cookie;
const char *s;
/* Delete old header lines. */
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
/* Wait for the status line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_error_from_syserror (); /* Out of core. */
if (!maxlen)
return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
if (!len)
return gpg_error (GPG_ERR_EOF);
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (line, "http.c:response:\n");
}
while (!*line);
if ((p = strchr (line, '/')))
*p++ = 0;
if (!p || strcmp (line, "HTTP"))
return 0; /* Assume http 0.9. */
if ((p2 = strpbrk (p, " \t")))
{
*p2++ = 0;
p2 += strspn (p2, " \t");
}
if (!p2)
return 0; /* Also assume http 0.9. */
p = p2;
/* TODO: Add HTTP version number check. */
if ((p2 = strpbrk (p, " \t")))
*p2++ = 0;
if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
|| !isdigit ((unsigned int)p[2]) || p[3])
{
/* Malformed HTTP status code - assume http 0.9. */
hd->is_http_0_9 = 1;
hd->status_code = 200;
return 0;
}
hd->status_code = atoi (p);
/* Skip all the header lines and wait for the empty line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_error_from_syserror (); /* Out of core. */
/* Note, that we can silently ignore truncated lines. */
if (!len)
return gpg_error (GPG_ERR_EOF);
/* Trim line endings of empty lines. */
if ((*line == '\r' && line[1] == '\n') || *line == '\n')
*line = 0;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_info ("http.c:RESP: '%.*s'\n",
(int)strlen(line)-(*line&&line[1]?2:0),line);
if (*line)
{
gpg_err_code_t ec = store_header (hd, line);
if (ec)
return gpg_error (ec);
}
}
while (len && *line);
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
s = http_get_header (hd, "Content-Length");
if (s)
{
cookie->content_length_valid = 1;
cookie->content_length = string_to_u64 (s);
}
}
return 0;
}
#if 0
static int
start_server ()
{
struct sockaddr_in mya;
struct sockaddr_in peer;
int fd, client;
fd_set rfds;
int addrlen;
int i;
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
log_error ("socket() failed: %s\n", strerror (errno));
return -1;
}
i = 1;
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
mya.sin_family = AF_INET;
memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
mya.sin_port = htons (11371);
if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
{
log_error ("bind to port 11371 failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
if (listen (fd, 5))
{
log_error ("listen failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
for (;;)
{
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* ignore any errors */
if (!FD_ISSET (fd, &rfds))
continue;
addrlen = sizeof peer;
client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
if (client == -1)
continue; /* oops */
log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
fflush (stdout);
fflush (stderr);
if (!fork ())
{
int c;
FILE *fp;
fp = fdopen (client, "r");
while ((c = getc (fp)) != EOF)
putchar (c);
fclose (fp);
exit (0);
}
sock_close (client);
}
return 0;
}
#endif
/* Return true if SOCKS shall be used. This is the case if tor_mode
* is enabled and the desired address is not the loopback address.
* This function is basically a copy of the same internal function in
* Libassuan. */
static int
use_socks (struct sockaddr_storage *addr)
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
return 0; /* Not in Tor mode. */
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
const unsigned char *s;
int i;
s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->ss_family == AF_INET)
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
/* Wrapper around assuan_sock_new which takes the domain from an
* address parameter. */
static assuan_fd_t
my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
{
int domain;
if (use_socks (addr))
{
/* Libassaun always uses 127.0.0.1 to connect to the socks
* server (i.e. the Tor daemon). */
domain = AF_INET;
}
else
domain = addr->ss_family;
return assuan_sock_new (domain, type, proto);
}
/* Call WSAGetLastError and map it to a libgpg-error. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
my_wsagetlasterror (void)
{
int wsaerr;
gpg_err_code_t ec;
wsaerr = WSAGetLastError ();
switch (wsaerr)
{
case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break;
case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break;
case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break;
case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break;
case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break;
case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break;
case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break;
case WSAEISCONN: ec = GPG_ERR_EISCONN; break;
case WSAEALREADY: ec = GPG_ERR_EALREADY; break;
case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break;
default: ec = GPG_ERR_EIO; break;
}
return gpg_err_make (default_errsource, ec);
}
#endif /*HAVE_W32_SYSTEM*/
/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
* be established within TIMEOUT milliseconds. 0 indicates the
* system's default timeout. The other args are the usual connect
* args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
* another error code for other errors. On timeout the caller needs
* to close the socket as soon as possible to stop an ongoing
* handshake.
*
* This implementation is for well-behaving systems; see Stevens,
* Network Programming, 2nd edition, Vol 1, 15.4. */
static gpg_error_t
connect_with_timeout (assuan_fd_t sock,
struct sockaddr *addr, int addrlen,
unsigned int timeout)
{
gpg_error_t err;
int syserr;
socklen_t slen;
fd_set rset, wset;
struct timeval tval;
int n;
#ifndef HAVE_W32_SYSTEM
int oflags;
# define RESTORE_BLOCKING() do { \
fcntl (sock, F_SETFL, oflags); \
} while (0)
#else /*HAVE_W32_SYSTEM*/
# define RESTORE_BLOCKING() do { \
unsigned long along = 0; \
ioctlsocket (FD2INT (sock), FIONBIO, &along); \
} while (0)
#endif /*HAVE_W32_SYSTEM*/
if (!timeout)
{
/* Shortcut. */
if (assuan_sock_connect (sock, addr, addrlen))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
return err;
}
/* Switch the socket into non-blocking mode. */
#ifdef HAVE_W32_SYSTEM
{
unsigned long along = 1;
if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
return my_wsagetlasterror ();
}
#else
oflags = fcntl (sock, F_GETFL, 0);
if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
#endif
/* Do the connect. */
if (!assuan_sock_connect (sock, addr, addrlen))
{
/* Immediate connect. Restore flags. */
RESTORE_BLOCKING ();
return 0; /* Success. */
}
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (gpg_err_code (err) != GPG_ERR_EINPROGRESS
#ifdef HAVE_W32_SYSTEM
&& gpg_err_code (err) != GPG_ERR_EAGAIN
#endif
)
{
RESTORE_BLOCKING ();
return err;
}
FD_ZERO (&rset);
FD_SET (FD2INT (sock), &rset);
wset = rset;
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000) * 1000;
- n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval);
+ n = my_select (FD2NUM(sock)+1, &rset, &wset, NULL, &tval);
if (n < 0)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
RESTORE_BLOCKING ();
return err;
}
if (!n)
{
/* Timeout: We do not restore the socket flags on timeout
* because the caller is expected to close the socket. */
return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
}
if (!FD_ISSET (FD2INT (sock), &rset) && !FD_ISSET (FD2INT (sock), &wset))
{
/* select misbehaved. */
return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
}
slen = sizeof (syserr);
if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
(void*)&syserr, &slen) < 0)
{
/* Assume that this is Solaris which returns the error in ERRNO. */
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
else if (syserr)
err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
else
err = 0; /* Connected. */
RESTORE_BLOCKING ();
return err;
#undef RESTORE_BLOCKING
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
* TIMEOUT is the connect timeout in milliseconds. Note that the
* function tries to connect to all known addresses and the timeout is
* for each one. */
static gpg_error_t
connect_server (ctrl_t ctrl, const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout,
assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
unsigned int srvcount = 0;
int hostfound = 0;
int anyhostaddr = 0;
int srv, connected, v4_valid, v6_valid;
gpg_error_t last_err = 0;
struct srventry *serverlist = NULL;
*r_sock = ASSUAN_INVALID_FD;
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
init_sockets ();
#endif /*Windows*/
check_inet_support (&v4_valid, &v6_valid);
/* Onion addresses require special treatment. */
if (is_onion_address (server))
{
#ifdef ASSUAN_SOCK_TOR
if (opt_debug)
log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
server, port);
sock = assuan_sock_connect_byname (server, port, 0, NULL,
ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
(errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
: gpg_err_code_from_syserror ());
log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
return err;
}
notify_netactivity ();
*r_sock = sock;
return 0;
#else /*!ASSUAN_SOCK_TOR*/
err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
return ASSUAN_INVALID_FD;
#endif /*!HASSUAN_SOCK_TOR*/
}
/* Do the SRV thing */
if (srvtag)
{
err = get_dns_srv (ctrl, server, srvtag, NULL, &serverlist, &srvcount);
if (err)
log_info ("getting '%s' SRV for '%s' failed: %s\n",
srvtag, server, gpg_strerror (err));
/* Note that on error SRVCOUNT is zero. */
err = 0;
}
if (!serverlist)
{
/* Either we're not using SRV, or the SRV lookup failed. Make
up a fake SRV record. */
serverlist = xtrycalloc (1, sizeof *serverlist);
if (!serverlist)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
serverlist->port = port;
strncpy (serverlist->target, server, DIMof (struct srventry, target));
serverlist->target[DIMof (struct srventry, target)-1] = '\0';
srvcount = 1;
}
connected = 0;
for (srv=0; srv < srvcount && !connected; srv++)
{
dns_addrinfo_t aibuf, ai;
if (opt_debug)
log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
serverlist[srv].target, port);
err = resolve_dns_name (ctrl,
serverlist[srv].target, port, 0, SOCK_STREAM,
&aibuf, NULL);
if (err)
{
log_info ("resolving '%s' failed: %s\n",
serverlist[srv].target, gpg_strerror (err));
last_err = err;
continue; /* Not found - try next one. */
}
hostfound = 1;
for (ai = aibuf; ai && !connected; ai = ai->next)
{
if (ai->family == AF_INET
&& ((flags & HTTP_FLAG_IGNORE_IPv4) || !v4_valid))
continue;
if (ai->family == AF_INET6
&& ((flags & HTTP_FLAG_IGNORE_IPv6) || !v6_valid))
continue;
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
if (sock == ASSUAN_INVALID_FD)
{
if (errno == EAFNOSUPPORT)
{
if (ai->family == AF_INET)
v4_valid = 0;
if (ai->family == AF_INET6)
v6_valid = 0;
continue;
}
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
log_error ("error creating socket: %s\n", gpg_strerror (err));
free_dns_addrinfo (aibuf);
xfree (serverlist);
return err;
}
anyhostaddr = 1;
err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
ai->addrlen, timeout);
if (err)
{
last_err = err;
}
else
{
connected = 1;
notify_netactivity ();
}
}
free_dns_addrinfo (aibuf);
}
xfree (serverlist);
if (!connected)
{
if (!hostfound)
log_error ("can't connect to '%s': %s\n",
server, "host not found");
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
else
{
#ifdef HAVE_W32_SYSTEM
log_error ("can't connect to '%s': ec=%d\n",
server, (int)WSAGetLastError());
#else
log_error ("can't connect to '%s': %s\n",
server, gpg_strerror (last_err));
#endif
}
err = last_err? last_err : gpg_err_make (default_errsource,
GPG_ERR_UNKNOWN_HOST);
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
return err;
}
*r_sock = sock;
return 0;
}
/* Helper to read from a socket. This handles npth things and
* EINTR. */
static gpgrt_ssize_t
read_server (assuan_fd_t sock, void *buffer, size_t size)
{
int nread;
do
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nread = recv (FD2INT (sock), buffer, size, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nread = npth_read (sock, buffer, size);
# else
nread = read (sock, buffer, size);
# endif
#endif /*!HAVE_W32_SYSTEM*/
}
while (nread == -1 && errno == EINTR);
return nread;
}
static gpg_error_t
write_server (assuan_fd_t sock, const char *data, size_t length)
{
int nleft;
int nwritten;
nleft = length;
while (nleft > 0)
{
#if defined(HAVE_W32_SYSTEM)
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock), data, nleft, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
if ( nwritten == SOCKET_ERROR )
{
log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
return gpg_error (GPG_ERR_NETWORK);
}
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nwritten = npth_write (sock, data, nleft);
# else
nwritten = write (sock, data, nleft);
# endif
if (nwritten == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("network write failed: %s\n", strerror (errno));
return gpg_error_from_syserror ();
}
#endif /*!HAVE_W32_SYSTEM*/
nleft -= nwritten;
data += nwritten;
}
return 0;
}
/* Read handler for estream. */
static gpgrt_ssize_t
cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
if (c->content_length_valid)
{
if (!c->content_length)
return 0; /* EOF */
if (c->content_length < size)
size = c->content_length;
}
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
nread = es_fread (buffer, 1, size, in);
if (opt_debug)
log_debug ("TLS network read: %d/%zu\n", nread, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
nread = gnutls_record_recv (c->session->tls_session, buffer, size);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
goto again;
if (nread == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
if (nread == GNUTLS_E_REHANDSHAKE)
goto again; /* A client is allowed to just ignore this request. */
if (nread == GNUTLS_E_PREMATURE_TERMINATION)
{
/* The server terminated the connection. Close the TLS
session, and indicate EOF using a short read. */
close_tls_session (c->session);
return 0;
}
log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
gpg_err_set_errno (EIO);
return -1;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
nread = read_server (c->sock->fd, buffer, size);
}
if (c->content_length_valid && nread > 0)
{
if (nread < c->content_length)
c->content_length -= nread;
else
c->content_length = 0;
}
return (gpgrt_ssize_t)nread;
}
/* Write handler for estream. */
static gpgrt_ssize_t
cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
cookie_t c = cookie;
int nwritten = 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
if (size == 0)
es_fflush (out);
else
nwritten = es_fwrite (buffer, 1, size, out);
if (opt_debug)
log_debug ("TLS network write: %d/%zu\n", nwritten, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
int nleft = size;
while (nleft > 0)
{
nwritten = gnutls_record_send (c->session->tls_session,
buffer, nleft);
if (nwritten <= 0)
{
if (nwritten == GNUTLS_E_INTERRUPTED)
continue;
if (nwritten == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("TLS network write failed: %s\n",
gnutls_strerror (nwritten));
gpg_err_set_errno (EIO);
return -1;
}
nleft -= nwritten;
buffer += nwritten;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
}
return (gpgrt_ssize_t)nwritten;
}
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t
simple_cookie_read (void *cookie, void *buffer, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
return read_server (sock, buffer, size);
}
static gpgrt_ssize_t
simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
const char *buffer = buffer_arg;
int nwritten;
if (write_server (sock, buffer, size))
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
return (gpgrt_ssize_t)nwritten;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HTTP_USE_GNUTLS
/* Wrapper for gnutls_bye used by my_socket_unref. */
static void
send_gnutls_bye (void *opaque)
{
tls_session_t tls_session = opaque;
int ret;
again:
do
ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
while (ret == GNUTLS_E_INTERRUPTED);
if (ret == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
}
#endif /*HTTP_USE_GNUTLS*/
/* Close handler for estream. */
static int
cookie_close (void *cookie)
{
cookie_t c = cookie;
if (!c)
return 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
/* FIXME!! Possibly call ntbtls_close_notify for close
of write stream. */
my_socket_unref (c->sock, NULL, NULL);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
else
#endif /*HTTP_USE_GNUTLS*/
if (c->sock)
my_socket_unref (c->sock, NULL, NULL);
if (c->session)
http_session_unref (c->session);
xfree (c);
return 0;
}
/* Verify the credentials of the server. Returns 0 on success and
store the result in the session object. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
#if HTTP_USE_GNUTLS
static const char errprefix[] = "TLS verification of peer failed";
int rc;
unsigned int status;
const char *hostname;
const gnutls_datum_t *certlist;
unsigned int certlistlen;
gnutls_x509_crt_t cert;
gpg_error_t err = 0;
sess->verify.done = 1;
sess->verify.status = 0;
sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
{
log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
return gpg_error (GPG_ERR_GENERAL);
}
rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
if (rc)
{
log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
else if (status)
{
log_error ("%s: status=0x%04x\n", errprefix, status);
#if GNUTLS_VERSION_NUMBER >= 0x030104
{
gnutls_datum_t statusdat;
if (!gnutls_certificate_verification_status_print
(status, GNUTLS_CRT_X509, &statusdat, 0))
{
log_info ("%s: %s\n", errprefix, statusdat.data);
gnutls_free (statusdat.data);
}
}
#endif /*gnutls >= 3.1.4*/
sess->verify.status = status;
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
hostname = sess->servername;
if (!hostname || !strchr (hostname, '.'))
{
log_error ("%s: %s\n", errprefix, "hostname missing");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
if (!certlistlen)
{
log_error ("%s: %s\n", errprefix, "server did not send a certificate");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
/* Need to stop here. */
if (err)
return err;
}
rc = gnutls_x509_crt_init (&cert);
if (rc < 0)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
if (err)
return err;
}
rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
if (rc < 0)
{
log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
if (!gnutls_x509_crt_check_hostname (cert, hostname))
{
log_error ("%s: %s\n", errprefix, "hostname does not match");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
gnutls_x509_crt_deinit (cert);
if (!err)
sess->verify.rc = 0;
if (sess->cert_log_cb)
{
const void *bufarr[10];
size_t buflenarr[10];
size_t n;
for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
{
bufarr[n] = certlist[n].data;
buflenarr[n] = certlist[n].size;
}
bufarr[n] = NULL;
buflenarr[n] = 0;
sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
}
return err;
#else /*!HTTP_USE_GNUTLS*/
(void)sess;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Return the first query variable with the specified key. If there
is no such variable, return NULL. */
struct uri_tuple_s *
uri_query_lookup (parsed_uri_t uri, const char *key)
{
struct uri_tuple_s *t;
for (t = uri->query; t; t = t->next)
if (strcmp (t->name, key) == 0)
return t;
return NULL;
}
const char *
uri_query_value (parsed_uri_t url, const char *key)
{
struct uri_tuple_s *t;
t = uri_query_lookup (url, key);
return t? t->value : NULL;
}
/* Return true if both URI point to the same host for the purpose of
* redirection check. A is the original host and B the host given in
* the Location header. As a temporary workaround a fixed list of
* exceptions is also consulted. */
static int
same_host_p (parsed_uri_t a, parsed_uri_t b)
{
static struct
{
const char *from; /* NULL uses the last entry from the table. */
const char *to;
} allow[] =
{
{ "protonmail.com", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "protonmail.ch", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "pm.me", "api.protonmail.ch" }
};
static const char *subdomains[] =
{
"openpgpkey."
};
int i;
const char *from;
if (!a->host || !b->host)
return 0;
if (!ascii_strcasecmp (a->host, b->host))
return 1;
from = NULL;
for (i=0; i < DIM (allow); i++)
{
if (allow[i].from)
from = allow[i].from;
if (!from)
continue;
if (!ascii_strcasecmp (from, a->host)
&& !ascii_strcasecmp (allow[i].to, b->host))
return 1;
}
/* Also consider hosts the same if they differ only in a subdomain;
* in both direction. This allows to have redirection between the
* WKD advanced and direct lookup methods. */
for (i=0; i < DIM (subdomains); i++)
{
const char *subdom = subdomains[i];
size_t subdomlen = strlen (subdom);
if (!ascii_strncasecmp (a->host, subdom, subdomlen)
&& !ascii_strcasecmp (a->host + subdomlen, b->host))
return 1;
if (!ascii_strncasecmp (b->host, subdom, subdomlen)
&& !ascii_strcasecmp (b->host + subdomlen, a->host))
return 1;
}
return 0;
}
/* Prepare a new URL for a HTTP redirect. INFO has flags controlling
* the operation, STATUS_CODE is used for diagnostics, LOCATION is the
* value of the "Location" header, and R_URL reveives the new URL on
* success or NULL or error. Note that INFO->ORIG_URL is
* required. */
gpg_error_t
http_prepare_redirect (http_redir_info_t *info, unsigned int status_code,
const char *location, char **r_url)
{
gpg_error_t err;
parsed_uri_t locuri;
parsed_uri_t origuri;
char *newurl;
char *p;
*r_url = NULL;
if (!info || !info->orig_url)
return gpg_error (GPG_ERR_INV_ARG);
if (!info->silent)
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
info->orig_url, location? location:"[none]", status_code);
if (!info->redirects_left)
{
if (!info->silent)
log_error (_("too many redirections\n"));
return gpg_error (GPG_ERR_NO_DATA);
}
info->redirects_left--;
if (!location || !*location)
return gpg_error (GPG_ERR_NO_DATA);
err = http_parse_uri (&locuri, location, 0);
if (err)
return err;
/* Make sure that an onion address only redirects to another
* onion address, or that a https address only redirects to a
* https address. */
if (info->orig_onion && !locuri->onion)
{
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect from onion to non-onion address"
" rejected",
err);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_FORBIDDEN);
}
if (!info->allow_downgrade && info->orig_https && !locuri->use_tls)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect '%s' to '%s' rejected",
err, info->orig_url, location);
http_release_parsed_uri (locuri);
return err;
}
if (info->trust_location)
{
/* We trust the Location - return it verbatim. */
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
return err;
}
}
else if ((err = http_parse_uri (&origuri, info->orig_url, 0)))
{
http_release_parsed_uri (locuri);
return err;
}
else if (!info->restrict_redir || same_host_p (origuri, locuri))
{
/* Take the syntactically correct location or if restrict_redir
* is set the host is the same or on an exception list and thus
* we can take the location verbatim. */
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
return err;
}
}
else /* Strictly rectricted redirection which we used in the past. */
{
/* We take only the host and port from the URL given in the
* Location. This limits the effects of redirection attacks by
* rogue hosts returning an URL to servers in the client's own
* network. We don't even include the userinfo because they
* should be considered similar to the path and query parts.
*/
if (!(locuri->off_path - locuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
if (!(origuri->off_path - origuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
newurl = xtrymalloc (strlen (origuri->original)
+ (locuri->off_path - locuri->off_host) + 1);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return err;
}
/* Build new URL from
* uriguri: scheme userinfo ---- ---- path rest
* locuri: ------ -------- host port ---- ----
*/
p = newurl;
memcpy (p, origuri->original, origuri->off_host);
p += origuri->off_host;
memcpy (p, locuri->original + locuri->off_host,
(locuri->off_path - locuri->off_host));
p += locuri->off_path - locuri->off_host;
strcpy (p, origuri->original + origuri->off_path);
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
if (!info->silent)
log_info (_("redirection changed to '%s'\n"), newurl);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect_cleanup %u"
" changed from '%s' to '%s'",
0, info->orig_url, newurl);
}
*r_url = newurl;
return 0;
}
/* Return string describing the http STATUS. Returns an empty string
* for an unknown status. */
const char *
http_status2string (unsigned int status)
{
switch (status)
{
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP version Not Supported";
case 506: return "Variant Also Negation";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
}
return "";
}
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
index 8fbc36195..bfb0c653d 100644
--- a/kbx/keyboxd.c
+++ b/kbx/keyboxd.c
@@ -1,1843 +1,1843 @@
/* keyboxd.c - The GnuPG Keybox Daemon
* Copyright (C) 2000-2020 Free Software Foundation, Inc.
* Copyright (C) 2000-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* 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 .
* SPDX-License-Identifier: GPL-3.0+
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include
#else /*!HAVE_W32_SYSTEM*/
# include
# include
#endif /*!HAVE_W32_SYSTEM*/
#include
#ifdef HAVE_SIGNAL_H
# include
#endif
#include
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "keyboxd.h"
#include /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/comopt.h"
#include "frontend.h"
/* Urrgs: Put this into a separate header - but it needs assuan.h first. */
extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg);
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oStealSocket,
oLogFile,
oServer,
oDaemon,
oFakedSystemTime,
oListenBacklog,
oDisableCheckOwnSocket,
oDummy
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms.
* CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flag to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* Name of the communication socket used for client requests. */
static char *socket_name;
/* We need to keep track of the server's nonces (these are dummies for
* POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Keep track of the current log file so that we can avoid updating
* the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how keyboxd was started, the homedir inotify watch may
* not be reliable. This flag is set if we assume that inotify works
* reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
* Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int cygwin,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void kbxd_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void kbxd_init_default_ctrl (ctrl_t ctrl);
static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd);
static void check_own_socket (void);
static int check_for_running_kbxd (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
* Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
* This function is expected to be called only once. GETFNC is
* expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
return xstrconcat (libname, " ", getfnc (NULL), NULL);
}
/* Return strings describing this program. The case values are
* described in Libgpg-error. The values here override the default
* values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "keyboxd (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: keyboxd [options] (-h for help)");
break;
case 41: p = _("Syntax: keyboxd [options] [command [args]]\n"
"Public key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. Note that we don't fail here, because it is
* important to keep keyboxd running even after re-reading the options
* due to a SIGHUP. */
static void
set_debug (void)
{
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. */
static void
remove_socket (char *name)
{
if (name && *name)
{
gnupg_remove (name);
*name = 0;
}
}
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
if (!inhibit_socket_removal)
remove_socket (socket_name);
}
/* Handle options which are allowed to be reset after program start.
* Return true when the current option in PARGS could be handled and
* false if not. As a special feature, passing a value of NULL for
* PARGS, resets the options to the default. REREAD should be set
* true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
disable_check_own_socket = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
}
/* The main entry point. */
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
char *configname = NULL;
int debug_argparser = 0;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
char *logfile = NULL;
int gpgconf_list = 0;
int debug_wait = 0;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
* file descriptors and the signal mask. This info is required to
* do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
gpgrt_set_strusage (my_strusage);
log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor);
setup_libgcrypt_logging ();
gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL);
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, "keyboxd" EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oStealSocket: steal_socket = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL);
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit (2);
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* keyboxd usually does not output any messages because it runs in
* the background. For log files it is acceptable to have messages
* always encoded in utf-8. We switch here to utf-8, so that
* commands like --help still give native messages. It is far
* easier to switch only once instead of for every message and it
* actually helps when more then one thread is active (avoids an
* extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list)
{
/* We have been called without any command and thus we merely
* check whether an instance of us is already running. We do
* this right here so that we don't clobber a logfile with this
* check but print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_kbxd (0);
kbxd_exit (0);
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 2)
kbxd_exit (0);
else if (gpgconf_list)
{
kbxd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular daemon mode. */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Create the sockets. */
socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 0, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
* we restore it. That is not strictly necessary but some
* programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
exit (0);
/*NOTREACHED*/
} /* End parent */
/*
* This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach)
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Because we don't support running a program on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
{
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13));
handle_connections (fd);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
kbxd_exit (int rc)
{
/* As usual we run our cleanup handler. */
cleanup ();
/* at this time a bit annoying */
if ((opt.debug & DBG_MEMSTAT_VALUE))
gcry_control (GCRYCTL_DUMP_MEMORY_STATS );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
* set once at startup and dispatches progress messages to the
* corresponding threads of ours. */
static void
kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
* structure usually identified by an argument named CTRL. This
* function is called immediately after allocating the control
* structure. Its purpose is to setup the default values for that
* structure. Note that some values may have already been set. */
static void
kbxd_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to kbxd_init_default_ctrl. */
static void
kbxd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
kbxd_release_session_info (ctrl);
ctrl->magic = 0xdeadbeef;
unregister_progress_cb ();
xfree (ctrl->lc_messages);
}
/* Reread parts of the configuration. Note, that this function is
* obviously not thread-safe and should only be called from the PTH
* signal handler.
*
* Fixme: Due to the way the argument parsing works, we create a
* memory leak here for all string type arguments. There is currently
* no clean way to tell whether the memory for the argument has been
* allocated or points into the process's original arguments. Unless
* we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!config_filename)
goto finish; /* No config file. */
twopart = strconcat ("keyboxd" EXTSEP_S "conf" PATHSEP_S,
config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
finalize_rereadable_options ();
set_debug ();
finish:
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* Return the file name of the socket we are using for requests. */
const char *
get_kbxd_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_kbxd_active_connection_count (void)
{
return active_connections;
}
/* Create a name for the socket in the home directory as using
* STANDARD_NAME. We also check for valid characters as well as
* against a maximum allowed length for a Unix domain socket is done.
* The function terminates the process in case of an error. The
* function returns a pointer to an allocated string with the absolute
* name of the socket used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
kbxd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
* or terminates the process in case of an error. If CYGWIN is set a
* Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
kbxd_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
if (assuan_sock_set_sockaddr_un (name, addr, NULL))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a keyboxd is already running. */
if (!check_for_running_kbxd (1))
{
if (steal_socket)
log_info (N_("trying to steal socket from running %s\n"),
"keyboxd");
else
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a keyboxd is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the public keys exists and
* create it if not. This function won't fail as it is only a
* convenience function and not strictly necessary. */
static void
create_public_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL);
if (gnupg_stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwxr-x"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwxr-x"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
* same as the default one. This way we avoid to create arbitrary
* directories when a non-default home directory is used. To cope
* with HOME, we compare only the suffix if we see that the default
* homedir does start with a tilde. We don't stop here in case of
* problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (gnupg_stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_public_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
* and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
* other places too. This is only used when build for W32. */
void
kbxd_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
reread_configuration ();
}
/* A helper function to handle SIGUSR2. */
static void
kbxd_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - no action\n");
/* Nothing to do right now. */
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
* its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
kbxd_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
break;
case SIGUSR2:
kbxd_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13) );
cleanup ();
kbxd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
kbxd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
static void *
do_start_connection_thread (ctrl_t ctrl)
{
static unsigned int last_session_id;
unsigned int session_id;
active_connections++;
kbxd_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* Connection handler loop. Wait for connection requests and spawn a
* thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread },
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n", strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
events[0] = INVALID_HANDLE_VALUE;
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
- nfd = FD2INT (listen_fd);
+ nfd = FD2NUM (listen_fd);
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new keyboxd instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if ((events_set & 1))
kbxd_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
* next timeout. */
continue;
}
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
* separate thread so that check_own_thread can be called from the
* timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
* server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
* another keyboxd process started after us has taken ownership of our
* socket, we would linger around without any real task. Thus we
* better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
{
xfree (sockname);
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether a keyboxd is available and running. Prints an
* error if not. If SILENT is true, no messages are printed. Returns
* 0 if the agent is running. */
static int
check_for_running_kbxd (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no keyboxd running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("keyboxd running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 6422ebec9..653fc78cc 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -1,1454 +1,1454 @@
/* scdaemon.c - The GnuPG Smartcard Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2020 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2019 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* 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 .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
#include
#include
#endif /*HAVE_W32_SYSTEM*/
#include
#include
#include
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "scdaemon.h"
#include
#include
#include /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "iso7816.h"
#include "apdu.h"
#include "ccid-driver.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/comopt.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugCCIDDriver,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oBatch,
oReaderPort,
oCardTimeout,
octapiDriver,
opcscDriver,
opcscShared,
oDisableCCID,
oDisableOpenSC,
oDisablePinpad,
oAllowAdmin,
oDenyAdmin,
oDisableApplication,
oApplicationPriority,
oEnablePinpadVarlen,
oListenBacklog
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_s (oReaderPort, "reader-port",
N_("|N|connect to reader at port N")),
ARGPARSE_s_s (octapiDriver, "ctapi-driver",
N_("|NAME|use NAME as ct-API driver")),
ARGPARSE_s_s (opcscDriver, "pcsc-driver",
N_("|NAME|use NAME as PC/SC driver")),
ARGPARSE_s_n (opcscShared, "pcsc-shared", "@"),
ARGPARSE_s_n (oDisableCCID, "disable-ccid",
#ifdef HAVE_LIBUSB
N_("do not use the internal CCID driver")
#else
"@"
#endif
/* end --disable-ccid */),
ARGPARSE_s_u (oCardTimeout, "card-timeout",
N_("|N|disconnect the card after N seconds of inactivity")),
ARGPARSE_s_n (oDisablePinpad, "disable-pinpad",
N_("do not use a reader's pinpad")),
ARGPARSE_ignore (300, "disable-keypad"),
ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen",
N_("use variable length input for pinpad")),
ARGPARSE_s_s (oDisableApplication, "disable-application", "@"),
ARGPARSE_s_s (oApplicationPriority, "application-priority",
N_("|LIST|change the application priority to LIST")),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_header("Security", N_("Options controlling the security")),
ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"),
ARGPARSE_s_n (oDenyAdmin, "deny-admin",
N_("deny the use of admin card commands")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CARD_VALUE , "card" },
{ DBG_CARD_IO_VALUE, "cardio" },
{ DBG_READER_VALUE , "reader" },
{ DBG_APP_VALUE , "app" },
{ 0, NULL }
};
/* The card driver we use by default for PC/SC. */
#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__)
#define DEFAULT_PCSC_DRIVER "winscard.dll"
#elif defined(__APPLE__)
#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC"
#elif defined(__GLIBC__)
#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1"
#else
#define DEFAULT_PCSC_DRIVER "libpcsclite.so"
#endif
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (gnupg_fd_t listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@SCDAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 1:
case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
scd_exit(2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
static void
setup_signal_mask (void)
{
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int debug_argparser = 0;
const char *debug_level = NULL;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
const char *application_priority = NULL;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.allow_admin = 1;
opt.pcsc_driver = DEFAULT_PCSC_DRIVER;
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
* flags so that the internal intialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, SCDAEMON_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugCCIDDriver:
#ifdef HAVE_LIBUSB
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
#endif /*HAVE_LIBUSB*/
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
case opcscShared: opt.pcsc_shared = 1; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oDisableOpenSC: break;
case oDisablePinpad: opt.disable_pinpad = 1; break;
case oAllowAdmin: /* Dummy because allow is now the default. */
break;
case oDenyAdmin: opt.allow_admin = 0; break;
case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break;
case oDisableApplication:
add_to_strlist (&opt.disabled_applications, pargs.r.ret_str);
break;
case oApplicationPriority:
application_priority = pargs.r.ret_str;
break;
case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
SCDAEMON_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
/* Process common component options. */
if (parse_comopt (GNUPG_MODULE_NAME_SCDAEMON, debug_argparser))
exit(2);
if (!logfile)
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage (11),gpgrt_strusage (13),gpgrt_strusage (14));
es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15));
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (initialize_module_command ())
{
log_error ("initialization failed\n");
cleanup ();
exit (1);
}
if (gpgconf_list == 2)
scd_exit (0);
if (gpgconf_list)
{
/* List options and default values in the GPG Conf format. */
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("pcsc-driver:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER );
es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0);
scd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (application_priority)
app_update_priority_list (application_priority);
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
gnupg_fd_t fd = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
scd_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
scd_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
scd_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != GNUPG_INVALID_FD)
assuan_sock_close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: :: */
if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
socket_name, (ulong) 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 */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
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);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export SCDAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
assuan_sock_close (fd);
}
xfree (config_filename);
return 0;
}
void
scd_exit (int rc)
{
apdu_prepare_exit ();
#if 0
#warning no update_random_seed_file
update_random_seed_file();
#endif
#if 0
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
static void
scd_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
scd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
scd_get_socket_name (void)
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration and resetting cards\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
app_dump_state ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
scd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
scd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Returns: Pointer to an allocated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
scd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
scd_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
scd_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
scd_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD_DBG (ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (scd_command_handler (ctrl, ctrl->thread_startup.fd)
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD_DBG (ctrl->thread_startup.fd));
scd_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
scd_kick_the_loop ();
return NULL;
}
void
scd_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("sending signal for scd_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init(&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
/* Create event for manual reset, initially non-signaled. Make it
* waitable and inheritable. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != GNUPG_INVALID_FD)
{
FD_SET (FD2INT (listen_fd), &fdset);
- nfd = FD2INT (listen_fd);
+ nfd = FD2NUM (listen_fd);
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = GNUPG_INVALID_FD;
}
periodical_check = scd_update_reader_status_file ();
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#else
(void)max_fd;
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != GNUPG_INVALID_FD
&& FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
ctrl_t ctrl;
gnupg_fd_t fd;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT (listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
assuan_sock_close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d",
FD_DBG (fd));
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
assuan_sock_close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/tpm2d/tpm2daemon.c b/tpm2d/tpm2daemon.c
index 3246cfc9a..383421c30 100644
--- a/tpm2d/tpm2daemon.c
+++ b/tpm2d/tpm2daemon.c
@@ -1,1292 +1,1292 @@
/* tpm2daemon.c - The GnuPG tpm2 Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* 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 .
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef HAVE_W32_SYSTEM
#include
#include
#endif /*HAVE_W32_SYSTEM*/
#include
#include
#include
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "tpm2daemon.h"
#include
#include /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oListenBacklog,
oParent
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_p_u (oParent, "tpm2-parent",
N_("Specify tpm2 parent for key")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (gnupg_fd_t listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@TPM2DAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @TPM2DAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: tpm2daemon [options] [command [args]]\n"
"TPM2 daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "guru") || numok)
opt.debug = ~0;
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
tpm2d_exit (2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int parse_debug = 0;
const char *debug_level = NULL;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("tpm2daemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.parent = 0; /* 0 means TPM uses default */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
parse_debug++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
* flags so that the internal intialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, TPM2DAEMON_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (parse_debug)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oParent:
opt.parent = pargs.r.ret_ulong;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
TPM2DAEMON_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount (0))
exit (2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage (11), gpgrt_strusage (13),
gpgrt_strusage (14) );
es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (gpgconf_list == 2)
tpm2d_exit (0);
if (gpgconf_list)
{
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
tpm2d_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid ());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
gnupg_fd_t fd = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir ("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name,
&socket_nonce);
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
tpm2d_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
tpm2d_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
tpm2d_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != GNUPG_INVALID_FD)
assuan_sock_close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: :: */
if (gpgrt_asprintf (&infostr, "TPM2DAEMON_INFO=%s:%lu:1",
socket_name, (ulong) 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 */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
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);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export TPM2DAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid () == -1)
{
log_error ("setsid() failed: %s\n", strerror (errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
assuan_sock_close (fd);
}
xfree (config_filename);
return 0;
}
void
tpm2d_exit (int rc)
{
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount (0)? 2 : 0;
exit (rc);
}
static void
tpm2d_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
tpm2d_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
tpm2d_get_socket_name (void)
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
#if 0
app_dump_state ();
#endif
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage (11),
gpgrt_strusage (13) );
cleanup ();
tpm2d_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info ( "%s %s stopped\n", gpgrt_strusage (11), gpgrt_strusage (13));
cleanup ();
tpm2d_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Retunrs: Pointer to an allcoated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
tpm2d_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
tpm2d_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
tpm2d_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
tpm2d_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD_DBG (ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (tpm2d_command_handler (ctrl, ctrl->thread_startup.fd)
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD_DBG (ctrl->thread_startup.fd));
tpm2d_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
tpm2d_kick_the_loop ();
return NULL;
}
void
tpm2d_kick_the_loop (void)
{
#ifdef HAVE_W32_SYSTEM
int ret;
/* Kick the select loop. */
ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret;
ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init (&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create tpm2d event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess (), h,
GetCurrentProcess (), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#else
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != GNUPG_INVALID_FD)
{
FD_SET (FD2INT (listen_fd), &fdset);
- nfd = FD2INT (listen_fd);
+ nfd = FD2NUM (listen_fd);
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = GNUPG_INVALID_FD;
}
periodical_check = 0;
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != GNUPG_INVALID_FD
&& FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
ctrl_t ctrl;
gnupg_fd_t fd;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT (listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
assuan_sock_close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", FD_DBG (fd));
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
assuan_sock_close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage (11), gpgrt_strusage (13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}