diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 6524cb1b6..1ff405951 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1,1580 +1,1580 @@ /* 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 #include #ifndef HAVE_W32_SYSTEM # include # include # include # include #endif #include #include "agent.h" #include #include "../common/sysutils.h" #include "../common/i18n.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) /* 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; /* The control variable of the connection owning the current pinentry. This is only valid if ENTRY_CTX is not NULL. Note, that we care only about the value of the pointer and that it should never be dereferenced. */ static ctrl_t entry_owner; /* 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; }; /* 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; if (!initialized) { if (npth_mutex_init (&entry_lock, NULL)) initialized = 1; } } -/* This function may be called to print infromation pertaining to the +/* 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=%p\n", entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)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 && entry_owner == ctrl) { 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 (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; default: rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc)); break; } } 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; } /* 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) { int iterator = 0; const char *name, *assname, *value; gcry_control (GCRYCTL_TERM_SECMEM); 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. */ if (!assname || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) gnupg_setenv (name, value, 1); } } } } /* 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; 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; } entry_owner = ctrl; 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 implementaion does not flush stdin,stdout and stderr + 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 (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) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); 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; } /* 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 (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)); assuan_release (ctx); return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY)); } entry_ctx = ctx; if (DBG_IPC) log_debug ("connection to PIN entry established\n"); 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 (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 (rc); } rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (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 (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (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 (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 (rc); } { /* Provide a few default strings for use by the pinentries. This may help a pinentry to avoid implementing localization code. */ static struct { const char *key, *value; int what; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc used in Pinentries. An underscore indicates that the next letter should be used as an accelerator. Double the underscore for a literal one. The actual to be translated text starts after the second vertical bar. Note that gpg-agent has been set to utf-8 so that the strings are in the expected encoding. */ { "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") }, { 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 (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 %s", ctrl->client_pid, 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. Noet 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 && (pid_t)pinentry_pid == (pid_t)(-1)) 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 (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; } /* Handle the QUALITY inquiry. */ static gpg_error_t inq_quality (void *opaque, const char *line) { assuan_context_t ctx = opaque; const char *s; char *pin; int rc; int percent; char numbuf[20]; if ((s = has_leading_keyword (line, "QUALITY"))) { pin = unescape_passphrase_string (s); if (!pin) rc = gpg_error_from_syserror (); else { percent = estimate_passphrase_quality (pin); if (check_passphrase_constraints (NULL, pin, NULL)) percent = -percent; snprintf (numbuf, sizeof numbuf, "%d", percent); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); xfree (pin); } } else { log_error ("unsupported inquiry '%s' from pinentry\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } /* 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; } enum { PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, PINENTRY_STATUS_PIN_REPEATED = 1 << 8, PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 }; /* 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 funcion so that +/* 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; } } /* 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 saveflag; unsigned int pinentry_status; 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 (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 (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 (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 (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 (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. */ } pininfo->repeat_okay = 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; 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 (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 (rc); } saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, pinentry_status_cb, &pinentry_status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* 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 ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); 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 (rc); if (!errtext && pininfo->min_digits) { /* 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) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE && 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 (rc); } if (!errtext) { if (pininfo->with_repeat && (pinentry_status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (0); /* okay, got a PIN or passphrase */ } if ((pinentry_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 (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. */ 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) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; int saveflag; unsigned int pinentry_status; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); 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) { size_t size; size_t len = ASSUAN_LINELENGTH/2; return pinentry_loopback (ctrl, "PASSPHRASE", (unsigned char **)retpass, &size, len); } return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (!prompt) prompt = desc && strstr (desc, "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 (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 (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 (rc); if (with_qualitybar && opt.min_passphrase_len) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (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 (rc); } 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 (out_of_core ()); saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, pinentry_status_cb, &pinentry_status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* 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 ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (rc) xfree (parm.buffer); else *retpass = parm.buffer; return unlock_pinentry (rc); } /* 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); 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 (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 (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 (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); return unlock_pinentry (rc); } /* Pop up the PINentry, display the text DESC and a button with the text OK_BTN (which may be NULL to use the default of "OK") and wait for the user to hit this button. The return value is not relevant. */ int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) return gpg_error (GPG_ERR_CANCELED); 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 (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 (rc); } rc = assuan_transact (entry_ctx, "CONFIRM --one-button", 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); return unlock_pinentry (rc); } /* The thread running the popup message. */ static void * popup_message_thread (void *arg) { (void)arg; /* 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); 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) return gpg_error (GPG_ERR_CANCELED); 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 (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 (rc); } err = npth_attr_init (&tattr); if (err) return unlock_pinentry (gpg_error_from_errno (err)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); popup_finished = 0; err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL); 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 (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 (!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 && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { /* The daemon already died. No need to send a kill. However because we already waited for the process, we need to tell assuan that it should not wait again (done by unlock_pinentry). */ if (rc == pid) assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); } 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)); entry_owner = NULL; /* Now we can close the connection. */ unlock_pinentry (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 (rc); } diff --git a/agent/call-scd.c b/agent/call-scd.c index 09ec4fd49..cf61a3546 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1,1367 +1,1367 @@ /* call-scd.c - fork of the scdaemon to do SC operations * Copyright (C) 2001, 2002, 2005, 2007, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 2013 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 . */ #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #ifndef HAVE_W32_SYSTEM #include #endif #include #include "agent.h" #include #include "../common/strlist.h" #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Definition of module local data of the CTRL structure. */ struct scd_local_s { /* We keep a list of all allocated context with an anchor at SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; /* We need to get back to the ctrl object actually referencing this structure. This is really an awkward way of enumerating the local contexts. A much cleaner way would be to keep a global list of ctrl objects to enumerate them. */ ctrl_t ctrl_backlink; assuan_context_t ctx; /* NULL or session context for the SCdaemon used with this connection. */ int locked; /* This flag is used to assert proper use of start_scd and unlock_scd. */ }; /* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); void *certinfo_cb_arg; void (*sinfo_cb)(void*, const char *, size_t, const char *); void *sinfo_cb_arg; }; /* Callback parameter used by inq_getpin and inq_writekey_parms. */ struct inq_needpin_parm_s { assuan_context_t ctx; int (*getpin_cb)(void *, const char *, const char *, char*, size_t); void *getpin_cb_arg; const char *getpin_cb_desc; assuan_context_t passthru; /* If not NULL, pass unknown inquiries up to the caller. */ int any_inq_seen; /* The next fields are used by inq_writekey_parm. */ const unsigned char *keydata; size_t keydatalen; }; /* To keep track of all active SCD contexts, we keep a linked list anchored at this variable. */ static struct scd_local_s *scd_local_list; /* A Mutex used inside the start_scd function. */ static npth_mutex_t start_scd_lock; /* A malloced string with the name of the socket to be used for additional connections. May be NULL if not provided by SCdaemon. */ static char *socket_name; /* The context of the primary connection. This is also used as a flag to indicate whether the scdaemon has been started. */ static assuan_context_t primary_scd_ctx; /* To allow reuse of the primary connection, the following flag is set to true if the primary context has been reset and is not in use by any connection. */ static int primary_scd_ctx_reusable; /* Local prototypes. */ /* 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 NPth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_scd (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&start_scd_lock, NULL); if (err) log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } -/* This function may be called to print infromation pertaining to the +/* This function may be called to print information pertaining to the current state of this module to the log. */ void agent_scd_dump_state (void) { log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", primary_scd_ctx, (long)assuan_get_pid (primary_scd_ctx), primary_scd_ctx_reusable); if (socket_name) log_info ("agent_scd_dump_state: socket='%s'\n", socket_name); } /* The unlock_scd function shall be called after having accessed the SCD. It is currently not very useful but gives an opportunity to keep track of connections currently calling SCD. Note that the "lock" operation is done by the start_scd() function which must be called and error checked before any SCD operation. CTRL is the usual connection context and RC the error code to be passed trhough the function. */ static int unlock_scd (ctrl_t ctrl, int rc) { if (ctrl->scd_local->locked != 1) { log_error ("unlock_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked = 0; return rc; } /* To make sure we leave no secrets in our image after forking of the scdaemon, we use this callback. */ static void atfork_cb (void *opaque, int where) { (void)opaque; if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. This function might also lock the daemon, which means that the caller must call unlock_scd after this function has returned success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { gpg_error_t err = 0; const char *pgmname; assuan_context_t ctx = NULL; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; int rc; char *abs_homedir = NULL; if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* If this is the first call for this session, setup the local data structure. */ if (!ctrl->scd_local) { ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); if (!ctrl->scd_local) return gpg_error_from_syserror (); ctrl->scd_local->ctrl_backlink = ctrl; ctrl->scd_local->next_local = scd_local_list; scd_local_list = ctrl->scd_local; } /* Assert that the lock count is as expected. */ if (ctrl->scd_local->locked) { log_error ("start_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); return gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked++; if (ctrl->scd_local->ctx) return 0; /* Okay, the context is fine. We used to test for an alive context here and do an disconnect. Now that we have a ticker function to check for it, it is easier not to check here but to let the connection run on an error instead. */ /* We need to protect the following code. */ rc = npth_mutex_lock (&start_scd_lock); if (rc) { log_error ("failed to acquire the start_scd lock: %s\n", strerror (rc)); return gpg_error (GPG_ERR_INTERNAL); } /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ if (primary_scd_ctx && primary_scd_ctx_reusable) { ctx = primary_scd_ctx; primary_scd_ctx_reusable = 0; if (opt.verbose) log_info ("new connection to SCdaemon established (reusing)\n"); goto leave; } rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); err = rc; goto leave; } if (socket_name) { rc = assuan_socket_connect (ctx, socket_name, 0, 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", socket_name, gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("new connection to SCdaemon established\n"); goto leave; } if (primary_scd_ctx) { log_info ("SCdaemon is running but won't accept further connections\n"); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM err = gpg_error_from_syserror (); #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 Wime implementaion does not flush stdin,stdout and stderr + the Wime implementation does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM goto leave; #endif } if (!opt.scdaemon_program || !*opt.scdaemon_program) opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) pgmname = opt.scdaemon_program; else pgmname++; argv[0] = pgmname; argv[1] = "--multi-server"; if (gnupg_default_homedir_p ()) argv[2] = NULL; else { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) { log_error ("error building filename: %s\n", gpg_strerror (gpg_error_from_syserror ())); goto leave; } argv[2] = "--homedir"; argv[3] = abs_homedir; argv[4] = NULL; } i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to the scdaemon and perform initial handshaking. Use detached flag so that under Windows SCDAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_debug ("first connection to SCdaemon established\n"); /* Get the name of the additional socket opened by scdaemon. */ { membuf_t data; unsigned char *databuf; size_t datalen; xfree (socket_name); socket_name = NULL; init_membuf (&data, 256); assuan_transact (ctx, "GETINFO socket_name", put_membuf_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (databuf && datalen) { socket_name = xtrymalloc (datalen + 1); if (!socket_name) log_error ("warning: can't store socket name: %s\n", strerror (errno)); else { memcpy (socket_name, databuf, datalen); socket_name[datalen] = 0; if (DBG_IPC) log_debug ("additional connections at '%s'\n", socket_name); } } xfree (databuf); } /* Tell the scdaemon we want him to send us an event signal. We don't support this for W32CE. */ #ifndef HAVE_W32CE_SYSTEM if (opt.sigusr2_enabled) { char buf[100]; #ifdef HAVE_W32_SYSTEM snprintf (buf, sizeof buf, "OPTION event-signal=%lx", (unsigned long)get_agent_scd_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); } #endif /*HAVE_W32CE_SYSTEM*/ primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; leave: xfree (abs_homedir); if (err) { unlock_scd (ctrl, err); if (ctx) assuan_release (ctx); } else { ctrl->scd_local->ctx = ctx; } rc = npth_mutex_unlock (&start_scd_lock); if (rc) log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); return err; } /* Check whether the SCdaemon is active. This is a fast check without any locking and might give a wrong result if another thread is about to start the daemon or the daemon is about to be stopped.. */ int agent_scd_check_running (void) { return !!primary_scd_ctx; } /* Check whether the Scdaemon is still alive and clean it up if not. */ void agent_scd_check_aliveness (void) { pid_t pid; #ifdef HAVE_W32_SYSTEM DWORD rc; #else int rc; #endif struct timespec abstime; int err; if (!primary_scd_ctx) return; /* No scdaemon running. */ /* This is not a critical function so we use a short timeout while acquiring the lock. */ npth_clock_gettime (&abstime); abstime.tv_sec += 1; err = npth_mutex_timedlock (&start_scd_lock, &abstime); if (err) { if (err == ETIMEDOUT) { if (opt.verbose > 1) log_info ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", strerror (err)); } else log_error ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", strerror (err)); return; } if (primary_scd_ctx) { pid = assuan_get_pid (primary_scd_ctx); #ifdef HAVE_W32_SYSTEM /* If we have a PID we disconnect if either GetExitProcessCode fails or if ir returns the exit code of the scdaemon. 259 is the error code for STILL_ALIVE. */ if (pid != (pid_t)(void*)(-1) && pid && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) #else if (pid != (pid_t)(-1) && pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) #endif { /* Okay, scdaemon died. Disconnect the primary connection now but take care that it won't do another wait. Also cleanup all other connections and release their resources. The next use will start a new daemon then. Due to the use of the START_SCD_LOCAL we are sure that none of these context are actually in use. */ struct scd_local_s *sl; assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); assuan_release (primary_scd_ctx); for (sl=scd_local_list; sl; sl = sl->next_local) { if (sl->ctx) { if (sl->ctx != primary_scd_ctx) assuan_release (sl->ctx); sl->ctx = NULL; } } primary_scd_ctx = NULL; primary_scd_ctx_reusable = 0; xfree (socket_name); socket_name = NULL; } } err = npth_mutex_unlock (&start_scd_lock); if (err) log_error ("failed to release the start_scd lock while" " doing the aliveness check: %s\n", strerror (err)); } /* Reset the SCD if it has been used. Actually it is not a reset but a cleanup of resources used by the current connection. */ int agent_reset_scd (ctrl_t ctrl) { if (ctrl->scd_local) { if (ctrl->scd_local->ctx) { /* We can't disconnect the primary context because libassuan does a waitpid on it and thus the system would hang. Instead we send a reset and keep that connection for reuse. */ if (ctrl->scd_local->ctx == primary_scd_ctx) { /* Send a RESTART to the SCD. This is required for the primary connection as a kind of virtual EOF; we don't have another way to tell it that the next command should be viewed as if a new connection has been made. For the non-primary connections this is not needed as we simply close the socket. We don't check for an error here because the RESTART may fail for example if the scdaemon has already been terminated. Anyway, we need to set the reusable flag to make sure that the aliveness check can clean it up. */ assuan_transact (primary_scd_ctx, "RESTART", NULL, NULL, NULL, NULL, NULL, NULL); primary_scd_ctx_reusable = 1; } else assuan_release (ctrl->scd_local->ctx); ctrl->scd_local->ctx = NULL; } /* Remove the local context from our list and release it. */ if (!scd_local_list) BUG (); else if (scd_local_list == ctrl->scd_local) scd_local_list = ctrl->scd_local->next_local; else { struct scd_local_s *sl; for (sl=scd_local_list; sl->next_local; sl = sl->next_local) if (sl->next_local == ctrl->scd_local) break; if (!sl->next_local) BUG (); sl->next_local = ctrl->scd_local->next_local; } xfree (ctrl->scd_local); ctrl->scd_local = NULL; } return 0; } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct learn_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen)) { parm->certinfo_cb (parm->certinfo_cb_arg, line); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { parm->kpinfo_cb (parm->kpinfo_cb_arg, line); } else if (keywordlen && *line) { parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); } return 0; } /* Perform the LEARN command and return a list of all private keys stored on the card. */ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg) { int rc; struct learn_parm_s parm; rc = start_scd (ctrl); if (rc) return rc; memset (&parm, 0, sizeof parm); parm.kpinfo_cb = kpinfo_cb; parm.kpinfo_cb_arg = kpinfo_cb_arg; parm.certinfo_cb = certinfo_cb; parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) return unlock_scd (ctrl, rc); return unlock_scd (ctrl, 0); } static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return out_of_core (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Return the serial number of the card or an appropriate error. The serial number is returned as a hexstring. */ int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) { int rc; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; rc = start_scd (ctrl); if (rc) return rc; if (!demand) strcpy (line, "SERIALNO"); else snprintf (line, DIM(line), "SERIALNO --demand=%s", demand); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); return unlock_scd (ctrl, rc); } *r_serialno = serialno; return unlock_scd (ctrl, 0); } /* Handle the NEEDPIN inquiry. */ static gpg_error_t inq_needpin (void *opaque, const char *line) { struct inq_needpin_parm_s *parm = opaque; const char *s; char *pin; size_t pinlen; int rc; parm->any_inq_seen = 1; if ((s = has_leading_keyword (line, "NEEDPIN"))) { line = s; pinlen = 90; pin = gcry_malloc_secure (pinlen); if (!pin) return out_of_core (); rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, line, pin, pinlen); if (!rc) rc = assuan_send_data (parm->ctx, pin, pinlen); xfree (pin); } else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT"))) { rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, s, NULL, 1); } else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT"))) { rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, "", NULL, 0); } else if (parm->passthru) { unsigned char *value; size_t valuelen; int rest; int needrest = !strncmp (line, "KEYDATA", 8); /* Pass the inquiry up to our caller. We limit the maximum amount to an arbitrary value. As we know that the KEYDATA enquiry is pretty sensitive we disable logging then */ if ((rest = (needrest && !assuan_get_flag (parm->passthru, ASSUAN_CONFIDENTIAL)))) assuan_begin_confidential (parm->passthru); rc = assuan_inquire (parm->passthru, line, &value, &valuelen, 8096); if (rest) assuan_end_confidential (parm->passthru); if (!rc) { if ((rest = (needrest && !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL)))) assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->ctx, value, valuelen); if (rest) assuan_end_confidential (parm->ctx); xfree (value); } else log_error ("error forwarding inquiry '%s': %s\n", line, gpg_strerror (rc)); } else { log_error ("unsupported inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } /* Helper returning a command option to describe the used hash algorithm. See scd/command.c:cmd_pksign. */ static const char * hash_algo_option (int algo) { switch (algo) { case GCRY_MD_MD5 : return "--hash=md5"; case GCRY_MD_RMD160: return "--hash=rmd160"; case GCRY_MD_SHA1 : return "--hash=sha1"; case GCRY_MD_SHA224: return "--hash=sha224"; case GCRY_MD_SHA256: return "--hash=sha256"; case GCRY_MD_SHA384: return "--hash=sha384"; case GCRY_MD_SHA512: return "--hash=sha512"; default: return ""; } } static gpg_error_t cancel_inquire (ctrl_t ctrl, gpg_error_t rc) { gpg_error_t oldrc = rc; /* The inquire callback was called and transact returned a cancel error. We assume that the inquired process sent a CANCEL. The passthrough code is not able to pass on the CANCEL and thus scdaemon would stuck on this. As a workaround we send a CANCEL now. */ rc = assuan_write_line (ctrl->scd_local->ctx, "CAN"); if (!rc) { char *line; size_t len; rc = assuan_read_line (ctrl->scd_local->ctx, &line, &len); if (!rc) rc = oldrc; } return rc; } /* Create a signature using the current card. MDALGO is either 0 or * gives the digest algorithm. DESC_TEXT is an additional parameter * passed to GETPIN_CB. */ int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, int mdalgo, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_parm_s inqparm; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; if (indatalen*2 + 50 > DIM(line)) return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 0; inqparm.any_inq_seen = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; if (ctrl->use_auth_call) snprintf (line, sizeof line, "PKAUTH %s", keyid); else snprintf (line, sizeof line, "PKSIGN %s %s", hash_algo_option (mdalgo), keyid); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (inqparm.any_inq_seen && (gpg_err_code(rc) == GPG_ERR_CANCELED || gpg_err_code(rc) == GPG_ERR_ASS_CANCELED)) rc = cancel_inquire (ctrl, rc); if (rc) { size_t len; xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); return unlock_scd (ctrl, 0); } /* Check whether there is any padding info from scdaemon. */ static gpg_error_t padding_info_cb (void *opaque, const char *line) { int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } return 0; } /* Decipher INDATA using the current card. Note that the returned * value is not an s-expression but the raw data as returned by * scdaemon. The padding information is stored at R_PADDING with -1 * for not known. DESC_TEXT is an additional parameter passed to * GETPIN_CB. */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen, int *r_padding) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_parm_s inqparm; size_t len; *r_buf = NULL; *r_padding = -1; /* Unknown. */ rc = start_scd (ctrl); if (rc) return rc; /* FIXME: use secure memory where appropriate */ for (len = 0; len < indatalen;) { p = stpcpy (line, "SETDATA "); if (len) p = stpcpy (p, "--append "); for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++) { sprintf (p, "%02X", indata[len]); p += 2; } rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); } init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 0; inqparm.any_inq_seen = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; snprintf (line, DIM(line), "PKDECRYPT %s", keyid); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, inq_needpin, &inqparm, padding_info_cb, r_padding); if (inqparm.any_inq_seen && (gpg_err_code(rc) == GPG_ERR_CANCELED || gpg_err_code(rc) == GPG_ERR_ASS_CANCELED)) rc = cancel_inquire (ctrl, rc); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a certificate with ID into R_BUF and R_BUFLEN. */ int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line), "READCERT %s", id); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a key with ID and return it in an allocate buffer pointed to by r_BUF as a valid S-expression. */ int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line), "READKEY %s", id); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } return unlock_scd (ctrl, 0); } /* Handle a KEYDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_writekey_parms (void *opaque, const char *line) { struct inq_needpin_parm_s *parm = opaque; if (has_leading_keyword (line, "KEYDATA")) return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); else return inq_needpin (opaque, line); } int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg) { int rc; char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; (void)serialno; rc = start_scd (ctrl); if (rc) return rc; snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); parms.ctx = ctrl->scd_local->ctx; parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; parms.getpin_cb_desc= NULL; parms.passthru = 0; parms.any_inq_seen = 0; parms.keydata = keydata; parms.keydatalen = keydatalen; rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, inq_writekey_parms, &parms, NULL, NULL); if (parms.any_inq_seen && (gpg_err_code(rc) == GPG_ERR_CANCELED || gpg_err_code(rc) == GPG_ERR_ASS_CANCELED)) rc = cancel_inquire (ctrl, rc); return unlock_scd (ctrl, rc); } /* Type used with the card_getattr_cb. */ struct card_getattr_parm_s { const char *keyword; /* Keyword to look for. */ size_t keywordlen; /* strlen of KEYWORD. */ char *data; /* Malloced and unescaped data. */ int error; /* ERRNO value or 0 on success. */ }; /* Callback function for agent_card_getattr. */ static gpg_error_t card_getattr_cb (void *opaque, const char *line) { struct card_getattr_parm_s *parm = opaque; const char *keyword = line; int keywordlen; if (parm->data) return 0; /* We want only the first occurrence. */ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == parm->keywordlen && !memcmp (keyword, parm->keyword, keywordlen)) { parm->data = percent_plus_unescape ((const unsigned char*)line, 0xff); if (!parm->data) parm->error = errno; } return 0; } /* Call the agent to retrieve a single line data object. On success the object is malloced and stored at RESULT; it is guaranteed that NULL is never stored in this case. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result) { int err; struct card_getattr_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&parm, 0, sizeof parm); parm.keyword = name; parm.keywordlen = strlen (name); /* We assume that NAME does not need escaping. */ if (8 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "GETATTR "), name); err = start_scd (ctrl); if (err) return err; err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm); if (!err && parm.error) err = gpg_error_from_errno (parm.error); if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *result = parm.data; else xfree (parm.data); return unlock_scd (ctrl, err); } struct card_cardlist_parm_s { int error; strlist_t list; }; /* Callback function for agent_card_cardlist. */ static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { const char *s; int n; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1) || *s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } return 0; } /* Call the scdaemon to retrieve list of available cards. On success the allocated strlist is stored at RESULT. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result) { int err; struct card_cardlist_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; memset (&parm, 0, sizeof parm); strcpy (line, "GETINFO card_list"); err = start_scd (ctrl); if (err) return err; err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_strlist (parm.list); return unlock_scd (ctrl, err); } static gpg_error_t pass_status_thru (void *opaque, const char *line) { assuan_context_t ctx = opaque; char keyword[200]; int i; if (line[0] == '#' && (!line[1] || spacep (line+1))) { /* We are called in convey comments mode. Now, if we see a comment marker as keyword we forward the line verbatim to the the caller. This way the comment lines from scdaemon won't appear as status lines with keyword '#'. */ assuan_write_line (ctx, line); } else { for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++) keyword[i] = *line; keyword[i] = 0; /* Truncate any remaining keyword stuff. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; assuan_write_status (ctx, keyword, line); } return 0; } static gpg_error_t pass_data_thru (void *opaque, const void *buffer, size_t length) { assuan_context_t ctx = opaque; assuan_send_data (ctx, buffer, length); return 0; } /* Send the line CMDLINE with command for the SCDdaemon to it and send all status messages back. This command is used as a general quoting mechanism to pass everything verbatim to SCDAEMON. The PIN inquiry is handled inside gpg-agent. */ int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context) { int rc; struct inq_needpin_parm_s inqparm; int saveflag; rc = start_scd (ctrl); if (rc) return rc; inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = NULL; inqparm.passthru = assuan_context; inqparm.any_inq_seen = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS); assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1); rc = assuan_transact (ctrl->scd_local->ctx, cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); if (inqparm.any_inq_seen && gpg_err_code(rc) == GPG_ERR_ASS_CANCELED) rc = cancel_inquire (ctrl, rc); assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag); if (rc) { return unlock_scd (ctrl, rc); } return unlock_scd (ctrl, 0); } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index fdde0fbfd..57e2e425b 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1,3816 +1,3816 @@ /* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol. * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc. * Copyright (C) 2004-2006, 2009, 2012-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 . */ /* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs are: RFC-4250 - Protocol Assigned Numbers RFC-4251 - Protocol Architecture RFC-4252 - Authentication Protocol RFC-4253 - Transport Layer Protocol RFC-5656 - ECC support The protocol for the agent is defined in OpenSSH's PROTOCL.agent file. */ #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_UCRED_H #include #endif #include "agent.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/ssh-utils.h" /* Request types. */ #define SSH_REQUEST_REQUEST_IDENTITIES 11 #define SSH_REQUEST_SIGN_REQUEST 13 #define SSH_REQUEST_ADD_IDENTITY 17 #define SSH_REQUEST_REMOVE_IDENTITY 18 #define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 #define SSH_REQUEST_LOCK 22 #define SSH_REQUEST_UNLOCK 23 #define SSH_REQUEST_ADD_ID_CONSTRAINED 25 /* Options. */ #define SSH_OPT_CONSTRAIN_LIFETIME 1 #define SSH_OPT_CONSTRAIN_CONFIRM 2 /* Response types. */ #define SSH_RESPONSE_SUCCESS 6 #define SSH_RESPONSE_FAILURE 5 #define SSH_RESPONSE_IDENTITIES_ANSWER 12 #define SSH_RESPONSE_SIGN_RESPONSE 14 /* Other constants. */ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) #define SPEC_FLAG_IS_ECDSA (1 << 1) #define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/ #define SPEC_FLAG_WITH_CERT (1 << 7) /* The name of the control file. */ #define SSH_CONTROL_FILE_NAME "sshcontrol" /* The blurb we put into the header of a newly created control file. */ static const char sshcontrolblurb[] = "# List of allowed ssh keys. Only keys present in this file are used\n" "# in the SSH protocol. The ssh-add tool may add new entries to this\n" "# file to enable them; you may also add them manually. Comment\n" "# lines, like this one, as well as empty lines are ignored. Lines do\n" "# have a certain length limit but this is not serious limitation as\n" "# the format of the entries is fixed and checked by gpg-agent. A\n" "# non-comment line starts with optional white spaces, followed by the\n" "# keygrip of the key given as 40 hex digits, optionally followed by a\n" "# caching TTL in seconds, and another optional field for arbitrary\n" "# flags. Prepend the keygrip with an '!' mark to disable it.\n" "\n"; /* Macros. */ /* Return a new uint32 with b0 being the most significant byte and b3 being the least significant byte. */ #define uint32_construct(b0, b1, b2, b3) \ ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) /* * Basic types. */ /* Type for a request handler. */ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, estream_t request, estream_t response); struct ssh_key_type_spec; typedef struct ssh_key_type_spec ssh_key_type_spec_t; /* Type, which is used for associating request handlers with the appropriate request IDs. */ typedef struct ssh_request_spec { unsigned char type; ssh_request_handler_t handler; const char *identifier; unsigned int secret_input; } ssh_request_spec_t; /* Type for "key modifier functions", which are necessary since OpenSSH and GnuPG treat key material slightly different. A key modifier is called right after a new key identity has been received in order to "sanitize" the material. */ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, gcry_mpi_t *mpis); /* The encoding of a generated signature is dependent on the algorithm; therefore algorithm specific signature encoding functions are necessary. */ typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t sig); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; /* Human readable name of the algorithm. */ const char *name; /* Algorithm identifier as used by GnuPG. */ int algo; /* List of MPI names for secret keys; order matches the one of the agent protocol. */ const char *elems_key_secret; /* List of MPI names for public keys; order matches the one of the agent protocol. */ const char *elems_key_public; /* List of MPI names for signature data. */ const char *elems_signature; /* List of MPI names for secret keys; order matches the one, which is required by gpg-agent's key access layer. */ const char *elems_sexp_order; /* Key modifier function. Key modifier functions are necessary in order to fix any inconsistencies between the representation of keys on the SSH and on the GnuPG side. */ ssh_key_modifier_t key_modifier; /* Signature encoder function. Signature encoder functions are necessary since the encoding of signatures depends on the used algorithm. */ ssh_signature_encoder_t signature_encoder; /* The name of the ECC curve or NULL. */ const char *curve_name; /* The hash algorithm to be used with this key. 0 for using the default. */ int hash_algo; /* Misc flags. */ unsigned int flags; }; /* Definition of an object to access the sshcontrol file. */ struct ssh_control_file_s { char *fname; /* Name of the file. */ FILE *fp; /* This is never NULL. */ int lnr; /* The current line number. */ struct { int valid; /* True if the data of this structure is valid. */ int disabled; /* The item is disabled. */ int ttl; /* The TTL of the item. */ int confirm; /* The confirm flag is set. */ char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ } item; }; /* Prototypes. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment); /* Global variables. */ /* Associating request types with the corresponding request handlers. */ static ssh_request_spec_t request_specs[] = { #define REQUEST_SPEC_DEFINE(id, name, secret_input) \ { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input } REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1), REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0), REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1), REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1), REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0), REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0), REQUEST_SPEC_DEFINE (LOCK, lock, 0), REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0) #undef REQUEST_SPEC_DEFINE }; /* Table holding key type specifications. */ static ssh_key_type_spec_t ssh_key_types[] = { { "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", 0, SPEC_FLAG_IS_EdDSA }, { "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, 0, 0 }, { "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA }, { "ssh-ed25519-cert-v01@openssh.com", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT }, { "ssh-rsa-cert-v01@openssh.com", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT }, { "ssh-dss-cert-v01@openssh.com", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT } }; /* General utility functions. */ /* A secure realloc, i.e. it makes sure to allocate secure memory if A is NULL. This is required because the standard gcry_realloc does not know whether to allocate secure or normal if NULL is passed as existing buffer. */ static void * realloc_secure (void *a, size_t n) { void *p; if (a) p = gcry_realloc (a, n); else p = gcry_malloc_secure (n); return p; } /* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns NULL if not found. */ static const char * ssh_identifier_from_curve_name (const char *curve_name) { int i; for (i = 0; i < DIM (ssh_key_types); i++) if (ssh_key_types[i].curve_name && !strcmp (ssh_key_types[i].curve_name, curve_name)) return ssh_key_types[i].ssh_identifier; return NULL; } /* Primitive I/O functions. */ /* Read a byte from STREAM, store it in B. */ static gpg_error_t stream_read_byte (estream_t stream, unsigned char *b) { gpg_error_t err; int ret; ret = es_fgetc (stream); if (ret == EOF) { if (es_ferror (stream)) err = gpg_error_from_syserror (); else err = gpg_error (GPG_ERR_EOF); *b = 0; } else { *b = ret & 0xFF; err = 0; } return err; } /* Write the byte contained in B to STREAM. */ static gpg_error_t stream_write_byte (estream_t stream, unsigned char b) { gpg_error_t err; int ret; ret = es_fputc (b, stream); if (ret == EOF) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) { unsigned char buffer[4]; size_t bytes_read; gpg_error_t err; int ret; ret = es_read (stream, buffer, sizeof (buffer), &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != sizeof (buffer)) err = gpg_error (GPG_ERR_EOF); else { u32 n; n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]); *uint32 = n; err = 0; } } return err; } /* Write the uint32 contained in UINT32 to STREAM. */ static gpg_error_t stream_write_uint32 (estream_t stream, u32 uint32) { unsigned char buffer[4]; gpg_error_t err; int ret; buffer[0] = uint32 >> 24; buffer[1] = uint32 >> 16; buffer[2] = uint32 >> 8; buffer[3] = uint32 >> 0; ret = es_write (stream, buffer, sizeof (buffer), NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read SIZE bytes from STREAM into BUFFER. */ static gpg_error_t stream_read_data (estream_t stream, unsigned char *buffer, size_t size) { gpg_error_t err; size_t bytes_read; int ret; ret = es_read (stream, buffer, size, &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != size) err = gpg_error (GPG_ERR_EOF); else err = 0; } return err; } /* Skip over SIZE bytes from STREAM. */ static gpg_error_t stream_read_skip (estream_t stream, size_t size) { char buffer[128]; size_t bytes_to_read, bytes_read; int ret; do { bytes_to_read = size; if (bytes_to_read > sizeof buffer) bytes_to_read = sizeof buffer; ret = es_read (stream, buffer, bytes_to_read, &bytes_read); if (ret) return gpg_error_from_syserror (); else if (bytes_read != bytes_to_read) return gpg_error (GPG_ERR_EOF); else size -= bytes_to_read; } while (size); return 0; } /* Write SIZE bytes from BUFFER to STREAM. */ static gpg_error_t stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) { gpg_error_t err; int ret; ret = es_write (stream, buffer, size, NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a binary string from STREAM into STRING, store size of string in STRING_SIZE. Append a hidden nul so that the result may directly be used as a C string. Depending on SECURE use secure memory for STRING. If STRING is NULL do only a dummy read. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; if (string_size) *string_size = 0; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto out; if (string) { /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length + 1); else buffer = xtrymalloc (length + 1); if (! buffer) { err = gpg_error_from_syserror (); goto out; } /* Read data. */ err = stream_read_data (stream, buffer, length); if (err) goto out; /* Finalize string object. */ buffer[length] = 0; *string = buffer; } else /* Dummy read requested. */ { err = stream_read_skip (stream, length); if (err) goto out; } if (string_size) *string_size = length; out: if (err) xfree (buffer); return err; } /* Read a binary string from STREAM and store it as an opaque MPI at R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP). Depending on SECURE use secure memory. If the string is too large for key material return an error. */ static gpg_error_t stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; *r_mpi = NULL; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto leave; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (length > (4096/8) + 8) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto leave; } /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length+1); else buffer = xtrymalloc (length+1); if (!buffer) { err = gpg_error_from_syserror (); goto leave; } /* Read data. */ err = stream_read_data (stream, buffer + 1, length); if (err) goto leave; buffer[0] = 0x40; *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1)); buffer = NULL; leave: xfree (buffer); return err; } /* Read a C-string from STREAM, store copy in STRING. */ static gpg_error_t stream_read_cstring (estream_t stream, char **string) { return stream_read_string (stream, 0, (unsigned char **)string, NULL); } /* Write a binary string from STRING of size STRING_N to STREAM. */ static gpg_error_t stream_write_string (estream_t stream, const unsigned char *string, u32 string_n) { gpg_error_t err; err = stream_write_uint32 (stream, string_n); if (err) goto out; err = stream_write_data (stream, string, string_n); out: return err; } /* Write a C-string from STRING to STREAM. */ static gpg_error_t stream_write_cstring (estream_t stream, const char *string) { gpg_error_t err; err = stream_write_string (stream, (const unsigned char *) string, strlen (string)); return err; } /* Read an MPI from STREAM, store it in MPINT. Depending on SECURE use secure memory. */ static gpg_error_t stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint) { unsigned char *mpi_data; u32 mpi_data_size; gpg_error_t err; gcry_mpi_t mpi; mpi_data = NULL; err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size); if (err) goto out; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (mpi_data_size > 520) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto out; } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL); if (err) goto out; *mpint = mpi; out: xfree (mpi_data); return err; } /* Write the MPI contained in MPINT to STREAM. */ static gpg_error_t stream_write_mpi (estream_t stream, gcry_mpi_t mpint) { unsigned char *mpi_buffer; size_t mpi_buffer_n; gpg_error_t err; mpi_buffer = NULL; err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); if (err) goto out; err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); out: xfree (mpi_buffer); return err; } /* Copy data from SRC to DST until EOF is reached. */ static gpg_error_t stream_copy (estream_t dst, estream_t src) { char buffer[BUFSIZ]; size_t bytes_read; gpg_error_t err; int ret; err = 0; while (1) { ret = es_read (src, buffer, sizeof (buffer), &bytes_read); if (ret || (! bytes_read)) { if (ret) err = gpg_error_from_syserror (); break; } ret = es_write (dst, buffer, bytes_read, NULL); if (ret) { err = gpg_error_from_syserror (); break; } } return err; } /* Open the ssh control file and create it if not available. With APPEND passed as true the file will be opened in append mode, otherwise in read only mode. On success 0 is returned and a new control file object stored at R_CF. On error an error code is returned and NULL is stored at R_CF. */ static gpg_error_t open_control_file (ssh_control_file_t *r_cf, int append) { gpg_error_t err; ssh_control_file_t cf; cf = xtrycalloc (1, sizeof *cf); if (!cf) { err = gpg_error_from_syserror (); goto leave; } /* Note: As soon as we start to use non blocking functions here (i.e. where Pth might switch threads) we need to employ a mutex. */ cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL); if (!cf->fname) { err = gpg_error_from_syserror (); goto leave; } /* FIXME: With "a+" we are not able to check whether this will be created and thus the blurb needs to be written first. */ cf->fp = fopen (cf->fname, append? "a+":"r"); if (!cf->fp && errno == ENOENT) { estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r"); if (!stream) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } es_fputs (sshcontrolblurb, stream); es_fclose (stream); cf->fp = fopen (cf->fname, append? "a+":"r"); } if (!cf->fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } err = 0; leave: if (err && cf) { if (cf->fp) fclose (cf->fp); xfree (cf->fname); xfree (cf); } else *r_cf = cf; return err; } static void rewind_control_file (ssh_control_file_t cf) { fseek (cf->fp, 0, SEEK_SET); cf->lnr = 0; clearerr (cf->fp); } static void close_control_file (ssh_control_file_t cf) { if (!cf) return; fclose (cf->fp); xfree (cf->fname); xfree (cf); } /* Read the next line from the control file and store the data in CF. Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */ static gpg_error_t read_control_file_item (ssh_control_file_t cf) { int c, i, n; char *p, *pend, line[256]; long ttl = 0; cf->item.valid = 0; clearerr (cf->fp); do { if (!fgets (line, DIM(line)-1, cf->fp) ) { if (feof (cf->fp)) return gpg_error (GPG_ERR_EOF); return gpg_error_from_syserror (); } cf->lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line */ while ( (c=getc (cf->fp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); cf->item.disabled = 0; if (*p == '!') { cf->item.disabled = 1; for (p++; spacep (p); p++) ; } for (i=0; hexdigitp (p) && i < 40; p++, i++) cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p); cf->item.hexgrip[i] = 0; if (i != 40 || !(spacep (p) || *p == '\n')) { log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr); return gpg_error (GPG_ERR_BAD_DATA); } ttl = strtol (p, &pend, 10); p = pend; if (!(spacep (p) || *p == '\n') || (int)ttl < -1) { log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr); cf->item.ttl = 0; } cf->item.ttl = ttl; /* Now check for key-value pairs of the form NAME[=VALUE]. */ cf->item.confirm = 0; while (*p) { for (; spacep (p) && *p != '\n'; p++) ; if (!*p || *p == '\n') break; n = strcspn (p, "= \t\n"); if (p[n] == '=') { log_error ("%s:%d: assigning a value to a flag is not yet supported; " "flag ignored\n", cf->fname, cf->lnr); p++; } else if (n == 7 && !memcmp (p, "confirm", 7)) { cf->item.confirm = 1; } else log_error ("%s:%d: invalid flag '%.*s'; ignored\n", cf->fname, cf->lnr, n, p); p += n; } /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */ /* cf->fname, cf->lnr, */ /* cf->item.hexgrip, cf->item.ttl, */ /* cf->item.disabled? " disabled":"", */ /* cf->item.confirm? " confirm":""); */ cf->item.valid = 1; return 0; /* Okay: valid entry found. */ } /* Search the control file CF from the beginning until a matching HEXGRIP is found; return success in this case and store true at DISABLED if the found key has been disabled. If R_TTL is not NULL a specified TTL for that key is stored there. If R_CONFIRM is not NULL it is set to 1 if the key has the confirm flag set. */ static gpg_error_t search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; assert (strlen (hexgrip) == 40 ); if (r_disabled) *r_disabled = 0; if (r_ttl) *r_ttl = 0; if (r_confirm) *r_confirm = 0; rewind_control_file (cf); while (!(err=read_control_file_item (cf))) { if (!cf->item.valid) continue; /* Should not happen. */ if (!strcmp (hexgrip, cf->item.hexgrip)) break; } if (!err) { if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Add an entry to the control file to mark the key with the keygrip HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks for it. FMTFPR is the fingerprint string. This function is in general used to add a key received through the ssh-add function. We can assume that the user wants to allow ssh using this key. */ static gpg_error_t add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec, const char *hexgrip, const char *fmtfpr, int ttl, int confirm) { gpg_error_t err; ssh_control_file_t cf; int disabled; (void)ctrl; err = open_control_file (&cf, 1); if (err) return err; err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; time_t atime = time (NULL); /* Not yet in the file - add it. Because the file has been opened in append mode, we simply need to write to it. */ tp = localtime (&atime); fprintf (cf->fp, ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n" "# MD5 Fingerprint: %s\n" "%s %d%s\n"), spec->name, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, fmtfpr, hexgrip, ttl, confirm? " confirm":""); } close_control_file (cf); return 0; } /* Scan the sshcontrol file and return the TTL. */ static int ttl_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, ttl; if (!hexgrip || strlen (hexgrip) != 40) return 0; /* Wrong input: Use global default. */ if (open_control_file (&cf, 0)) return 0; /* Error: Use the global default TTL. */ if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) || disabled) ttl = 0; /* Use the global default if not found or disabled. */ close_control_file (cf); return ttl; } /* Scan the sshcontrol file and return the confirm flag. */ static int confirm_flag_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, confirm; if (!hexgrip || strlen (hexgrip) != 40) return 1; /* Wrong input: Better ask for confirmation. */ if (open_control_file (&cf, 0)) return 1; /* Error: Better ask for confirmation. */ if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm) || disabled) confirm = 0; /* If not found or disabled, there is no reason to ask for confirmation. */ close_control_file (cf); return confirm; } /* Open the ssh control file for reading. This is a public version of open_control_file. The caller must use ssh_close_control_file to release the returned handle. */ ssh_control_file_t ssh_open_control_file (void) { ssh_control_file_t cf; /* Then look at all the registered and non-disabled keys. */ if (open_control_file (&cf, 0)) return NULL; return cf; } /* Close an ssh control file handle. This is the public version of close_control_file. CF may be NULL. */ void ssh_close_control_file (ssh_control_file_t cf) { close_control_file (cf); } /* Read the next item from the ssh control file. The function returns 0 if a item was read, GPG_ERR_EOF on eof or another error value. R_HEXGRIP shall either be null or a BUFFER of at least 41 byte. R_DISABLED, R_TTLm and R_CONFIRM return flags from the control file; they are only set on success. */ gpg_error_t ssh_read_control_file (ssh_control_file_t cf, char *r_hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; do err = read_control_file_item (cf); while (!err && !cf->item.valid); if (!err) { if (r_hexgrip) strcpy (r_hexgrip, cf->item.hexgrip); if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Search for a key with HEXGRIP in sshcontrol and return all info. */ gpg_error_t ssh_search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; int i; const char *s; char uphexgrip[41]; /* We need to make sure that HEXGRIP is all uppercase. The easiest way to do this and also check its length is by copying to a second buffer. */ for (i=0, s=hexgrip; i < 40 && *s; s++, i++) uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s; uphexgrip[i] = 0; if (i != 40) err = gpg_error (GPG_ERR_INV_LENGTH); else err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm); if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); return err; } /* MPI lists. */ /* Free the list of MPIs MPI_LIST. */ static void mpint_list_free (gcry_mpi_t *mpi_list) { if (mpi_list) { unsigned int i; for (i = 0; mpi_list[i]; i++) gcry_mpi_release (mpi_list[i]); xfree (mpi_list); } } /* Receive key material MPIs from STREAM according to KEY_SPEC; depending on SECRET expect a public key or secret key. CERT is the certificate blob used if KEY_SPEC indicates the certificate format; it needs to be positioned to the end of the nonce. The newly allocated list of MPIs is stored in MPI_LIST. Returns usual error code. */ static gpg_error_t ssh_receive_mpint_list (estream_t stream, int secret, ssh_key_type_spec_t *spec, estream_t cert, gcry_mpi_t **mpi_list) { const char *elems_public; unsigned int elems_n; const char *elems; int elem_is_secret; gcry_mpi_t *mpis = NULL; gpg_error_t err = 0; unsigned int i; if (secret) elems = spec->elems_key_secret; else elems = spec->elems_key_public; elems_n = strlen (elems); elems_public = spec->elems_key_public; /* Check that either both, CERT and the WITH_CERT flag, are given or none of them. */ if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert)) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } mpis = xtrycalloc (elems_n + 1, sizeof *mpis ); if (!mpis) { err = gpg_error_from_syserror (); goto out; } elem_is_secret = 0; for (i = 0; i < elems_n; i++) { if (secret) elem_is_secret = !strchr (elems_public, elems[i]); if (cert && !elem_is_secret) err = stream_read_mpi (cert, elem_is_secret, &mpis[i]); else err = stream_read_mpi (stream, elem_is_secret, &mpis[i]); if (err) goto out; } *mpi_list = mpis; mpis = NULL; out: if (err) mpint_list_free (mpis); return err; } /* Key modifier function for RSA. */ static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) { gcry_mpi_t p; gcry_mpi_t q; gcry_mpi_t u; if (strcmp (elems, "nedupq")) /* Modifying only necessary for secret keys. */ goto out; u = mpis[3]; p = mpis[4]; q = mpis[5]; if (gcry_mpi_cmp (p, q) > 0) { /* P shall be smaller then Q! Swap primes. iqmp becomes u. */ gcry_mpi_t tmp; tmp = mpis[4]; mpis[4] = mpis[5]; mpis[5] = tmp; } else /* U needs to be recomputed. */ gcry_mpi_invm (u, p, q); out: return 0; } /* Signature encoder function for RSA. */ static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data; size_t data_n; gcry_mpi_t s; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* RSA specific */ s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); if (err) goto out; err = stream_write_string (signature_blob, data, data_n); xfree (data); out: gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for DSA. */ static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data = NULL; size_t data_n; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* DSA specific code. */ /* FIXME: Why this complicated code? Why collecting boths mpis in a buffer instead of writing them out one after the other? */ for (i = 0; i < 2; i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); if (err) break; if (data_n > SSH_DSA_SIGNATURE_PADDING) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0, SSH_DSA_SIGNATURE_PADDING - data_n); memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING) + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n); xfree (data); data = NULL; } if (err) goto out; err = stream_write_string (signature_blob, buffer, sizeof (buffer)); out: xfree (data); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for ECDSA. */ static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t innerlen; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* ECDSA specific */ innerlen = 0; for (i = 0; i < DIM(data); i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]); if (err) goto out; innerlen += 4 + data_n[i]; } err = stream_write_uint32 (stream, innerlen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_string (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for EdDSA. */ static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t totallen = 0; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); if (elems_n != DIM(data)) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } for (i = 0; i < DIM(data); i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]); if (!data[i]) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } totallen += data_n[i]; gcry_sexp_release (sublist); sublist = NULL; } if (err) goto out; err = stream_write_uint32 (stream, totallen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_data (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); return err; } /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to be stored at R_SEXP. Returns an error code. */ static gpg_error_t sexp_key_construct (gcry_sexp_t *r_sexp, ssh_key_type_spec_t key_spec, int secret, const char *curve_name, gcry_mpi_t *mpis, const char *comment) { gpg_error_t err; gcry_sexp_t sexp_new = NULL; void *formatbuf = NULL; void **arg_list = NULL; estream_t format = NULL; char *algo_name = NULL; if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { /* It is much easier and more readable to use a separate code path for EdDSA. */ if (!curve_name) err = gpg_error (GPG_ERR_INV_CURVE); else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE)) err = gpg_error (GPG_ERR_BAD_PUBKEY); else if (secret && (!mpis[1] || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE))) err = gpg_error (GPG_ERR_BAD_SECKEY); else if (secret) err = gcry_sexp_build (&sexp_new, NULL, "(private-key(ecc(curve %s)" "(flags eddsa)(q %m)(d %m))" "(comment%s))", curve_name, mpis[0], mpis[1], comment? comment:""); else err = gcry_sexp_build (&sexp_new, NULL, "(public-key(ecc(curve %s)" "(flags eddsa)(q %m))" "(comment%s))", curve_name, mpis[0], comment? comment:""); } else { const char *key_identifier[] = { "public-key", "private-key" }; int arg_idx; const char *elems; size_t elems_n; unsigned int i, j; if (secret) elems = key_spec.elems_sexp_order; else elems = key_spec.elems_key_public; elems_n = strlen (elems); format = es_fopenmem (0, "a+b"); if (!format) { err = gpg_error_from_syserror (); goto out; } /* Key identifier, algorithm identifier, mpis, comment, and a NULL as a safeguard. */ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1)); if (!arg_list) { err = gpg_error_from_syserror (); goto out; } arg_idx = 0; es_fputs ("(%s(%s", format); arg_list[arg_idx++] = &key_identifier[secret]; algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo)); if (!algo_name) { err = gpg_error_from_syserror (); goto out; } strlwr (algo_name); arg_list[arg_idx++] = &algo_name; if (curve_name) { es_fputs ("(curve%s)", format); arg_list[arg_idx++] = &curve_name; } for (i = 0; i < elems_n; i++) { es_fprintf (format, "(%c%%m)", elems[i]); if (secret) { for (j = 0; j < elems_n; j++) if (key_spec.elems_key_secret[j] == elems[i]) break; } else j = i; arg_list[arg_idx++] = &mpis[j]; } es_fputs (")(comment%s))", format); arg_list[arg_idx++] = &comment; arg_list[arg_idx] = NULL; es_putc (0, format); if (es_ferror (format)) { err = gpg_error_from_syserror (); goto out; } if (es_fclose_snatch (format, &formatbuf, NULL)) { err = gpg_error_from_syserror (); goto out; } format = NULL; err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); } if (!err) *r_sexp = sexp_new; out: es_fclose (format); xfree (arg_list); xfree (formatbuf); xfree (algo_name); return err; } /* This function extracts the key from the s-expression SEXP according to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If WITH_SECRET is true, the secret key parts are also extracted if possible. Returns 0 on success or an error code. Note that data stored at R_BLOB must be freed using es_free! */ static gpg_error_t ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, ssh_key_type_spec_t key_spec, void **r_blob, size_t *r_blob_size) { gpg_error_t err = 0; gcry_sexp_t value_list = NULL; gcry_sexp_t value_pair = NULL; char *curve_name = NULL; estream_t stream = NULL; void *blob = NULL; size_t blob_size; const char *elems, *p_elems; const char *data; size_t datalen; *r_blob = NULL; *r_blob_size = 0; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } /* Get the type of the key extpression. */ data = gcry_sexp_nth_data (sexp, 0, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((datalen == 10 && !strncmp (data, "public-key", 10)) || (datalen == 21 && !strncmp (data, "protected-private-key", 21)) || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20))) elems = key_spec.elems_key_public; else if (datalen == 11 && !strncmp (data, "private-key", 11)) elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public; else { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Get key value list. */ value_list = gcry_sexp_cadr (sexp); if (!value_list) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Write the ssh algorithm identifier. */ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) { /* Parse the "curve" parameter. We currently expect the curve name for ECC and not the parameters of the curve. This can easily be changed but then we need to find the curve name from the parameters using gcry_pk_get_curve. */ const char *mapped; const char *sshname; gcry_sexp_release (value_pair); value_pair = gcry_sexp_find_token (value_list, "curve", 5); if (!value_pair) { err = gpg_error (GPG_ERR_INV_CURVE); goto out; } curve_name = gcry_sexp_nth_string (value_pair, 1); if (!curve_name) { err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ goto out; } /* Fixme: The mapping should be done by using gcry_pk_get_curve et al to iterate over all name aliases. */ if (!strcmp (curve_name, "NIST P-256")) mapped = "nistp256"; else if (!strcmp (curve_name, "NIST P-384")) mapped = "nistp384"; else if (!strcmp (curve_name, "NIST P-521")) mapped = "nistp521"; else mapped = NULL; if (mapped) { xfree (curve_name); curve_name = xtrystrdup (mapped); if (!curve_name) { err = gpg_error_from_syserror (); goto out; } } sshname = ssh_identifier_from_curve_name (curve_name); if (!sshname) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); goto out; } err = stream_write_cstring (stream, sshname); if (err) goto out; err = stream_write_cstring (stream, curve_name); if (err) goto out; } else { /* Note: This is also used for EdDSA. */ err = stream_write_cstring (stream, key_spec.ssh_identifier); if (err) goto out; } /* Write the parameters. */ for (p_elems = elems; *p_elems; p_elems++) { gcry_sexp_release (value_pair); value_pair = gcry_sexp_find_token (value_list, p_elems, 1); if (!value_pair) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { data = gcry_sexp_nth_data (value_pair, 1, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if (*p_elems == 'q' && datalen) { /* Remove the prefix 0x40. */ data++; datalen--; } err = stream_write_string (stream, data, datalen); if (err) goto out; } else { gcry_mpi_t mpi; /* Note that we need to use STD format; i.e. prepend a 0x00 to indicate a positive number if the high bit is set. */ mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); if (!mpi) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } err = stream_write_mpi (stream, mpi); gcry_mpi_release (mpi); if (err) goto out; } } if (es_fclose_snatch (stream, &blob, &blob_size)) { err = gpg_error_from_syserror (); goto out; } stream = NULL; *r_blob = blob; blob = NULL; *r_blob_size = blob_size; out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); xfree (curve_name); es_fclose (stream); es_free (blob); return err; } /* Key I/O. */ /* Search for a key specification entry. If SSH_NAME is not NULL, search for an entry whose "ssh_name" is equal to SSH_NAME; otherwise, search for an entry whose algorithm is equal to ALGO. Store found entry in SPEC on success, return error otherwise. */ static gpg_error_t ssh_key_type_lookup (const char *ssh_name, int algo, ssh_key_type_spec_t *spec) { gpg_error_t err; unsigned int i; for (i = 0; i < DIM (ssh_key_types); i++) if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier))) || algo == ssh_key_types[i].algo) break; if (i == DIM (ssh_key_types)) err = gpg_error (GPG_ERR_NOT_FOUND); else { *spec = ssh_key_types[i]; err = 0; } return err; } /* Receive a key from STREAM, according to the key specification given as KEY_SPEC. Depending on SECRET, receive a secret or a public key. If READ_COMMENT is true, receive a comment string as well. Constructs a new S-Expression from received data and stores it in KEY_NEW. Returns zero on success or an error code. */ static gpg_error_t ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, int read_comment, ssh_key_type_spec_t *key_spec) { gpg_error_t err; char *key_type = NULL; char *comment = NULL; estream_t cert = NULL; gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; char *curve_name = NULL; err = stream_read_cstring (stream, &key_type); if (err) goto out; err = ssh_key_type_lookup (key_type, 0, &spec); if (err) goto out; if ((spec.flags & SPEC_FLAG_WITH_CERT)) { /* This is an OpenSSH certificate+private key. The certificate is an SSH string and which we store in an estream object. */ unsigned char *buffer; u32 buflen; char *cert_key_type; err = stream_read_string (stream, 0, &buffer, &buflen); if (err) goto out; cert = es_fopenmem_init (0, "rb", buffer, buflen); xfree (buffer); if (!cert) { err = gpg_error_from_syserror (); goto out; } /* Check that the key type matches. */ err = stream_read_cstring (cert, &cert_key_type); if (err) goto out; if (strcmp (cert_key_type, key_type) ) { xfree (cert_key_type); log_error ("key types in received ssh certificate do not match\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } xfree (cert_key_type); /* Skip the nonce. */ err = stream_read_string (cert, 0, NULL, NULL); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { /* The format of an EdDSA key is: * string key_type ("ssh-ed25519") * string public_key * string private_key * * Note that the private key is the concatenation of the private - * key with the public key. Thus theres are 64 bytes; however + * key with the public key. Thus there's are 64 bytes; however * we only want the real 32 byte private key - Libgcrypt expects * this. */ mpi_list = xtrycalloc (3, sizeof *mpi_list); if (!mpi_list) { err = gpg_error_from_syserror (); goto out; } err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]); if (err) goto out; if (secret) { u32 len = 0; unsigned char *buffer; /* Read string length. */ err = stream_read_uint32 (stream, &len); if (err) goto out; if (len != 32 && len != 64) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto out; } buffer = xtrymalloc_secure (32); if (!buffer) { err = gpg_error_from_syserror (); goto out; } err = stream_read_data (stream, buffer, 32); if (err) { xfree (buffer); goto out; } mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32); buffer = NULL; if (len == 64) { err = stream_read_skip (stream, 32); if (err) goto out; } } } else if ((spec.flags & SPEC_FLAG_IS_ECDSA)) { /* The format of an ECDSA key is: * string key_type ("ecdsa-sha2-nistp256" | * "ecdsa-sha2-nistp384" | * "ecdsa-sha2-nistp521" ) * string ecdsa_curve_name * string ecdsa_public_key * mpint ecdsa_private * * Note that we use the mpint reader instead of the string * reader for ecsa_public_key. For the certificate variante * ecdsa_curve_name+ecdsa_public_key are replaced by the * certificate. */ unsigned char *buffer; const char *mapped; err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); if (err) goto out; curve_name = buffer; /* Fixme: Check that curve_name matches the keytype. */ /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" curve names, we need to translate them here to Libgcrypt's native names. */ if (!strcmp (curve_name, "nistp256")) mapped = "NIST P-256"; else if (!strcmp (curve_name, "nistp384")) mapped = "NIST P-384"; else if (!strcmp (curve_name, "nistp521")) mapped = "NIST P-521"; else mapped = NULL; if (mapped) { xfree (curve_name); curve_name = xtrystrdup (mapped); if (!curve_name) { err = gpg_error_from_syserror (); goto out; } } err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } else { err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } if (read_comment) { err = stream_read_cstring (stream, &comment); if (err) goto out; } if (secret) elems = spec.elems_key_secret; else elems = spec.elems_key_public; if (spec.key_modifier) { err = (*spec.key_modifier) (elems, mpi_list); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { if (secret) { err = gcry_sexp_build (&key, NULL, "(private-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m)(d %m))" "(comment%s))", mpi_list[0], mpi_list[1], comment? comment:""); } else { err = gcry_sexp_build (&key, NULL, "(public-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m))" "(comment%s))", mpi_list[0], comment? comment:""); } } else { err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, comment? comment:""); if (err) goto out; } if (key_spec) *key_spec = spec; *key_new = key; out: es_fclose (cert); mpint_list_free (mpi_list); xfree (curve_name); xfree (key_type); xfree (comment); return err; } /* Write the public key from KEY to STREAM in SSH key format. If OVERRIDE_COMMENT is not NULL, it will be used instead of the comment stored in the key. */ static gpg_error_t ssh_send_key_public (estream_t stream, gcry_sexp_t key, const char *override_comment) { ssh_key_type_spec_t spec; int algo; char *comment = NULL; void *blob = NULL; size_t bloblen; gpg_error_t err = 0; algo = get_pk_algo_from_key (key); if (algo == 0) goto out; err = ssh_key_type_lookup (NULL, algo, &spec); if (err) goto out; err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen); if (err) goto out; err = stream_write_string (stream, blob, bloblen); if (err) goto out; if (override_comment) err = stream_write_cstring (stream, override_comment); else { err = ssh_key_extract_comment (key, &comment); if (err) err = stream_write_cstring (stream, "(none)"); else err = stream_write_cstring (stream, comment); } if (err) goto out; out: xfree (comment); es_free (blob); return err; } /* Read a public key out of BLOB/BLOB_SIZE according to the key specification given as KEY_SPEC, storing the new key in KEY_PUBLIC. Returns zero on success or an error code. */ static gpg_error_t ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, gcry_sexp_t *key_public, ssh_key_type_spec_t *key_spec) { gpg_error_t err; estream_t blob_stream; blob_stream = es_fopenmem (0, "r+b"); if (!blob_stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (blob_stream, blob, blob_size); if (err) goto out; err = es_fseek (blob_stream, 0, SEEK_SET); if (err) goto out; err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec); out: es_fclose (blob_stream); return err; } /* This function calculates the key grip for the key contained in the S-Expression KEY and writes it to BUFFER, which must be large enough to hold it. Returns usual error code. */ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) { gpg_error_t err = gcry_pk_testkey (key); return err? err : gpg_error (GPG_ERR_INTERNAL); } return 0; } static gpg_error_t card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result) { gpg_error_t err; *r_serialno = NULL; *result = NULL; err = agent_card_serialno (ctrl, r_serialno, NULL); if (err) { if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); /* Nothing available. */ return 0; } err = agent_card_cardlist (ctrl, result); if (err) { xfree (*r_serialno); *r_serialno = NULL; } return err; } /* Check whether a smartcard is available and whether it has a usable key. Store a copy of that key at R_PK and return 0. If no key is available store NULL at R_PK and return an error code. If CARDSN is not NULL, a string with the serial number of the card will be a malloced and stored there. */ static gpg_error_t card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) { gpg_error_t err; char *authkeyid; char *serialno = NULL; unsigned char *pkbuf; size_t pkbuflen; gcry_sexp_t s_pk; unsigned char grip[20]; *r_pk = NULL; if (cardsn) *cardsn = NULL; /* First see whether a card is available and whether the application is supported. */ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) { /* Ask for the serial number to reset the card. */ err = agent_card_serialno (ctrl, &serialno, NULL); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); return err; } log_info (_("detected card with S/N: %s\n"), serialno); err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); } if (err) { log_error (_("no authentication key for ssh on card: %s\n"), gpg_strerror (err)); xfree (serialno); return err; } /* Get the S/N if we don't have it yet. Use the fast getattr method. */ if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) ) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (err)); xfree (authkeyid); return err; } /* Read the public key. */ err = agent_card_readkey (ctrl, authkeyid, &pkbuf); if (err) { if (opt.verbose) log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); xfree (serialno); xfree (authkeyid); return err; } pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); if (err) { log_error ("failed to build S-Exp from received card key: %s\n", gpg_strerror (err)); xfree (pkbuf); xfree (serialno); xfree (authkeyid); return err; } err = ssh_key_grip (s_pk, grip); if (err) { log_debug ("error computing keygrip from received card key: %s\n", gcry_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } if ( agent_key_available (grip) ) { /* (Shadow)-key is not available in our key storage. */ err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0); if (err) { xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } if (cardsn) { char *dispsn; /* If the card handler is able to return a short serialnumber, use that one, else use the complete serialno. */ if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn)) { *cardsn = xtryasprintf ("cardno:%s", dispsn); xfree (dispsn); } else *cardsn = xtryasprintf ("cardno:%s", serialno); if (!*cardsn) { err = gpg_error_from_syserror (); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } xfree (pkbuf); xfree (serialno); xfree (authkeyid); *r_pk = s_pk; return 0; } /* Request handler. Each handler is provided with a CTRL context, a REQUEST object and a RESPONSE object. The actual request is to be read from REQUEST, the response needs to be written to RESPONSE. */ /* Handler for the "request_identities" command. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response) { u32 key_counter; estream_t key_blobs; gcry_sexp_t key_public; gpg_error_t err; int ret; ssh_control_file_t cf = NULL; gpg_error_t ret_err; (void)request; /* Prepare buffer stream. */ key_public = NULL; key_counter = 0; key_blobs = es_fopenmem (0, "r+b"); if (! key_blobs) { err = gpg_error_from_syserror (); goto out; } /* First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol. */ if (!opt.disable_scdaemon) { char *serialno; strlist_t card_list, sl; err = card_key_list (ctrl, &serialno, &card_list); if (err) { if (opt.verbose) log_info (_("error getting list of cards: %s\n"), gpg_strerror (err)); goto scd_out; } for (sl = card_list; sl; sl = sl->next) { char *serialno0; char *cardsn; err = agent_card_serialno (ctrl, &serialno0, sl->d); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); continue; } xfree (serialno0); if (card_key_available (ctrl, &key_public, &cardsn)) continue; err = ssh_send_key_public (key_blobs, key_public, cardsn); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); if (err) { xfree (serialno); free_strlist (card_list); goto out; } key_counter++; } xfree (serialno); free_strlist (card_list); } scd_out: /* Then look at all the registered and non-disabled keys. */ err = open_control_file (&cf, 0); if (err) goto out; while (!read_control_file_item (cf)) { unsigned char grip[20]; if (!cf->item.valid) continue; /* Should not happen. */ if (cf->item.disabled) continue; assert (strlen (cf->item.hexgrip) == 40); hex2bin (cf->item.hexgrip, grip, sizeof (grip)); err = agent_public_key_from_file (ctrl, grip, &key_public); if (err) { log_error ("%s:%d: key '%s' skipped: %s\n", cf->fname, cf->lnr, cf->item.hexgrip, gpg_strerror (err)); continue; } err = ssh_send_key_public (key_blobs, key_public, NULL); if (err) goto out; gcry_sexp_release (key_public); key_public = NULL; key_counter++; } err = 0; ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) { err = gpg_error_from_syserror (); goto out; } out: /* Send response. */ gcry_sexp_release (key_public); if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); if (!ret_err) ret_err = stream_write_uint32 (response, key_counter); if (!ret_err) ret_err = stream_copy (response, key_blobs); } else { ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); } es_fclose (key_blobs); close_control_file (cf); return ret_err; } /* This function hashes the data contained in DATA of size DATA_N according to the message digest algorithm specified by MD_ALGORITHM and writes the message digest to HASH, which needs to large enough for the digest. */ static gpg_error_t data_hash (unsigned char *data, size_t data_n, int md_algorithm, unsigned char *hash) { gcry_md_hash_buffer (md_algorithm, hash, data, data_n); return 0; } /* This function signs the data described by CTRL. If HASH is not NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to allow the use of signature algorithms that implement the hashing internally (e.g. Ed25519). On success the created signature is stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller must use es_free to releaase this memory. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, const void *hash, size_t hashlen, unsigned char **r_sig, size_t *r_siglen) { gpg_error_t err; gcry_sexp_t signature_sexp = NULL; estream_t stream = NULL; void *blob = NULL; size_t bloblen; char hexgrip[40+1]; *r_sig = NULL; *r_siglen = 0; /* Quick check to see whether we have a valid keygrip and convert it to hex. */ if (!ctrl->have_keygrip) { err = gpg_error (GPG_ERR_NO_SECKEY); goto out; } bin2hex (ctrl->keygrip, 20, hexgrip); /* Ask for confirmation if needed. */ if (confirm_flag_from_sshcontrol (hexgrip)) { gcry_sexp_t key; char *fpr, *prompt; char *comment = NULL; err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key); if (err) goto out; err = ssh_get_fingerprint_string (key, &fpr); if (!err) { gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0); if (tmpsxp) comment = gcry_sexp_nth_string (tmpsxp, 1); gcry_sexp_release (tmpsxp); } gcry_sexp_release (key); if (err) goto out; prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A" " %s%%0A" " (%s)%%0A" "Do you want to allow this?"), fpr, comment? comment:""); xfree (fpr); gcry_free (comment); err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0); xfree (prompt); if (err) goto out; } /* Create signature. */ ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, NULL, L_("Please enter the passphrase " "for the ssh key%%0A %F%%0A (%c)"), &signature_sexp, CACHE_MODE_SSH, ttl_from_sshcontrol, hash, hashlen); ctrl->use_auth_call = 0; if (err) goto out; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_cstring (stream, spec->ssh_identifier); if (err) goto out; err = spec->signature_encoder (spec, stream, signature_sexp); if (err) goto out; err = es_fclose_snatch (stream, &blob, &bloblen); if (err) goto out; stream = NULL; *r_sig = blob; blob = NULL; *r_siglen = bloblen; out: xfree (blob); es_fclose (stream); gcry_sexp_release (signature_sexp); return err; } /* Handler for the "sign_request" command. */ static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) { gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; unsigned char hash[MAX_DIGEST_LEN]; unsigned int hash_n; unsigned char key_grip[20]; unsigned char *key_blob = NULL; u32 key_blob_size; unsigned char *data = NULL; unsigned char *sig = NULL; size_t sig_n; u32 data_size; u32 flags; gpg_error_t err; gpg_error_t ret_err; int hash_algo; /* Receive key. */ err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec); if (err) goto out; /* Receive data to sign. */ err = stream_read_string (request, 0, &data, &data_size); if (err) goto out; /* FIXME? */ err = stream_read_uint32 (request, &flags); if (err) goto out; hash_algo = spec.hash_algo; if (!hash_algo) hash_algo = GCRY_MD_SHA1; /* Use the default. */ ctrl->digest.algo = hash_algo; if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) ctrl->digest.raw_value = 0; else ctrl->digest.raw_value = 1; /* Calculate key grip. */ err = ssh_key_grip (key, key_grip); if (err) goto out; ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); /* Hash data unless we use EdDSA. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { ctrl->digest.valuelen = 0; } else { hash_n = gcry_md_get_algo_dlen (hash_algo); if (!hash_n) { err = gpg_error (GPG_ERR_INTERNAL); goto out; } err = data_hash (data, data_size, hash_algo, hash); if (err) goto out; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; } /* Sign data. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n); else err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n); out: /* Done. */ if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); if (ret_err) goto leave; ret_err = stream_write_string (response, sig, sig_n); if (ret_err) goto leave; } else { log_error ("ssh sign request failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); if (ret_err) goto leave; } leave: gcry_sexp_release (key); xfree (key_blob); xfree (data); es_free (sig); return ret_err; } /* This function extracts the comment contained in the key s-expression KEY and stores a copy in COMMENT. Returns usual error code. */ static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **r_comment) { gcry_sexp_t comment_list; *r_comment = NULL; comment_list = gcry_sexp_find_token (key, "comment", 0); if (!comment_list) return gpg_error (GPG_ERR_INV_SEXP); *r_comment = gcry_sexp_nth_string (comment_list, 1); gcry_sexp_release (comment_list); if (!*r_comment) return gpg_error (GPG_ERR_INV_SEXP); return 0; } /* This function converts the key contained in the S-Expression KEY into a buffer, which is protected by the passphrase PASSPHRASE. Returns usual error code. */ static gpg_error_t ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, unsigned char **buffer, size_t *buffer_n) { unsigned char *buffer_new; unsigned int buffer_new_n; gpg_error_t err; buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0); buffer_new = xtrymalloc_secure (buffer_new_n); if (! buffer_new) { err = gpg_error_from_syserror (); goto out; } gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n); /* FIXME: guarantee? */ err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1); out: xfree (buffer_new); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return gpg_error (GPG_ERR_BAD_PASSPHRASE); } /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in our key storage, don't do anything. When entering a key also add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, gcry_sexp_t key, int ttl, int confirm) { gpg_error_t err; unsigned char key_grip_raw[20]; char key_grip[41]; unsigned char *buffer = NULL; size_t buffer_n; char *description = NULL; const char *description2 = L_("Please re-enter this passphrase"); char *comment = NULL; char *key_fpr = NULL; const char *initial_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; bin2hex (key_grip_raw, 20, key_grip); err = ssh_get_fingerprint_string (key, &key_fpr); if (err) goto out; /* Check whether the key is already in our key storage. Don't do anything then besides (re-)adding it to sshcontrol. */ if ( !agent_key_available (key_grip_raw) ) goto key_exists; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); if (err) goto out; if ( asprintf (&description, L_("Please enter a passphrase to protect" " the received secret key%%0A" " %s%%0A" " %s%%0A" "within gpg-agent's key storage"), key_fpr, comment ? comment : "") < 0) { err = gpg_error_from_syserror (); goto out; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { err = gpg_error_from_syserror (); goto out; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); goto out; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 1; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 1; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0); initial_errtext = NULL; if (err) goto out; /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = L_("does not match - try again"); goto next_try; } } err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; /* Store this key to our key storage. */ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); if (err) goto out; /* Cache this passphrase. */ err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl); if (err) goto out; key_exists: /* And add an entry to the sshcontrol file. */ err = add_control_entry (ctrl, spec, key_grip, key_fpr, ttl, confirm); out: if (pi2 && pi2->max_length) wipememory (pi2->pin, pi2->max_length); xfree (pi2); if (pi && pi->max_length) wipememory (pi->pin, pi->max_length); xfree (pi); xfree (buffer); xfree (comment); xfree (key_fpr); xfree (description); return err; } /* This function removes the key contained in the S-Expression KEY from the local key storage, in case it exists there. Returns usual error code. FIXME: this function is a stub. */ static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { unsigned char key_grip[21] = { 0 }; gpg_error_t err; err = ssh_key_grip (key, key_grip); if (err) goto out; key_grip[sizeof (key_grip) - 1] = 0; /* FIXME: What to do here - forgetting the passphrase or deleting the key from key cache? */ out: return err; } /* Handler for the "add_identity" command. */ static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; ssh_key_type_spec_t spec; gpg_error_t err; gcry_sexp_t key; unsigned char b; int confirm; int ttl; confirm = 0; key = NULL; ttl = 0; /* FIXME? */ err = ssh_receive_key (request, &key, 1, 1, &spec); if (err) goto out; while (1) { err = stream_read_byte (request, &b); if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; } switch (b) { case SSH_OPT_CONSTRAIN_LIFETIME: { u32 n = 0; err = stream_read_uint32 (request, &n); if (! err) ttl = n; break; } case SSH_OPT_CONSTRAIN_CONFIRM: { confirm = 1; break; } default: /* FIXME: log/bad? */ break; } } if (err) goto out; err = ssh_identity_register (ctrl, &spec, key, ttl, confirm); out: gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "remove_identity" command. */ static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response) { unsigned char *key_blob; u32 key_blob_size; gcry_sexp_t key; gpg_error_t ret_err; gpg_error_t err; (void)ctrl; /* Receive key. */ key_blob = NULL; key = NULL; err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL); if (err) goto out; err = ssh_identity_drop (key); out: xfree (key_blob); gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* FIXME: stub function. Actually useful? */ static gpg_error_t ssh_identities_remove_all (void) { gpg_error_t err; err = 0; /* FIXME: shall we remove _all_ cache entries or only those registered through the ssh-agent protocol? */ return err; } /* Handler for the "remove_all_identities" command. */ static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_identities_remove_all (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Lock agent? FIXME: stub function. */ static gpg_error_t ssh_lock (void) { gpg_error_t err; /* FIXME */ log_error ("ssh-agent's lock command is not implemented\n"); err = 0; return err; } /* Unock agent? FIXME: stub function. */ static gpg_error_t ssh_unlock (void) { gpg_error_t err; log_error ("ssh-agent's unlock command is not implemented\n"); err = 0; return err; } /* Handler for the "lock" command. */ static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_lock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "unlock" command. */ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_unlock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Return the request specification for the request identified by TYPE or NULL in case the requested request specification could not be found. */ static ssh_request_spec_t * request_spec_lookup (int type) { ssh_request_spec_t *spec; unsigned int i; for (i = 0; i < DIM (request_specs); i++) if (request_specs[i].type == type) break; if (i == DIM (request_specs)) { if (opt.verbose) log_info ("ssh request %u is not supported\n", type); spec = NULL; } else spec = request_specs + i; return spec; } /* Process a single request. The request is read from and the response is written to STREAM_SOCK. Uses CTRL as context. Returns zero in case of success, non zero in case of failure. */ static int ssh_request_process (ctrl_t ctrl, estream_t stream_sock) { ssh_request_spec_t *spec; estream_t response = NULL; estream_t request = NULL; unsigned char request_type; gpg_error_t err; int send_err = 0; int ret; unsigned char *request_data = NULL; u32 request_data_size; u32 response_size; /* Create memory streams for request/response data. The entire request will be stored in secure memory, since it might contain secret key material. The response does not have to be stored in secure memory, since we never give out secret keys. Note: we only have little secure memory, but there is NO possibility of DoS here; only trusted clients are allowed to connect to the agent. What could happen is that the agent returns out-of-secure-memory errors on requests in case the agent's owner floods his own agent with many large messages. -moritz */ /* Retrieve request. */ err = stream_read_string (stream_sock, 1, &request_data, &request_data_size); if (err) goto out; if (opt.verbose > 1) log_info ("received ssh request of length %u\n", (unsigned int)request_data_size); if (! request_data_size) { send_err = 1; goto out; /* Broken request; FIXME. */ } request_type = request_data[0]; spec = request_spec_lookup (request_type); if (! spec) { send_err = 1; goto out; /* Unknown request; FIXME. */ } if (spec->secret_input) request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b"); else request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b"); if (! request) { err = gpg_error_from_syserror (); goto out; } ret = es_setvbuf (request, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (request, request_data + 1, request_data_size - 1); if (err) goto out; es_rewind (request); response = es_fopenmem (0, "r+b"); if (! response) { err = gpg_error_from_syserror (); goto out; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request, response); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } if (err) { send_err = 1; goto out; } response_size = es_ftell (response); if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); err = es_fseek (response, 0, SEEK_SET); if (err) { send_err = 1; goto out; } err = stream_write_uint32 (stream_sock, response_size); if (err) { send_err = 1; goto out; } err = stream_copy (stream_sock, response); if (err) goto out; err = es_fflush (stream_sock); if (err) goto out; out: if (err && es_feof (stream_sock)) log_error ("error occurred while processing request: %s\n", gpg_strerror (err)); if (send_err) { if (opt.verbose > 1) log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE); if (err) goto leave; } leave: es_fclose (request); es_fclose (response); xfree (request_data); return !!err; } /* Return the peer's pid. */ static unsigned long get_client_pid (int fd) { pid_t client_pid = (pid_t)0; #ifdef SO_PEERCRED { #ifdef HAVE_STRUCT_SOCKPEERCRED_PID struct sockpeercred cr; #else struct ucred cr; #endif socklen_t cl = sizeof cr; if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)) { #if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) client_pid = cr.pid; #elif defined (HAVE_STRUCT_UCRED_CR_PID) client_pid = cr.cr_pid; #else #error "Unknown SO_PEERCRED struct" #endif } } #elif defined (LOCAL_PEERPID) { socklen_t len = sizeof (pid_t); getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len); } #elif defined (LOCAL_PEEREID) { struct unpcbid unp; socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) client_pid = unp.unp_pid; } #elif defined (HAVE_GETPEERUCRED) { ucred_t *ucred = NULL; if (getpeerucred (fd, &ucred) != -1) { client_pid= ucred_getpid (ucred); ucred_free (ucred); } } #else (void)fd; #endif return (unsigned long)client_pid; } /* Start serving client on SOCK_CLIENT. */ void start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) { estream_t stream_sock = NULL; gpg_error_t err; int ret; err = agent_copy_startup_env (ctrl); if (err) goto out; ctrl->client_pid = get_client_pid (FD2INT(sock_client)); /* Create stream from socket. */ stream_sock = es_fdopen (FD2INT(sock_client), "r+"); if (!stream_sock) { err = gpg_error_from_syserror (); log_error (_("failed to create stream from socket: %s\n"), gpg_strerror (err)); goto out; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ ret = es_setvbuf (stream_sock, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); log_error ("failed to disable buffering " "on socket stream: %s\n", gpg_strerror (err)); goto out; } /* Main processing loop. */ while ( !ssh_request_process (ctrl, stream_sock) ) { - /* Check wether we have reached EOF before trying to read + /* Check whether we have reached EOF before trying to read another request. */ int c; c = es_fgetc (stream_sock); if (c == EOF) break; es_ungetc (c, stream_sock); } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); out: if (stream_sock) es_fclose (stream_sock); } #ifdef HAVE_W32_SYSTEM /* Serve one ssh-agent request. This is used for the Putty support. REQUEST is the mmapped memory which may be accessed up to a length of MAXREQLEN. Returns 0 on success which also indicates that a valid SSH response message is now in REQUEST. */ int serve_mmapped_ssh_request (ctrl_t ctrl, unsigned char *request, size_t maxreqlen) { gpg_error_t err; int send_err = 0; int valid_response = 0; ssh_request_spec_t *spec; u32 msglen; estream_t request_stream, response_stream; if (agent_copy_startup_env (ctrl)) goto leave; /* Error setting up the environment. */ if (maxreqlen < 5) goto leave; /* Caller error. */ msglen = uint32_construct (request[0], request[1], request[2], request[3]); if (msglen < 1 || msglen > maxreqlen - 4) { log_error ("ssh message len (%u) out of range", (unsigned int)msglen); goto leave; } spec = request_spec_lookup (request[4]); if (!spec) { send_err = 1; /* Unknown request type. */ goto leave; } /* Create a stream object with the data part of the request. */ if (spec->secret_input) request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); else request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); if (!request_stream) { err = gpg_error_from_syserror (); goto leave; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ if (es_setvbuf (request_stream, NULL, _IONBF, 0)) { err = gpg_error_from_syserror (); goto leave; } /* Copy the request to the stream but omit the request type. */ err = stream_write_data (request_stream, request + 5, msglen - 1); if (err) goto leave; es_rewind (request_stream); response_stream = es_fopenmem (0, "r+b"); if (!response_stream) { err = gpg_error_from_syserror (); goto leave; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request_stream, response_stream); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } es_fclose (request_stream); request_stream = NULL; if (err) { send_err = 1; goto leave; } /* Put the response back into the mmapped buffer. */ { void *response_data; size_t response_size; /* NB: In contrast to the request-stream, the response stream includes the message type byte. */ if (es_fclose_snatch (response_stream, &response_data, &response_size)) { log_error ("snatching ssh response failed: %s", gpg_strerror (gpg_error_from_syserror ())); send_err = 1; /* Ooops. */ goto leave; } if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); if (response_size > maxreqlen - 4) { log_error ("invalid length of the ssh response: %s", gpg_strerror (GPG_ERR_INTERNAL)); es_free (response_data); send_err = 1; goto leave; } request[0] = response_size >> 24; request[1] = response_size >> 16; request[2] = response_size >> 8; request[3] = response_size >> 0; memcpy (request+4, response_data, response_size); es_free (response_data); valid_response = 1; } leave: if (send_err) { request[0] = 0; request[1] = 0; request[2] = 0; request[3] = 1; request[4] = SSH_RESPONSE_FAILURE; valid_response = 1; } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); return valid_response? 0 : -1; } #endif /*HAVE_W32_SYSTEM*/ diff --git a/agent/findkey.c b/agent/findkey.c index f3c8ca986..b24d8f181 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,1591 +1,1591 @@ /* findkey.c - Locate the secret key * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, * 2010, 2011 Free Software Foundation, Inc. * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include /* (we use pth_sleep) */ #include "agent.h" #include "../common/i18n.h" #include "../common/ssh-utils.h" #include "../common/name-value.h" #ifndef O_BINARY #define O_BINARY 0 #endif /* Helper to pass data to the check callback of the unprotect function. */ struct try_unprotect_arg_s { ctrl_t ctrl; const unsigned char *protected_key; unsigned char *unprotected_key; int change_required; /* Set by the callback to indicate that the user should change the passphrase. */ }; /* Note: Ownership of FNAME and FP are moved to this function. */ static gpg_error_t write_extended_private_key (char *fname, estream_t fp, int update, const void *buf, size_t len) { gpg_error_t err; nvc_t pk = NULL; gcry_sexp_t key = NULL; int remove = 0; if (update) { int line; err = nvc_parse_private_key (&pk, &line, fp); if (err && gpg_err_code (err) != GPG_ERR_ENOENT) { log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (err)); goto leave; } } else { pk = nvc_new_private_key (); if (!pk) { err = gpg_error_from_syserror (); goto leave; } } es_clearerr (fp); err = gcry_sexp_sscan (&key, NULL, buf, len); if (err) goto leave; err = nvc_set_private_key (pk, key); if (err) goto leave; err = es_fseek (fp, 0, SEEK_SET); if (err) goto leave; err = nvc_write (pk, fp); if (err) { log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (ftruncate (es_fileno (fp), es_ftello (fp))) { err = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } else fp = NULL; bump_key_eventcounter (); leave: es_fclose (fp); if (remove) gnupg_remove (fname); xfree (fname); gcry_sexp_release (key); nvc_release (pk); return err; } /* Write an S-expression formatted key to our key storage. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) { char *fname; estream_t fp; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); /* FIXME: Write to a temp file first so that write failures during key updates won't lead to a key loss. */ if (!force && !access (fname, F_OK)) { log_error ("secret key file '%s' already exists\n", fname); xfree (fname); return gpg_error (GPG_ERR_EEXIST); } fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); if (!fp) { gpg_error_t tmperr = gpg_error_from_syserror (); if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) { fp = es_fopen (fname, "wbx,mode=-rw"); if (!fp) tmperr = gpg_error_from_syserror (); } if (!fp) { log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); xfree (fname); return tmperr; } } else if (force) { gpg_error_t rc; char first; /* See if an existing key is in extended format. */ if (es_fread (&first, 1, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } rc = es_fseek (fp, 0, SEEK_SET); if (rc) { log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } if (first != '(') { /* Key is already in the extended format. */ return write_extended_private_key (fname, fp, 1, buffer, length); } if (first == '(' && opt.enable_extended_key_format) { /* Key is in the old format - but we want the extended format. */ return write_extended_private_key (fname, fp, 0, buffer, length); } } if (opt.enable_extended_key_format) return write_extended_private_key (fname, fp, 0, buffer, length); if (es_fwrite (buffer, length, 1, fp) != 1) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } /* When force is given, the file might have to be truncated. */ if (force && ftruncate (es_fileno (fp), es_ftello (fp))) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } if (es_fclose (fp)) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr)); gnupg_remove (fname); xfree (fname); return tmperr; } bump_key_eventcounter (); xfree (fname); return 0; } /* Callback function to try the unprotection from the passphrase query code. */ static gpg_error_t try_unprotect_cb (struct pin_entry_info_s *pi) { struct try_unprotect_arg_s *arg = pi->check_cb_arg; ctrl_t ctrl = arg->ctrl; size_t dummy; gpg_error_t err; gnupg_isotime_t now, protected_at, tmptime; char *desc = NULL; assert (!arg->unprotected_key); arg->change_required = 0; err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at, &arg->unprotected_key, &dummy); if (err) return err; if (!opt.max_passphrase_days || ctrl->in_passwd) return 0; /* No regular passphrase change required. */ if (!*protected_at) { /* No protection date known - must force passphrase change. */ desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A" "Please change it now.")); if (!desc) return gpg_error_from_syserror (); } else { gnupg_get_isotime (now); gnupg_copy_time (tmptime, protected_at); err = add_days_to_isotime (tmptime, opt.max_passphrase_days); if (err) return err; if (strcmp (now, tmptime) > 0 ) { /* Passphrase "expired". */ desc = xtryasprintf (L_("This passphrase has not been changed%%0A" "since %.4s-%.2s-%.2s. Please change it now."), protected_at, protected_at+4, protected_at+6); if (!desc) return gpg_error_from_syserror (); } } if (desc) { /* Change required. */ if (opt.enforce_passphrase_constraints) { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), NULL, 0); if (!err) arg->change_required = 1; } else { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), L_("I'll change it later"), 0); if (!err) arg->change_required = 1; else if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) err = 0; } xfree (desc); } return err; } /* Modify a Key description, replacing certain special format characters. List of currently supported replacements: %% - Replaced by a single % %c - Replaced by the content of COMMENT. %C - Same as %c but put into parentheses. %F - Replaced by an ssh style fingerprint computed from KEY. The functions returns 0 on success or an error code. On success a newly allocated string is stored at the address of RESULT. */ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result) { size_t comment_length; size_t in_len; size_t out_len; char *out; size_t i; int special, pass; char *ssh_fpr = NULL; char *p; *result = NULL; if (!comment) comment = ""; comment_length = strlen (comment); in_len = strlen (in); /* First pass calculates the length, second pass does the actual copying. */ /* FIXME: This can be simplified by using es_fopenmem. */ out = NULL; out_len = 0; for (pass=0; pass < 2; pass++) { special = 0; for (i = 0; i < in_len; i++) { if (special) { special = 0; switch (in[i]) { case '%': if (out) *out++ = '%'; else out_len++; break; case 'c': /* Comment. */ if (out) { memcpy (out, comment, comment_length); out += comment_length; } else out_len += comment_length; break; case 'C': /* Comment. */ if (!comment_length) ; else if (out) { *out++ = '('; memcpy (out, comment, comment_length); out += comment_length; *out++ = ')'; } else out_len += comment_length + 2; break; case 'F': /* SSH style fingerprint. */ if (!ssh_fpr && key) ssh_get_fingerprint_string (key, &ssh_fpr); if (ssh_fpr) { if (out) out = stpcpy (out, ssh_fpr); else out_len += strlen (ssh_fpr); } break; default: /* Invalid special sequences are kept as they are. */ if (out) { *out++ = '%'; *out++ = in[i]; } else out_len+=2; break; } } else if (in[i] == '%') special = 1; else { if (out) *out++ = in[i]; else out_len++; } } if (!pass) { *result = out = xtrymalloc (out_len + 1); if (!out) { xfree (ssh_fpr); return gpg_error_from_syserror (); } } } *out = 0; log_assert (*result + out_len == out); xfree (ssh_fpr); /* The ssh prompt may sometimes end in * "...%0A ()" * The empty parentheses doesn't look very good. We use this hack * here to remove them as well as the indentation spaces. */ p = *result; i = strlen (p); if (i > 2 && !strcmp (p + i - 2, "()")) { p += i - 2; *p-- = 0; while (p > *result && spacep (p)) *p-- = 0; } return 0; } /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. If LOOKUP_TTL is given this function is used to lookup the default ttl. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ static int unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, char **r_passphrase) { struct pin_entry_info_s *pi; struct try_unprotect_arg_s arg; int rc; unsigned char *result; size_t resultlen; char hexgrip[40+1]; if (r_passphrase) *r_passphrase = NULL; bin2hex (grip, 20, hexgrip); /* Initially try to get it using a cache nonce. */ if (cache_nonce) { char *pw; pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } } /* First try to get it from the cache - if there is none or we can't unprotect it, we fall back to ask the user */ if (cache_mode != CACHE_MODE_IGNORE) { char *pw; retry: pw = agent_get_cache (hexgrip, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (cache_mode == CACHE_MODE_NORMAL) agent_store_cache_hit (hexgrip); if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } else if (cache_mode == CACHE_MODE_NORMAL) { /* The standard use of GPG keys is to have a signing and an encryption subkey. Commonly both use the same passphrase. We try to help the user to enter the passphrase only once by silently trying the last correctly entered passphrase. Checking one additional passphrase should be acceptable; despite the S2K introduced delays. The assumed workflow is: 1. Read encrypted message in a MUA and thus enter a passphrase for the encryption subkey. 2. Reply to that mail with an encrypted and signed mail, thus entering the passphrase for the signing subkey. We can often avoid the passphrase entry in the second step. We do this only in normal mode, so not to interfere with unrelated cache entries. */ pw = agent_get_cache (NULL, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } } /* If the pinentry is currently in use, we wait up to 60 seconds for it to close and check the cache again. This solves a common situation where several requests for unprotecting a key have been made but the user is still entering the passphrase for the first request. Because all requests to agent_askpin are serialized they would then pop up one after the other to request the passphrase - despite that the user has already entered it and is then available in the cache. This implementation is not race free but in the worst case the user has to enter the passphrase only once more. */ if (pinentry_active_p (ctrl, 0)) { /* Active - wait */ if (!pinentry_active_p (ctrl, 60)) { /* We need to give the other thread a chance to actually put it into the cache. */ npth_sleep (1); goto retry; } /* Timeout - better call pinentry now the plain way. */ } } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 16; pi->max_tries = 3; pi->check_cb = try_unprotect_cb; arg.ctrl = ctrl; arg.protected_key = *keybuf; arg.unprotected_key = NULL; arg.change_required = 0; pi->check_cb_arg = &arg; rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode); if (!rc) { assert (arg.unprotected_key); if (arg.change_required) { /* The callback told as that the user should change their passphrase. Present the dialog to do. */ size_t canlen, erroff; gcry_sexp_t s_skey; assert (arg.unprotected_key); canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)arg.unprotected_key, canlen); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } rc = agent_protect_and_store (ctrl, s_skey, NULL); gcry_sexp_release (s_skey); if (rc) { log_error ("changing the passphrase failed: %s\n", gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } } else { /* Passphrase is fine. */ agent_put_cache (hexgrip, cache_mode, pi->pin, lookup_ttl? lookup_ttl (hexgrip) : 0); agent_store_cache_hit (hexgrip); if (r_passphrase && *pi->pin) *r_passphrase = xtrystrdup (pi->pin); } xfree (*keybuf); *keybuf = arg.unprotected_key; } xfree (pi); return rc; } /* Read the key identified by GRIP from the private key directory and return it as an gcrypt S-expression object in RESULT. On failure returns an error code and stores NULL at RESULT. */ static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result) { int rc; char *fname; estream_t fp; struct stat st; unsigned char *buf; size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; char first; *result = NULL; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); fp = es_fopen (fname, "rb"); if (!fp) { rc = gpg_error_from_syserror (); if (gpg_err_code (rc) != GPG_ERR_ENOENT) log_error ("can't open '%s': %s\n", fname, strerror (errno)); xfree (fname); return rc; } if (es_fread (&first, 1, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } rc = es_fseek (fp, 0, SEEK_SET); if (rc) { log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } if (first != '(') { /* Key is in extended format. */ nvc_t pk; int line; rc = nvc_parse_private_key (&pk, &line, fp); es_fclose (fp); if (rc) log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (rc)); else { rc = nvc_get_private_key (pk, result); nvc_release (pk); if (rc) log_error ("error getting private key from '%s': %s\n", fname, gpg_strerror (rc)); } xfree (fname); return rc; } if (fstat (es_fileno (fp), &st)) { rc = gpg_error_from_syserror (); log_error ("can't stat '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) { rc = gpg_error_from_syserror (); log_error ("error allocating %zu bytes for '%s': %s\n", buflen, fname, strerror (errno)); xfree (fname); es_fclose (fp); xfree (buf); return rc; } if (es_fread (buf, buflen, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading %zu bytes from '%s': %s\n", buflen, fname, strerror (errno)); xfree (fname); es_fclose (fp); xfree (buf); return rc; } /* Convert the file into a gcrypt S-expression object. */ rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); xfree (fname); es_fclose (fp); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return rc; } *result = s_skey; return 0; } /* Remove the key identified by GRIP from the private key directory. */ static gpg_error_t remove_key_file (const unsigned char *grip) { gpg_error_t err = 0; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); if (gnupg_remove (fname)) err = gpg_error_from_syserror (); xfree (fname); return err; } /* Return the secret key as an S-Exp in RESULT after locating it using the GRIP. If the operation shall be diverted to a token, an allocated S-expression with the shadow_info part from the file is stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO. CACHE_MODE defines now the cache shall be used. DESC_TEXT may be set to present a custom description for the pinentry. LOOKUP_TTL is an optional function to convey a TTL to the cache manager; we do not simply pass the TTL value because the value is only needed if an unprotect action was needed and looking up the TTL may have some overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is given that cache item is first tried to get a passphrase. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, gcry_sexp_t *result, char **r_passphrase) { int rc; unsigned char *buf; size_t len, buflen, erroff; gcry_sexp_t s_skey; *result = NULL; if (shadow_info) *shadow_info = NULL; if (r_passphrase) *r_passphrase = NULL; rc = read_key_file (grip, &s_skey); if (rc) { if (gpg_err_code (rc) == GPG_ERR_ENOENT) rc = gpg_error (GPG_ERR_NO_SECKEY); return rc; } /* For use with the protection functions we also need the key as an canonical encoded S-expression in a buffer. Create this buffer now. */ rc = make_canon_sexp (s_skey, &buf, &len); if (rc) return rc; switch (agent_private_key_type (buf)) { case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_OPENPGP_NONE: { unsigned char *buf_new; size_t buf_newlen; rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen); if (rc) log_error ("failed to convert unprotected openpgp key: %s\n", gpg_strerror (rc)); else { xfree (buf); buf = buf_new; } } break; case PRIVATE_KEY_PROTECTED: { char *desc_text_final; char *comment = NULL; /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); } desc_text_final = NULL; if (desc_text) rc = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); gcry_free (comment); if (!rc) { rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip, cache_mode, lookup_ttl, r_passphrase); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); } xfree (desc_text_final); } break; case PRIVATE_KEY_SHADOWED: if (shadow_info) { const unsigned char *s; size_t n; rc = agent_get_shadow_info (buf, &s); if (!rc) { n = gcry_sexp_canon_len (s, 0, NULL,NULL); assert (n); *shadow_info = xtrymalloc (n); if (!*shadow_info) rc = out_of_core (); else { memcpy (*shadow_info, s, n); rc = 0; } } if (rc) log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); } else rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: log_error ("invalid private key format\n"); rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } gcry_sexp_release (s_skey); s_skey = NULL; if (rc) { xfree (buf); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } return rc; } buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); wipememory (buf, buflen); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } return rc; } *result = s_skey; return 0; } /* Return the string name from the S-expression S_KEY as well as a string describing the names of the parameters. ALGONAMESIZE and ELEMSSIZE give the allocated size of the provided buffers. The buffers may be NULL if not required. If R_LIST is not NULL the top level list will be stored there; the caller needs to release it in this case. */ static gpg_error_t key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list, char *r_algoname, size_t algonamesize, char *r_elems, size_t elemssize) { gcry_sexp_t list, l2; const char *name, *algoname, *elems; size_t n; if (r_list) *r_list = NULL; list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "protected-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "private-key", 0 ); if (!list) { log_error ("invalid private key format\n"); return gpg_error (GPG_ERR_BAD_SECKEY); } l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; name = gcry_sexp_nth_data (list, 0, &n); if (n==3 && !memcmp (name, "rsa", 3)) { algoname = "rsa"; elems = "ne"; } else if (n==3 && !memcmp (name, "dsa", 3)) { algoname = "dsa"; elems = "pqgy"; } else if (n==3 && !memcmp (name, "ecc", 3)) { algoname = "ecc"; elems = "pabgnq"; } else if (n==5 && !memcmp (name, "ecdsa", 5)) { algoname = "ecdsa"; elems = "pabgnq"; } else if (n==4 && !memcmp (name, "ecdh", 4)) { algoname = "ecdh"; elems = "pabgnq"; } else if (n==3 && !memcmp (name, "elg", 3)) { algoname = "elg"; elems = "pgy"; } else { log_error ("unknown private key algorithm\n"); gcry_sexp_release (list); return gpg_error (GPG_ERR_BAD_SECKEY); } if (r_algoname) { if (strlen (algoname) >= algonamesize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_algoname, algoname); } if (r_elems) { if (strlen (elems) >= elemssize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_elems, elems); } if (r_list) *r_list = list; else gcry_sexp_release (list); return 0; } /* Return true if KEYPARMS holds an EdDSA key. */ static int is_eddsa (gcry_sexp_t keyparms) { int result = 0; gcry_sexp_t list; const char *s; size_t n; int i; list = gcry_sexp_find_token (keyparms, "flags", 0); for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--) { s = gcry_sexp_nth_data (list, i, &n); if (!s) continue; /* Not a data element. */ if (n == 5 && !memcmp (s, "eddsa", 5)) { result = 1; break; } } gcry_sexp_release (list); return result; } /* Return the public key algorithm number if S_KEY is a DSA style key. If it is not a DSA style key, return 0. */ int agent_is_dsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an DSA key. */ if (!strcmp (algoname, "dsa")) result = GCRY_PK_DSA; else if (!strcmp (algoname, "ecc")) { if (is_eddsa (list)) result = 0; else result = GCRY_PK_ECDSA; } else if (!strcmp (algoname, "ecdsa")) result = GCRY_PK_ECDSA; else result = 0; gcry_sexp_release (list); return result; } /* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */ int agent_is_eddsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an EdDSA key. */ if (!strcmp (algoname, "ecc") && is_eddsa (list)) result = 1; else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */ result = 1; else result = 0; gcry_sexp_release (list); return result; } /* Return the key for the keygrip GRIP. The result is stored at RESULT. This function extracts the key from the private key database and returns it as an S-expression object as it is. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; gcry_sexp_t s_skey; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey); if (!err) *result = s_skey; return err; } /* Return the public key for the keygrip GRIP. The result is stored at RESULT. This function extracts the public key from the private key database. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; int i, idx; gcry_sexp_t s_skey; const char *algoname, *elems; int npkey; gcry_mpi_t array[10]; gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; gcry_sexp_t uri_sexp, comment_sexp; const char *uri, *comment; size_t uri_length, comment_length; char *format, *p; void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2 for comment + end-of-list. */ int argidx; gcry_sexp_t list = NULL; const char *s; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey); if (err) return err; for (i=0; i < DIM (array); i++) array[i] = NULL; err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems, array, DIM (array), &curve, &flags); if (err) { gcry_sexp_release (s_skey); return err; } uri = NULL; uri_length = 0; uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0); if (uri_sexp) uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length); comment = NULL; comment_length = 0; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length); gcry_sexp_release (s_skey); s_skey = NULL; /* FIXME: The following thing is pretty ugly code; we should investigate how to make it cleaner. Probably code to handle canonical S-expressions in a memory buffer is better suited for - such a task. After all that is what we do in protect.c. Neeed + such a task. After all that is what we do in protect.c. Need to find common patterns and write a straightformward API to use them. */ assert (sizeof (size_t) <= sizeof (void*)); format = xtrymalloc (15+4+7*npkey+10+15+1+1); if (!format) { err = gpg_error_from_syserror (); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); return err; } argidx = 0; p = stpcpy (stpcpy (format, "(public-key("), algoname); p = stpcpy (p, "%S%S"); /* curve name and flags. */ args[argidx++] = &curve; args[argidx++] = &flags; for (idx=0, s=elems; idx < npkey; idx++) { *p++ = '('; *p++ = *s++; p = stpcpy (p, " %m)"); assert (argidx < DIM (args)); args[argidx++] = &array[idx]; } *p++ = ')'; if (uri) { p = stpcpy (p, "(uri %b)"); assert (argidx+1 < DIM (args)); args[argidx++] = (void *)&uri_length; args[argidx++] = (void *)&uri; } if (comment) { p = stpcpy (p, "(comment %b)"); assert (argidx+1 < DIM (args)); args[argidx++] = (void *)&comment_length; args[argidx++] = (void*)&comment; } *p++ = ')'; *p = 0; assert (argidx < DIM (args)); args[argidx] = NULL; err = gcry_sexp_build_array (&list, NULL, format, args); xfree (format); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); if (!err) *result = list; return err; } /* Check whether the secret key identified by GRIP is available. Returns 0 is the key is available. */ int agent_key_available (const unsigned char *grip) { int result; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); result = !access (fname, R_OK)? 0 : -1; xfree (fname); return result; } /* Return the information about the secret key specified by the binary keygrip GRIP. If the key is a shadowed one the shadow information will be stored at the address R_SHADOW_INFO as an allocated S-expression. */ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info) { gpg_error_t err; unsigned char *buf; size_t len; int keytype; (void)ctrl; if (r_keytype) *r_keytype = PRIVATE_KEY_UNKNOWN; if (r_shadow_info) *r_shadow_info = NULL; { gcry_sexp_t sexp; err = read_key_file (grip, &sexp); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) return gpg_error (GPG_ERR_NOT_FOUND); else return err; } err = make_canon_sexp (sexp, &buf, &len); gcry_sexp_release (sexp); if (err) return err; } keytype = agent_private_key_type (buf); switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: break; case PRIVATE_KEY_PROTECTED: /* If we ever require it we could retrieve the comment fields from such a key. */ break; case PRIVATE_KEY_SHADOWED: if (r_shadow_info) { const unsigned char *s; size_t n; err = agent_get_shadow_info (buf, &s); if (!err) { n = gcry_sexp_canon_len (s, 0, NULL, NULL); assert (n); *r_shadow_info = xtrymalloc (n); if (!*r_shadow_info) err = gpg_error_from_syserror (); else memcpy (*r_shadow_info, s, n); } } break; default: err = gpg_error (GPG_ERR_BAD_SECKEY); break; } if (!err && r_keytype) *r_keytype = keytype; xfree (buf); return err; } /* Delete the key with GRIP from the disk after having asked for * confirmation using DESC_TEXT. If FORCE is set the function won't * require a confirmation via Pinentry or warns if the key is also * used by ssh. If ONLY_STUBS is set only stub keys (references to * smartcards) will be affected. * * Common error codes are: * GPG_ERR_NO_SECKEY * GPG_ERR_KEY_ON_CARD * GPG_ERR_NOT_CONFIRMED * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs) { gpg_error_t err; gcry_sexp_t s_skey = NULL; unsigned char *buf = NULL; size_t len; char *desc_text_final = NULL; char *comment = NULL; ssh_control_file_t cf = NULL; char hexgrip[40+4+1]; char *default_desc = NULL; int key_type; err = read_key_file (grip, &s_skey); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); if (err) goto leave; err = make_canon_sexp (s_skey, &buf, &len); if (err) goto leave; key_type = agent_private_key_type (buf); if (only_stubs && key_type != PRIVATE_KEY_SHADOWED) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } switch (key_type) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: case PRIVATE_KEY_PROTECTED: bin2hex (grip, 20, hexgrip); if (!force) { if (!desc_text) { default_desc = xtryasprintf (L_("Do you really want to delete the key identified by keygrip%%0A" " %s%%0A %%C%%0A?"), hexgrip); desc_text = default_desc; } /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); } if (desc_text) err = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); if (err) goto leave; err = agent_get_confirmation (ctrl, desc_text_final, L_("Delete key"), L_("No"), 0); if (err) goto leave; cf = ssh_open_control_file (); if (cf) { if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL)) { err = agent_get_confirmation (ctrl, L_("Warning: This key is also listed for use with SSH!\n" "Deleting the key might remove your ability to " "access remote machines."), L_("Delete key"), L_("No"), 0); if (err) goto leave; } } } err = remove_key_file (grip); break; case PRIVATE_KEY_SHADOWED: err = remove_key_file (grip); break; default: log_error ("invalid private key format\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); break; } leave: ssh_close_control_file (cf); gcry_free (comment); xfree (desc_text_final); xfree (default_desc); xfree (buf); gcry_sexp_release (s_skey); return err; } /* Write an S-expression formatted shadow key to our key storage. Shadow key is created by an S-expression public key in PKBUF and card's SERIALNO and the IDSTRING. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force) { gpg_error_t err; unsigned char *shadow_info; unsigned char *shdkey; size_t len; shadow_info = make_shadow_info (serialno, keyid); if (!shadow_info) return gpg_error_from_syserror (); err = agent_shadow_key (pkbuf, shadow_info, &shdkey); xfree (shadow_info); if (err) { log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); return err; } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); err = agent_write_private_key (grip, shdkey, len, force); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); return err; } diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index f1023b433..46697bae1 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -1,146 +1,146 @@ -/* pkdecrypt.c - public key decryption (well, acually using a secret key) +/* pkdecrypt.c - public key decryption (well, actually using a secret key) * Copyright (C) 2001, 2003 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 #include #include "agent.h" /* DECRYPT the stuff in ciphertext which is expected to be a S-Exp. Try to get the key from CTRL and write the decoded stuff back to OUTFP. The padding information is stored at R_PADDING with -1 for not known. */ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf, int *r_padding) { gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; unsigned char *shadow_info = NULL; int rc; char *buf = NULL; size_t len; *r_padding = -1; if (!ctrl->have_keygrip) { log_error ("speculative decryption not yet supported\n"); rc = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } rc = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); if (rc) { log_error ("failed to convert ciphertext: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_DATA); goto leave; } if (DBG_CRYPTO) { log_printhex ("keygrip:", ctrl->keygrip, 20); log_printhex ("cipher: ", ciphertext, ciphertextlen); } rc = agent_key_from_file (ctrl, NULL, desc_text, ctrl->keygrip, &shadow_info, CACHE_MODE_NORMAL, NULL, &s_skey, NULL); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY) log_error ("failed to read the secret key\n"); goto leave; } if (shadow_info) { /* divert operation to the smartcard */ if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) { rc = gpg_error (GPG_ERR_INV_SEXP); goto leave; } rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, &buf, &len, r_padding); if (rc) { log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); goto leave; } put_membuf_printf (outbuf, "(5:value%u:", (unsigned int)len); put_membuf (outbuf, buf, len); put_membuf (outbuf, ")", 2); } else { /* No smartcard, but a private key */ /* if (DBG_CRYPTO ) */ /* { */ /* log_debug ("skey: "); */ /* gcry_sexp_dump (s_skey); */ /* } */ rc = gcry_pk_decrypt (&s_plain, s_cipher, s_skey); if (rc) { log_error ("decryption failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) { log_debug ("plain: "); gcry_sexp_dump (s_plain); } len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xmalloc (len); len = gcry_sexp_sprint (s_plain, GCRYSEXP_FMT_CANON, buf, len); assert (len); if (*buf == '(') put_membuf (outbuf, buf, len); else { /* Old style libgcrypt: This is only an S-expression part. Turn it into a complete S-expression. */ put_membuf (outbuf, "(5:value", 8); put_membuf (outbuf, buf, len); put_membuf (outbuf, ")", 2); } } leave: gcry_sexp_release (s_skey); gcry_sexp_release (s_plain); gcry_sexp_release (s_cipher); xfree (buf); xfree (shadow_info); return rc; } diff --git a/agent/pksign.c b/agent/pksign.c index f0b10e6e0..8faf4a483 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -1,566 +1,566 @@ /* pksign.c - public key signing (well, actually using a secret key) * Copyright (C) 2001-2004, 2010 Free Software Foundation, Inc. * Copyright (C) 2001-2004, 2010, 2013 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 . */ #include #include #include #include #include #include #include #include #include #include "agent.h" #include "../common/i18n.h" static int do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, int raw_value) { gcry_sexp_t hash; int rc; if (!raw_value) { const char *s; char tmp[16+1]; int i; s = gcry_md_algo_name (algo); if (s && strlen (s) < 16) { for (i=0; i < strlen (s); i++) tmp[i] = tolower (s[i]); tmp[i] = '\0'; } rc = gcry_sexp_build (&hash, NULL, "(data (flags pkcs1) (hash %s %b))", tmp, (int)mdlen, md); } else { gcry_mpi_t mpi; rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL); if (!rc) { rc = gcry_sexp_build (&hash, NULL, "(data (flags raw) (value %m))", mpi); gcry_mpi_release (mpi); } else hash = NULL; } *r_hash = hash; return rc; } /* Return the number of bits of the Q parameter from the DSA key KEY. */ static unsigned int get_dsa_qbits (gcry_sexp_t key) { gcry_sexp_t l1, l2; gcry_mpi_t q; unsigned int nbits; l1 = gcry_sexp_find_token (key, "private-key", 0); if (!l1) l1 = gcry_sexp_find_token (key, "protected-private-key", 0); if (!l1) l1 = gcry_sexp_find_token (key, "shadowed-private-key", 0); if (!l1) l1 = gcry_sexp_find_token (key, "public-key", 0); if (!l1) return 0; /* Does not contain a key object. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); l1 = gcry_sexp_find_token (l2, "q", 1); gcry_sexp_release (l2); if (!l1) return 0; /* Invalid object. */ q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG); gcry_sexp_release (l1); if (!q) return 0; /* Missing value. */ nbits = gcry_mpi_get_nbits (q); gcry_mpi_release (q); return nbits; } /* Return an appropriate hash algorithm to be used with RFC-6979 for a message digest of length MDLEN. Although a fallback of SHA-256 is used the current implementation in Libgcrypt will reject a hash algorithm which does not match the length of the message. */ static const char * rfc6979_hash_algo_string (size_t mdlen) { switch (mdlen) { case 20: return "sha1"; case 28: return "sha224"; case 32: return "sha256"; case 48: return "sha384"; case 64: return "sha512"; default: return "sha256"; } } /* Encode a message digest for use with the EdDSA algorithm (i.e. curve Ed25519). */ static gpg_error_t do_encode_eddsa (const byte *md, size_t mdlen, gcry_sexp_t *r_hash) { gpg_error_t err; gcry_sexp_t hash; *r_hash = NULL; err = gcry_sexp_build (&hash, NULL, "(data(flags eddsa)(hash-algo sha512)(value %b))", (int)mdlen, md); if (!err) *r_hash = hash; return err; } /* Encode a message digest for use with an DSA algorithm. */ static gpg_error_t do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey, gcry_sexp_t *r_hash) { gpg_error_t err; gcry_sexp_t hash; unsigned int qbits; *r_hash = NULL; if (pkalgo == GCRY_PK_ECDSA) qbits = gcry_pk_get_nbits (pkey); else if (pkalgo == GCRY_PK_DSA) qbits = get_dsa_qbits (pkey); else return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); if (pkalgo == GCRY_PK_DSA && (qbits%8)) { /* FIXME: We check the QBITS but print a message about the hash length. */ log_error (_("DSA requires the hash length to be a" " multiple of 8 bits\n")); return gpg_error (GPG_ERR_INV_LENGTH); } /* Don't allow any Q smaller than 160 bits. We don't want someone to issue signatures from a key with a 16-bit Q or something like that, which would look correct but allow trivial forgeries. Yes, I know this rules out using MD5 with DSA. ;) */ if (qbits < 160) { log_error (_("%s key uses an unsafe (%u bit) hash\n"), gcry_pk_algo_name (pkalgo), qbits); return gpg_error (GPG_ERR_INV_LENGTH); } /* ECDSA 521 is special has it is larger than the largest hash - we have (SHA-512). Thus we chnage the size for further + we have (SHA-512). Thus we change the size for further processing to 512. */ if (pkalgo == GCRY_PK_ECDSA && qbits > 512) qbits = 512; /* Check if we're too short. Too long is safe as we'll automatically left-truncate. */ if (mdlen < qbits/8) { log_error (_("a %zu bit hash is not valid for a %u bit %s key\n"), mdlen*8, gcry_pk_get_nbits (pkey), gcry_pk_algo_name (pkalgo)); return gpg_error (GPG_ERR_INV_LENGTH); } /* Truncate. */ if (mdlen > qbits/8) mdlen = qbits/8; /* Create the S-expression. */ err = gcry_sexp_build (&hash, NULL, "(data (flags rfc6979) (hash %s %b))", rfc6979_hash_algo_string (mdlen), (int)mdlen, md); if (!err) *r_hash = hash; return err; } /* Special version of do_encode_md to take care of pkcs#1 padding. For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does not know about this special scheme. Fixme: We should have a pkcs1-only-padding flag for Libgcrypt. */ static int do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits, gcry_sexp_t *r_hash) { int rc; gcry_sexp_t hash; unsigned char *frame; size_t i, n, nframe; nframe = (nbits+7) / 8; if ( !mdlen || mdlen + 8 + 4 > nframe ) { /* Can't encode this hash into a frame of size NFRAME. */ return gpg_error (GPG_ERR_TOO_SHORT); } frame = xtrymalloc (nframe); if (!frame) return gpg_error_from_syserror (); /* Assemble the pkcs#1 block type 1. */ n = 0; frame[n++] = 0; frame[n++] = 1; /* Block type. */ i = nframe - mdlen - 3 ; assert (i >= 8); /* At least 8 bytes of padding. */ memset (frame+n, 0xff, i ); n += i; frame[n++] = 0; memcpy (frame+n, md, mdlen ); n += mdlen; assert (n == nframe); /* Create the S-expression. */ rc = gcry_sexp_build (&hash, NULL, "(data (flags raw) (value %b))", (int)nframe, frame); xfree (frame); *r_hash = hash; return rc; } /* SIGN whatever information we have accumulated in CTRL and return the signature S-expression. LOOKUP is an optional function to provide a way for lower layers to ask for the caching TTL. If a CACHE_NONCE is given that cache item is first tried to get a passphrase. If OVERRIDEDATA is not NULL, OVERRIDEDATALEN bytes from this buffer are used instead of the data in CTRL. The override feature is required to allow the use of Ed25519 with ssh because Ed25519 does the hashing itself. */ int agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, gcry_sexp_t *signature_sexp, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, const void *overridedata, size_t overridedatalen) { gcry_sexp_t s_skey = NULL; gcry_sexp_t s_sig = NULL; gcry_sexp_t s_hash = NULL; gcry_sexp_t s_pkey = NULL; unsigned char *shadow_info = NULL; unsigned int rc = 0; /* FIXME: gpg-error? */ const unsigned char *data; int datalen; int check_signature = 0; if (overridedata) { data = overridedata; datalen = overridedatalen; } else { data = ctrl->digest.value; datalen = ctrl->digest.valuelen; } if (!ctrl->have_keygrip) return gpg_error (GPG_ERR_NO_SECKEY); rc = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip, &shadow_info, cache_mode, lookup_ttl, &s_skey, NULL); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NO_SECKEY) log_error ("failed to read the secret key\n"); goto leave; } if (shadow_info) { /* Divert operation to the smartcard */ size_t len; unsigned char *buf = NULL; int key_type; int is_RSA = 0; int is_ECDSA = 0; int is_EdDSA = 0; rc = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey); if (rc) { log_error ("failed to read the public key\n"); goto leave; } if (agent_is_eddsa_key (s_skey)) is_EdDSA = 1; else { key_type = agent_is_dsa_key (s_skey); if (key_type == 0) is_RSA = 1; else if (key_type == GCRY_PK_ECDSA) is_ECDSA = 1; } { char *desc2 = NULL; if (desc_text) agent_modify_description (desc_text, NULL, s_skey, &desc2); rc = divert_pksign (ctrl, desc2? desc2 : desc_text, data, datalen, ctrl->digest.algo, shadow_info, &buf, &len); xfree (desc2); } if (rc) { log_error ("smartcard signing failed: %s\n", gpg_strerror (rc)); goto leave; } if (is_RSA) { check_signature = 1; if (*buf & 0x80) { len++; buf = xtryrealloc (buf, len); if (!buf) goto leave; memmove (buf + 1, buf, len - 1); *buf = 0; } rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))", (int)len, buf); } else if (is_EdDSA) { rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(eddsa(r%b)(s%b)))", (int)len/2, buf, (int)len/2, buf + len/2); } else if (is_ECDSA) { unsigned char *r_buf_allocated = NULL; unsigned char *s_buf_allocated = NULL; unsigned char *r_buf, *s_buf; int r_buflen, s_buflen; r_buflen = s_buflen = len/2; if (*buf & 0x80) { r_buflen++; r_buf_allocated = xtrymalloc (r_buflen); if (!r_buf_allocated) goto leave; r_buf = r_buf_allocated; memcpy (r_buf + 1, buf, len/2); *r_buf = 0; } else r_buf = buf; if (*(buf + len/2) & 0x80) { s_buflen++; s_buf_allocated = xtrymalloc (s_buflen); if (!s_buf_allocated) { xfree (r_buf_allocated); goto leave; } s_buf = s_buf_allocated; memcpy (s_buf + 1, buf + len/2, len/2); *s_buf = 0; } else s_buf = buf + len/2; rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))", r_buflen, r_buf, s_buflen, s_buf); xfree (r_buf_allocated); xfree (s_buf_allocated); } else rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); xfree (buf); if (rc) { log_error ("failed to convert sigbuf returned by divert_pksign " "into S-Exp: %s", gpg_strerror (rc)); goto leave; } } else { /* No smartcard, but a private key */ int dsaalgo = 0; /* Put the hash into a sexp */ if (agent_is_eddsa_key (s_skey)) rc = do_encode_eddsa (data, datalen, &s_hash); else if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1) rc = do_encode_raw_pkcs1 (data, datalen, gcry_pk_get_nbits (s_skey), &s_hash); else if ( (dsaalgo = agent_is_dsa_key (s_skey)) ) rc = do_encode_dsa (data, datalen, dsaalgo, s_skey, &s_hash); else rc = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash, ctrl->digest.raw_value); if (rc) goto leave; if (dsaalgo == 0 && GCRYPT_VERSION_NUMBER < 0x010700) /* It's RSA and Libgcrypt < 1.7 */ check_signature = 1; if (DBG_CRYPTO) { gcry_log_debugsxp ("skey", s_skey); gcry_log_debugsxp ("hash", s_hash); } /* sign */ rc = gcry_pk_sign (&s_sig, s_hash, s_skey); if (rc) { log_error ("signing failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_CRYPTO) gcry_log_debugsxp ("rslt", s_sig); } /* Check that the signature verification worked and nothing is * fooling us e.g. by a bug in the signature create code or by * deliberately introduced faults. Because Libgcrypt 1.7 does this * for RSA internally there is no need to do it here again. */ if (check_signature) { gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey; if (s_hash == NULL) { if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1) rc = do_encode_raw_pkcs1 (data, datalen, gcry_pk_get_nbits (sexp_key), &s_hash); else rc = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash, ctrl->digest.raw_value); } if (! rc) rc = gcry_pk_verify (s_sig, s_hash, sexp_key); if (rc) { log_error (_("checking created signature failed: %s\n"), gpg_strerror (rc)); gcry_sexp_release (s_sig); s_sig = NULL; } } leave: *signature_sexp = s_sig; gcry_sexp_release (s_pkey); gcry_sexp_release (s_skey); gcry_sexp_release (s_hash); xfree (shadow_info); return rc; } /* SIGN whatever information we have accumulated in CTRL and write it back to OUTFP. If a CACHE_NONCE is given that cache item is first tried to get a passphrase. */ int agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, membuf_t *outbuf, cache_mode_t cache_mode) { gcry_sexp_t s_sig = NULL; char *buf = NULL; size_t len = 0; int rc = 0; rc = agent_pksign_do (ctrl, cache_nonce, desc_text, &s_sig, cache_mode, NULL, NULL, 0); if (rc) goto leave; len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xmalloc (len); len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len); assert (len); put_membuf (outbuf, buf, len); leave: gcry_sexp_release (s_sig); xfree (buf); return rc; } diff --git a/agent/trustlist.c b/agent/trustlist.c index 5554485d6..af177b2e2 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -1,825 +1,825 @@ /* trustlist.c - Maintain the list of trusted keys * Copyright (C) 2002, 2004, 2006, 2007, 2009, * 2012 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 #include #include #include "agent.h" #include /* fixme: need a way to avoid assuan calls here */ #include "../common/i18n.h" /* A structure to store the information from the trust file. */ struct trustitem_s { struct { int disabled:1; /* This entry is disabled. */ int for_pgp:1; /* Set by '*' or 'P' as first flag. */ int for_smime:1; /* Set by '*' or 'S' as first flag. */ int relax:1; /* Relax checking of root certificate constraints. */ int cm:1; /* Use chain model for validation. */ } flags; unsigned char fpr[20]; /* The binary fingerprint. */ }; typedef struct trustitem_s trustitem_t; /* Malloced table and its allocated size with all trust items. */ static trustitem_t *trusttable; static size_t trusttablesize; /* A mutex used to protect the table. */ static npth_mutex_t trusttable_lock; static const char headerblurb[] = "# This is the list of trusted keys. Comment lines, like this one, as\n" "# well as empty lines are ignored. Lines have a length limit but this\n" "# is not a serious limitation as the format of the entries is fixed and\n" "# checked by gpg-agent. A non-comment line starts with optional white\n" "# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n" "# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n" "# other flags. The fingerprint may be prefixed with a '!' to mark the\n" "# key as not trusted. You should give the gpg-agent a HUP or run the\n" "# command \"gpgconf --reload gpg-agent\" after changing this file.\n" "\n\n" "# Include the default trust list\n" "include-default\n" "\n"; /* 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_trustlist (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&trusttable_lock, NULL); if (err) log_fatal ("failed to init mutex in %s: %s\n", __FILE__,strerror (err)); initialized = 1; } } static void lock_trusttable (void) { int err; err = npth_mutex_lock (&trusttable_lock); if (err) log_fatal ("failed to acquire mutex in %s: %s\n", __FILE__, strerror (err)); } static void unlock_trusttable (void) { int err; err = npth_mutex_unlock (&trusttable_lock); if (err) log_fatal ("failed to release mutex in %s: %s\n", __FILE__, strerror (err)); } /* Clear the trusttable. The caller needs to make sure that the trusttable is locked. */ static inline void clear_trusttable (void) { xfree (trusttable); trusttable = NULL; trusttablesize = 0; } static gpg_error_t read_one_trustfile (const char *fname, int allow_include, trustitem_t **addr_of_table, size_t *addr_of_tablesize, int *addr_of_tableidx) { gpg_error_t err = 0; estream_t fp; int n, c; char *p, line[256]; trustitem_t *table, *ti; int tableidx; size_t tablesize; int lnr = 0; table = *addr_of_table; tablesize = *addr_of_tablesize; tableidx = *addr_of_tableidx; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } while (es_fgets (line, DIM(line)-1, fp)) { lnr++; n = strlen (line); if (!n || line[n-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 (_("file '%s', line %d: %s\n"), fname, lnr, gpg_strerror (err)); continue; } line[--n] = 0; /* Chop the LF. */ if (n && line[n-1] == '\r') line[--n] = 0; /* Chop an optional CR. */ /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; if (!strncmp (p, "include-default", 15) && (!p[15] || spacep (p+15))) { char *etcname; gpg_error_t err2; if (!allow_include) { log_error (_("statement \"%s\" ignored in '%s', line %d\n"), "include-default", fname, lnr); continue; } /* fixme: Should check for trailing garbage. */ etcname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL); if ( !strcmp (etcname, fname) ) /* Same file. */ log_info (_("statement \"%s\" ignored in '%s', line %d\n"), "include-default", fname, lnr); else if ( access (etcname, F_OK) && errno == ENOENT ) { /* A non existent system trustlist is not an error. Just print a note. */ log_info (_("system trustlist '%s' not available\n"), etcname); } else { err2 = read_one_trustfile (etcname, 0, &table, &tablesize, &tableidx); if (err2) err = err2; } xfree (etcname); continue; } if (tableidx == tablesize) /* Need more space. */ { trustitem_t *tmp; size_t tmplen; tmplen = tablesize + 20; tmp = xtryrealloc (table, tmplen * sizeof *table); if (!tmp) { err = gpg_error_from_syserror (); goto leave; } table = tmp; tablesize = tmplen; } ti = table + tableidx; memset (&ti->flags, 0, sizeof ti->flags); if (*p == '!') { ti->flags.disabled = 1; p++; while (spacep (p)) p++; } n = hexcolon2bin (p, ti->fpr, 20); if (n < 0) { log_error (_("bad fingerprint in '%s', line %d\n"), fname, lnr); err = gpg_error (GPG_ERR_BAD_DATA); continue; } p += n; for (; spacep (p); p++) ; /* Process the first flag which needs to be the first for backward compatibility. */ if (!*p || *p == '*' ) { ti->flags.for_smime = 1; ti->flags.for_pgp = 1; } else if ( *p == 'P' || *p == 'p') { ti->flags.for_pgp = 1; } else if ( *p == 'S' || *p == 's') { ti->flags.for_smime = 1; } else { log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr); err = gpg_error (GPG_ERR_BAD_DATA); continue; } p++; if ( *p && !spacep (p) ) { log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr); err = gpg_error (GPG_ERR_BAD_DATA); continue; } /* Now check for more key-value pairs of the form NAME[=VALUE]. */ while (*p) { for (; spacep (p); p++) ; if (!*p) break; n = strcspn (p, "= \t"); if (p[n] == '=') { log_error ("assigning a value to a flag is not yet supported; " "in '%s', line %d\n", fname, lnr); err = gpg_error (GPG_ERR_BAD_DATA); p++; } else if (n == 5 && !memcmp (p, "relax", 5)) ti->flags.relax = 1; else if (n == 2 && !memcmp (p, "cm", 2)) ti->flags.cm = 1; else log_error ("flag '%.*s' in '%s', line %d ignored\n", n, p, fname, lnr); p += n; } tableidx++; } if ( !err && !es_feof (fp) ) { err = gpg_error_from_syserror (); log_error (_("error reading '%s', line %d: %s\n"), fname, lnr, gpg_strerror (err)); } leave: es_fclose (fp); *addr_of_table = table; *addr_of_tablesize = tablesize; *addr_of_tableidx = tableidx; return err; } /* Read the trust files and update the global table on success. The trusttable is assumed to be locked. */ static gpg_error_t read_trustfiles (void) { gpg_error_t err; trustitem_t *table, *ti; int tableidx; size_t tablesize; char *fname; int allow_include = 1; tablesize = 20; table = xtrycalloc (tablesize, sizeof *table); if (!table) return gpg_error_from_syserror (); tableidx = 0; fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); if (!fname) { err = gpg_error_from_syserror (); xfree (table); return err; } if ( access (fname, F_OK) ) { if ( errno == ENOENT ) ; /* Silently ignore a non-existing trustfile. */ else { err = gpg_error_from_syserror (); log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); } xfree (fname); fname = make_filename (gnupg_sysconfdir (), "trustlist.txt", NULL); allow_include = 0; } err = read_one_trustfile (fname, allow_include, &table, &tablesize, &tableidx); xfree (fname); if (err) { xfree (table); if (gpg_err_code (err) == GPG_ERR_ENOENT) { /* Take a missing trustlist as an empty one. */ clear_trusttable (); err = 0; } return err; } /* Fixme: we should drop duplicates and sort the table. */ ti = xtryrealloc (table, (tableidx?tableidx:1) * sizeof *table); if (!ti) { err = gpg_error_from_syserror (); xfree (table); return err; } /* Replace the trusttable. */ xfree (trusttable); trusttable = ti; trusttablesize = tableidx; return 0; } /* Check whether the given fpr is in our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is true the function assumes that the trusttable is already locked. */ static gpg_error_t istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, int already_locked) { gpg_error_t err = 0; int locked = already_locked; trustitem_t *ti; size_t len; unsigned char fprbin[20]; if (r_disabled) *r_disabled = 0; if ( hexcolon2bin (fpr, fprbin, 20) < 0 ) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (!already_locked) { lock_trusttable (); locked = 1; } if (!trusttable) { err = read_trustfiles (); if (err) { log_error (_("error reading list of trusted root certificates\n")); goto leave; } } if (trusttable) { for (ti=trusttable, len = trusttablesize; len; ti++, len--) if (!memcmp (ti->fpr, fprbin, 20)) { if (ti->flags.disabled && r_disabled) *r_disabled = 1; /* Print status messages only if we have not been called in a locked state. */ if (already_locked) ; else if (ti->flags.relax) { unlock_trusttable (); locked = 0; err = agent_write_status (ctrl, "TRUSTLISTFLAG", "relax", NULL); } else if (ti->flags.cm) { unlock_trusttable (); locked = 0; err = agent_write_status (ctrl, "TRUSTLISTFLAG", "cm", NULL); } if (!err) err = ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0; goto leave; } } err = gpg_error (GPG_ERR_NOT_TRUSTED); leave: if (locked && !already_locked) unlock_trusttable (); return err; } /* Check whether the given fpr is in our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. */ gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled) { return istrusted_internal (ctrl, fpr, r_disabled, 0); } /* Write all trust entries to FP. */ gpg_error_t agent_listtrusted (void *assuan_context) { trustitem_t *ti; char key[51]; gpg_error_t err; size_t len; lock_trusttable (); if (!trusttable) { err = read_trustfiles (); if (err) { unlock_trusttable (); log_error (_("error reading list of trusted root certificates\n")); return err; } } if (trusttable) { for (ti=trusttable, len = trusttablesize; len; ti++, len--) { if (ti->flags.disabled) continue; bin2hex (ti->fpr, 20, key); key[40] = ' '; key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*' : ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' '); key[42] = '\n'; assuan_send_data (assuan_context, key, 43); assuan_send_data (assuan_context, NULL, 0); /* flush */ } } unlock_trusttable (); return 0; } /* Create a copy of string with colons inserted after each two bytes. Caller needs to release the string. In case of a memory failure, NULL is returned. */ static char * insert_colons (const char *string) { char *buffer, *p; size_t n = strlen (string); size_t nnew = n + (n+1)/2; p = buffer = xtrymalloc ( nnew + 1 ); if (!buffer) return NULL; while (*string) { *p++ = *string++; if (*string) { *p++ = *string++; if (*string) *p++ = ':'; } } *p = 0; assert (strlen (buffer) <= nnew); return buffer; } /* To pretty print DNs in the Pinentry, we replace slashes by REPLSTRING. The caller needs to free the returned string. NULL is returned on error with ERRNO set. */ static char * reformat_name (const char *name, const char *replstring) { const char *s; char *newname; char *d; size_t count; size_t replstringlen = strlen (replstring); /* If the name does not start with a slash it is not a preformatted DN and thus we don't bother to reformat it. */ if (*name != '/') return xtrystrdup (name); /* Count the names. Note that a slash contained in a DN part is expected to be C style escaped and thus the slashes we see here are the actual part delimiters. */ for (s=name+1, count=0; *s; s++) if (*s == '/') count++; newname = xtrymalloc (strlen (name) + count*replstringlen + 1); if (!newname) return NULL; for (s=name+1, d=newname; *s; s++) if (*s == '/') d = stpcpy (d, replstring); else *d++ = *s; *d = 0; return newname; } /* Insert the given fpr into our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'. This function does first check whether that key has already been put into the trustdb and returns success in this case. Before a FPR actually gets inserted, the user is asked by means of the Pinentry whether this is actual what he wants to do. */ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) { gpg_error_t err = 0; char *desc; char *fname; estream_t fp; char *fprformatted; char *nameformatted; int is_disabled; int yes_i_trust; /* Check whether we are at all allowed to modify the trustlist. This is useful so that the trustlist may be a symlink to a global - trustlist with only admin priviliges to modify it. Of course + trustlist with only admin privileges to modify it. Of course this is not a secure way of denying access, but it avoids the usual clicking on an Okay button most users are used to. */ fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); if (!fname) return gpg_error_from_syserror (); if ( access (fname, W_OK) && errno != ENOENT) { xfree (fname); return gpg_error (GPG_ERR_EPERM); } xfree (fname); if (!agent_istrusted (ctrl, fpr, &is_disabled)) { return 0; /* We already got this fingerprint. Silently return success. */ } /* This feature must explicitly been enabled. */ if (!opt.allow_mark_trusted) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (is_disabled) { /* There is an disabled entry in the trustlist. Return an error so that the user won't be asked again for that one. Changing this flag with the integrated marktrusted feature is and will not be made possible. */ return gpg_error (GPG_ERR_NOT_TRUSTED); } /* Insert a new one. */ nameformatted = reformat_name (name, "%0A "); if (!nameformatted) return gpg_error_from_syserror (); /* First a general question whether this is trusted. */ desc = xtryasprintf ( /* TRANSLATORS: This prompt is shown by the Pinentry and has one special property: A "%%0A" is used by Pinentry to insert a line break. The double percent sign is actually needed because it is also a printf format string. If you need to insert a plain % sign, you need to encode it as "%%25". The "%s" gets replaced by the name as stored in the certificate. */ L_("Do you ultimately trust%%0A" " \"%s\"%%0A" "to correctly certify user certificates?"), nameformatted); if (!desc) { xfree (nameformatted); return out_of_core (); } err = agent_get_confirmation (ctrl, desc, L_("Yes"), L_("No"), 1); xfree (desc); if (!err) yes_i_trust = 1; else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED) yes_i_trust = 0; else { xfree (nameformatted); return err; } fprformatted = insert_colons (fpr); if (!fprformatted) { xfree (nameformatted); return out_of_core (); } /* If the user trusts this certificate he has to verify the fingerprint of course. */ if (yes_i_trust) { desc = xtryasprintf ( /* TRANSLATORS: This prompt is shown by the Pinentry and has one special property: A "%%0A" is used by Pinentry to insert a line break. The double percent sign is actually needed because it is also a printf format string. If you need to insert a plain % sign, you need to encode it as "%%25". The second "%s" gets replaced by a hexdecimal fingerprint string whereas the first one receives the name as stored in the certificate. */ L_("Please verify that the certificate identified as:%%0A" " \"%s\"%%0A" "has the fingerprint:%%0A" " %s"), nameformatted, fprformatted); if (!desc) { xfree (fprformatted); xfree (nameformatted); return out_of_core (); } /* TRANSLATORS: "Correct" is the label of a button and intended to be hit if the fingerprint matches the one of the CA. The other button is "the default "Cancel" of the Pinentry. */ err = agent_get_confirmation (ctrl, desc, L_("Correct"), L_("Wrong"), 1); xfree (desc); if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED) yes_i_trust = 0; else if (err) { xfree (fprformatted); xfree (nameformatted); return err; } } /* Now check again to avoid duplicates. We take the lock to make sure that nobody else plays with our file and force a reread. */ lock_trusttable (); clear_trusttable (); if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled) { unlock_trusttable (); xfree (fprformatted); xfree (nameformatted); return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0; } fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL); if (!fname) { err = gpg_error_from_syserror (); unlock_trusttable (); xfree (fprformatted); xfree (nameformatted); return err; } if ( access (fname, F_OK) && errno == ENOENT) { fp = es_fopen (fname, "wx,mode=-rw-r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't create '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); unlock_trusttable (); xfree (fprformatted); xfree (nameformatted); return err; } es_fputs (headerblurb, fp); es_fclose (fp); } fp = es_fopen (fname, "a+,mode=-rw-r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); unlock_trusttable (); xfree (fprformatted); xfree (nameformatted); return err; } /* Append the key. */ es_fputs ("\n# ", fp); xfree (nameformatted); nameformatted = reformat_name (name, "\n# "); if (!nameformatted || strchr (name, '\n')) { /* Note that there should never be a LF in NAME but we better play safe and print a sanitized version in this case. */ es_write_sanitized (fp, name, strlen (name), NULL, NULL); } else es_fputs (nameformatted, fp); es_fprintf (fp, "\n%s%s %c%s\n", yes_i_trust?"":"!", fprformatted, flag, flag == 'S'? " relax":""); if (es_ferror (fp)) err = gpg_error_from_syserror (); if (es_fclose (fp)) err = gpg_error_from_syserror (); clear_trusttable (); xfree (fname); unlock_trusttable (); xfree (fprformatted); xfree (nameformatted); if (!err) bump_key_eventcounter (); return err; } /* This function may be called to force reloading of the trustlist. */ void agent_reload_trustlist (void) { /* All we need to do is to delete the trusttable. At the next access it will get re-read. */ lock_trusttable (); clear_trusttable (); unlock_trusttable (); bump_key_eventcounter (); } diff --git a/build-aux/texinfo.tex b/build-aux/texinfo.tex index a18189828..5a17f9793 100644 --- a/build-aux/texinfo.tex +++ b/build-aux/texinfo.tex @@ -1,8638 +1,8638 @@ % texinfo.tex -- TeX macros to handle Texinfo files. % % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % \def\texinfoversion{2007-05-03.09} % % Copyright (C) 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, % 2007 Free Software Foundation, Inc. % % This texinfo.tex file 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, or (at % your option) any later version. % % This texinfo.tex 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 texinfo.tex file; see the file COPYING. If not, % see . % % As a special exception, when this file is read by TeX when processing % a Texinfo source document, you may use the result without % restriction. (This has been our intent since Texinfo was invented.) % % Please try the latest version of texinfo.tex before submitting bug % reports; you can get the latest version from: % http://www.gnu.org/software/texinfo/ (the Texinfo home page), or % ftp://tug.org/tex/texinfo.tex % (and all CTAN mirrors, see http://www.ctan.org). % The texinfo.tex in any given distribution could well be out % of date, so if that's what you're using, please check. % % Send bug reports to bug-texinfo@gnu.org. Please include including a % complete document in each bug report with which we can reproduce the % problem. Patches are, of course, greatly appreciated. % % To process a Texinfo manual with TeX, it's most reliable to use the % texi2dvi shell script that comes with the distribution. For a simple % manual foo.texi, however, you can get away with this: % tex foo.texi % texindex foo.?? % tex foo.texi % tex foo.texi % dvips foo.dvi -o # or whatever; this makes foo.ps. % The extra TeX runs get the cross-reference information correct. % Sometimes one run after texindex suffices, and sometimes you need more % than two; texi2dvi does it as many times as necessary. % % It is possible to adapt texinfo.tex for other languages, to some % extent. You can get the existing language-specific files from the % full Texinfo distribution. % % The GNU Texinfo home page is http://www.gnu.org/software/texinfo. \message{Loading texinfo [version \texinfoversion]:} % If in a .fmt file, print the version number % and turn on active characters that we couldn't do earlier because % they might have appeared in the input file name. \everyjob{\message{[Texinfo version \texinfoversion]}% \catcode`+=\active \catcode`\_=\active} \chardef\other=12 % We never want plain's \outer definition of \+ in Texinfo. % For @tex, we can use \tabalign. \let\+ = \relax % Save some plain tex macros whose names we will redefine. \let\ptexb=\b \let\ptexbullet=\bullet \let\ptexc=\c \let\ptexcomma=\, \let\ptexdot=\. \let\ptexdots=\dots \let\ptexend=\end \let\ptexequiv=\equiv \let\ptexexclam=\! \let\ptexfootnote=\footnote \let\ptexgtr=> \let\ptexhat=^ \let\ptexi=\i \let\ptexindent=\indent \let\ptexinsert=\insert \let\ptexlbrace=\{ \let\ptexless=< \let\ptexnewwrite\newwrite \let\ptexnoindent=\noindent \let\ptexplus=+ \let\ptexrbrace=\} \let\ptexslash=\/ \let\ptexstar=\* \let\ptext=\t % If this character appears in an error message or help string, it % starts a new line in the output. \newlinechar = `^^J % Use TeX 3.0's \inputlineno to get the line number, for better error % messages, but if we're using an old version of TeX, don't do anything. % \ifx\inputlineno\thisisundefined \let\linenumber = \empty % Pre-3.0. \else \def\linenumber{l.\the\inputlineno:\space} \fi % Set up fixed words for English if not already set. \ifx\putwordAppendix\undefined \gdef\putwordAppendix{Appendix}\fi \ifx\putwordChapter\undefined \gdef\putwordChapter{Chapter}\fi \ifx\putwordfile\undefined \gdef\putwordfile{file}\fi \ifx\putwordin\undefined \gdef\putwordin{in}\fi \ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi \ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi \ifx\putwordInfo\undefined \gdef\putwordInfo{Info}\fi \ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi \ifx\putwordMethodon\undefined \gdef\putwordMethodon{Method on}\fi \ifx\putwordNoTitle\undefined \gdef\putwordNoTitle{No Title}\fi \ifx\putwordof\undefined \gdef\putwordof{of}\fi \ifx\putwordon\undefined \gdef\putwordon{on}\fi \ifx\putwordpage\undefined \gdef\putwordpage{page}\fi \ifx\putwordsection\undefined \gdef\putwordsection{section}\fi \ifx\putwordSection\undefined \gdef\putwordSection{Section}\fi \ifx\putwordsee\undefined \gdef\putwordsee{see}\fi \ifx\putwordSee\undefined \gdef\putwordSee{See}\fi \ifx\putwordShortTOC\undefined \gdef\putwordShortTOC{Short Contents}\fi \ifx\putwordTOC\undefined \gdef\putwordTOC{Table of Contents}\fi % \ifx\putwordMJan\undefined \gdef\putwordMJan{January}\fi \ifx\putwordMFeb\undefined \gdef\putwordMFeb{February}\fi \ifx\putwordMMar\undefined \gdef\putwordMMar{March}\fi \ifx\putwordMApr\undefined \gdef\putwordMApr{April}\fi \ifx\putwordMMay\undefined \gdef\putwordMMay{May}\fi \ifx\putwordMJun\undefined \gdef\putwordMJun{June}\fi \ifx\putwordMJul\undefined \gdef\putwordMJul{July}\fi \ifx\putwordMAug\undefined \gdef\putwordMAug{August}\fi \ifx\putwordMSep\undefined \gdef\putwordMSep{September}\fi \ifx\putwordMOct\undefined \gdef\putwordMOct{October}\fi \ifx\putwordMNov\undefined \gdef\putwordMNov{November}\fi \ifx\putwordMDec\undefined \gdef\putwordMDec{December}\fi % \ifx\putwordDefmac\undefined \gdef\putwordDefmac{Macro}\fi \ifx\putwordDefspec\undefined \gdef\putwordDefspec{Special Form}\fi \ifx\putwordDefvar\undefined \gdef\putwordDefvar{Variable}\fi \ifx\putwordDefopt\undefined \gdef\putwordDefopt{User Option}\fi \ifx\putwordDeffunc\undefined \gdef\putwordDeffunc{Function}\fi % Since the category of space is not known, we have to be careful. \chardef\spacecat = 10 \def\spaceisspace{\catcode`\ =\spacecat} % sometimes characters are active, so we need control sequences. \chardef\colonChar = `\: \chardef\commaChar = `\, \chardef\dashChar = `\- \chardef\dotChar = `\. \chardef\exclamChar= `\! \chardef\lquoteChar= `\` \chardef\questChar = `\? \chardef\rquoteChar= `\' \chardef\semiChar = `\; \chardef\underChar = `\_ % Ignore a token. % \def\gobble#1{} % The following is used inside several \edef's. \def\makecsname#1{\expandafter\noexpand\csname#1\endcsname} % Hyphenation fixes. \hyphenation{ Flor-i-da Ghost-script Ghost-view Mac-OS Post-Script ap-pen-dix bit-map bit-maps data-base data-bases eshell fall-ing half-way long-est man-u-script man-u-scripts mini-buf-fer mini-buf-fers over-view par-a-digm par-a-digms rath-er rec-tan-gu-lar ro-bot-ics se-vere-ly set-up spa-ces spell-ing spell-ings stand-alone strong-est time-stamp time-stamps which-ever white-space wide-spread wrap-around } % Margin to add to right of even pages, to left of odd pages. \newdimen\bindingoffset \newdimen\normaloffset \newdimen\pagewidth \newdimen\pageheight % For a final copy, take out the rectangles % that mark overfull boxes (in case you have decided % that the text looks ok even though it passes the margin). % \def\finalout{\overfullrule=0pt} % @| inserts a changebar to the left of the current line. It should % surround any changed text. This approach does *not* work if the % change spans more than two lines of output. To handle that, we would % have adopt a much more difficult approach (putting marks into the main % vertical list for the beginning and end of each change). % \def\|{% % \vadjust can only be used in horizontal mode. \leavevmode % % Append this vertical mode material after the current line in the output. \vadjust{% % We want to insert a rule with the height and depth of the current % leading; that is exactly what \strutbox is supposed to record. \vskip-\baselineskip % % \vadjust-items are inserted at the left edge of the type. So % the \llap here moves out into the left-hand margin. \llap{% % % For a thicker or thinner bar, change the `1pt'. \vrule height\baselineskip width1pt % % This is the space between the bar and the text. \hskip 12pt }% }% } % Sometimes it is convenient to have everything in the transcript file % and nothing on the terminal. We don't just call \tracingall here, % since that produces some useless output on the terminal. We also make % some effort to order the tracing commands to reduce output in the log % file; cf. trace.sty in LaTeX. % \def\gloggingall{\begingroup \globaldefs = 1 \loggingall \endgroup}% \def\loggingall{% \tracingstats2 \tracingpages1 \tracinglostchars2 % 2 gives us more in etex \tracingparagraphs1 \tracingoutput1 \tracingmacros2 \tracingrestores1 \showboxbreadth\maxdimen \showboxdepth\maxdimen \ifx\eTeXversion\undefined\else % etex gives us more logging \tracingscantokens1 \tracingifs1 \tracinggroups1 \tracingnesting2 \tracingassigns1 \fi \tracingcommands3 % 3 gives us more in etex \errorcontextlines16 }% % add check for \lastpenalty to plain's definitions. If the last thing % we did was a \nobreak, we don't want to insert more space. % \def\smallbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\smallskipamount \removelastskip\penalty-50\smallskip\fi\fi} \def\medbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\medskipamount \removelastskip\penalty-100\medskip\fi\fi} \def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount \removelastskip\penalty-200\bigskip\fi\fi} % For @cropmarks command. % Do @cropmarks to get crop marks. % \newif\ifcropmarks \let\cropmarks = \cropmarkstrue % % Dimensions to add cropmarks at corners. % Added by P. A. MacKay, 12 Nov. 1986 % \newdimen\outerhsize \newdimen\outervsize % set by the paper size routines \newdimen\cornerlong \cornerlong=1pc \newdimen\cornerthick \cornerthick=.3pt \newdimen\topandbottommargin \topandbottommargin=.75in % Main output routine. \chardef\PAGE = 255 \output = {\onepageout{\pagecontents\PAGE}} \newbox\headlinebox \newbox\footlinebox % \onepageout takes a vbox as an argument. Note that \pagecontents % does insertions, but you have to call it yourself. \def\onepageout#1{% \ifcropmarks \hoffset=0pt \else \hoffset=\normaloffset \fi % \ifodd\pageno \advance\hoffset by \bindingoffset \else \advance\hoffset by -\bindingoffset\fi % % Do this outside of the \shipout so @code etc. will be expanded in % the headline as they should be, not taken literally (outputting ''code). \setbox\headlinebox = \vbox{\let\hsize=\pagewidth \makeheadline}% \setbox\footlinebox = \vbox{\let\hsize=\pagewidth \makefootline}% % {% % Have to do this stuff outside the \shipout because we want it to % take effect in \write's, yet the group defined by the \vbox ends % before the \shipout runs. % \indexdummies % don't expand commands in the output. \normalturnoffactive % \ in index entries must not stay \, e.g., if % the page break happens to be in the middle of an example. % We don't want .vr (or whatever) entries like this: % \entry{{\tt \indexbackslash }acronym}{32}{\code {\acronym}} % "\acronym" won't work when it's read back in; % it needs to be % {\code {{\tt \backslashcurfont }acronym} \shipout\vbox{% % Do this early so pdf references go to the beginning of the page. \ifpdfmakepagedest \pdfdest name{\the\pageno} xyz\fi % \ifcropmarks \vbox to \outervsize\bgroup \hsize = \outerhsize \vskip-\topandbottommargin \vtop to0pt{% \line{\ewtop\hfil\ewtop}% \nointerlineskip \line{% \vbox{\moveleft\cornerthick\nstop}% \hfill \vbox{\moveright\cornerthick\nstop}% }% \vss}% \vskip\topandbottommargin \line\bgroup \hfil % center the page within the outer (page) hsize. \ifodd\pageno\hskip\bindingoffset\fi \vbox\bgroup \fi % \unvbox\headlinebox \pagebody{#1}% \ifdim\ht\footlinebox > 0pt % Only leave this space if the footline is nonempty. % (We lessened \vsize for it in \oddfootingyyy.) % The \baselineskip=24pt in plain's \makefootline has no effect. \vskip 24pt \unvbox\footlinebox \fi % \ifcropmarks \egroup % end of \vbox\bgroup \hfil\egroup % end of (centering) \line\bgroup \vskip\topandbottommargin plus1fill minus1fill \boxmaxdepth = \cornerthick \vbox to0pt{\vss \line{% \vbox{\moveleft\cornerthick\nsbot}% \hfill \vbox{\moveright\cornerthick\nsbot}% }% \nointerlineskip \line{\ewbot\hfil\ewbot}% }% \egroup % \vbox from first cropmarks clause \fi }% end of \shipout\vbox }% end of group with \indexdummies \advancepageno \ifnum\outputpenalty>-20000 \else\dosupereject\fi } \newinsert\margin \dimen\margin=\maxdimen \def\pagebody#1{\vbox to\pageheight{\boxmaxdepth=\maxdepth #1}} {\catcode`\@ =11 \gdef\pagecontents#1{\ifvoid\topins\else\unvbox\topins\fi % marginal hacks, juha@viisa.uucp (Juha Takala) \ifvoid\margin\else % marginal info is present \rlap{\kern\hsize\vbox to\z@{\kern1pt\box\margin \vss}}\fi \dimen@=\dp#1 \unvbox#1 \ifvoid\footins\else\vskip\skip\footins\footnoterule \unvbox\footins\fi \ifr@ggedbottom \kern-\dimen@ \vfil \fi} } % Here are the rules for the cropmarks. Note that they are % offset so that the space between them is truly \outerhsize or \outervsize % (P. A. MacKay, 12 November, 1986) % \def\ewtop{\vrule height\cornerthick depth0pt width\cornerlong} \def\nstop{\vbox {\hrule height\cornerthick depth\cornerlong width\cornerthick}} \def\ewbot{\vrule height0pt depth\cornerthick width\cornerlong} \def\nsbot{\vbox {\hrule height\cornerlong depth\cornerthick width\cornerthick}} % Parse an argument, then pass it to #1. The argument is the rest of % the input line (except we remove a trailing comment). #1 should be a % macro which expects an ordinary undelimited TeX argument. % \def\parsearg{\parseargusing{}} \def\parseargusing#1#2{% \def\argtorun{#2}% \begingroup \obeylines \spaceisspace #1% \parseargline\empty% Insert the \empty token, see \finishparsearg below. } {\obeylines % \gdef\parseargline#1^^M{% \endgroup % End of the group started in \parsearg. \argremovecomment #1\comment\ArgTerm% }% } % First remove any @comment, then any @c comment. \def\argremovecomment#1\comment#2\ArgTerm{\argremovec #1\c\ArgTerm} \def\argremovec#1\c#2\ArgTerm{\argcheckspaces#1\^^M\ArgTerm} -% Each occurence of `\^^M' or `\^^M' is replaced by a single space. +% Each occurrence of `\^^M' or `\^^M' is replaced by a single space. % % \argremovec might leave us with trailing space, e.g., % @end itemize @c foo % This space token undergoes the same procedure and is eventually removed % by \finishparsearg. % \def\argcheckspaces#1\^^M{\argcheckspacesX#1\^^M \^^M} \def\argcheckspacesX#1 \^^M{\argcheckspacesY#1\^^M} \def\argcheckspacesY#1\^^M#2\^^M#3\ArgTerm{% \def\temp{#3}% \ifx\temp\empty % Do not use \next, perhaps the caller of \parsearg uses it; reuse \temp: \let\temp\finishparsearg \else \let\temp\argcheckspaces \fi % Put the space token in: \temp#1 #3\ArgTerm } % If a _delimited_ argument is enclosed in braces, they get stripped; so % to get _exactly_ the rest of the line, we had to prevent such situation. % We prepended an \empty token at the very beginning and we expand it now, % just before passing the control to \argtorun. -% (Similarily, we have to think about #3 of \argcheckspacesY above: it is +% (Similarly, we have to think about #3 of \argcheckspacesY above: it is % either the null string, or it ends with \^^M---thus there is no danger % that a pair of braces would be stripped. % % But first, we have to remove the trailing space token. % \def\finishparsearg#1 \ArgTerm{\expandafter\argtorun\expandafter{#1}} % \parseargdef\foo{...} % is roughly equivalent to % \def\foo{\parsearg\Xfoo} % \def\Xfoo#1{...} % % Actually, I use \csname\string\foo\endcsname, ie. \\foo, as it is my % favourite TeX trick. --kasal, 16nov03 \def\parseargdef#1{% \expandafter \doparseargdef \csname\string#1\endcsname #1% } \def\doparseargdef#1#2{% \def#2{\parsearg#1}% \def#1##1% } % Several utility definitions with active space: { \obeyspaces \gdef\obeyedspace{ } % Make each space character in the input produce a normal interword % space in the output. Don't allow a line break at this space, as this % is used only in environments like @example, where each line of input % should produce a line of output anyway. % \gdef\sepspaces{\obeyspaces\let =\tie} % If an index command is used in an @example environment, any spaces % therein should become regular spaces in the raw index file, not the % expansion of \tie (\leavevmode \penalty \@M \ ). \gdef\unsepspaces{\let =\space} } \def\flushcr{\ifx\par\lisppar \def\next##1{}\else \let\next=\relax \fi \next} % Define the framework for environments in texinfo.tex. It's used like this: % % \envdef\foo{...} % \def\Efoo{...} % % It's the responsibility of \envdef to insert \begingroup before the % actual body; @end closes the group after calling \Efoo. \envdef also % defines \thisenv, so the current environment is known; @end checks % whether the environment name matches. The \checkenv macro can also be % used to check whether the current environment is the one expected. % % Non-false conditionals (@iftex, @ifset) don't fit into this, so they -% are not treated as enviroments; they don't open a group. (The +% are not treated as environments; they don't open a group. (The % implementation of @end takes care not to call \endgroup in this % special case.) % At runtime, environments start with this: \def\startenvironment#1{\begingroup\def\thisenv{#1}} % initialize \let\thisenv\empty % ... but they get defined via ``\envdef\foo{...}'': \long\def\envdef#1#2{\def#1{\startenvironment#1#2}} \def\envparseargdef#1#2{\parseargdef#1{\startenvironment#1#2}} % Check whether we're in the right environment: \def\checkenv#1{% \def\temp{#1}% \ifx\thisenv\temp \else \badenverr \fi } -% Evironment mismatch, #1 expected: +% Environment mismatch, #1 expected: \def\badenverr{% \errhelp = \EMsimple \errmessage{This command can appear only \inenvironment\temp, not \inenvironment\thisenv}% } \def\inenvironment#1{% \ifx#1\empty out of any environment% \else in environment \expandafter\string#1% \fi } % @end foo executes the definition of \Efoo. % But first, it executes a specialized version of \checkenv % \parseargdef\end{% \if 1\csname iscond.#1\endcsname \else % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03 \expandafter\checkenv\csname#1\endcsname \csname E#1\endcsname \endgroup \fi } \newhelp\EMsimple{Press RETURN to continue.} %% Simple single-character @ commands % @@ prints an @ % Kludge this until the fonts are right (grr). \def\@{{\tt\char64}} % This is turned off because it was never documented % and you can use @w{...} around a quote to suppress ligatures. %% Define @` and @' to be the same as ` and ' %% but suppressing ligatures. %\def\`{{`}} %\def\'{{'}} % Used to generate quoted braces. \def\mylbrace {{\tt\char123}} \def\myrbrace {{\tt\char125}} \let\{=\mylbrace \let\}=\myrbrace \begingroup % Definitions to produce \{ and \} commands for indices, % and @{ and @} for the aux/toc files. \catcode`\{ = \other \catcode`\} = \other \catcode`\[ = 1 \catcode`\] = 2 \catcode`\! = 0 \catcode`\\ = \other !gdef!lbracecmd[\{]% !gdef!rbracecmd[\}]% !gdef!lbraceatcmd[@{]% !gdef!rbraceatcmd[@}]% !endgroup % @comma{} to avoid , parsing problems. \let\comma = , % Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent % Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. \let\, = \c \let\dotaccent = \. \def\ringaccent#1{{\accent23 #1}} \let\tieaccent = \t \let\ubaraccent = \b \let\udotaccent = \d % Other special characters: @questiondown @exclamdown @ordf @ordm % Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. \def\questiondown{?`} \def\exclamdown{!`} \def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} \def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} % Dotless i and dotless j, used for accents. \def\imacro{i} \def\jmacro{j} \def\dotless#1{% \def\temp{#1}% \ifx\temp\imacro \ptexi \else\ifx\temp\jmacro \j \else \errmessage{@dotless can be used only with i or j}% \fi\fi } % The \TeX{} logo, as in plain, but resetting the spacing so that a % period following counts as ending a sentence. (Idea found in latex.) % \edef\TeX{\TeX \spacefactor=1000 } % @LaTeX{} logo. Not quite the same results as the definition in % latex.ltx, since we use a different font for the raised A; it's most % convenient for us to use an explicitly smaller font, rather than using % the \scriptstyle font (since we don't reset \scriptstyle and % \scriptscriptstyle). % \def\LaTeX{% L\kern-.36em {\setbox0=\hbox{T}% \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}% \kern-.15em \TeX } % Be sure we're in horizontal mode when doing a tie, since we make space % equivalent to this in @example-like environments. Otherwise, a space % at the beginning of a line will start with \penalty -- and % since \penalty is valid in vertical mode, we'd end up putting the % penalty on the vertical list instead of in the new paragraph. {\catcode`@ = 11 % Avoid using \@M directly, because that causes trouble % if the definition is written into an index file. \global\let\tiepenalty = \@M \gdef\tie{\leavevmode\penalty\tiepenalty\ } } % @: forces normal size whitespace following. \def\:{\spacefactor=1000 } % @* forces a line break. \def\*{\hfil\break\hbox{}\ignorespaces} % @/ allows a line break. \let\/=\allowbreak % @. is an end-of-sentence period. \def\.{.\spacefactor=\endofsentencespacefactor\space} % @! is an end-of-sentence bang. \def\!{!\spacefactor=\endofsentencespacefactor\space} % @? is an end-of-sentence query. \def\?{?\spacefactor=\endofsentencespacefactor\space} % @frenchspacing on|off says whether to put extra space after punctuation. % \def\onword{on} \def\offword{off} % \parseargdef\frenchspacing{% \def\temp{#1}% \ifx\temp\onword \plainfrenchspacing \else\ifx\temp\offword \plainnonfrenchspacing \else \errhelp = \EMsimple \errmessage{Unknown @frenchspacing option `\temp', must be on/off}% \fi\fi } % @w prevents a word break. Without the \leavevmode, @w at the % beginning of a paragraph, when TeX is still in vertical mode, would % produce a whole line of output instead of starting the paragraph. \def\w#1{\leavevmode\hbox{#1}} % @group ... @end group forces ... to be all on one page, by enclosing % it in a TeX vbox. We use \vtop instead of \vbox to construct the box % to keep its height that of a normal line. According to the rules for % \topskip (p.114 of the TeXbook), the glue inserted is % max (\topskip - \ht (first item), 0). If that height is large, % therefore, no glue is inserted, and the space between the headline and % the text is small, which looks bad. % % Another complication is that the group might be very large. This can % cause the glue on the previous page to be unduly stretched, because it % does not have much material. In this case, it's better to add an % explicit \vfill so that the extra space is at the bottom. The % threshold for doing this is if the group is more than \vfilllimit % percent of a page (\vfilllimit can be changed inside of @tex). % \newbox\groupbox \def\vfilllimit{0.7} % \envdef\group{% \ifnum\catcode`\^^M=\active \else \errhelp = \groupinvalidhelp \errmessage{@group invalid in context where filling is enabled}% \fi \startsavinginserts % \setbox\groupbox = \vtop\bgroup % Do @comment since we are called inside an environment such as % @example, where each end-of-line in the input causes an % end-of-line in the output. We don't want the end-of-line after % the `@group' to put extra space in the output. Since @group % should appear on a line by itself (according to the Texinfo % manual), we don't worry about eating any user text. \comment } % % The \vtop produces a box with normal height and large depth; thus, TeX puts % \baselineskip glue before it, and (when the next line of text is done) % \lineskip glue after it. Thus, space below is not quite equal to space % above. But it's pretty close. \def\Egroup{% % To get correct interline space between the last line of the group % and the first line afterwards, we have to propagate \prevdepth. \endgraf % Not \par, as it may have been set to \lisppar. \global\dimen1 = \prevdepth \egroup % End the \vtop. % \dimen0 is the vertical size of the group's box. \dimen0 = \ht\groupbox \advance\dimen0 by \dp\groupbox % \dimen2 is how much space is left on the page (more or less). \dimen2 = \pageheight \advance\dimen2 by -\pagetotal % if the group doesn't fit on the current page, and it's a big big % group, force a page break. \ifdim \dimen0 > \dimen2 \ifdim \pagetotal < \vfilllimit\pageheight \page \fi \fi \box\groupbox \prevdepth = \dimen1 \checkinserts } % % TeX puts in an \escapechar (i.e., `@') at the beginning of the help % message, so this ends up printing `@group can only ...'. % \newhelp\groupinvalidhelp{% group can only be used in environments such as @example,^^J% where each line of input produces a line of output.} % @need space-in-mils % forces a page break if there is not space-in-mils remaining. \newdimen\mil \mil=0.001in % Old definition--didn't work. %\parseargdef\need{\par % %% This method tries to make TeX break the page naturally %% if the depth of the box does not fit. %{\baselineskip=0pt% %\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak %\prevdepth=-1000pt %}} \parseargdef\need{% % Ensure vertical mode, so we don't make a big box in the middle of a % paragraph. \par % % If the @need value is less than one line space, it's useless. \dimen0 = #1\mil \dimen2 = \ht\strutbox \advance\dimen2 by \dp\strutbox \ifdim\dimen0 > \dimen2 % % Do a \strut just to make the height of this box be normal, so the % normal leading is inserted relative to the preceding line. % And a page break here is fine. \vtop to #1\mil{\strut\vfil}% % % TeX does not even consider page breaks if a penalty added to the % main vertical list is 10000 or more. But in order to see if the % empty box we just added fits on the page, we must make it consider % page breaks. On the other hand, we don't want to actually break the % page after the empty box. So we use a penalty of 9999. % % There is an extremely small chance that TeX will actually break the % page at this \penalty, if there are no other feasible breakpoints in % sight. (If the user is using lots of big @group commands, which % almost-but-not-quite fill up a page, TeX will have a hard time doing % good page breaking, for example.) However, I could not construct an % example where a page broke at this \penalty; if it happens in a real % document, then we can reconsider our strategy. \penalty9999 % % Back up by the size of the box, whether we did a page break or not. \kern -#1\mil % % Do not allow a page break right after this kern. \nobreak \fi } % @br forces paragraph break (and is undocumented). \let\br = \par % @page forces the start of a new page. % \def\page{\par\vfill\supereject} % @exdent text.... % outputs text on separate line in roman font, starting at standard page margin % This records the amount of indent in the innermost environment. % That's how much \exdent should take out. \newskip\exdentamount % This defn is used inside fill environments such as @defun. \parseargdef\exdent{\hfil\break\hbox{\kern -\exdentamount{\rm#1}}\hfil\break} % This defn is used inside nofill environments such as @example. \parseargdef\nofillexdent{{\advance \leftskip by -\exdentamount \leftline{\hskip\leftskip{\rm#1}}}} % @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current % paragraph. For more general purposes, use the \margin insertion % class. WHICH is `l' or `r'. % \newskip\inmarginspacing \inmarginspacing=1cm \def\strutdepth{\dp\strutbox} % \def\doinmargin#1#2{\strut\vadjust{% \nobreak \kern-\strutdepth \vtop to \strutdepth{% \baselineskip=\strutdepth \vss % if you have multiple lines of stuff to put here, you'll need to % make the vbox yourself of the appropriate size. \ifx#1l% \llap{\ignorespaces #2\hskip\inmarginspacing}% \else \rlap{\hskip\hsize \hskip\inmarginspacing \ignorespaces #2}% \fi \null }% }} \def\inleftmargin{\doinmargin l} \def\inrightmargin{\doinmargin r} % % @inmargin{TEXT [, RIGHT-TEXT]} % (if RIGHT-TEXT is given, use TEXT for left page, RIGHT-TEXT for right; % else use TEXT for both). % \def\inmargin#1{\parseinmargin #1,,\finish} \def\parseinmargin#1,#2,#3\finish{% not perfect, but better than nothing. \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0 > 0pt \def\lefttext{#1}% have both texts \def\righttext{#2}% \else \def\lefttext{#1}% have only one text \def\righttext{#1}% \fi % \ifodd\pageno \def\temp{\inrightmargin\righttext}% odd page -> outside is right margin \else \def\temp{\inleftmargin\lefttext}% \fi \temp } % @include file insert text of that file as input. % \def\include{\parseargusing\filenamecatcodes\includezzz} \def\includezzz#1{% \pushthisfilestack \def\thisfile{#1}% {% \makevalueexpandable \def\temp{\input #1 }% \expandafter }\temp \popthisfilestack } \def\filenamecatcodes{% \catcode`\\=\other \catcode`~=\other \catcode`^=\other \catcode`_=\other \catcode`|=\other \catcode`<=\other \catcode`>=\other \catcode`+=\other \catcode`-=\other } \def\pushthisfilestack{% \expandafter\pushthisfilestackX\popthisfilestack\StackTerm } \def\pushthisfilestackX{% \expandafter\pushthisfilestackY\thisfile\StackTerm } \def\pushthisfilestackY #1\StackTerm #2\StackTerm {% \gdef\popthisfilestack{\gdef\thisfile{#1}\gdef\popthisfilestack{#2}}% } \def\popthisfilestack{\errthisfilestackempty} \def\errthisfilestackempty{\errmessage{Internal error: the stack of filenames is empty.}} \def\thisfile{} % @center line % outputs that line, centered. % \parseargdef\center{% \ifhmode \let\next\centerH \else \let\next\centerV \fi \next{\hfil \ignorespaces#1\unskip \hfil}% } \def\centerH#1{% {% \hfil\break \advance\hsize by -\leftskip \advance\hsize by -\rightskip \line{#1}% \break }% } \def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}} % @sp n outputs n lines of vertical space \parseargdef\sp{\vskip #1\baselineskip} % @comment ...line which is ignored... % @c is the same as @comment % @ignore ... @end ignore is another way to write a comment \def\comment{\begingroup \catcode`\^^M=\other% \catcode`\@=\other \catcode`\{=\other \catcode`\}=\other% \commentxxx} {\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}} \let\c=\comment % @paragraphindent NCHARS % We'll use ems for NCHARS, close enough. % NCHARS can also be the word `asis' or `none'. % We cannot feasibly implement @paragraphindent asis, though. % \def\asisword{asis} % no translation, these are keywords \def\noneword{none} % \parseargdef\paragraphindent{% \def\temp{#1}% \ifx\temp\asisword \else \ifx\temp\noneword \defaultparindent = 0pt \else \defaultparindent = #1em \fi \fi \parindent = \defaultparindent } % @exampleindent NCHARS % We'll use ems for NCHARS like @paragraphindent. % It seems @exampleindent asis isn't necessary, but % I preserve it to make it similar to @paragraphindent. \parseargdef\exampleindent{% \def\temp{#1}% \ifx\temp\asisword \else \ifx\temp\noneword \lispnarrowing = 0pt \else \lispnarrowing = #1em \fi \fi } % @firstparagraphindent WORD % If WORD is `none', then suppress indentation of the first paragraph % after a section heading. If WORD is `insert', then do indent at such % paragraphs. % % The paragraph indentation is suppressed or not by calling % \suppressfirstparagraphindent, which the sectioning commands do. % We switch the definition of this back and forth according to WORD. % By default, we suppress indentation. % \def\suppressfirstparagraphindent{\dosuppressfirstparagraphindent} \def\insertword{insert} % \parseargdef\firstparagraphindent{% \def\temp{#1}% \ifx\temp\noneword \let\suppressfirstparagraphindent = \dosuppressfirstparagraphindent \else\ifx\temp\insertword \let\suppressfirstparagraphindent = \relax \else \errhelp = \EMsimple \errmessage{Unknown @firstparagraphindent option `\temp'}% \fi\fi } % Here is how we actually suppress indentation. Redefine \everypar to % \kern backwards by \parindent, and then reset itself to empty. % % We also make \indent itself not actually do anything until the next % paragraph. % \gdef\dosuppressfirstparagraphindent{% \gdef\indent{% \restorefirstparagraphindent \indent }% \gdef\noindent{% \restorefirstparagraphindent \noindent }% \global\everypar = {% \kern -\parindent \restorefirstparagraphindent }% } \gdef\restorefirstparagraphindent{% \global \let \indent = \ptexindent \global \let \noindent = \ptexnoindent \global \everypar = {}% } % @asis just yields its argument. Used with @table, for example. % \def\asis#1{#1} % @math outputs its argument in math mode. % % One complication: _ usually means subscripts, but it could also mean % an actual _ character, as in @math{@var{some_variable} + 1}. So make % _ active, and distinguish by seeing if the current family is \slfam, % which is what @var uses. { \catcode`\_ = \active \gdef\mathunderscore{% \catcode`\_=\active \def_{\ifnum\fam=\slfam \_\else\sb\fi}% } } % Another complication: we want \\ (and @\) to output a \ character. % FYI, plain.tex uses \\ as a temporary control sequence (why?), but % this is not advertised and we don't care. Texinfo does not % otherwise define @\. % % The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. \def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} % \def\math{% \tex \mathunderscore \let\\ = \mathbackslash \mathactive $\finishmath } \def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. % Some active characters (such as <) are spaced differently in math. % We have to reset their definitions in case the @math was an argument % to a command which sets the catcodes (such as @item or @section). % { \catcode`^ = \active \catcode`< = \active \catcode`> = \active \catcode`+ = \active \gdef\mathactive{% \let^ = \ptexhat \let< = \ptexless \let> = \ptexgtr \let+ = \ptexplus } } % @bullet and @minus need the same treatment as @math, just above. \def\bullet{$\ptexbullet$} \def\minus{$-$} % @dots{} outputs an ellipsis using the current font. % We do .5em per period so that it has the same spacing in the cm % typewriter fonts as three actual period characters; on the other hand, % in other typewriter fonts three periods are wider than 1.5em. So do % whichever is larger. % \def\dots{% \leavevmode \setbox0=\hbox{...}% get width of three periods \ifdim\wd0 > 1.5em \dimen0 = \wd0 \else \dimen0 = 1.5em \fi \hbox to \dimen0{% \hskip 0pt plus.25fil .\hskip 0pt plus1fil .\hskip 0pt plus1fil .\hskip 0pt plus.5fil }% } % @enddots{} is an end-of-sentence ellipsis. % \def\enddots{% \dots \spacefactor=\endofsentencespacefactor } % @comma{} is so commas can be inserted into text without messing up % Texinfo's parsing. % \let\comma = , % @refill is a no-op. \let\refill=\relax % If working on a large document in chapters, it is convenient to % be able to disable indexing, cross-referencing, and contents, for test runs. % This is done with @novalidate (before @setfilename). % \newif\iflinks \linkstrue % by default we want the aux files. \let\novalidate = \linksfalse % @setfilename is done at the beginning of every texinfo file. % So open here the files we need to have open while reading the input. % This makes it possible to make a .fmt file for texinfo. \def\setfilename{% \fixbackslash % Turn off hack to swallow `\input texinfo'. \iflinks \tryauxfile % Open the new aux file. TeX will close it automatically at exit. \immediate\openout\auxfile=\jobname.aux \fi % \openindices needs to do some work in any case. \openindices \let\setfilename=\comment % Ignore extra @setfilename cmds. % % If texinfo.cnf is present on the system, read it. % Useful for site-wide @afourpaper, etc. \openin 1 texinfo.cnf \ifeof 1 \else \input texinfo.cnf \fi \closein 1 % \comment % Ignore the actual filename. } % Called from \setfilename. % \def\openindices{% \newindex{cp}% \newcodeindex{fn}% \newcodeindex{vr}% \newcodeindex{tp}% \newcodeindex{ky}% \newcodeindex{pg}% } % @bye. \outer\def\bye{\pagealignmacro\tracingstats=1\ptexend} \message{pdf,} % adobe `portable' document format \newcount\tempnum \newcount\lnkcount \newtoks\filename \newcount\filenamelength \newcount\pgn \newtoks\toksA \newtoks\toksB \newtoks\toksC \newtoks\toksD \newbox\boxA \newcount\countA \newif\ifpdf \newif\ifpdfmakepagedest % when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1 % can be set). So we test for \relax and 0 as well as \undefined, % borrowed from ifpdf.sty. \ifx\pdfoutput\undefined \else \ifx\pdfoutput\relax \else \ifcase\pdfoutput \else \pdftrue \fi \fi \fi % PDF uses PostScript string constants for the names of xref targets, % for display in the outlines, and in other places. Thus, we have to % double any backslashes. Otherwise, a name like "\node" will be % interpreted as a newline (\n), followed by o, d, e. Not good. % http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html % (and related messages, the final outcome is that it is up to the TeX % user to double the backslashes and otherwise make the string valid, so % that's what we do). % double active backslashes. % {\catcode`\@=0 \catcode`\\=\active @gdef@activebackslashdouble{% @catcode`@\=@active @let\=@doublebackslash} } % To handle parens, we must adopt a different approach, since parens are % not active characters. hyperref.dtx (which has the same problem as % us) handles it with this amazing macro to replace tokens, with minor % changes for Texinfo. It is included here under the GPL by permission % from the author, Heiko Oberdiek. % % #1 is the tokens to replace. % #2 is the replacement. % #3 is the control sequence with the string. % \def\HyPsdSubst#1#2#3{% \def\HyPsdReplace##1#1##2\END{% ##1% \ifx\\##2\\% \else #2% \HyReturnAfterFi{% \HyPsdReplace##2\END }% \fi }% \xdef#3{\expandafter\HyPsdReplace#3#1\END}% } \long\def\HyReturnAfterFi#1\fi{\fi#1} % #1 is a control sequence in which to do the replacements. \def\backslashparens#1{% \xdef#1{#1}% redefine it as its expansion; the definition is simply % \lastnode when called from \setref -> \pdfmkdest. \HyPsdSubst{(}{\realbackslash(}{#1}% \HyPsdSubst{)}{\realbackslash)}{#1}% } \newhelp\nopdfimagehelp{Texinfo supports .png, .jpg, .jpeg, and .pdf images with PDF output, and none of those formats could be found. (.eps cannot be supported due to the design of the PDF format; use regular TeX (DVI output) for that.)} \ifpdf \input pdfcolor \pdfcatalog{/PageMode /UseOutlines} % % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto). \def\dopdfimage#1#2#3{% \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% % % pdftex (and the PDF format) support .png, .jpg, .pdf (among % others). Let's try in that order. \let\pdfimgext=\empty \begingroup \openin 1 #1.png \ifeof 1 \openin 1 #1.jpg \ifeof 1 \openin 1 #1.jpeg \ifeof 1 \openin 1 #1.JPG \ifeof 1 \openin 1 #1.pdf \ifeof 1 \errhelp = \nopdfimagehelp \errmessage{Could not find image file #1 for pdf}% \else \gdef\pdfimgext{pdf}% \fi \else \gdef\pdfimgext{JPG}% \fi \else \gdef\pdfimgext{jpeg}% \fi \else \gdef\pdfimgext{jpg}% \fi \else \gdef\pdfimgext{png}% \fi \closein 1 \endgroup % % without \immediate, pdftex seg faults when the same image is % included twice. (Version 3.14159-pre-1.0-unofficial-20010704.) \ifnum\pdftexversion < 14 \immediate\pdfimage \else \immediate\pdfximage \fi \ifdim \wd0 >0pt width \imagewidth \fi \ifdim \wd2 >0pt height \imageheight \fi \ifnum\pdftexversion<13 #1.\pdfimgext \else {#1.\pdfimgext}% \fi \ifnum\pdftexversion < 14 \else \pdfrefximage \pdflastximage \fi} % \def\pdfmkdest#1{{% % We have to set dummies so commands such as @code, and characters % such as \, aren't expanded when present in a section title. \indexnofonts \turnoffactive \activebackslashdouble \makevalueexpandable \def\pdfdestname{#1}% \backslashparens\pdfdestname \safewhatsit{\pdfdest name{\pdfdestname} xyz}% }} % % used to mark target names; must be expandable. \def\pdfmkpgn#1{#1} % % by default, use a color that is dark enough to print on paper as % nearly black, but still distinguishable for online viewing. % (Defined in pdfcolor.tex.) \let\urlcolor = \BrickRed \let\linkcolor = \BrickRed \def\endlink{\Black\pdfendlink} % % Adding outlines to PDF; macros for calculating structure of outlines % come from Petr Olsak \def\expnumber#1{\expandafter\ifx\csname#1\endcsname\relax 0% \else \csname#1\endcsname \fi} \def\advancenumber#1{\tempnum=\expnumber{#1}\relax \advance\tempnum by 1 \expandafter\xdef\csname#1\endcsname{\the\tempnum}} % % #1 is the section text, which is what will be displayed in the % outline by the pdf viewer. #2 is the pdf expression for the number % of subentries (or empty, for subsubsections). #3 is the node text, % which might be empty if this toc entry had no corresponding node. % #4 is the page number % \def\dopdfoutline#1#2#3#4{% % Generate a link to the node text if that exists; else, use the % page number. We could generate a destination for the section % text in the case where a section has no node, but it doesn't % seem worth the trouble, since most documents are normally structured. \def\pdfoutlinedest{#3}% \ifx\pdfoutlinedest\empty \def\pdfoutlinedest{#4}% \else % Doubled backslashes in the name. {\activebackslashdouble \xdef\pdfoutlinedest{#3}% \backslashparens\pdfoutlinedest}% \fi % % Also double the backslashes in the display string. {\activebackslashdouble \xdef\pdfoutlinetext{#1}% \backslashparens\pdfoutlinetext}% % \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}% } % \def\pdfmakeoutlines{% \begingroup % Thanh's hack / proper braces in bookmarks \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace % % Read toc silently, to get counts of subentries for \pdfoutline. \def\numchapentry##1##2##3##4{% \def\thischapnum{##2}% \def\thissecnum{0}% \def\thissubsecnum{0}% }% \def\numsecentry##1##2##3##4{% \advancenumber{chap\thischapnum}% \def\thissecnum{##2}% \def\thissubsecnum{0}% }% \def\numsubsecentry##1##2##3##4{% \advancenumber{sec\thissecnum}% \def\thissubsecnum{##2}% }% \def\numsubsubsecentry##1##2##3##4{% \advancenumber{subsec\thissubsecnum}% }% \def\thischapnum{0}% \def\thissecnum{0}% \def\thissubsecnum{0}% % % use \def rather than \let here because we redefine \chapentry et % al. a second time, below. \def\appentry{\numchapentry}% \def\appsecentry{\numsecentry}% \def\appsubsecentry{\numsubsecentry}% \def\appsubsubsecentry{\numsubsubsecentry}% \def\unnchapentry{\numchapentry}% \def\unnsecentry{\numsecentry}% \def\unnsubsecentry{\numsubsecentry}% \def\unnsubsubsecentry{\numsubsubsecentry}% \readdatafile{toc}% % % Read toc second time, this time actually producing the outlines. % The `-' means take the \expnumber as the absolute number of % subentries, which we calculated on our first read of the .toc above. % % We use the node names as the destinations. \def\numchapentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}% \def\numsecentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}% \def\numsubsecentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{subsec##2}}{##3}{##4}}% \def\numsubsubsecentry##1##2##3##4{% count is always zero \dopdfoutline{##1}{}{##3}{##4}}% % % PDF outlines are displayed using system fonts, instead of % document fonts. Therefore we cannot use special characters, % since the encoding is unknown. For example, the eogonek from % Latin 2 (0xea) gets translated to a | character. Info from % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100. % % xx to do this right, we have to translate 8-bit characters to % their "best" equivalent, based on the @documentencoding. Right % now, I guess we'll just let the pdf reader have its way. \indexnofonts \setupdatafile \catcode`\\=\active \otherbackslash \input \jobname.toc \endgroup } % \def\skipspaces#1{\def\PP{#1}\def\D{|}% \ifx\PP\D\let\nextsp\relax \else\let\nextsp\skipspaces \ifx\p\space\else\addtokens{\filename}{\PP}% \advance\filenamelength by 1 \fi \fi \nextsp} \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax} \ifnum\pdftexversion < 14 \let \startlink \pdfannotlink \else \let \startlink \pdfstartlink \fi % make a live url in pdf output. \def\pdfurl#1{% \begingroup % it seems we really need yet another set of dummies; have not % tried to figure out what each command should do in the context % of @url. for now, just make @/ a no-op, that's the only one % people have actually reported a problem with. % \normalturnoffactive \def\@{@}% \let\/=\empty \makevalueexpandable \leavevmode\urlcolor \startlink attr{/Border [0 0 0]}% user{/Subtype /Link /A << /S /URI /URI (#1) >>}% \endgroup} \def\pdfgettoks#1.{\setbox\boxA=\hbox{\toksA={#1.}\toksB={}\maketoks}} \def\addtokens#1#2{\edef\addtoks{\noexpand#1={\the#1#2}}\addtoks} \def\adn#1{\addtokens{\toksC}{#1}\global\countA=1\let\next=\maketoks} \def\poptoks#1#2|ENDTOKS|{\let\first=#1\toksD={#1}\toksA={#2}} \def\maketoks{% \expandafter\poptoks\the\toksA|ENDTOKS|\relax \ifx\first0\adn0 \else\ifx\first1\adn1 \else\ifx\first2\adn2 \else\ifx\first3\adn3 \else\ifx\first4\adn4 \else\ifx\first5\adn5 \else\ifx\first6\adn6 \else\ifx\first7\adn7 \else\ifx\first8\adn8 \else\ifx\first9\adn9 \else \ifnum0=\countA\else\makelink\fi \ifx\first.\let\next=\done\else \let\next=\maketoks \addtokens{\toksB}{\the\toksD} \ifx\first,\addtokens{\toksB}{\space}\fi \fi \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \next} \def\makelink{\addtokens{\toksB}% {\noexpand\pdflink{\the\toksC}}\toksC={}\global\countA=0} \def\pdflink#1{% \startlink attr{/Border [0 0 0]} goto name{\pdfmkpgn{#1}} \linkcolor #1\endlink} \def\done{\edef\st{\global\noexpand\toksA={\the\toksB}}\st} \else \let\pdfmkdest = \gobble \let\pdfurl = \gobble \let\endlink = \relax \let\linkcolor = \relax \let\pdfmakeoutlines = \relax \fi % \ifx\pdfoutput \message{fonts,} % Change the current font style to #1, remembering it in \curfontstyle. % For now, we do not accumulate font styles: @b{@i{foo}} prints foo in % italics, not bold italics. % \def\setfontstyle#1{% \def\curfontstyle{#1}% not as a control sequence, because we are \edef'd. \csname ten#1\endcsname % change the current font } % Select #1 fonts with the current style. % \def\selectfonts#1{\csname #1fonts\endcsname \csname\curfontstyle\endcsname} \def\rm{\fam=0 \setfontstyle{rm}} \def\it{\fam=\itfam \setfontstyle{it}} \def\sl{\fam=\slfam \setfontstyle{sl}} \def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf} \def\tt{\fam=\ttfam \setfontstyle{tt}} % Texinfo sort of supports the sans serif font style, which plain TeX does not. % So we set up a \sf. \newfam\sffam \def\sf{\fam=\sffam \setfontstyle{sf}} \let\li = \sf % Sometimes we call it \li, not \sf. % We don't need math for this font style. \def\ttsl{\setfontstyle{ttsl}} % Default leading. \newdimen\textleading \textleading = 13.2pt % Set the baselineskip to #1, and the lineskip and strut size % correspondingly. There is no deep meaning behind these magic numbers % used as factors; they just match (closely enough) what Knuth defined. % \def\lineskipfactor{.08333} \def\strutheightpercent{.70833} \def\strutdepthpercent {.29167} % \def\setleading#1{% \normalbaselineskip = #1\relax \normallineskip = \lineskipfactor\normalbaselineskip \normalbaselines \setbox\strutbox =\hbox{% \vrule width0pt height\strutheightpercent\baselineskip depth \strutdepthpercent \baselineskip }% } % % PDF CMaps. See also LaTeX's t1.cmap. % % \cmapOT1 \ifpdf \begingroup \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap %%DocumentNeededResources: ProcSet (CIDInit) %%IncludeResource: ProcSet (CIDInit) %%BeginResource: CMap (TeX-OT1-0) %%Title: (TeX-OT1-0 TeX OT1 0) %%Version: 1.000 %%EndComments /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (TeX) /Ordering (OT1) /Supplement 0 >> def /CMapName /TeX-OT1-0 def /CMapType 2 def 1 begincodespacerange <00> <7F> endcodespacerange 8 beginbfrange <00> <01> <0393> <09> <0A> <03A8> <23> <26> <0023> <28> <3B> <0028> <3F> <5B> <003F> <5D> <5E> <005D> <61> <7A> <0061> <7B> <7C> <2013> endbfrange 40 beginbfchar <02> <0398> <03> <039B> <04> <039E> <05> <03A0> <06> <03A3> <07> <03D2> <08> <03A6> <0B> <00660066> <0C> <00660069> <0D> <0066006C> <0E> <006600660069> <0F> <00660066006C> <10> <0131> <11> <0237> <12> <0060> <13> <00B4> <14> <02C7> <15> <02D8> <16> <00AF> <17> <02DA> <18> <00B8> <19> <00DF> <1A> <00E6> <1B> <0153> <1C> <00F8> <1D> <00C6> <1E> <0152> <1F> <00D8> <21> <0021> <22> <201D> <27> <2019> <3C> <00A1> <3D> <003D> <3E> <00BF> <5C> <201C> <5F> <02D9> <60> <2018> <7D> <02DD> <7E> <007E> <7F> <00A8> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end %%EndResource %%EOF }\endgroup \expandafter\edef\csname cmapOT1\endcsname#1{% \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% }% % % \cmapOT1IT \begingroup \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap %%DocumentNeededResources: ProcSet (CIDInit) %%IncludeResource: ProcSet (CIDInit) %%BeginResource: CMap (TeX-OT1IT-0) %%Title: (TeX-OT1IT-0 TeX OT1IT 0) %%Version: 1.000 %%EndComments /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (TeX) /Ordering (OT1IT) /Supplement 0 >> def /CMapName /TeX-OT1IT-0 def /CMapType 2 def 1 begincodespacerange <00> <7F> endcodespacerange 8 beginbfrange <00> <01> <0393> <09> <0A> <03A8> <25> <26> <0025> <28> <3B> <0028> <3F> <5B> <003F> <5D> <5E> <005D> <61> <7A> <0061> <7B> <7C> <2013> endbfrange 42 beginbfchar <02> <0398> <03> <039B> <04> <039E> <05> <03A0> <06> <03A3> <07> <03D2> <08> <03A6> <0B> <00660066> <0C> <00660069> <0D> <0066006C> <0E> <006600660069> <0F> <00660066006C> <10> <0131> <11> <0237> <12> <0060> <13> <00B4> <14> <02C7> <15> <02D8> <16> <00AF> <17> <02DA> <18> <00B8> <19> <00DF> <1A> <00E6> <1B> <0153> <1C> <00F8> <1D> <00C6> <1E> <0152> <1F> <00D8> <21> <0021> <22> <201D> <23> <0023> <24> <00A3> <27> <2019> <3C> <00A1> <3D> <003D> <3E> <00BF> <5C> <201C> <5F> <02D9> <60> <2018> <7D> <02DD> <7E> <007E> <7F> <00A8> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end %%EndResource %%EOF }\endgroup \expandafter\edef\csname cmapOT1IT\endcsname#1{% \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% }% % % \cmapOT1TT \begingroup \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap %%DocumentNeededResources: ProcSet (CIDInit) %%IncludeResource: ProcSet (CIDInit) %%BeginResource: CMap (TeX-OT1TT-0) %%Title: (TeX-OT1TT-0 TeX OT1TT 0) %%Version: 1.000 %%EndComments /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (TeX) /Ordering (OT1TT) /Supplement 0 >> def /CMapName /TeX-OT1TT-0 def /CMapType 2 def 1 begincodespacerange <00> <7F> endcodespacerange 5 beginbfrange <00> <01> <0393> <09> <0A> <03A8> <21> <26> <0021> <28> <5F> <0028> <61> <7E> <0061> endbfrange 32 beginbfchar <02> <0398> <03> <039B> <04> <039E> <05> <03A0> <06> <03A3> <07> <03D2> <08> <03A6> <0B> <2191> <0C> <2193> <0D> <0027> <0E> <00A1> <0F> <00BF> <10> <0131> <11> <0237> <12> <0060> <13> <00B4> <14> <02C7> <15> <02D8> <16> <00AF> <17> <02DA> <18> <00B8> <19> <00DF> <1A> <00E6> <1B> <0153> <1C> <00F8> <1D> <00C6> <1E> <0152> <1F> <00D8> <20> <2423> <27> <2019> <60> <2018> <7F> <00A8> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end %%EndResource %%EOF }\endgroup \expandafter\edef\csname cmapOT1TT\endcsname#1{% \pdffontattr#1{/ToUnicode \the\pdflastobj\space 0 R}% }% \else \expandafter\let\csname cmapOT1\endcsname\gobble \expandafter\let\csname cmapOT1IT\endcsname\gobble \expandafter\let\csname cmapOT1TT\endcsname\gobble \fi % Set the font macro #1 to the font named #2, adding on the % specified font prefix (normally `cm'). % #3 is the font's design size, #4 is a scale factor, #5 is the CMap % encoding (currently only OT1, OT1IT and OT1TT are allowed, pass % empty to omit). \def\setfont#1#2#3#4#5{% \font#1=\fontprefix#2#3 scaled #4 \csname cmap#5\endcsname#1% } % This is what gets called when #5 of \setfont is empty. \let\cmap\gobble % Use cm as the default font prefix. % To specify the font prefix, you must define \fontprefix % before you read in texinfo.tex. \ifx\fontprefix\undefined \def\fontprefix{cm} \fi % Support font families that don't use the same naming scheme as CM. \def\rmshape{r} \def\rmbshape{bx} %where the normal face is bold \def\bfshape{b} \def\bxshape{bx} \def\ttshape{tt} \def\ttbshape{tt} \def\ttslshape{sltt} \def\itshape{ti} \def\itbshape{bxti} \def\slshape{sl} \def\slbshape{bxsl} \def\sfshape{ss} \def\sfbshape{ss} \def\scshape{csc} \def\scbshape{csc} % Definitions for a main text size of 11pt. This is the default in % Texinfo. % \def\definetextfontsizexi{% % Text fonts (11.2pt, magstep1). \def\textnominalsize{11pt} \edef\mainmagstep{\magstephalf} \setfont\textrm\rmshape{10}{\mainmagstep}{OT1} \setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT} \setfont\textbf\bfshape{10}{\mainmagstep}{OT1} \setfont\textit\itshape{10}{\mainmagstep}{OT1IT} \setfont\textsl\slshape{10}{\mainmagstep}{OT1} \setfont\textsf\sfshape{10}{\mainmagstep}{OT1} \setfont\textsc\scshape{10}{\mainmagstep}{OT1} \setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT} \font\texti=cmmi10 scaled \mainmagstep \font\textsy=cmsy10 scaled \mainmagstep % A few fonts for @defun names and args. \setfont\defbf\bfshape{10}{\magstep1}{OT1} \setfont\deftt\ttshape{10}{\magstep1}{OT1TT} \setfont\defttsl\ttslshape{10}{\magstep1}{OT1TT} \def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} % Fonts for indices, footnotes, small examples (9pt). \def\smallnominalsize{9pt} \setfont\smallrm\rmshape{9}{1000}{OT1} \setfont\smalltt\ttshape{9}{1000}{OT1TT} \setfont\smallbf\bfshape{10}{900}{OT1} \setfont\smallit\itshape{9}{1000}{OT1IT} \setfont\smallsl\slshape{9}{1000}{OT1} \setfont\smallsf\sfshape{9}{1000}{OT1} \setfont\smallsc\scshape{10}{900}{OT1} \setfont\smallttsl\ttslshape{10}{900}{OT1TT} \font\smalli=cmmi9 \font\smallsy=cmsy9 % Fonts for small examples (8pt). \def\smallernominalsize{8pt} \setfont\smallerrm\rmshape{8}{1000}{OT1} \setfont\smallertt\ttshape{8}{1000}{OT1TT} \setfont\smallerbf\bfshape{10}{800}{OT1} \setfont\smallerit\itshape{8}{1000}{OT1IT} \setfont\smallersl\slshape{8}{1000}{OT1} \setfont\smallersf\sfshape{8}{1000}{OT1} \setfont\smallersc\scshape{10}{800}{OT1} \setfont\smallerttsl\ttslshape{10}{800}{OT1TT} \font\smalleri=cmmi8 \font\smallersy=cmsy8 % Fonts for title page (20.4pt): \def\titlenominalsize{20pt} \setfont\titlerm\rmbshape{12}{\magstep3}{OT1} \setfont\titleit\itbshape{10}{\magstep4}{OT1IT} \setfont\titlesl\slbshape{10}{\magstep4}{OT1} \setfont\titlett\ttbshape{12}{\magstep3}{OT1TT} \setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT} \setfont\titlesf\sfbshape{17}{\magstep1}{OT1} \let\titlebf=\titlerm \setfont\titlesc\scbshape{10}{\magstep4}{OT1} \font\titlei=cmmi12 scaled \magstep3 \font\titlesy=cmsy10 scaled \magstep4 \def\authorrm{\secrm} \def\authortt{\sectt} % Chapter (and unnumbered) fonts (17.28pt). \def\chapnominalsize{17pt} \setfont\chaprm\rmbshape{12}{\magstep2}{OT1} \setfont\chapit\itbshape{10}{\magstep3}{OT1IT} \setfont\chapsl\slbshape{10}{\magstep3}{OT1} \setfont\chaptt\ttbshape{12}{\magstep2}{OT1TT} \setfont\chapttsl\ttslshape{10}{\magstep3}{OT1TT} \setfont\chapsf\sfbshape{17}{1000}{OT1} \let\chapbf=\chaprm \setfont\chapsc\scbshape{10}{\magstep3}{OT1} \font\chapi=cmmi12 scaled \magstep2 \font\chapsy=cmsy10 scaled \magstep3 % Section fonts (14.4pt). \def\secnominalsize{14pt} \setfont\secrm\rmbshape{12}{\magstep1}{OT1} \setfont\secit\itbshape{10}{\magstep2}{OT1IT} \setfont\secsl\slbshape{10}{\magstep2}{OT1} \setfont\sectt\ttbshape{12}{\magstep1}{OT1TT} \setfont\secttsl\ttslshape{10}{\magstep2}{OT1TT} \setfont\secsf\sfbshape{12}{\magstep1}{OT1} \let\secbf\secrm \setfont\secsc\scbshape{10}{\magstep2}{OT1} \font\seci=cmmi12 scaled \magstep1 \font\secsy=cmsy10 scaled \magstep2 % Subsection fonts (13.15pt). \def\ssecnominalsize{13pt} \setfont\ssecrm\rmbshape{12}{\magstephalf}{OT1} \setfont\ssecit\itbshape{10}{1315}{OT1IT} \setfont\ssecsl\slbshape{10}{1315}{OT1} \setfont\ssectt\ttbshape{12}{\magstephalf}{OT1TT} \setfont\ssecttsl\ttslshape{10}{1315}{OT1TT} \setfont\ssecsf\sfbshape{12}{\magstephalf}{OT1} \let\ssecbf\ssecrm \setfont\ssecsc\scbshape{10}{1315}{OT1} \font\sseci=cmmi12 scaled \magstephalf \font\ssecsy=cmsy10 scaled 1315 % Reduced fonts for @acro in text (10pt). \def\reducednominalsize{10pt} \setfont\reducedrm\rmshape{10}{1000}{OT1} \setfont\reducedtt\ttshape{10}{1000}{OT1TT} \setfont\reducedbf\bfshape{10}{1000}{OT1} \setfont\reducedit\itshape{10}{1000}{OT1IT} \setfont\reducedsl\slshape{10}{1000}{OT1} \setfont\reducedsf\sfshape{10}{1000}{OT1} \setfont\reducedsc\scshape{10}{1000}{OT1} \setfont\reducedttsl\ttslshape{10}{1000}{OT1TT} \font\reducedi=cmmi10 \font\reducedsy=cmsy10 % reset the current fonts \textfonts \rm } % end of 11pt text font size definitions % Definitions to make the main text be 10pt Computer Modern, with % section, chapter, etc., sizes following suit. This is for the GNU % Press printing of the Emacs 22 manual. Maybe other manuals in the % future. Used with @smallbook, which sets the leading to 12pt. % \def\definetextfontsizex{% % Text fonts (10pt). \def\textnominalsize{10pt} \edef\mainmagstep{1000} \setfont\textrm\rmshape{10}{\mainmagstep}{OT1} \setfont\texttt\ttshape{10}{\mainmagstep}{OT1TT} \setfont\textbf\bfshape{10}{\mainmagstep}{OT1} \setfont\textit\itshape{10}{\mainmagstep}{OT1IT} \setfont\textsl\slshape{10}{\mainmagstep}{OT1} \setfont\textsf\sfshape{10}{\mainmagstep}{OT1} \setfont\textsc\scshape{10}{\mainmagstep}{OT1} \setfont\textttsl\ttslshape{10}{\mainmagstep}{OT1TT} \font\texti=cmmi10 scaled \mainmagstep \font\textsy=cmsy10 scaled \mainmagstep % A few fonts for @defun names and args. \setfont\defbf\bfshape{10}{\magstephalf}{OT1} \setfont\deftt\ttshape{10}{\magstephalf}{OT1TT} \setfont\defttsl\ttslshape{10}{\magstephalf}{OT1TT} \def\df{\let\tentt=\deftt \let\tenbf = \defbf \let\tenttsl=\defttsl \bf} % Fonts for indices, footnotes, small examples (9pt). \def\smallnominalsize{9pt} \setfont\smallrm\rmshape{9}{1000}{OT1} \setfont\smalltt\ttshape{9}{1000}{OT1TT} \setfont\smallbf\bfshape{10}{900}{OT1} \setfont\smallit\itshape{9}{1000}{OT1IT} \setfont\smallsl\slshape{9}{1000}{OT1} \setfont\smallsf\sfshape{9}{1000}{OT1} \setfont\smallsc\scshape{10}{900}{OT1} \setfont\smallttsl\ttslshape{10}{900}{OT1TT} \font\smalli=cmmi9 \font\smallsy=cmsy9 % Fonts for small examples (8pt). \def\smallernominalsize{8pt} \setfont\smallerrm\rmshape{8}{1000}{OT1} \setfont\smallertt\ttshape{8}{1000}{OT1TT} \setfont\smallerbf\bfshape{10}{800}{OT1} \setfont\smallerit\itshape{8}{1000}{OT1IT} \setfont\smallersl\slshape{8}{1000}{OT1} \setfont\smallersf\sfshape{8}{1000}{OT1} \setfont\smallersc\scshape{10}{800}{OT1} \setfont\smallerttsl\ttslshape{10}{800}{OT1TT} \font\smalleri=cmmi8 \font\smallersy=cmsy8 % Fonts for title page (20.4pt): \def\titlenominalsize{20pt} \setfont\titlerm\rmbshape{12}{\magstep3}{OT1} \setfont\titleit\itbshape{10}{\magstep4}{OT1IT} \setfont\titlesl\slbshape{10}{\magstep4}{OT1} \setfont\titlett\ttbshape{12}{\magstep3}{OT1TT} \setfont\titlettsl\ttslshape{10}{\magstep4}{OT1TT} \setfont\titlesf\sfbshape{17}{\magstep1}{OT1} \let\titlebf=\titlerm \setfont\titlesc\scbshape{10}{\magstep4}{OT1} \font\titlei=cmmi12 scaled \magstep3 \font\titlesy=cmsy10 scaled \magstep4 \def\authorrm{\secrm} \def\authortt{\sectt} % Chapter fonts (14.4pt). \def\chapnominalsize{14pt} \setfont\chaprm\rmbshape{12}{\magstep1}{OT1} \setfont\chapit\itbshape{10}{\magstep2}{OT1IT} \setfont\chapsl\slbshape{10}{\magstep2}{OT1} \setfont\chaptt\ttbshape{12}{\magstep1}{OT1TT} \setfont\chapttsl\ttslshape{10}{\magstep2}{OT1TT} \setfont\chapsf\sfbshape{12}{\magstep1}{OT1} \let\chapbf\chaprm \setfont\chapsc\scbshape{10}{\magstep2}{OT1} \font\chapi=cmmi12 scaled \magstep1 \font\chapsy=cmsy10 scaled \magstep2 % Section fonts (12pt). \def\secnominalsize{12pt} \setfont\secrm\rmbshape{12}{1000}{OT1} \setfont\secit\itbshape{10}{\magstep1}{OT1IT} \setfont\secsl\slbshape{10}{\magstep1}{OT1} \setfont\sectt\ttbshape{12}{1000}{OT1TT} \setfont\secttsl\ttslshape{10}{\magstep1}{OT1TT} \setfont\secsf\sfbshape{12}{1000}{OT1} \let\secbf\secrm \setfont\secsc\scbshape{10}{\magstep1}{OT1} \font\seci=cmmi12 \font\secsy=cmsy10 scaled \magstep1 % Subsection fonts (10pt). \def\ssecnominalsize{10pt} \setfont\ssecrm\rmbshape{10}{1000}{OT1} \setfont\ssecit\itbshape{10}{1000}{OT1IT} \setfont\ssecsl\slbshape{10}{1000}{OT1} \setfont\ssectt\ttbshape{10}{1000}{OT1TT} \setfont\ssecttsl\ttslshape{10}{1000}{OT1TT} \setfont\ssecsf\sfbshape{10}{1000}{OT1} \let\ssecbf\ssecrm \setfont\ssecsc\scbshape{10}{1000}{OT1} \font\sseci=cmmi10 \font\ssecsy=cmsy10 % Reduced fonts for @acro in text (9pt). \def\reducednominalsize{9pt} \setfont\reducedrm\rmshape{9}{1000}{OT1} \setfont\reducedtt\ttshape{9}{1000}{OT1TT} \setfont\reducedbf\bfshape{10}{900}{OT1} \setfont\reducedit\itshape{9}{1000}{OT1IT} \setfont\reducedsl\slshape{9}{1000}{OT1} \setfont\reducedsf\sfshape{9}{1000}{OT1} \setfont\reducedsc\scshape{10}{900}{OT1} \setfont\reducedttsl\ttslshape{10}{900}{OT1TT} \font\reducedi=cmmi9 \font\reducedsy=cmsy9 % reduce space between paragraphs \divide\parskip by 2 % reset the current fonts \textfonts \rm } % end of 10pt text font size definitions % We provide the user-level command % @fonttextsize 10 % (or 11) to redefine the text font size. pt is assumed. % \def\xword{10} \def\xiword{11} % \parseargdef\fonttextsize{% \def\textsizearg{#1}% \wlog{doing @fonttextsize \textsizearg}% % % Set \globaldefs so that documents can use this inside @tex, since % makeinfo 4.8 does not support it, but we need it nonetheless. % \begingroup \globaldefs=1 \ifx\textsizearg\xword \definetextfontsizex \else \ifx\textsizearg\xiword \definetextfontsizexi \else \errhelp=\EMsimple \errmessage{@fonttextsize only supports `10' or `11', not `\textsizearg'} \fi\fi \endgroup } % In order for the font changes to affect most math symbols and letters, % we have to define the \textfont of the standard families. Since % texinfo doesn't allow for producing subscripts and superscripts except % in the main text, we don't bother to reset \scriptfont and % \scriptscriptfont (which would also require loading a lot more fonts). % \def\resetmathfonts{% \textfont0=\tenrm \textfont1=\teni \textfont2=\tensy \textfont\itfam=\tenit \textfont\slfam=\tensl \textfont\bffam=\tenbf \textfont\ttfam=\tentt \textfont\sffam=\tensf } % The font-changing commands redefine the meanings of \tenSTYLE, instead % of just \STYLE. We do this because \STYLE needs to also set the % current \fam for math mode. Our \STYLE (e.g., \rm) commands hardwire % \tenSTYLE to set the current font. % % Each font-changing command also sets the names \lsize (one size lower) % and \lllsize (three sizes lower). These relative commands are used in % the LaTeX logo and acronyms. % % This all needs generalizing, badly. % \def\textfonts{% \let\tenrm=\textrm \let\tenit=\textit \let\tensl=\textsl \let\tenbf=\textbf \let\tentt=\texttt \let\smallcaps=\textsc \let\tensf=\textsf \let\teni=\texti \let\tensy=\textsy \let\tenttsl=\textttsl \def\curfontsize{text}% \def\lsize{reduced}\def\lllsize{smaller}% \resetmathfonts \setleading{\textleading}} \def\titlefonts{% \let\tenrm=\titlerm \let\tenit=\titleit \let\tensl=\titlesl \let\tenbf=\titlebf \let\tentt=\titlett \let\smallcaps=\titlesc \let\tensf=\titlesf \let\teni=\titlei \let\tensy=\titlesy \let\tenttsl=\titlettsl \def\curfontsize{title}% \def\lsize{chap}\def\lllsize{subsec}% \resetmathfonts \setleading{25pt}} \def\titlefont#1{{\titlefonts\rm #1}} \def\chapfonts{% \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl \let\tenbf=\chapbf \let\tentt=\chaptt \let\smallcaps=\chapsc \let\tensf=\chapsf \let\teni=\chapi \let\tensy=\chapsy \let\tenttsl=\chapttsl \def\curfontsize{chap}% \def\lsize{sec}\def\lllsize{text}% \resetmathfonts \setleading{19pt}} \def\secfonts{% \let\tenrm=\secrm \let\tenit=\secit \let\tensl=\secsl \let\tenbf=\secbf \let\tentt=\sectt \let\smallcaps=\secsc \let\tensf=\secsf \let\teni=\seci \let\tensy=\secsy \let\tenttsl=\secttsl \def\curfontsize{sec}% \def\lsize{subsec}\def\lllsize{reduced}% \resetmathfonts \setleading{16pt}} \def\subsecfonts{% \let\tenrm=\ssecrm \let\tenit=\ssecit \let\tensl=\ssecsl \let\tenbf=\ssecbf \let\tentt=\ssectt \let\smallcaps=\ssecsc \let\tensf=\ssecsf \let\teni=\sseci \let\tensy=\ssecsy \let\tenttsl=\ssecttsl \def\curfontsize{ssec}% \def\lsize{text}\def\lllsize{small}% \resetmathfonts \setleading{15pt}} \let\subsubsecfonts = \subsecfonts \def\reducedfonts{% \let\tenrm=\reducedrm \let\tenit=\reducedit \let\tensl=\reducedsl \let\tenbf=\reducedbf \let\tentt=\reducedtt \let\reducedcaps=\reducedsc \let\tensf=\reducedsf \let\teni=\reducedi \let\tensy=\reducedsy \let\tenttsl=\reducedttsl \def\curfontsize{reduced}% \def\lsize{small}\def\lllsize{smaller}% \resetmathfonts \setleading{10.5pt}} \def\smallfonts{% \let\tenrm=\smallrm \let\tenit=\smallit \let\tensl=\smallsl \let\tenbf=\smallbf \let\tentt=\smalltt \let\smallcaps=\smallsc \let\tensf=\smallsf \let\teni=\smalli \let\tensy=\smallsy \let\tenttsl=\smallttsl \def\curfontsize{small}% \def\lsize{smaller}\def\lllsize{smaller}% \resetmathfonts \setleading{10.5pt}} \def\smallerfonts{% \let\tenrm=\smallerrm \let\tenit=\smallerit \let\tensl=\smallersl \let\tenbf=\smallerbf \let\tentt=\smallertt \let\smallcaps=\smallersc \let\tensf=\smallersf \let\teni=\smalleri \let\tensy=\smallersy \let\tenttsl=\smallerttsl \def\curfontsize{smaller}% \def\lsize{smaller}\def\lllsize{smaller}% \resetmathfonts \setleading{9.5pt}} % Set the fonts to use with the @small... environments. \let\smallexamplefonts = \smallfonts % About \smallexamplefonts. If we use \smallfonts (9pt), @smallexample % can fit this many characters: % 8.5x11=86 smallbook=72 a4=90 a5=69 % If we use \scriptfonts (8pt), then we can fit this many characters: % 8.5x11=90+ smallbook=80 a4=90+ a5=77 % For me, subjectively, the few extra characters that fit aren't worth % the additional smallness of 8pt. So I'm making the default 9pt. % % By the way, for comparison, here's what fits with @example (10pt): % 8.5x11=71 smallbook=60 a4=75 a5=58 % % I wish the USA used A4 paper. % --karl, 24jan03. % Set up the default fonts, so we can use them for creating boxes. % \definetextfontsizexi % Define these so they can be easily changed for other fonts. \def\angleleft{$\langle$} \def\angleright{$\rangle$} % Count depth in font-changes, for error checks \newcount\fontdepth \fontdepth=0 % Fonts for short table of contents. \setfont\shortcontrm\rmshape{12}{1000}{OT1} \setfont\shortcontbf\bfshape{10}{\magstep1}{OT1} % no cmb12 \setfont\shortcontsl\slshape{12}{1000}{OT1} \setfont\shortconttt\ttshape{12}{1000}{OT1TT} %% Add scribe-like font environments, plus @l for inline lisp (usually sans %% serif) and @ii for TeX italic % \smartitalic{ARG} outputs arg in italics, followed by an italic correction % unless the following character is such as not to need one. \def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else \ptexslash\fi\fi\fi} \def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx} \def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx} % like \smartslanted except unconditionally uses \ttsl. % @var is set to this for defun arguments. \def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx} % like \smartslanted except unconditionally use \sl. We never want % ttsl for book titles, do we? \def\cite#1{{\sl #1}\futurelet\next\smartitalicx} \let\i=\smartitalic \let\slanted=\smartslanted \let\var=\smartslanted \let\dfn=\smartslanted \let\emph=\smartitalic % @b, explicit bold. \def\b#1{{\bf #1}} \let\strong=\b % @sansserif, explicit sans. \def\sansserif#1{{\sf #1}} % We can't just use \exhyphenpenalty, because that only has effect at % the end of a paragraph. Restore normal hyphenation at the end of the % group within which \nohyphenation is presumably called. % \def\nohyphenation{\hyphenchar\font = -1 \aftergroup\restorehyphenation} \def\restorehyphenation{\hyphenchar\font = `- } % Set sfcode to normal for the chars that usually have another value. % Can't use plain's \frenchspacing because it uses the `\x notation, and % sometimes \x has an active definition that messes things up. % \catcode`@=11 \def\plainfrenchspacing{% \sfcode\dotChar =\@m \sfcode\questChar=\@m \sfcode\exclamChar=\@m \sfcode\colonChar=\@m \sfcode\semiChar =\@m \sfcode\commaChar =\@m \def\endofsentencespacefactor{1000}% for @. and friends } \def\plainnonfrenchspacing{% \sfcode`\.3000\sfcode`\?3000\sfcode`\!3000 \sfcode`\:2000\sfcode`\;1500\sfcode`\,1250 \def\endofsentencespacefactor{3000}% for @. and friends } \catcode`@=\other \def\endofsentencespacefactor{3000}% default \def\t#1{% {\tt \rawbackslash \plainfrenchspacing #1}% \null } \def\samp#1{`\tclose{#1}'\null} \setfont\keyrm\rmshape{8}{1000}{OT1} \font\keysy=cmsy9 \def\key#1{{\keyrm\textfont2=\keysy \leavevmode\hbox{% \raise0.4pt\hbox{\angleleft}\kern-.08em\vtop{% \vbox{\hrule\kern-0.4pt \hbox{\raise0.4pt\hbox{\vphantom{\angleleft}}#1}}% \kern-0.4pt\hrule}% \kern-.06em\raise0.4pt\hbox{\angleright}}}} \def\key #1{{\nohyphenation \uppercase{#1}}\null} % The old definition, with no lozenge: %\def\key #1{{\ttsl \nohyphenation \uppercase{#1}}\null} \def\ctrl #1{{\tt \rawbackslash \hat}#1} % @file, @option are the same as @samp. \let\file=\samp \let\option=\samp % @code is a modification of @t, % which makes spaces the same size as normal in the surrounding text. \def\tclose#1{% {% % Change normal interword space to be same as for the current font. \spaceskip = \fontdimen2\font % % Switch to typewriter. \tt % % But `\ ' produces the large typewriter interword space. \def\ {{\spaceskip = 0pt{} }}% % % Turn off hyphenation. \nohyphenation % \rawbackslash \plainfrenchspacing #1% }% \null } % We *must* turn on hyphenation at `-' and `_' in @code. % Otherwise, it is too hard to avoid overfull hboxes % in the Emacs manual, the Library manual, etc. % Unfortunately, TeX uses one parameter (\hyphenchar) to control % both hyphenation at - and hyphenation within words. % We must therefore turn them both off (\tclose does that) % and arrange explicitly to hyphenate at a dash. % -- rms. { \catcode`\-=\active \catcode`\_=\active \catcode`\'=\active \catcode`\`=\active % \global\def\code{\begingroup \catcode\rquoteChar=\active \catcode\lquoteChar=\active \let'\codequoteright \let`\codequoteleft % \catcode\dashChar=\active \catcode\underChar=\active \ifallowcodebreaks \let-\codedash \let_\codeunder \else \let-\realdash \let_\realunder \fi \codex } } \def\realdash{-} \def\codedash{-\discretionary{}{}{}} \def\codeunder{% % this is all so @math{@code{var_name}+1} can work. In math mode, _ % is "active" (mathcode"8000) and \normalunderscore (or \char95, etc.) % will therefore expand the active definition of _, which is us % (inside @code that is), therefore an endless loop. \ifusingtt{\ifmmode \mathchar"075F % class 0=ordinary, family 7=ttfam, pos 0x5F=_. \else\normalunderscore \fi \discretionary{}{}{}}% {\_}% } \def\codex #1{\tclose{#1}\endgroup} % An additional complication: the above will allow breaks after, e.g., % each of the four underscores in __typeof__. This is undesirable in % some manuals, especially if they don't have long identifiers in % general. @allowcodebreaks provides a way to control this. % \newif\ifallowcodebreaks \allowcodebreakstrue \def\keywordtrue{true} \def\keywordfalse{false} \parseargdef\allowcodebreaks{% \def\txiarg{#1}% \ifx\txiarg\keywordtrue \allowcodebreakstrue \else\ifx\txiarg\keywordfalse \allowcodebreaksfalse \else \errhelp = \EMsimple \errmessage{Unknown @allowcodebreaks option `\txiarg'}% \fi\fi } % @kbd is like @code, except that if the argument is just one @key command, % then @kbd has no effect. % @kbdinputstyle -- arg is `distinct' (@kbd uses slanted tty font always), % `example' (@kbd uses ttsl only inside of @example and friends), % or `code' (@kbd uses normal tty font always). \parseargdef\kbdinputstyle{% \def\txiarg{#1}% \ifx\txiarg\worddistinct \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\ttsl}% \else\ifx\txiarg\wordexample \gdef\kbdexamplefont{\ttsl}\gdef\kbdfont{\tt}% \else\ifx\txiarg\wordcode \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}% \else \errhelp = \EMsimple \errmessage{Unknown @kbdinputstyle option `\txiarg'}% \fi\fi\fi } \def\worddistinct{distinct} \def\wordexample{example} \def\wordcode{code} % Default is `distinct.' \kbdinputstyle distinct \def\xkey{\key} \def\kbdfoo#1#2#3\par{\def\one{#1}\def\three{#3}\def\threex{??}% \ifx\one\xkey\ifx\threex\three \key{#2}% \else{\tclose{\kbdfont\look}}\fi \else{\tclose{\kbdfont\look}}\fi} % For @indicateurl, @env, @command quotes seem unnecessary, so use \code. \let\indicateurl=\code \let\env=\code \let\command=\code % @uref (abbreviation for `urlref') takes an optional (comma-separated) % second argument specifying the text to display and an optional third % arg as text to display instead of (rather than in addition to) the url % itself. First (mandatory) arg is the url. Perhaps eventually put in % a hypertex \special here. % \def\uref#1{\douref #1,,,\finish} \def\douref#1,#2,#3,#4\finish{\begingroup \unsepspaces \pdfurl{#1}% \setbox0 = \hbox{\ignorespaces #3}% \ifdim\wd0 > 0pt \unhbox0 % third arg given, show only that \else \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0 > 0pt \ifpdf \unhbox0 % PDF: 2nd arg given, show only it \else \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url \fi \else \code{#1}% only url given, so show it \fi \fi \endlink \endgroup} % @url synonym for @uref, since that's how everyone uses it. % \let\url=\uref % rms does not like angle brackets --karl, 17may97. % So now @email is just like @uref, unless we are pdf. % %\def\email#1{\angleleft{\tt #1}\angleright} \ifpdf \def\email#1{\doemail#1,,\finish} \def\doemail#1,#2,#3\finish{\begingroup \unsepspaces \pdfurl{mailto:#1}% \setbox0 = \hbox{\ignorespaces #2}% \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi \endlink \endgroup} \else \let\email=\uref \fi % Check if we are currently using a typewriter font. Since all the % Computer Modern typewriter fonts have zero interword stretch (and % shrink), and it is reasonable to expect all typewriter fonts to have % this property, we can check that font parameter. % \def\ifmonospace{\ifdim\fontdimen3\font=0pt } % Typeset a dimension, e.g., `in' or `pt'. The only reason for the % argument is to make the input look right: @dmn{pt} instead of @dmn{}pt. % \def\dmn#1{\thinspace #1} \def\kbd#1{\def\look{#1}\expandafter\kbdfoo\look??\par} % @l was never documented to mean ``switch to the Lisp font'', % and it is not used as such in any manual I can find. We need it for % Polish suppressed-l. --karl, 22sep96. %\def\l#1{{\li #1}\null} % Explicit font changes: @r, @sc, undocumented @ii. \def\r#1{{\rm #1}} % roman font \def\sc#1{{\smallcaps#1}} % smallcaps font \def\ii#1{{\it #1}} % italic font % @acronym for "FBI", "NATO", and the like. % We print this one point size smaller, since it's intended for % all-uppercase. % \def\acronym#1{\doacronym #1,,\finish} \def\doacronym#1,#2,#3\finish{% {\selectfonts\lsize #1}% \def\temp{#2}% \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi } % @abbr for "Comput. J." and the like. % No font change, but don't do end-of-sentence spacing. % \def\abbr#1{\doabbr #1,,\finish} \def\doabbr#1,#2,#3\finish{% {\plainfrenchspacing #1}% \def\temp{#2}% \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi } % @pounds{} is a sterling sign, which Knuth put in the CM italic font. % \def\pounds{{\it\$}} % @euro{} comes from a separate font, depending on the current style. % We use the free feym* fonts from the eurosym package by Henrik % Theiling, which support regular, slanted, bold and bold slanted (and % "outlined" (blackboard board, sort of) versions, which we don't need). % It is available from http://www.ctan.org/tex-archive/fonts/eurosym. % % Although only regular is the truly official Euro symbol, we ignore % that. The Euro is designed to be slightly taller than the regular % font height. % % feymr - regular % feymo - slanted % feybr - bold % feybo - bold slanted % % There is no good (free) typewriter version, to my knowledge. % A feymr10 euro is ~7.3pt wide, while a normal cmtt10 char is ~5.25pt wide. % Hmm. % % Also doesn't work in math. Do we need to do math with euro symbols? % Hope not. % % \def\euro{{\eurofont e}} \def\eurofont{% % We set the font at each command, rather than predefining it in % \textfonts and the other font-switching commands, so that % installations which never need the symbol don't have to have the % font installed. % % There is only one designed size (nominal 10pt), so we always scale % that to the current nominal size. % % By the way, simply using "at 1em" works for cmr10 and the like, but % does not work for cmbx10 and other extended/shrunken fonts. % \def\eurosize{\csname\curfontsize nominalsize\endcsname}% % \ifx\curfontstyle\bfstylename % bold: \font\thiseurofont = \ifusingit{feybo10}{feybr10} at \eurosize \else % regular: \font\thiseurofont = \ifusingit{feymo10}{feymr10} at \eurosize \fi \thiseurofont } % @registeredsymbol - R in a circle. The font for the R should really % be smaller yet, but lllsize is the best we can do for now. % Adapted from the plain.tex definition of \copyright. % \def\registeredsymbol{% $^{{\ooalign{\hfil\raise.07ex\hbox{\selectfonts\lllsize R}% \hfil\crcr\Orb}}% }$% } % @textdegree - the normal degrees sign. % \def\textdegree{$^\circ$} % Laurent Siebenmann reports \Orb undefined with: % Textures 1.7.7 (preloaded format=plain 93.10.14) (68K) 16 APR 2004 02:38 % so we'll define it if necessary. % \ifx\Orb\undefined \def\Orb{\mathhexbox20D} \fi \message{page headings,} \newskip\titlepagetopglue \titlepagetopglue = 1.5in \newskip\titlepagebottomglue \titlepagebottomglue = 2pc % First the title page. Must do @settitle before @titlepage. \newif\ifseenauthor \newif\iffinishedtitlepage % Do an implicit @contents or @shortcontents after @end titlepage if the % user says @setcontentsaftertitlepage or @setshortcontentsaftertitlepage. % \newif\ifsetcontentsaftertitlepage \let\setcontentsaftertitlepage = \setcontentsaftertitlepagetrue \newif\ifsetshortcontentsaftertitlepage \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue \parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}% \endgroup\page\hbox{}\page} \envdef\titlepage{% % Open one extra group, as we want to close it in the middle of \Etitlepage. \begingroup \parindent=0pt \textfonts % Leave some space at the very top of the page. \vglue\titlepagetopglue % No rule at page bottom unless we print one at the top with @title. \finishedtitlepagetrue % % Most title ``pages'' are actually two pages long, with space % at the top of the second. We don't want the ragged left on the second. \let\oldpage = \page \def\page{% \iffinishedtitlepage\else \finishtitlepage \fi \let\page = \oldpage \page \null }% } \def\Etitlepage{% \iffinishedtitlepage\else \finishtitlepage \fi % It is important to do the page break before ending the group, % because the headline and footline are only empty inside the group. % If we use the new definition of \page, we always get a blank page % after the title page, which we certainly don't want. \oldpage \endgroup % % Need this before the \...aftertitlepage checks so that if they are % in effect the toc pages will come out with page numbers. \HEADINGSon % % If they want short, they certainly want long too. \ifsetshortcontentsaftertitlepage \shortcontents \contents \global\let\shortcontents = \relax \global\let\contents = \relax \fi % \ifsetcontentsaftertitlepage \contents \global\let\contents = \relax \global\let\shortcontents = \relax \fi } \def\finishtitlepage{% \vskip4pt \hrule height 2pt width \hsize \vskip\titlepagebottomglue \finishedtitlepagetrue } %%% Macros to be used within @titlepage: \let\subtitlerm=\tenrm \def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines} \def\authorfont{\authorrm \normalbaselineskip = 16pt \normalbaselines \let\tt=\authortt} \parseargdef\title{% \checkenv\titlepage \leftline{\titlefonts\rm #1} % print a rule at the page bottom also. \finishedtitlepagefalse \vskip4pt \hrule height 4pt width \hsize \vskip4pt } \parseargdef\subtitle{% \checkenv\titlepage {\subtitlefont \rightline{#1}}% } % @author should come last, but may come many times. % It can also be used inside @quotation. % \parseargdef\author{% \def\temp{\quotation}% \ifx\thisenv\temp \def\quotationauthor{#1}% printed in \Equotation. \else \checkenv\titlepage \ifseenauthor\else \vskip 0pt plus 1filll \seenauthortrue \fi {\authorfont \leftline{#1}}% \fi } %%% Set up page headings and footings. \let\thispage=\folio \newtoks\evenheadline % headline on even pages \newtoks\oddheadline % headline on odd pages \newtoks\evenfootline % footline on even pages \newtoks\oddfootline % footline on odd pages % Now make TeX use those variables \headline={{\textfonts\rm \ifodd\pageno \the\oddheadline \else \the\evenheadline \fi}} \footline={{\textfonts\rm \ifodd\pageno \the\oddfootline \else \the\evenfootline \fi}\HEADINGShook} \let\HEADINGShook=\relax % Commands to set those variables. % For example, this is what @headings on does % @evenheading @thistitle|@thispage|@thischapter % @oddheading @thischapter|@thispage|@thistitle % @evenfooting @thisfile|| % @oddfooting ||@thisfile \def\evenheading{\parsearg\evenheadingxxx} \def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish} \def\evenheadingyyy #1\|#2\|#3\|#4\finish{% \global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \def\oddheading{\parsearg\oddheadingxxx} \def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish} \def\oddheadingyyy #1\|#2\|#3\|#4\finish{% \global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}% \def\evenfooting{\parsearg\evenfootingxxx} \def\evenfootingxxx #1{\evenfootingyyy #1\|\|\|\|\finish} \def\evenfootingyyy #1\|#2\|#3\|#4\finish{% \global\evenfootline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} \def\oddfooting{\parsearg\oddfootingxxx} \def\oddfootingxxx #1{\oddfootingyyy #1\|\|\|\|\finish} \def\oddfootingyyy #1\|#2\|#3\|#4\finish{% \global\oddfootline = {\rlap{\centerline{#2}}\line{#1\hfil#3}}% % % Leave some space for the footline. Hopefully ok to assume % @evenfooting will not be used by itself. \global\advance\pageheight by -12pt \global\advance\vsize by -12pt } \parseargdef\everyfooting{\oddfootingxxx{#1}\evenfootingxxx{#1}} % @headings double turns headings on for double-sided printing. % @headings single turns headings on for single-sided printing. % @headings off turns them off. % @headings on same as @headings double, retained for compatibility. % @headings after turns on double-sided headings after this page. % @headings doubleafter turns on double-sided headings after this page. % @headings singleafter turns on single-sided headings after this page. % By default, they are off at the start of a document, % and turned `on' after @end titlepage. \def\headings #1 {\csname HEADINGS#1\endcsname} \def\HEADINGSoff{% \global\evenheadline={\hfil} \global\evenfootline={\hfil} \global\oddheadline={\hfil} \global\oddfootline={\hfil}} \HEADINGSoff % When we turn headings on, set the page number to 1. % For double-sided printing, put current file name in lower left corner, % chapter name on inside top of right hand pages, document % title on inside top of left hand pages, and page numbers on outside top % edge of all pages. \def\HEADINGSdouble{% \global\pageno=1 \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\folio\hfil\thistitle}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chapoddpage } \let\contentsalignmacro = \chappager % For single-sided printing, chapter title goes across top left of page, % page number on top right. \def\HEADINGSsingle{% \global\pageno=1 \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\thischapter\hfil\folio}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chappager } \def\HEADINGSon{\HEADINGSdouble} \def\HEADINGSafter{\let\HEADINGShook=\HEADINGSdoublex} \let\HEADINGSdoubleafter=\HEADINGSafter \def\HEADINGSdoublex{% \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\folio\hfil\thistitle}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chapoddpage } \def\HEADINGSsingleafter{\let\HEADINGShook=\HEADINGSsinglex} \def\HEADINGSsinglex{% \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\thischapter\hfil\folio}} \global\oddheadline={\line{\thischapter\hfil\folio}} \global\let\contentsalignmacro = \chappager } % Subroutines used in generating headings % This produces Day Month Year style of output. % Only define if not already defined, in case a txi-??.tex file has set % up a different format (e.g., txi-cs.tex does this). \ifx\today\undefined \def\today{% \number\day\space \ifcase\month \or\putwordMJan\or\putwordMFeb\or\putwordMMar\or\putwordMApr \or\putwordMMay\or\putwordMJun\or\putwordMJul\or\putwordMAug \or\putwordMSep\or\putwordMOct\or\putwordMNov\or\putwordMDec \fi \space\number\year} \fi % @settitle line... specifies the title of the document, for headings. % It generates no output of its own. \def\thistitle{\putwordNoTitle} \def\settitle{\parsearg{\gdef\thistitle}} \message{tables,} % Tables -- @table, @ftable, @vtable, @item(x). % default indentation of table text \newdimen\tableindent \tableindent=.8in % default indentation of @itemize and @enumerate text \newdimen\itemindent \itemindent=.3in % margin between end of table item and start of table text. \newdimen\itemmargin \itemmargin=.1in % used internally for \itemindent minus \itemmargin \newdimen\itemmax % Note @table, @ftable, and @vtable define @item, @itemx, etc., with % these defs. % They also define \itemindex % to index the item name in whatever manner is desired (perhaps none). \newif\ifitemxneedsnegativevskip \def\itemxpar{\par\ifitemxneedsnegativevskip\nobreak\vskip-\parskip\nobreak\fi} \def\internalBitem{\smallbreak \parsearg\itemzzz} \def\internalBitemx{\itemxpar \parsearg\itemzzz} \def\itemzzz #1{\begingroup % \advance\hsize by -\rightskip \advance\hsize by -\tableindent \setbox0=\hbox{\itemindicate{#1}}% \itemindex{#1}% \nobreak % This prevents a break before @itemx. % % If the item text does not fit in the space we have, put it on a line % by itself, and do not allow a page break either before or after that % line. We do not start a paragraph here because then if the next % command is, e.g., @kindex, the whatsit would get put into the % horizontal list on a line by itself, resulting in extra blank space. \ifdim \wd0>\itemmax % % Make this a paragraph so we get the \parskip glue and wrapping, % but leave it ragged-right. \begingroup \advance\leftskip by-\tableindent \advance\hsize by\tableindent \advance\rightskip by0pt plus1fil \leavevmode\unhbox0\par \endgroup % % We're going to be starting a paragraph, but we don't want the % \parskip glue -- logically it's part of the @item we just started. \nobreak \vskip-\parskip % % Stop a page break at the \parskip glue coming up. However, if % what follows is an environment such as @example, there will be no % \parskip glue; then the negative vskip we just inserted would % cause the example and the item to crash together. So we use this % bizarre value of 10001 as a signal to \aboveenvbreak to insert % \parskip glue after all. Section titles are handled this way also. % \penalty 10001 \endgroup \itemxneedsnegativevskipfalse \else % The item text fits into the space. Start a paragraph, so that the % following text (if any) will end up on the same line. \noindent % Do this with kerns and \unhbox so that if there is a footnote in % the item text, it can migrate to the main vertical list and % eventually be printed. \nobreak\kern-\tableindent \dimen0 = \itemmax \advance\dimen0 by \itemmargin \advance\dimen0 by -\wd0 \unhbox0 \nobreak\kern\dimen0 \endgroup \itemxneedsnegativevskiptrue \fi } \def\item{\errmessage{@item while not in a list environment}} \def\itemx{\errmessage{@itemx while not in a list environment}} % @table, @ftable, @vtable. \envdef\table{% \let\itemindex\gobble \tablecheck{table}% } \envdef\ftable{% \def\itemindex ##1{\doind {fn}{\code{##1}}}% \tablecheck{ftable}% } \envdef\vtable{% \def\itemindex ##1{\doind {vr}{\code{##1}}}% \tablecheck{vtable}% } \def\tablecheck#1{% \ifnum \the\catcode`\^^M=\active \endgroup \errmessage{This command won't work in this context; perhaps the problem is that we are \inenvironment\thisenv}% \def\next{\doignore{#1}}% \else \let\next\tablex \fi \next } \def\tablex#1{% \def\itemindicate{#1}% \parsearg\tabley } \def\tabley#1{% {% \makevalueexpandable \edef\temp{\noexpand\tablez #1\space\space\space}% \expandafter }\temp \endtablez } \def\tablez #1 #2 #3 #4\endtablez{% \aboveenvbreak \ifnum 0#1>0 \advance \leftskip by #1\mil \fi \ifnum 0#2>0 \tableindent=#2\mil \fi \ifnum 0#3>0 \advance \rightskip by #3\mil \fi \itemmax=\tableindent \advance \itemmax by -\itemmargin \advance \leftskip by \tableindent \exdentamount=\tableindent \parindent = 0pt \parskip = \smallskipamount \ifdim \parskip=0pt \parskip=2pt \fi \let\item = \internalBitem \let\itemx = \internalBitemx } \def\Etable{\endgraf\afterenvbreak} \let\Eftable\Etable \let\Evtable\Etable \let\Eitemize\Etable \let\Eenumerate\Etable % This is the counter used by @enumerate, which is really @itemize \newcount \itemno \envdef\itemize{\parsearg\doitemize} \def\doitemize#1{% \aboveenvbreak \itemmax=\itemindent \advance\itemmax by -\itemmargin \advance\leftskip by \itemindent \exdentamount=\itemindent \parindent=0pt \parskip=\smallskipamount \ifdim\parskip=0pt \parskip=2pt \fi \def\itemcontents{#1}% % @itemize with no arg is equivalent to @itemize @bullet. \ifx\itemcontents\empty\def\itemcontents{\bullet}\fi \let\item=\itemizeitem } % Definition of @item while inside @itemize and @enumerate. % \def\itemizeitem{% \advance\itemno by 1 % for enumerations {\let\par=\endgraf \smallbreak}% reasonable place to break {% % If the document has an @itemize directly after a section title, a % \nobreak will be last on the list, and \sectionheading will have % done a \vskip-\parskip. In that case, we don't want to zero % parskip, or the item text will crash with the heading. On the % other hand, when there is normal text preceding the item (as there % usually is), we do want to zero parskip, or there would be too much % space. In that case, we won't have a \nobreak before. At least % that's the theory. \ifnum\lastpenalty<10000 \parskip=0in \fi \noindent \hbox to 0pt{\hss \itemcontents \kern\itemmargin}% \vadjust{\penalty 1200}}% not good to break after first line of item. \flushcr } % \splitoff TOKENS\endmark defines \first to be the first token in % TOKENS, and \rest to be the remainder. % \def\splitoff#1#2\endmark{\def\first{#1}\def\rest{#2}}% % Allow an optional argument of an uppercase letter, lowercase letter, % or number, to specify the first label in the enumerated list. No % argument is the same as `1'. % \envparseargdef\enumerate{\enumeratey #1 \endenumeratey} \def\enumeratey #1 #2\endenumeratey{% % If we were given no argument, pretend we were given `1'. \def\thearg{#1}% \ifx\thearg\empty \def\thearg{1}\fi % % Detect if the argument is a single token. If so, it might be a % letter. Otherwise, the only valid thing it can be is a number. % (We will always have one token, because of the test we just made. % This is a good thing, since \splitoff doesn't work given nothing at % all -- the first parameter is undelimited.) \expandafter\splitoff\thearg\endmark \ifx\rest\empty % Only one token in the argument. It could still be anything. % A ``lowercase letter'' is one whose \lccode is nonzero. % An ``uppercase letter'' is one whose \lccode is both nonzero, and % not equal to itself. % Otherwise, we assume it's a number. % % We need the \relax at the end of the \ifnum lines to stop TeX from % continuing to look for a . % \ifnum\lccode\expandafter`\thearg=0\relax \numericenumerate % a number (we hope) \else % It's a letter. \ifnum\lccode\expandafter`\thearg=\expandafter`\thearg\relax \lowercaseenumerate % lowercase letter \else \uppercaseenumerate % uppercase letter \fi \fi \else % Multiple tokens in the argument. We hope it's a number. \numericenumerate \fi } % An @enumerate whose labels are integers. The starting integer is % given in \thearg. % \def\numericenumerate{% \itemno = \thearg \startenumeration{\the\itemno}% } % The starting (lowercase) letter is in \thearg. \def\lowercaseenumerate{% \itemno = \expandafter`\thearg \startenumeration{% % Be sure we're not beyond the end of the alphabet. \ifnum\itemno=0 \errmessage{No more lowercase letters in @enumerate; get a bigger alphabet}% \fi \char\lccode\itemno }% } % The starting (uppercase) letter is in \thearg. \def\uppercaseenumerate{% \itemno = \expandafter`\thearg \startenumeration{% % Be sure we're not beyond the end of the alphabet. \ifnum\itemno=0 \errmessage{No more uppercase letters in @enumerate; get a bigger alphabet} \fi \char\uccode\itemno }% } % Call \doitemize, adding a period to the first argument and supplying the % common last two arguments. Also subtract one from the initial value in % \itemno, since @item increments \itemno. % \def\startenumeration#1{% \advance\itemno by -1 \doitemize{#1.}\flushcr } % @alphaenumerate and @capsenumerate are abbreviations for giving an arg % to @enumerate. % \def\alphaenumerate{\enumerate{a}} \def\capsenumerate{\enumerate{A}} \def\Ealphaenumerate{\Eenumerate} \def\Ecapsenumerate{\Eenumerate} % @multitable macros % Amy Hendrickson, 8/18/94, 3/6/96 % % @multitable ... @end multitable will make as many columns as desired. % Contents of each column will wrap at width given in preamble. Width % can be specified either with sample text given in a template line, % or in percent of \hsize, the current width of text on page. % Table can continue over pages but will only break between lines. % To make preamble: % % Either define widths of columns in terms of percent of \hsize: % @multitable @columnfractions .25 .3 .45 % @item ... % % Numbers following @columnfractions are the percent of the total % current hsize to be used for each column. You may use as many % columns as desired. % Or use a template: % @multitable {Column 1 template} {Column 2 template} {Column 3 template} % @item ... % using the widest term desired in each column. % Each new table line starts with @item, each subsequent new column % starts with @tab. Empty columns may be produced by supplying @tab's % with nothing between them for as many times as empty columns are needed, % ie, @tab@tab@tab will produce two empty columns. % @item, @tab do not need to be on their own lines, but it will not hurt % if they are. % Sample multitable: % @multitable {Column 1 template} {Column 2 template} {Column 3 template} % @item first col stuff @tab second col stuff @tab third col % @item % first col stuff % @tab % second col stuff % @tab % third col % @item first col stuff @tab second col stuff % @tab Many paragraphs of text may be used in any column. % % They will wrap at the width determined by the template. % @item@tab@tab This will be in third column. % @end multitable % Default dimensions may be reset by user. % @multitableparskip is vertical space between paragraphs in table. % @multitableparindent is paragraph indent in table. % @multitablecolmargin is horizontal space to be left between columns. % @multitablelinespace is space to leave between table items, baseline % to baseline. % 0pt means it depends on current normal line spacing. % \newskip\multitableparskip \newskip\multitableparindent \newdimen\multitablecolspace \newskip\multitablelinespace \multitableparskip=0pt \multitableparindent=6pt \multitablecolspace=12pt \multitablelinespace=0pt % Macros used to set up halign preamble: % \let\endsetuptable\relax \def\xendsetuptable{\endsetuptable} \let\columnfractions\relax \def\xcolumnfractions{\columnfractions} \newif\ifsetpercent % #1 is the @columnfraction, usually a decimal number like .5, but might % be just 1. We just use it, whatever it is. % \def\pickupwholefraction#1 {% \global\advance\colcount by 1 \expandafter\xdef\csname col\the\colcount\endcsname{#1\hsize}% \setuptable } \newcount\colcount \def\setuptable#1{% \def\firstarg{#1}% \ifx\firstarg\xendsetuptable \let\go = \relax \else \ifx\firstarg\xcolumnfractions \global\setpercenttrue \else \ifsetpercent \let\go\pickupwholefraction \else \global\advance\colcount by 1 \setbox0=\hbox{#1\unskip\space}% Add a normal word space as a % separator; typically that is always in the input, anyway. \expandafter\xdef\csname col\the\colcount\endcsname{\the\wd0}% \fi \fi \ifx\go\pickupwholefraction % Put the argument back for the \pickupwholefraction call, so % we'll always have a period there to be parsed. \def\go{\pickupwholefraction#1}% \else \let\go = \setuptable \fi% \fi \go } % multitable-only commands. % % @headitem starts a heading row, which we typeset in bold. % Assignments have to be global since we are inside the implicit group % of an alignment entry. Note that \everycr resets \everytab. \def\headitem{\checkenv\multitable \crcr \global\everytab={\bf}\the\everytab}% % % A \tab used to include \hskip1sp. But then the space in a template % line is not enough. That is bad. So let's go back to just `&' until % we encounter the problem it was intended to solve again. % --karl, nathan@acm.org, 20apr99. \def\tab{\checkenv\multitable &\the\everytab}% % @multitable ... @end multitable definitions: % \newtoks\everytab % insert after every tab. % \envdef\multitable{% \vskip\parskip \startsavinginserts % % @item within a multitable starts a normal row. % We use \def instead of \let so that if one of the multitable entries % contains an @itemize, we don't choke on the \item (seen as \crcr aka % \endtemplate) expanding \doitemize. \def\item{\crcr}% % \tolerance=9500 \hbadness=9500 \setmultitablespacing \parskip=\multitableparskip \parindent=\multitableparindent \overfullrule=0pt \global\colcount=0 % \everycr = {% \noalign{% \global\everytab={}% \global\colcount=0 % Reset the column counter. % Check for saved footnotes, etc. \checkinserts % Keeps underfull box messages off when table breaks over pages. %\filbreak % Maybe so, but it also creates really weird page breaks when the % table breaks over pages. Wouldn't \vfil be better? Wait until the % problem manifests itself, so it can be fixed for real --karl. }% }% % \parsearg\domultitable } \def\domultitable#1{% % To parse everything between @multitable and @item: \setuptable#1 \endsetuptable % % This preamble sets up a generic column definition, which will % be used as many times as user calls for columns. % \vtop will set a single line and will also let text wrap and % continue for many paragraphs if desired. \halign\bgroup &% \global\advance\colcount by 1 \multistrut \vtop{% % Use the current \colcount to find the correct column width: \hsize=\expandafter\csname col\the\colcount\endcsname % % In order to keep entries from bumping into each other % we will add a \leftskip of \multitablecolspace to all columns after % the first one. % % If a template has been used, we will add \multitablecolspace % to the width of each template entry. % % If the user has set preamble in terms of percent of \hsize we will % use that dimension as the width of the column, and the \leftskip % will keep entries from bumping into each other. Table will start at % left margin and final column will justify at right margin. % % Make sure we don't inherit \rightskip from the outer environment. \rightskip=0pt \ifnum\colcount=1 % The first column will be indented with the surrounding text. \advance\hsize by\leftskip \else \ifsetpercent \else % If user has not set preamble in terms of percent of \hsize % we will advance \hsize by \multitablecolspace. \advance\hsize by \multitablecolspace \fi % In either case we will make \leftskip=\multitablecolspace: \leftskip=\multitablecolspace \fi % Ignoring space at the beginning and end avoids an occasional spurious % blank line, when TeX decides to break the line at the space before the % box from the multistrut, so the strut ends up on a line by itself. % For example: % @multitable @columnfractions .11 .89 % @item @code{#} % @tab Legal holiday which is valid in major parts of the whole country. % Is automatically provided with highlighting sequences respectively % marking characters. \noindent\ignorespaces##\unskip\multistrut }\cr } \def\Emultitable{% \crcr \egroup % end the \halign \global\setpercentfalse } \def\setmultitablespacing{% \def\multistrut{\strut}% just use the standard line spacing % % Compute \multitablelinespace (if not defined by user) for use in % \multitableparskip calculation. We used define \multistrut based on % this, but (ironically) that caused the spacing to be off. % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100. \ifdim\multitablelinespace=0pt \setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip \global\advance\multitablelinespace by-\ht0 \fi %% Test to see if parskip is larger than space between lines of %% table. If not, do nothing. %% If so, set to same dimension as multitablelinespace. \ifdim\multitableparskip>\multitablelinespace \global\multitableparskip=\multitablelinespace \global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller %% than skip between lines in the table. \fi% \ifdim\multitableparskip=0pt \global\multitableparskip=\multitablelinespace \global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller %% than skip between lines in the table. \fi} \message{conditionals,} % @iftex, @ifnotdocbook, @ifnothtml, @ifnotinfo, @ifnotplaintext, % @ifnotxml always succeed. They currently do nothing; we don't % attempt to check whether the conditionals are properly nested. But we % have to remember that they are conditionals, so that @end doesn't % attempt to close an environment group. % \def\makecond#1{% \expandafter\let\csname #1\endcsname = \relax \expandafter\let\csname iscond.#1\endcsname = 1 } \makecond{iftex} \makecond{ifnotdocbook} \makecond{ifnothtml} \makecond{ifnotinfo} \makecond{ifnotplaintext} \makecond{ifnotxml} % Ignore @ignore, @ifhtml, @ifinfo, and the like. % \def\direntry{\doignore{direntry}} \def\documentdescription{\doignore{documentdescription}} \def\docbook{\doignore{docbook}} \def\html{\doignore{html}} \def\ifdocbook{\doignore{ifdocbook}} \def\ifhtml{\doignore{ifhtml}} \def\ifinfo{\doignore{ifinfo}} \def\ifnottex{\doignore{ifnottex}} \def\ifplaintext{\doignore{ifplaintext}} \def\ifxml{\doignore{ifxml}} \def\ignore{\doignore{ignore}} \def\menu{\doignore{menu}} \def\xml{\doignore{xml}} % Ignore text until a line `@end #1', keeping track of nested conditionals. % % A count to remember the depth of nesting. \newcount\doignorecount \def\doignore#1{\begingroup % Scan in ``verbatim'' mode: \obeylines \catcode`\@ = \other \catcode`\{ = \other \catcode`\} = \other % % Make sure that spaces turn into tokens that match what \doignoretext wants. \spaceisspace % % Count number of #1's that we've seen. \doignorecount = 0 % % Swallow text until we reach the matching `@end #1'. \dodoignore{#1}% } { \catcode`_=11 % We want to use \_STOP_ which cannot appear in texinfo source. \obeylines % % \gdef\dodoignore#1{% % #1 contains the command name as a string, e.g., `ifinfo'. % % Define a command to find the next `@end #1'. \long\def\doignoretext##1^^M@end #1{% \doignoretextyyy##1^^M@#1\_STOP_}% % % And this command to find another #1 command, at the beginning of a % line. (Otherwise, we would consider a line `@c @ifset', for % example, to count as an @ifset for nesting.) \long\def\doignoretextyyy##1^^M@#1##2\_STOP_{\doignoreyyy{##2}\_STOP_}% % % And now expand that command. \doignoretext ^^M% }% } \def\doignoreyyy#1{% \def\temp{#1}% \ifx\temp\empty % Nothing found. \let\next\doignoretextzzz \else % Found a nested condition, ... \advance\doignorecount by 1 \let\next\doignoretextyyy % ..., look for another. % If we're here, #1 ends with ^^M\ifinfo (for example). \fi \next #1% the token \_STOP_ is present just after this macro. } % We have to swallow the remaining "\_STOP_". % \def\doignoretextzzz#1{% \ifnum\doignorecount = 0 % We have just found the outermost @end. \let\next\enddoignore \else % Still inside a nested condition. \advance\doignorecount by -1 \let\next\doignoretext % Look for the next @end. \fi \next } % Finish off ignored text. { \obeylines% % Ignore anything after the last `@end #1'; this matters in verbatim % environments, where otherwise the newline after an ignored conditional % would result in a blank line in the output. \gdef\enddoignore#1^^M{\endgroup\ignorespaces}% } % @set VAR sets the variable VAR to an empty value. % @set VAR REST-OF-LINE sets VAR to the value REST-OF-LINE. % % Since we want to separate VAR from REST-OF-LINE (which might be % empty), we can't just use \parsearg; we have to insert a space of our % own to delimit the rest of the line, and then take it out again if we % didn't need it. % We rely on the fact that \parsearg sets \catcode`\ =10. % \parseargdef\set{\setyyy#1 \endsetyyy} \def\setyyy#1 #2\endsetyyy{% {% \makevalueexpandable \def\temp{#2}% \edef\next{\gdef\makecsname{SET#1}}% \ifx\temp\empty \next{}% \else \setzzz#2\endsetzzz \fi }% } % Remove the trailing space \setxxx inserted. \def\setzzz#1 \endsetzzz{\next{#1}} % @clear VAR clears (i.e., unsets) the variable VAR. % \parseargdef\clear{% {% \makevalueexpandable \global\expandafter\let\csname SET#1\endcsname=\relax }% } % @value{foo} gets the text saved in variable foo. \def\value{\begingroup\makevalueexpandable\valuexxx} \def\valuexxx#1{\expandablevalue{#1}\endgroup} { \catcode`\- = \active \catcode`\_ = \active % \gdef\makevalueexpandable{% \let\value = \expandablevalue % We don't want these characters active, ... \catcode`\-=\other \catcode`\_=\other % ..., but we might end up with active ones in the argument if % we're called from @code, as @code{@value{foo-bar_}}, though. % So \let them to their normal equivalents. \let-\realdash \let_\normalunderscore } } % We have this subroutine so that we can handle at least some @value's % properly in indexes (we call \makevalueexpandable in \indexdummies). % The command has to be fully expandable (if the variable is set), since % the result winds up in the index file. This means that if the % variable's value contains other Texinfo commands, it's almost certain % it will fail (although perhaps we could fix that with sufficient work % to do a one-level expansion on the result, instead of complete). % \def\expandablevalue#1{% \expandafter\ifx\csname SET#1\endcsname\relax {[No value for ``#1'']}% \message{Variable `#1', used in @value, is not set.}% \else \csname SET#1\endcsname \fi } % @ifset VAR ... @end ifset reads the `...' iff VAR has been defined % with @set. % % To get special treatment of `@end ifset,' call \makeond and the redefine. % \makecond{ifset} \def\ifset{\parsearg{\doifset{\let\next=\ifsetfail}}} \def\doifset#1#2{% {% \makevalueexpandable \let\next=\empty \expandafter\ifx\csname SET#2\endcsname\relax #1% If not set, redefine \next. \fi \expandafter }\next } \def\ifsetfail{\doignore{ifset}} % @ifclear VAR ... @end ifclear reads the `...' iff VAR has never been % defined with @set, or has been undefined with @clear. % % The `\else' inside the `\doifset' parameter is a trick to reuse the % above code: if the variable is not set, do nothing, if it is set, % then redefine \next to \ifclearfail. % \makecond{ifclear} \def\ifclear{\parsearg{\doifset{\else \let\next=\ifclearfail}}} \def\ifclearfail{\doignore{ifclear}} % @dircategory CATEGORY -- specify a category of the dir file % which this file should belong to. Ignore this in TeX. \let\dircategory=\comment % @defininfoenclose. \let\definfoenclose=\comment \message{indexing,} % Index generation facilities % Define \newwrite to be identical to plain tex's \newwrite % except not \outer, so it can be used within macros and \if's. \edef\newwrite{\makecsname{ptexnewwrite}} % \newindex {foo} defines an index named foo. % It automatically defines \fooindex such that % \fooindex ...rest of line... puts an entry in the index foo. % It also defines \fooindfile to be the number of the output channel for % the file that accumulates this index. The file's extension is foo. % The name of an index should be no more than 2 characters long % for the sake of vms. % \def\newindex#1{% \iflinks \expandafter\newwrite \csname#1indfile\endcsname \openout \csname#1indfile\endcsname \jobname.#1 % Open the file \fi \expandafter\xdef\csname#1index\endcsname{% % Define @#1index \noexpand\doindex{#1}} } % @defindex foo == \newindex{foo} % \def\defindex{\parsearg\newindex} % Define @defcodeindex, like @defindex except put all entries in @code. % \def\defcodeindex{\parsearg\newcodeindex} % \def\newcodeindex#1{% \iflinks \expandafter\newwrite \csname#1indfile\endcsname \openout \csname#1indfile\endcsname \jobname.#1 \fi \expandafter\xdef\csname#1index\endcsname{% \noexpand\docodeindex{#1}}% } % @synindex foo bar makes index foo feed into index bar. % Do this instead of @defindex foo if you don't want it as a separate index. % % @syncodeindex foo bar similar, but put all entries made for index foo % inside @code. % \def\synindex#1 #2 {\dosynindex\doindex{#1}{#2}} \def\syncodeindex#1 #2 {\dosynindex\docodeindex{#1}{#2}} % #1 is \doindex or \docodeindex, #2 the index getting redefined (foo), % #3 the target index (bar). \def\dosynindex#1#2#3{% % Only do \closeout if we haven't already done it, else we'll end up % closing the target index. \expandafter \ifx\csname donesynindex#2\endcsname \undefined % The \closeout helps reduce unnecessary open files; the limit on the % Acorn RISC OS is a mere 16 files. \expandafter\closeout\csname#2indfile\endcsname \expandafter\let\csname\donesynindex#2\endcsname = 1 \fi % redefine \fooindfile: \expandafter\let\expandafter\temp\expandafter=\csname#3indfile\endcsname \expandafter\let\csname#2indfile\endcsname=\temp % redefine \fooindex: \expandafter\xdef\csname#2index\endcsname{\noexpand#1{#3}}% } % Define \doindex, the driver for all \fooindex macros. % Argument #1 is generated by the calling \fooindex macro, % and it is "foo", the name of the index. % \doindex just uses \parsearg; it calls \doind for the actual work. % This is because \doind is more useful to call from other macros. % There is also \dosubind {index}{topic}{subtopic} % which makes an entry in a two-level index such as the operation index. \def\doindex#1{\edef\indexname{#1}\parsearg\singleindexer} \def\singleindexer #1{\doind{\indexname}{#1}} % like the previous two, but they put @code around the argument. \def\docodeindex#1{\edef\indexname{#1}\parsearg\singlecodeindexer} \def\singlecodeindexer #1{\doind{\indexname}{\code{#1}}} % Take care of Texinfo commands that can appear in an index entry. % Since there are some commands we want to expand, and others we don't, % we have to laboriously prevent expansion for those that we don't. % \def\indexdummies{% \escapechar = `\\ % use backslash in output files. \def\@{@}% change to @@ when we switch to @ as escape char in index files. \def\ {\realbackslash\space }% % % Need these in case \tex is in effect and \{ is a \delimiter again. % But can't use \lbracecmd and \rbracecmd because texindex assumes % braces and backslashes are used only as delimiters. \let\{ = \mylbrace \let\} = \myrbrace % % I don't entirely understand this, but when an index entry is % generated from a macro call, the \endinput which \scanmacro inserts % causes processing to be prematurely terminated. This is, % apparently, because \indexsorttmp is fully expanded, and \endinput % is an expandable command. The redefinition below makes \endinput % disappear altogether for that purpose -- although logging shows that % processing continues to some further point. On the other hand, it % seems \endinput does not hurt in the printed index arg, since that % is still getting written without apparent harm. % % Sample source (mac-idx3.tex, reported by Graham Percival to % help-texinfo, 22may06): % @macro funindex {WORD} % @findex xyz % @end macro % ... % @funindex commtest % % The above is not enough to reproduce the bug, but it gives the flavor. % % Sample whatsit resulting: % .@write3{\entry{xyz}{@folio }{@code {xyz@endinput }}} % % So: \let\endinput = \empty % % Do the redefinitions. \commondummies } % For the aux and toc files, @ is the escape character. So we want to % redefine everything using @ as the escape character (instead of % \realbackslash, still used for index files). When everything uses @, % this will be simpler. % \def\atdummies{% \def\@{@@}% \def\ {@ }% \let\{ = \lbraceatcmd \let\} = \rbraceatcmd % % Do the redefinitions. \commondummies \otherbackslash } % Called from \indexdummies and \atdummies. % \def\commondummies{% % % \definedummyword defines \#1 as \string\#1\space, thus effectively % preventing its expansion. This is used only for control% words, % not control letters, because the \space would be incorrect for % control characters, but is needed to separate the control word % from whatever follows. % % For control letters, we have \definedummyletter, which omits the % space. % % These can be used both for control words that take an argument and % those that do not. If it is followed by {arg} in the input, then % that will dutifully get written to the index (or wherever). % \def\definedummyword ##1{\def##1{\string##1\space}}% \def\definedummyletter##1{\def##1{\string##1}}% \let\definedummyaccent\definedummyletter % \commondummiesnofonts % \definedummyletter\_% % % Non-English letters. \definedummyword\AA \definedummyword\AE \definedummyword\L \definedummyword\OE \definedummyword\O \definedummyword\aa \definedummyword\ae \definedummyword\l \definedummyword\oe \definedummyword\o \definedummyword\ss \definedummyword\exclamdown \definedummyword\questiondown \definedummyword\ordf \definedummyword\ordm % % Although these internal commands shouldn't show up, sometimes they do. \definedummyword\bf \definedummyword\gtr \definedummyword\hat \definedummyword\less \definedummyword\sf \definedummyword\sl \definedummyword\tclose \definedummyword\tt % \definedummyword\LaTeX \definedummyword\TeX % % Assorted special characters. \definedummyword\bullet \definedummyword\comma \definedummyword\copyright \definedummyword\registeredsymbol \definedummyword\dots \definedummyword\enddots \definedummyword\equiv \definedummyword\error \definedummyword\euro \definedummyword\expansion \definedummyword\minus \definedummyword\pounds \definedummyword\point \definedummyword\print \definedummyword\result \definedummyword\textdegree % % We want to disable all macros so that they are not expanded by \write. \macrolist % \normalturnoffactive % % Handle some cases of @value -- where it does not contain any % (non-fully-expandable) commands. \makevalueexpandable } % \commondummiesnofonts: common to \commondummies and \indexnofonts. % \def\commondummiesnofonts{% % Control letters and accents. \definedummyletter\!% \definedummyaccent\"% \definedummyaccent\'% \definedummyletter\*% \definedummyaccent\,% \definedummyletter\.% \definedummyletter\/% \definedummyletter\:% \definedummyaccent\=% \definedummyletter\?% \definedummyaccent\^% \definedummyaccent\`% \definedummyaccent\~% \definedummyword\u \definedummyword\v \definedummyword\H \definedummyword\dotaccent \definedummyword\ringaccent \definedummyword\tieaccent \definedummyword\ubaraccent \definedummyword\udotaccent \definedummyword\dotless % % Texinfo font commands. \definedummyword\b \definedummyword\i \definedummyword\r \definedummyword\sc \definedummyword\t % % Commands that take arguments. \definedummyword\acronym \definedummyword\cite \definedummyword\code \definedummyword\command \definedummyword\dfn \definedummyword\emph \definedummyword\env \definedummyword\file \definedummyword\kbd \definedummyword\key \definedummyword\math \definedummyword\option \definedummyword\pxref \definedummyword\ref \definedummyword\samp \definedummyword\strong \definedummyword\tie \definedummyword\uref \definedummyword\url \definedummyword\var \definedummyword\verb \definedummyword\w \definedummyword\xref } % \indexnofonts is used when outputting the strings to sort the index % by, and when constructing control sequence names. It eliminates all % control sequences and just writes whatever the best ASCII sort string % would be for a given command (usually its argument). % \def\indexnofonts{% % Accent commands should become @asis. \def\definedummyaccent##1{\let##1\asis}% % We can just ignore other control letters. \def\definedummyletter##1{\let##1\empty}% % Hopefully, all control words can become @asis. \let\definedummyword\definedummyaccent % \commondummiesnofonts % % Don't no-op \tt, since it isn't a user-level command % and is used in the definitions of the active chars like <, >, |, etc. % Likewise with the other plain tex font commands. %\let\tt=\asis % \def\ { }% \def\@{@}% % how to handle braces? \def\_{\normalunderscore}% % % Non-English letters. \def\AA{AA}% \def\AE{AE}% \def\L{L}% \def\OE{OE}% \def\O{O}% \def\aa{aa}% \def\ae{ae}% \def\l{l}% \def\oe{oe}% \def\o{o}% \def\ss{ss}% \def\exclamdown{!}% \def\questiondown{?}% \def\ordf{a}% \def\ordm{o}% % \def\LaTeX{LaTeX}% \def\TeX{TeX}% % % Assorted special characters. % (The following {} will end up in the sort string, but that's ok.) \def\bullet{bullet}% \def\comma{,}% \def\copyright{copyright}% \def\registeredsymbol{R}% \def\dots{...}% \def\enddots{...}% \def\equiv{==}% \def\error{error}% \def\euro{euro}% \def\expansion{==>}% \def\minus{-}% \def\pounds{pounds}% \def\point{.}% \def\print{-|}% \def\result{=>}% \def\textdegree{degrees}% % % We need to get rid of all macros, leaving only the arguments (if present). % Of course this is not nearly correct, but it is the best we can do for now. % makeinfo does not expand macros in the argument to @deffn, which ends up % writing an index entry, and texindex isn't prepared for an index sort entry % that starts with \. % % Since macro invocations are followed by braces, we can just redefine them % to take a single TeX argument. The case of a macro invocation that % goes to end-of-line is not handled. % \macrolist } \let\indexbackslash=0 %overridden during \printindex. \let\SETmarginindex=\relax % put index entries in margin (undocumented)? % Most index entries go through here, but \dosubind is the general case. % #1 is the index name, #2 is the entry text. \def\doind#1#2{\dosubind{#1}{#2}{}} % Workhorse for all \fooindexes. % #1 is name of index, #2 is stuff to put there, #3 is subentry -- % empty if called from \doind, as we usually are (the main exception % is with most defuns, which call us directly). % \def\dosubind#1#2#3{% \iflinks {% % Store the main index entry text (including the third arg). \toks0 = {#2}% % If third arg is present, precede it with a space. \def\thirdarg{#3}% \ifx\thirdarg\empty \else \toks0 = \expandafter{\the\toks0 \space #3}% \fi % \edef\writeto{\csname#1indfile\endcsname}% % \safewhatsit\dosubindwrite }% \fi } % Write the entry in \toks0 to the index file: % \def\dosubindwrite{% % Put the index entry in the margin if desired. \ifx\SETmarginindex\relax\else \insert\margin{\hbox{\vrule height8pt depth3pt width0pt \the\toks0}}% \fi % % Remember, we are within a group. \indexdummies % Must do this here, since \bf, etc expand at this stage \def\backslashcurfont{\indexbackslash}% \indexbackslash isn't defined now % so it will be output as is; and it will print as backslash. % % Process the index entry with all font commands turned off, to % get the string to sort by. {\indexnofonts \edef\temp{\the\toks0}% need full expansion \xdef\indexsorttmp{\temp}% }% % % Set up the complete index entry, with both the sort key and % the original text, including any font commands. We write % three arguments to \entry to the .?? file (four in the % subentry case), texindex reduces to two when writing the .??s % sorted result. \edef\temp{% \write\writeto{% \string\entry{\indexsorttmp}{\noexpand\folio}{\the\toks0}}% }% \temp } % Take care of unwanted page breaks/skips around a whatsit: % % If a skip is the last thing on the list now, preserve it % by backing up by \lastskip, doing the \write, then inserting % the skip again. Otherwise, the whatsit generated by the % \write or \pdfdest will make \lastskip zero. The result is that % sequences like this: % @end defun % @tindex whatever % @defun ... % will have extra space inserted, because the \medbreak in the % start of the @defun won't see the skip inserted by the @end of % the previous defun. % % But don't do any of this if we're not in vertical mode. We % don't want to do a \vskip and prematurely end a paragraph. % % Avoid page breaks due to these extra skips, too. % % But wait, there is a catch there: % We'll have to check whether \lastskip is zero skip. \ifdim is not % sufficient for this purpose, as it ignores stretch and shrink parts % of the skip. The only way seems to be to check the textual % representation of the skip. % % The following is almost like \def\zeroskipmacro{0.0pt} except that % the ``p'' and ``t'' characters have catcode \other, not 11 (letter). % \edef\zeroskipmacro{\expandafter\the\csname z@skip\endcsname} % \newskip\whatsitskip \newcount\whatsitpenalty % % ..., ready, GO: % \def\safewhatsit#1{% \ifhmode #1% \else % \lastskip and \lastpenalty cannot both be nonzero simultaneously. \whatsitskip = \lastskip \edef\lastskipmacro{\the\lastskip}% \whatsitpenalty = \lastpenalty % % If \lastskip is nonzero, that means the last item was a % skip. And since a skip is discardable, that means this % -\skip0 glue we're inserting is preceded by a % non-discardable item, therefore it is not a potential % breakpoint, therefore no \nobreak needed. \ifx\lastskipmacro\zeroskipmacro \else \vskip-\whatsitskip \fi % #1% % \ifx\lastskipmacro\zeroskipmacro % If \lastskip was zero, perhaps the last item was a penalty, and % perhaps it was >=10000, e.g., a \nobreak. In that case, we want % to re-insert the same penalty (values >10000 are used for various % signals); since we just inserted a non-discardable item, any % following glue (such as a \parskip) would be a breakpoint. For example: % % @deffn deffn-whatever % @vindex index-whatever % Description. % would allow a break between the index-whatever whatsit % and the "Description." paragraph. \ifnum\whatsitpenalty>9999 \penalty\whatsitpenalty \fi \else % On the other hand, if we had a nonzero \lastskip, % this make-up glue would be preceded by a non-discardable item % (the whatsit from the \write), so we must insert a \nobreak. \nobreak\vskip\whatsitskip \fi \fi } % The index entry written in the file actually looks like % \entry {sortstring}{page}{topic} % or % \entry {sortstring}{page}{topic}{subtopic} % The texindex program reads in these files and writes files % containing these kinds of lines: % \initial {c} % before the first topic whose initial is c % \entry {topic}{pagelist} % for a topic that is used without subtopics % \primary {topic} % for the beginning of a topic that is used with subtopics % \secondary {subtopic}{pagelist} % for each subtopic. % Define the user-accessible indexing commands % @findex, @vindex, @kindex, @cindex. \def\findex {\fnindex} \def\kindex {\kyindex} \def\cindex {\cpindex} \def\vindex {\vrindex} \def\tindex {\tpindex} \def\pindex {\pgindex} \def\cindexsub {\begingroup\obeylines\cindexsub} {\obeylines % \gdef\cindexsub "#1" #2^^M{\endgroup % \dosubind{cp}{#2}{#1}}} % Define the macros used in formatting output of the sorted index material. % @printindex causes a particular index (the ??s file) to get printed. % It does not print any chapter heading (usually an @unnumbered). % \parseargdef\printindex{\begingroup \dobreak \chapheadingskip{10000}% % \smallfonts \rm \tolerance = 9500 \plainfrenchspacing \everypar = {}% don't want the \kern\-parindent from indentation suppression. % % See if the index file exists and is nonempty. % Change catcode of @ here so that if the index file contains % \initial {@} % as its first line, TeX doesn't complain about mismatched braces % (because it thinks @} is a control sequence). \catcode`\@ = 11 \openin 1 \jobname.#1s \ifeof 1 % \enddoublecolumns gets confused if there is no text in the index, % and it loses the chapter title and the aux file entries for the % index. The easiest way to prevent this problem is to make sure % there is some text. \putwordIndexNonexistent \else % % If the index file exists but is empty, then \openin leaves \ifeof % false. We have to make TeX try to read something from the file, so % it can discover if there is anything in it. \read 1 to \temp \ifeof 1 \putwordIndexIsEmpty \else % Index files are almost Texinfo source, but we use \ as the escape % character. It would be better to use @, but that's too big a change % to make right now. \def\indexbackslash{\backslashcurfont}% \catcode`\\ = 0 \escapechar = `\\ \begindoublecolumns \input \jobname.#1s \enddoublecolumns \fi \fi \closein 1 \endgroup} % These macros are used by the sorted index file itself. % Change them to control the appearance of the index. \def\initial#1{{% % Some minor font changes for the special characters. \let\tentt=\sectt \let\tt=\sectt \let\sf=\sectt % % Remove any glue we may have, we'll be inserting our own. \removelastskip % % We like breaks before the index initials, so insert a bonus. \nobreak \vskip 0pt plus 3\baselineskip \penalty 0 \vskip 0pt plus -3\baselineskip % % Typeset the initial. Making this add up to a whole number of % baselineskips increases the chance of the dots lining up from column % to column. It still won't often be perfect, because of the stretch % we need before each entry, but it's better. % % No shrink because it confuses \balancecolumns. \vskip 1.67\baselineskip plus .5\baselineskip \leftline{\secbf #1}% % Do our best not to break after the initial. \nobreak \vskip .33\baselineskip plus .1\baselineskip }} % \entry typesets a paragraph consisting of the text (#1), dot leaders, and % then page number (#2) flushed to the right margin. It is used for index % and table of contents entries. The paragraph is indented by \leftskip. % % A straightforward implementation would start like this: % \def\entry#1#2{... % But this frozes the catcodes in the argument, and can cause problems to % @code, which sets - active. This problem was fixed by a kludge--- % ``-'' was active throughout whole index, but this isn't really right. % % The right solution is to prevent \entry from swallowing the whole text. % --kasal, 21nov03 \def\entry{% \begingroup % % Start a new paragraph if necessary, so our assignments below can't % affect previous text. \par % % Do not fill out the last line with white space. \parfillskip = 0in % % No extra space above this paragraph. \parskip = 0in % % Do not prefer a separate line ending with a hyphen to fewer lines. \finalhyphendemerits = 0 % % \hangindent is only relevant when the entry text and page number % don't both fit on one line. In that case, bob suggests starting the % dots pretty far over on the line. Unfortunately, a large % indentation looks wrong when the entry text itself is broken across % lines. So we use a small indentation and put up with long leaders. % % \hangafter is reset to 1 (which is the value we want) at the start % of each paragraph, so we need not do anything with that. \hangindent = 2em % % When the entry text needs to be broken, just fill out the first line % with blank space. \rightskip = 0pt plus1fil % % A bit of stretch before each entry for the benefit of balancing % columns. \vskip 0pt plus1pt % % Swallow the left brace of the text (first parameter): \afterassignment\doentry \let\temp = } \def\doentry{% \bgroup % Instead of the swallowed brace. \noindent \aftergroup\finishentry % And now comes the text of the entry. } \def\finishentry#1{% % #1 is the page number. % % The following is kludged to not output a line of dots in the index if % there are no page numbers. The next person who breaks this will be % cursed by a Unix daemon. \def\tempa{{\rm }}% \def\tempb{#1}% \edef\tempc{\tempa}% \edef\tempd{\tempb}% \ifx\tempc\tempd \ % \else % % If we must, put the page number on a line of its own, and fill out % this line with blank space. (The \hfil is overwhelmed with the % fill leaders glue in \indexdotfill if the page number does fit.) \hfil\penalty50 \null\nobreak\indexdotfill % Have leaders before the page number. % % The `\ ' here is removed by the implicit \unskip that TeX does as % part of (the primitive) \par. Without it, a spurious underfull % \hbox ensues. \ifpdf \pdfgettoks#1.% \ \the\toksA \else \ #1% \fi \fi \par \endgroup } % Like plain.tex's \dotfill, except uses up at least 1 em. \def\indexdotfill{\cleaders \hbox{$\mathsurround=0pt \mkern1.5mu.\mkern1.5mu$}\hskip 1em plus 1fill} \def\primary #1{\line{#1\hfil}} \newskip\secondaryindent \secondaryindent=0.5cm \def\secondary#1#2{{% \parfillskip=0in \parskip=0in \hangindent=1in \hangafter=1 \noindent\hskip\secondaryindent\hbox{#1}\indexdotfill \ifpdf \pdfgettoks#2.\ \the\toksA % The page number ends the paragraph. \else #2 \fi \par }} % Define two-column mode, which we use to typeset indexes. % Adapted from the TeXbook, page 416, which is to say, % the manmac.tex format used to print the TeXbook itself. \catcode`\@=11 \newbox\partialpage \newdimen\doublecolumnhsize \def\begindoublecolumns{\begingroup % ended by \enddoublecolumns % Grab any single-column material above us. \output = {% % % Here is a possibility not foreseen in manmac: if we accumulate a % whole lot of material, we might end up calling this \output % routine twice in a row (see the doublecol-lose test, which is % essentially a couple of indexes with @setchapternewpage off). In % that case we just ship out what is in \partialpage with the normal % output routine. Generally, \partialpage will be empty when this % runs and this will be a no-op. See the indexspread.tex test case. \ifvoid\partialpage \else \onepageout{\pagecontents\partialpage}% \fi % \global\setbox\partialpage = \vbox{% % Unvbox the main output page. \unvbox\PAGE \kern-\topskip \kern\baselineskip }% }% \eject % run that output routine to set \partialpage % % Use the double-column output routine for subsequent pages. \output = {\doublecolumnout}% % % Change the page size parameters. We could do this once outside this % routine, in each of @smallbook, @afourpaper, and the default 8.5x11 % format, but then we repeat the same computation. Repeating a couple % of assignments once per index is clearly meaningless for the % execution time, so we may as well do it in one place. % % First we halve the line length, less a little for the gutter between % the columns. We compute the gutter based on the line length, so it % changes automatically with the paper format. The magic constant % below is chosen so that the gutter has the same value (well, +-<1pt) % as it did when we hard-coded it. % % We put the result in a separate register, \doublecolumhsize, so we % can restore it in \pagesofar, after \hsize itself has (potentially) % been clobbered. % \doublecolumnhsize = \hsize \advance\doublecolumnhsize by -.04154\hsize \divide\doublecolumnhsize by 2 \hsize = \doublecolumnhsize % % Double the \vsize as well. (We don't need a separate register here, % since nobody clobbers \vsize.) \vsize = 2\vsize } % The double-column output routine for all double-column pages except % the last. % \def\doublecolumnout{% \splittopskip=\topskip \splitmaxdepth=\maxdepth % Get the available space for the double columns -- the normal % (undoubled) page height minus any material left over from the % previous page. \dimen@ = \vsize \divide\dimen@ by 2 \advance\dimen@ by -\ht\partialpage % % box0 will be the left-hand column, box2 the right. \setbox0=\vsplit255 to\dimen@ \setbox2=\vsplit255 to\dimen@ \onepageout\pagesofar \unvbox255 \penalty\outputpenalty } % % Re-output the contents of the output page -- any previous material, % followed by the two boxes we just split, in box0 and box2. \def\pagesofar{% \unvbox\partialpage % \hsize = \doublecolumnhsize \wd0=\hsize \wd2=\hsize \hbox to\pagewidth{\box0\hfil\box2}% } % % All done with double columns. \def\enddoublecolumns{% % The following penalty ensures that the page builder is exercised % _before_ we change the output routine. This is necessary in the % following situation: % % The last section of the index consists only of a single entry. % Before this section, \pagetotal is less than \pagegoal, so no % break occurs before the last section starts. However, the last % section, consisting of \initial and the single \entry, does not % fit on the page and has to be broken off. Without the following % penalty the page builder will not be exercised until \eject % below, and by that time we'll already have changed the output % routine to the \balancecolumns version, so the next-to-last % double-column page will be processed with \balancecolumns, which % is wrong: The two columns will go to the main vertical list, with % the broken-off section in the recent contributions. As soon as % the output routine finishes, TeX starts reconsidering the page % break. The two columns and the broken-off section both fit on the % page, because the two columns now take up only half of the page % goal. When TeX sees \eject from below which follows the final % section, it invokes the new output routine that we've set after % \balancecolumns below; \onepageout will try to fit the two columns % and the final section into the vbox of \pageheight (see % \pagebody), causing an overfull box. % % Note that glue won't work here, because glue does not exercise the % page builder, unlike penalties (see The TeXbook, pp. 280-281). \penalty0 % \output = {% % Split the last of the double-column material. Leave it on the % current page, no automatic page break. \balancecolumns % % If we end up splitting too much material for the current page, % though, there will be another page break right after this \output % invocation ends. Having called \balancecolumns once, we do not % want to call it again. Therefore, reset \output to its normal % definition right away. (We hope \balancecolumns will never be % called on to balance too much material, but if it is, this makes % the output somewhat more palatable.) \global\output = {\onepageout{\pagecontents\PAGE}}% }% \eject \endgroup % started in \begindoublecolumns % % \pagegoal was set to the doubled \vsize above, since we restarted % the current page. We're now back to normal single-column % typesetting, so reset \pagegoal to the normal \vsize (after the % \endgroup where \vsize got restored). \pagegoal = \vsize } % % Called at the end of the double column material. \def\balancecolumns{% \setbox0 = \vbox{\unvbox255}% like \box255 but more efficient, see p.120. \dimen@ = \ht0 \advance\dimen@ by \topskip \advance\dimen@ by-\baselineskip \divide\dimen@ by 2 % target to split to %debug\message{final 2-column material height=\the\ht0, target=\the\dimen@.}% \splittopskip = \topskip % Loop until we get a decent breakpoint. {% \vbadness = 10000 \loop \global\setbox3 = \copy0 \global\setbox1 = \vsplit3 to \dimen@ \ifdim\ht3>\dimen@ \global\advance\dimen@ by 1pt \repeat }% %debug\message{split to \the\dimen@, column heights: \the\ht1, \the\ht3.}% \setbox0=\vbox to\dimen@{\unvbox1}% \setbox2=\vbox to\dimen@{\unvbox3}% % \pagesofar } \catcode`\@ = \other \message{sectioning,} % Chapters, sections, etc. % \unnumberedno is an oxymoron, of course. But we count the unnumbered % sections so that we can refer to them unambiguously in the pdf % outlines by their "section number". We avoid collisions with chapter % numbers by starting them at 10000. (If a document ever has 10000 % chapters, we're in trouble anyway, I'm sure.) \newcount\unnumberedno \unnumberedno = 10000 \newcount\chapno \newcount\secno \secno=0 \newcount\subsecno \subsecno=0 \newcount\subsubsecno \subsubsecno=0 % This counter is funny since it counts through charcodes of letters A, B, ... \newcount\appendixno \appendixno = `\@ % % \def\appendixletter{\char\the\appendixno} % We do the following ugly conditional instead of the above simple % construct for the sake of pdftex, which needs the actual % letter in the expansion, not just typeset. % \def\appendixletter{% \ifnum\appendixno=`A A% \else\ifnum\appendixno=`B B% \else\ifnum\appendixno=`C C% \else\ifnum\appendixno=`D D% \else\ifnum\appendixno=`E E% \else\ifnum\appendixno=`F F% \else\ifnum\appendixno=`G G% \else\ifnum\appendixno=`H H% \else\ifnum\appendixno=`I I% \else\ifnum\appendixno=`J J% \else\ifnum\appendixno=`K K% \else\ifnum\appendixno=`L L% \else\ifnum\appendixno=`M M% \else\ifnum\appendixno=`N N% \else\ifnum\appendixno=`O O% \else\ifnum\appendixno=`P P% \else\ifnum\appendixno=`Q Q% \else\ifnum\appendixno=`R R% \else\ifnum\appendixno=`S S% \else\ifnum\appendixno=`T T% \else\ifnum\appendixno=`U U% \else\ifnum\appendixno=`V V% \else\ifnum\appendixno=`W W% \else\ifnum\appendixno=`X X% \else\ifnum\appendixno=`Y Y% \else\ifnum\appendixno=`Z Z% % The \the is necessary, despite appearances, because \appendixletter is % expanded while writing the .toc file. \char\appendixno is not % expandable, thus it is written literally, thus all appendixes come out % with the same letter (or @) in the toc without it. \else\char\the\appendixno \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi \fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi} % Each @chapter defines this as the name of the chapter. % page headings and footings can use it. @section does likewise. % However, they are not reliable, because we don't use marks. \def\thischapter{} \def\thissection{} \newcount\absseclevel % used to calculate proper heading level \newcount\secbase\secbase=0 % @raisesections/@lowersections modify this count % @raisesections: treat @section as chapter, @subsection as section, etc. \def\raisesections{\global\advance\secbase by -1} \let\up=\raisesections % original BFox name % @lowersections: treat @chapter as section, @section as subsection, etc. \def\lowersections{\global\advance\secbase by 1} \let\down=\lowersections % original BFox name % we only have subsub. \chardef\maxseclevel = 3 % % A numbered section within an unnumbered changes to unnumbered too. % To achive this, remember the "biggest" unnum. sec. we are currently in: \chardef\unmlevel = \maxseclevel % % Trace whether the current chapter is an appendix or not: % \chapheadtype is "N" or "A", unnumbered chapters are ignored. \def\chapheadtype{N} % Choose a heading macro % #1 is heading type % #2 is heading level % #3 is text for heading \def\genhead#1#2#3{% % Compute the abs. sec. level: \absseclevel=#2 \advance\absseclevel by \secbase % Make sure \absseclevel doesn't fall outside the range: \ifnum \absseclevel < 0 \absseclevel = 0 \else \ifnum \absseclevel > 3 \absseclevel = 3 \fi \fi % The heading type: \def\headtype{#1}% \if \headtype U% \ifnum \absseclevel < \unmlevel \chardef\unmlevel = \absseclevel \fi \else % Check for appendix sections: \ifnum \absseclevel = 0 \edef\chapheadtype{\headtype}% \else \if \headtype A\if \chapheadtype N% \errmessage{@appendix... within a non-appendix chapter}% \fi\fi \fi % Check for numbered within unnumbered: \ifnum \absseclevel > \unmlevel \def\headtype{U}% \else \chardef\unmlevel = 3 \fi \fi % Now print the heading: \if \headtype U% \ifcase\absseclevel \unnumberedzzz{#3}% \or \unnumberedseczzz{#3}% \or \unnumberedsubseczzz{#3}% \or \unnumberedsubsubseczzz{#3}% \fi \else \if \headtype A% \ifcase\absseclevel \appendixzzz{#3}% \or \appendixsectionzzz{#3}% \or \appendixsubseczzz{#3}% \or \appendixsubsubseczzz{#3}% \fi \else \ifcase\absseclevel \chapterzzz{#3}% \or \seczzz{#3}% \or \numberedsubseczzz{#3}% \or \numberedsubsubseczzz{#3}% \fi \fi \fi \suppressfirstparagraphindent } % an interface: \def\numhead{\genhead N} \def\apphead{\genhead A} \def\unnmhead{\genhead U} % @chapter, @appendix, @unnumbered. Increment top-level counter, reset % all lower-level sectioning counters to zero. % % Also set \chaplevelprefix, which we prepend to @float sequence numbers % (e.g., figures), q.v. By default (before any chapter), that is empty. \let\chaplevelprefix = \empty % \outer\parseargdef\chapter{\numhead0{#1}} % normally numhead0 calls chapterzzz \def\chapterzzz#1{% % section resetting is \global in case the chapter is in a group, such % as an @include file. \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\chapno by 1 % % Used for \float. \gdef\chaplevelprefix{\the\chapno.}% \resetallfloatnos % \message{\putwordChapter\space \the\chapno}% % % Write the actual heading. \chapmacro{#1}{Ynumbered}{\the\chapno}% % % So @section and the like are numbered underneath this chapter. \global\let\section = \numberedsec \global\let\subsection = \numberedsubsec \global\let\subsubsection = \numberedsubsubsec } \outer\parseargdef\appendix{\apphead0{#1}} % normally apphead0 calls appendixzzz \def\appendixzzz#1{% \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\appendixno by 1 \gdef\chaplevelprefix{\appendixletter.}% \resetallfloatnos % \def\appendixnum{\putwordAppendix\space \appendixletter}% \message{\appendixnum}% % \chapmacro{#1}{Yappendix}{\appendixletter}% % \global\let\section = \appendixsec \global\let\subsection = \appendixsubsec \global\let\subsubsection = \appendixsubsubsec } \outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz \def\unnumberedzzz#1{% \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\unnumberedno by 1 % % Since an unnumbered has no number, no prefix for figures. \global\let\chaplevelprefix = \empty \resetallfloatnos % % This used to be simply \message{#1}, but TeX fully expands the % argument to \message. Therefore, if #1 contained @-commands, TeX % expanded them. For example, in `@unnumbered The @cite{Book}', TeX % expanded @cite (which turns out to cause errors because \cite is meant % to be executed, not expanded). % % Anyway, we don't want the fully-expanded definition of @cite to appear % as a result of the \message, we just want `@cite' itself. We use % \the to achieve this: TeX expands \the only once, % simply yielding the contents of . (We also do this for % the toc entries.) \toks0 = {#1}% \message{(\the\toks0)}% % \chapmacro{#1}{Ynothing}{\the\unnumberedno}% % \global\let\section = \unnumberedsec \global\let\subsection = \unnumberedsubsec \global\let\subsubsection = \unnumberedsubsubsec } % @centerchap is like @unnumbered, but the heading is centered. \outer\parseargdef\centerchap{% % Well, we could do the following in a group, but that would break % an assumption that \chapmacro is called at the outermost level. % Thus we are safer this way: --kasal, 24feb04 \let\centerparametersmaybe = \centerparameters \unnmhead0{#1}% \let\centerparametersmaybe = \relax } % @top is like @unnumbered. \let\top\unnumbered % Sections. \outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz \def\seczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}% } \outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz \def\appendixsectionzzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}% } \let\appendixsec\appendixsection \outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz \def\unnumberedseczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}% } % Subsections. \outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz \def\numberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}% } \outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz \def\appendixsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno}% } \outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz \def\unnumberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynothing}% {\the\unnumberedno.\the\secno.\the\subsecno}% } % Subsubsections. \outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz \def\numberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynumbered}% {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}% } \outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz \def\appendixsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}% } \outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz \def\unnumberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynothing}% {\the\unnumberedno.\the\secno.\the\subsecno.\the\subsubsecno}% } % These macros control what the section commands do, according % to what kind of chapter we are in (ordinary, appendix, or unnumbered). % Define them by default for a numbered chapter. \let\section = \numberedsec \let\subsection = \numberedsubsec \let\subsubsection = \numberedsubsubsec % Define @majorheading, @heading and @subheading % NOTE on use of \vbox for chapter headings, section headings, and such: % 1) We use \vbox rather than the earlier \line to permit % overlong headings to fold. % 2) \hyphenpenalty is set to 10000 because hyphenation in a % heading is obnoxious; this forbids it. % 3) Likewise, headings look best if no \parindent is used, and % if justification is not attempted. Hence \raggedright. \def\majorheading{% {\advance\chapheadingskip by 10pt \chapbreak }% \parsearg\chapheadingzzz } \def\chapheading{\chapbreak \parsearg\chapheadingzzz} \def\chapheadingzzz#1{% {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt\raggedright \rm #1\hfill}}% \bigskip \par\penalty 200\relax \suppressfirstparagraphindent } % @heading, @subheading, @subsubheading. \parseargdef\heading{\sectionheading{#1}{sec}{Yomitfromtoc}{} \suppressfirstparagraphindent} \parseargdef\subheading{\sectionheading{#1}{subsec}{Yomitfromtoc}{} \suppressfirstparagraphindent} \parseargdef\subsubheading{\sectionheading{#1}{subsubsec}{Yomitfromtoc}{} \suppressfirstparagraphindent} % These macros generate a chapter, section, etc. heading only % (including whitespace, linebreaking, etc. around it), % given all the information in convenient, parsed form. %%% Args are the skip and penalty (usually negative) \def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi} %%% Define plain chapter starts, and page on/off switching for it % Parameter controlling skip before chapter headings (if needed) \newskip\chapheadingskip \def\chapbreak{\dobreak \chapheadingskip {-4000}} \def\chappager{\par\vfill\supereject} \def\chapoddpage{\chappager \ifodd\pageno \else \hbox to 0pt{} \chappager\fi} \def\setchapternewpage #1 {\csname CHAPPAG#1\endcsname} \def\CHAPPAGoff{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chapbreak \global\let\pagealignmacro=\chappager} \def\CHAPPAGon{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chappager \global\let\pagealignmacro=\chappager \global\def\HEADINGSon{\HEADINGSsingle}} \def\CHAPPAGodd{% \global\let\contentsalignmacro = \chapoddpage \global\let\pchapsepmacro=\chapoddpage \global\let\pagealignmacro=\chapoddpage \global\def\HEADINGSon{\HEADINGSdouble}} \CHAPPAGon % Chapter opening. % % #1 is the text, #2 is the section type (Ynumbered, Ynothing, % Yappendix, Yomitfromtoc), #3 the chapter number. % % To test against our argument. \def\Ynothingkeyword{Ynothing} \def\Yomitfromtockeyword{Yomitfromtoc} \def\Yappendixkeyword{Yappendix} % \def\chapmacro#1#2#3{% \pchapsepmacro {% \chapfonts \rm % % Have to define \thissection before calling \donoderef, because the % xref code eventually uses it. On the other hand, it has to be called % after \pchapsepmacro, or the headline will change too soon. \gdef\thissection{#1}% \gdef\thischaptername{#1}% % % Only insert the separating space if we have a chapter/appendix % number, and don't print the unnumbered ``number''. \def\temptype{#2}% \ifx\temptype\Ynothingkeyword \setbox0 = \hbox{}% \def\toctype{unnchap}% \gdef\thischapternum{}% \gdef\thischapter{#1}% \else\ifx\temptype\Yomitfromtockeyword \setbox0 = \hbox{}% contents like unnumbered, but no toc entry \def\toctype{omit}% \gdef\thischapternum{}% \gdef\thischapter{}% \else\ifx\temptype\Yappendixkeyword \setbox0 = \hbox{\putwordAppendix{} #3\enspace}% \def\toctype{app}% \xdef\thischapternum{\appendixletter}% % We don't substitute the actual chapter name into \thischapter % because we don't want its macros evaluated now. And we don't % use \thissection because that changes with each section. % \xdef\thischapter{\putwordAppendix{} \appendixletter: \noexpand\thischaptername}% \else \setbox0 = \hbox{#3\enspace}% \def\toctype{numchap}% \xdef\thischapternum{\the\chapno}% \xdef\thischapter{\putwordChapter{} \the\chapno: \noexpand\thischaptername}% \fi\fi\fi % % Write the toc entry for this chapter. Must come before the % \donoderef, because we include the current node name in the toc % entry, and \donoderef resets it to empty. \writetocentry{\toctype}{#1}{#3}% % % For pdftex, we have to write out the node definition (aka, make % the pdfdest) after any page break, but before the actual text has % been typeset. If the destination for the pdf outline is after the % text, then jumping from the outline may wind up with the text not % being visible, for instance under high magnification. \donoderef{#2}% % % Typeset the actual heading. \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright \hangindent=\wd0 \centerparametersmaybe \unhbox0 #1\par}% }% \nobreak\bigskip % no page break after a chapter title \nobreak } % @centerchap -- centered and unnumbered. \let\centerparametersmaybe = \relax \def\centerparameters{% \advance\rightskip by 3\rightskip \leftskip = \rightskip \parfillskip = 0pt } % I don't think this chapter style is supported any more, so I'm not % updating it with the new noderef stuff. We'll see. --karl, 11aug03. % \def\setchapterstyle #1 {\csname CHAPF#1\endcsname} % \def\unnchfopen #1{% \chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt\raggedright \rm #1\hfill}}\bigskip \par\nobreak } \def\chfopen #1#2{\chapoddpage {\chapfonts \vbox to 3in{\vfil \hbox to\hsize{\hfil #2} \hbox to\hsize{\hfil #1} \vfil}}% \par\penalty 5000 % } \def\centerchfopen #1{% \chapoddpage {\chapfonts \vbox{\hyphenpenalty=10000\tolerance=5000 \parindent=0pt \hfill {\rm #1}\hfill}}\bigskip \par\nobreak } \def\CHAPFopen{% \global\let\chapmacro=\chfopen \global\let\centerchapmacro=\centerchfopen} % Section titles. These macros combine the section number parts and % call the generic \sectionheading to do the printing. % \newskip\secheadingskip \def\secheadingbreak{\dobreak \secheadingskip{-1000}} % Subsection titles. \newskip\subsecheadingskip \def\subsecheadingbreak{\dobreak \subsecheadingskip{-500}} % Subsubsection titles. \def\subsubsecheadingskip{\subsecheadingskip} \def\subsubsecheadingbreak{\subsecheadingbreak} % Print any size, any type, section title. % % #1 is the text, #2 is the section level (sec/subsec/subsubsec), #3 is % the section type for xrefs (Ynumbered, Ynothing, Yappendix), #4 is the % section number. % \def\sectionheading#1#2#3#4{% {% % Switch to the right set of fonts. \csname #2fonts\endcsname \rm % % Insert space above the heading. \csname #2headingbreak\endcsname % % Only insert the space after the number if we have a section number. \def\sectionlevel{#2}% \def\temptype{#3}% % \ifx\temptype\Ynothingkeyword \setbox0 = \hbox{}% \def\toctype{unn}% \gdef\thissection{#1}% \else\ifx\temptype\Yomitfromtockeyword % for @headings -- no section number, don't include in toc, % and don't redefine \thissection. \setbox0 = \hbox{}% \def\toctype{omit}% \let\sectionlevel=\empty \else\ifx\temptype\Yappendixkeyword \setbox0 = \hbox{#4\enspace}% \def\toctype{app}% \gdef\thissection{#1}% \else \setbox0 = \hbox{#4\enspace}% \def\toctype{num}% \gdef\thissection{#1}% \fi\fi\fi % % Write the toc entry (before \donoderef). See comments in \chapmacro. \writetocentry{\toctype\sectionlevel}{#1}{#4}% % % Write the node reference (= pdf destination for pdftex). % Again, see comments in \chapmacro. \donoderef{#3}% % % Interline glue will be inserted when the vbox is completed. % That glue will be a valid breakpoint for the page, since it'll be % preceded by a whatsit (usually from the \donoderef, or from the % \writetocentry if there was no node). We don't want to allow that % break, since then the whatsits could end up on page n while the % section is on page n+1, thus toc/etc. are wrong. Debian bug 276000. \nobreak % % Output the actual section heading. \vbox{\hyphenpenalty=10000 \tolerance=5000 \parindent=0pt \raggedright \hangindent=\wd0 % zero if no section number \unhbox0 #1}% }% % Add extra space after the heading -- half of whatever came above it. % Don't allow stretch, though. \kern .5 \csname #2headingskip\endcsname % % Do not let the kern be a potential breakpoint, as it would be if it % was followed by glue. \nobreak % % We'll almost certainly start a paragraph next, so don't let that % glue accumulate. (Not a breakpoint because it's preceded by a % discardable item.) \vskip-\parskip % % This is purely so the last item on the list is a known \penalty > % 10000. This is so \startdefun can avoid allowing breakpoints after % section headings. Otherwise, it would insert a valid breakpoint between: % % @section sec-whatever % @deffn def-whatever \penalty 10001 } \message{toc,} % Table of contents. \newwrite\tocfile % Write an entry to the toc file, opening it if necessary. % Called from @chapter, etc. % % Example usage: \writetocentry{sec}{Section Name}{\the\chapno.\the\secno} % We append the current node name (if any) and page number as additional % arguments for the \{chap,sec,...}entry macros which will eventually % read this. The node name is used in the pdf outlines as the % destination to jump to. % % We open the .toc file for writing here instead of at @setfilename (or % any other fixed time) so that @contents can be anywhere in the document. % But if #1 is `omit', then we don't do anything. This is used for the % table of contents chapter openings themselves. % \newif\iftocfileopened \def\omitkeyword{omit}% % \def\writetocentry#1#2#3{% \edef\writetoctype{#1}% \ifx\writetoctype\omitkeyword \else \iftocfileopened\else \immediate\openout\tocfile = \jobname.toc \global\tocfileopenedtrue \fi % \iflinks {\atdummies \edef\temp{% \write\tocfile{@#1entry{#2}{#3}{\lastnode}{\noexpand\folio}}}% \temp }% \fi \fi % % Tell \shipout to create a pdf destination on each page, if we're % writing pdf. These are used in the table of contents. We can't % just write one on every page because the title pages are numbered % 1 and 2 (the page numbers aren't printed), and so are the first % two pages of the document. Thus, we'd have two destinations named % `1', and two named `2'. \ifpdf \global\pdfmakepagedesttrue \fi } % These characters do not print properly in the Computer Modern roman % fonts, so we must take special care. This is more or less redundant % with the Texinfo input format setup at the end of this file. % \def\activecatcodes{% \catcode`\"=\active \catcode`\$=\active \catcode`\<=\active \catcode`\>=\active \catcode`\\=\active \catcode`\^=\active \catcode`\_=\active \catcode`\|=\active \catcode`\~=\active } % Read the toc file, which is essentially Texinfo input. \def\readtocfile{% \setupdatafile \activecatcodes \input \jobname.toc } \newskip\contentsrightmargin \contentsrightmargin=1in \newcount\savepageno \newcount\lastnegativepageno \lastnegativepageno = -1 % Prepare to read what we've written to \tocfile. % \def\startcontents#1{% % If @setchapternewpage on, and @headings double, the contents should % start on an odd page, unlike chapters. Thus, we maintain % \contentsalignmacro in parallel with \pagealignmacro. % From: Torbjorn Granlund \contentsalignmacro \immediate\closeout\tocfile % % Don't need to put `Contents' or `Short Contents' in the headline. % It is abundantly clear what they are. \def\thischapter{}% \chapmacro{#1}{Yomitfromtoc}{}% % \savepageno = \pageno \begingroup % Set up to handle contents files properly. \raggedbottom % Worry more about breakpoints than the bottom. \advance\hsize by -\contentsrightmargin % Don't use the full line length. % % Roman numerals for page numbers. \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi } % Normal (long) toc. \def\contents{% \startcontents{\putwordTOC}% \openin 1 \jobname.toc \ifeof 1 \else \readtocfile \fi \vfill \eject \contentsalignmacro % in case @setchapternewpage odd is in effect \ifeof 1 \else \pdfmakeoutlines \fi \closein 1 \endgroup \lastnegativepageno = \pageno \global\pageno = \savepageno } % And just the chapters. \def\summarycontents{% \startcontents{\putwordShortTOC}% % \let\numchapentry = \shortchapentry \let\appentry = \shortchapentry \let\unnchapentry = \shortunnchapentry % We want a true roman here for the page numbers. \secfonts \let\rm=\shortcontrm \let\bf=\shortcontbf \let\sl=\shortcontsl \let\tt=\shortconttt \rm \hyphenpenalty = 10000 \advance\baselineskip by 1pt % Open it up a little. \def\numsecentry##1##2##3##4{} \let\appsecentry = \numsecentry \let\unnsecentry = \numsecentry \let\numsubsecentry = \numsecentry \let\appsubsecentry = \numsecentry \let\unnsubsecentry = \numsecentry \let\numsubsubsecentry = \numsecentry \let\appsubsubsecentry = \numsecentry \let\unnsubsubsecentry = \numsecentry \openin 1 \jobname.toc \ifeof 1 \else \readtocfile \fi \closein 1 \vfill \eject \contentsalignmacro % in case @setchapternewpage odd is in effect \endgroup \lastnegativepageno = \pageno \global\pageno = \savepageno } \let\shortcontents = \summarycontents % Typeset the label for a chapter or appendix for the short contents. % The arg is, e.g., `A' for an appendix, or `3' for a chapter. % \def\shortchaplabel#1{% % This space should be enough, since a single number is .5em, and the % widest letter (M) is 1em, at least in the Computer Modern fonts. % But use \hss just in case. % (This space doesn't include the extra space that gets added after % the label; that gets put in by \shortchapentry above.) % % We'd like to right-justify chapter numbers, but that looks strange % with appendix letters. And right-justifying numbers and % left-justifying letters looks strange when there is less than 10 % chapters. Have to read the whole toc once to know how many chapters % there are before deciding ... \hbox to 1em{#1\hss}% } % These macros generate individual entries in the table of contents. % The first argument is the chapter or section name. % The last argument is the page number. % The arguments in between are the chapter number, section number, ... % Chapters, in the main contents. \def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}} % % Chapters, in the short toc. % See comments in \dochapentry re vbox and related settings. \def\shortchapentry#1#2#3#4{% \tocentry{\shortchaplabel{#2}\labelspace #1}{\doshortpageno\bgroup#4\egroup}% } % Appendices, in the main contents. % Need the word Appendix, and a fixed-size box. % \def\appendixbox#1{% % We use M since it's probably the widest letter. \setbox0 = \hbox{\putwordAppendix{} M}% \hbox to \wd0{\putwordAppendix{} #1\hss}} % \def\appentry#1#2#3#4{\dochapentry{\appendixbox{#2}\labelspace#1}{#4}} % Unnumbered chapters. \def\unnchapentry#1#2#3#4{\dochapentry{#1}{#4}} \def\shortunnchapentry#1#2#3#4{\tocentry{#1}{\doshortpageno\bgroup#4\egroup}} % Sections. \def\numsecentry#1#2#3#4{\dosecentry{#2\labelspace#1}{#4}} \let\appsecentry=\numsecentry \def\unnsecentry#1#2#3#4{\dosecentry{#1}{#4}} % Subsections. \def\numsubsecentry#1#2#3#4{\dosubsecentry{#2\labelspace#1}{#4}} \let\appsubsecentry=\numsubsecentry \def\unnsubsecentry#1#2#3#4{\dosubsecentry{#1}{#4}} % And subsubsections. \def\numsubsubsecentry#1#2#3#4{\dosubsubsecentry{#2\labelspace#1}{#4}} \let\appsubsubsecentry=\numsubsubsecentry \def\unnsubsubsecentry#1#2#3#4{\dosubsubsecentry{#1}{#4}} % This parameter controls the indentation of the various levels. % Same as \defaultparindent. \newdimen\tocindent \tocindent = 15pt % Now for the actual typesetting. In all these, #1 is the text and #2 is the % page number. % % If the toc has to be broken over pages, we want it to be at chapters % if at all possible; hence the \penalty. \def\dochapentry#1#2{% \penalty-300 \vskip1\baselineskip plus.33\baselineskip minus.25\baselineskip \begingroup \chapentryfonts \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup \nobreak\vskip .25\baselineskip plus.1\baselineskip } \def\dosecentry#1#2{\begingroup \secentryfonts \leftskip=\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} \def\dosubsecentry#1#2{\begingroup \subsecentryfonts \leftskip=2\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} \def\dosubsubsecentry#1#2{\begingroup \subsubsecentryfonts \leftskip=3\tocindent \tocentry{#1}{\dopageno\bgroup#2\egroup}% \endgroup} % We use the same \entry macro as for the index entries. \let\tocentry = \entry % Space between chapter (or whatever) number and the title. \def\labelspace{\hskip1em \relax} \def\dopageno#1{{\rm #1}} \def\doshortpageno#1{{\rm #1}} \def\chapentryfonts{\secfonts \rm} \def\secentryfonts{\textfonts} \def\subsecentryfonts{\textfonts} \def\subsubsecentryfonts{\textfonts} \message{environments,} % @foo ... @end foo. % @point{}, @result{}, @expansion{}, @print{}, @equiv{}. % % Since these characters are used in examples, it should be an even number of % \tt widths. Each \tt character is 1en, so two makes it 1em. % \def\point{$\star$} \def\result{\leavevmode\raise.15ex\hbox to 1em{\hfil$\Rightarrow$\hfil}} \def\expansion{\leavevmode\raise.1ex\hbox to 1em{\hfil$\mapsto$\hfil}} \def\print{\leavevmode\lower.1ex\hbox to 1em{\hfil$\dashv$\hfil}} \def\equiv{\leavevmode\lower.1ex\hbox to 1em{\hfil$\ptexequiv$\hfil}} % The @error{} command. % Adapted from the TeXbook's \boxit. % \newbox\errorbox % {\tentt \global\dimen0 = 3em}% Width of the box. \dimen2 = .55pt % Thickness of rules % The text. (`r' is open on the right, `e' somewhat less so on the left.) \setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt} % \setbox\errorbox=\hbox to \dimen0{\hfil \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right. \advance\hsize by -2\dimen2 % Rules. \vbox{% \hrule height\dimen2 \hbox{\vrule width\dimen2 \kern3pt % Space to left of text. \vtop{\kern2.4pt \box0 \kern2.4pt}% Space above/below. \kern3pt\vrule width\dimen2}% Space to right. \hrule height\dimen2} \hfil} % \def\error{\leavevmode\lower.7ex\copy\errorbox} % @tex ... @end tex escapes into raw Tex temporarily. % One exception: @ is still an escape character, so that @end tex works. % But \@ or @@ will get a plain tex @ character. \envdef\tex{% \catcode `\\=0 \catcode `\{=1 \catcode `\}=2 \catcode `\$=3 \catcode `\&=4 \catcode `\#=6 \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie \catcode `\%=14 \catcode `\+=\other \catcode `\"=\other \catcode `\|=\other \catcode `\<=\other \catcode `\>=\other \escapechar=`\\ % \let\b=\ptexb \let\bullet=\ptexbullet \let\c=\ptexc \let\,=\ptexcomma \let\.=\ptexdot \let\dots=\ptexdots \let\equiv=\ptexequiv \let\!=\ptexexclam \let\i=\ptexi \let\indent=\ptexindent \let\noindent=\ptexnoindent \let\{=\ptexlbrace \let\+=\tabalign \let\}=\ptexrbrace \let\/=\ptexslash \let\*=\ptexstar \let\t=\ptext \let\frenchspacing=\plainfrenchspacing % \def\endldots{\mathinner{\ldots\ldots\ldots\ldots}}% \def\enddots{\relax\ifmmode\endldots\else$\mathsurround=0pt \endldots\,$\fi}% \def\@{@}% } % There is no need to define \Etex. % Define @lisp ... @end lisp. % @lisp environment forms a group so it can rebind things, % including the definition of @end lisp (which normally is erroneous). % Amount to narrow the margins by for @lisp. \newskip\lispnarrowing \lispnarrowing=0.4in % This is the definition that ^^M gets inside @lisp, @example, and other % such environments. \null is better than a space, since it doesn't % have any width. \def\lisppar{\null\endgraf} % This space is always present above and below environments. \newskip\envskipamount \envskipamount = 0pt % Make spacing and below environment symmetrical. We use \parskip here % to help in doing that, since in @example-like environments \parskip % is reset to zero; thus the \afterenvbreak inserts no space -- but the % start of the next paragraph will insert \parskip. % \def\aboveenvbreak{{% % =10000 instead of <10000 because of a special case in \itemzzz and % \sectionheading, q.v. \ifnum \lastpenalty=10000 \else \advance\envskipamount by \parskip \endgraf \ifdim\lastskip<\envskipamount \removelastskip % it's not a good place to break if the last penalty was \nobreak % or better ... \ifnum\lastpenalty<10000 \penalty-50 \fi \vskip\envskipamount \fi \fi }} \let\afterenvbreak = \aboveenvbreak % \nonarrowing is a flag. If "set", @lisp etc don't narrow margins; it will % also clear it, so that its embedded environments do the narrowing again. \let\nonarrowing=\relax % @cartouche ... @end cartouche: draw rectangle w/rounded corners around % environment contents. \font\circle=lcircle10 \newdimen\circthick \newdimen\cartouter\newdimen\cartinner \newskip\normbskip\newskip\normpskip\newskip\normlskip \circthick=\fontdimen8\circle % \def\ctl{{\circle\char'013\hskip -6pt}}% 6pt from pl file: 1/2charwidth \def\ctr{{\hskip 6pt\circle\char'010}} \def\cbl{{\circle\char'012\hskip -6pt}} \def\cbr{{\hskip 6pt\circle\char'011}} \def\carttop{\hbox to \cartouter{\hskip\lskip \ctl\leaders\hrule height\circthick\hfil\ctr \hskip\rskip}} \def\cartbot{\hbox to \cartouter{\hskip\lskip \cbl\leaders\hrule height\circthick\hfil\cbr \hskip\rskip}} % \newskip\lskip\newskip\rskip \envdef\cartouche{% \ifhmode\par\fi % can't be in the midst of a paragraph. \startsavinginserts \lskip=\leftskip \rskip=\rightskip \leftskip=0pt\rightskip=0pt % we want these *outside*. \cartinner=\hsize \advance\cartinner by-\lskip \advance\cartinner by-\rskip \cartouter=\hsize \advance\cartouter by 18.4pt % allow for 3pt kerns on either % side, and for 6pt waste from % each corner char, and rule thickness \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip % Flag to tell @lisp, etc., not to narrow margin. \let\nonarrowing = t% \vbox\bgroup \baselineskip=0pt\parskip=0pt\lineskip=0pt \carttop \hbox\bgroup \hskip\lskip \vrule\kern3pt \vbox\bgroup \kern3pt \hsize=\cartinner \baselineskip=\normbskip \lineskip=\normlskip \parskip=\normpskip \vskip -\parskip \comment % For explanation, see the end of \def\group. } \def\Ecartouche{% \ifhmode\par\fi \kern3pt \egroup \kern3pt\vrule \hskip\rskip \egroup \cartbot \egroup \checkinserts } % This macro is called at the beginning of all the @example variants, % inside a group. \def\nonfillstart{% \aboveenvbreak \hfuzz = 12pt % Don't be fussy \sepspaces % Make spaces be word-separators rather than space tokens. \let\par = \lisppar % don't ignore blank lines \obeylines % each line of input is a line of output \parskip = 0pt \parindent = 0pt \emergencystretch = 0pt % don't try to avoid overfull boxes \ifx\nonarrowing\relax \advance \leftskip by \lispnarrowing \exdentamount=\lispnarrowing \else \let\nonarrowing = \relax \fi \let\exdent=\nofillexdent } % If you want all examples etc. small: @set dispenvsize small. % If you want even small examples the full size: @set dispenvsize nosmall. % This affects the following displayed environments: % @example, @display, @format, @lisp % \def\smallword{small} \def\nosmallword{nosmall} \let\SETdispenvsize\relax \def\setnormaldispenv{% \ifx\SETdispenvsize\smallword % end paragraph for sake of leading, in case document has no blank % line. This is redundant with what happens in \aboveenvbreak, but % we need to do it before changing the fonts, and it's inconvenient % to change the fonts afterward. \ifnum \lastpenalty=10000 \else \endgraf \fi \smallexamplefonts \rm \fi } \def\setsmalldispenv{% \ifx\SETdispenvsize\nosmallword \else \ifnum \lastpenalty=10000 \else \endgraf \fi \smallexamplefonts \rm \fi } % We often define two environments, @foo and @smallfoo. % Let's do it by one command: \def\makedispenv #1#2{ \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2} \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2} \expandafter\let\csname E#1\endcsname \afterenvbreak \expandafter\let\csname Esmall#1\endcsname \afterenvbreak } % Define two synonyms: \def\maketwodispenvs #1#2#3{ \makedispenv{#1}{#3} \makedispenv{#2}{#3} } % @lisp: indented, narrowed, typewriter font; @example: same as @lisp. % % @smallexample and @smalllisp: use smaller fonts. % Originally contributed by Pavel@xerox. % \maketwodispenvs {lisp}{example}{% \nonfillstart \tt\quoteexpand \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special. \gobble % eat return } % @display/@smalldisplay: same as @lisp except keep current font. % \makedispenv {display}{% \nonfillstart \gobble } % @format/@smallformat: same as @display except don't narrow margins. % \makedispenv{format}{% \let\nonarrowing = t% \nonfillstart \gobble } % @flushleft: same as @format, but doesn't obey \SETdispenvsize. \envdef\flushleft{% \let\nonarrowing = t% \nonfillstart \gobble } \let\Eflushleft = \afterenvbreak % @flushright. % \envdef\flushright{% \let\nonarrowing = t% \nonfillstart \advance\leftskip by 0pt plus 1fill \gobble } \let\Eflushright = \afterenvbreak % @quotation does normal linebreaking (hence we can't use \nonfillstart) % and narrows the margins. We keep \parskip nonzero in general, since % we're doing normal filling. So, when using \aboveenvbreak and % \afterenvbreak, temporarily make \parskip 0. % \envdef\quotation{% {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip \parindent=0pt % % @cartouche defines \nonarrowing to inhibit narrowing at next level down. \ifx\nonarrowing\relax \advance\leftskip by \lispnarrowing \advance\rightskip by \lispnarrowing \exdentamount = \lispnarrowing \else \let\nonarrowing = \relax \fi \parsearg\quotationlabel } % We have retained a nonzero parskip for the environment, since we're % doing normal filling. % \def\Equotation{% \par \ifx\quotationauthor\undefined\else % indent a bit. \leftline{\kern 2\leftskip \sl ---\quotationauthor}% \fi {\parskip=0pt \afterenvbreak}% } % If we're given an argument, typeset it in bold with a colon after. \def\quotationlabel#1{% \def\temp{#1}% \ifx\temp\empty \else {\bf #1: }% \fi } % LaTeX-like @verbatim...@end verbatim and @verb{...} % If we want to allow any as delimiter, % we need the curly braces so that makeinfo sees the @verb command, eg: % `@verbx...x' would look like the '@verbx' command. --janneke@gnu.org % % [Knuth]: Donald Ervin Knuth, 1996. The TeXbook. % % [Knuth] p.344; only we need to do the other characters Texinfo sets % active too. Otherwise, they get lost as the first character on a % verbatim line. \def\dospecials{% \do\ \do\\\do\{\do\}\do\$\do\&% \do\#\do\^\do\^^K\do\_\do\^^A\do\%\do\~% \do\<\do\>\do\|\do\@\do+\do\"% } % % [Knuth] p. 380 \def\uncatcodespecials{% \def\do##1{\catcode`##1=\other}\dospecials} % % [Knuth] pp. 380,381,391 % Disable Spanish ligatures ?` and !` of \tt font \begingroup \catcode`\`=\active\gdef`{\relax\lq} \endgroup % % Setup for the @verb command. % % Eight spaces for a tab \begingroup \catcode`\^^I=\active \gdef\tabeightspaces{\catcode`\^^I=\active\def^^I{\ \ \ \ \ \ \ \ }} \endgroup % \def\setupverb{% \tt % easiest (and conventionally used) font for verbatim \def\par{\leavevmode\endgraf}% \catcode`\`=\active \tabeightspaces % Respect line breaks, % print special symbols as themselves, and % make each space count % must do in this order: \obeylines \uncatcodespecials \sepspaces } % Setup for the @verbatim environment % % Real tab expansion \newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount % \def\starttabbox{\setbox0=\hbox\bgroup} % Allow an option to not replace quotes with a regular directed right % quote/apostrophe (char 0x27), but instead use the undirected quote % from cmtt (char 0x0d). The undirected quote is ugly, so don't make it % the default, but it works for pasting with more pdf viewers (at least % evince), the lilypond developers report. xpdf does work with the % regular 0x27. % \def\codequoteright{% \expandafter\ifx\csname SETcodequoteundirected\endcsname\relax '% \else \char'15 \fi } % % and a similar option for the left quote char vs. a grave accent. % Modern fonts display ASCII 0x60 as a grave accent, so some people like % the code environments to do likewise. % \def\codequoteleft{% \expandafter\ifx\csname SETcodequotebacktick\endcsname\relax `% \else \char'22 \fi } % \begingroup \catcode`\^^I=\active \gdef\tabexpand{% \catcode`\^^I=\active \def^^I{\leavevmode\egroup \dimen0=\wd0 % the width so far, or since the previous tab \divide\dimen0 by\tabw \multiply\dimen0 by\tabw % compute previous multiple of \tabw \advance\dimen0 by\tabw % advance to next multiple of \tabw \wd0=\dimen0 \box0 \starttabbox }% } \catcode`\'=\active \gdef\rquoteexpand{\catcode\rquoteChar=\active \def'{\codequoteright}}% % \catcode`\`=\active \gdef\lquoteexpand{\catcode\lquoteChar=\active \def`{\codequoteleft}}% % \gdef\quoteexpand{\rquoteexpand \lquoteexpand}% \endgroup % start the verbatim environment. \def\setupverbatim{% \let\nonarrowing = t% \nonfillstart % Easiest (and conventionally used) font for verbatim \tt \def\par{\leavevmode\egroup\box0\endgraf}% \catcode`\`=\active \tabexpand \quoteexpand % Respect line breaks, % print special symbols as themselves, and % make each space count % must do in this order: \obeylines \uncatcodespecials \sepspaces \everypar{\starttabbox}% } % Do the @verb magic: verbatim text is quoted by unique % delimiter characters. Before first delimiter expect a % right brace, after last delimiter expect closing brace: % % \def\doverb'{'#1'}'{#1} % % [Knuth] p. 382; only eat outer {} \begingroup \catcode`[=1\catcode`]=2\catcode`\{=\other\catcode`\}=\other \gdef\doverb{#1[\def\next##1#1}[##1\endgroup]\next] \endgroup % \def\verb{\begingroup\setupverb\doverb} % % % Do the @verbatim magic: define the macro \doverbatim so that % the (first) argument ends when '@end verbatim' is reached, ie: % % \def\doverbatim#1@end verbatim{#1} % % For Texinfo it's a lot easier than for LaTeX, % because texinfo's \verbatim doesn't stop at '\end{verbatim}': % we need not redefine '\', '{' and '}'. % % Inspired by LaTeX's verbatim command set [latex.ltx] % \begingroup \catcode`\ =\active \obeylines % % ignore everything up to the first ^^M, that's the newline at the end % of the @verbatim input line itself. Otherwise we get an extra blank % line in the output. \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}% % We really want {...\end verbatim} in the body of the macro, but % without the active space; thus we have to use \xdef and \gobble. \endgroup % \envdef\verbatim{% \setupverbatim\doverbatim } \let\Everbatim = \afterenvbreak % @verbatiminclude FILE - insert text of file in verbatim environment. % \def\verbatiminclude{\parseargusing\filenamecatcodes\doverbatiminclude} % \def\doverbatiminclude#1{% {% \makevalueexpandable \setupverbatim \input #1 \afterenvbreak }% } % @copying ... @end copying. % Save the text away for @insertcopying later. % % We save the uninterpreted tokens, rather than creating a box. % Saving the text in a box would be much easier, but then all the % typesetting commands (@smallbook, font changes, etc.) have to be done % beforehand -- and a) we want @copying to be done first in the source % file; b) letting users define the frontmatter in as flexible order as % possible is very desirable. % \def\copying{\checkenv{}\begingroup\scanargctxt\docopying} \def\docopying#1@end copying{\endgroup\def\copyingtext{#1}} % \def\insertcopying{% \begingroup \parindent = 0pt % paragraph indentation looks wrong on title page \scanexp\copyingtext \endgroup } \message{defuns,} % @defun etc. \newskip\defbodyindent \defbodyindent=.4in \newskip\defargsindent \defargsindent=50pt \newskip\deflastargmargin \deflastargmargin=18pt \newcount\defunpenalty % Start the processing of @deffn: \def\startdefun{% \ifnum\lastpenalty<10000 \medbreak \defunpenalty=10003 % Will keep this @deffn together with the % following @def command, see below. \else % If there are two @def commands in a row, we'll have a \nobreak, % which is there to keep the function description together with its % header. But if there's nothing but headers, we need to allow a % break somewhere. Check specifically for penalty 10002, inserted % by \printdefunline, instead of 10000, since the sectioning % commands also insert a nobreak penalty, and we don't want to allow % a break between a section heading and a defun. % % As a minor refinement, we avoid "club" headers by signalling % with penalty of 10003 after the very first @deffn in the % sequence (see above), and penalty of 10002 after any following % @def command. \ifnum\lastpenalty=10002 \penalty2000 \else \defunpenalty=10002 \fi % % Similarly, after a section heading, do not allow a break. % But do insert the glue. \medskip % preceded by discardable penalty, so not a breakpoint \fi % \parindent=0in \advance\leftskip by \defbodyindent \exdentamount=\defbodyindent } \def\dodefunx#1{% % First, check whether we are in the right environment: \checkenv#1% % % As above, allow line break if we have multiple x headers in a row. % It's not a great place, though. \ifnum\lastpenalty=10002 \penalty3000 \else \defunpenalty=10002 \fi % % And now, it's time to reuse the body of the original defun: \expandafter\gobbledefun#1% } \def\gobbledefun#1\startdefun{} % \printdefunline \deffnheader{text} % \def\printdefunline#1#2{% \begingroup % call \deffnheader: #1#2 \endheader % common ending: \interlinepenalty = 10000 \advance\rightskip by 0pt plus 1fil \endgraf \nobreak\vskip -\parskip \penalty\defunpenalty % signal to \startdefun and \dodefunx % Some of the @defun-type tags do not enable magic parentheses, % rendering the following check redundant. But we don't optimize. \checkparencounts \endgroup } \def\Edefun{\endgraf\medbreak} % \makedefun{deffn} creates \deffn, \deffnx and \Edeffn; % the only thing remainnig is to define \deffnheader. % \def\makedefun#1{% \expandafter\let\csname E#1\endcsname = \Edefun \edef\temp{\noexpand\domakedefun \makecsname{#1}\makecsname{#1x}\makecsname{#1header}}% \temp } % \domakedefun \deffn \deffnx \deffnheader % % Define \deffn and \deffnx, without parameters. % \deffnheader has to be defined explicitly. % \def\domakedefun#1#2#3{% \envdef#1{% \startdefun \parseargusing\activeparens{\printdefunline#3}% }% \def#2{\dodefunx#1}% \def#3% } %%% Untyped functions: % @deffn category name args \makedefun{deffn}{\deffngeneral{}} % @deffn category class name args \makedefun{defop}#1 {\defopon{#1\ \putwordon}} % \defopon {category on}class name args \def\defopon#1#2 {\deffngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } % \deffngeneral {subind}category name args % \def\deffngeneral#1#2 #3 #4\endheader{% % Remember that \dosubind{fn}{foo}{} is equivalent to \doind{fn}{foo}. \dosubind{fn}{\code{#3}}{#1}% \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}% } %%% Typed functions: % @deftypefn category type name args \makedefun{deftypefn}{\deftypefngeneral{}} % @deftypeop category class type name args \makedefun{deftypeop}#1 {\deftypeopon{#1\ \putwordon}} % \deftypeopon {category on}class type name args \def\deftypeopon#1#2 {\deftypefngeneral{\putwordon\ \code{#2}}{#1\ \code{#2}} } % \deftypefngeneral {subind}category type name args % \def\deftypefngeneral#1#2 #3 #4 #5\endheader{% \dosubind{fn}{\code{#4}}{#1}% \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } %%% Typed variables: % @deftypevr category type var args \makedefun{deftypevr}{\deftypecvgeneral{}} % @deftypecv category class type var args \makedefun{deftypecv}#1 {\deftypecvof{#1\ \putwordof}} % \deftypecvof {category of}class type var args \def\deftypecvof#1#2 {\deftypecvgeneral{\putwordof\ \code{#2}}{#1\ \code{#2}} } % \deftypecvgeneral {subind}category type var args % \def\deftypecvgeneral#1#2 #3 #4 #5\endheader{% \dosubind{vr}{\code{#4}}{#1}% \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } %%% Untyped variables: % @defvr category var args \makedefun{defvr}#1 {\deftypevrheader{#1} {} } % @defcv category class var args \makedefun{defcv}#1 {\defcvof{#1\ \putwordof}} % \defcvof {category of}class var args \def\defcvof#1#2 {\deftypecvof{#1}#2 {} } %%% Type: % @deftp category name args \makedefun{deftp}#1 #2 #3\endheader{% \doind{tp}{\code{#2}}% \defname{#1}{}{#2}\defunargs{#3\unskip}% } % Remaining @defun-like shortcuts: \makedefun{defun}{\deffnheader{\putwordDeffunc} } \makedefun{defmac}{\deffnheader{\putwordDefmac} } \makedefun{defspec}{\deffnheader{\putwordDefspec} } \makedefun{deftypefun}{\deftypefnheader{\putwordDeffunc} } \makedefun{defvar}{\defvrheader{\putwordDefvar} } \makedefun{defopt}{\defvrheader{\putwordDefopt} } \makedefun{deftypevar}{\deftypevrheader{\putwordDefvar} } \makedefun{defmethod}{\defopon\putwordMethodon} \makedefun{deftypemethod}{\deftypeopon\putwordMethodon} \makedefun{defivar}{\defcvof\putwordInstanceVariableof} \makedefun{deftypeivar}{\deftypecvof\putwordInstanceVariableof} % \defname, which formats the name of the @def (not the args). % #1 is the category, such as "Function". % #2 is the return type, if any. % #3 is the function name. % % We are followed by (but not passed) the arguments, if any. % \def\defname#1#2#3{% % Get the values of \leftskip and \rightskip as they were outside the @def... \advance\leftskip by -\defbodyindent % % How we'll format the type name. Putting it in brackets helps % distinguish it from the body text that may end up on the next line % just below it. \def\temp{#1}% \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi} % % Figure out line sizes for the paragraph shape. % The first line needs space for \box0; but if \rightskip is nonzero, % we need only space for the part of \box0 which exceeds it: \dimen0=\hsize \advance\dimen0 by -\wd0 \advance\dimen0 by \rightskip % The continuations: \dimen2=\hsize \advance\dimen2 by -\defargsindent % (plain.tex says that \dimen1 should be used only as global.) \parshape 2 0in \dimen0 \defargsindent \dimen2 % % Put the type name to the right margin. \noindent \hbox to 0pt{% \hfil\box0 \kern-\hsize % \hsize has to be shortened this way: \kern\leftskip % Intentionally do not respect \rightskip, since we need the space. }% % % Allow all lines to be underfull without complaint: \tolerance=10000 \hbadness=10000 \exdentamount=\defbodyindent {% % defun fonts. We use typewriter by default (used to be bold) because: % . we're printing identifiers, they should be in tt in principle. % . in languages with many accents, such as Czech or French, it's % common to leave accents off identifiers. The result looks ok in % tt, but exceedingly strange in rm. % . we don't want -- and --- to be treated as ligatures. % . this still does not fix the ?` and !` ligatures, but so far no % one has made identifiers using them :). \df \tt \def\temp{#2}% return value type \ifx\temp\empty\else \tclose{\temp} \fi #3% output function name }% {\rm\enskip}% hskip 0.5 em of \tenrm % \boldbrax % arguments will be output next, if any. } % Print arguments in slanted roman (not ttsl), inconsistently with using % tt for the name. This is because literal text is sometimes needed in % the argument list (groff manual), and ttsl and tt are not very % distinguishable. Prevent hyphenation at `-' chars. % \def\defunargs#1{% % use sl by default (not ttsl), % tt for the names. \df \sl \hyphenchar\font=0 % % On the other hand, if an argument has two dashes (for instance), we % want a way to get ttsl. Let's try @var for that. \let\var=\ttslanted #1% \sl\hyphenchar\font=45 } % We want ()&[] to print specially on the defun line. % \def\activeparens{% \catcode`\(=\active \catcode`\)=\active \catcode`\[=\active \catcode`\]=\active \catcode`\&=\active } % Make control sequences which act like normal parenthesis chars. \let\lparen = ( \let\rparen = ) % Be sure that we always have a definition for `(', etc. For example, % if the fn name has parens in it, \boldbrax will not be in effect yet, % so TeX would otherwise complain about undefined control sequence. { \activeparens \global\let(=\lparen \global\let)=\rparen \global\let[=\lbrack \global\let]=\rbrack \global\let& = \& \gdef\boldbrax{\let(=\opnr\let)=\clnr\let[=\lbrb\let]=\rbrb} \gdef\magicamp{\let&=\amprm} } \newcount\parencount % If we encounter &foo, then turn on ()-hacking afterwards \newif\ifampseen \def\amprm#1 {\ampseentrue{\bf\ }} \def\parenfont{% \ifampseen % At the first level, print parens in roman, % otherwise use the default font. \ifnum \parencount=1 \rm \fi \else % The \sf parens (in \boldbrax) actually are a little bolder than % the contained text. This is especially needed for [ and ] . \sf \fi } \def\infirstlevel#1{% \ifampseen \ifnum\parencount=1 #1% \fi \fi } \def\bfafterword#1 {#1 \bf} \def\opnr{% \global\advance\parencount by 1 {\parenfont(}% \infirstlevel \bfafterword } \def\clnr{% {\parenfont)}% \infirstlevel \sl \global\advance\parencount by -1 } \newcount\brackcount \def\lbrb{% \global\advance\brackcount by 1 {\bf[}% } \def\rbrb{% {\bf]}% \global\advance\brackcount by -1 } \def\checkparencounts{% \ifnum\parencount=0 \else \badparencount \fi \ifnum\brackcount=0 \else \badbrackcount \fi } \def\badparencount{% \errmessage{Unbalanced parentheses in @def}% \global\parencount=0 } \def\badbrackcount{% \errmessage{Unbalanced square braces in @def}% \global\brackcount=0 } \message{macros,} % @macro. % To do this right we need a feature of e-TeX, \scantokens, % which we arrange to emulate with a temporary file in ordinary TeX. \ifx\eTeXversion\undefined \newwrite\macscribble \def\scantokens#1{% \toks0={#1}% \immediate\openout\macscribble=\jobname.tmp \immediate\write\macscribble{\the\toks0}% \immediate\closeout\macscribble \input \jobname.tmp } \fi \def\scanmacro#1{% \begingroup \newlinechar`\^^M \let\xeatspaces\eatspaces % Undo catcode changes of \startcontents and \doprintindex % When called from @insertcopying or (short)caption, we need active % backslash to get it printed correctly. Previously, we had % \catcode`\\=\other instead. We'll see whether a problem appears % with macro expansion. --kasal, 19aug04 \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ % ... and \example \spaceisspace % % Append \endinput to make sure that TeX does not see the ending newline. % I've verified that it is necessary both for e-TeX and for ordinary TeX % --kasal, 29nov03 \scantokens{#1\endinput}% \endgroup } \def\scanexp#1{% \edef\temp{\noexpand\scanmacro{#1}}% \temp } \newcount\paramno % Count of parameters \newtoks\macname % Macro name \newif\ifrecursive % Is it recursive? % List of all defined macros in the form % \definedummyword\macro1\definedummyword\macro2... % Currently is also contains all @aliases; the list can be split % if there is a need. \def\macrolist{} % Add the macro to \macrolist \def\addtomacrolist#1{\expandafter \addtomacrolistxxx \csname#1\endcsname} \def\addtomacrolistxxx#1{% \toks0 = \expandafter{\macrolist\definedummyword#1}% \xdef\macrolist{\the\toks0}% } % Utility routines. % This does \let #1 = #2, with \csnames; that is, % \let \csname#1\endcsname = \csname#2\endcsname % (except of course we have to play expansion games). % \def\cslet#1#2{% \expandafter\let \csname#1\expandafter\endcsname \csname#2\endcsname } % Trim leading and trailing spaces off a string. % Concepts from aro-bend problem 15 (see CTAN). {\catcode`\@=11 \gdef\eatspaces #1{\expandafter\trim@\expandafter{#1 }} \gdef\trim@ #1{\trim@@ @#1 @ #1 @ @@} \gdef\trim@@ #1@ #2@ #3@@{\trim@@@\empty #2 @} \def\unbrace#1{#1} \unbrace{\gdef\trim@@@ #1 } #2@{#1} } % Trim a single trailing ^^M off a string. {\catcode`\^^M=\other \catcode`\Q=3% \gdef\eatcr #1{\eatcra #1Q^^MQ}% \gdef\eatcra#1^^MQ{\eatcrb#1Q}% \gdef\eatcrb#1Q#2Q{#1}% } % Macro bodies are absorbed as an argument in a context where % all characters are catcode 10, 11 or 12, except \ which is active % (as in normal texinfo). It is necessary to change the definition of \. % It's necessary to have hard CRs when the macro is executed. This is % done by making ^^M (\endlinechar) catcode 12 when reading the macro % body, and then making it the \newlinechar in \scanmacro. \def\scanctxt{% \catcode`\"=\other \catcode`\+=\other \catcode`\<=\other \catcode`\>=\other \catcode`\@=\other \catcode`\^=\other \catcode`\_=\other \catcode`\|=\other \catcode`\~=\other } \def\scanargctxt{% \scanctxt \catcode`\\=\other \catcode`\^^M=\other } \def\macrobodyctxt{% \scanctxt \catcode`\{=\other \catcode`\}=\other \catcode`\^^M=\other \usembodybackslash } \def\macroargctxt{% \scanctxt \catcode`\\=\other } % \mbodybackslash is the definition of \ in @macro bodies. % It maps \foo\ => \csname macarg.foo\endcsname => #N % where N is the macro parameter number. % We define \csname macarg.\endcsname to be \realbackslash, so % \\ in macro replacement text gets you a backslash. {\catcode`@=0 @catcode`@\=@active @gdef@usembodybackslash{@let\=@mbodybackslash} @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname} } \expandafter\def\csname macarg.\endcsname{\realbackslash} \def\macro{\recursivefalse\parsearg\macroxxx} \def\rmacro{\recursivetrue\parsearg\macroxxx} \def\macroxxx#1{% \getargs{#1}% now \macname is the macname and \argl the arglist \ifx\argl\empty % no arguments \paramno=0% \else \expandafter\parsemargdef \argl;% \fi \if1\csname ismacro.\the\macname\endcsname \message{Warning: redefining \the\macname}% \else \expandafter\ifx\csname \the\macname\endcsname \relax \else \errmessage{Macro name \the\macname\space already defined}\fi \global\cslet{macsave.\the\macname}{\the\macname}% \global\expandafter\let\csname ismacro.\the\macname\endcsname=1% \addtomacrolist{\the\macname}% \fi \begingroup \macrobodyctxt \ifrecursive \expandafter\parsermacbody \else \expandafter\parsemacbody \fi} \parseargdef\unmacro{% \if1\csname ismacro.#1\endcsname \global\cslet{#1}{macsave.#1}% \global\expandafter\let \csname ismacro.#1\endcsname=0% % Remove the macro name from \macrolist: \begingroup \expandafter\let\csname#1\endcsname \relax \let\definedummyword\unmacrodo \xdef\macrolist{\macrolist}% \endgroup \else \errmessage{Macro #1 not defined}% \fi } % Called by \do from \dounmacro on each macro. The idea is to omit any % macro definitions that have been changed to \relax. % \def\unmacrodo#1{% \ifx #1\relax % remove this \else \noexpand\definedummyword \noexpand#1% \fi } % This makes use of the obscure feature that if the last token of a % is #, then the preceding argument is delimited by % an opening brace, and that opening brace is not consumed. \def\getargs#1{\getargsxxx#1{}} \def\getargsxxx#1#{\getmacname #1 \relax\getmacargs} \def\getmacname #1 #2\relax{\macname={#1}} \def\getmacargs#1{\def\argl{#1}} % Parse the optional {params} list. Set up \paramno and \paramlist % so \defmacro knows what to do. Define \macarg.blah for each blah % in the params list, to be ##N where N is the position in that list. % That gets used by \mbodybackslash (above). % We need to get `macro parameter char #' into several definitions. % The technique used is stolen from LaTeX: let \hash be something % unexpandable, insert that wherever you need a #, and then redefine % it to # just before using the token list produced. % % The same technique is used to protect \eatspaces till just before % the macro is used. \def\parsemargdef#1;{\paramno=0\def\paramlist{}% \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,} \def\parsemargdefxxx#1,{% \if#1;\let\next=\relax \else \let\next=\parsemargdefxxx \advance\paramno by 1% \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname {\xeatspaces{\hash\the\paramno}}% \edef\paramlist{\paramlist\hash\the\paramno,}% \fi\next} % These two commands read recursive and nonrecursive macro bodies. % (They're different since rec and nonrec macros end differently.) \long\def\parsemacbody#1@end macro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% \long\def\parsermacbody#1@end rmacro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% % This defines the macro itself. There are six cases: recursive and % nonrecursive macros of zero, one, and many arguments. % Much magic with \expandafter here. % \xdef is used so that macro definitions will survive the file % they're defined in; @include reads the file inside a group. \def\defmacro{% \let\hash=##% convert placeholders to macro parameter chars \ifrecursive \ifcase\paramno % 0 \expandafter\xdef\csname\the\macname\endcsname{% \noexpand\scanmacro{\temp}}% \or % 1 \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\braceorline \expandafter\noexpand\csname\the\macname xxx\endcsname}% \expandafter\xdef\csname\the\macname xxx\endcsname##1{% \egroup\noexpand\scanmacro{\temp}}% \else % many \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\csname\the\macname xx\endcsname}% \expandafter\xdef\csname\the\macname xx\endcsname##1{% \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% \expandafter\expandafter \expandafter\xdef \expandafter\expandafter \csname\the\macname xxx\endcsname \paramlist{\egroup\noexpand\scanmacro{\temp}}% \fi \else \ifcase\paramno % 0 \expandafter\xdef\csname\the\macname\endcsname{% \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \or % 1 \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \noexpand\braceorline \expandafter\noexpand\csname\the\macname xxx\endcsname}% \expandafter\xdef\csname\the\macname xxx\endcsname##1{% \egroup \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \else % many \expandafter\xdef\csname\the\macname\endcsname{% \bgroup\noexpand\macroargctxt \expandafter\noexpand\csname\the\macname xx\endcsname}% \expandafter\xdef\csname\the\macname xx\endcsname##1{% \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% \expandafter\expandafter \expandafter\xdef \expandafter\expandafter \csname\the\macname xxx\endcsname \paramlist{% \egroup \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% \fi \fi} \def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}} % \braceorline decides whether the next nonwhitespace character is a % {. If so it reads up to the closing }, if not, it reads the whole % line. Whatever was read is then fed to the next control sequence % as an argument (by \parsebrace or \parsearg) \def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx} \def\braceorlinexxx{% \ifx\nchar\bgroup\else \expandafter\parsearg \fi \macnamexxx} % @alias. % We need some trickery to remove the optional spaces around the equal % sign. Just make them active and then expand them all to nothing. \def\alias{\parseargusing\obeyspaces\aliasxxx} \def\aliasxxx #1{\aliasyyy#1\relax} \def\aliasyyy #1=#2\relax{% {% \expandafter\let\obeyedspace=\empty \addtomacrolist{#1}% \xdef\next{\global\let\makecsname{#1}=\makecsname{#2}}% }% \next } \message{cross references,} \newwrite\auxfile \newif\ifhavexrefs % True if xref values are known. \newif\ifwarnedxrefs % True if we warned once that they aren't known. % @inforef is relatively simple. \def\inforef #1{\inforefzzz #1,,,,**} \def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, node \samp{\ignorespaces#1{}}} % @node's only job in TeX is to define \lastnode, which is used in % cross-references. The @node line might or might not have commas, and % might or might not have spaces before the first comma, like: % @node foo , bar , ... % We don't want such trailing spaces in the node name. % \parseargdef\node{\checkenv{}\donode #1 ,\finishnodeparse} % % also remove a trailing comma, in case of something like this: % @node Help-Cross, , , Cross-refs \def\donode#1 ,#2\finishnodeparse{\dodonode #1,\finishnodeparse} \def\dodonode#1,#2\finishnodeparse{\gdef\lastnode{#1}} \let\nwnode=\node \let\lastnode=\empty % Write a cross-reference definition for the current node. #1 is the % type (Ynumbered, Yappendix, Ynothing). % \def\donoderef#1{% \ifx\lastnode\empty\else \setref{\lastnode}{#1}% \global\let\lastnode=\empty \fi } % @anchor{NAME} -- define xref target at arbitrary point. % \newcount\savesfregister % \def\savesf{\relax \ifhmode \savesfregister=\spacefactor \fi} \def\restoresf{\relax \ifhmode \spacefactor=\savesfregister \fi} \def\anchor#1{\savesf \setref{#1}{Ynothing}\restoresf \ignorespaces} % \setref{NAME}{SNT} defines a cross-reference point NAME (a node or an % anchor), which consists of three parts: % 1) NAME-title - the current sectioning name taken from \thissection, % or the anchor name. % 2) NAME-snt - section number and type, passed as the SNT arg, or % empty for anchors. % 3) NAME-pg - the page number. % % This is called from \donoderef, \anchor, and \dofloat. In the case of % floats, there is an additional part, which is not written here: % 4) NAME-lof - the text as it should appear in a @listoffloats. % \def\setref#1#2{% \pdfmkdest{#1}% \iflinks {% \atdummies % preserve commands, but don't expand them \edef\writexrdef##1##2{% \write\auxfile{@xrdef{#1-% #1 of \setref, expanded by the \edef ##1}{##2}}% these are parameters of \writexrdef }% \toks0 = \expandafter{\thissection}% \immediate \writexrdef{title}{\the\toks0 }% \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc. \safewhatsit{\writexrdef{pg}{\folio}}% will be written later, during \shipout }% \fi } % @xref, @pxref, and @ref generate cross-references. For \xrefX, #1 is % the node name, #2 the name of the Info cross-reference, #3 the printed % node name, #4 the name of the Info file, #5 the name of the printed % manual. All but the node name can be omitted. % \def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]} \def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]} \def\ref#1{\xrefX[#1,,,,,,,]} \def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup \unsepspaces \def\printedmanual{\ignorespaces #5}% \def\printedrefname{\ignorespaces #3}% \setbox1=\hbox{\printedmanual\unskip}% \setbox0=\hbox{\printedrefname\unskip}% \ifdim \wd0 = 0pt % No printed node name was explicitly given. \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax % Use the node name inside the square brackets. \def\printedrefname{\ignorespaces #1}% \else % Use the actual chapter/section title appear inside % the square brackets. Use the real section title if we have it. \ifdim \wd1 > 0pt % It is in another manual, so we don't have it. \def\printedrefname{\ignorespaces #1}% \else \ifhavexrefs % We know the real title if we have the xref values. \def\printedrefname{\refx{#1-title}{}}% \else % Otherwise just copy the Info node name. \def\printedrefname{\ignorespaces #1}% \fi% \fi \fi \fi % % Make link in pdf output. \ifpdf \leavevmode \getfilename{#4}% {\indexnofonts \turnoffactive % See comments at \activebackslashdouble. {\activebackslashdouble \xdef\pdfxrefdest{#1}% \backslashparens\pdfxrefdest}% % \ifnum\filenamelength>0 \startlink attr{/Border [0 0 0]}% goto file{\the\filename.pdf} name{\pdfxrefdest}% \else \startlink attr{/Border [0 0 0]}% goto name{\pdfmkpgn{\pdfxrefdest}}% \fi }% \linkcolor \fi % % Float references are printed completely differently: "Figure 1.2" % instead of "[somenode], p.3". We distinguish them by the % LABEL-title being set to a magic string. {% % Have to otherify everything special to allow the \csname to % include an _ in the xref name, etc. \indexnofonts \turnoffactive \expandafter\global\expandafter\let\expandafter\Xthisreftitle \csname XR#1-title\endcsname }% \iffloat\Xthisreftitle % If the user specified the print name (third arg) to the ref, % print it instead of our usual "Figure 1.2". \ifdim\wd0 = 0pt \refx{#1-snt}{}% \else \printedrefname \fi % % if the user also gave the printed manual name (fifth arg), append % "in MANUALNAME". \ifdim \wd1 > 0pt \space \putwordin{} \cite{\printedmanual}% \fi \else % node/anchor (non-float) references. % % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not % insert empty discretionaries after hyphens, which means that it will % not find a line break at a hyphen in a node names. Since some manuals % are best written with fairly long node names, containing hyphens, this % is a loss. Therefore, we give the text of the node name again, so it % is as if TeX is seeing it for the first time. \ifdim \wd1 > 0pt \putwordsection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}% \else % _ (for example) has to be the character _ for the purposes of the % control sequence corresponding to the node, but it has to expand % into the usual \leavevmode...\vrule stuff for purposes of % printing. So we \turnoffactive for the \refx-snt, back on for the % printing, back off for the \refx-pg. {\turnoffactive % Only output a following space if the -snt ref is nonempty; for % @unnumbered and @anchor, it won't be. \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}% \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi }% % output the `[mynode]' via a macro so it can be overridden. \xrefprintnodename\printedrefname % % But we always want a comma and a space: ,\space % % output the `page 3'. \turnoffactive \putwordpage\tie\refx{#1-pg}{}% \fi \fi \endlink \endgroup} % This macro is called from \xrefX for the `[nodename]' part of xref % output. It's a separate macro only so it can be changed more easily, % since square brackets don't work well in some documents. Particularly % one that Bob is working on :). % \def\xrefprintnodename#1{[#1]} % Things referred to by \setref. % \def\Ynothing{} \def\Yomitfromtoc{} \def\Ynumbered{% \ifnum\secno=0 \putwordChapter@tie \the\chapno \else \ifnum\subsecno=0 \putwordSection@tie \the\chapno.\the\secno \else \ifnum\subsubsecno=0 \putwordSection@tie \the\chapno.\the\secno.\the\subsecno \else \putwordSection@tie \the\chapno.\the\secno.\the\subsecno.\the\subsubsecno \fi\fi\fi } \def\Yappendix{% \ifnum\secno=0 \putwordAppendix@tie @char\the\appendixno{}% \else \ifnum\subsecno=0 \putwordSection@tie @char\the\appendixno.\the\secno \else \ifnum\subsubsecno=0 \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno \else \putwordSection@tie @char\the\appendixno.\the\secno.\the\subsecno.\the\subsubsecno \fi\fi\fi } % Define \refx{NAME}{SUFFIX} to reference a cross-reference string named NAME. % If its value is nonempty, SUFFIX is output afterward. % \def\refx#1#2{% {% \indexnofonts \otherbackslash \expandafter\global\expandafter\let\expandafter\thisrefX \csname XR#1\endcsname }% \ifx\thisrefX\relax % If not defined, say something at least. \angleleft un\-de\-fined\angleright \iflinks \ifhavexrefs \message{\linenumber Undefined cross reference `#1'.}% \else \ifwarnedxrefs\else \global\warnedxrefstrue \message{Cross reference values unknown; you must run TeX again.}% \fi \fi \fi \else % It's defined, so just use it. \thisrefX \fi #2% Output the suffix in any case. } % This is the macro invoked by entries in the aux file. Usually it's % just a \def (we prepend XR to the control sequence name to avoid % collisions). But if this is a float type, we have more work to do. % \def\xrdef#1#2{% {% The node name might contain 8-bit characters, which in our current % implementation are changed to commands like @'e. Don't let these % mess up the control sequence name. \indexnofonts \turnoffactive \xdef\safexrefname{#1}% }% % \expandafter\gdef\csname XR\safexrefname\endcsname{#2}% remember this xref % % Was that xref control sequence that we just defined for a float? \expandafter\iffloat\csname XR\safexrefname\endcsname % it was a float, and we have the (safe) float type in \iffloattype. \expandafter\let\expandafter\floatlist \csname floatlist\iffloattype\endcsname % % Is this the first time we've seen this float type? \expandafter\ifx\floatlist\relax \toks0 = {\do}% yes, so just \do \else % had it before, so preserve previous elements in list. \toks0 = \expandafter{\floatlist\do}% \fi % % Remember this xref in the control sequence \floatlistFLOATTYPE, % for later use in \listoffloats. \expandafter\xdef\csname floatlist\iffloattype\endcsname{\the\toks0 {\safexrefname}}% \fi } % Read the last existing aux file, if any. No error if none exists. % \def\tryauxfile{% \openin 1 \jobname.aux \ifeof 1 \else \readdatafile{aux}% \global\havexrefstrue \fi \closein 1 } \def\setupdatafile{% \catcode`\^^@=\other \catcode`\^^A=\other \catcode`\^^B=\other \catcode`\^^C=\other \catcode`\^^D=\other \catcode`\^^E=\other \catcode`\^^F=\other \catcode`\^^G=\other \catcode`\^^H=\other \catcode`\^^K=\other \catcode`\^^L=\other \catcode`\^^N=\other \catcode`\^^P=\other \catcode`\^^Q=\other \catcode`\^^R=\other \catcode`\^^S=\other \catcode`\^^T=\other \catcode`\^^U=\other \catcode`\^^V=\other \catcode`\^^W=\other \catcode`\^^X=\other \catcode`\^^Z=\other \catcode`\^^[=\other \catcode`\^^\=\other \catcode`\^^]=\other \catcode`\^^^=\other \catcode`\^^_=\other % It was suggested to set the catcode of ^ to 7, which would allow ^^e4 etc. % in xref tags, i.e., node names. But since ^^e4 notation isn't % supported in the main text, it doesn't seem desirable. Furthermore, % that is not enough: for node names that actually contain a ^ % character, we would end up writing a line like this: 'xrdef {'hat % b-title}{'hat b} and \xrdef does a \csname...\endcsname on the first % argument, and \hat is not an expandable control sequence. It could % all be worked out, but why? Either we support ^^ or we don't. % % The other change necessary for this was to define \auxhat: % \def\auxhat{\def^{'hat }}% extra space so ok if followed by letter % and then to call \auxhat in \setq. % \catcode`\^=\other % % Special characters. Should be turned off anyway, but... \catcode`\~=\other \catcode`\[=\other \catcode`\]=\other \catcode`\"=\other \catcode`\_=\other \catcode`\|=\other \catcode`\<=\other \catcode`\>=\other \catcode`\$=\other \catcode`\#=\other \catcode`\&=\other \catcode`\%=\other \catcode`+=\other % avoid \+ for paranoia even though we've turned it off % % This is to support \ in node names and titles, since the \ % characters end up in a \csname. It's easier than % leaving it active and making its active definition an actual \ % character. What I don't understand is why it works in the *value* % of the xrdef. Seems like it should be a catcode12 \, and that % should not typeset properly. But it works, so I'm moving on for % now. --karl, 15jan04. \catcode`\\=\other % % Make the characters 128-255 be printing characters. {% \count1=128 \def\loop{% \catcode\count1=\other \advance\count1 by 1 \ifnum \count1<256 \loop \fi }% }% % % @ is our escape character in .aux files, and we need braces. \catcode`\{=1 \catcode`\}=2 \catcode`\@=0 } \def\readdatafile#1{% \begingroup \setupdatafile \input\jobname.#1 \endgroup} \message{insertions,} % including footnotes. \newcount \footnoteno % The trailing space in the following definition for supereject is % vital for proper filling; pages come out unaligned when you do a % pagealignmacro call if that space before the closing brace is % removed. (Generally, numeric constants should always be followed by a % space to prevent strange expansion errors.) \def\supereject{\par\penalty -20000\footnoteno =0 } % @footnotestyle is meaningful for info output only. \let\footnotestyle=\comment {\catcode `\@=11 % % Auto-number footnotes. Otherwise like plain. \gdef\footnote{% \let\indent=\ptexindent \let\noindent=\ptexnoindent \global\advance\footnoteno by \@ne \edef\thisfootno{$^{\the\footnoteno}$}% % % In case the footnote comes at the end of a sentence, preserve the % extra spacing after we do the footnote number. \let\@sf\empty \ifhmode\edef\@sf{\spacefactor\the\spacefactor}\ptexslash\fi % % Remove inadvertent blank space before typesetting the footnote number. \unskip \thisfootno\@sf \dofootnote }% % Don't bother with the trickery in plain.tex to not require the % footnote text as a parameter. Our footnotes don't need to be so general. % % Oh yes, they do; otherwise, @ifset (and anything else that uses % \parseargline) fails inside footnotes because the tokens are fixed when % the footnote is read. --karl, 16nov96. % \gdef\dofootnote{% \insert\footins\bgroup % We want to typeset this text as a normal paragraph, even if the % footnote reference occurs in (for example) a display environment. % So reset some parameters. \hsize=\pagewidth \interlinepenalty\interfootnotelinepenalty \splittopskip\ht\strutbox % top baseline for broken footnotes \splitmaxdepth\dp\strutbox \floatingpenalty\@MM \leftskip\z@skip \rightskip\z@skip \spaceskip\z@skip \xspaceskip\z@skip \parindent\defaultparindent % \smallfonts \rm % % Because we use hanging indentation in footnotes, a @noindent appears % to exdent this text, so make it be a no-op. makeinfo does not use % hanging indentation so @noindent can still be needed within footnote % text after an @example or the like (not that this is good style). \let\noindent = \relax % % Hang the footnote text off the number. Use \everypar in case the % footnote extends for more than one paragraph. \everypar = {\hang}% \textindent{\thisfootno}% % % Don't crash into the line above the footnote text. Since this % expands into a box, it must come within the paragraph, lest it % provide a place where TeX can split the footnote. \footstrut \futurelet\next\fo@t } }%end \catcode `\@=11 % In case a @footnote appears in a vbox, save the footnote text and create % the real \insert just after the vbox finished. Otherwise, the insertion % would be lost. -% Similarily, if a @footnote appears inside an alignment, save the footnote +% Similarly, if a @footnote appears inside an alignment, save the footnote % text to a box and make the \insert when a row of the table is finished. % And the same can be done for other insert classes. --kasal, 16nov03. % Replace the \insert primitive by a cheating macro. % Deeper inside, just make sure that the saved insertions are not spilled % out prematurely. % \def\startsavinginserts{% \ifx \insert\ptexinsert \let\insert\saveinsert \else \let\checkinserts\relax \fi } % This \insert replacement works for both \insert\footins{foo} and % \insert\footins\bgroup foo\egroup, but it doesn't work for \insert27{foo}. % \def\saveinsert#1{% \edef\next{\noexpand\savetobox \makeSAVEname#1}% \afterassignment\next % swallow the left brace \let\temp = } \def\makeSAVEname#1{\makecsname{SAVE\expandafter\gobble\string#1}} \def\savetobox#1{\global\setbox#1 = \vbox\bgroup \unvbox#1} \def\checksaveins#1{\ifvoid#1\else \placesaveins#1\fi} \def\placesaveins#1{% \ptexinsert \csname\expandafter\gobblesave\string#1\endcsname {\box#1}% } % eat @SAVE -- beware, all of them have catcode \other: { \def\dospecials{\do S\do A\do V\do E} \uncatcodespecials % ;-) \gdef\gobblesave @SAVE{} } % initialization: \def\newsaveins #1{% \edef\next{\noexpand\newsaveinsX \makeSAVEname#1}% \next } \def\newsaveinsX #1{% \csname newbox\endcsname #1% \expandafter\def\expandafter\checkinserts\expandafter{\checkinserts \checksaveins #1}% } % initialize: \let\checkinserts\empty \newsaveins\footins \newsaveins\margin % @image. We use the macros from epsf.tex to support this. % If epsf.tex is not installed and @image is used, we complain. % % Check for and read epsf.tex up front. If we read it only at @image % time, we might be inside a group, and then its definitions would get % undone and the next image would fail. \openin 1 = epsf.tex \ifeof 1 \else % Do not bother showing banner with epsf.tex v2.7k (available in % doc/epsf.tex and on ctan). \def\epsfannounce{\toks0 = }% \input epsf.tex \fi \closein 1 % % We will only complain once about lack of epsf.tex. \newif\ifwarnednoepsf \newhelp\noepsfhelp{epsf.tex must be installed for images to work. It is also included in the Texinfo distribution, or you can get it from ftp://tug.org/tex/epsf.tex.} % \def\image#1{% \ifx\epsfbox\undefined \ifwarnednoepsf \else \errhelp = \noepsfhelp \errmessage{epsf.tex not found, images will be ignored}% \global\warnednoepsftrue \fi \else \imagexxx #1,,,,,\finish \fi } % % Arguments to @image: % #1 is (mandatory) image filename; we tack on .eps extension. % #2 is (optional) width, #3 is (optional) height. % #4 is (ignored optional) html alt text. % #5 is (ignored optional) extension. % #6 is just the usual extra ignored arg for parsing this stuff. \newif\ifimagevmode \def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup \catcode`\^^M = 5 % in case we're inside an example \normalturnoffactive % allow _ et al. in names % If the image is by itself, center it. \ifvmode \imagevmodetrue \nobreak\bigskip % Usually we'll have text after the image which will insert % \parskip glue, so insert it here too to equalize the space % above and below. \nobreak\vskip\parskip \nobreak \line\bgroup \fi % % Output the image. \ifpdf \dopdfimage{#1}{#2}{#3}% \else % \epsfbox itself resets \epsf?size at each figure. \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \epsfxsize=#2\relax \fi \setbox0 = \hbox{\ignorespaces #3}\ifdim\wd0 > 0pt \epsfysize=#3\relax \fi \epsfbox{#1.eps}% \fi % \ifimagevmode \egroup \bigbreak \fi % space after the image \endgroup} % @float FLOATTYPE,LABEL,LOC ... @end float for displayed figures, tables, % etc. We don't actually implement floating yet, we always include the % float "here". But it seemed the best name for the future. % \envparseargdef\float{\eatcommaspace\eatcommaspace\dofloat#1, , ,\finish} % There may be a space before second and/or third parameter; delete it. \def\eatcommaspace#1, {#1,} % #1 is the optional FLOATTYPE, the text label for this float, typically % "Figure", "Table", "Example", etc. Can't contain commas. If omitted, % this float will not be numbered and cannot be referred to. % % #2 is the optional xref label. Also must be present for the float to % be referable. % % #3 is the optional positioning argument; for now, it is ignored. It % will somehow specify the positions allowed to float to (here, top, bottom). % % We keep a separate counter for each FLOATTYPE, which we reset at each % chapter-level command. \let\resetallfloatnos=\empty % \def\dofloat#1,#2,#3,#4\finish{% \let\thiscaption=\empty \let\thisshortcaption=\empty % % don't lose footnotes inside @float. % % BEWARE: when the floats start float, we have to issue warning whenever an % insert appears inside a float which could possibly float. --kasal, 26may04 % \startsavinginserts % % We can't be used inside a paragraph. \par % \vtop\bgroup \def\floattype{#1}% \def\floatlabel{#2}% \def\floatloc{#3}% we do nothing with this yet. % \ifx\floattype\empty \let\safefloattype=\empty \else {% % the floattype might have accents or other special characters, % but we need to use it in a control sequence name. \indexnofonts \turnoffactive \xdef\safefloattype{\floattype}% }% \fi % % If label is given but no type, we handle that as the empty type. \ifx\floatlabel\empty \else % We want each FLOATTYPE to be numbered separately (Figure 1, % Table 1, Figure 2, ...). (And if no label, no number.) % \expandafter\getfloatno\csname\safefloattype floatno\endcsname \global\advance\floatno by 1 % {% % This magic value for \thissection is output by \setref as the % XREFLABEL-title value. \xrefX uses it to distinguish float % labels (which have a completely different output format) from % node and anchor labels. And \xrdef uses it to construct the % lists of floats. % \edef\thissection{\floatmagic=\safefloattype}% \setref{\floatlabel}{Yfloat}% }% \fi % % start with \parskip glue, I guess. \vskip\parskip % % Don't suppress indentation if a float happens to start a section. \restorefirstparagraphindent } % we have these possibilities: % @float Foo,lbl & @caption{Cap}: Foo 1.1: Cap % @float Foo,lbl & no caption: Foo 1.1 % @float Foo & @caption{Cap}: Foo: Cap % @float Foo & no caption: Foo % @float ,lbl & Caption{Cap}: 1.1: Cap % @float ,lbl & no caption: 1.1 % @float & @caption{Cap}: Cap % @float & no caption: % \def\Efloat{% \let\floatident = \empty % % In all cases, if we have a float type, it comes first. \ifx\floattype\empty \else \def\floatident{\floattype}\fi % % If we have an xref label, the number comes next. \ifx\floatlabel\empty \else \ifx\floattype\empty \else % if also had float type, need tie first. \appendtomacro\floatident{\tie}% \fi % the number. \appendtomacro\floatident{\chaplevelprefix\the\floatno}% \fi % % Start the printed caption with what we've constructed in % \floatident, but keep it separate; we need \floatident again. \let\captionline = \floatident % \ifx\thiscaption\empty \else \ifx\floatident\empty \else \appendtomacro\captionline{: }% had ident, so need a colon between \fi % % caption text. \appendtomacro\captionline{\scanexp\thiscaption}% \fi % % If we have anything to print, print it, with space before. % Eventually this needs to become an \insert. \ifx\captionline\empty \else \vskip.5\parskip \captionline % % Space below caption. \vskip\parskip \fi % % If have an xref label, write the list of floats info. Do this % after the caption, to avoid chance of it being a breakpoint. \ifx\floatlabel\empty \else % Write the text that goes in the lof to the aux file as % \floatlabel-lof. Besides \floatident, we include the short % caption if specified, else the full caption if specified, else nothing. {% \atdummies % % since we read the caption text in the macro world, where ^^M % is turned into a normal character, we have to scan it back, so % we don't write the literal three characters "^^M" into the aux file. \scanexp{% \xdef\noexpand\gtemp{% \ifx\thisshortcaption\empty \thiscaption \else \thisshortcaption \fi }% }% \immediate\write\auxfile{@xrdef{\floatlabel-lof}{\floatident \ifx\gtemp\empty \else : \gtemp \fi}}% }% \fi \egroup % end of \vtop % % place the captured inserts % % BEWARE: when the floats start floating, we have to issue warning % whenever an insert appears inside a float which could possibly % float. --kasal, 26may04 % \checkinserts } % Append the tokens #2 to the definition of macro #1, not expanding either. % \def\appendtomacro#1#2{% \expandafter\def\expandafter#1\expandafter{#1#2}% } % @caption, @shortcaption % \def\caption{\docaption\thiscaption} \def\shortcaption{\docaption\thisshortcaption} \def\docaption{\checkenv\float \bgroup\scanargctxt\defcaption} \def\defcaption#1#2{\egroup \def#1{#2}} % The parameter is the control sequence identifying the counter we are % going to use. Create it if it doesn't exist and assign it to \floatno. \def\getfloatno#1{% \ifx#1\relax % Haven't seen this figure type before. \csname newcount\endcsname #1% % % Remember to reset this floatno at the next chap. \expandafter\gdef\expandafter\resetallfloatnos \expandafter{\resetallfloatnos #1=0 }% \fi \let\floatno#1% } % \setref calls this to get the XREFLABEL-snt value. We want an @xref % to the FLOATLABEL to expand to "Figure 3.1". We call \setref when we % first read the @float command. % \def\Yfloat{\floattype@tie \chaplevelprefix\the\floatno}% % Magic string used for the XREFLABEL-title value, so \xrefX can % distinguish floats from other xref types. \def\floatmagic{!!float!!} % #1 is the control sequence we are passed; we expand into a conditional % which is true if #1 represents a float ref. That is, the magic % \thissection value which we \setref above. % \def\iffloat#1{\expandafter\doiffloat#1==\finish} % % #1 is (maybe) the \floatmagic string. If so, #2 will be the % (safe) float type for this float. We set \iffloattype to #2. % \def\doiffloat#1=#2=#3\finish{% \def\temp{#1}% \def\iffloattype{#2}% \ifx\temp\floatmagic } % @listoffloats FLOATTYPE - print a list of floats like a table of contents. % \parseargdef\listoffloats{% \def\floattype{#1}% floattype {% % the floattype might have accents or other special characters, % but we need to use it in a control sequence name. \indexnofonts \turnoffactive \xdef\safefloattype{\floattype}% }% % % \xrdef saves the floats as a \do-list in \floatlistSAFEFLOATTYPE. \expandafter\ifx\csname floatlist\safefloattype\endcsname \relax \ifhavexrefs % if the user said @listoffloats foo but never @float foo. \message{\linenumber No `\safefloattype' floats to list.}% \fi \else \begingroup \leftskip=\tocindent % indent these entries like a toc \let\do=\listoffloatsdo \csname floatlist\safefloattype\endcsname \endgroup \fi } % This is called on each entry in a list of floats. We're passed the % xref label, in the form LABEL-title, which is how we save it in the % aux file. We strip off the -title and look up \XRLABEL-lof, which % has the text we're supposed to typeset here. % % Figures without xref labels will not be included in the list (since % they won't appear in the aux file). % \def\listoffloatsdo#1{\listoffloatsdoentry#1\finish} \def\listoffloatsdoentry#1-title\finish{{% % Can't fully expand XR#1-lof because it can contain anything. Just % pass the control sequence. On the other hand, XR#1-pg is just the % page number, and we want to fully expand that so we can get a link % in pdf output. \toksA = \expandafter{\csname XR#1-lof\endcsname}% % % use the same \entry macro we use to generate the TOC and index. \edef\writeentry{\noexpand\entry{\the\toksA}{\csname XR#1-pg\endcsname}}% \writeentry }} \message{localization,} % @documentlanguage is usually given very early, just after % @setfilename. If done too late, it may not override everything % properly. Single argument is the language abbreviation. % It would be nice if we could set up a hyphenation file here. % \parseargdef\documentlanguage{% \tex % read txi-??.tex file in plain TeX. % Read the file if it exists. \openin 1 txi-#1.tex \ifeof 1 \errhelp = \nolanghelp \errmessage{Cannot read language file txi-#1.tex}% \else \input txi-#1.tex \fi \closein 1 \endgroup } \newhelp\nolanghelp{The given language definition file cannot be found or is empty. Maybe you need to install it? In the current directory should work if nowhere else does.} % Set the catcode of characters 128 through 255 to the specified number. % \def\setnonasciicharscatcode#1{% \count255=128 \loop\ifnum\count255<256 \global\catcode\count255=#1 \advance\count255 by 1 \repeat } % @documentencoding sets the definition of non-ASCII characters % according to the specified encoding. % \parseargdef\documentencoding{% % Encoding being declared for the document. \def\declaredencoding{\csname #1.enc\endcsname}% % % Supported encodings: names converted to tokens in order to be able % to compare them with \ifx. \def\ascii{\csname US-ASCII.enc\endcsname}% \def\latnine{\csname ISO-8859-15.enc\endcsname}% \def\latone{\csname ISO-8859-1.enc\endcsname}% \def\lattwo{\csname ISO-8859-2.enc\endcsname}% \def\utfeight{\csname UTF-8.enc\endcsname}% % \ifx \declaredencoding \ascii \asciichardefs % \else \ifx \declaredencoding \lattwo \setnonasciicharscatcode\active \lattwochardefs % \else \ifx \declaredencoding \latone \setnonasciicharscatcode\active \latonechardefs % \else \ifx \declaredencoding \latnine \setnonasciicharscatcode\active \latninechardefs % \else \ifx \declaredencoding \utfeight \setnonasciicharscatcode\active \utfeightchardefs % \else \message{Unknown document encoding #1, ignoring.}% % \fi % utfeight \fi % latnine \fi % latone \fi % lattwo \fi % ascii } % A message to be logged when using a character that isn't available % the default font encoding (OT1). % \def\missingcharmsg#1{\message{Character missing in OT1 encoding: #1.}} % Take account of \c (plain) vs. \, (Texinfo) difference. \def\cedilla#1{\ifx\c\ptexc\c{#1}\else\,{#1}\fi} % First, make active non-ASCII characters in order for them to be % correctly categorized when TeX reads the replacement text of % macros containing the character definitions. \setnonasciicharscatcode\active % % Latin1 (ISO-8859-1) character definitions. \def\latonechardefs{% \gdef^^a0{~} \gdef^^a1{\exclamdown} \gdef^^a2{\missingcharmsg{CENT SIGN}} \gdef^^a3{{\pounds}} \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} \gdef^^a5{\missingcharmsg{YEN SIGN}} \gdef^^a6{\missingcharmsg{BROKEN BAR}} \gdef^^a7{\S} \gdef^^a8{\"{}} \gdef^^a9{\copyright} \gdef^^aa{\ordf} \gdef^^ab{\missingcharmsg{LEFT-POINTING DOUBLE ANGLE QUOTATION MARK}} \gdef^^ac{$\lnot$} \gdef^^ad{\-} \gdef^^ae{\registeredsymbol} \gdef^^af{\={}} % \gdef^^b0{\textdegree} \gdef^^b1{$\pm$} \gdef^^b2{$^2$} \gdef^^b3{$^3$} \gdef^^b4{\'{}} \gdef^^b5{$\mu$} \gdef^^b6{\P} % \gdef^^b7{$^.$} \gdef^^b8{\cedilla\ } \gdef^^b9{$^1$} \gdef^^ba{\ordm} % \gdef^^bb{\missingcharmsg{RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK}} \gdef^^bc{$1\over4$} \gdef^^bd{$1\over2$} \gdef^^be{$3\over4$} \gdef^^bf{\questiondown} % \gdef^^c0{\`A} \gdef^^c1{\'A} \gdef^^c2{\^A} \gdef^^c3{\~A} \gdef^^c4{\"A} \gdef^^c5{\ringaccent A} \gdef^^c6{\AE} \gdef^^c7{\cedilla C} \gdef^^c8{\`E} \gdef^^c9{\'E} \gdef^^ca{\^E} \gdef^^cb{\"E} \gdef^^cc{\`I} \gdef^^cd{\'I} \gdef^^ce{\^I} \gdef^^cf{\"I} % \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER ETH}} \gdef^^d1{\~N} \gdef^^d2{\`O} \gdef^^d3{\'O} \gdef^^d4{\^O} \gdef^^d5{\~O} \gdef^^d6{\"O} \gdef^^d7{$\times$} \gdef^^d8{\O} \gdef^^d9{\`U} \gdef^^da{\'U} \gdef^^db{\^U} \gdef^^dc{\"U} \gdef^^dd{\'Y} \gdef^^de{\missingcharmsg{LATIN CAPITAL LETTER THORN}} \gdef^^df{\ss} % \gdef^^e0{\`a} \gdef^^e1{\'a} \gdef^^e2{\^a} \gdef^^e3{\~a} \gdef^^e4{\"a} \gdef^^e5{\ringaccent a} \gdef^^e6{\ae} \gdef^^e7{\cedilla c} \gdef^^e8{\`e} \gdef^^e9{\'e} \gdef^^ea{\^e} \gdef^^eb{\"e} \gdef^^ec{\`{\dotless i}} \gdef^^ed{\'{\dotless i}} \gdef^^ee{\^{\dotless i}} \gdef^^ef{\"{\dotless i}} % \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER ETH}} \gdef^^f1{\~n} \gdef^^f2{\`o} \gdef^^f3{\'o} \gdef^^f4{\^o} \gdef^^f5{\~o} \gdef^^f6{\"o} \gdef^^f7{$\div$} \gdef^^f8{\o} \gdef^^f9{\`u} \gdef^^fa{\'u} \gdef^^fb{\^u} \gdef^^fc{\"u} \gdef^^fd{\'y} \gdef^^fe{\missingcharmsg{LATIN SMALL LETTER THORN}} \gdef^^ff{\"y} } % Latin9 (ISO-8859-15) encoding character definitions. \def\latninechardefs{% % Encoding is almost identical to Latin1. \latonechardefs % \gdef^^a4{\euro} \gdef^^a6{\v S} \gdef^^a8{\v s} \gdef^^b4{\v Z} \gdef^^b8{\v z} \gdef^^bc{\OE} \gdef^^bd{\oe} \gdef^^be{\"Y} } % Latin2 (ISO-8859-2) character definitions. \def\lattwochardefs{% \gdef^^a0{~} \gdef^^a1{\missingcharmsg{LATIN CAPITAL LETTER A WITH OGONEK}} \gdef^^a2{\u{}} \gdef^^a3{\L} \gdef^^a4{\missingcharmsg{CURRENCY SIGN}} \gdef^^a5{\v L} \gdef^^a6{\'S} \gdef^^a7{\S} \gdef^^a8{\"{}} \gdef^^a9{\v S} \gdef^^aa{\cedilla S} \gdef^^ab{\v T} \gdef^^ac{\'Z} \gdef^^ad{\-} \gdef^^ae{\v Z} \gdef^^af{\dotaccent Z} % \gdef^^b0{\textdegree} \gdef^^b1{\missingcharmsg{LATIN SMALL LETTER A WITH OGONEK}} \gdef^^b2{\missingcharmsg{OGONEK}} \gdef^^b3{\l} \gdef^^b4{\'{}} \gdef^^b5{\v l} \gdef^^b6{\'s} \gdef^^b7{\v{}} \gdef^^b8{\cedilla\ } \gdef^^b9{\v s} \gdef^^ba{\cedilla s} \gdef^^bb{\v t} \gdef^^bc{\'z} \gdef^^bd{\H{}} \gdef^^be{\v z} \gdef^^bf{\dotaccent z} % \gdef^^c0{\'R} \gdef^^c1{\'A} \gdef^^c2{\^A} \gdef^^c3{\u A} \gdef^^c4{\"A} \gdef^^c5{\'L} \gdef^^c6{\'C} \gdef^^c7{\cedilla C} \gdef^^c8{\v C} \gdef^^c9{\'E} \gdef^^ca{\missingcharmsg{LATIN CAPITAL LETTER E WITH OGONEK}} \gdef^^cb{\"E} \gdef^^cc{\v E} \gdef^^cd{\'I} \gdef^^ce{\^I} \gdef^^cf{\v D} % \gdef^^d0{\missingcharmsg{LATIN CAPITAL LETTER D WITH STROKE}} \gdef^^d1{\'N} \gdef^^d2{\v N} \gdef^^d3{\'O} \gdef^^d4{\^O} \gdef^^d5{\H O} \gdef^^d6{\"O} \gdef^^d7{$\times$} \gdef^^d8{\v R} \gdef^^d9{\ringaccent U} \gdef^^da{\'U} \gdef^^db{\H U} \gdef^^dc{\"U} \gdef^^dd{\'Y} \gdef^^de{\cedilla T} \gdef^^df{\ss} % \gdef^^e0{\'r} \gdef^^e1{\'a} \gdef^^e2{\^a} \gdef^^e3{\u a} \gdef^^e4{\"a} \gdef^^e5{\'l} \gdef^^e6{\'c} \gdef^^e7{\cedilla c} \gdef^^e8{\v c} \gdef^^e9{\'e} \gdef^^ea{\missingcharmsg{LATIN SMALL LETTER E WITH OGONEK}} \gdef^^eb{\"e} \gdef^^ec{\v e} \gdef^^ed{\'\i} \gdef^^ee{\^\i} \gdef^^ef{\v d} % \gdef^^f0{\missingcharmsg{LATIN SMALL LETTER D WITH STROKE}} \gdef^^f1{\'n} \gdef^^f2{\v n} \gdef^^f3{\'o} \gdef^^f4{\^o} \gdef^^f5{\H o} \gdef^^f6{\"o} \gdef^^f7{$\div$} \gdef^^f8{\v r} \gdef^^f9{\ringaccent u} \gdef^^fa{\'u} \gdef^^fb{\H u} \gdef^^fc{\"u} \gdef^^fd{\'y} \gdef^^fe{\cedilla t} \gdef^^ff{\dotaccent{}} } % UTF-8 character definitions. % % This code to support UTF-8 is based on LaTeX's utf8.def, with some % changes for Texinfo conventions. It is included here under the GPL by % permission from Frank Mittelbach and the LaTeX team. % \newcount\countUTFx \newcount\countUTFy \newcount\countUTFz \gdef\UTFviiiTwoOctets#1#2{\expandafter \UTFviiiDefined\csname u8:#1\string #2\endcsname} % \gdef\UTFviiiThreeOctets#1#2#3{\expandafter \UTFviiiDefined\csname u8:#1\string #2\string #3\endcsname} % \gdef\UTFviiiFourOctets#1#2#3#4{\expandafter \UTFviiiDefined\csname u8:#1\string #2\string #3\string #4\endcsname} \gdef\UTFviiiDefined#1{% \ifx #1\relax \message{\linenumber Unicode char \string #1 not defined for Texinfo}% \else \expandafter #1% \fi } \begingroup \catcode`\~13 \catcode`\"12 \def\UTFviiiLoop{% \global\catcode\countUTFx\active \uccode`\~\countUTFx \uppercase\expandafter{\UTFviiiTmp}% \advance\countUTFx by 1 \ifnum\countUTFx < \countUTFy \expandafter\UTFviiiLoop \fi} \countUTFx = "C2 \countUTFy = "E0 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiTwoOctets\string~}} \UTFviiiLoop \countUTFx = "E0 \countUTFy = "F0 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiThreeOctets\string~}} \UTFviiiLoop \countUTFx = "F0 \countUTFy = "F4 \def\UTFviiiTmp{% \xdef~{\noexpand\UTFviiiFourOctets\string~}} \UTFviiiLoop \endgroup \begingroup \catcode`\"=12 \catcode`\<=12 \catcode`\.=12 \catcode`\,=12 \catcode`\;=12 \catcode`\!=12 \catcode`\~=13 \gdef\DeclareUnicodeCharacter#1#2{% \countUTFz = "#1\relax \wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}% \begingroup \parseXMLCharref \def\UTFviiiTwoOctets##1##2{% \csname u8:##1\string ##2\endcsname}% \def\UTFviiiThreeOctets##1##2##3{% \csname u8:##1\string ##2\string ##3\endcsname}% \def\UTFviiiFourOctets##1##2##3##4{% \csname u8:##1\string ##2\string ##3\string ##4\endcsname}% \expandafter\expandafter\expandafter\expandafter \expandafter\expandafter\expandafter \gdef\UTFviiiTmp{#2}% \endgroup} \gdef\parseXMLCharref{% \ifnum\countUTFz < "A0\relax \errhelp = \EMsimple \errmessage{Cannot define Unicode char value < 00A0}% \else\ifnum\countUTFz < "800\relax \parseUTFviiiA,% \parseUTFviiiB C\UTFviiiTwoOctets.,% \else\ifnum\countUTFz < "10000\relax \parseUTFviiiA;% \parseUTFviiiA,% \parseUTFviiiB E\UTFviiiThreeOctets.{,;}% \else \parseUTFviiiA;% \parseUTFviiiA,% \parseUTFviiiA!% \parseUTFviiiB F\UTFviiiFourOctets.{!,;}% \fi\fi\fi } \gdef\parseUTFviiiA#1{% \countUTFx = \countUTFz \divide\countUTFz by 64 \countUTFy = \countUTFz \multiply\countUTFz by 64 \advance\countUTFx by -\countUTFz \advance\countUTFx by 128 \uccode `#1\countUTFx \countUTFz = \countUTFy} \gdef\parseUTFviiiB#1#2#3#4{% \advance\countUTFz by "#10\relax \uccode `#3\countUTFz \uppercase{\gdef\UTFviiiTmp{#2#3#4}}} \endgroup \def\utfeightchardefs{% \DeclareUnicodeCharacter{00A0}{\tie} \DeclareUnicodeCharacter{00A1}{\exclamdown} \DeclareUnicodeCharacter{00A3}{\pounds} \DeclareUnicodeCharacter{00A8}{\"{ }} \DeclareUnicodeCharacter{00A9}{\copyright} \DeclareUnicodeCharacter{00AA}{\ordf} \DeclareUnicodeCharacter{00AD}{\-} \DeclareUnicodeCharacter{00AE}{\registeredsymbol} \DeclareUnicodeCharacter{00AF}{\={ }} \DeclareUnicodeCharacter{00B0}{\ringaccent{ }} \DeclareUnicodeCharacter{00B4}{\'{ }} \DeclareUnicodeCharacter{00B8}{\cedilla{ }} \DeclareUnicodeCharacter{00BA}{\ordm} \DeclareUnicodeCharacter{00BF}{\questiondown} \DeclareUnicodeCharacter{00C0}{\`A} \DeclareUnicodeCharacter{00C1}{\'A} \DeclareUnicodeCharacter{00C2}{\^A} \DeclareUnicodeCharacter{00C3}{\~A} \DeclareUnicodeCharacter{00C4}{\"A} \DeclareUnicodeCharacter{00C5}{\AA} \DeclareUnicodeCharacter{00C6}{\AE} \DeclareUnicodeCharacter{00C7}{\cedilla{C}} \DeclareUnicodeCharacter{00C8}{\`E} \DeclareUnicodeCharacter{00C9}{\'E} \DeclareUnicodeCharacter{00CA}{\^E} \DeclareUnicodeCharacter{00CB}{\"E} \DeclareUnicodeCharacter{00CC}{\`I} \DeclareUnicodeCharacter{00CD}{\'I} \DeclareUnicodeCharacter{00CE}{\^I} \DeclareUnicodeCharacter{00CF}{\"I} \DeclareUnicodeCharacter{00D1}{\~N} \DeclareUnicodeCharacter{00D2}{\`O} \DeclareUnicodeCharacter{00D3}{\'O} \DeclareUnicodeCharacter{00D4}{\^O} \DeclareUnicodeCharacter{00D5}{\~O} \DeclareUnicodeCharacter{00D6}{\"O} \DeclareUnicodeCharacter{00D8}{\O} \DeclareUnicodeCharacter{00D9}{\`U} \DeclareUnicodeCharacter{00DA}{\'U} \DeclareUnicodeCharacter{00DB}{\^U} \DeclareUnicodeCharacter{00DC}{\"U} \DeclareUnicodeCharacter{00DD}{\'Y} \DeclareUnicodeCharacter{00DF}{\ss} \DeclareUnicodeCharacter{00E0}{\`a} \DeclareUnicodeCharacter{00E1}{\'a} \DeclareUnicodeCharacter{00E2}{\^a} \DeclareUnicodeCharacter{00E3}{\~a} \DeclareUnicodeCharacter{00E4}{\"a} \DeclareUnicodeCharacter{00E5}{\aa} \DeclareUnicodeCharacter{00E6}{\ae} \DeclareUnicodeCharacter{00E7}{\cedilla{c}} \DeclareUnicodeCharacter{00E8}{\`e} \DeclareUnicodeCharacter{00E9}{\'e} \DeclareUnicodeCharacter{00EA}{\^e} \DeclareUnicodeCharacter{00EB}{\"e} \DeclareUnicodeCharacter{00EC}{\`{\dotless{i}}} \DeclareUnicodeCharacter{00ED}{\'{\dotless{i}}} \DeclareUnicodeCharacter{00EE}{\^{\dotless{i}}} \DeclareUnicodeCharacter{00EF}{\"{\dotless{i}}} \DeclareUnicodeCharacter{00F1}{\~n} \DeclareUnicodeCharacter{00F2}{\`o} \DeclareUnicodeCharacter{00F3}{\'o} \DeclareUnicodeCharacter{00F4}{\^o} \DeclareUnicodeCharacter{00F5}{\~o} \DeclareUnicodeCharacter{00F6}{\"o} \DeclareUnicodeCharacter{00F8}{\o} \DeclareUnicodeCharacter{00F9}{\`u} \DeclareUnicodeCharacter{00FA}{\'u} \DeclareUnicodeCharacter{00FB}{\^u} \DeclareUnicodeCharacter{00FC}{\"u} \DeclareUnicodeCharacter{00FD}{\'y} \DeclareUnicodeCharacter{00FF}{\"y} \DeclareUnicodeCharacter{0100}{\=A} \DeclareUnicodeCharacter{0101}{\=a} \DeclareUnicodeCharacter{0102}{\u{A}} \DeclareUnicodeCharacter{0103}{\u{a}} \DeclareUnicodeCharacter{0106}{\'C} \DeclareUnicodeCharacter{0107}{\'c} \DeclareUnicodeCharacter{0108}{\^C} \DeclareUnicodeCharacter{0109}{\^c} \DeclareUnicodeCharacter{010A}{\dotaccent{C}} \DeclareUnicodeCharacter{010B}{\dotaccent{c}} \DeclareUnicodeCharacter{010C}{\v{C}} \DeclareUnicodeCharacter{010D}{\v{c}} \DeclareUnicodeCharacter{010E}{\v{D}} \DeclareUnicodeCharacter{0112}{\=E} \DeclareUnicodeCharacter{0113}{\=e} \DeclareUnicodeCharacter{0114}{\u{E}} \DeclareUnicodeCharacter{0115}{\u{e}} \DeclareUnicodeCharacter{0116}{\dotaccent{E}} \DeclareUnicodeCharacter{0117}{\dotaccent{e}} \DeclareUnicodeCharacter{011A}{\v{E}} \DeclareUnicodeCharacter{011B}{\v{e}} \DeclareUnicodeCharacter{011C}{\^G} \DeclareUnicodeCharacter{011D}{\^g} \DeclareUnicodeCharacter{011E}{\u{G}} \DeclareUnicodeCharacter{011F}{\u{g}} \DeclareUnicodeCharacter{0120}{\dotaccent{G}} \DeclareUnicodeCharacter{0121}{\dotaccent{g}} \DeclareUnicodeCharacter{0124}{\^H} \DeclareUnicodeCharacter{0125}{\^h} \DeclareUnicodeCharacter{0128}{\~I} \DeclareUnicodeCharacter{0129}{\~{\dotless{i}}} \DeclareUnicodeCharacter{012A}{\=I} \DeclareUnicodeCharacter{012B}{\={\dotless{i}}} \DeclareUnicodeCharacter{012C}{\u{I}} \DeclareUnicodeCharacter{012D}{\u{\dotless{i}}} \DeclareUnicodeCharacter{0130}{\dotaccent{I}} \DeclareUnicodeCharacter{0131}{\dotless{i}} \DeclareUnicodeCharacter{0132}{IJ} \DeclareUnicodeCharacter{0133}{ij} \DeclareUnicodeCharacter{0134}{\^J} \DeclareUnicodeCharacter{0135}{\^{\dotless{j}}} \DeclareUnicodeCharacter{0139}{\'L} \DeclareUnicodeCharacter{013A}{\'l} \DeclareUnicodeCharacter{0141}{\L} \DeclareUnicodeCharacter{0142}{\l} \DeclareUnicodeCharacter{0143}{\'N} \DeclareUnicodeCharacter{0144}{\'n} \DeclareUnicodeCharacter{0147}{\v{N}} \DeclareUnicodeCharacter{0148}{\v{n}} \DeclareUnicodeCharacter{014C}{\=O} \DeclareUnicodeCharacter{014D}{\=o} \DeclareUnicodeCharacter{014E}{\u{O}} \DeclareUnicodeCharacter{014F}{\u{o}} \DeclareUnicodeCharacter{0150}{\H{O}} \DeclareUnicodeCharacter{0151}{\H{o}} \DeclareUnicodeCharacter{0152}{\OE} \DeclareUnicodeCharacter{0153}{\oe} \DeclareUnicodeCharacter{0154}{\'R} \DeclareUnicodeCharacter{0155}{\'r} \DeclareUnicodeCharacter{0158}{\v{R}} \DeclareUnicodeCharacter{0159}{\v{r}} \DeclareUnicodeCharacter{015A}{\'S} \DeclareUnicodeCharacter{015B}{\'s} \DeclareUnicodeCharacter{015C}{\^S} \DeclareUnicodeCharacter{015D}{\^s} \DeclareUnicodeCharacter{015E}{\cedilla{S}} \DeclareUnicodeCharacter{015F}{\cedilla{s}} \DeclareUnicodeCharacter{0160}{\v{S}} \DeclareUnicodeCharacter{0161}{\v{s}} \DeclareUnicodeCharacter{0162}{\cedilla{t}} \DeclareUnicodeCharacter{0163}{\cedilla{T}} \DeclareUnicodeCharacter{0164}{\v{T}} \DeclareUnicodeCharacter{0168}{\~U} \DeclareUnicodeCharacter{0169}{\~u} \DeclareUnicodeCharacter{016A}{\=U} \DeclareUnicodeCharacter{016B}{\=u} \DeclareUnicodeCharacter{016C}{\u{U}} \DeclareUnicodeCharacter{016D}{\u{u}} \DeclareUnicodeCharacter{016E}{\ringaccent{U}} \DeclareUnicodeCharacter{016F}{\ringaccent{u}} \DeclareUnicodeCharacter{0170}{\H{U}} \DeclareUnicodeCharacter{0171}{\H{u}} \DeclareUnicodeCharacter{0174}{\^W} \DeclareUnicodeCharacter{0175}{\^w} \DeclareUnicodeCharacter{0176}{\^Y} \DeclareUnicodeCharacter{0177}{\^y} \DeclareUnicodeCharacter{0178}{\"Y} \DeclareUnicodeCharacter{0179}{\'Z} \DeclareUnicodeCharacter{017A}{\'z} \DeclareUnicodeCharacter{017B}{\dotaccent{Z}} \DeclareUnicodeCharacter{017C}{\dotaccent{z}} \DeclareUnicodeCharacter{017D}{\v{Z}} \DeclareUnicodeCharacter{017E}{\v{z}} \DeclareUnicodeCharacter{01C4}{D\v{Z}} \DeclareUnicodeCharacter{01C5}{D\v{z}} \DeclareUnicodeCharacter{01C6}{d\v{z}} \DeclareUnicodeCharacter{01C7}{LJ} \DeclareUnicodeCharacter{01C8}{Lj} \DeclareUnicodeCharacter{01C9}{lj} \DeclareUnicodeCharacter{01CA}{NJ} \DeclareUnicodeCharacter{01CB}{Nj} \DeclareUnicodeCharacter{01CC}{nj} \DeclareUnicodeCharacter{01CD}{\v{A}} \DeclareUnicodeCharacter{01CE}{\v{a}} \DeclareUnicodeCharacter{01CF}{\v{I}} \DeclareUnicodeCharacter{01D0}{\v{\dotless{i}}} \DeclareUnicodeCharacter{01D1}{\v{O}} \DeclareUnicodeCharacter{01D2}{\v{o}} \DeclareUnicodeCharacter{01D3}{\v{U}} \DeclareUnicodeCharacter{01D4}{\v{u}} \DeclareUnicodeCharacter{01E2}{\={\AE}} \DeclareUnicodeCharacter{01E3}{\={\ae}} \DeclareUnicodeCharacter{01E6}{\v{G}} \DeclareUnicodeCharacter{01E7}{\v{g}} \DeclareUnicodeCharacter{01E8}{\v{K}} \DeclareUnicodeCharacter{01E9}{\v{k}} \DeclareUnicodeCharacter{01F0}{\v{\dotless{j}}} \DeclareUnicodeCharacter{01F1}{DZ} \DeclareUnicodeCharacter{01F2}{Dz} \DeclareUnicodeCharacter{01F3}{dz} \DeclareUnicodeCharacter{01F4}{\'G} \DeclareUnicodeCharacter{01F5}{\'g} \DeclareUnicodeCharacter{01F8}{\`N} \DeclareUnicodeCharacter{01F9}{\`n} \DeclareUnicodeCharacter{01FC}{\'{\AE}} \DeclareUnicodeCharacter{01FD}{\'{\ae}} \DeclareUnicodeCharacter{01FE}{\'{\O}} \DeclareUnicodeCharacter{01FF}{\'{\o}} \DeclareUnicodeCharacter{021E}{\v{H}} \DeclareUnicodeCharacter{021F}{\v{h}} \DeclareUnicodeCharacter{0226}{\dotaccent{A}} \DeclareUnicodeCharacter{0227}{\dotaccent{a}} \DeclareUnicodeCharacter{0228}{\cedilla{E}} \DeclareUnicodeCharacter{0229}{\cedilla{e}} \DeclareUnicodeCharacter{022E}{\dotaccent{O}} \DeclareUnicodeCharacter{022F}{\dotaccent{o}} \DeclareUnicodeCharacter{0232}{\=Y} \DeclareUnicodeCharacter{0233}{\=y} \DeclareUnicodeCharacter{0237}{\dotless{j}} \DeclareUnicodeCharacter{1E02}{\dotaccent{B}} \DeclareUnicodeCharacter{1E03}{\dotaccent{b}} \DeclareUnicodeCharacter{1E04}{\udotaccent{B}} \DeclareUnicodeCharacter{1E05}{\udotaccent{b}} \DeclareUnicodeCharacter{1E06}{\ubaraccent{B}} \DeclareUnicodeCharacter{1E07}{\ubaraccent{b}} \DeclareUnicodeCharacter{1E0A}{\dotaccent{D}} \DeclareUnicodeCharacter{1E0B}{\dotaccent{d}} \DeclareUnicodeCharacter{1E0C}{\udotaccent{D}} \DeclareUnicodeCharacter{1E0D}{\udotaccent{d}} \DeclareUnicodeCharacter{1E0E}{\ubaraccent{D}} \DeclareUnicodeCharacter{1E0F}{\ubaraccent{d}} \DeclareUnicodeCharacter{1E1E}{\dotaccent{F}} \DeclareUnicodeCharacter{1E1F}{\dotaccent{f}} \DeclareUnicodeCharacter{1E20}{\=G} \DeclareUnicodeCharacter{1E21}{\=g} \DeclareUnicodeCharacter{1E22}{\dotaccent{H}} \DeclareUnicodeCharacter{1E23}{\dotaccent{h}} \DeclareUnicodeCharacter{1E24}{\udotaccent{H}} \DeclareUnicodeCharacter{1E25}{\udotaccent{h}} \DeclareUnicodeCharacter{1E26}{\"H} \DeclareUnicodeCharacter{1E27}{\"h} \DeclareUnicodeCharacter{1E30}{\'K} \DeclareUnicodeCharacter{1E31}{\'k} \DeclareUnicodeCharacter{1E32}{\udotaccent{K}} \DeclareUnicodeCharacter{1E33}{\udotaccent{k}} \DeclareUnicodeCharacter{1E34}{\ubaraccent{K}} \DeclareUnicodeCharacter{1E35}{\ubaraccent{k}} \DeclareUnicodeCharacter{1E36}{\udotaccent{L}} \DeclareUnicodeCharacter{1E37}{\udotaccent{l}} \DeclareUnicodeCharacter{1E3A}{\ubaraccent{L}} \DeclareUnicodeCharacter{1E3B}{\ubaraccent{l}} \DeclareUnicodeCharacter{1E3E}{\'M} \DeclareUnicodeCharacter{1E3F}{\'m} \DeclareUnicodeCharacter{1E40}{\dotaccent{M}} \DeclareUnicodeCharacter{1E41}{\dotaccent{m}} \DeclareUnicodeCharacter{1E42}{\udotaccent{M}} \DeclareUnicodeCharacter{1E43}{\udotaccent{m}} \DeclareUnicodeCharacter{1E44}{\dotaccent{N}} \DeclareUnicodeCharacter{1E45}{\dotaccent{n}} \DeclareUnicodeCharacter{1E46}{\udotaccent{N}} \DeclareUnicodeCharacter{1E47}{\udotaccent{n}} \DeclareUnicodeCharacter{1E48}{\ubaraccent{N}} \DeclareUnicodeCharacter{1E49}{\ubaraccent{n}} \DeclareUnicodeCharacter{1E54}{\'P} \DeclareUnicodeCharacter{1E55}{\'p} \DeclareUnicodeCharacter{1E56}{\dotaccent{P}} \DeclareUnicodeCharacter{1E57}{\dotaccent{p}} \DeclareUnicodeCharacter{1E58}{\dotaccent{R}} \DeclareUnicodeCharacter{1E59}{\dotaccent{r}} \DeclareUnicodeCharacter{1E5A}{\udotaccent{R}} \DeclareUnicodeCharacter{1E5B}{\udotaccent{r}} \DeclareUnicodeCharacter{1E5E}{\ubaraccent{R}} \DeclareUnicodeCharacter{1E5F}{\ubaraccent{r}} \DeclareUnicodeCharacter{1E60}{\dotaccent{S}} \DeclareUnicodeCharacter{1E61}{\dotaccent{s}} \DeclareUnicodeCharacter{1E62}{\udotaccent{S}} \DeclareUnicodeCharacter{1E63}{\udotaccent{s}} \DeclareUnicodeCharacter{1E6A}{\dotaccent{T}} \DeclareUnicodeCharacter{1E6B}{\dotaccent{t}} \DeclareUnicodeCharacter{1E6C}{\udotaccent{T}} \DeclareUnicodeCharacter{1E6D}{\udotaccent{t}} \DeclareUnicodeCharacter{1E6E}{\ubaraccent{T}} \DeclareUnicodeCharacter{1E6F}{\ubaraccent{t}} \DeclareUnicodeCharacter{1E7C}{\~V} \DeclareUnicodeCharacter{1E7D}{\~v} \DeclareUnicodeCharacter{1E7E}{\udotaccent{V}} \DeclareUnicodeCharacter{1E7F}{\udotaccent{v}} \DeclareUnicodeCharacter{1E80}{\`W} \DeclareUnicodeCharacter{1E81}{\`w} \DeclareUnicodeCharacter{1E82}{\'W} \DeclareUnicodeCharacter{1E83}{\'w} \DeclareUnicodeCharacter{1E84}{\"W} \DeclareUnicodeCharacter{1E85}{\"w} \DeclareUnicodeCharacter{1E86}{\dotaccent{W}} \DeclareUnicodeCharacter{1E87}{\dotaccent{w}} \DeclareUnicodeCharacter{1E88}{\udotaccent{W}} \DeclareUnicodeCharacter{1E89}{\udotaccent{w}} \DeclareUnicodeCharacter{1E8A}{\dotaccent{X}} \DeclareUnicodeCharacter{1E8B}{\dotaccent{x}} \DeclareUnicodeCharacter{1E8C}{\"X} \DeclareUnicodeCharacter{1E8D}{\"x} \DeclareUnicodeCharacter{1E8E}{\dotaccent{Y}} \DeclareUnicodeCharacter{1E8F}{\dotaccent{y}} \DeclareUnicodeCharacter{1E90}{\^Z} \DeclareUnicodeCharacter{1E91}{\^z} \DeclareUnicodeCharacter{1E92}{\udotaccent{Z}} \DeclareUnicodeCharacter{1E93}{\udotaccent{z}} \DeclareUnicodeCharacter{1E94}{\ubaraccent{Z}} \DeclareUnicodeCharacter{1E95}{\ubaraccent{z}} \DeclareUnicodeCharacter{1E96}{\ubaraccent{h}} \DeclareUnicodeCharacter{1E97}{\"t} \DeclareUnicodeCharacter{1E98}{\ringaccent{w}} \DeclareUnicodeCharacter{1E99}{\ringaccent{y}} \DeclareUnicodeCharacter{1EA0}{\udotaccent{A}} \DeclareUnicodeCharacter{1EA1}{\udotaccent{a}} \DeclareUnicodeCharacter{1EB8}{\udotaccent{E}} \DeclareUnicodeCharacter{1EB9}{\udotaccent{e}} \DeclareUnicodeCharacter{1EBC}{\~E} \DeclareUnicodeCharacter{1EBD}{\~e} \DeclareUnicodeCharacter{1ECA}{\udotaccent{I}} \DeclareUnicodeCharacter{1ECB}{\udotaccent{i}} \DeclareUnicodeCharacter{1ECC}{\udotaccent{O}} \DeclareUnicodeCharacter{1ECD}{\udotaccent{o}} \DeclareUnicodeCharacter{1EE4}{\udotaccent{U}} \DeclareUnicodeCharacter{1EE5}{\udotaccent{u}} \DeclareUnicodeCharacter{1EF2}{\`Y} \DeclareUnicodeCharacter{1EF3}{\`y} \DeclareUnicodeCharacter{1EF4}{\udotaccent{Y}} \DeclareUnicodeCharacter{1EF8}{\~Y} \DeclareUnicodeCharacter{1EF9}{\~y} \DeclareUnicodeCharacter{2013}{--} \DeclareUnicodeCharacter{2014}{---} \DeclareUnicodeCharacter{2022}{\bullet} \DeclareUnicodeCharacter{2026}{\dots} \DeclareUnicodeCharacter{20AC}{\euro} \DeclareUnicodeCharacter{2192}{\expansion} \DeclareUnicodeCharacter{21D2}{\result} \DeclareUnicodeCharacter{2212}{\minus} \DeclareUnicodeCharacter{2217}{\point} \DeclareUnicodeCharacter{2261}{\equiv} }% end of \utfeightchardefs % US-ASCII character definitions. \def\asciichardefs{% nothing need be done \relax } % Make non-ASCII characters printable again for compatibility with % existing Texinfo documents that may use them, even without declaring a % document encoding. % \setnonasciicharscatcode \other \message{formatting,} \newdimen\defaultparindent \defaultparindent = 15pt \chapheadingskip = 15pt plus 4pt minus 2pt \secheadingskip = 12pt plus 3pt minus 2pt \subsecheadingskip = 9pt plus 2pt minus 2pt % Prevent underfull vbox error messages. \vbadness = 10000 % Don't be so finicky about underfull hboxes, either. \hbadness = 2000 % Following George Bush, just get rid of widows and orphans. \widowpenalty=10000 \clubpenalty=10000 % Use TeX 3.0's \emergencystretch to help line breaking, but if we're % using an old version of TeX, don't do anything. We want the amount of % stretch added to depend on the line length, hence the dependence on % \hsize. We call this whenever the paper size is set. % \def\setemergencystretch{% \ifx\emergencystretch\thisisundefined % Allow us to assign to \emergencystretch anyway. \def\emergencystretch{\dimen0}% \else \emergencystretch = .15\hsize \fi } % Parameters in order: 1) textheight; 2) textwidth; % 3) voffset; 4) hoffset; 5) binding offset; 6) topskip; % 7) physical page height; 8) physical page width. % % We also call \setleading{\textleading}, so the caller should define % \textleading. The caller should also set \parskip. % \def\internalpagesizes#1#2#3#4#5#6#7#8{% \voffset = #3\relax \topskip = #6\relax \splittopskip = \topskip % \vsize = #1\relax \advance\vsize by \topskip \outervsize = \vsize \advance\outervsize by 2\topandbottommargin \pageheight = \vsize % \hsize = #2\relax \outerhsize = \hsize \advance\outerhsize by 0.5in \pagewidth = \hsize % \normaloffset = #4\relax \bindingoffset = #5\relax % \ifpdf \pdfpageheight #7\relax \pdfpagewidth #8\relax \fi % \setleading{\textleading} % \parindent = \defaultparindent \setemergencystretch } % @letterpaper (the default). \def\letterpaper{{\globaldefs = 1 \parskip = 3pt plus 2pt minus 1pt \textleading = 13.2pt % % If page is nothing but text, make it come out even. \internalpagesizes{46\baselineskip}{6in}% {\voffset}{.25in}% {\bindingoffset}{36pt}% {11in}{8.5in}% }} % Use @smallbook to reset parameters for 7x9.25 trim size. \def\smallbook{{\globaldefs = 1 \parskip = 2pt plus 1pt \textleading = 12pt % \internalpagesizes{7.5in}{5in}% {\voffset}{.25in}% {\bindingoffset}{16pt}% {9.25in}{7in}% % \lispnarrowing = 0.3in \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = .5cm }} % Use @smallerbook to reset parameters for 6x9 trim size. % (Just testing, parameters still in flux.) \def\smallerbook{{\globaldefs = 1 \parskip = 1.5pt plus 1pt \textleading = 12pt % \internalpagesizes{7.4in}{4.8in}% {-.2in}{-.4in}% {0pt}{14pt}% {9in}{6in}% % \lispnarrowing = 0.25in \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = .4cm }} % Use @afourpaper to print on European A4 paper. \def\afourpaper{{\globaldefs = 1 \parskip = 3pt plus 2pt minus 1pt \textleading = 13.2pt % % Double-side printing via postscript on Laserjet 4050 % prints double-sided nicely when \bindingoffset=10mm and \hoffset=-6mm. % To change the settings for a different printer or situation, adjust % \normaloffset until the front-side and back-side texts align. Then % do the same for \bindingoffset. You can set these for testing in % your texinfo source file like this: % @tex % \global\normaloffset = -6mm % \global\bindingoffset = 10mm % @end tex \internalpagesizes{51\baselineskip}{160mm} {\voffset}{\hoffset}% {\bindingoffset}{44pt}% {297mm}{210mm}% % \tolerance = 700 \hfuzz = 1pt \contentsrightmargin = 0pt \defbodyindent = 5mm }} % Use @afivepaper to print on European A5 paper. % From romildo@urano.iceb.ufop.br, 2 July 2000. % He also recommends making @example and @lisp be small. \def\afivepaper{{\globaldefs = 1 \parskip = 2pt plus 1pt minus 0.1pt \textleading = 12.5pt % \internalpagesizes{160mm}{120mm}% {\voffset}{\hoffset}% {\bindingoffset}{8pt}% {210mm}{148mm}% % \lispnarrowing = 0.2in \tolerance = 800 \hfuzz = 1.2pt \contentsrightmargin = 0pt \defbodyindent = 2mm \tableindent = 12mm }} % A specific text layout, 24x15cm overall, intended for A4 paper. \def\afourlatex{{\globaldefs = 1 \afourpaper \internalpagesizes{237mm}{150mm}% {\voffset}{4.6mm}% {\bindingoffset}{7mm}% {297mm}{210mm}% % % Must explicitly reset to 0 because we call \afourpaper. \globaldefs = 0 }} % Use @afourwide to print on A4 paper in landscape format. \def\afourwide{{\globaldefs = 1 \afourpaper \internalpagesizes{241mm}{165mm}% {\voffset}{-2.95mm}% {\bindingoffset}{7mm}% {297mm}{210mm}% \globaldefs = 0 }} % @pagesizes TEXTHEIGHT[,TEXTWIDTH] % Perhaps we should allow setting the margins, \topskip, \parskip, % and/or leading, also. Or perhaps we should compute them somehow. % \parseargdef\pagesizes{\pagesizesyyy #1,,\finish} \def\pagesizesyyy#1,#2,#3\finish{{% \setbox0 = \hbox{\ignorespaces #2}\ifdim\wd0 > 0pt \hsize=#2\relax \fi \globaldefs = 1 % \parskip = 3pt plus 2pt minus 1pt \setleading{\textleading}% % \dimen0 = #1 \advance\dimen0 by \voffset % \dimen2 = \hsize \advance\dimen2 by \normaloffset % \internalpagesizes{#1}{\hsize}% {\voffset}{\normaloffset}% {\bindingoffset}{44pt}% {\dimen0}{\dimen2}% }} % Set default to letter. % \letterpaper \message{and turning on texinfo input format.} % Define macros to output various characters with catcode for normal text. \catcode`\"=\other \catcode`\~=\other \catcode`\^=\other \catcode`\_=\other \catcode`\|=\other \catcode`\<=\other \catcode`\>=\other \catcode`\+=\other \catcode`\$=\other \def\normaldoublequote{"} \def\normaltilde{~} \def\normalcaret{^} \def\normalunderscore{_} \def\normalverticalbar{|} \def\normalless{<} \def\normalgreater{>} \def\normalplus{+} \def\normaldollar{$}%$ font-lock fix % This macro is used to make a character print one way in \tt % (where it can probably be output as-is), and another way in other fonts, % where something hairier probably needs to be done. % % #1 is what to print if we are indeed using \tt; #2 is what to print % otherwise. Since all the Computer Modern typewriter fonts have zero % interword stretch (and shrink), and it is reasonable to expect all % typewriter fonts to have this, we can check that font parameter. % \def\ifusingtt#1#2{\ifdim \fontdimen3\font=0pt #1\else #2\fi} % Same as above, but check for italic font. Actually this also catches % non-italic slanted fonts since it is impossible to distinguish them from % italic fonts. But since this is only used by $ and it uses \sl anyway % this is not a problem. \def\ifusingit#1#2{\ifdim \fontdimen1\font>0pt #1\else #2\fi} % Turn off all special characters except @ % (and those which the user can use as if they were ordinary). % Most of these we simply print from the \tt font, but for some, we can % use math or other variants that look better in normal text. \catcode`\"=\active \def\activedoublequote{{\tt\char34}} \let"=\activedoublequote \catcode`\~=\active \def~{{\tt\char126}} \chardef\hat=`\^ \catcode`\^=\active \def^{{\tt \hat}} \catcode`\_=\active \def_{\ifusingtt\normalunderscore\_} \let\realunder=_ % Subroutine for the previous macro. \def\_{\leavevmode \kern.07em \vbox{\hrule width.3em height.1ex}\kern .07em } \catcode`\|=\active \def|{{\tt\char124}} \chardef \less=`\< \catcode`\<=\active \def<{{\tt \less}} \chardef \gtr=`\> \catcode`\>=\active \def>{{\tt \gtr}} \catcode`\+=\active \def+{{\tt \char 43}} \catcode`\$=\active \def${\ifusingit{{\sl\$}}\normaldollar}%$ font-lock fix % If a .fmt file is being used, characters that might appear in a file % name cannot be active until we have parsed the command line. % So turn them off again, and have \everyjob (or @setfilename) turn them on. % \otherifyactive is called near the end of this file. \def\otherifyactive{\catcode`+=\other \catcode`\_=\other} % Used sometimes to turn off (effectively) the active characters even after % parsing them. \def\turnoffactive{% \normalturnoffactive \otherbackslash } \catcode`\@=0 % \backslashcurfont outputs one backslash character in current font, % as in \char`\\. \global\chardef\backslashcurfont=`\\ \global\let\rawbackslashxx=\backslashcurfont % let existing .??s files work % \realbackslash is an actual character `\' with catcode other, and % \doublebackslash is two of them (for the pdf outlines). {\catcode`\\=\other @gdef@realbackslash{\} @gdef@doublebackslash{\\}} % In texinfo, backslash is an active character; it prints the backslash % in fixed width font. \catcode`\\=\active @def@normalbackslash{{@tt@backslashcurfont}} % On startup, @fixbackslash assigns: % @let \ = @normalbackslash % \rawbackslash defines an active \ to do \backslashcurfont. % \otherbackslash defines an active \ to be a literal `\' character with % catcode other. @gdef@rawbackslash{@let\=@backslashcurfont} @gdef@otherbackslash{@let\=@realbackslash} % Same as @turnoffactive except outputs \ as {\tt\char`\\} instead of % the literal character `\'. % @def@normalturnoffactive{% @let\=@normalbackslash @let"=@normaldoublequote @let~=@normaltilde @let^=@normalcaret @let_=@normalunderscore @let|=@normalverticalbar @let<=@normalless @let>=@normalgreater @let+=@normalplus @let$=@normaldollar %$ font-lock fix @unsepspaces } % Make _ and + \other characters, temporarily. % This is canceled by @fixbackslash. @otherifyactive % If a .fmt file is being used, we don't want the `\input texinfo' to show up. % That is what \eatinput is for; after that, the `\' should revert to printing % a backslash. % @gdef@eatinput input texinfo{@fixbackslash} @global@let\ = @eatinput % On the other hand, perhaps the file did not have a `\input texinfo'. Then % the first `\' in the file would cause an error. This macro tries to fix % that, assuming it is called before the first `\' could plausibly occur. % Also turn back on active characters that might appear in the input % file name, in case not using a pre-dumped format. % @gdef@fixbackslash{% @ifx\@eatinput @let\ = @normalbackslash @fi @catcode`+=@active @catcode`@_=@active } % Say @foo, not \foo, in error messages. @escapechar = `@@ % These look ok in all fonts, so just make them not special. @catcode`@& = @other @catcode`@# = @other @catcode`@% = @other @c Local variables: @c eval: (add-hook 'write-file-hooks 'time-stamp) @c page-delimiter: "^\\\\message" @c time-stamp-start: "def\\\\texinfoversion{" @c time-stamp-format: "%:y-%02m-%02d.%02H" @c time-stamp-end: "}" @c End: @c vim:sw=2: @ignore arch-tag: e1b36e32-c96e-4135-a41a-0b2efa2ea115 @end ignore diff --git a/common/ChangeLog-2011.include b/common/ChangeLog-2011.include index b5a9a5ec6..b9ca86135 100644 --- a/common/ChangeLog-2011.include +++ b/common/ChangeLog-2011.include @@ -1,458 +1,458 @@ # This is the ChangeLog-2011 from the former ../include directory. It # was moved to here after the removal of the directory on 2014-01-29. 2011-12-01 Werner Koch NB: ChangeLog files are no longer manually maintained. Starting on December 1st, 2011 we put change information only in the GIT commit log, and generate a top-level ChangeLog file from logs at "make dist". See doc/HACKING for details. 2011-02-01 Werner Koch * cipher.h (PUBKEY_MAX_NPKEY, PUBKEY_MAX_NSKEY): Bump up to accommodate gcrypt ECC keys. 2011-01-21 Werner Koch * cipher.h (GCRY_PK_USAGE_CERT): Remove compatibility macros because we now require libgcrypt 1.4.6. (GCRY_PK_ECDH): Add replacement. 2009-08-20 Daiki Ueno (wk) * cipher.h (struct DEK): Add field S2K_CACHEID. 2008-04-18 Werner Koch * cipher.h (CIPHER_ALGO_CAMELLIA256): Change ID to 13. (CIPHER_ALGO_CAMELLIA192): New. 2007-12-12 Werner Koch * cipher.h (CIPHER_ALGO_CAMELLIA128, CIPHER_ALGO_CAMELLIA256): New. 2006-09-20 Werner Koch * errors.h, http.h, memory.h, mpi.h, util.h, i18n.h: Removed. * Makefile.am: New. * distfiles: Removed. 2006-08-16 Werner Koch * keyserver.h: Moved to ../common. * http.h: Retired. 2006-04-28 Werner Koch * cipher.h (DIGEST_ALGO_SHA224): Define it. 2006-04-18 Werner Koch * keyserver.h, i18n.h, http.h, cipher.h: Updated to gpg 1.4.3. 2003-09-04 David Shaw * cipher.h: Drop TIGER/192 support. * types.h: Prefer using uint64_t when creating a 64-bit unsigned type. This avoids a warning on compilers that support but complain about unsigned long long. * util.h: Make sure that only ascii is passed to isfoo functions. (From Werner on stable branch). 2003-09-04 Werner Koch * cipher.h (PUBKEY_USAGE_AUTH): Added. 2003-07-03 Werner Koch * cipher.h (DBG_CIPHER,g10c_debug_mode): Removed. 2003-06-11 Werner Koch * cipher.h: Include gcrypt.h and mapped cipher algo names to gcrypt ones. Removed twofish_old and skipjack. Removed all handle definitions and other raerely used stuff. This file will eventually be entirely removed. 2003-06-10 Werner Koch * types.h (struct strlist): Removed. 2003-05-24 David Shaw * cipher.h, i18n.h, iobuf.h, memory.h, mpi.h, types.h, util.h: Edit all preprocessor instructions to remove whitespace before the '#'. This is not required by C89, but there are some compilers out there that don't like it. 2003-05-14 David Shaw * types.h: Add initializer macros for 64-bit unsigned type. 2003-05-02 David Shaw * cipher.h: Add constants for compression algorithms. 2003-03-11 David Shaw * http.h: Add HTTP_FLAG_TRY_SRV. 2003-02-11 David Shaw * types.h: Try and use uint64_t for a 64-bit type. 2003-02-04 David Shaw * cipher.h: Add constants for new SHAs. 2002-11-13 David Shaw * util.h [__CYGWIN32__]: Don't need the registry prototypes. From Werner on stable branch. 2002-11-06 David Shaw * util.h: Add wipememory2() macro (same as wipememory, but can specify the byte to wipe with). 2002-10-31 Stefan Bellon * util.h [__riscos__]: Prefixed all RISC OS prototypes with riscos_* * zlib-riscos.h: New. This is macro magic in order to make the zlib library calls indeed call the RISC OS ZLib module. 2002-10-31 David Shaw * util.h: Add wipememory() macro. 2002-10-29 Stefan Bellon * util.h: Added parameter argument to make_basename() needed for filetype support. [__riscos__]: Added prototype. 2002-10-28 Stefan Bellon * util.h [__riscos__]: Added prototypes for new filetype support. 2002-10-19 David Shaw * distfiles, _regex.h: Add _regex.h from glibc 2.3.1. 2002-10-14 David Shaw * keyserver.h: Go to KEYSERVER_PROTO_VERSION 1. 2002-10-08 David Shaw * keyserver.h: Add new error code KEYSERVER_UNREACHABLE. 2002-10-03 David Shaw * util.h: Add new log_warning logger command which can be switched between log_info and log_error via log_set_strict. 2002-09-24 David Shaw * keyserver.h: Add some new error codes for better GPA support. 2002-09-10 Werner Koch * mpi.h (mpi_is_protected, mpi_set_protect_flag) (mpi_clear_protect_flag): Removed. (mpi_get_nbit_info, mpi_set_nbit_info): Removed. 2002-08-13 David Shaw * cipher.h: Add AES aliases for RIJNDAEL algo numbers. 2002-08-07 David Shaw * cipher.h: Add md_algo_present(). 2002-08-06 Stefan Bellon * util.h [__riscos__]: Added riscos_getchar(). 2002-06-21 Stefan Bellon * util.h [__riscos__]: Further moving away of RISC OS specific stuff from general code. 2002-06-20 Stefan Bellon * util.h [__riscos__]: Added riscos_set_filetype(). 2002-06-14 David Shaw * util.h: Add pop_strlist() from strgutil.c. 2002-06-07 Stefan Bellon * util.h [__riscos__]: RISC OS needs strings.h for strcasecmp() and strncasecmp(). 2002-05-22 Werner Koch * util.h: Add strncasecmp. Removed stricmp and memicmp. 2002-05-10 Stefan Bellon * mpi.h: New function mpi_debug_alloc_like for M_DEBUG. * util.h [__riscos__]: Make use of __func__ that later Norcroft compiler provides. * memory.h: Fixed wrong definition of m_alloc_secure_clear. 2002-04-23 David Shaw * util.h: New function answer_is_yes_no_default() to give a default answer. 2002-04-22 Stefan Bellon * util.h [__riscos__]: Removed riscos_open, riscos_fopen and riscos_fstat as those special versions aren't needed anymore. 2002-02-19 David Shaw * keyserver.h: Add KEYSERVER_NOT_SUPPORTED for unsupported actions (say, a keyserver that has no way to search, or a readonly keyserver that has no way to add). 2002-01-02 Stefan Bellon * util.h [__riscos__]: Updated prototype list. * types.h [__riscos__]: Changed comment wording. 2001-12-27 David Shaw * KEYSERVER_SCHEME_NOT_FOUND should be 127 to match the POSIX system() (via /bin/sh) way of signaling this. * Added G10ERR_KEYSERVER 2001-12-27 Werner Koch * util.h [MINGW32]: Fixed name of include file. 2001-12-22 Timo Schulz * util.h (is_file_compressed): New. 2001-12-19 Werner Koch * util.h [CYGWIN32]: Allow this as an alias for MINGW32. Include - stdarg.h becuase we use the va_list type. By Disastry. + stdarg.h because we use the va_list type. By Disastry. 2001-09-28 Werner Koch * cipher.h (PUBKEY_USAGE_CERT): New. 2001-09-07 Werner Koch * util.h: Add strsep(). 2001-08-30 Werner Koch * cipher.h (DEK): Added use_mdc. 2001-08-24 Werner Koch * cipher.h (md_write): Made buf arg const. 2001-08-20 Werner Koch * cipher.h (DEK): Added algo_info_printed; * util.h [__riscos__]: Added prototypes and made sure that we never use __attribute__. * cipher.h, iobuf.h, memory.h, mpi.h [__riscos__]: extern hack. * i18n.h [__riscos__]: Use another include file 2001-05-30 Werner Koch * ttyio.h (tty_printf): Add missing parenthesis for non gcc. * http.h: Removed trailing comma to make old ccs happy. Both are by Albert Chin. 2001-05-25 Werner Koch * ttyio.h (tty_printf): Add printf attribute. 2001-04-23 Werner Koch * http.h: New flag HTTP_FLAG_NO_SHUTDOWN. 2001-04-13 Werner Koch * iobuf.h: Removed iobuf_fopen. 2001-03-01 Werner Koch * errors.h (G10ERR_UNU_SECKEY,G10ERR_UNU_PUBKEY): New 2000-11-30 Werner Koch * iobuf.h (iobuf_translate_file_handle): Add prototype. 2000-11-11 Paul Eggert * iobuf.h (iobuf_get_filelength): Now returns off_t, not u32. (struct iobuf_struct, iobuf_set_limit, iobuf_tell, iobuf_seek): Use off_t, not ulong, for file offsets. 2000-10-12 Werner Koch * mpi.h: Changed the way mpi_limb_t is defined. Wed Sep 6 17:55:47 CEST 2000 Werner Koch * iobuf.c (IOBUF_FILELENGTH_LIMIT): New. 2000-03-14 14:03:43 Werner Koch (wk@habibti.openit.de) * types.h (HAVE_U64_TYPEDEF): Defined depending on configure test. Thu Jan 13 19:31:58 CET 2000 Werner Koch * types.h (HAVE_U64_TYPEDEF): Add a test for _LONGLONG which fixes this long living SGI bug. Reported by Alec Habig. Sat Dec 4 12:30:28 CET 1999 Werner Koch * iobuf.h (IOBUFCTRL_CANCEL): Nww. Mon Oct 4 21:23:04 CEST 1999 Werner Koch * errors.h (G10ERR_NOT_PROCESSED): New. Wed Sep 15 16:22:17 CEST 1999 Werner Koch * i18n.h: Add support for simple-gettext. Tue Jun 29 21:44:25 CEST 1999 Werner Koch * util.h (stricmp): Use strcasecmp as replacement. Sat Jun 26 12:15:59 CEST 1999 Werner Koch * cipher.h (MD_HANDLE): Assigned a structure name. Fri Apr 9 12:26:25 CEST 1999 Werner Koch * cipher.h (BLOWFISH160): Removed. Tue Apr 6 19:58:12 CEST 1999 Werner Koch * cipher.h (DEK): increased max. key length to 32 bytes Sat Feb 20 21:40:49 CET 1999 Werner Koch * g10lib.h: Removed file and changed all files that includes this. Tue Feb 16 14:10:02 CET 1999 Werner Koch * types.h (STRLIST): Add field flags. Wed Feb 10 17:15:39 CET 1999 Werner Koch * cipher.h (CIPHER_ALGO_TWOFISH): Chnaged ID to 10 and renamed the old experimenatl algorithm to xx_OLD. Thu Jan 7 18:00:58 CET 1999 Werner Koch * cipher.h (MD_BUFFER_SIZE): Removed. Mon Dec 14 21:18:49 CET 1998 Werner Koch * types.h: fix for SUNPRO_C Tue Dec 8 13:15:16 CET 1998 Werner Koch * mpi.h (MPI): Changed the structure name to gcry_mpi and changed all users. Tue Oct 20 11:40:00 1998 Werner Koch (wk@isil.d.shuttle.de) * iobuf.h (iobuf_get_temp_buffer): New. Tue Oct 13 12:40:48 1998 Werner Koch (wk@isil.d.shuttle.de) * iobuf.h (iobuf_get): Now uses .nofast (iobuf_get2): Removed. Mon Sep 14 09:17:22 1998 Werner Koch (wk@(none)) * util.h (HAVE_ATEXIT): New. (HAVE_RAISE): New. Mon Jul 6 10:41:55 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h (PUBKEY_USAGE_): New. Mon Jul 6 09:49:51 1998 Werner Koch (wk@isil.d.shuttle.de) * iobuf.h (iobuf_set_error): New. (iobuf_error): New. Sat Jun 13 17:31:32 1998 Werner Koch (wk@isil.d.shuttle.de) * g10lib.h: New as interface for the g10lib. Mon Jun 8 22:14:48 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h (CIPHER_ALGO_CAST5): Changed name from .. CAST Thu May 21 13:25:51 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h: removed ROT 5 and changed one id and add dummy Tue May 19 18:09:05 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h (DIGEST_ALGO_TIGER): Chnaged id from 101 to 6. Mon May 4 16:37:17 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h (PUBKEY_ALGO_ELGAMAL_E): New, with value of the old one. * (is_ELGAMAL, is_RSA): New macros Sun Apr 26 14:35:24 1998 Werner Koch (wk@isil.d.shuttle.de) * types.h: New type u64 Mon Mar 9 12:59:55 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h: Included dsa.h. Tue Mar 3 15:11:21 1998 Werner Koch (wk@isil.d.shuttle.de) * cipher.h (random.h): Add new header and move all relevalt functions to this header. Copyright 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Local Variables: buffer-read-only: t End: diff --git a/common/audit.c b/common/audit.c index 7d545a349..179bf72fe 100644 --- a/common/audit.c +++ b/common/audit.c @@ -1,1323 +1,1323 @@ /* audit.c - GnuPG's audit subsystem * Copyright (C) 2007, 2009 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 "util.h" #include "i18n.h" #include "audit.h" #include "audit-events.h" /* A list to maintain a list of helptags. */ struct helptag_s { struct helptag_s *next; const char *name; }; typedef struct helptag_s *helptag_t; /* One log entry. */ struct log_item_s { audit_event_t event; /* The event. */ gpg_error_t err; /* The logged error code. */ int intvalue; /* A logged integer value. */ char *string; /* A malloced string or NULL. */ ksba_cert_t cert; /* A certifciate or NULL. */ int have_err:1; int have_intvalue:1; }; typedef struct log_item_s *log_item_t; /* The main audit object. */ struct audit_ctx_s { const char *failure; /* If set a description of the internal failure. */ audit_type_t type; log_item_t log; /* The table with the log entries. */ size_t logsize; /* The allocated size for LOG. */ size_t logused; /* The used size of LOG. */ estream_t outstream; /* The current output stream. */ int use_html; /* The output shall be HTML formatted. */ int indentlevel; /* Current level of indentation. */ helptag_t helptags; /* List of help keys. */ }; static void writeout_para (audit_ctx_t ctx, const char *format, ...) GPGRT_ATTR_PRINTF(2,3); static void writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...) GPGRT_ATTR_PRINTF(3,4); static void writeout_rem (audit_ctx_t ctx, const char *format, ...) GPGRT_ATTR_PRINTF(2,3); /* Add NAME to the list of help tags. NAME needs to be a const string an this function merly stores this pointer. */ static void add_helptag (audit_ctx_t ctx, const char *name) { helptag_t item; for (item=ctx->helptags; item; item = item->next) if (!strcmp (item->name, name)) return; /* Already in the list. */ item = xtrycalloc (1, sizeof *item); if (!item) return; /* Don't care about memory problems. */ item->name = name; item->next = ctx->helptags; ctx->helptags = item; } /* Remove all help tags from the context. */ static void clear_helptags (audit_ctx_t ctx) { while (ctx->helptags) { helptag_t tmp = ctx->helptags->next; xfree (ctx->helptags); ctx->helptags = tmp; } } static const char * event2str (audit_event_t event) { /* We need the cast so that compiler does not complain about an always true comparison (>= 0) for an unsigned value. */ int idx = eventstr_msgidxof ((int)event); if (idx == -1) return "Unknown event"; else return eventstr_msgstr + eventstr_msgidx[idx]; } /* Create a new audit context. In case of an error NULL is returned and errno set appropriately. */ audit_ctx_t audit_new (void) { audit_ctx_t ctx; ctx = xtrycalloc (1, sizeof *ctx); return ctx; } /* Release an audit context. Passing NULL for CTX is allowed and does nothing. */ void audit_release (audit_ctx_t ctx) { int idx; if (!ctx) return; if (ctx->log) { for (idx=0; idx < ctx->logused; idx++) { if (ctx->log[idx].string) xfree (ctx->log[idx].string); if (ctx->log[idx].cert) ksba_cert_release (ctx->log[idx].cert); } xfree (ctx->log); } clear_helptags (ctx); xfree (ctx); } /* Set the type for the audit operation. If CTX is NULL, this is a dummy function. */ void audit_set_type (audit_ctx_t ctx, audit_type_t type) { if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (ctx->type && ctx->type != type) { ctx->failure = "conflict in type initialization"; return; } ctx->type = type; } /* Create a new log item and put it into the table. Return that log item on success; return NULL on memory failure and mark that in CTX. */ static log_item_t create_log_item (audit_ctx_t ctx) { log_item_t item, table; size_t size; if (!ctx->log) { size = 10; table = xtrymalloc (size * sizeof *table); if (!table) { ctx->failure = "Out of memory in create_log_item"; return NULL; } ctx->log = table; ctx->logsize = size; item = ctx->log + 0; ctx->logused = 1; } else if (ctx->logused >= ctx->logsize) { size = ctx->logsize + 10; table = xtryrealloc (ctx->log, size * sizeof *table); if (!table) { ctx->failure = "Out of memory while reallocating in create_log_item"; return NULL; } ctx->log = table; ctx->logsize = size; item = ctx->log + ctx->logused++; } else item = ctx->log + ctx->logused++; item->event = AUDIT_NULL_EVENT; item->err = 0; item->have_err = 0; item->intvalue = 0; item->have_intvalue = 0; item->string = NULL; item->cert = NULL; return item; } /* Add a new event to the audit log. If CTX is NULL, this function does nothing. */ void audit_log (audit_ctx_t ctx, audit_event_t event) { log_item_t item; if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (!event) { ctx->failure = "Invalid event passed to audit_log"; return; } if (!(item = create_log_item (ctx))) return; item->event = event; } /* Add a new event to the audit log. If CTX is NULL, this function does nothing. This version also adds the result of the operation to the log. */ void audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err) { log_item_t item; if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (!event) { ctx->failure = "Invalid event passed to audit_log_ok"; return; } if (!(item = create_log_item (ctx))) return; item->event = event; item->err = err; item->have_err = 1; } /* Add a new event to the audit log. If CTX is NULL, this function does nothing. This version also add the integer VALUE to the log. */ void audit_log_i (audit_ctx_t ctx, audit_event_t event, int value) { log_item_t item; if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (!event) { ctx->failure = "Invalid event passed to audit_log_i"; return; } if (!(item = create_log_item (ctx))) return; item->event = event; item->intvalue = value; item->have_intvalue = 1; } /* Add a new event to the audit log. If CTX is NULL, this function does nothing. This version also add the integer VALUE to the log. */ void audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value) { log_item_t item; char *tmp; if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (!event) { ctx->failure = "Invalid event passed to audit_log_s"; return; } tmp = xtrystrdup (value? value : ""); if (!tmp) { ctx->failure = "Out of memory in audit_event"; return; } if (!(item = create_log_item (ctx))) { xfree (tmp); return; } item->event = event; item->string = tmp; } /* Add a new event to the audit log. If CTX is NULL, this function does nothing. This version also adds the certificate CERT and the result of an operation to the log. */ void audit_log_cert (audit_ctx_t ctx, audit_event_t event, ksba_cert_t cert, gpg_error_t err) { log_item_t item; if (!ctx || ctx->failure) return; /* Audit not enabled or an internal error has occurred. */ if (!event) { ctx->failure = "Invalid event passed to audit_log_cert"; return; } if (!(item = create_log_item (ctx))) return; item->event = event; item->err = err; item->have_err = 1; if (cert) { ksba_cert_ref (cert); item->cert = cert; } } /* Write TEXT to the outstream. */ static void writeout (audit_ctx_t ctx, const char *text) { if (ctx->use_html) { for (; *text; text++) { if (*text == '<') es_fputs ("<", ctx->outstream); else if (*text == '&') es_fputs ("&", ctx->outstream); else es_putc (*text, ctx->outstream); } } else es_fputs (text, ctx->outstream); } /* Write TEXT to the outstream using a variable argument list. */ static void writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr) { char *buf; gpgrt_vasprintf (&buf, format, arg_ptr); if (buf) { writeout (ctx, buf); xfree (buf); } else writeout (ctx, "[!!Out of core!!]"); } /* Write TEXT as a paragraph. */ static void writeout_para (audit_ctx_t ctx, const char *format, ...) { va_list arg_ptr; if (ctx->use_html) es_fputs ("

", ctx->outstream); va_start (arg_ptr, format) ; writeout_v (ctx, format, arg_ptr); va_end (arg_ptr); if (ctx->use_html) es_fputs ("

\n", ctx->outstream); else es_fputc ('\n', ctx->outstream); } static void enter_li (audit_ctx_t ctx) { if (ctx->use_html) { if (!ctx->indentlevel) { es_fputs ("\n" " \n" " \n" " \n" " \n", ctx->outstream); } } ctx->indentlevel++; } static void leave_li (audit_ctx_t ctx) { ctx->indentlevel--; if (ctx->use_html) { if (!ctx->indentlevel) es_fputs ("
\n", ctx->outstream); } } /* Write TEXT as a list element. If OKTEXT is not NULL, append it to the last line. */ static void writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...) { va_list arg_ptr; const char *color = NULL; if (ctx->use_html && format && oktext) { if (!strcmp (oktext, "Yes") || !strcmp (oktext, "good") ) color = "green"; else if (!strcmp (oktext, "No") || !strcmp (oktext, "bad") ) color = "red"; } if (format && oktext) { const char *s = NULL; if (!strcmp (oktext, "Yes")) oktext = _("Yes"); else if (!strcmp (oktext, "No")) oktext = _("No"); else if (!strcmp (oktext, "good")) { /* TRANSLATORS: Copy the prefix between the vertical bars verbatim. It will not be printed. */ oktext = _("|audit-log-result|Good"); } else if (!strcmp (oktext, "bad")) oktext = _("|audit-log-result|Bad"); else if (!strcmp (oktext, "unsupported")) oktext = _("|audit-log-result|Not supported"); else if (!strcmp (oktext, "no-cert")) oktext = _("|audit-log-result|No certificate"); else if (!strcmp (oktext, "disabled")) oktext = _("|audit-log-result|Not enabled"); else if (!strcmp (oktext, "error")) oktext = _("|audit-log-result|Error"); else if (!strcmp (oktext, "not-used")) oktext = _("|audit-log-result|Not used"); else if (!strcmp (oktext, "okay")) oktext = _("|audit-log-result|Okay"); else if (!strcmp (oktext, "skipped")) oktext = _("|audit-log-result|Skipped"); else if (!strcmp (oktext, "some")) oktext = _("|audit-log-result|Some"); else s = ""; /* If we have set a prefix, skip it. */ if (!s && *oktext == '|' && (s=strchr (oktext+1,'|'))) oktext = s+1; } if (ctx->use_html) { int i; es_fputs ("
", ctx->outstream); if (color) es_fprintf (ctx->outstream, "*", color); else es_fputs ("*", ctx->outstream); for (i=1; i < ctx->indentlevel; i++) es_fputs ("  ", ctx->outstream); es_fputs ("", ctx->outstream); } else es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, ""); if (format) { va_start (arg_ptr, format) ; writeout_v (ctx, format, arg_ptr); va_end (arg_ptr); } if (ctx->use_html) es_fputs ("
", ctx->outstream); if (format && oktext) { if (ctx->use_html) { es_fputs ("", ctx->outstream); if (color) es_fprintf (ctx->outstream, "", color); } else writeout (ctx, ": "); writeout (ctx, oktext); if (color) es_fputs ("", ctx->outstream); } if (ctx->use_html) es_fputs ("\n", ctx->outstream); else es_fputc ('\n', ctx->outstream); } /* Write a remark line. */ static void writeout_rem (audit_ctx_t ctx, const char *format, ...) { va_list arg_ptr; if (ctx->use_html) { int i; es_fputs ("
*", ctx->outstream); for (i=1; i < ctx->indentlevel; i++) es_fputs ("  ", ctx->outstream); es_fputs ("    (", ctx->outstream); } else es_fprintf (ctx->outstream, "* %*s (", (ctx->indentlevel-1)*2, ""); if (format) { va_start (arg_ptr, format) ; writeout_v (ctx, format, arg_ptr); va_end (arg_ptr); } if (ctx->use_html) es_fputs (")
\n", ctx->outstream); else es_fputs (")\n", ctx->outstream); } /* Return the first log item for EVENT. If STOPEVENT is not 0 never look behind that event in the log. If STARTITEM is not NULL start search _after_that item. */ static log_item_t find_next_log_item (audit_ctx_t ctx, log_item_t startitem, audit_event_t event, audit_event_t stopevent) { int idx; for (idx=0; idx < ctx->logused; idx++) { if (startitem) { if (ctx->log + idx == startitem) startitem = NULL; } else if (stopevent && ctx->log[idx].event == stopevent) break; else if (ctx->log[idx].event == event) return ctx->log + idx; } return NULL; } static log_item_t find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent) { return find_next_log_item (ctx, NULL, event, stopevent); } /* Helper to a format a serial number. */ static char * format_serial (ksba_const_sexp_t sn) { const char *p = (const char *)sn; unsigned long n; char *endp; if (!p) return NULL; if (*p != '(') BUG (); /* Not a valid S-expression. */ n = strtoul (p+1, &endp, 10); p = endp; if (*p != ':') BUG (); /* Not a valid S-expression. */ return bin2hex (p+1, n, NULL); } /* Return a malloced string with the serial number and the issuer DN of the certificate. */ static char * get_cert_name (ksba_cert_t cert) { char *result; ksba_sexp_t sn; char *issuer, *p; if (!cert) return xtrystrdup ("[no certificate]"); issuer = ksba_cert_get_issuer (cert, 0); sn = ksba_cert_get_serial (cert); if (issuer && sn) { p = format_serial (sn); if (!p) result = xtrystrdup ("[invalid S/N]"); else { result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1); if (result) { *result = '#'; strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer); } xfree (p); } } else result = xtrystrdup ("[missing S/N or issuer]"); ksba_free (sn); xfree (issuer); return result; } /* Return a malloced string with the serial number and the issuer DN of the certificate. */ static char * get_cert_subject (ksba_cert_t cert, int idx) { char *result; char *subject; if (!cert) return xtrystrdup ("[no certificate]"); subject = ksba_cert_get_subject (cert, idx); if (subject) { result = xtrymalloc (strlen (subject) + 1 + 1); if (result) { *result = '/'; strcpy (result+1, subject); } } else result = NULL; xfree (subject); return result; } /* List the given certificiate. If CERT is NULL, this is a NOP. */ static void list_cert (audit_ctx_t ctx, ksba_cert_t cert, int with_subj) { char *name; int idx; name = get_cert_name (cert); writeout_rem (ctx, "%s", name); xfree (name); if (with_subj) { enter_li (ctx); for (idx=0; (name = get_cert_subject (cert, idx)); idx++) { writeout_rem (ctx, "%s", name); xfree (name); } leave_li (ctx); } } /* List the chain of certificates from STARTITEM up to STOPEVENT. The - certifcates are written out as comments. */ + certificates are written out as comments. */ static void list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent) { log_item_t item; startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent); writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available")); if (!startitem) return; item = find_next_log_item (ctx, startitem, AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END); if (!item) writeout_rem (ctx, "%s", _("root certificate missing")); else { list_cert (ctx, item->cert, 0); } item = startitem; while ( ((item = find_next_log_item (ctx, item, AUDIT_CHAIN_CERT, AUDIT_CHAIN_END)))) { list_cert (ctx, item->cert, 1); } } /* Process an encrypt operation's log. */ static void proc_type_encrypt (audit_ctx_t ctx) { log_item_t loopitem, item; int recp_no, idx; char numbuf[35]; int algo; char *name; item = find_log_item (ctx, AUDIT_ENCRYPTION_DONE, 0); writeout_li (ctx, item?"Yes":"No", "%s", _("Data encryption succeeded")); enter_li (ctx); item = find_log_item (ctx, AUDIT_GOT_DATA, 0); writeout_li (ctx, item? "Yes":"No", "%s", _("Data available")); item = find_log_item (ctx, AUDIT_SESSION_KEY, 0); writeout_li (ctx, item? "Yes":"No", "%s", _("Session key created")); if (item) { algo = gcry_cipher_map_name (item->string); if (algo) writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo)); else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2")) writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2"); else if (item->string) writeout_rem (ctx, _("unsupported algorithm: %s"), item->string); else writeout_rem (ctx, _("seems to be not encrypted")); } item = find_log_item (ctx, AUDIT_GOT_RECIPIENTS, 0); snprintf (numbuf, sizeof numbuf, "%d", item && item->have_intvalue? item->intvalue : 0); writeout_li (ctx, numbuf, "%s", _("Number of recipients")); /* Loop over all recipients. */ loopitem = NULL; recp_no = 0; while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_ENCRYPTED_TO, 0))) { recp_no++; writeout_li (ctx, NULL, _("Recipient %d"), recp_no); if (loopitem->cert) { name = get_cert_name (loopitem->cert); writeout_rem (ctx, "%s", name); xfree (name); enter_li (ctx); for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++) { writeout_rem (ctx, "%s", name); xfree (name); } leave_li (ctx); } } leave_li (ctx); } /* Process a sign operation's log. */ static void proc_type_sign (audit_ctx_t ctx) { log_item_t item, loopitem; int signer, idx; const char *result; ksba_cert_t cert; char *name; int lastalgo; item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0); writeout_li (ctx, item?"Yes":"No", "%s", _("Data signing succeeded")); enter_li (ctx); item = find_log_item (ctx, AUDIT_GOT_DATA, 0); writeout_li (ctx, item? "Yes":"No", "%s", _("Data available")); /* Write remarks with the data hash algorithms. We use a very simple scheme to avoid some duplicates. */ loopitem = NULL; lastalgo = 0; while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG))) { if (loopitem->intvalue && loopitem->intvalue != lastalgo) writeout_rem (ctx, _("data hash algorithm: %s"), gcry_md_algo_name (loopitem->intvalue)); lastalgo = loopitem->intvalue; } /* Loop over all signer. */ loopitem = NULL; signer = 0; while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0))) { signer++; item = find_next_log_item (ctx, loopitem, AUDIT_SIGNED_BY, AUDIT_NEW_SIG); if (!item) result = "error"; else if (!item->err) result = "okay"; else if (gpg_err_code (item->err) == GPG_ERR_CANCELED) result = "skipped"; else result = gpg_strerror (item->err); cert = item? item->cert : NULL; writeout_li (ctx, result, _("Signer %d"), signer); item = find_next_log_item (ctx, loopitem, AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG); if (item) writeout_rem (ctx, _("attr hash algorithm: %s"), gcry_md_algo_name (item->intvalue)); if (cert) { name = get_cert_name (cert); writeout_rem (ctx, "%s", name); xfree (name); enter_li (ctx); for (idx=0; (name = get_cert_subject (cert, idx)); idx++) { writeout_rem (ctx, "%s", name); xfree (name); } leave_li (ctx); } } leave_li (ctx); } /* Process a decrypt operation's log. */ static void proc_type_decrypt (audit_ctx_t ctx) { log_item_t loopitem, item; int algo, recpno; char *name; char numbuf[35]; int idx; item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0); writeout_li (ctx, item && !item->err?"Yes":"No", "%s", _("Data decryption succeeded")); enter_li (ctx); item = find_log_item (ctx, AUDIT_GOT_DATA, 0); writeout_li (ctx, item? "Yes":"No", "%s", _("Data available")); item = find_log_item (ctx, AUDIT_DATA_CIPHER_ALGO, 0); algo = item? item->intvalue : 0; writeout_li (ctx, algo?"Yes":"No", "%s", _("Encryption algorithm supported")); if (algo) writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo)); item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0); if (item && item->string) { algo = gcry_cipher_map_name (item->string); if (algo) writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo)); else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2")) writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2"); else if (item->string) writeout_rem (ctx, _("unsupported algorithm: %s"), item->string); else writeout_rem (ctx, _("seems to be not encrypted")); } for (recpno = 0, item = NULL; (item = find_next_log_item (ctx, item, AUDIT_NEW_RECP, 0)); recpno++) ; snprintf (numbuf, sizeof numbuf, "%d", recpno); writeout_li (ctx, numbuf, "%s", _("Number of recipients")); /* Loop over all recipients. */ loopitem = NULL; while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_RECP, 0))) { const char *result; recpno = loopitem->have_intvalue? loopitem->intvalue : -1; item = find_next_log_item (ctx, loopitem, AUDIT_RECP_RESULT, AUDIT_NEW_RECP); if (!item) result = "not-used"; else if (!item->err) result = "okay"; else if (gpg_err_code (item->err) == GPG_ERR_CANCELED) result = "skipped"; else result = gpg_strerror (item->err); item = find_next_log_item (ctx, loopitem, AUDIT_RECP_NAME, AUDIT_NEW_RECP); writeout_li (ctx, result, _("Recipient %d"), recpno); if (item && item->string) writeout_rem (ctx, "%s", item->string); /* If we have a certificate write out more infos. */ item = find_next_log_item (ctx, loopitem, AUDIT_SAVE_CERT, AUDIT_NEW_RECP); if (item && item->cert) { enter_li (ctx); for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++) { writeout_rem (ctx, "%s", name); xfree (name); } leave_li (ctx); } } leave_li (ctx); } /* Process a verification operation's log. */ static void proc_type_verify (audit_ctx_t ctx) { log_item_t loopitem, item; int signo, count, idx, n_good, n_bad; char numbuf[35]; const char *result; /* If there is at least one signature status we claim that the verification succeeded. This does not mean that the data has verified okay. */ item = find_log_item (ctx, AUDIT_SIG_STATUS, 0); writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded")); enter_li (ctx); item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG); writeout_li (ctx, item? "Yes":"No", "%s", _("Data available")); if (!item) goto leave; item = find_log_item (ctx, AUDIT_NEW_SIG, 0); writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available")); if (!item) goto leave; /* Print info about the used data hashing algorithms. */ for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++) { item = ctx->log + idx; if (item->event == AUDIT_NEW_SIG) break; else if (item->event == AUDIT_DATA_HASH_ALGO) n_good++; else if (item->event == AUDIT_BAD_DATA_HASH_ALGO) n_bad++; } item = find_log_item (ctx, AUDIT_DATA_HASHING, AUDIT_NEW_SIG); if (!item || item->err || !n_good) result = "No"; else if (n_good && !n_bad) result = "Yes"; else result = "Some"; writeout_li (ctx, result, "%s", _("Parsing data succeeded")); if (n_good || n_bad) { for (idx=0; idx < ctx->logused; idx++) { item = ctx->log + idx; if (item->event == AUDIT_NEW_SIG) break; else if (item->event == AUDIT_DATA_HASH_ALGO) writeout_rem (ctx, _("data hash algorithm: %s"), gcry_md_algo_name (item->intvalue)); else if (item->event == AUDIT_BAD_DATA_HASH_ALGO) writeout_rem (ctx, _("bad data hash algorithm: %s"), item->string? item->string:"?"); } } /* Loop over all signatures. */ loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0); assert (loopitem); do { signo = loopitem->have_intvalue? loopitem->intvalue : -1; item = find_next_log_item (ctx, loopitem, AUDIT_SIG_STATUS, AUDIT_NEW_SIG); writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo); item = find_next_log_item (ctx, loopitem, AUDIT_SIG_NAME, AUDIT_NEW_SIG); if (item) writeout_rem (ctx, "%s", item->string); item = find_next_log_item (ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG); if (item) writeout_rem (ctx, _("data hash algorithm: %s"), gcry_md_algo_name (item->intvalue)); item = find_next_log_item (ctx, loopitem, AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG); if (item) writeout_rem (ctx, _("attr hash algorithm: %s"), gcry_md_algo_name (item->intvalue)); enter_li (ctx); /* List the certificate chain. */ list_certchain (ctx, loopitem, AUDIT_NEW_SIG); /* Show the result of the chain validation. */ item = find_next_log_item (ctx, loopitem, AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG); if (item && item->have_err) { writeout_li (ctx, item->err? "No":"Yes", _("Certificate chain valid")); if (item->err) writeout_rem (ctx, "%s", gpg_strerror (item->err)); } /* Show whether the root certificate is fine. */ item = find_next_log_item (ctx, loopitem, AUDIT_ROOT_TRUSTED, AUDIT_CHAIN_STATUS); if (item) { writeout_li (ctx, item->err?"No":"Yes", "%s", _("Root certificate trustworthy")); if (item->err) { add_helptag (ctx, "gpgsm.root-cert-not-trusted"); writeout_rem (ctx, "%s", gpg_strerror (item->err)); list_cert (ctx, item->cert, 0); } } /* Show result of the CRL/OCSP check. */ item = find_next_log_item (ctx, loopitem, AUDIT_CRL_CHECK, AUDIT_NEW_SIG); if (item) { const char *ok; switch (gpg_err_code (item->err)) { case 0: ok = "good"; break; case GPG_ERR_CERT_REVOKED: ok = "bad"; break; case GPG_ERR_NOT_ENABLED: ok = "disabled"; break; case GPG_ERR_NO_CRL_KNOWN: ok = _("no CRL found for certificate"); break; case GPG_ERR_CRL_TOO_OLD: ok = _("the available CRL is too old"); break; default: ok = gpg_strerror (item->err); break; } writeout_li (ctx, ok, "%s", _("CRL/OCSP check of certificates")); if (item->err && gpg_err_code (item->err) != GPG_ERR_CERT_REVOKED && gpg_err_code (item->err) != GPG_ERR_NOT_ENABLED) add_helptag (ctx, "gpgsm.crl-problem"); } leave_li (ctx); } while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0))); leave: /* Always list the certificates stored in the signature. */ item = NULL; count = 0; while ( ((item = find_next_log_item (ctx, item, AUDIT_SAVE_CERT, AUDIT_NEW_SIG)))) count++; snprintf (numbuf, sizeof numbuf, "%d", count); writeout_li (ctx, numbuf, _("Included certificates")); item = NULL; while ( ((item = find_next_log_item (ctx, item, AUDIT_SAVE_CERT, AUDIT_NEW_SIG)))) { char *name = get_cert_name (item->cert); writeout_rem (ctx, "%s", name); xfree (name); enter_li (ctx); for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++) { writeout_rem (ctx, "%s", name); xfree (name); } leave_li (ctx); } leave_li (ctx); } /* Print the formatted audit result. THIS IS WORK IN PROGRESS. */ void audit_print_result (audit_ctx_t ctx, estream_t out, int use_html) { int idx; size_t n; log_item_t item; helptag_t helptag; const char *s; int show_raw = 0; char *orig_codeset; if (!ctx) return; orig_codeset = i18n_switchto_utf8 (); /* We use an environment variable to include some debug info in the log. */ if ((s = getenv ("gnupg_debug_audit"))) show_raw = 1; assert (!ctx->outstream); ctx->outstream = out; ctx->use_html = use_html; ctx->indentlevel = 0; clear_helptags (ctx); if (use_html) es_fputs ("
\n", ctx->outstream); if (!ctx->log || !ctx->logused) { writeout_para (ctx, _("No audit log entries.")); goto leave; } if (show_raw) { int maxlen; for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++) { n = strlen (eventstr_msgstr + eventstr_msgidx[idx]); if (n > maxlen) maxlen = n; } if (use_html) es_fputs ("
\n", out);
       for (idx=0; idx < ctx->logused; idx++)
         {
           es_fprintf (out, "log: %-*s",
                       maxlen, event2str (ctx->log[idx].event));
           if (ctx->log[idx].have_intvalue)
             es_fprintf (out, " i=%d", ctx->log[idx].intvalue);
           if (ctx->log[idx].string)
             {
               es_fputs (" s='", out);
               writeout (ctx, ctx->log[idx].string);
               es_fputs ("'", out);
             }
           if (ctx->log[idx].cert)
             es_fprintf (out, " has_cert");
           if (ctx->log[idx].have_err)
             {
               es_fputs (" err='", out);
               writeout (ctx, gpg_strerror (ctx->log[idx].err));
               es_fputs ("'", out);
             }
           es_fputs ("\n", out);
         }
       if (use_html)
         es_fputs ("
\n", out); else es_fputs ("\n", out); } enter_li (ctx); switch (ctx->type) { case AUDIT_TYPE_NONE: writeout_li (ctx, NULL, _("Unknown operation")); break; case AUDIT_TYPE_ENCRYPT: proc_type_encrypt (ctx); break; case AUDIT_TYPE_SIGN: proc_type_sign (ctx); break; case AUDIT_TYPE_DECRYPT: proc_type_decrypt (ctx); break; case AUDIT_TYPE_VERIFY: proc_type_verify (ctx); break; } item = find_log_item (ctx, AUDIT_AGENT_READY, 0); if (item && item->have_err) { writeout_li (ctx, item->err? "No":"Yes", "%s", _("Gpg-Agent usable")); if (item->err) { writeout_rem (ctx, "%s", gpg_strerror (item->err)); add_helptag (ctx, "gnupg.agent-problem"); } } item = find_log_item (ctx, AUDIT_DIRMNGR_READY, 0); if (item && item->have_err) { writeout_li (ctx, item->err? "No":"Yes", "%s", _("Dirmngr usable")); if (item->err) { writeout_rem (ctx, "%s", gpg_strerror (item->err)); add_helptag (ctx, "gnupg.dirmngr-problem"); } } leave_li (ctx); /* Show the help from the collected help tags. */ if (ctx->helptags) { if (use_html) { es_fputs ("
\n", ctx->outstream); if (ctx->helptags->next) es_fputs ("
    \n", ctx->outstream); } else es_fputs ("\n\n", ctx->outstream); } for (helptag = ctx->helptags; helptag; helptag = helptag->next) { char *text; if (use_html && ctx->helptags->next) es_fputs ("
  • \n", ctx->outstream); text = gnupg_get_help_string (helptag->name, 0); if (text) { writeout_para (ctx, "%s", text); xfree (text); } else writeout_para (ctx, _("No help available for '%s'."), helptag->name); if (use_html && ctx->helptags->next) es_fputs ("
  • \n", ctx->outstream); if (helptag->next) es_fputs ("\n", ctx->outstream); } if (use_html && ctx->helptags && ctx->helptags->next) es_fputs ("
\n", ctx->outstream); leave: if (use_html) es_fputs ("
\n", ctx->outstream); ctx->outstream = NULL; ctx->use_html = 0; clear_helptags (ctx); i18n_switchback (orig_codeset); } diff --git a/common/call-gpg.c b/common/call-gpg.c index d42325aed..c1472e961 100644 --- a/common/call-gpg.c +++ b/common/call-gpg.c @@ -1,753 +1,753 @@ /* call-gpg.c - Communication with the GPG * Copyright (C) 2009 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 #include #include "call-gpg.h" #include "exechelp.h" #include "i18n.h" #include "logging.h" #include "membuf.h" #include "strlist.h" #include "util.h" static GPGRT_INLINE gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static GPGRT_INLINE gpg_error_t my_error_from_errno (int e) { return gpg_err_make (default_errsource, gpg_err_code_from_errno (e)); } /* Fire up a new GPG. Handle the server's initial greeting. Returns 0 on success and stores the assuan context at R_CTX. */ static gpg_error_t start_gpg (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, int input_fd, int output_fd, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; const char **argv; assuan_fd_t no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; (void)ctrl; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } - /* The first time we are used, intialize the gpg_program variable. */ + /* The first time we are used, initialize the gpg_program variable. */ if ( !gpg_program || !*gpg_program ) gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); /* Compute argv[0]. */ if ( !(pgmname = strrchr (gpg_program, '/'))) pgmname = gpg_program; else pgmname++; if (fflush (NULL)) { err = my_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } argv = xtrycalloc (strlist_length (gpg_arguments) + 3, sizeof *argv); if (argv == NULL) { err = my_error_from_syserror (); return err; } i = 0; argv[i++] = pgmname; argv[i++] = "--server"; for (; gpg_arguments; gpg_arguments = gpg_arguments->next) argv[i++] = gpg_arguments->d; argv[i++] = NULL; i = 0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); if (input_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (input_fd); if (output_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (output_fd); no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to GPG and perform initial handshaking. */ err = assuan_pipe_connect (ctx, gpg_program, argv, no_close_list, NULL, NULL, 0); if (err) { assuan_release (ctx); log_error ("can't connect to GPG: %s\n", gpg_strerror (err)); return gpg_error (GPG_ERR_NO_ENGINE); } if (input_fd != -1) { snprintf (line, sizeof line, "INPUT FD=%d", input_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending INPUT command: %s\n", gpg_strerror (err)); return err; } } if (output_fd != -1) { snprintf (line, sizeof line, "OUTPUT FD=%d", output_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending OUTPUT command: %s\n", gpg_strerror (err)); return err; } } *r_ctx = ctx; return 0; } /* Release the assuan context created by start_gpg. */ static void release_gpg (assuan_context_t ctx) { assuan_release (ctx); } /* The data passed to the writer_thread. */ struct writer_thread_parms { int fd; const void *data; size_t datalen; estream_t stream; gpg_error_t *err_addr; }; /* The thread started by start_writer. */ static void * writer_thread_main (void *arg) { gpg_error_t err = 0; struct writer_thread_parms *parm = arg; char _buffer[4096]; char *buffer; size_t length; if (parm->stream) { buffer = _buffer; err = es_read (parm->stream, buffer, sizeof _buffer, &length); if (err) { log_error ("reading stream failed: %s\n", gpg_strerror (err)); goto leave; } } else { buffer = (char *) parm->data; length = parm->datalen; } while (length) { ssize_t nwritten; nwritten = npth_write (parm->fd, buffer, length < 4096? length:4096); if (nwritten < 0) { if (errno == EINTR) continue; err = my_error_from_syserror (); break; /* Write error. */ } length -= nwritten; if (parm->stream) { if (length == 0) { err = es_read (parm->stream, buffer, sizeof _buffer, &length); if (err) { log_error ("reading stream failed: %s\n", gpg_strerror (err)); break; } if (length == 0) /* We're done. */ break; } } else buffer += nwritten; } leave: *parm->err_addr = err; if (close (parm->fd)) log_error ("closing writer fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to send (DATA,DATALEN) to the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. WRITER_ERR is the address of an gpg_error_t variable to receive a possible write error after the thread has finished. */ static gpg_error_t start_writer (int fd, const void *data, size_t datalen, estream_t stream, npth_t *r_thread, gpg_error_t *err_addr) { gpg_error_t err; struct writer_thread_parms *parm; npth_attr_t tattr; npth_t thread; int ret; memset (r_thread, '\0', sizeof (*r_thread)); *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return my_error_from_syserror (); parm->fd = fd; parm->data = data; parm->datalen = datalen; parm->stream = stream; parm->err_addr = err_addr; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); ret = npth_create (&thread, &tattr, writer_thread_main, parm); if (ret) { err = my_error_from_errno (ret); log_error ("error spawning writer thread: %s\n", gpg_strerror (err)); } else { npth_setname_np (thread, "fd-writer"); err = 0; *r_thread = thread; } npth_attr_destroy (&tattr); return err; } /* The data passed to the reader_thread. */ struct reader_thread_parms { int fd; membuf_t *mb; estream_t stream; gpg_error_t *err_addr; }; /* The thread started by start_reader. */ static void * reader_thread_main (void *arg) { gpg_error_t err = 0; struct reader_thread_parms *parm = arg; char buffer[4096]; int nread; while ( (nread = npth_read (parm->fd, buffer, sizeof buffer)) ) { if (nread < 0) { if (errno == EINTR) continue; err = my_error_from_syserror (); break; /* Read error. */ } if (parm->stream) { const char *p = buffer; size_t nwritten; while (nread) { err = es_write (parm->stream, p, nread, &nwritten); if (err) { log_error ("writing stream failed: %s\n", gpg_strerror (err)); goto leave; } nread -= nwritten; p += nwritten; } } else put_membuf (parm->mb, buffer, nread); } leave: *parm->err_addr = err; if (close (parm->fd)) log_error ("closing reader fd %d failed: %s\n", parm->fd, strerror (errno)); xfree (parm); return NULL; } /* Fire up a thread to receive data from the file descriptor FD. On success the thread receives the ownership over FD. The thread ID is stored at R_TID. After the thread has finished an error from the thread will be stored at ERR_ADDR. */ static gpg_error_t start_reader (int fd, membuf_t *mb, estream_t stream, npth_t *r_thread, gpg_error_t *err_addr) { gpg_error_t err; struct reader_thread_parms *parm; npth_attr_t tattr; npth_t thread; int ret; memset (r_thread, '\0', sizeof (*r_thread)); *err_addr = 0; parm = xtrymalloc (sizeof *parm); if (!parm) return my_error_from_syserror (); parm->fd = fd; parm->mb = mb; parm->stream = stream; parm->err_addr = err_addr; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); ret = npth_create (&thread, &tattr, reader_thread_main, parm); if (ret) { err = my_error_from_errno (ret); log_error ("error spawning reader thread: %s\n", gpg_strerror (err)); } else { npth_setname_np (thread, "fd-reader"); err = 0; *r_thread = thread; } npth_attr_destroy (&tattr); return err; } /* Call GPG to encrypt a block of data. */ static gpg_error_t _gpg_encrypt (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *plain, size_t plainlen, estream_t plain_stream, strlist_t keys, membuf_t *reader_mb, estream_t cipher_stream) { gpg_error_t err; assuan_context_t ctx = NULL; int outbound_fds[2] = { -1, -1 }; int inbound_fds[2] = { -1, -1 }; npth_t writer_thread = (npth_t)0; npth_t reader_thread = (npth_t)0; gpg_error_t writer_err, reader_err; char line[ASSUAN_LINELENGTH]; strlist_t sl; int ret; /* Make sure that either the stream interface xor the buffer interface is used. */ assert ((plain == NULL) != (plain_stream == NULL)); assert ((reader_mb == NULL) != (cipher_stream == NULL)); /* Create two pipes. */ err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); if (!err) err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } /* Start GPG and send the INPUT and OUTPUT commands. */ err = start_gpg (ctrl, gpg_program, gpg_arguments, outbound_fds[0], inbound_fds[1], &ctx); if (err) goto leave; close (outbound_fds[0]); outbound_fds[0] = -1; close (inbound_fds[1]); inbound_fds[1] = -1; /* Start a writer thread to feed the INPUT command of the server. */ err = start_writer (outbound_fds[1], plain, plainlen, plain_stream, &writer_thread, &writer_err); if (err) return err; outbound_fds[1] = -1; /* The thread owns the FD now. */ /* Start a reader thread to eat from the OUTPUT command of the server. */ err = start_reader (inbound_fds[0], reader_mb, cipher_stream, &reader_thread, &reader_err); if (err) return err; outbound_fds[0] = -1; /* The thread owns the FD now. */ /* Run the encryption. */ for (sl = keys; sl; sl = sl->next) { snprintf (line, sizeof line, "RECIPIENT -- %s", sl->d); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's RECIPIENT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } } err = assuan_transact (ctx, "ENCRYPT", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's ENCRYPT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } /* Wait for reader and return the data. */ ret = npth_join (reader_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); goto leave; } /* FIXME: Not really valid, as npth_t is an opaque type. */ memset (&reader_thread, '\0', sizeof (reader_thread)); if (reader_err) { err = reader_err; log_error ("read error in reader thread: %s\n", gpg_strerror (err)); goto leave; } /* Wait for the writer to catch a writer error. */ ret = npth_join (writer_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&writer_thread, '\0', sizeof (writer_thread)); if (writer_err) { err = writer_err; log_error ("write error in writer thread: %s\n", gpg_strerror (err)); goto leave; } leave: /* FIXME: Not valid, as npth_t is an opaque type. */ if (reader_thread) npth_detach (reader_thread); if (writer_thread) npth_detach (writer_thread); if (outbound_fds[0] != -1) close (outbound_fds[0]); if (outbound_fds[1] != -1) close (outbound_fds[1]); if (inbound_fds[0] != -1) close (inbound_fds[0]); if (inbound_fds[1] != -1) close (inbound_fds[1]); release_gpg (ctx); return err; } gpg_error_t gpg_encrypt_blob (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *plain, size_t plainlen, strlist_t keys, void **r_ciph, size_t *r_ciphlen) { gpg_error_t err; membuf_t reader_mb; *r_ciph = NULL; *r_ciphlen = 0; /* Init the memory buffer to receive the encrypted stuff. */ init_membuf (&reader_mb, 4096); err = _gpg_encrypt (ctrl, gpg_program, gpg_arguments, plain, plainlen, NULL, keys, &reader_mb, NULL); if (! err) { /* Return the data. */ *r_ciph = get_membuf (&reader_mb, r_ciphlen); if (!*r_ciph) { err = my_error_from_syserror (); log_error ("error while storing the data in the reader thread: %s\n", gpg_strerror (err)); } } xfree (get_membuf (&reader_mb, NULL)); return err; } gpg_error_t gpg_encrypt_stream (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, estream_t plain_stream, strlist_t keys, estream_t cipher_stream) { return _gpg_encrypt (ctrl, gpg_program, gpg_arguments, NULL, 0, plain_stream, keys, NULL, cipher_stream); } /* Call GPG to decrypt a block of data. */ static gpg_error_t _gpg_decrypt (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *ciph, size_t ciphlen, estream_t cipher_stream, membuf_t *reader_mb, estream_t plain_stream) { gpg_error_t err; assuan_context_t ctx = NULL; int outbound_fds[2] = { -1, -1 }; int inbound_fds[2] = { -1, -1 }; npth_t writer_thread = (npth_t)0; npth_t reader_thread = (npth_t)0; gpg_error_t writer_err, reader_err; int ret; /* Make sure that either the stream interface xor the buffer interface is used. */ assert ((ciph == NULL) != (cipher_stream == NULL)); assert ((reader_mb == NULL) != (plain_stream == NULL)); /* Create two pipes. */ err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); if (!err) err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } /* Start GPG and send the INPUT and OUTPUT commands. */ err = start_gpg (ctrl, gpg_program, gpg_arguments, outbound_fds[0], inbound_fds[1], &ctx); if (err) goto leave; close (outbound_fds[0]); outbound_fds[0] = -1; close (inbound_fds[1]); inbound_fds[1] = -1; /* Start a writer thread to feed the INPUT command of the server. */ err = start_writer (outbound_fds[1], ciph, ciphlen, cipher_stream, &writer_thread, &writer_err); if (err) return err; outbound_fds[1] = -1; /* The thread owns the FD now. */ /* Start a reader thread to eat from the OUTPUT command of the server. */ err = start_reader (inbound_fds[0], reader_mb, plain_stream, &reader_thread, &reader_err); if (err) return err; outbound_fds[0] = -1; /* The thread owns the FD now. */ /* Run the decryption. */ err = assuan_transact (ctx, "DECRYPT", NULL, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("the engine's DECRYPT command failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); goto leave; } /* Wait for reader and return the data. */ ret = npth_join (reader_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&reader_thread, '\0', sizeof (reader_thread)); if (reader_err) { err = reader_err; log_error ("read error in reader thread: %s\n", gpg_strerror (err)); goto leave; } /* Wait for the writer to catch a writer error. */ ret = npth_join (writer_thread, NULL); if (ret) { err = my_error_from_errno (ret); log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); goto leave; } memset (&writer_thread, '\0', sizeof (writer_thread)); if (writer_err) { err = writer_err; log_error ("write error in writer thread: %s\n", gpg_strerror (err)); goto leave; } leave: if (reader_thread) npth_detach (reader_thread); if (writer_thread) npth_detach (writer_thread); if (outbound_fds[0] != -1) close (outbound_fds[0]); if (outbound_fds[1] != -1) close (outbound_fds[1]); if (inbound_fds[0] != -1) close (inbound_fds[0]); if (inbound_fds[1] != -1) close (inbound_fds[1]); release_gpg (ctx); return err; } gpg_error_t gpg_decrypt_blob (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, const void *ciph, size_t ciphlen, void **r_plain, size_t *r_plainlen) { gpg_error_t err; membuf_t reader_mb; *r_plain = NULL; *r_plainlen = 0; /* Init the memory buffer to receive the encrypted stuff. */ init_membuf_secure (&reader_mb, 1024); err = _gpg_decrypt (ctrl, gpg_program, gpg_arguments, ciph, ciphlen, NULL, &reader_mb, NULL); if (! err) { /* Return the data. */ *r_plain = get_membuf (&reader_mb, r_plainlen); if (!*r_plain) { err = my_error_from_syserror (); log_error ("error while storing the data in the reader thread: %s\n", gpg_strerror (err)); } } xfree (get_membuf (&reader_mb, NULL)); return err; } gpg_error_t gpg_decrypt_stream (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, estream_t cipher_stream, estream_t plain_stream) { return _gpg_decrypt (ctrl, gpg_program, gpg_arguments, NULL, 0, cipher_stream, NULL, plain_stream); } diff --git a/common/dotlock.c b/common/dotlock.c index cbbd0f3f9..5227bb64e 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -1,1379 +1,1379 @@ /* dotlock.c - dotfile locking * Copyright (C) 1998, 2000, 2001, 2003, 2004, * 2005, 2006, 2008, 2010, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG 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. * * 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 copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . * * ALTERNATIVELY, this file may be distributed under the terms of the * following license, in which case the provisions of this license are * required INSTEAD OF the GNU Lesser General License or the GNU * General Public License. If you wish to allow use of your version of * this file only under the terms of the GNU Lesser General License or * the GNU General Public License, and not to allow others to use your * version of this file under the terms of the following license, * indicate your decision by deleting this paragraph and the license * below. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Overview: ========= This module implements advisory file locking in a portable way. Due to the problems with POSIX fcntl locking a separate lock file is used. It would be possible to use fcntl locking on this lock file and thus avoid the weird auto unlock bug of POSIX while still having an unproved better performance of fcntl locking. However there are still problems left, thus we resort to use a hardlink which has the well defined property that a link call will fail if the target file already exists. Given that hardlinks are also available on NTFS file systems since Windows XP; it will be possible to enhance this module to use hardlinks even on Windows and thus allow Windows and Posix clients to use locking on the same directory. This is not yet implemented; instead we use a lockfile on Windows along with W32 style file locking. On FAT file systems hardlinks are not supported. Thus this method does not work. Our solution is to use a O_EXCL locking instead. Querying the type of the file system is not easy to do in a portable way (e.g. Linux has a statfs, BSDs have a the same call but using different structures and constants). What we do instead is to check at runtime whether link(2) works for a specific lock file. How to use: =========== At program initialization time, the module should be explicitly initialized: dotlock_create (NULL, 0); This installs an atexit handler and may also initialize mutex etc. It is optional for non-threaded applications. Only the first call has an effect. This needs to be done before any extra threads are started. To create a lock file (which prepares it but does not take the lock) you do: dotlock_t h h = dotlock_create (fname, 0); if (!h) error ("error creating lock file: %s\n", strerror (errno)); It is important to handle the error. For example on a read-only file system a lock can't be created (but is usually not needed). FNAME is the file you want to lock; the actual lockfile is that name with the suffix ".lock" appended. On success a handle to be used with the other functions is returned or NULL on error. Note that the handle shall only be used by one thread at a time. This function creates a unique file temporary file (".#lk*") in the same directory as FNAME and returns a handle for further operations. The module keeps track of theses unique files so that they will be unlinked using the atexit handler. If you don't need the lock file anymore, you may also explicitly remove it with a call to: dotlock_destroy (h); To actually lock the file, you use: if (dotlock_take (h, -1)) error ("error taking lock: %s\n", strerror (errno)); This function will wait until the lock is acquired. If an unexpected error occurs if will return non-zero and set ERRNO. If you pass (0) instead of (-1) the function does not wait in case the file is already locked but returns -1 and sets ERRNO to EACCES. Any other positive value for the second parameter is considered a timeout valuie in milliseconds. To release the lock you call: if (dotlock_release (h)) error ("error releasing lock: %s\n", strerror (errno)); or, if the lock file is not anymore needed, you may just call dotlock_destroy. However dotlock_release does some extra checks before releasing the lock and prints diagnostics to help detecting bugs. If you want to explicitly destroy all lock files you may call dotlock_remove_lockfiles (); which is the core of the installed atexit handler. In case your application wants to disable locking completely it may call disable_locking () before any locks are created. There are two convenience functions to store an integer (e.g. a file descriptor) value with the handle: void dotlock_set_fd (dotlock_t h, int fd); int dotlock_get_fd (dotlock_t h); If nothing has been stored dotlock_get_fd returns -1. How to build: ============= This module was originally developed for GnuPG but later changed to allow its use without any GnuPG dependency. If you want to use it with you application you may simply use it and it should figure out most things automagically. You may use the common config.h file to pass macros, but take care to pass -DHAVE_CONFIG_H to the compiler. Macros used by this module are: DOTLOCK_USE_PTHREAD - Define if POSIX threads are in use. DOTLOCK_GLIB_LOGGING - Define this to use Glib logging functions. DOTLOCK_EXT_SYM_PREFIX - Prefix all external symbols with the string to which this macro evaluates. GNUPG_MAJOR_VERSION - Defined when used by GnuPG. HAVE_DOSISH_SYSTEM - Defined for Windows etc. Will be automatically defined if a the target is Windows. HAVE_POSIX_SYSTEM - Internally defined to !HAVE_DOSISH_SYSTEM. HAVE_SIGNAL_H - Should be defined on Posix systems. If config.h is not used defaults to defined. DIRSEP_C - Separation character for file name parts. Usually not redefined. EXTSEP_S - Separation string for file name suffixes. Usually not redefined. HAVE_W32CE_SYSTEM - Currently only used by GnuPG. Note that there is a test program t-dotlock which has compile instructions at its end. At least for SMBFS and CIFS it is important that 64 bit versions of stat are used; most programming environments do this these days, just in case you want to compile it on the command line, remember to pass -D_FILE_OFFSET_BITS=64 Bugs: ===== On Windows this module is not yet thread-safe. Miscellaneous notes: ==================== On hardlinks: - Hardlinks are supported under Windows with NTFS since XP/Server2003. - In Linux 2.6.33 both SMBFS and CIFS seem to support hardlinks. - NFS supports hard links. But there are solvable problems. - FAT does not support links On the file locking API: - CIFS on Linux 2.6.33 supports several locking methods. SMBFS seems not to support locking. No closer checks done. - NFS supports Posix locks. flock is emulated in the server. However there are a couple of problems; see below. - FAT does not support locks. - An advantage of fcntl locking is that R/W locks can be implemented which is not easy with a straight lock file. On O_EXCL: - Does not work reliable on NFS - Should work on CIFS and SMBFS but how can we delete lockfiles? On NFS problems: - Locks vanish if the server crashes and reboots. - Client crashes keep the lock in the server until the client re-connects. - Communication problems may return unreliable error codes. The MUA Postfix's workaround is to compare the link count after seeing an error for link. However that gives a race. If using a unique file to link to a lockfile and using stat to check the link count instead of looking at the error return of link(2) is the best solution. - O_EXCL seems to have a race and may re-create a file anyway. */ #ifdef HAVE_CONFIG_H # include #endif /* Some quick replacements for stuff we usually expect to be defined in config.h. Define HAVE_POSIX_SYSTEM for better readability. */ #if !defined (HAVE_DOSISH_SYSTEM) && defined(_WIN32) # define HAVE_DOSISH_SYSTEM 1 #endif #if !defined (HAVE_DOSISH_SYSTEM) && !defined (HAVE_POSIX_SYSTEM) # define HAVE_POSIX_SYSTEM 1 #endif /* With no config.h assume that we have sitgnal.h. */ #if !defined (HAVE_CONFIG_H) && defined (HAVE_POSIX_SYSTEM) # define HAVE_SIGNAL_H 1 #endif /* Standard headers. */ #include #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM # define WIN32_LEAN_AND_MEAN /* We only need the OS core stuff. */ # include #else # include # include # include #endif #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #ifdef DOTLOCK_USE_PTHREAD # include #endif #ifdef DOTLOCK_GLIB_LOGGING # include #endif #ifdef GNUPG_MAJOR_VERSION # include "util.h" # include "common-defs.h" # include "stringhelp.h" /* For stpcpy and w32_strerror. */ #endif #ifdef HAVE_W32CE_SYSTEM # include "utf8conv.h" /* WindowsCE requires filename conversion. */ #endif #include "dotlock.h" /* Define constants for file name construction. */ #if !defined(DIRSEP_C) && !defined(EXTSEP_S) # ifdef HAVE_DOSISH_SYSTEM # define DIRSEP_C '\\' # define EXTSEP_S "." #else # define DIRSEP_C '/' # define EXTSEP_S "." # endif #endif -/* In GnuPG we use wrappers around the malloc fucntions. If they are +/* In GnuPG we use wrappers around the malloc functions. If they are not defined we assume that this code is used outside of GnuPG and fall back to the regular malloc functions. */ #ifndef xtrymalloc # define xtrymalloc(a) malloc ((a)) # define xtrycalloc(a,b) calloc ((a), (b)) # define xfree(a) free ((a)) #endif /* Wrapper to set ERRNO (required for W32CE). */ #ifdef GPG_ERROR_VERSION # define my_set_errno(e) gpg_err_set_errno ((e)) #else # define my_set_errno(e) do { errno = (e); } while (0) #endif /* Gettext macro replacement. */ #ifndef _ # define _(a) (a) #endif #ifdef GNUPG_MAJOR_VERSION # define my_info_0(a) log_info ((a)) # define my_info_1(a,b) log_info ((a), (b)) # define my_info_2(a,b,c) log_info ((a), (b), (c)) # define my_info_3(a,b,c,d) log_info ((a), (b), (c), (d)) # define my_error_0(a) log_error ((a)) # define my_error_1(a,b) log_error ((a), (b)) # define my_error_2(a,b,c) log_error ((a), (b), (c)) # define my_debug_1(a,b) log_debug ((a), (b)) # define my_fatal_0(a) log_fatal ((a)) #elif defined (DOTLOCK_GLIB_LOGGING) # define my_info_0(a) g_message ((a)) # define my_info_1(a,b) g_message ((a), (b)) # define my_info_2(a,b,c) g_message ((a), (b), (c)) # define my_info_3(a,b,c,d) g_message ((a), (b), (c), (d)) # define my_error_0(a) g_warning ((a)) # define my_error_1(a,b) g_warning ((a), (b)) # define my_error_2(a,b,c) g_warning ((a), (b), (c)) # define my_debug_1(a,b) g_debug ((a), (b)) # define my_fatal_0(a) g_error ((a)) #else # define my_info_0(a) fprintf (stderr, (a)) # define my_info_1(a,b) fprintf (stderr, (a), (b)) # define my_info_2(a,b,c) fprintf (stderr, (a), (b), (c)) # define my_info_3(a,b,c,d) fprintf (stderr, (a), (b), (c), (d)) # define my_error_0(a) fprintf (stderr, (a)) # define my_error_1(a,b) fprintf (stderr, (a), (b)) # define my_error_2(a,b,c) fprintf (stderr, (a), (b), (c)) # define my_debug_1(a,b) fprintf (stderr, (a), (b)) # define my_fatal_0(a) do { fprintf (stderr,(a)); fflush (stderr); \ abort (); } while (0) #endif /* The object describing a lock. */ struct dotlock_handle { struct dotlock_handle *next; char *lockname; /* Name of the actual lockfile. */ unsigned int locked:1; /* Lock status. */ unsigned int disable:1; /* If true, locking is disabled. */ unsigned int use_o_excl:1; /* Use open (O_EXCL) for locking. */ int extra_fd; /* A place for the caller to store an FD. */ #ifdef HAVE_DOSISH_SYSTEM HANDLE lockhd; /* The W32 handle of the lock file. */ #else /*!HAVE_DOSISH_SYSTEM */ char *tname; /* Name of the lockfile template. */ size_t nodename_off; /* Offset in TNAME of the nodename part. */ size_t nodename_len; /* Length of the nodename part. */ #endif /*!HAVE_DOSISH_SYSTEM */ }; /* A list of all lock handles. The volatile attribute might help if used in an atexit handler. Note that [UN]LOCK_all_lockfiles must not change ERRNO. */ static volatile dotlock_t all_lockfiles; #ifdef DOTLOCK_USE_PTHREAD static pthread_mutex_t all_lockfiles_mutex = PTHREAD_MUTEX_INITIALIZER; # define LOCK_all_lockfiles() do { \ if (pthread_mutex_lock (&all_lockfiles_mutex)) \ my_fatal_0 ("locking all_lockfiles_mutex failed\n"); \ } while (0) # define UNLOCK_all_lockfiles() do { \ if (pthread_mutex_unlock (&all_lockfiles_mutex)) \ my_fatal_0 ("unlocking all_lockfiles_mutex failed\n"); \ } while (0) #else /*!DOTLOCK_USE_PTHREAD*/ # define LOCK_all_lockfiles() do { } while (0) # define UNLOCK_all_lockfiles() do { } while (0) #endif /*!DOTLOCK_USE_PTHREAD*/ /* If this has the value true all locking is disabled. */ static int never_lock; #ifdef HAVE_DOSISH_SYSTEM static int map_w32_to_errno (DWORD w32_err) { switch (w32_err) { case 0: return 0; case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_PATH_NOT_FOUND: return ENOENT; case ERROR_ACCESS_DENIED: return EPERM; case ERROR_INVALID_HANDLE: case ERROR_INVALID_BLOCK: return EINVAL; case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM; case ERROR_NO_DATA: case ERROR_BROKEN_PIPE: return EPIPE; default: return EIO; } } #endif /*HAVE_DOSISH_SYSTEM*/ /* Entirely disable all locking. This function should be called before any locking is done. It may be called right at startup of the process as it only sets a global value. */ void dotlock_disable (void) { never_lock = 1; } #ifdef HAVE_POSIX_SYSTEM static int maybe_deadlock (dotlock_t h) { dotlock_t r; int res = 0; LOCK_all_lockfiles (); for (r=all_lockfiles; r; r = r->next) { if ( r != h && r->locked ) { res = 1; break; } } UNLOCK_all_lockfiles (); return res; } #endif /*HAVE_POSIX_SYSTEM*/ /* Read the lock file and return the pid, returns -1 on error. True will be stored in the integer at address SAME_NODE if the lock file has been created on the same node. */ #ifdef HAVE_POSIX_SYSTEM static int read_lockfile (dotlock_t h, int *same_node ) { char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node names are usually shorter. */ int fd; int pid = -1; char *buffer, *p; size_t expected_len; int res, nread; *same_node = 0; expected_len = 10 + 1 + h->nodename_len + 1; if ( expected_len >= sizeof buffer_space) { buffer = xtrymalloc (expected_len); if (!buffer) return -1; } else buffer = buffer_space; if ( (fd = open (h->lockname, O_RDONLY)) == -1 ) { int e = errno; my_info_2 ("error opening lockfile '%s': %s\n", h->lockname, strerror(errno) ); if (buffer != buffer_space) xfree (buffer); my_set_errno (e); /* Need to return ERRNO here. */ return -1; } p = buffer; nread = 0; do { res = read (fd, p, expected_len - nread); if (res == -1 && errno == EINTR) continue; if (res < 0) { int e = errno; my_info_1 ("error reading lockfile '%s'\n", h->lockname ); close (fd); if (buffer != buffer_space) xfree (buffer); my_set_errno (e); return -1; } p += res; nread += res; } while (res && nread != expected_len); close(fd); if (nread < 11) { my_info_1 ("invalid size of lockfile '%s'\n", h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); return -1; } if (buffer[10] != '\n' || (buffer[10] = 0, pid = atoi (buffer)) == -1 || !pid ) { my_error_2 ("invalid pid %d in lockfile '%s'\n", pid, h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); return -1; } if (nread == expected_len && !memcmp (h->tname+h->nodename_off, buffer+11, h->nodename_len) && buffer[11+h->nodename_len] == '\n') *same_node = 1; if (buffer != buffer_space) xfree (buffer); return pid; } #endif /*HAVE_POSIX_SYSTEM */ /* Check whether the file system which stores TNAME supports hardlinks. Instead of using the non-portable statsfs call which differs between various Unix versions, we do a runtime test. Returns: 0 supports hardlinks; 1 no hardlink support, -1 unknown (test error). */ #ifdef HAVE_POSIX_SYSTEM static int use_hardlinks_p (const char *tname) { char *lname; struct stat sb; unsigned int nlink; int res; if (stat (tname, &sb)) return -1; nlink = (unsigned int)sb.st_nlink; lname = xtrymalloc (strlen (tname) + 1 + 1); if (!lname) return -1; strcpy (lname, tname); strcat (lname, "x"); /* We ignore the return value of link() because it is unreliable. */ (void) link (tname, lname); if (stat (tname, &sb)) res = -1; /* Ooops. */ else if (sb.st_nlink == nlink + 1) res = 0; /* Yeah, hardlinks are supported. */ else res = 1; /* No hardlink support. */ unlink (lname); xfree (lname); return res; } #endif /*HAVE_POSIX_SYSTEM */ #ifdef HAVE_POSIX_SYSTEM /* Locking core for Unix. It used a temporary file and the link system call to make locking an atomic operation. */ static dotlock_t dotlock_create_unix (dotlock_t h, const char *file_to_lock) { int fd = -1; char pidstr[16]; const char *nodename; const char *dirpart; int dirpartlen; struct utsname utsbuf; size_t tnamelen; snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() ); /* Create a temporary file. */ if ( uname ( &utsbuf ) ) nodename = "unknown"; else nodename = utsbuf.nodename; if ( !(dirpart = strrchr (file_to_lock, DIRSEP_C)) ) { dirpart = EXTSEP_S; dirpartlen = 1; } else { dirpartlen = dirpart - file_to_lock; dirpart = file_to_lock; } LOCK_all_lockfiles (); h->next = all_lockfiles; all_lockfiles = h; tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10 + 1; h->tname = xtrymalloc (tnamelen + 1); if (!h->tname) { all_lockfiles = h->next; UNLOCK_all_lockfiles (); xfree (h); return NULL; } h->nodename_len = strlen (nodename); snprintf (h->tname, tnamelen, "%.*s/.#lk%p.", dirpartlen, dirpart, h ); h->nodename_off = strlen (h->tname); snprintf (h->tname+h->nodename_off, tnamelen - h->nodename_off, "%s.%d", nodename, (int)getpid ()); do { my_set_errno (0); fd = open (h->tname, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR ); } while (fd == -1 && errno == EINTR); if ( fd == -1 ) { int saveerrno = errno; all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("failed to create temporary file '%s': %s\n"), h->tname, strerror (errno)); xfree (h->tname); xfree (h); my_set_errno (saveerrno); return NULL; } if ( write (fd, pidstr, 11 ) != 11 ) goto write_failed; if ( write (fd, nodename, strlen (nodename) ) != strlen (nodename) ) goto write_failed; if ( write (fd, "\n", 1 ) != 1 ) goto write_failed; if ( close (fd) ) { if ( errno == EINTR ) fd = -1; goto write_failed; } fd = -1; /* Check whether we support hard links. */ switch (use_hardlinks_p (h->tname)) { case 0: /* Yes. */ break; case 1: /* No. */ unlink (h->tname); h->use_o_excl = 1; break; default: { int saveerrno = errno; my_error_2 ("can't check whether hardlinks are supported for '%s': %s\n" , h->tname, strerror (saveerrno)); my_set_errno (saveerrno); } goto write_failed; } h->lockname = xtrymalloc (strlen (file_to_lock) + 6 ); if (!h->lockname) { int saveerrno = errno; all_lockfiles = h->next; UNLOCK_all_lockfiles (); unlink (h->tname); xfree (h->tname); xfree (h); my_set_errno (saveerrno); return NULL; } strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock"); UNLOCK_all_lockfiles (); if (h->use_o_excl) my_debug_1 ("locking for '%s' done via O_EXCL\n", h->lockname); return h; write_failed: { int saveerrno = errno; all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("error writing to '%s': %s\n"), h->tname, strerror (errno)); if ( fd != -1 ) close (fd); unlink (h->tname); xfree (h->tname); xfree (h); my_set_errno (saveerrno); } return NULL; } #endif /*HAVE_POSIX_SYSTEM*/ #ifdef HAVE_DOSISH_SYSTEM /* Locking core for Windows. This version does not need a temporary file but uses the plain lock file along with record locking. We create this file here so that we later only need to do the file locking. For error reporting it is useful to keep the name of the file in the handle. */ static dotlock_t dotlock_create_w32 (dotlock_t h, const char *file_to_lock) { LOCK_all_lockfiles (); h->next = all_lockfiles; all_lockfiles = h; h->lockname = xtrymalloc ( strlen (file_to_lock) + 6 ); if (!h->lockname) { all_lockfiles = h->next; UNLOCK_all_lockfiles (); xfree (h); return NULL; } strcpy (stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock"); /* If would be nice if we would use the FILE_FLAG_DELETE_ON_CLOSE along with FILE_SHARE_DELETE but that does not work due to a race condition: Despite the OPEN_ALWAYS flag CreateFile may return an error and we can't reliable create/open the lock file unless we would wait here until it works - however there are other valid reasons why a lock file can't be created and thus the process would not stop as expected but spin until Windows crashes. Our solution is to keep the lock file open; that does not harm. */ { #ifdef HAVE_W32CE_SYSTEM wchar_t *wname = utf8_to_wchar (h->lockname); if (wname) h->lockhd = CreateFile (wname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); else h->lockhd = INVALID_HANDLE_VALUE; xfree (wname); #else h->lockhd = CreateFile (h->lockname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL); #endif } if (h->lockhd == INVALID_HANDLE_VALUE) { int saveerrno = map_w32_to_errno (GetLastError ()); all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("can't create '%s': %s\n"), h->lockname, w32_strerror (-1)); xfree (h->lockname); xfree (h); my_set_errno (saveerrno); return NULL; } return h; } #endif /*HAVE_DOSISH_SYSTEM*/ /* Create a lockfile for a file name FILE_TO_LOCK and returns an object of type dotlock_t which may be used later to actually acquire the lock. A cleanup routine gets installed to cleanup left over locks or other files used internally by the lock mechanism. Calling this function with NULL does only install the atexit handler and may thus be used to assure that the cleanup is called after all other atexit handlers. This function creates a lock file in the same directory as FILE_TO_LOCK using that name and a suffix of ".lock". Note that on POSIX systems a temporary file ".#lk..pid[.threadid] is used. FLAGS must be 0. The function returns an new handle which needs to be released using destroy_dotlock but gets also released at the termination of the process. On error NULL is returned. */ dotlock_t dotlock_create (const char *file_to_lock, unsigned int flags) { static int initialized; dotlock_t h; if ( !initialized ) { atexit (dotlock_remove_lockfiles); initialized = 1; } if ( !file_to_lock ) return NULL; /* Only initialization was requested. */ if (flags) { my_set_errno (EINVAL); return NULL; } h = xtrycalloc (1, sizeof *h); if (!h) return NULL; h->extra_fd = -1; if (never_lock) { h->disable = 1; LOCK_all_lockfiles (); h->next = all_lockfiles; all_lockfiles = h; UNLOCK_all_lockfiles (); return h; } #ifdef HAVE_DOSISH_SYSTEM return dotlock_create_w32 (h, file_to_lock); #else /*!HAVE_DOSISH_SYSTEM */ return dotlock_create_unix (h, file_to_lock); #endif /*!HAVE_DOSISH_SYSTEM*/ } /* Convenience function to store a file descriptor (or any other integer value) in the context of handle H. */ void dotlock_set_fd (dotlock_t h, int fd) { h->extra_fd = fd; } /* Convenience function to retrieve a file descriptor (or any other integer value) stored in the context of handle H. */ int dotlock_get_fd (dotlock_t h) { return h->extra_fd; } #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of destroy_dotlock. */ static void dotlock_destroy_unix (dotlock_t h) { if (h->locked && h->lockname) unlink (h->lockname); if (h->tname && !h->use_o_excl) unlink (h->tname); xfree (h->tname); } #endif /*HAVE_POSIX_SYSTEM*/ #ifdef HAVE_DOSISH_SYSTEM /* Windows specific code of destroy_dotlock. */ static void dotlock_destroy_w32 (dotlock_t h) { if (h->locked) { OVERLAPPED ovl; memset (&ovl, 0, sizeof ovl); UnlockFileEx (h->lockhd, 0, 1, 0, &ovl); } CloseHandle (h->lockhd); } #endif /*HAVE_DOSISH_SYSTEM*/ /* Destroy the lock handle H and release the lock. */ void dotlock_destroy (dotlock_t h) { dotlock_t hprev, htmp; if ( !h ) return; /* First remove the handle from our global list of all locks. */ LOCK_all_lockfiles (); for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next) if (htmp == h) { if (hprev) hprev->next = htmp->next; else all_lockfiles = htmp->next; h->next = NULL; break; } UNLOCK_all_lockfiles (); /* Then destroy the lock. */ if (!h->disable) { #ifdef HAVE_DOSISH_SYSTEM dotlock_destroy_w32 (h); #else /* !HAVE_DOSISH_SYSTEM */ dotlock_destroy_unix (h); #endif /* HAVE_DOSISH_SYSTEM */ xfree (h->lockname); } xfree(h); } #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of make_dotlock. Returns 0 on success and -1 on error. */ static int dotlock_take_unix (dotlock_t h, long timeout) { int wtime = 0; int sumtime = 0; int pid; int lastpid = -1; int ownerchanged; const char *maybe_dead=""; int same_node; int saveerrno; again: if (h->use_o_excl) { /* No hardlink support - use open(O_EXCL). */ int fd; do { my_set_errno (0); fd = open (h->lockname, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR ); } while (fd == -1 && errno == EINTR); if (fd == -1 && errno == EEXIST) ; /* Lock held by another process. */ else if (fd == -1) { saveerrno = errno; my_error_2 ("lock not made: open(O_EXCL) of '%s' failed: %s\n", h->lockname, strerror (saveerrno)); my_set_errno (saveerrno); return -1; } else { char pidstr[16]; snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid()); if (write (fd, pidstr, 11 ) == 11 && write (fd, h->tname + h->nodename_off,h->nodename_len) == h->nodename_len && write (fd, "\n", 1) == 1 && !close (fd)) { h->locked = 1; return 0; } /* Write error. */ saveerrno = errno; my_error_2 ("lock not made: writing to '%s' failed: %s\n", h->lockname, strerror (errno)); close (fd); unlink (h->lockname); my_set_errno (saveerrno); return -1; } } else /* Standard method: Use hardlinks. */ { struct stat sb; /* We ignore the return value of link() because it is unreliable. */ (void) link (h->tname, h->lockname); if (stat (h->tname, &sb)) { saveerrno = errno; my_error_1 ("lock not made: Oops: stat of tmp file failed: %s\n", strerror (errno)); /* In theory this might be a severe error: It is possible that link succeeded but stat failed due to changed permissions. We can't do anything about it, though. */ my_set_errno (saveerrno); return -1; } if (sb.st_nlink == 2) { h->locked = 1; return 0; /* Okay. */ } } /* Check for stale lock files. */ if ( (pid = read_lockfile (h, &same_node)) == -1 ) { if ( errno != ENOENT ) { saveerrno = errno; my_info_0 ("cannot read lockfile\n"); my_set_errno (saveerrno); return -1; } my_info_0 ("lockfile disappeared\n"); goto again; } else if ( pid == getpid() && same_node ) { my_info_0 ("Oops: lock already held by us\n"); h->locked = 1; return 0; /* okay */ } else if ( same_node && kill (pid, 0) && errno == ESRCH ) { /* Note: It is unlikley that we get a race here unless a pid is reused too fast or a new process with the same pid as the one of the stale file tries to lock right at the same time as we. */ my_info_1 (_("removing stale lockfile (created by %d)\n"), pid); unlink (h->lockname); goto again; } if (lastpid == -1) lastpid = pid; ownerchanged = (pid != lastpid); if (timeout) { struct timeval tv; /* Wait until lock has been released. We use increasing retry intervals of 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s but reset it if the lock owner meanwhile changed. */ if (!wtime || ownerchanged) wtime = 50; else if (wtime < 800) wtime *= 2; else if (wtime == 800) wtime = 2000; else if (wtime < 8000) wtime *= 2; if (timeout > 0) { if (wtime > timeout) wtime = timeout; timeout -= wtime; } sumtime += wtime; if (sumtime >= 1500) { sumtime = 0; my_info_3 (_("waiting for lock (held by %d%s) %s...\n"), pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):""); } tv.tv_sec = wtime / 1000; tv.tv_usec = (wtime % 1000) * 1000; select (0, NULL, NULL, NULL, &tv); goto again; } my_set_errno (EACCES); return -1; } #endif /*HAVE_POSIX_SYSTEM*/ #ifdef HAVE_DOSISH_SYSTEM /* Windows specific code of make_dotlock. Returns 0 on success and -1 on error. */ static int dotlock_take_w32 (dotlock_t h, long timeout) { int wtime = 0; int w32err; OVERLAPPED ovl; again: /* Lock one byte at offset 0. The offset is given by OVL. */ memset (&ovl, 0, sizeof ovl); if (LockFileEx (h->lockhd, (LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ovl)) { h->locked = 1; return 0; /* okay */ } w32err = GetLastError (); if (w32err != ERROR_LOCK_VIOLATION) { my_error_2 (_("lock '%s' not made: %s\n"), h->lockname, w32_strerror (w32err)); my_set_errno (map_w32_to_errno (w32err)); return -1; } if (timeout) { /* Wait until lock has been released. We use retry intervals of 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s. */ if (!wtime) wtime = 50; else if (wtime < 800) wtime *= 2; else if (wtime == 800) wtime = 2000; else if (wtime < 8000) wtime *= 2; if (timeout > 0) { if (wtime > timeout) wtime = timeout; timeout -= wtime; } if (wtime >= 800) my_info_1 (_("waiting for lock %s...\n"), h->lockname); Sleep (wtime); goto again; } my_set_errno (EACCES); return -1; } #endif /*HAVE_DOSISH_SYSTEM*/ /* Take a lock on H. A value of 0 for TIMEOUT returns immediately if the lock can't be taked, -1 waits forever (hopefully not), other values wait for TIMEOUT milliseconds. Returns: 0 on success */ int dotlock_take (dotlock_t h, long timeout) { int ret; if ( h->disable ) return 0; /* Locks are completely disabled. Return success. */ if ( h->locked ) { my_debug_1 ("Oops, '%s' is already locked\n", h->lockname); return 0; } #ifdef HAVE_DOSISH_SYSTEM ret = dotlock_take_w32 (h, timeout); #else /*!HAVE_DOSISH_SYSTEM*/ ret = dotlock_take_unix (h, timeout); #endif /*!HAVE_DOSISH_SYSTEM*/ return ret; } #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of release_dotlock. */ static int dotlock_release_unix (dotlock_t h) { int pid, same_node; int saveerrno; pid = read_lockfile (h, &same_node); if ( pid == -1 ) { saveerrno = errno; my_error_0 ("release_dotlock: lockfile error\n"); my_set_errno (saveerrno); return -1; } if ( pid != getpid() || !same_node ) { my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid); my_set_errno (EACCES); return -1; } if ( unlink( h->lockname ) ) { saveerrno = errno; my_error_1 ("release_dotlock: error removing lockfile '%s'\n", h->lockname); my_set_errno (saveerrno); return -1; } /* Fixme: As an extra check we could check whether the link count is now really at 1. */ return 0; } #endif /*HAVE_POSIX_SYSTEM */ #ifdef HAVE_DOSISH_SYSTEM /* Windows specific code of release_dotlock. */ static int dotlock_release_w32 (dotlock_t h) { OVERLAPPED ovl; memset (&ovl, 0, sizeof ovl); if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl)) { int saveerrno = map_w32_to_errno (GetLastError ()); my_error_2 ("release_dotlock: error removing lockfile '%s': %s\n", h->lockname, w32_strerror (-1)); my_set_errno (saveerrno); return -1; } return 0; } #endif /*HAVE_DOSISH_SYSTEM */ /* Release a lock. Returns 0 on success. */ int dotlock_release (dotlock_t h) { int ret; /* To avoid atexit race conditions we first check whether there are any locks left. It might happen that another atexit handler tries to release the lock while the atexit handler of this module already ran and thus H is undefined. */ LOCK_all_lockfiles (); ret = !all_lockfiles; UNLOCK_all_lockfiles (); if (ret) return 0; if ( h->disable ) return 0; if ( !h->locked ) { my_debug_1 ("Oops, '%s' is not locked\n", h->lockname); return 0; } #ifdef HAVE_DOSISH_SYSTEM ret = dotlock_release_w32 (h); #else ret = dotlock_release_unix (h); #endif if (!ret) h->locked = 0; return ret; } /* Remove all lockfiles. This is called by the atexit handler installed by this module but may also be called by other termination handlers. */ void dotlock_remove_lockfiles (void) { dotlock_t h, h2; /* First set the lockfiles list to NULL so that for example dotlock_release is aware that this function is currently running. */ LOCK_all_lockfiles (); h = all_lockfiles; all_lockfiles = NULL; UNLOCK_all_lockfiles (); while ( h ) { h2 = h->next; dotlock_destroy (h); h = h2; } } diff --git a/common/exechelp.h b/common/exechelp.h index 6f2653b5d..2b40ba098 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -1,199 +1,199 @@ /* exechelp.h - Definitions for the fork and exec helpers * Copyright (C) 2004, 2009, 2010 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_EXECHELP_H #define GNUPG_COMMON_EXECHELP_H /* Return the maximum number of currently allowed file descriptors. Only useful on POSIX systems. */ int get_max_fds (void); /* Close all file descriptors starting with descriptor FIRST. If EXCEPT is not NULL, it is expected to be a list of file descriptors which are not to close. This list shall be sorted in ascending order with its end marked by -1. */ void close_all_fds (int first, int *except); /* Returns an array with all currently open file descriptors. The end of the array is marked by -1. The caller needs to release this array using the *standard free* and not with xfree. This allow the use of this function right at startup even before libgcrypt has been initialized. Returns NULL on error and sets ERRNO accordingly. */ int *get_all_open_fds (void); /* Portable function to create a pipe. Under Windows the write end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock); /* Portable function to create a pipe. Under Windows the read end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock); /* Portable function to create a pipe. Under Windows both ends are inheritable. */ gpg_error_t gnupg_create_pipe (int filedes[2]); #define GNUPG_SPAWN_NONBLOCK 16 #define GNUPG_SPAWN_RUN_ASFW 64 #define GNUPG_SPAWN_DETACHED 128 /* Fork and exec the program PGMNAME. If R_INFP is NULL connect stdin of the new process to /dev/null; if it is not NULL store the address of a pointer to a new estream there. If R_OUTFP is NULL connect stdout of the new process to /dev/null; if it is not NULL store the address of a pointer to a new estream there. If R_ERRFP is NULL connect stderr of the new process to /dev/null; if it is not NULL store the address of a pointer to a new estream there. On success the pid of the new process is stored at PID. On error -1 is stored at PID and if R_OUTFP or R_ERRFP are not NULL, NULL is stored there. The arguments for the process are expected in the NULL terminated array ARGV. The program name itself should not be included there. If PREEXEC is not NULL, the given function will be called right before the exec. IF EXCEPT is not NULL, it is expected to be an ordered list of file descriptors, terminated by an entry with the value (-1). These file descriptors won't be closed before spawning a new program. Returns 0 on success or an error code. Calling gnupg_wait_process and gnupg_release_process is required if the function succeeded. FLAGS is a bit vector: GNUPG_SPAWN_NONBLOCK If set the two output streams are created in non-blocking mode and the input stream is switched to non-blocking mode. This is merely a convenience feature because the caller could do the same with gpgrt_set_nonblock. Does not yet work for Windows. GNUPG_SPAWN_DETACHED If set the process will be started as a background process. This flag is only useful under W32 (but not W32CE) systems, so that no new console is created and pops up a console window when starting the server. Does not work on W32CE. GNUPG_SPAWN_RUN_ASFW On W32 (but not on W32CE) run AllowSetForegroundWindow for the child. Note that due to unknown problems this actually - allows SetForegroundWindow for all childs of this process. + allows SetForegroundWindow for all children of this process. */ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], int *execpt, void (*preexec)(void), unsigned int flags, estream_t *r_infp, estream_t *r_outfp, estream_t *r_errfp, pid_t *pid); /* Simplified version of gnupg_spawn_process. This function forks and then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout and ERRFD to stderr (any of them may be -1 to connect them to /dev/null). The arguments for the process are expected in the NULL terminated array ARGV. The program name itself should not be included there. Calling gnupg_wait_process and gnupg_release_process is required. Returns 0 on success or an error code. */ gpg_error_t gnupg_spawn_process_fd (const char *pgmname, const char *argv[], int infd, int outfd, int errfd, pid_t *pid); /* If HANG is true, waits for the process identified by PID to exit; if HANG is false, checks whether the process has terminated. PGMNAME should be the same as supplied to the spawn function and is only used for diagnostics. Return values: 0 The process exited successful. 0 is stored at R_EXITCODE. GPG_ERR_GENERAL The process exited without success. The exit code of process is then stored at R_EXITCODE. An exit code of -1 indicates that the process terminated abnormally (e.g. due to a signal). GPG_ERR_TIMEOUT The process is still running (returned only if HANG is false). GPG_ERR_INV_VALUE An invalid PID has been specified. Other error codes may be returned as well. Unless otherwise noted, -1 will be stored at R_EXITCODE. R_EXITCODE may be passed as NULL - if the exit code is not required (in that case an error messge will + if the exit code is not required (in that case an error message will be printed). Note that under Windows PID is not the process id but the handle of the process. */ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode); /* Like gnupg_wait_process, but for COUNT processes. */ gpg_error_t gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, int hang, int *r_exitcodes); /* Kill a process; that is send an appropriate signal to the process. gnupg_wait_process must be called to actually remove the process from the system. An invalid PID is ignored. */ void gnupg_kill_process (pid_t pid); /* Release the process identified by PID. This function is actually only required for Windows but it does not harm to always call it. It is a nop if PID is invalid. */ void gnupg_release_process (pid_t pid); /* Spawn a new process and immediately detach from it. The name of the program to exec is PGMNAME and its arguments are in ARGV (the programname is automatically passed as first argument). Environment strings in ENVP are set. An error is returned if pgmname is not executable; to make this work it is necessary to provide an absolute file name. */ gpg_error_t gnupg_spawn_process_detached (const char *pgmname, const char *argv[], const char *envp[] ); #endif /*GNUPG_COMMON_EXECHELP_H*/ diff --git a/common/exectool.c b/common/exectool.c index c9e00205a..3458de4a7 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -1,650 +1,650 @@ /* exectool.c - Utility functions to execute a helper tool * Copyright (C) 2015 Werner Koch * Copyright (C) 2016 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 . */ #include #include #include #include #include #include #include #include #include #include "i18n.h" #include "logging.h" #include "membuf.h" #include "mischelp.h" #include "exechelp.h" #include "sysutils.h" #include "util.h" #include "exectool.h" typedef struct { const char *pgmname; exec_tool_status_cb_t status_cb; void *status_cb_value; int cont; size_t used; size_t buffer_size; char *buffer; } read_and_log_buffer_t; static inline gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static void read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr) { gpg_error_t err; int c; if (!fderr) { /* Flush internal buffer. */ if (state->used) { const char *pname; int len; state->buffer[state->used] = 0; state->used = 0; pname = strrchr (state->pgmname, '/'); if (pname && pname != state->pgmname && pname[1]) pname++; else pname = state->pgmname; len = strlen (pname); if (state->status_cb && !strncmp (state->buffer, "[GNUPG:] ", 9) && state->buffer[9] >= 'A' && state->buffer[9] <= 'Z') { char *rest; rest = strchr (state->buffer + 9, ' '); if (!rest) { /* Set REST to an empty string. */ rest = state->buffer + strlen (state->buffer); } else { *rest++ = 0; trim_spaces (rest); } state->status_cb (state->status_cb_value, state->buffer + 9, rest); } else if (!state->cont && !strncmp (state->buffer, pname, len) && strlen (state->buffer) > strlen (pname) && state->buffer[len] == ':' ) { /* PGMNAME plus colon is identical to the start of the output: print only the output. */ log_info ("%s\n", state->buffer); } else log_info ("%s%c %s\n", pname, state->cont? '+':':', state->buffer); } state->cont = 0; return; } for (;;) { c = es_fgetc (fderr->stream); if (c == EOF) { if (es_feof (fderr->stream)) { fderr->ignore = 1; /* Not anymore needed. */ } else if (es_ferror (fderr->stream)) { err = my_error_from_syserror (); log_error ("error reading stderr of '%s': %s\n", state->pgmname, gpg_strerror (err)); fderr->ignore = 1; /* Disable. */ } break; } else if (c == '\n') { read_and_log_stderr (state, NULL); } else { if (state->used >= state->buffer_size - 1) { if (state->status_cb) { /* A status callback requires that we have a full * line. Thus we need to enlarget the buffer in * this case. */ char *newbuffer; size_t newsize = state->buffer_size + 256; newbuffer = xtrymalloc (newsize); if (!newbuffer) { log_error ("error allocating memory for status cb: %s\n", gpg_strerror (my_error_from_syserror ())); /* We better disable the status CB in this case. */ state->status_cb = NULL; read_and_log_stderr (state, NULL); state->cont = 1; } else { memcpy (newbuffer, state->buffer, state->used); xfree (state->buffer); state->buffer = newbuffer; state->buffer_size = newsize; } } else { read_and_log_stderr (state, NULL); state->cont = 1; } } state->buffer[state->used++] = c; } } } /* A buffer to copy from one stream to another. */ struct copy_buffer { char buffer[4096]; char *writep; size_t nread; }; /* Initialize a copy buffer. */ static void copy_buffer_init (struct copy_buffer *c) { c->writep = c->buffer; c->nread = 0; } /* Securely wipe a copy buffer. */ static void copy_buffer_shred (struct copy_buffer *c) { if (c == NULL) return; wipememory (c->buffer, sizeof c->buffer); c->writep = NULL; c->nread = ~0U; } /* Copy data from SOURCE to SINK using copy buffer C. */ static gpg_error_t copy_buffer_do_copy (struct copy_buffer *c, estream_t source, estream_t sink) { gpg_error_t err; size_t nwritten = 0; if (c->nread == 0) { c->writep = c->buffer; if (es_read (source, c->buffer, sizeof c->buffer, &c->nread)) { err = my_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EAGAIN) return 0; /* We will just retry next time. */ return err; } log_assert (c->nread <= sizeof c->buffer); } if (c->nread == 0) return 0; /* Done copying. */ nwritten = 0; if (sink && es_write (sink, c->writep, c->nread, &nwritten)) err = my_error_from_syserror (); else err = 0; log_assert (nwritten <= c->nread); c->writep += nwritten; c->nread -= nwritten; log_assert (c->writep - c->buffer <= sizeof c->buffer); if (err) { if (gpg_err_code (err) == GPG_ERR_EAGAIN) return 0; /* We will just retry next time. */ return err; } if (sink && es_fflush (sink) && errno != EAGAIN) err = my_error_from_syserror (); return err; } /* Flush the remaining data to SINK. */ static gpg_error_t copy_buffer_flush (struct copy_buffer *c, estream_t sink) { gpg_error_t err = 0; size_t nwritten = 0; if (es_write (sink, c->writep, c->nread, &nwritten)) err = my_error_from_syserror (); log_assert (nwritten <= c->nread); c->writep += nwritten; c->nread -= nwritten; log_assert (c->writep - c->buffer <= sizeof c->buffer); if (err) return err; if (es_fflush (sink)) err = my_error_from_syserror (); return err; } /* Run the program PGMNAME with the command line arguments given in * the NULL terminates array ARGV. If INPUT is not NULL it will be * fed to stdin of the process. stderr is logged using log_info and * the process' stdout is written to OUTPUT. If OUTPUT is NULL the * output is discarded. If INEXTRA is given, an additional input * stream will be passed to the child; to tell the child about this * ARGV is scanned and the first occurrence of an argument * "-&@INEXTRA@" is replaced by the concatenation of "-&" and the * child's file descriptor of the pipe created for the INEXTRA stream. * * On error a diagnostic is printed and an error code returned. */ gpg_error_t gnupg_exec_tool_stream (const char *pgmname, const char *argv[], estream_t input, estream_t inextra, estream_t output, exec_tool_status_cb_t status_cb, void *status_cb_value) { gpg_error_t err; pid_t pid = (pid_t) -1; estream_t infp = NULL; estream_t extrafp = NULL; estream_t outfp = NULL, errfp = NULL; es_poll_t fds[4]; int exceptclose[2]; int extrapipe[2] = {-1, -1}; char extrafdbuf[20]; const char *argsave = NULL; int argsaveidx; int count; read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); cpbuf_in = xtrymalloc (sizeof *cpbuf_in); if (cpbuf_in == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_in); cpbuf_out = xtrymalloc (sizeof *cpbuf_out); if (cpbuf_out == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_out); cpbuf_extra = xtrymalloc (sizeof *cpbuf_extra); if (cpbuf_extra == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_extra); fderrstate.pgmname = pgmname; fderrstate.status_cb = status_cb; fderrstate.status_cb_value = status_cb_value; fderrstate.buffer_size = 256; fderrstate.buffer = xtrymalloc (fderrstate.buffer_size); if (!fderrstate.buffer) { err = my_error_from_syserror (); goto leave; } if (inextra) { err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1); if (err) { log_error ("error running outbound pipe for extra fp: %s\n", gpg_strerror (err)); goto leave; } exceptclose[0] = extrapipe[0]; /* Do not close in child. */ exceptclose[1] = -1; /* Now find the argument marker and replace by the pipe's fd. Yeah, that is an ugly non-thread safe hack but it safes us to create a copy of the array. */ #ifdef HAVE_W32_SYSTEM snprintf (extrafdbuf, sizeof extrafdbuf, "-&%lu", (unsigned long)(void*)_get_osfhandle (extrapipe[0])); #else snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]); #endif for (argsaveidx=0; argv[argsaveidx]; argsaveidx++) if (!strcmp (argv[argsaveidx], "-&@INEXTRA@")) { argsave = argv[argsaveidx]; argv[argsaveidx] = extrafdbuf; break; } } else exceptclose[0] = -1; err = gnupg_spawn_process (pgmname, argv, exceptclose, NULL, GNUPG_SPAWN_NONBLOCK, input? &infp : NULL, &outfp, &errfp, &pid); if (extrapipe[0] != -1) close (extrapipe[0]); if (argsave) argv[argsaveidx] = argsave; if (err) { log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[0].stream = infp; fds[0].want_write = 1; if (!input) fds[0].ignore = 1; fds[1].stream = outfp; fds[1].want_read = 1; fds[2].stream = errfp; fds[2].want_read = 1; fds[3].stream = extrafp; fds[3].want_write = 1; if (!inextra) fds[3].ignore = 1; /* Now read as long as we have something to poll. We continue reading even after EOF or error on stdout so that we get the - other error messages or remaining outut. */ + other error messages or remaining output. */ while (! (fds[1].ignore && fds[2].ignore)) { count = es_poll (fds, DIM(fds), -1); if (count == -1) { err = my_error_from_syserror (); log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (!count) { log_debug ("unexpected timeout while polling '%s'\n", pgmname); break; } if (fds[0].got_write) { err = copy_buffer_do_copy (cpbuf_in, input, fds[0].stream); if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (input)) { err = copy_buffer_flush (cpbuf_in, fds[0].stream); if (gpg_err_code (err) == GPG_ERR_EAGAIN) continue; /* Retry next time. */ if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[0].ignore = 1; /* ready. */ es_fclose (infp); infp = NULL; } } if (fds[3].got_write) { log_assert (inextra); err = copy_buffer_do_copy (cpbuf_extra, inextra, fds[3].stream); if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (inextra)) { err = copy_buffer_flush (cpbuf_extra, fds[3].stream); if (gpg_err_code (err) == GPG_ERR_EAGAIN) continue; /* Retry next time. */ if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[3].ignore = 1; /* ready. */ es_fclose (extrafp); extrafp = NULL; } } if (fds[1].got_read) { err = copy_buffer_do_copy (cpbuf_out, fds[1].stream, output); if (err) { log_error ("error reading data from '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (fds[1].stream)) { err = copy_buffer_flush (cpbuf_out, output); if (err) { log_error ("error reading data from '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[1].ignore = 1; /* ready. */ } } if (fds[2].got_read) read_and_log_stderr (&fderrstate, fds + 2); } read_and_log_stderr (&fderrstate, NULL); /* Flush. */ es_fclose (infp); infp = NULL; es_fclose (extrafp); extrafp = NULL; es_fclose (outfp); outfp = NULL; es_fclose (errfp); errfp = NULL; err = gnupg_wait_process (pgmname, pid, 1, NULL); pid = (pid_t)(-1); leave: if (err && pid != (pid_t) -1) gnupg_kill_process (pid); es_fclose (infp); es_fclose (extrafp); es_fclose (outfp); es_fclose (errfp); if (pid != (pid_t)(-1)) gnupg_wait_process (pgmname, pid, 1, NULL); gnupg_release_process (pid); copy_buffer_shred (cpbuf_in); xfree (cpbuf_in); copy_buffer_shred (cpbuf_out); xfree (cpbuf_out); copy_buffer_shred (cpbuf_extra); xfree (cpbuf_extra); xfree (fderrstate.buffer); return err; } /* A dummy free function to pass to 'es_mopen'. */ static void nop_free (void *ptr) { (void) ptr; } /* Run the program PGMNAME with the command line arguments given in the NULL terminates array ARGV. If INPUT_STRING is not NULL it will be fed to stdin of the process. stderr is logged using log_info and the process' stdout is returned in a newly malloced buffer RESULT with the length stored at RESULTLEN if not given as NULL. A hidden Nul is appended to the output. On error NULL is stored at RESULT, a diagnostic is printed, and an error code returned. */ gpg_error_t gnupg_exec_tool (const char *pgmname, const char *argv[], const char *input_string, char **result, size_t *resultlen) { gpg_error_t err; estream_t input = NULL; estream_t output; size_t len; size_t nread; *result = NULL; if (resultlen) *resultlen = 0; if (input_string) { len = strlen (input_string); input = es_mopen ((char *) input_string, len, len, 0 /* don't grow */, NULL, nop_free, "rb"); if (! input) return my_error_from_syserror (); } output = es_fopenmem (0, "wb"); if (! output) { err = my_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (pgmname, argv, input, NULL, output, NULL, NULL); if (err) goto leave; len = es_ftello (output); err = es_fseek (output, 0, SEEK_SET); if (err) goto leave; *result = xtrymalloc (len + 1); if (!*result) { err = my_error_from_syserror (); goto leave; } if (len) { if (es_read (output, *result, len, &nread)) { err = my_error_from_syserror (); goto leave; } if (nread != len) log_fatal ("%s: short read from memstream\n", __func__); } (*result)[len] = 0; if (resultlen) *resultlen = len; leave: es_fclose (input); es_fclose (output); if (err) { xfree (*result); *result = NULL; } return err; } diff --git a/common/init.h b/common/init.h index 28462a729..3a5becaeb 100644 --- a/common/init.h +++ b/common/init.h @@ -1,47 +1,47 @@ -/* init.h - Definitions for init fucntions. +/* init.h - Definitions for init functions. * Copyright (C) 2007, 2012 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_INIT_H #define GNUPG_COMMON_INIT_H #ifndef GPG_ERR_SOURCE_DEFAULT # error GPG_ERR_SOURCE_DEFAULT is not defined #elseif GPG_ERR_SOURCE_DEFAULT == GPG_ERR_SOURCE_UNKNOWN # error GPG_ERR_SOURCE_DEFAULT has default value #endif void register_mem_cleanup_func (void (*func)(void)); void early_system_init (void); void _init_common_subsystems (gpg_err_source_t errsource, int *argcp, char ***argvp); #define init_common_subsystems(a,b) \ _init_common_subsystems (GPG_ERR_SOURCE_DEFAULT, (a), (b)) #endif /*GNUPG_COMMON_INIT_H*/ diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index e7c68f290..d800e7d57 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -1,443 +1,443 @@ /* openpgp-oids.c - OID helper for OpenPGP * Copyright (C) 2011 Free Software Foundation, Inc. * Copyright (C) 2013 Werner Koch * * 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 . */ #include #include #include #include #include #include "util.h" #include "openpgpdefs.h" /* A table with all our supported OpenPGP curves. */ static struct { const char *name; /* Standard name. */ const char *oidstr; /* IETF formatted OID. */ unsigned int nbits; /* Nominal bit length of the curve. */ const char *alias; /* NULL or alternative name of the curve. */ int pubkey_algo; /* Required OpenPGP algo or 0 for ECDSA/ECDH. */ } oidtable[] = { { "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", PUBKEY_ALGO_ECDH }, { "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", PUBKEY_ALGO_EDDSA }, { "NIST P-256", "1.2.840.10045.3.1.7", 256, "nistp256" }, { "NIST P-384", "1.3.132.0.34", 384, "nistp384" }, { "NIST P-521", "1.3.132.0.35", 521, "nistp521" }, { "brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 256 }, { "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384 }, { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512 }, { "secp256k1", "1.3.132.0.10", 256 }, { NULL, NULL, 0} }; /* The OID for Curve Ed25519 in OpenPGP format. */ static const char oid_ed25519[] = { 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01 }; /* The OID for Curve25519 in OpenPGP format. */ static const char oid_cv25519[] = { 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01 }; /* Helper for openpgp_oid_from_str. */ static size_t make_flagged_int (unsigned long value, char *buf, size_t buflen) { int more = 0; int shift; /* fixme: figure out the number of bits in an ulong and start with that value as shift (after making it a multiple of 7) a more straigtforward implementation is to do it in reverse order using a temporary buffer - saves a lot of compares */ for (more=0, shift=28; shift > 0; shift -= 7) { if (more || value >= (1<> shift); value -= (value >> shift) << shift; more = 1; } } buf[buflen++] = value; return buflen; } /* Convert the OID given in dotted decimal form in STRING to an DER * encoding and store it as an opaque value at R_MPI. The format of * the DER encoded is not a regular ASN.1 object but the modified * format as used by OpenPGP for the ECC curve description. On error * the function returns and error code an NULL is stored at R_BUG. * Note that scanning STRING stops at the first white space * character. */ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi) { unsigned char *buf; size_t buflen; unsigned long val1, val; const char *endp; int arcno; *r_mpi = NULL; if (!string || !*string) return gpg_error (GPG_ERR_INV_VALUE); /* We can safely assume that the encoded OID is shorter than the string. */ buf = xtrymalloc (1 + strlen (string) + 2); if (!buf) return gpg_error_from_syserror (); /* Save the first byte for the length. */ buflen = 1; val1 = 0; /* Avoid compiler warning. */ arcno = 0; do { arcno++; val = strtoul (string, (char**)&endp, 10); if (!digitp (string) || !(*endp == '.' || !*endp)) { xfree (buf); return gpg_error (GPG_ERR_INV_OID_STRING); } if (*endp == '.') string = endp+1; if (arcno == 1) { if (val > 2) - break; /* Not allowed, error catched below. */ + break; /* Not allowed, error caught below. */ val1 = val; } else if (arcno == 2) { /* Need to combine the first two arcs in one octet. */ if (val1 < 2) { if (val > 39) { xfree (buf); return gpg_error (GPG_ERR_INV_OID_STRING); } buf[buflen++] = val1*40 + val; } else { val += 80; buflen = make_flagged_int (val, buf, buflen); } } else { buflen = make_flagged_int (val, buf, buflen); } } while (*endp == '.'); if (arcno == 1 || buflen < 2 || buflen > 254 ) { /* It is not possible to encode only the first arc. */ xfree (buf); return gpg_error (GPG_ERR_INV_OID_STRING); } *buf = buflen - 1; *r_mpi = gcry_mpi_set_opaque (NULL, buf, buflen * 8); if (!*r_mpi) { xfree (buf); return gpg_error_from_syserror (); } return 0; } /* Return a malloced string represenation of the OID in the opaque MPI A. In case of an error NULL is returned and ERRNO is set. */ char * openpgp_oid_to_str (gcry_mpi_t a) { const unsigned char *buf; size_t length; unsigned int lengthi; char *string, *p; int n = 0; unsigned long val, valmask; valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1)); if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE) || !(buf = gcry_mpi_get_opaque (a, &lengthi))) { gpg_err_set_errno (EINVAL); return NULL; } buf = gcry_mpi_get_opaque (a, &lengthi); length = (lengthi+7)/8; /* The first bytes gives the length; check consistency. */ if (!length || buf[0] != length -1) { gpg_err_set_errno (EINVAL); return NULL; } /* Skip length byte. */ length--; buf++; /* To calculate the length of the string we can safely assume an upper limit of 3 decimal characters per byte. Two extra bytes account for the special first octect */ string = p = xtrymalloc (length*(1+3)+2+1); if (!string) return NULL; if (!length) { *p = 0; return string; } if (buf[0] < 40) p += sprintf (p, "0.%d", buf[n]); else if (buf[0] < 80) p += sprintf (p, "1.%d", buf[n]-40); else { val = buf[n] & 0x7f; while ( (buf[n]&0x80) && ++n < length ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ val <<= 7; val |= buf[n] & 0x7f; } if (val < 80) goto badoid; val -= 80; sprintf (p, "2.%lu", val); p += strlen (p); } for (n++; n < length; n++) { val = buf[n] & 0x7f; while ( (buf[n]&0x80) && ++n < length ) { if ( (val & valmask) ) goto badoid; /* Overflow. */ val <<= 7; val |= buf[n] & 0x7f; } sprintf (p, ".%lu", val); p += strlen (p); } *p = 0; return string; badoid: /* Return a special OID (gnu.gnupg.badoid) to indicate the error case. The OID is broken and thus we return one which can't do any harm. Formally this does not need to be a bad OID but an OID with an arc that can't be represented in a 32 bit word is more than likely corrupt. */ xfree (string); return xtrystrdup ("1.3.6.1.4.1.11591.2.12242973"); } /* Return true if A represents the OID for Ed25519. */ int openpgp_oid_is_ed25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); n = (nbits+7)/8; return (n == DIM (oid_ed25519) && !memcmp (buf, oid_ed25519, DIM (oid_ed25519))); } int openpgp_oid_is_cv25519 (gcry_mpi_t a) { const unsigned char *buf; unsigned int nbits; size_t n; if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) return 0; buf = gcry_mpi_get_opaque (a, &nbits); n = (nbits+7)/8; return (n == DIM (oid_cv25519) && !memcmp (buf, oid_cv25519, DIM (oid_cv25519))); } /* Map the Libgcrypt ECC curve NAME to an OID. If R_NBITS is not NULL store the bit size of the curve there. Returns NULL for unknown curve names. */ const char * openpgp_curve_to_oid (const char *name, unsigned int *r_nbits) { int i; unsigned int nbits = 0; const char *oidstr = NULL; if (name) { for (i=0; oidtable[i].name; i++) if (!strcmp (oidtable[i].name, name) || (oidtable[i].alias && !strcmp (oidtable[i].alias, name))) { oidstr = oidtable[i].oidstr; nbits = oidtable[i].nbits; break; } if (!oidtable[i].name) { /* If not found assume the input is already an OID and check whether we support it. */ for (i=0; oidtable[i].name; i++) if (!strcmp (name, oidtable[i].oidstr)) { oidstr = oidtable[i].oidstr; nbits = oidtable[i].nbits; break; } } } if (r_nbits) *r_nbits = nbits; return oidstr; } /* Map an OpenPGP OID to the Libgcrypt curve NAME. Returns NULL for unknown curve names. Unless CANON is set we prefer an alias name here which is more suitable for printing. */ const char * openpgp_oid_to_curve (const char *oidstr, int canon) { int i; if (!oidstr) return NULL; for (i=0; oidtable[i].name; i++) if (!strcmp (oidtable[i].oidstr, oidstr)) return !canon && oidtable[i].alias? oidtable[i].alias : oidtable[i].name; return NULL; } /* Return true if the curve with NAME is supported. */ static int curve_supported_p (const char *name) { int result = 0; gcry_sexp_t keyparms; if (!gcry_sexp_build (&keyparms, NULL, "(public-key(ecc(curve %s)))", name)) { result = !!gcry_pk_get_curve (keyparms, 0, NULL); gcry_sexp_release (keyparms); } return result; } /* Enumerate available and supported OpenPGP curves. The caller needs to set the integer variable at ITERP to zero and keep on calling this function until NULL is returned. */ const char * openpgp_enum_curves (int *iterp) { int idx = *iterp; while (idx >= 0 && idx < DIM (oidtable) && oidtable[idx].name) { if (curve_supported_p (oidtable[idx].name)) { *iterp = idx + 1; return oidtable[idx].alias? oidtable[idx].alias : oidtable[idx].name; } idx++; } *iterp = idx; return NULL; } /* Return the Libgcrypt name for the gpg curve NAME if supported. If * R_ALGO is not NULL the required OpenPGP public key algo or 0 is * stored at that address. If R_NBITS is not NULL the nominal bitsize * of the curves is stored there. NULL is returned if the curve is * not supported. */ const char * openpgp_is_curve_supported (const char *name, int *r_algo, unsigned int *r_nbits) { int idx; if (r_algo) *r_algo = 0; if (r_nbits) *r_nbits = 0; for (idx = 0; idx < DIM (oidtable) && oidtable[idx].name; idx++) { if ((!strcmp (name, oidtable[idx].name) || (oidtable[idx].alias && !strcmp (name, (oidtable[idx].alias)))) && curve_supported_p (oidtable[idx].name)) { if (r_algo) *r_algo = oidtable[idx].pubkey_algo; if (r_nbits) *r_nbits = oidtable[idx].nbits; return oidtable[idx].name; } } return NULL; } diff --git a/common/recsel.c b/common/recsel.c index 66c1c5e08..b2b302b75 100644 --- a/common/recsel.c +++ b/common/recsel.c @@ -1,624 +1,624 @@ /* recsel.c - Record selection * Copyright (C) 2014, 2016 Werner Koch * * 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 . */ #include #include #include #include #include #include #include "util.h" #include "recsel.h" /* Select operators. */ typedef enum { SELECT_SAME, SELECT_SUB, SELECT_NONEMPTY, SELECT_ISTRUE, SELECT_EQ, /* Numerically equal. */ SELECT_LE, SELECT_GE, SELECT_LT, SELECT_GT, SELECT_STRLE, /* String is less or equal. */ SELECT_STRGE, SELECT_STRLT, SELECT_STRGT } select_op_t; /* Definition for a select expression. */ struct recsel_expr_s { recsel_expr_t next; select_op_t op; /* Operation code. */ unsigned int not:1; /* Negate operators. */ unsigned int disjun:1;/* Start of a disjunction. */ unsigned int xcase:1; /* String match is case sensitive. */ const char *value; /* (Points into NAME.) */ long numvalue; /* strtol of VALUE. */ char name[1]; /* Name of the property. */ }; /* Helper */ static inline gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } /* Helper */ static inline gpg_error_t my_error (gpg_err_code_t ec) { return gpg_err_make (default_errsource, ec); } /* This is a case-sensitive version of our memistr. I wonder why no * standard function memstr exists but I better do not use the name * memstr to avoid future conflicts. * * FIXME: Move this to a stringhelp.c */ static const char * my_memstr (const void *buffer, size_t buflen, const char *sub) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buf; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if (*t == *s) { for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) ; if (!*s) return (const char*)buf; t = (const unsigned char *)buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } /* Return a pointer to the next logical connection operator or NULL if * none. */ static char * find_next_lc (char *string) { char *p1, *p2; p1 = strchr (string, '&'); if (p1 && p1[1] != '&') p1 = NULL; p2 = strchr (string, '|'); if (p2 && p2[1] != '|') p2 = NULL; if (p1 && !p2) return p1; if (!p1) return p2; return p1 < p2 ? p1 : p2; } /* Parse an expression. The expression syntax is: * * [] {{} PROPNAME VALUE []} * * A [] indicates an optional part, a {} a repetition. PROPNAME and * VALUE may not be the empty string. White space between the * elements is ignored. Numerical values are computed as long int; * standard C notation applies. is the logical connection * operator; either "&&" for a conjunction or "||" for a disjunction. * A conjunction is assumed at the begin of an expression and * conjunctions have higher precedence than disjunctions. If VALUE * starts with one of the characters used in any a space after * the is required. A VALUE is terminated by an unless the * "--" is used in which case the VALUE spans to the end of the * expression. may be any of * * =~ Substring must match * !~ Substring must not match * = The full string must match * <> The full string must not match * == The numerical value must match * != The numerical value must not match * <= The numerical value of the field must be LE than the value. * < The numerical value of the field must be LT than the value. * >= The numerical value of the field must be GT than the value. * >= The numerical value of the field must be GE than the value. * -n True if value is not empty (no VALUE parameter allowed). * -z True if value is empty (no VALUE parameter allowed). * -t Alias for "PROPNAME != 0" (no VALUE parameter allowed). * -f Alias for "PROPNAME == 0" (no VALUE parameter allowed). * * Values for must be space separated and any of: * * -- VALUE spans to the end of the expression. * -c The string match in this part is done case-sensitive. * * For example four calls to recsel_parse_expr() with these values for * EXPR * * "uid =~ Alfa" * "&& uid !~ Test" * "|| uid =~ Alpha" * "uid !~ Test" * * or the equivalent expression * * "uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test" * * are making a selector for records where the "uid" property contains * the strings "Alfa" or "Alpha" but not the String "test". * * The caller must pass the address of a selector variable to this * function and initialize the value of the function to NULL before * the first call. recset_release needs to be called to free the * selector. */ gpg_error_t recsel_parse_expr (recsel_expr_t *selector, const char *expression) { recsel_expr_t se_head = NULL; recsel_expr_t se, se2; char *expr_buffer; char *expr; char *s0, *s; int toend = 0; int xcase = 0; int disjun = 0; char *next_lc = NULL; while (*expression == ' ' || *expression == '\t') expression++; expr_buffer = xtrystrdup (expression); if (!expr_buffer) return my_error_from_syserror (); expr = expr_buffer; if (*expr == '|' && expr[1] == '|') { disjun = 1; expr += 2; } else if (*expr == '&' && expr[1] == '&') expr += 2; next_term: while (*expr == ' ' || *expr == '\t') expr++; while (*expr == '-') { switch (*++expr) { case '-': toend = 1; break; case 'c': xcase = 1; break; default: log_error ("invalid flag '-%c' in expression\n", *expr); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_INV_FLAG); } expr++; while (*expr == ' ' || *expr == '\t') expr++; } next_lc = toend? NULL : find_next_lc (expr); if (next_lc) *next_lc = 0; /* Terminate this term. */ se = xtrymalloc (sizeof *se + strlen (expr)); if (!se) return my_error_from_syserror (); strcpy (se->name, expr); se->next = NULL; se->not = 0; se->disjun = disjun; se->xcase = xcase; if (!se_head) se_head = se; else { for (se2 = se_head; se2->next; se2 = se2->next) ; se2->next = se; } s = strpbrk (expr, "=<>!~-"); if (!s || s == expr ) { log_error ("no field name given in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_NO_NAME); } s0 = s; if (!strncmp (s, "=~", 2)) { se->op = SELECT_SUB; s += 2; } else if (!strncmp (s, "!~", 2)) { se->op = SELECT_SUB; se->not = 1; s += 2; } else if (!strncmp (s, "<>", 2)) { se->op = SELECT_SAME; se->not = 1; s += 2; } else if (!strncmp (s, "==", 2)) { se->op = SELECT_EQ; s += 2; } else if (!strncmp (s, "!=", 2)) { se->op = SELECT_EQ; se->not = 1; s += 2; } else if (!strncmp (s, "<=", 2)) { se->op = SELECT_LE; s += 2; } else if (!strncmp (s, ">=", 2)) { se->op = SELECT_GE; s += 2; } else if (!strncmp (s, "<", 1)) { se->op = SELECT_LT; s += 1; } else if (!strncmp (s, ">", 1)) { se->op = SELECT_GT; s += 1; } else if (!strncmp (s, "=", 1)) { se->op = SELECT_SAME; s += 1; } else if (!strncmp (s, "-z", 2)) { se->op = SELECT_NONEMPTY; se->not = 1; s += 2; } else if (!strncmp (s, "-n", 2)) { se->op = SELECT_NONEMPTY; s += 2; } else if (!strncmp (s, "-f", 2)) { se->op = SELECT_ISTRUE; se->not = 1; s += 2; } else if (!strncmp (s, "-t", 2)) { se->op = SELECT_ISTRUE; s += 2; } else if (!strncmp (s, "-le", 3)) { se->op = SELECT_STRLE; s += 3; } else if (!strncmp (s, "-ge", 3)) { se->op = SELECT_STRGE; s += 3; } else if (!strncmp (s, "-lt", 3)) { se->op = SELECT_STRLT; s += 3; } else if (!strncmp (s, "-gt", 3)) { se->op = SELECT_STRGT; s += 3; } else { log_error ("invalid operator in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_INV_OP); } /* We require that a space is used if the value starts with any of the operator characters. */ if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE) ; else if (strchr ("=<>!~", *s)) { log_error ("invalid operator in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_INV_OP); } while (*s == ' ' || *s == '\t') s++; if (se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE) { if (*s) { log_error ("value given for -n or -z\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_SYNTAX); } } else { if (!*s) { log_error ("no value given in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_MISSING_VALUE); } } se->name[s0 - expr] = 0; trim_spaces (se->name); if (!se->name[0]) { log_error ("no field name given in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_NO_NAME); } trim_spaces (se->name + (s - expr)); se->value = se->name + (s - expr); if (!se->value[0] && !(se->op == SELECT_NONEMPTY || se->op == SELECT_ISTRUE)) { log_error ("no value given in expression\n"); recsel_release (se_head); xfree (expr_buffer); return my_error (GPG_ERR_MISSING_VALUE); } se->numvalue = strtol (se->value, NULL, 0); if (next_lc) { disjun = next_lc[1] == '|'; expr = next_lc + 2; goto next_term; } /* Read:y Append to passes last selector. */ if (!*selector) *selector = se_head; else { for (se2 = *selector; se2->next; se2 = se2->next) ; se2->next = se_head; } xfree (expr_buffer); return 0; } void recsel_release (recsel_expr_t a) { while (a) { recsel_expr_t tmp = a->next; xfree (a); a = tmp; } } void recsel_dump (recsel_expr_t selector) { recsel_expr_t se; log_debug ("--- Begin selectors ---\n"); for (se = selector; se; se = se->next) { log_debug ("%s %s %s %s '%s'\n", se==selector? " ": (se->disjun? "||":"&&"), se->xcase? "-c":" ", se->name, se->op == SELECT_SAME? (se->not? "<>":"= "): se->op == SELECT_SUB? (se->not? "!~":"=~"): se->op == SELECT_NONEMPTY?(se->not? "-z":"-n"): se->op == SELECT_ISTRUE? (se->not? "-f":"-t"): se->op == SELECT_EQ? (se->not? "!=":"=="): se->op == SELECT_LT? "< ": se->op == SELECT_LE? "<=": se->op == SELECT_GT? "> ": se->op == SELECT_GE? ">=": se->op == SELECT_STRLT? "-lt": se->op == SELECT_STRLE? "-le": se->op == SELECT_STRGT? "-gt": se->op == SELECT_STRGE? "-ge": /**/ "[oops]", se->value); } log_debug ("--- End selectors ---\n"); } /* Return true if the record RECORD has been selected. The GETVAL * function is called with COOKIE and the NAME of a property used in * the expression. */ int recsel_select (recsel_expr_t selector, const char *(*getval)(void *cookie, const char *propname), void *cookie) { recsel_expr_t se; const char *value; size_t selen, valuelen; long numvalue; int result = 1; se = selector; while (se) { value = getval? getval (cookie, se->name) : NULL; if (!value) value = ""; if (!*value) { /* Field is empty. */ result = 0; } else /* Field has a value. */ { valuelen = strlen (value); numvalue = strtol (value, NULL, 0); selen = strlen (se->value); switch (se->op) { case SELECT_SAME: if (se->xcase) result = (valuelen==selen && !memcmp (value,se->value,selen)); else result = (valuelen==selen && !memicmp (value,se->value,selen)); break; case SELECT_SUB: if (se->xcase) result = !!my_memstr (value, valuelen, se->value); else result = !!memistr (value, valuelen, se->value); break; case SELECT_NONEMPTY: result = !!valuelen; break; case SELECT_ISTRUE: result = !!numvalue; break; case SELECT_EQ: result = (numvalue == se->numvalue); break; case SELECT_GT: result = (numvalue > se->numvalue); break; case SELECT_GE: result = (numvalue >= se->numvalue); break; case SELECT_LT: result = (numvalue < se->numvalue); break; case SELECT_LE: result = (numvalue <= se->numvalue); break; case SELECT_STRGT: if (se->xcase) result = strcmp (value, se->value) > 0; else result = strcasecmp (value, se->value) > 0; break; case SELECT_STRGE: if (se->xcase) result = strcmp (value, se->value) >= 0; else result = strcasecmp (value, se->value) >= 0; break; case SELECT_STRLT: if (se->xcase) result = strcmp (value, se->value) < 0; else result = strcasecmp (value, se->value) < 0; break; case SELECT_STRLE: if (se->xcase) result = strcmp (value, se->value) <= 0; else result = strcasecmp (value, se->value) <= 0; break; } } if (se->not) result = !result; if (result) { - /* This expression evaluated to true. See wether there are + /* This expression evaluated to true. See whether there are remaining expressions in this conjunction. */ if (!se->next || se->next->disjun) break; /* All expressions are true. Return True. */ se = se->next; /* Test the next. */ } else { /* This expression evaluated to false and thus the * conjunction evaluates to false. We skip over the * remaining expressions of this conjunction and continue * with the next disjunction if any. */ do se = se->next; while (se && !se->disjun); } } return result; } diff --git a/common/sexputil.c b/common/sexputil.c index a8dc1a58c..f30790aa1 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -1,579 +1,579 @@ /* sexputil.c - Utility functions for S-expressions. * Copyright (C) 2005, 2007, 2009 Free Software Foundation, Inc. * Copyright (C) 2013 Werner Koch * * 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 . */ /* This file implements a few utility functions useful when working - with canonical encrypted S-expresions (i.e. not the S-exprssion + with canonical encrypted S-expressions (i.e. not the S-exprssion objects from libgcrypt). */ #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "util.h" #include "tlv.h" #include "sexp-parse.h" #include "openpgpdefs.h" /* for pubkey_algo_t */ /* Return a malloced string with the S-expression CANON in advanced format. Returns NULL on error. */ static char * sexp_to_string (gcry_sexp_t sexp) { size_t n; char *result; if (!sexp) return NULL; n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); if (!n) return NULL; result = xtrymalloc (n); if (!result) return NULL; n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, n); if (!n) BUG (); return result; } /* Return a malloced string with the S-expression CANON in advanced format. Returns NULL on error. */ char * canon_sexp_to_string (const unsigned char *canon, size_t canonlen) { size_t n; gcry_sexp_t sexp; char *result; n = gcry_sexp_canon_len (canon, canonlen, NULL, NULL); if (!n) return NULL; if (gcry_sexp_sscan (&sexp, NULL, canon, n)) return NULL; result = sexp_to_string (sexp); gcry_sexp_release (sexp); return result; } /* Print the canonical encoded S-expression in SEXP in advanced format. SEXPLEN may be passed as 0 is SEXP is known to be valid. With TEXT of NULL print just the raw S-expression, with TEXT just an empty string, print a trailing linefeed, otherwise print an entire debug line. */ void log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen) { if (text && *text) log_debug ("%s ", text); if (sexp) { char *buf = canon_sexp_to_string (sexp, sexplen); log_printf ("%s", buf? buf : "[invalid S-expression]"); xfree (buf); } if (text) log_printf ("\n"); } /* Print the gcryp S-expression in SEXP in advanced format. With TEXT of NULL print just the raw S-expression, with TEXT just an empty string, print a trailing linefeed, otherwise print an entire debug line. */ void log_printsexp (const char *text, gcry_sexp_t sexp) { if (text && *text) log_debug ("%s ", text); if (sexp) { char *buf = sexp_to_string (sexp); log_printf ("%s", buf? buf : "[invalid S-expression]"); xfree (buf); } if (text) log_printf ("\n"); } /* Helper function to create a canonical encoded S-expression from a Libgcrypt S-expression object. The function returns 0 on success and the malloced canonical S-expression is stored at R_BUFFER and the allocated length at R_BUFLEN. On error an error code is returned and (NULL, 0) stored at R_BUFFER and R_BUFLEN. If the allocated buffer length is not required, NULL by be used for R_BUFLEN. */ gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen) { size_t len; unsigned char *buf; *r_buffer = NULL; if (r_buflen) *r_buflen = 0;; len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); if (!len) return gpg_error (GPG_ERR_BUG); buf = xtrymalloc (len); if (!buf) return gpg_error_from_syserror (); len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len); if (!len) return gpg_error (GPG_ERR_BUG); *r_buffer = buf; if (r_buflen) *r_buflen = len; return 0; } /* Same as make_canon_sexp but pad the buffer to multiple of 64 bits. If SECURE is set, secure memory will be allocated. */ gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen) { size_t len; unsigned char *buf; *r_buffer = NULL; if (r_buflen) *r_buflen = 0;; len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); if (!len) return gpg_error (GPG_ERR_BUG); len += (8 - len % 8) % 8; buf = secure? xtrycalloc_secure (1, len) : xtrycalloc (1, len); if (!buf) return gpg_error_from_syserror (); if (!gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len)) return gpg_error (GPG_ERR_BUG); *r_buffer = buf; if (r_buflen) *r_buflen = len; return 0; } /* Return the so called "keygrip" which is the SHA-1 hash of the public key parameters expressed in a way depended on the algorithm. KEY is expected to be an canonical encoded S-expression with a public or private key. KEYLEN is the length of that buffer. GRIP must be at least 20 bytes long. On success 0 is returned, on error an error code. */ gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip) { gpg_error_t err; gcry_sexp_t sexp; if (!grip) return gpg_error (GPG_ERR_INV_VALUE); err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen); if (err) return err; if (!gcry_pk_get_keygrip (sexp, grip)) err = gpg_error (GPG_ERR_INTERNAL); gcry_sexp_release (sexp); return err; } /* Compare two simple S-expressions like "(3:foo)". Returns 0 if they are identical or !0 if they are not. Note that this function can't be used for sorting. */ int cmp_simple_canon_sexp (const unsigned char *a_orig, const unsigned char *b_orig) { const char *a = (const char *)a_orig; const char *b = (const char *)b_orig; unsigned long n1, n2; char *endp; if (!a && !b) return 0; /* Both are NULL, they are identical. */ if (!a || !b) return 1; /* One is NULL, they are not identical. */ if (*a != '(' || *b != '(') log_bug ("invalid S-exp in cmp_simple_canon_sexp\n"); a++; n1 = strtoul (a, &endp, 10); a = endp; b++; n2 = strtoul (b, &endp, 10); b = endp; if (*a != ':' || *b != ':' ) log_bug ("invalid S-exp in cmp_simple_canon_sexp\n"); if (n1 != n2) return 1; /* Not the same. */ for (a++, b++; n1; n1--, a++, b++) if (*a != *b) return 1; /* Not the same. */ return 0; } /* Create a simple S-expression from the hex string at LINE. Returns a newly allocated buffer with that canonical encoded S-expression or NULL in case of an error. On return the number of characters - scanned in LINE will be stored at NSCANNED. This fucntions stops + scanned in LINE will be stored at NSCANNED. This functions stops converting at the first character not representing a hexdigit. Odd numbers of hex digits are allowed; a leading zero is then assumed. If no characters have been found, NULL is returned.*/ unsigned char * make_simple_sexp_from_hexstr (const char *line, size_t *nscanned) { size_t n, len; const char *s; unsigned char *buf; unsigned char *p; char numbuf[50], *numbufp; size_t numbuflen; for (n=0, s=line; hexdigitp (s); s++, n++) ; if (nscanned) *nscanned = n; if (!n) return NULL; len = ((n+1) & ~0x01)/2; numbufp = smklen (numbuf, sizeof numbuf, len, &numbuflen); buf = xtrymalloc (1 + numbuflen + len + 1 + 1); if (!buf) return NULL; buf[0] = '('; p = (unsigned char *)stpcpy ((char *)buf+1, numbufp); s = line; if ((n&1)) { *p++ = xtoi_1 (s); s++; n--; } for (; n > 1; n -=2, s += 2) *p++ = xtoi_2 (s); *p++ = ')'; *p = 0; /* (Not really neaded.) */ return buf; } /* Return the hash algorithm from a KSBA sig-val. SIGVAL is a canonical encoded S-expression. Return 0 if the hash algorithm is not encoded in SIG-VAL or it is not supported by libgcrypt. */ int hash_algo_from_sigval (const unsigned char *sigval) { const unsigned char *s = sigval; size_t n; int depth; char buffer[50]; if (!s || *s != '(') return 0; /* Invalid S-expression. */ s++; n = snext (&s); if (!n) return 0; /* Invalid S-expression. */ if (!smatch (&s, n, "sig-val")) return 0; /* Not a sig-val. */ if (*s != '(') return 0; /* Invalid S-expression. */ s++; /* Skip over the algo+parameter list. */ depth = 1; if (sskip (&s, &depth) || depth) return 0; /* Invalid S-expression. */ if (*s != '(') return 0; /* No further list. */ /* Check whether this is (hash ALGO). */ s++; n = snext (&s); if (!n) return 0; /* Invalid S-expression. */ if (!smatch (&s, n, "hash")) return 0; /* Not a "hash" keyword. */ n = snext (&s); if (!n || n+1 >= sizeof (buffer)) return 0; /* Algorithm string is missing or too long. */ memcpy (buffer, s, n); buffer[n] = 0; return gcry_md_map_name (buffer); } /* Create a public key S-expression for an RSA public key from the modulus M with length MLEN and the public exponent E with length ELEN. Returns a newly allocated buffer of NULL in case of a memory allocation problem. If R_LEN is not NULL, the length of the canonical S-expression is stored there. */ unsigned char * make_canon_sexp_from_rsa_pk (const void *m_arg, size_t mlen, const void *e_arg, size_t elen, size_t *r_len) { const unsigned char *m = m_arg; const unsigned char *e = e_arg; int m_extra = 0; int e_extra = 0; char mlen_str[35]; char elen_str[35]; unsigned char *keybuf, *p; const char part1[] = "(10:public-key(3:rsa(1:n"; const char part2[] = ")(1:e"; const char part3[] = ")))"; /* Remove leading zeroes. */ for (; mlen && !*m; mlen--, m++) ; for (; elen && !*e; elen--, e++) ; /* Insert a leading zero if the number would be zero or interpreted as negative. */ if (!mlen || (m[0] & 0x80)) m_extra = 1; if (!elen || (e[0] & 0x80)) e_extra = 1; /* Build the S-expression. */ snprintf (mlen_str, sizeof mlen_str, "%u:", (unsigned int)mlen+m_extra); snprintf (elen_str, sizeof elen_str, "%u:", (unsigned int)elen+e_extra); keybuf = xtrymalloc (strlen (part1) + strlen (mlen_str) + mlen + m_extra + strlen (part2) + strlen (elen_str) + elen + e_extra + strlen (part3) + 1); if (!keybuf) return NULL; p = stpcpy (keybuf, part1); p = stpcpy (p, mlen_str); if (m_extra) *p++ = 0; memcpy (p, m, mlen); p += mlen; p = stpcpy (p, part2); p = stpcpy (p, elen_str); if (e_extra) *p++ = 0; memcpy (p, e, elen); p += elen; p = stpcpy (p, part3); if (r_len) *r_len = p - keybuf; return keybuf; } /* Return the parameters of a public RSA key expressed as an canonical encoded S-expression. */ gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen) { gpg_error_t err; const unsigned char *buf, *tok; size_t buflen, toklen; int depth, last_depth1, last_depth2; const unsigned char *rsa_n = NULL; const unsigned char *rsa_e = NULL; size_t rsa_n_len, rsa_e_len; *r_n = NULL; *r_nlen = 0; *r_e = NULL; *r_elen = 0; buf = keydata; buflen = keydatalen; depth = 0; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen)) return gpg_error (GPG_ERR_BAD_PUBKEY); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (tok && toklen == 1) { const unsigned char **mpi; size_t *mpi_len; switch (*tok) { case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; default: mpi = NULL; mpi_len = NULL; break; } if (mpi && *mpi) return gpg_error (GPG_ERR_DUP_VALUE); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (tok && mpi) { /* Strip off leading zero bytes and save. */ for (;toklen && !*tok; toklen--, tok++) ; *mpi = tok; *mpi_len = toklen; } } /* Skip to the end of the list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) return err; } if (err) return err; if (!rsa_n || !rsa_n_len || !rsa_e || !rsa_e_len) return gpg_error (GPG_ERR_BAD_PUBKEY); *r_n = rsa_n; *r_nlen = rsa_n_len; *r_e = rsa_e; *r_elen = rsa_e_len; return 0; } /* Return the algo of a public KEY of SEXP. */ int get_pk_algo_from_key (gcry_sexp_t key) { gcry_sexp_t list; const char *s; size_t n; char algoname[6]; int algo = 0; list = gcry_sexp_nth (key, 1); if (!list) goto out; s = gcry_sexp_nth_data (list, 0, &n); if (!s) goto out; if (n >= sizeof (algoname)) goto out; memcpy (algoname, s, n); algoname[n] = 0; algo = gcry_pk_map_name (algoname); if (algo == GCRY_PK_ECC) { gcry_sexp_t l1 = gcry_sexp_find_token (list, "flags", 0); int i; for (i = l1 ? gcry_sexp_length (l1)-1 : 0; i > 0; i--) { s = gcry_sexp_nth_data (l1, i, &n); if (!s) continue; /* Not a data element. */ if (n == 5 && !memcmp (s, "eddsa", 5)) { algo = GCRY_PK_EDDSA; break; } } gcry_sexp_release (l1); } out: gcry_sexp_release (list); return algo; } /* This is a variant of get_pk_algo_from_key but takes an canonical * encoded S-expression as input. Returns a GCRYPT public key * identiier or 0 on error. */ int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen) { gcry_sexp_t sexp; int algo; if (gcry_sexp_sscan (&sexp, NULL, keydata, keydatalen)) return 0; algo = get_pk_algo_from_key (sexp); gcry_sexp_release (sexp); return algo; } diff --git a/common/simple-pwquery.h b/common/simple-pwquery.h index 772aa398c..9bbc53f50 100644 --- a/common/simple-pwquery.h +++ b/common/simple-pwquery.h @@ -1,70 +1,70 @@ -/* simple-pwquery.c - A simple password query cleint for gpg-agent +/* simple-pwquery.c - A simple password query client for gpg-agent * Copyright (C) 2002 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 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 . */ #ifndef SIMPLE_PWQUERY_H #define SIMPLE_PWQUERY_H #ifdef SIMPLE_PWQUERY_IMPLEMENTATION /* Begin configuration stuff. */ /* Include whatever files you need. */ #include #include "../common/logging.h" /* Try to write error message using the standard gnupg log mechanism. */ #define SPWQ_USE_LOGGING 1 /* Memory allocation functions used by the implementation. Note, that the returned value is expected to be freed with spwq_secure_free. */ #define spwq_malloc(a) gcry_malloc (a) #define spwq_free(a) gcry_free (a) #define spwq_secure_malloc(a) gcry_malloc_secure (a) #define spwq_secure_free(a) gcry_free (a) #endif /*SIMPLE_PWQUERY_IMPLEMENTATION*/ /* End configuration stuff. */ /* Ask the gpg-agent for a passphrase and present the user with a DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text. If a CACHEID is not NULL it is used to locate the passphrase in the cache and store it under this ID. If OPT_CHECK is true gpg-agent is asked to apply some checks on the passphrase security. If ERRORCODE is not NULL it should point a variable receiving an errorcode; this errocode might be 0 if the user canceled the operation. The function returns NULL to indicate an error. */ char *simple_pwquery (const char *cacheid, const char *tryagain, const char *prompt, const char *description, int opt_check, int *errorcode); /* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */ int simple_pwclear (const char *cacheid); /* Perform the simple query QUERY (which must be new-line and 0 terminated) and return the error code. */ int simple_query (const char *query); /* Set the name of the standard socket to be used if GPG_AGENT_INFO is not defined. The use of this function is optional but if it needs to be called before any other function. Returns 0 on success. */ int simple_pw_set_socket (const char *name); #endif /*SIMPLE_PWQUERY_H*/ diff --git a/common/sysutils.c b/common/sysutils.c index a796677ba..ea0acdb3e 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -1,1294 +1,1294 @@ /* sysutils.c - system helpers * Copyright (C) 1991-2001, 2003-2004, * 2006-2008 Free Software Foundation, Inc. * Copyright (C) 2013-2016 Werner Koch * * 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 . */ #include #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ # undef HAVE_NPTH # undef USE_NPTH #endif #include #include #include #include #include #include #ifdef HAVE_STAT # include #endif #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 # include # include #endif #include #ifdef HAVE_SETRLIMIT # include # include #endif #ifdef HAVE_W32_SYSTEM # if WINVER < 0x0500 # define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */ # endif # ifdef HAVE_WINSOCK2_H # include # endif # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif #ifdef HAVE_INOTIFY_INIT # include #endif /*HAVE_INOTIFY_INIT*/ #ifdef HAVE_NPTH # include #endif #include #include #include "util.h" #include "i18n.h" #include "sysutils.h" #define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) /* Flag to tell whether special file names are enabled. See gpg.c for * an explanation of these file names. */ static int allow_special_filenames; static GPGRT_INLINE gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static GPGRT_INLINE gpg_error_t my_error (int e) { return gpg_err_make (default_errsource, (e)); } #if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2 #warning using trap_unaligned static int setsysinfo(unsigned long op, void *buffer, unsigned long size, int *start, void *arg, unsigned long flag) { return syscall(__NR_osf_setsysinfo, op, buffer, size, start, arg, flag); } void trap_unaligned(void) { unsigned int buf[2]; buf[0] = SSIN_UACPROC; buf[1] = UAC_SIGBUS | UAC_NOPRINT; setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0); } #else void trap_unaligned(void) { /* dummy */ } #endif int disable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; /* We only set the current limit unless we were not able to retrieve the old value. */ if (getrlimit (RLIMIT_CORE, &limit)) limit.rlim_max = 0; limit.rlim_cur = 0; if( !setrlimit (RLIMIT_CORE, &limit) ) return 0; if( errno != EINVAL && errno != ENOSYS ) log_fatal (_("can't disable core dumps: %s\n"), strerror(errno) ); #endif return 1; #endif } int enable_core_dumps (void) { #ifdef HAVE_DOSISH_SYSTEM return 0; #else # ifdef HAVE_SETRLIMIT struct rlimit limit; if (getrlimit (RLIMIT_CORE, &limit)) return 1; limit.rlim_cur = limit.rlim_max; setrlimit (RLIMIT_CORE, &limit); return 1; /* We always return true because this function is merely a debugging aid. */ # endif return 1; #endif } /* Allow the use of special "-&nnn" style file names. */ void enable_special_filenames (void) { allow_special_filenames = 1; } /* Return a string which is used as a kind of process ID. */ const byte * get_session_marker (size_t *rlen) { static byte marker[SIZEOF_UNSIGNED_LONG*2]; static int initialized; if (!initialized) { gcry_create_nonce (marker, sizeof marker); initialized = 1; } *rlen = sizeof (marker); return marker; } /* Return a random number in an unsigned int. */ unsigned int get_uint_nonce (void) { unsigned int value; gcry_create_nonce (&value, sizeof value); return value; } #if 0 /* not yet needed - Note that this will require inclusion of cmacros.am in Makefile.am */ int check_permissions(const char *path,int extension,int checkonly) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) char *tmppath; struct stat statbuf; int ret=1; int isdir=0; if(opt.no_perm_warn) return 0; if(extension && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(GNUPG_LIBDIR,path,NULL); } else tmppath=m_strdup(path); /* It's okay if the file doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } isdir=S_ISDIR(statbuf.st_mode); /* Per-user files must be owned by the user. Extensions must be owned by the user or root. */ if((!extension && statbuf.st_uid != getuid()) || (extension && statbuf.st_uid!=0 && statbuf.st_uid!=getuid())) { if(!checkonly) log_info(_("Warning: unsafe ownership on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } /* This works for both directories and files - basically, we don't care what the owner permissions are, so long as the group and other permissions are 0 for per-user files, and non-writable for extensions. */ if((extension && (statbuf.st_mode & (S_IWGRP|S_IWOTH)) !=0) || (!extension && (statbuf.st_mode & (S_IRWXG|S_IRWXO)) != 0)) { char *dir; /* However, if the directory the directory/file is in is owned by the user and is 700, then this is not a problem. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir= make_dirname (tmppath); if(stat(dir,&statbuf)==0 && statbuf.st_uid==getuid() && S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) { xfree (dir); ret=0; goto end; } m_free(dir); if(!checkonly) log_info(_("Warning: unsafe permissions on %s \"%s\"\n"), isdir?"directory":extension?"extension":"file",path); goto end; } ret=0; end: m_free(tmppath); return ret; #endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */ return 0; } #endif /* Wrapper around the usual sleep function. This one won't wake up before the sleep time has really elapsed. When build with Pth it merely calls pth_sleep and thus suspends only the current thread. */ void gnupg_sleep (unsigned int seconds) { #ifdef USE_NPTH npth_sleep (seconds); #else /* Fixme: make sure that a sleep won't wake up to early. */ # ifdef HAVE_W32_SYSTEM Sleep (seconds*1000); # else sleep (seconds); # endif #endif } /* Wrapper around the platforms usleep function. This one won't wake * up before the sleep time has really elapsed. When build with nPth * it merely calls npth_usleep and thus suspends only the current * thread. */ void gnupg_usleep (unsigned int usecs) { #if defined(USE_NPTH) npth_usleep (usecs); #elif defined(HAVE_W32_SYSTEM) Sleep ((usecs + 999) / 1000); #elif defined(HAVE_NANOSLEEP) if (usecs) { struct timespec req; struct timespec rem; req.tv_sec = 0; req.tv_nsec = usecs * 1000; while (nanosleep (&req, &rem) < 0 && errno == EINTR) req = rem; } #else /*Standard Unix*/ if (usecs) { struct timeval tv; tv.tv_sec = usecs / 1000000; tv.tv_usec = usecs % 1000000; select (0, NULL, NULL, NULL, &tv); } #endif } /* This function is a NOP for POSIX systems but required under Windows as the file handles as returned by OS calls (like CreateFile) are different from the libc file descriptors (like open). This function translates system file handles to libc file handles. FOR_WRITE gives the direction of the handle. */ int translate_sys2libc_fd (gnupg_fd_t fd, int for_write) { #if defined(HAVE_W32CE_SYSTEM) (void)for_write; return (int) fd; #elif defined(HAVE_W32_SYSTEM) int x; if (fd == GNUPG_INVALID_FD) return -1; /* Note that _open_osfhandle is currently defined to take and return a long. */ x = _open_osfhandle ((long)fd, for_write ? 1 : 0); if (x == -1) log_error ("failed to translate osfhandle %p\n", (void *) fd); return x; #else /*!HAVE_W32_SYSTEM */ (void)for_write; return fd; #endif } /* This is the same as translate_sys2libc_fd but takes an integer which is assumed to be such an system handle. On WindowsCE the passed FD is a rendezvous ID and the function finishes the pipe creation. */ int translate_sys2libc_fd_int (int fd, int for_write) { #if HAVE_W32CE_SYSTEM fd = (int) _assuan_w32ce_finish_pipe (fd, for_write); return translate_sys2libc_fd ((void*)fd, for_write); #elif HAVE_W32_SYSTEM if (fd <= 2) return fd; /* Do not do this for error, stdin, stdout, stderr. */ return translate_sys2libc_fd ((void*)fd, for_write); #else (void)for_write; return fd; #endif } /* Check whether FNAME has the form "-&nnnn", where N is a non-zero * number. Returns this number or -1 if it is not the case. If the * caller wants to use the file descriptor for writing FOR_WRITE shall - * be set to 1. If NOTRANSLATE is set the Windows spefic mapping is + * be set to 1. If NOTRANSLATE is set the Windows specific mapping is * not done. */ int check_special_filename (const char *fname, int for_write, int notranslate) { if (allow_special_filenames && fname && *fname == '-' && fname[1] == '&') { int i; fname += 2; for (i=0; digitp (fname+i); i++ ) ; if (!fname[i]) return notranslate? atoi (fname) /**/ : translate_sys2libc_fd_int (atoi (fname), for_write); } return -1; } /* Replacement for tmpfile(). This is required because the tmpfile function of Windows' runtime library is broken, insecure, ignores TMPDIR and so on. In addition we create a file with an inheritable handle. */ FILE * gnupg_tmpfile (void) { #ifdef HAVE_W32_SYSTEM int attempts, n; #ifdef HAVE_W32CE_SYSTEM wchar_t buffer[MAX_PATH+7+12+1]; # define mystrlen(a) wcslen (a) wchar_t *name, *p; #else char buffer[MAX_PATH+7+12+1]; # define mystrlen(a) strlen (a) char *name, *p; #endif HANDLE file; int pid = GetCurrentProcessId (); unsigned int value; int i; SECURITY_ATTRIBUTES sec_attr; memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = TRUE; n = GetTempPath (MAX_PATH+1, buffer); if (!n || n > MAX_PATH || mystrlen (buffer) > MAX_PATH) { gpg_err_set_errno (ENOENT); return NULL; } p = buffer + mystrlen (buffer); #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L"_gnupg"); p += 7; #else p = stpcpy (p, "_gnupg"); #endif /* We try to create the directory but don't care about an error as it may already exist and the CreateFile would throw an error anyway. */ CreateDirectory (buffer, NULL); *p++ = '\\'; name = p; for (attempts=0; attempts < 10; attempts++) { p = name; value = (GetTickCount () ^ ((pid<<16) & 0xffff0000)); for (i=0; i < 8; i++) { *p++ = tohex (((value >> 28) & 0x0f)); value <<= 4; } #ifdef HAVE_W32CE_SYSTEM wcscpy (p, L".tmp"); #else strcpy (p, ".tmp"); #endif file = CreateFile (buffer, GENERIC_READ | GENERIC_WRITE, 0, &sec_attr, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); if (file != INVALID_HANDLE_VALUE) { FILE *fp; #ifdef HAVE_W32CE_SYSTEM int fd = (int)file; fp = _wfdopen (fd, L"w+b"); #else int fd = _open_osfhandle ((long)file, 0); if (fd == -1) { CloseHandle (file); return NULL; } fp = fdopen (fd, "w+b"); #endif if (!fp) { int save = errno; close (fd); gpg_err_set_errno (save); return NULL; } return fp; } Sleep (1); /* One ms as this is the granularity of GetTickCount. */ } gpg_err_set_errno (ENOENT); return NULL; #undef mystrlen #else /*!HAVE_W32_SYSTEM*/ return tmpfile (); #endif /*!HAVE_W32_SYSTEM*/ } /* Make sure that the standard file descriptors are opened. Obviously some folks close them before an exec and the next file we open will get one of them assigned and thus any output (i.e. diagnostics) end up in that file (e.g. the trustdb). Not actually a gpg problem as this will happen with almost all utilities when called in a wrong way. However we try to minimize the damage here and raise awareness of the problem. Must be called before we open any files! */ void gnupg_reopen_std (const char *pgmname) { #if defined(HAVE_STAT) && !defined(HAVE_W32_SYSTEM) struct stat statbuf; int did_stdin = 0; int did_stdout = 0; int did_stderr = 0; FILE *complain; if (fstat (STDIN_FILENO, &statbuf) == -1 && errno ==EBADF) { if (open ("/dev/null",O_RDONLY) == STDIN_FILENO) did_stdin = 1; else did_stdin = 2; } if (fstat (STDOUT_FILENO, &statbuf) == -1 && errno == EBADF) { if (open ("/dev/null",O_WRONLY) == STDOUT_FILENO) did_stdout = 1; else did_stdout = 2; } if (fstat (STDERR_FILENO, &statbuf)==-1 && errno==EBADF) { if (open ("/dev/null", O_WRONLY) == STDERR_FILENO) did_stderr = 1; else did_stderr = 2; } /* It's hard to log this sort of thing since the filehandle we would complain to may be closed... */ if (!did_stderr) complain = stderr; else if (!did_stdout) complain = stdout; else complain = NULL; if (complain) { if (did_stdin == 1) fprintf (complain, "%s: WARNING: standard input reopened\n", pgmname); if (did_stdout == 1) fprintf (complain, "%s: WARNING: standard output reopened\n", pgmname); if (did_stderr == 1) fprintf (complain, "%s: WARNING: standard error reopened\n", pgmname); if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) fprintf(complain,"%s: fatal: unable to reopen standard input," " output, or error\n", pgmname); } if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2) exit (3); #else /* !(HAVE_STAT && !HAVE_W32_SYSTEM) */ (void)pgmname; #endif } /* Hack required for Windows. */ void gnupg_allow_set_foregound_window (pid_t pid) { if (!pid) log_info ("%s called with invalid pid %lu\n", "gnupg_allow_set_foregound_window", (unsigned long)pid); #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) else if (!AllowSetForegroundWindow ((pid_t)pid == (pid_t)(-1)?ASFW_ANY:pid)) log_info ("AllowSetForegroundWindow(%lu) failed: %s\n", (unsigned long)pid, w32_strerror (-1)); #endif } int gnupg_remove (const char *fname) { #ifdef HAVE_W32CE_SYSTEM int rc; wchar_t *wfname; wfname = utf8_to_wchar (fname); if (!wfname) rc = 0; else { rc = DeleteFile (wfname); xfree (wfname); } if (!rc) return -1; /* ERRNO is automagically provided by gpg-error.h. */ return 0; #else return remove (fname); #endif } /* Wrapper for rename(2) to handle Windows peculiarities. If * BLOCK_SIGNALS is not NULL and points to a variable set to true, all * signals will be blocked by calling gnupg_block_all_signals; the * caller needs to call gnupg_unblock_all_signals if that variable is * still set to true on return. */ gpg_error_t gnupg_rename_file (const char *oldname, const char *newname, int *block_signals) { gpg_error_t err = 0; if (block_signals && *block_signals) gnupg_block_all_signals (); #ifdef HAVE_DOSISH_SYSTEM { int wtime = 0; gnupg_remove (newname); again: if (rename (oldname, newname)) { if (GetLastError () == ERROR_SHARING_VIOLATION) { /* Another process has the file open. We do not use a * lock for read but instead we wait until the other * process has closed the file. This may take long but * that would also be the case with a dotlock approach for * read and write. Note that we don't need this on Unix * due to the inode concept. * * So let's wait until the rename has worked. The retry * intervals are 50, 100, 200, 400, 800, 50ms, ... */ if (!wtime || wtime >= 800) wtime = 50; else wtime *= 2; if (wtime >= 800) log_info (_("waiting for file '%s' to become accessible ...\n"), oldname); Sleep (wtime); goto again; } err = my_error_from_syserror (); } } #else /* Unix */ { #ifdef __riscos__ gnupg_remove (newname); #endif if (rename (oldname, newname) ) err = my_error_from_syserror (); } #endif /* Unix */ if (block_signals && *block_signals && err) { gnupg_unblock_all_signals (); *block_signals = 0; } if (err) log_error (_("renaming '%s' to '%s' failed: %s\n"), oldname, newname, gpg_strerror (err)); return err; } #ifndef HAVE_W32_SYSTEM static mode_t modestr_to_mode (const char *modestr) { mode_t mode = 0; if (modestr && *modestr) { modestr++; if (*modestr && *modestr++ == 'r') mode |= S_IRUSR; if (*modestr && *modestr++ == 'w') mode |= S_IWUSR; if (*modestr && *modestr++ == 'x') mode |= S_IXUSR; if (*modestr && *modestr++ == 'r') mode |= S_IRGRP; if (*modestr && *modestr++ == 'w') mode |= S_IWGRP; if (*modestr && *modestr++ == 'x') mode |= S_IXGRP; if (*modestr && *modestr++ == 'r') mode |= S_IROTH; if (*modestr && *modestr++ == 'w') mode |= S_IWOTH; if (*modestr && *modestr++ == 'x') mode |= S_IXOTH; } return mode; } #endif /* A wrapper around mkdir which takes a string for the mode argument. This makes it easier to handle the mode argument which is not defined on all systems. The format of the modestring is "-rwxrwxrwx" '-' is a don't care or not set. 'r', 'w', 'x' are read allowed, write allowed, execution allowed with the first group for the user, the second for the group and the third for all others. If the string is shorter than above the missing mode characters are meant to be not set. */ int gnupg_mkdir (const char *name, const char *modestr) { #ifdef HAVE_W32CE_SYSTEM wchar_t *wname; (void)modestr; wname = utf8_to_wchar (name); if (!wname) return -1; if (!CreateDirectoryW (wname, NULL)) { xfree (wname); return -1; /* ERRNO is automagically provided by gpg-error.h. */ } xfree (wname); return 0; #elif MKDIR_TAKES_ONE_ARG (void)modestr; /* Note: In the case of W32 we better use CreateDirectory and try to set appropriate permissions. However using mkdir is easier because this sets ERRNO. */ return mkdir (name); #else return mkdir (name, modestr_to_mode (modestr)); #endif } /* A wrapper around chmod which takes a string for the mode argument. This makes it easier to handle the mode argument which is not defined on all systems. The format of the modestring is the same as for gnupg_mkdir. */ int gnupg_chmod (const char *name, const char *modestr) { #ifdef HAVE_W32_SYSTEM (void)name; (void)modestr; return 0; #else return chmod (name, modestr_to_mode (modestr)); #endif } /* Our version of mkdtemp. The API is identical to POSIX.1-2008 version. We do not use a system provided mkdtemp because we have a good RNG instantly available and this way we don't have diverging versions. */ char * gnupg_mkdtemp (char *tmpl) { /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that can exist for a given template is 62**6 (5*36**3 for Windows). It should never be necessary to try all these combinations. Instead if a reasonable number of names is tried (we define reasonable as 62**3 or 5*36**3) fail to give the system administrator the chance to remove the problems. */ #ifdef HAVE_W32_SYSTEM static const char letters[] = "abcdefghijklmnopqrstuvwxyz0123456789"; # define NUMBER_OF_LETTERS 36 # define ATTEMPTS_MIN (5 * 36 * 36 * 36) #else static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; # define NUMBER_OF_LETTERS 62 # define ATTEMPTS_MIN (62 * 62 * 62) #endif int len; char *XXXXXX; uint64_t value; unsigned int count; int save_errno = errno; /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX unsigned int attempts = TMP_MAX; #else unsigned int attempts = ATTEMPTS_MIN; #endif len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { gpg_err_set_errno (EINVAL); return NULL; } /* This is where the Xs start. */ XXXXXX = &tmpl[len - 6]; /* Get a random start value. */ gcry_create_nonce (&value, sizeof value); /* Loop until a directory was created. */ for (count = 0; count < attempts; value += 7777, ++count) { uint64_t v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[1] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[2] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[3] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[4] = letters[v % NUMBER_OF_LETTERS]; v /= NUMBER_OF_LETTERS; XXXXXX[5] = letters[v % NUMBER_OF_LETTERS]; if (!gnupg_mkdir (tmpl, "-rwx")) { gpg_err_set_errno (save_errno); return tmpl; } if (errno != EEXIST) return NULL; } /* We got out of the loop because we ran out of combinations to try. */ gpg_err_set_errno (EEXIST); return NULL; } int gnupg_setenv (const char *name, const char *value, int overwrite) { #ifdef HAVE_W32CE_SYSTEM (void)name; (void)value; (void)overwrite; return 0; #else /*!W32CE*/ # ifdef HAVE_W32_SYSTEM /* Windows maintains (at least) two sets of environment variables. One set can be accessed by GetEnvironmentVariable and SetEnvironmentVariable. This set is inherited by the children. The other set is maintained in the C runtime, and is accessed using getenv and putenv. We try to keep them in sync by modifying both sets. */ { int exists; char tmpbuf[10]; exists = GetEnvironmentVariable (name, tmpbuf, sizeof tmpbuf); if ((! exists || overwrite) && !SetEnvironmentVariable (name, value)) { gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */ return -1; } } # endif /*W32*/ # ifdef HAVE_SETENV return setenv (name, value, overwrite); # else /*!HAVE_SETENV*/ if (! getenv (name) || overwrite) { char *buf; (void)overwrite; if (!name || !value) { gpg_err_set_errno (EINVAL); return -1; } buf = strconcat (name, "=", value, NULL); if (!buf) return -1; # if __GNUC__ # warning no setenv - using putenv but leaking memory. # endif return putenv (buf); } return 0; # endif /*!HAVE_SETENV*/ #endif /*!W32CE*/ } int gnupg_unsetenv (const char *name) { #ifdef HAVE_W32CE_SYSTEM (void)name; return 0; #else /*!W32CE*/ # ifdef HAVE_W32_SYSTEM /* Windows maintains (at least) two sets of environment variables. One set can be accessed by GetEnvironmentVariable and SetEnvironmentVariable. This set is inherited by the children. The other set is maintained in the C runtime, and is accessed using getenv and putenv. We try to keep them in sync by modifying both sets. */ if (!SetEnvironmentVariable (name, NULL)) { gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */ return -1; } # endif /*W32*/ # ifdef HAVE_UNSETENV return unsetenv (name); # else /*!HAVE_UNSETENV*/ { char *buf; if (!name) { gpg_err_set_errno (EINVAL); return -1; } buf = xtrystrdup (name); if (!buf) return -1; # if __GNUC__ # warning no unsetenv - trying putenv but leaking memory. # endif return putenv (buf); } # endif /*!HAVE_UNSETENV*/ #endif /*!W32CE*/ } /* Return the current working directory as a malloced string. Return NULL and sets ERRNo on error. */ char * gnupg_getcwd (void) { char *buffer; size_t size = 100; for (;;) { buffer = xtrymalloc (size+1); if (!buffer) return NULL; #ifdef HAVE_W32CE_SYSTEM strcpy (buffer, "/"); /* Always "/". */ return buffer; #else if (getcwd (buffer, size) == buffer) return buffer; xfree (buffer); if (errno != ERANGE) return NULL; size *= 2; #endif } } #ifdef HAVE_W32CE_SYSTEM /* There is a isatty function declaration in cegcc but it does not make sense, thus we redefine it. */ int _gnupg_isatty (int fd) { (void)fd; return 0; } #endif #ifdef HAVE_W32CE_SYSTEM /* Replacement for getenv which takes care of the our use of getenv. The code is not thread safe but we expect it to work in all cases because it is called for the first time early enough. */ char * _gnupg_getenv (const char *name) { static int initialized; static char *assuan_debug; if (!initialized) { assuan_debug = read_w32_registry_string (NULL, "\\Software\\GNU\\libassuan", "debug"); initialized = 1; } if (!strcmp (name, "ASSUAN_DEBUG")) return assuan_debug; else return NULL; } #endif /*HAVE_W32CE_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* Return the user's security identifier from the current process. */ PSID w32_get_user_sid (void) { int okay = 0; HANDLE proc = NULL; HANDLE token = NULL; TOKEN_USER *user = NULL; PSID sid = NULL; DWORD tokenlen, sidlen; proc = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); if (!proc) goto leave; if (!OpenProcessToken (proc, TOKEN_QUERY, &token)) goto leave; if (!GetTokenInformation (token, TokenUser, NULL, 0, &tokenlen) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) goto leave; user = xtrymalloc (tokenlen); if (!user) goto leave; if (!GetTokenInformation (token, TokenUser, user, tokenlen, &tokenlen)) goto leave; if (!IsValidSid (user->User.Sid)) goto leave; sidlen = GetLengthSid (user->User.Sid); sid = xtrymalloc (sidlen); if (!sid) goto leave; if (!CopySid (sidlen, sid, user->User.Sid)) goto leave; okay = 1; leave: xfree (user); if (token) CloseHandle (token); if (proc) CloseHandle (proc); if (!okay) { xfree (sid); sid = NULL; } return sid; } #endif /*HAVE_W32_SYSTEM*/ /* Support for inotify under Linux. */ /* Store a new inotify file handle for SOCKET_NAME at R_FD or return * an error code. */ gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name) { #if HAVE_INOTIFY_INIT gpg_error_t err; char *fname; int fd; char *p; *r_fd = -1; if (!socket_name) return my_error (GPG_ERR_INV_VALUE); fname = xtrystrdup (socket_name); if (!fname) return my_error_from_syserror (); fd = inotify_init (); if (fd == -1) { err = my_error_from_syserror (); xfree (fname); return err; } /* We need to watch the directory for the file because there won't * be an IN_DELETE_SELF for a socket file. To handle a removal of * the directory we also watch the directory itself. */ p = strrchr (fname, '/'); if (p) *p = 0; if (inotify_add_watch (fd, fname, (IN_DELETE|IN_DELETE_SELF|IN_EXCL_UNLINK)) == -1) { err = my_error_from_syserror (); close (fd); xfree (fname); return err; } xfree (fname); *r_fd = fd; return 0; #else /*!HAVE_INOTIFY_INIT*/ (void)socket_name; *r_fd = -1; return my_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_INOTIFY_INIT*/ } /* Read an inotify event and return true if it matches NAME or if it * sees an IN_DELETE_SELF event for the directory of NAME. */ int gnupg_inotify_has_name (int fd, const char *name) { #if USE_NPTH && HAVE_INOTIFY_INIT #define BUFSIZE_FOR_INOTIFY (sizeof (struct inotify_event) + 255 + 1) union { struct inotify_event ev; char _buf[sizeof (struct inotify_event) + 255 + 1]; } buf; struct inotify_event *evp; int n; n = npth_read (fd, &buf, sizeof buf); /* log_debug ("notify read: n=%d\n", n); */ evp = &buf.ev; while (n >= sizeof (struct inotify_event)) { /* log_debug (" mask=%x len=%u name=(%s)\n", */ /* evp->mask, (unsigned int)evp->len, evp->len? evp->name:""); */ if ((evp->mask & IN_UNMOUNT)) { /* log_debug (" found (dir unmounted)\n"); */ return 3; /* Directory was unmounted. */ } if ((evp->mask & IN_DELETE_SELF)) { /* log_debug (" found (dir removed)\n"); */ return 2; /* Directory was removed. */ } if ((evp->mask & IN_DELETE)) { if (evp->len >= strlen (name) && !strcmp (evp->name, name)) { /* log_debug (" found (file removed)\n"); */ return 1; /* File was removed. */ } } n -= sizeof (*evp) + evp->len; evp = (struct inotify_event *)(void *) ((char *)evp + sizeof (*evp) + evp->len); } #else /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/ (void)fd; (void)name; #endif /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/ return 0; /* Not found. */ } /* Return a malloc'ed string that is the path to the passed * unix-domain socket (or return NULL if this is not a valid * unix-domain socket). We use a plain int here because it is only * used on Linux. * * FIXME: This function needs to be moved to libassuan. */ #ifndef HAVE_W32_SYSTEM char * gnupg_get_socket_name (int fd) { struct sockaddr_un un; socklen_t len = sizeof(un); char *name = NULL; if (getsockname (fd, (struct sockaddr*)&un, &len) != 0) log_error ("could not getsockname(%d): %s\n", fd, gpg_strerror (my_error_from_syserror ())); else if (un.sun_family != AF_UNIX) log_error ("file descriptor %d is not a unix-domain socket\n", fd); else if (len <= offsetof (struct sockaddr_un, sun_path)) log_error ("socket name not present for file descriptor %d\n", fd); else if (len > sizeof(un)) log_error ("socket name for file descriptor %d was truncated " "(passed %zu bytes, wanted %u)\n", fd, sizeof(un), len); else { size_t namelen = len - offsetof (struct sockaddr_un, sun_path); /* log_debug ("file descriptor %d has path %s (%zu octets)\n", fd, */ /* un.sun_path, namelen); */ name = xtrymalloc (namelen + 1); if (!name) log_error ("failed to allocate memory for name of fd %d: %s\n", fd, gpg_strerror (my_error_from_syserror ())); else { memcpy (name, un.sun_path, namelen); name[namelen] = 0; } } return name; } #endif /*!HAVE_W32_SYSTEM*/ /* Check whether FD is valid. */ int gnupg_fd_valid (int fd) { int d = dup (fd); if (d < 0) return 0; close (d); return 1; } diff --git a/common/t-session-env.c b/common/t-session-env.c index aa9d596e5..e2b942c6a 100644 --- a/common/t-session-env.c +++ b/common/t-session-env.c @@ -1,304 +1,304 @@ /* t-session-env.c - Module test for session-env.c * Copyright (C) 2009 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 "util.h" #include "session-env.h" #define pass() do { ; } while(0) #define fail(e) do { fprintf (stderr, "%s:%d: function failed: %s\n", \ __FILE__,__LINE__, gpg_strerror (e)); \ exit (1); \ } while(0) static int verbose; static void listall (session_env_t se) { int iterator = 0; const char *name, *value; int def; if (verbose) printf ("environment of %p\n", se); while ( (name = session_env_listenv (se, &iterator, &value, &def)) ) if (verbose) printf (" %s%s=%s\n", def? "[def] ":" ", name, value); } static void show_stdnames (void) { const char *name, *assname; int iterator = 0; int count; printf (" > Known envvars:"); count = 20; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { if (count > 60) { printf ("\n >"); count = 7; } printf ( " %s", name); count += strlen (name) + 1; if (assname) { printf ( "(%s)", assname); count += strlen (assname) + 2; } } putchar('\n'); } static void test_all (void) { gpg_error_t err; session_env_t se_0, se; const char *s, *s2; int idx; se_0 = session_env_new (); if (!se_0) fail (gpg_error_from_syserror ()); se = session_env_new (); if (!se) fail (gpg_error_from_syserror ()); err = session_env_putenv (se, NULL); if (gpg_err_code (err) != GPG_ERR_INV_VALUE) fail (err); err = session_env_putenv (se, ""); if (gpg_err_code (err) != GPG_ERR_INV_VALUE) fail (err); err = session_env_putenv (se, "="); if (gpg_err_code (err) != GPG_ERR_INV_VALUE) fail (err); - /* Delete some nonexistant variables. */ + /* Delete some nonexistent variables. */ err = session_env_putenv (se, "A"); if (err) fail (err); err = session_env_putenv (se, "a"); if (err) fail (err); err = session_env_putenv (se, "_aaaa aaaaaasssssssssssss\nddd"); if (err) fail (err); /* Create a few variables. */ err = session_env_putenv (se, "EMPTY="); if (err) fail (err); err = session_env_putenv (se, "foo=value_of_foo"); if (err) fail (err); err = session_env_putenv (se, "bar=the value_of_bar"); if (err) fail (err); err = session_env_putenv (se, "baz=this-is-baz"); if (err) fail (err); err = session_env_putenv (se, "BAZ=this-is-big-baz"); if (err) fail (err); listall (se); /* Update one. */ err = session_env_putenv (se, "baz=this-is-another-baz"); if (err) fail (err); listall (se); /* Delete one. */ err = session_env_putenv (se, "bar"); if (err) fail (err); listall (se); /* Insert a new one. */ err = session_env_putenv (se, "FOO=value_of_foo"); if (err) fail (err); listall (se); /* Retrieve a default one. */ s = session_env_getenv_or_default (se, "HOME", NULL); if (!s) { fprintf (stderr, "failed to get default of HOME\n"); exit (1); } s = session_env_getenv (se, "HOME"); if (s) fail(0); /* This is a default value, thus we should not see it. */ s = session_env_getenv_or_default (se, "HOME", NULL); if (!s) fail(0); /* But here we should see it. */ /* Add a few more. */ err = session_env_putenv (se, "X1=A value"); if (err) fail (err); err = session_env_putenv (se, "X2=Another value"); if (err) fail (err); err = session_env_putenv (se, "X3=A value"); if (err) fail (err); listall (se); /* Check that we can overwrite a default value. */ err = session_env_putenv (se, "HOME=/this/is/my/new/home"); if (err) fail (err); /* And that we get this string back. */ s = session_env_getenv (se, "HOME"); if (!s) fail (0); if (strcmp (s, "/this/is/my/new/home")) fail (0); /* A new get default should return the very same string. */ s2 = session_env_getenv_or_default (se, "HOME", NULL); if (!s2) fail (0); if (s2 != s) fail (0); listall (se); /* Check that the other object is clean. */ { int iterator = 0; if (session_env_listenv (se_0, &iterator, NULL, NULL)) fail (0); } session_env_release (se); /* Use a new session for quick mass test. */ se = session_env_new (); if (!se) fail (gpg_error_from_syserror ()); /* Create. */ for (idx=0; idx < 500; idx++) { char buf[100]; snprintf (buf, sizeof buf, "FOO_%d=Value for %x", idx, idx); err = session_env_putenv (se, buf); if (err) fail (err); } err = session_env_setenv (se, "TEST1", "value1"); if (err) fail (err); err = session_env_setenv (se, "TEST1", "value1-updated"); if (err) fail (err); listall (se); /* Delete all. */ for (idx=0; idx < 500; idx++) { char buf[100]; snprintf (buf, sizeof buf, "FOO_%d", idx); err = session_env_putenv (se, buf); if (err) fail (err); } err = session_env_setenv (se, "TEST1", NULL); if (err) fail (err); /* Check that all are deleted. */ { int iterator = 0; if (session_env_listenv (se, &iterator, NULL, NULL)) fail (0); } /* Add a few strings again. */ for (idx=0; idx < 500; idx++) { char buf[100]; if (!(idx % 10)) { if ( !(idx % 3)) snprintf (buf, sizeof buf, "FOO_%d=", idx); else snprintf (buf, sizeof buf, "FOO_%d=new value for %x", idx, idx); err = session_env_putenv (se, buf); if (err) fail (err); } } listall (se); session_env_release (se); session_env_release (se_0); } int main (int argc, char **argv) { if (argc) { argc--; argv++; } if (argc && !strcmp (argv[0], "--verbose")) { verbose = 1; argc--; argv++; } show_stdnames (); test_all (); return 0; } diff --git a/configure.ac b/configure.ac index 494e44402..9395fb8c2 100644 --- a/configure.ac +++ b/configure.ac @@ -1,2006 +1,2006 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998-2017 Free Software Foundation, Inc. # Copyright (C) 1998-2017 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 . # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) min_automake_version="1.14" # To build a release you need to create a tag with the version number # (git tag -s gnupg-2.n.m) and run "./autogen.sh --force". Please # bump the version number immediately *after* the release and do # another commit and push so that the git magic is able to work. m4_define([mym4_package],[gnupg]) m4_define([mym4_major], [2]) m4_define([mym4_minor], [1]) m4_define([mym4_micro], [21]) # To start a new development series, i.e a new major or minor number # you need to mark an arbitrary commit before the first beta release # with an annotated tag. For example the 2.1 branch starts off with # the tag "gnupg-2.1-base". This is used as the base for counting # beta numbers before the first release of a series. # Below is m4 magic to extract and compute the git revision number, # the decimalized short revision number, a beta version string and a # flag indicating a development version (mym4_isbeta). Note that the # m4 processing is done by autoconf and not during the configure run. m4_define([mym4_verslist], m4_split(m4_esyscmd([./autogen.sh --find-version] \ mym4_package mym4_major mym4_minor mym4_micro),[:])) m4_define([mym4_isbeta], m4_argn(2, mym4_verslist)) m4_define([mym4_version], m4_argn(4, mym4_verslist)) m4_define([mym4_revision], m4_argn(7, mym4_verslist)) m4_define([mym4_revision_dec], m4_argn(8, mym4_verslist)) m4_esyscmd([echo ]mym4_version[>VERSION]) AC_INIT([mym4_package],[mym4_version], [https://bugs.gnupg.org]) NEED_GPG_ERROR_VERSION=1.24 NEED_LIBGCRYPT_API=1 NEED_LIBGCRYPT_VERSION=1.7.0 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.4.3 NEED_KSBA_API=1 NEED_KSBA_VERSION=1.3.4 NEED_NTBTLS_API=1 NEED_NTBTLS_VERSION=0.1.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 NEED_GNUTLS_VERSION=3.0 NEED_SQLITE_VERSION=3.7 development_version=mym4_isbeta PACKAGE=$PACKAGE_NAME PACKAGE_GT=${PACKAGE_NAME}2 VERSION=$PACKAGE_VERSION AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([sm/gpgsm.c]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip]) AC_CANONICAL_HOST AB_INIT AC_GNU_SOURCE # Before we do anything with the C compiler, we first save the user's # CFLAGS (they are restored at the end of the configure script). This # is because some configure checks don't work with -Werror, but we'd # like to use -Werror with our build. CFLAGS_orig=$CFLAGS CFLAGS= # Some status variables. have_gpg_error=no have_libgcrypt=no have_libassuan=no have_ksba=no have_ntbtls=no have_gnutls=no have_sqlite=no have_npth=no have_libusb=no have_system_resolver=no gnupg_have_ldap="n/a" use_zip=yes use_bzip2=yes use_exec=yes use_trust_models=yes use_tofu=yes use_libdns=yes card_support=yes use_ccid_driver=auto dirmngr_auto_start=yes use_tls_library=no large_secmem=no show_tor_support=no GNUPG_BUILD_PROGRAM(gpg, yes) GNUPG_BUILD_PROGRAM(gpgsm, yes) # The agent is a required part and can't be disabled anymore. build_agent=yes GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(g13, no) GNUPG_BUILD_PROGRAM(dirmngr, yes) GNUPG_BUILD_PROGRAM(doc, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) GNUPG_BUILD_PROGRAM(wks-tools, no) AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) AC_SUBST(VERSION) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT", [Name of this package for gettext]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT", [Bug report address]) AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION", [Required version of Libgcrypt]) AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION", [Required version of Libksba]) AC_DEFINE_UNQUOTED(NEED_NTBTLS_VERSION, "$NEED_NTBTLS_VERSION", [Required version of NTBTLS]) # The default is to use the modules from this package and the few # other packages in a standard place; i.e where this package gets # installed. With these options it is possible to override these # ${prefix} depended values with fixed paths, which can't be replaced # at make time. See also am/cmacros.am and the defaults in AH_BOTTOM. AC_ARG_WITH(agent-pgm, [ --with-agent-pgm=PATH Use PATH as the default for the agent)], GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" ) AC_SUBST(GNUPG_AGENT_PGM) AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM") show_gnupg_agent_pgm="(default)" test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM" AC_ARG_WITH(pinentry-pgm, [ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)], GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" ) AC_SUBST(GNUPG_PINENTRY_PGM) AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM") show_gnupg_pinentry_pgm="(default)" test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM" AC_ARG_WITH(scdaemon-pgm, [ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)], GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" ) AC_SUBST(GNUPG_SCDAEMON_PGM) AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM") show_gnupg_dirmngr_pgm="(default)" test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM" AC_ARG_WITH(protect-tool-pgm, [ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)], GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" ) AC_SUBST(GNUPG_PROTECT_TOOL_PGM) AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM") show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" AC_ARG_WITH(dirmngr-ldap-pgm, [ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmngr ldap wrapper)], GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM") show_gnupg_dirmngr_ldap_pgm="(default)" test -n "$GNUPG_DIRMNGR_LDAP_PGM" \ && show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM" # # On some platforms gpg2 is usually installed as gpg without using a # symlink. For correct operation of gpgconf it needs to know the # installed name of gpg. This option sets "gpg2"'s installed name to # just "gpg". Note that it might be required to rename gpg2 to gpg # manually after the build process. # AC_ARG_ENABLE(gpg2-is-gpg, AC_HELP_STRING([--enable-gpg2-is-gpg],[Set installed name of gpg2 to gpg]), gpg2_is_gpg=$enableval) if test "$gpg2_is_gpg" != "yes"; then AC_DEFINE(USE_GPG2_HACK, 1, [Define to install gpg as gpg2]) fi AM_CONDITIONAL(USE_GPG2_HACK, test "$gpg2_is_gpg" != "yes") # SELinux support includes tracking of sensitive files to avoid # leaking their contents through processing these files by gpg itself AC_MSG_CHECKING([whether SELinux support is requested]) AC_ARG_ENABLE(selinux-support, AC_HELP_STRING([--enable-selinux-support], [enable SELinux support]), selinux_support=$enableval, selinux_support=no) AC_MSG_RESULT($selinux_support) AC_MSG_CHECKING([whether to allocate extra secure memory]) AC_ARG_ENABLE(large-secmem, AC_HELP_STRING([--enable-large-secmem], [allocate extra secure memory]), large_secmem=$enableval, large_secmem=no) AC_MSG_RESULT($large_secmem) if test "$large_secmem" = yes ; then SECMEM_BUFFER_SIZE=65536 else SECMEM_BUFFER_SIZE=32768 fi AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE, [Size of secure memory buffer]) AC_MSG_CHECKING([whether to enable trust models]) AC_ARG_ENABLE(trust-models, AC_HELP_STRING([--disable-trust-models], [disable all trust models except "always"]), use_trust_models=$enableval) AC_MSG_RESULT($use_trust_models) if test "$use_trust_models" = no ; then AC_DEFINE(NO_TRUST_MODELS, 1, [Define to include only trust-model always]) fi AC_MSG_CHECKING([whether to enable TOFU]) AC_ARG_ENABLE(tofu, AC_HELP_STRING([--disable-tofu], [disable the TOFU trust model]), use_tofu=$enableval, use_tofu=$use_trust_models) AC_MSG_RESULT($use_tofu) if test "$use_trust_models" = no && test "$use_tofu" = yes; then AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given]) fi AC_MSG_CHECKING([whether to enable libdns]) AC_ARG_ENABLE(libdns, AC_HELP_STRING([--disable-libdns], [do not build with libdns support]), use_libdns=$enableval, use_libdns=yes) AC_MSG_RESULT($use_libdns) if test x"$use_libdns" = xyes ; then AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support]) fi AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes) # # Options to disable algorithm # GNUPG_GPG_DISABLE_ALGO([rsa],[RSA public key]) # Elgamal is a MUST algorithm # DSA is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([ecdh],[ECDH public key]) GNUPG_GPG_DISABLE_ALGO([ecdsa],[ECDSA public key]) GNUPG_GPG_DISABLE_ALGO([eddsa],[EdDSA public key]) GNUPG_GPG_DISABLE_ALGO([idea],[IDEA cipher]) # 3DES is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([cast5],[CAST5 cipher]) GNUPG_GPG_DISABLE_ALGO([blowfish],[BLOWFISH cipher]) GNUPG_GPG_DISABLE_ALGO([aes128],[AES128 cipher]) GNUPG_GPG_DISABLE_ALGO([aes192],[AES192 cipher]) GNUPG_GPG_DISABLE_ALGO([aes256],[AES256 cipher]) GNUPG_GPG_DISABLE_ALGO([twofish],[TWOFISH cipher]) GNUPG_GPG_DISABLE_ALGO([camellia128],[CAMELLIA128 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia192],[CAMELLIA192 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia256],[CAMELLIA256 cipher]) GNUPG_GPG_DISABLE_ALGO([md5],[MD5 hash]) # SHA1 is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([rmd160],[RIPE-MD160 hash]) GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash]) # SHA256 is a MUST algorithm for GnuPG. GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash]) GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash]) # Allow disabling of zip support. # This is in general not a good idea because according to rfc4880 OpenPGP # implementations SHOULD support ZLIB. AC_MSG_CHECKING([whether to enable the ZIP and ZLIB compression algorithm]) AC_ARG_ENABLE(zip, AC_HELP_STRING([--disable-zip], [disable the ZIP and ZLIB compression algorithm]), use_zip=$enableval) AC_MSG_RESULT($use_zip) # Allow disabling of bzib2 support. # It is defined only after we confirm the library is available later AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm]) AC_ARG_ENABLE(bzip2, AC_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]), use_bzip2=$enableval) AC_MSG_RESULT($use_bzip2) # Configure option to allow or disallow execution of external # programs, like a photo viewer. AC_MSG_CHECKING([whether to enable external program execution]) AC_ARG_ENABLE(exec, AC_HELP_STRING([--disable-exec],[disable all external program execution]), use_exec=$enableval) AC_MSG_RESULT($use_exec) if test "$use_exec" = no ; then AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution]) fi if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether to enable photo ID viewing]) AC_ARG_ENABLE(photo-viewers, [ --disable-photo-viewers disable photo ID viewers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing]) fi],enableval=yes) gnupg_cv_enable_photo_viewers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_photo_viewers" = yes ; then AC_MSG_CHECKING([whether to use a fixed photo ID viewer]) AC_ARG_WITH(photo-viewer, [ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer], [if test "$withval" = yes ; then withval=no elif test "$withval" != no ; then AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval", [if set, restrict photo-viewer to this]) fi],withval=no) AC_MSG_RESULT($withval) fi fi # # Check for the key/uid cache size. This can't be zero, but can be # pretty small on embedded systems. This is used for the gpg part. # AC_MSG_CHECKING([for the size of the key and uid cache]) AC_ARG_ENABLE(key-cache, AC_HELP_STRING([--enable-key-cache=SIZE], [Set key cache to SIZE (default 4096)]),,enableval=4096) if test "$enableval" = "no"; then enableval=5 elif test "$enableval" = "yes" || test "$enableval" = ""; then enableval=4096 fi changequote(,)dnl key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'` changequote([,])dnl if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then AC_MSG_ERROR([invalid key-cache size]) fi AC_MSG_RESULT($key_cache_size) AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size, [Size of the key and UID caches]) # # Check whether we want to use Linux capabilities # AC_MSG_CHECKING([whether use of capabilities is requested]) AC_ARG_WITH(capabilities, [ --with-capabilities use linux capabilities [default=no]], [use_capabilities="$withval"],[use_capabilities=no]) AC_MSG_RESULT($use_capabilities) # # Check whether to disable the card support AC_MSG_CHECKING([whether smartcard support is requested]) AC_ARG_ENABLE(card-support, AC_HELP_STRING([--disable-card-support], [disable smartcard support]), card_support=$enableval) AC_MSG_RESULT($card_support) if test "$card_support" = yes ; then AC_DEFINE(ENABLE_CARD_SUPPORT,1,[Define to include smartcard support]) else build_scdaemon=no fi # # Allow disabling of internal CCID support. # It is defined only after we confirm the library is available later # AC_MSG_CHECKING([whether to enable the internal CCID driver]) AC_ARG_ENABLE(ccid-driver, AC_HELP_STRING([--disable-ccid-driver], [disable the internal CCID driver]), use_ccid_driver=$enableval) AC_MSG_RESULT($use_ccid_driver) AC_MSG_CHECKING([whether to auto start dirmngr]) AC_ARG_ENABLE(dirmngr-auto-start, AC_HELP_STRING([--disable-dirmngr-auto-start], [disable auto starting of the dirmngr]), dirmngr_auto_start=$enableval) AC_MSG_RESULT($dirmngr_auto_start) if test "$dirmngr_auto_start" = yes ; then AC_DEFINE(USE_DIRMNGR_AUTO_START,1, [Define to enable auto starting of the dirmngr]) fi # # To avoid double inclusion of config.h which might happen at some # places, we add the usual double inclusion protection at the top of # config.h. # AH_TOP([ #ifndef GNUPG_CONFIG_H_INCLUDED #define GNUPG_CONFIG_H_INCLUDED ]) # # Stuff which goes at the bottom of config.h. # AH_BOTTOM([ /* This is the major version number of GnuPG so that source included files can test for this. Note, that we use 2 here even for GnuPG 1.9.x. */ #define GNUPG_MAJOR_VERSION 2 /* Now to separate file name parts. Please note that the string version must not contain more than one character because the code assumes strlen()==1 */ #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define DIRSEP_S "\\" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ';' #define PATHSEP_S ";" #define EXEEXT_S ".exe" #else #define DIRSEP_C '/' #define DIRSEP_S "/" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ':' #define PATHSEP_S ":" #define EXEEXT_S "" #endif /* This is the same as VERSION, but should be overridden if the platform cannot handle things like dots '.' in filenames. Set SAFE_VERSION_DOT and SAFE_VERSION_DASH to whatever SAFE_VERSION uses for dots and dashes. */ #define SAFE_VERSION VERSION #define SAFE_VERSION_DOT '.' #define SAFE_VERSION_DASH '-' /* Some global constants. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" # else # define GNUPG_DEFAULT_HOMEDIR "/gnupg" # endif #elif defined(__VMS) #define GNUPG_DEFAULT_HOMEDIR "/SYS$LOGIN/gnupg" #else #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that the values may be overridden by the make invocations; this is to comply with the GNU coding standards. Note that these values are only defaults. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_BINDIR "c:\\gnupg" # define GNUPG_LIBEXECDIR "c:\\gnupg" # define GNUPG_LIBDIR "c:\\gnupg" # define GNUPG_DATADIR "c:\\gnupg" # define GNUPG_SYSCONFDIR "c:\\gnupg" # else # define GNUPG_BINDIR "\\gnupg" # define GNUPG_LIBEXECDIR "\\gnupg" # define GNUPG_LIBDIR "\\gnupg" # define GNUPG_DATADIR "\\gnupg" # define GNUPG_SYSCONFDIR "\\gnupg" # endif #endif /* Derive some other constants. */ #if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)) #define EXEC_TEMPFILE_ONLY #endif /* We didn't define endianness above, so get it from OS macros. This is intended for making fat binary builds on OS X. */ #if !defined(BIG_ENDIAN_HOST) && !defined(LITTLE_ENDIAN_HOST) #if defined(__BIG_ENDIAN__) #define BIG_ENDIAN_HOST 1 #elif defined(__LITTLE_ENDIAN__) #define LITTLE_ENDIAN_HOST 1 #else #error "No endianness found" #endif #endif /* Hack used for W32: ldap.m4 also tests for the ASCII version of ldap_start_tls_s because that is the actual symbol used in the library. winldap.h redefines it to our commonly used value, thus we define our usual macro here. */ #ifdef HAVE_LDAP_START_TLS_SA # ifndef HAVE_LDAP_START_TLS_S # define HAVE_LDAP_START_TLS_S 1 # endif #endif /* Provide the es_ macro for estream. */ #define GPGRT_ENABLE_ES_MACROS 1 /* Tell libgcrypt not to use its own libgpg-error implementation. */ #define USE_LIBGPG_ERROR 1 /* Tell Libgcrypt not to include deprecated definitions. */ #define GCRYPT_NO_DEPRECATED 1 /* Our HTTP code is used in estream mode. */ #define HTTP_USE_ESTREAM 1 /* Under W32 we do an explicit socket initialization, thus we need to avoid the on-demand initialization which would also install an atexit handler. */ #define HTTP_NO_WSASTARTUP /* Under Windows we use the gettext code from libgpg-error. */ #define GPG_ERR_ENABLE_GETTEXT_MACROS /* Under WindowsCE we use the strerror replacement from libgpg-error. */ #define GPG_ERR_ENABLE_ERRNO_MACROS #endif /*GNUPG_CONFIG_H_INCLUDED*/ ]) AM_MAINTAINER_MODE AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory]) # Checks for programs. AC_MSG_NOTICE([checking for programs]) AC_PROG_MAKE_SET AM_SANITY_CHECK missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir) AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir) AM_SILENT_RULES AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AM_PROG_CC_C_O if test "x$ac_cv_prog_cc_c89" = "xno" ; then AC_MSG_ERROR([[No C-89 compiler found]]) fi AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_RANLIB AC_CHECK_TOOL(AR, ar, :) AC_PATH_PROG(PERL,"perl") AC_CHECK_TOOL(WINDRES, windres, :) AC_ISC_POSIX AC_SYS_LARGEFILE GNUPG_CHECK_USTAR # We need to compile and run a program on the build machine. A # comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in # the AC archive is broken for autoconf 2.57. Given that there is no # newer version of that macro, we assume that it is also broken for # autoconf 2.61 and thus we use a simple but usually sufficient # approach. AC_MSG_CHECKING(for cc for build) if test "$cross_compiling" = "yes"; then CC_FOR_BUILD="${CC_FOR_BUILD-cc}" else CC_FOR_BUILD="${CC_FOR_BUILD-$CC}" fi AC_MSG_RESULT($CC_FOR_BUILD) AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler]) # We need to call this macro because other pkg-config macros are # not always used. PKG_PROG_PKG_CONFIG try_gettext=yes require_iconv=yes have_dosish_system=no have_w32_system=no have_w32ce_system=no have_android_system=no use_simple_gettext=no use_ldapwrapper=yes mmap_needed=yes case "${host}" in *-mingw32*) # special stuff for Windoze NT ac_cv_have_dev_random=no AC_DEFINE(USE_ONLY_8DOT3,1, [Set this to limit filenames to the 8.3 format]) AC_DEFINE(USE_SIMPLE_GETTEXT,1, [Because the Unix gettext has too much overhead on MingW32 systems and these systems lack Posix functions, we use a simplified version of gettext]) have_dosish_system=yes have_w32_system=yes require_iconv=no use_ldapwrapper=no # Fixme: Do this only for CE. case "${host}" in *-mingw32ce*) have_w32ce_system=yes ;; *) AC_DEFINE(HAVE_DRIVE_LETTERS,1, [Defined if the OS supports drive letters.]) ;; esac try_gettext="no" use_simple_gettext=yes mmap_needed=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; i?86-*-msdosdjgpp*) # DOS with the DJGPP environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; *-*-hpux*) if test -z "$GCC" ; then CFLAGS="-Ae -D_HPUX_SOURCE $CFLAGS" fi ;; *-dec-osf4*) if test -z "$GCC" ; then # Suppress all warnings # to get rid of the unsigned/signed char mismatch warnings. CFLAGS="-w $CFLAGS" fi ;; *-dec-osf5*) if test -z "$GCC" ; then # Use the newer compiler `-msg_disable ptrmismatch1' to # get rid of the unsigned/signed char mismatch warnings. # Using this may hide other pointer mismatch warnings, but # it at least lets other warning classes through CFLAGS="-msg_disable ptrmismatch1 $CFLAGS" fi ;; m68k-atari-mint) ;; *-linux-android*) have_android_system=yes # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, Expose all libc features (__DARWIN_C_FULL).) ;; *) ;; esac if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes, case insensitive file names and preferred use of backslashes as directory name separators.]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) AM_CONDITIONAL(USE_SIMPLE_GETTEXT, test x"$use_simple_gettext" = xyes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system]) if test "$have_w32ce_system" = yes; then AC_DEFINE(HAVE_W32CE_SYSTEM,1,[Defined if we run on WindowsCE]) fi fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) AM_CONDITIONAL(HAVE_W32CE_SYSTEM, test "$have_w32ce_system" = yes) if test "$have_android_system" = yes; then AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system]) fi AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes) # (These need to go after AC_PROG_CC so that $EXEEXT is defined) AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any]) # # Checks for libraries. # AC_MSG_NOTICE([checking for libraries]) # # libgpg-error is a library with error codes shared between GnuPG # related projects. # AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION", have_gpg_error=yes,have_gpg_error=no) # # Libgcrypt is our generic crypto library # AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION", have_libgcrypt=yes,have_libgcrypt=no) # # libassuan is used for IPC # AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION", have_libassuan=yes,have_libassuan=no) if test "$have_libassuan" = "yes"; then AC_DEFINE_UNQUOTED(GNUPG_LIBASSUAN_VERSION, "$libassuan_version", [version of the libassuan library]) show_tor_support="only .onion" fi # # libksba is our X.509 support library # AM_PATH_KSBA("$NEED_KSBA_API:$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no) # # libusb allows us to use the integrated CCID smartcard reader driver. # # FiXME: Use GNUPG_CHECK_LIBUSB and modify to use separate AC_SUBSTs. if test "$use_ccid_driver" = auto || test "$use_ccid_driver" = yes; then case "${host}" in *-mingw32*) LIBUSB_NAME= LIBUSB_LIBS= LIBUSB_CPPFLAGS= ;; *-*-darwin*) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS="-Wl,-framework,CoreFoundation -Wl,-framework,IOKit" ;; *-*-freebsd*) # FreeBSD has a native 1.0 compatible library by -lusb. LIBUSB_NAME=usb LIBUSB_LIBS= ;; *) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS= ;; esac fi if test x"$LIBUSB_NAME" != x ; then AC_CHECK_LIB($LIBUSB_NAME, libusb_init, [ LIBUSB_LIBS="-l$LIBUSB_NAME $LIBUSB_LIBS" have_libusb=yes ]) AC_MSG_CHECKING([libusb include dir]) usb_incdir_found="no" for _incdir in "" "/usr/include/libusb-1.0" "/usr/local/include/libusb-1.0"; do _libusb_save_cppflags=$CPPFLAGS if test -n "${_incdir}"; then CPPFLAGS="-I${_incdir} ${CPPFLAGS}" fi AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include ]])], [usb_incdir=${_incdir}; usb_incdir_found="yes"], []) CPPFLAGS=${_libusb_save_cppflags} if test "$usb_incdir_found" = "yes"; then break fi done if test "$usb_incdir_found" = "yes"; then AC_MSG_RESULT([${usb_incdir}]) else AC_MSG_RESULT([not found]) usb_incdir="" have_libusb=no if test "$use_ccid_driver" != yes; then use_ccid_driver=no fi LIBUSB_LIBS="" fi if test "$have_libusb" = yes; then AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available]) fi if test x"$usb_incdir" = x; then LIBUSB_CPPFLAGS="" else LIBUSB_CPPFLAGS="-I${usb_incdir}" fi fi AC_SUBST(LIBUSB_LIBS) AC_SUBST(LIBUSB_CPPFLAGS) # -# Check wether it is necessary to link against libdl. +# Check whether it is necessary to link against libdl. # (For example to load libpcsclite) # gnupg_dlopen_save_libs="$LIBS" LIBS="" AC_SEARCH_LIBS(dlopen, c dl,,,) DL_LIBS=$LIBS AC_SUBST(DL_LIBS) LIBS="$gnupg_dlopen_save_libs" # Checks for g10 AC_ARG_ENABLE(sqlite, AC_HELP_STRING([--disable-sqlite], [disable the use of SQLITE]), try_sqlite=$enableval, try_sqlite=yes) if test x"$use_tofu" = xyes ; then if test x"$try_sqlite" = xyes ; then PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= $NEED_SQLITE_VERSION], [have_sqlite=yes], [have_sqlite=no]) fi if test "$have_sqlite" = "yes"; then : AC_SUBST([SQLITE3_CFLAGS]) AC_SUBST([SQLITE3_LIBS]) else use_tofu=no tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without SQLite support - TOFU disabled *** *** $tmp]]) fi fi AM_CONDITIONAL(SQLITE3, test "$have_sqlite" = "yes") if test x"$use_tofu" = xyes ; then AC_DEFINE(USE_TOFU, 1, [Enable to build the TOFU code]) fi # Checks for g13 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs) AC_DEFINE_UNQUOTED(ENCFS, "${ENCFS}", [defines the filename of the encfs program]) AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount) AC_DEFINE_UNQUOTED(FUSERMOUNT, "${FUSERMOUNT}", [defines the filename of the fusermount program]) # Checks for dirmngr # # Checks for symcryptrun: # # libutil has openpty() and login_tty(). AC_CHECK_LIB(util, openpty, [ LIBUTIL_LIBS="$LIBUTIL_LIBS -lutil" AC_DEFINE(HAVE_LIBUTIL,1, [defined if libutil is available]) ]) AC_SUBST(LIBUTIL_LIBS) # shred is used to clean temporary plain text files. AC_PATH_PROG(SHRED, shred, /usr/bin/shred) AC_DEFINE_UNQUOTED(SHRED, "${SHRED}", [defines the filename of the shred program]) # # Check whether the nPth library is available # AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no) if test "$have_npth" = "yes"; then AC_DEFINE(HAVE_NPTH, 1, [Defined if the New Portable Thread Library is available]) AC_DEFINE(USE_NPTH, 1, [Defined if support for nPth is requested and nPth is available]) else AC_MSG_WARN([[ *** *** To support concurrent access for example in gpg-agent and the SCdaemon *** we need the support of the New Portable Threads Library. ***]]) fi # # NTBTLS is our TLS library. If it is not available fallback to # GNUTLS. # AC_ARG_ENABLE(ntbtls, AC_HELP_STRING([--disable-ntbtls], [disable the use of NTBTLS as TLS library]), try_ntbtls=$enableval, try_ntbtls=yes) if test x"$try_ntbtls" = xyes ; then AM_PATH_NTBTLS("$NEED_NTBTLS_API:$NEED_NTBTLS_VERSION", [have_ntbtls=yes],[have_ntbtls=no]) fi if test "$have_ntbtls" = yes ; then use_tls_library=ntbtls AC_DEFINE(HTTP_USE_NTBTLS, 1, [Enable NTBTLS support in http.c]) else AC_ARG_ENABLE(gnutls, AC_HELP_STRING([--disable-gnutls], [disable GNUTLS as fallback TLS library]), try_gnutls=$enableval, try_gnutls=yes) if test x"$try_gnutls" = xyes ; then PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= $NEED_GNUTLS_VERSION], [have_gnutls=yes], [have_gnutls=no]) fi if test "$have_gnutls" = "yes"; then AC_SUBST([LIBGNUTLS_CFLAGS]) AC_SUBST([LIBGNUTLS_LIBS]) use_tls_library=gnutls AC_DEFINE(HTTP_USE_GNUTLS, 1, [Enable GNUTLS support in http.c]) else tmp=$(echo "$LIBGNUTLS_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without NTBTLS and GNUTLS - no TLS access to keyservers. *** *** $tmp]]) fi fi # # Allow to set a fixed trust store file for system provided certificates. # AC_ARG_WITH([default-trust-store-file], [AC_HELP_STRING([--with-default-trust-store-file=FILE], [Use FILE as system trust store])], default_trust_store_file="$withval", default_trust_store_file="") if test x"$default_trust_store_file" = xno;then default_trust_store_file="" fi if test x"$default_trust_store_file" != x ; then AC_DEFINE_UNQUOTED([DEFAULT_TRUST_STORE_FILE], ["$default_trust_store_file"], [Use as default system trust store file]) fi AC_MSG_NOTICE([checking for networking options]) # # Must check for network library requirements before doing link tests # for ldap, for example. If ldap libs are static (or dynamic and without # ELF runtime link paths), then link will fail and LDAP support won't # be detected. # AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname, [NETLIBS="-lnsl $NETLIBS"])) AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt, [NETLIBS="-lsocket $NETLIBS"])) # # Check standard resolver functions. # if test "$build_dirmngr" = "yes"; then _dns_save_libs=$LIBS LIBS="" # Find the system resolver which can always be enabled with # the dirmngr option --standard-resolver. # the double underscore thing is a glibc-ism? AC_SEARCH_LIBS(res_query,resolv bind,, AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no)) AC_SEARCH_LIBS(dn_expand,resolv bind,, AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no)) # macOS renames dn_skipname into res_9_dn_skipname in , # and for some reason fools us into believing we don't need # -lresolv even if we do. Since the test program checking for the # symbol does not include , we need to check for the # renamed symbol explicitly. AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,, AC_SEARCH_LIBS(dn_skipname,resolv bind,, AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no))) if test x"$have_resolver" != xno ; then # Make sure that the BIND 4 resolver interface is workable before # enabling any code that calls it. At some point I'll rewrite the # code to use the BIND 8 resolver API. # We might also want to use libdns instead. AC_MSG_CHECKING([whether the resolver is usable]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],have_resolver=yes,have_resolver=no) AC_MSG_RESULT($have_resolver) # This is Apple-specific and somewhat bizarre as they changed the # define in bind 8 for some reason. if test x"$have_resolver" != xyes ; then AC_MSG_CHECKING( [whether I can make the resolver usable with BIND_8_COMPAT]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT #include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],[have_resolver=yes ; need_compat=yes]) AC_MSG_RESULT($have_resolver) fi fi if test x"$have_resolver" = xyes ; then AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.]) DNSLIBS="$DNSLIBS $LIBS" if test x"$need_compat" = xyes ; then AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism]) fi if test "$use_libdns" = yes; then show_tor_support=yes fi elif test "$use_libdns" = yes; then show_tor_support=yes else AC_MSG_WARN([[ *** *** The system's DNS resolver is not usable. *** Dirmngr functionality is limited. ***]]) show_tor_support="${show_tor_support} (no system resolver)" fi if test "$have_w32_system" = yes; then if test "$use_libdns" = yes; then DNSLIBS="$DNSLIBS -liphlpapi" fi fi LIBS=$_dns_save_libs fi AC_SUBST(DNSLIBS) # # Check for LDAP # # Note that running the check changes the variable # gnupg_have_ldap from "n/a" to "no" or "yes". AC_ARG_ENABLE(ldap, AC_HELP_STRING([--disable-ldap],[disable LDAP support]), [if test "$enableval" = "no"; then gnupg_have_ldap=no; fi]) if test "$gnupg_have_ldap" != "no" ; then if test "$build_dirmngr" = "yes" ; then GNUPG_CHECK_LDAP($NETLIBS) AC_CHECK_LIB(lber, ber_free, [ LBER_LIBS="$LBER_LIBS -llber" AC_DEFINE(HAVE_LBER,1, [defined if liblber is available]) have_lber=yes ]) fi fi AC_SUBST(LBER_LIBS) if test "$gnupg_have_ldap" = "no"; then AC_MSG_WARN([[ *** *** Building without LDAP support. *** No CRL access or X.509 certificate search available. ***]]) fi AM_CONDITIONAL(USE_LDAP, [test "$gnupg_have_ldap" = yes]) if test "$gnupg_have_ldap" = yes ; then AC_DEFINE(USE_LDAP,1,[Defined if LDAP is support]) else use_ldapwrapper=no fi if test "$use_ldapwrapper" = yes; then AC_DEFINE(USE_LDAPWRAPPER,1, [Build dirmngr with LDAP wrapper process]) fi AM_CONDITIONAL(USE_LDAPWRAPPER, test "$use_ldapwrapper" = yes) # # Check for sendmail # # This isn't necessarily sendmail itself, but anything that gives a # sendmail-ish interface to the outside world. That includes Exim, # Postfix, etc. Basically, anything that can handle "sendmail -t". AC_ARG_WITH(mailprog, AC_HELP_STRING([--with-mailprog=NAME], [use "NAME -t" for mail transport]), ,with_mailprog=yes) if test x"$with_mailprog" = xyes ; then AC_PATH_PROG(SENDMAIL,sendmail,,$PATH:/usr/sbin:/usr/libexec:/usr/lib) elif test x"$with_mailprog" != xno ; then AC_MSG_CHECKING([for a mail transport program]) AC_SUBST(SENDMAIL,$with_mailprog) AC_MSG_RESULT($with_mailprog) fi # # Construct a printable name of the OS # case "${host}" in *-mingw32ce*) PRINTABLE_OS_NAME="W32CE" ;; *-mingw32*) PRINTABLE_OS_NAME="MingW32" ;; *-*-cygwin*) PRINTABLE_OS_NAME="Cygwin" ;; i?86-emx-os2 | i?86-*-os2*emx ) PRINTABLE_OS_NAME="OS/2" ;; i?86-*-msdosdjgpp*) PRINTABLE_OS_NAME="MSDOS/DJGPP" try_dynload=no ;; *-linux*) PRINTABLE_OS_NAME="GNU/Linux" ;; *) PRINTABLE_OS_NAME=`uname -s || echo "Unknown"` ;; esac AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME", [A human readable text with the name of the OS]) # # Checking for iconv # if test "$require_iconv" = yes; then AM_ICONV else LIBICONV= LTLIBICONV= AC_SUBST(LIBICONV) AC_SUBST(LTLIBICONV) fi # # Check for gettext # # This is "GNU gnupg" - The project-id script from gettext # needs this string # AC_MSG_NOTICE([checking for gettext]) AM_PO_SUBDIRS AM_GNU_GETTEXT_VERSION([0.17]) if test "$try_gettext" = yes; then AM_GNU_GETTEXT([external],[need-ngettext]) # gettext requires some extra checks. These really should be part of # the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific # function checks to here. AC_CHECK_FUNCS(strchr) else USE_NLS=no USE_INCLUDED_LIBINTL=no BUILD_INCLUDED_LIBINTL=no POSUB=po AC_SUBST(USE_NLS) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(BUILD_INCLUDED_LIBINTL) AC_SUBST(POSUB) fi # We use HAVE_LANGINFO_CODESET in a couple of places. AM_LANGINFO_CODESET # Checks required for our use of locales gt_LC_MESSAGES # # SELinux support # if test "$selinux_support" = yes ; then AC_DEFINE(ENABLE_SELINUX_HACKS,1,[Define to enable SELinux support]) fi # # Checks for header files. # AC_MSG_NOTICE([checking for header files]) AC_HEADER_STDC AC_CHECK_HEADERS([string.h unistd.h langinfo.h termio.h locale.h getopt.h \ pty.h utmp.h pwd.h inttypes.h signal.h sys/select.h \ stdint.h signal.h util.h libutil.h termios.h \ ucred.h sys/sysmacros.h sys/mkdev.h]) AC_HEADER_TIME # # Checks for typedefs, structures, and compiler characteristics. # AC_MSG_NOTICE([checking for system characteristics]) AC_C_CONST AC_C_INLINE AC_C_VOLATILE AC_TYPE_SIZE_T AC_TYPE_MODE_T AC_TYPE_SIGNAL AC_DECL_SYS_SIGLIST gl_HEADER_SYS_SOCKET gl_TYPE_SOCKLEN_T AC_SEARCH_LIBS([inet_addr], [nsl]) AC_ARG_ENABLE(endian-check, AC_HELP_STRING([--disable-endian-check], [disable the endian check and trust the OS provided macros]), endiancheck=$enableval,endiancheck=yes) if test x"$endiancheck" = xyes ; then GNUPG_CHECK_ENDIAN fi # fixme: we should get rid of the byte type GNUPG_CHECK_TYPEDEF(byte, HAVE_BYTE_TYPEDEF) GNUPG_CHECK_TYPEDEF(ushort, HAVE_USHORT_TYPEDEF) GNUPG_CHECK_TYPEDEF(ulong, HAVE_ULONG_TYPEDEF) GNUPG_CHECK_TYPEDEF(u16, HAVE_U16_TYPEDEF) GNUPG_CHECK_TYPEDEF(u32, HAVE_U32_TYPEDEF) AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) AC_HEADER_TIME AC_CHECK_SIZEOF(time_t,,[[ #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif ]]) GNUPG_TIME_T_UNSIGNED if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ || test "$ac_cv_sizeof_unsigned_long" = "0"; then AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]); fi # # Checks for library functions. # AC_MSG_NOTICE([checking for library functions]) AC_CHECK_DECLS(getpagesize) AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK AC_CHECK_FUNCS([strerror strlwr tcgetattr mmap canonicalize_file_name]) AC_CHECK_FUNCS([strcasecmp strncasecmp ctermid times gmtime_r strtoull]) AC_CHECK_FUNCS([setenv unsetenv fcntl ftruncate inet_ntop]) AC_CHECK_FUNCS([canonicalize_file_name]) AC_CHECK_FUNCS([gettimeofday getrusage getrlimit setrlimit clock_gettime]) AC_CHECK_FUNCS([atexit raise getpagesize strftime nl_langinfo setlocale]) AC_CHECK_FUNCS([waitpid wait4 sigaction sigprocmask pipe getaddrinfo]) AC_CHECK_FUNCS([ttyname rand ftello fsync stat lstat]) AC_CHECK_FUNCS([memicmp stpcpy strsep strlwr strtoul memmove stricmp strtol \ memrchr isascii timegm getrusage setrlimit stat setlocale \ flockfile funlockfile getpwnam getpwuid \ getenv inet_pton strpbrk]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select # based wait function we want it only if it is available in libc. _save_libs="$LIBS" AC_SEARCH_LIBS([nanosleep], [], [AC_DEFINE(HAVE_NANOSLEEP,1, [Define to 1 if you have the `nanosleep' function in libc.])]) LIBS="$_save_libs" # See whether libc supports the Linux inotify interface case "${host}" in *-*-linux*) AC_CHECK_FUNCS([inotify_init]) ;; esac if test "$have_android_system" = yes; then # On Android ttyname is a stub but prints an error message. AC_DEFINE(HAVE_BROKEN_TTYNAME,1, [Defined if ttyname does not work properly]) fi AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) # Dirmngr requires mmap on Unix systems. if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then - AC_MSG_ERROR([[Sorry, the current implemenation requires mmap.]]) + AC_MSG_ERROR([[Sorry, the current implementation requires mmap.]]) fi # # Check for the getsockopt SO_PEERCRED, etc. # AC_CHECK_MEMBERS([struct ucred.pid, struct ucred.cr_pid, struct sockpeercred.pid], [], [], [#include #include ]) # (Open)Solaris AC_CHECK_FUNCS([getpeerucred]) # # W32 specific test # GNUPG_FUNC_MKDIR_TAKES_ONE_ARG # # Sanity check regex. Tests adapted from mutt. # AC_MSG_CHECKING([whether regular expression support is requested]) AC_ARG_ENABLE(regex, AC_HELP_STRING([--disable-regex], [do not handle regular expressions in trust signatures]), use_regex=$enableval, use_regex=yes) AC_MSG_RESULT($use_regex) if test "$use_regex" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(regex, AC_HELP_STRING([--with-regex=DIR],[look for regex in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Does the system have regex functions at all? AC_SEARCH_LIBS([regcomp], [regex]) AC_CHECK_FUNC(regcomp, gnupg_cv_have_regex=yes, gnupg_cv_have_regex=no) if test $gnupg_cv_have_regex = no; then use_regex=no else if test x"$cross_compiling" = xyes; then AC_MSG_WARN([cross compiling; assuming regexp libray is not broken]) else AC_CACHE_CHECK([whether your system's regexp library is broken], [gnupg_cv_regex_broken], AC_TRY_RUN([ #include #include main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }], gnupg_cv_regex_broken=no, gnupg_cv_regex_broken=yes, gnupg_cv_regex_broken=yes)) if test $gnupg_cv_regex_broken = yes; then AC_MSG_WARN([your regex is broken - disabling regex use]) use_regex=no fi fi fi CPPFLAGS="${_cppflags}" LDFLAGS="${_ldflags}" fi if test "$use_regex" != yes ; then AC_DEFINE(DISABLE_REGEX,1, [Define to disable regular expression support]) fi AM_CONDITIONAL(DISABLE_REGEX, test x"$use_regex" != xyes) # # Do we have zlib? Must do it here because Solaris failed # when compiling a conftest (due to the "-lz" from LIBS). # Note that we combine zlib and bzlib2 in ZLIBS. # if test "$use_zip" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(zlib, [ --with-zlib=DIR use libz in DIR],[ if test -d "$withval"; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ]) AC_CHECK_HEADER(zlib.h, AC_CHECK_LIB(z, deflateInit2_, [ ZLIBS="-lz" AC_DEFINE(HAVE_ZIP,1, [Defined if ZIP and ZLIB are supported]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}) fi # # Check whether we can support bzip2 # if test "$use_bzip2" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(bzip2, AC_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Checking alongside stdio.h as an early version of bzip2 (1.0) # required stdio.h to be included before bzlib.h, and Solaris 9 is # woefully out of date. if test "$withval" != no ; then AC_CHECK_HEADER(bzlib.h, AC_CHECK_LIB(bz2,BZ2_bzCompressInit, [ have_bz2=yes ZLIBS="$ZLIBS -lbz2" AC_DEFINE(HAVE_BZIP2,1, [Defined if the bz2 compression library is available]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include ]) fi fi AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes") AC_SUBST(ZLIBS) # Check for readline support GNUPG_CHECK_READLINE if test "$development_version" = yes; then AC_DEFINE(IS_DEVELOPMENT_VERSION,1, [Defined if this is not a regular release]) fi AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes) GNUPG_CHECK_GNUMAKE # Add some extra libs here so that previous tests don't fail for # mysterious reasons - the final link step should bail out. # W32SOCKLIBS is also defined so that if can be used for tools not # requiring any network stuff but linking to code in libcommon which # tracks in winsock stuff (e.g. init_common_subsystems). if test "$have_w32_system" = yes; then if test "$have_w32ce_system" = yes; then W32SOCKLIBS="-lws2" else W32SOCKLIBS="-lws2_32" fi NETLIBS="${NETLIBS} ${W32SOCKLIBS}" fi AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) # # Setup gcc specific options # USE_C99_CFLAGS= AC_MSG_NOTICE([checking for cc features]) if test "$GCC" = yes; then mycflags= mycflags_save=$CFLAGS - # Check whether gcc does not emit a diagnositc for unknow -Wno-* + # Check whether gcc does not emit a diagnositc for unknown -Wno-* # options. This is the case for gcc >= 4.6 AC_MSG_CHECKING([if gcc ignores unknown -Wno-* options]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6 ) #kickerror #endif]],[])],[_gcc_silent_wno=yes],[_gcc_silent_wno=no]) AC_MSG_RESULT($_gcc_silent_wno) # Note that it is okay to use CFLAGS here because these are just # warning options and the user should have a chance of overriding # them. if test "$USE_MAINTAINER_MODE" = "yes"; then mycflags="$mycflags -O3 -Wall -Wcast-align -Wshadow -Wstrict-prototypes" mycflags="$mycflags -Wformat -Wno-format-y2k -Wformat-security" if test x"$_gcc_silent_wno" = xyes ; then _gcc_wopt=yes else AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) CFLAGS="-Wno-missing-field-initializers" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_wopt=yes],[_gcc_wopt=no]) AC_MSG_RESULT($_gcc_wopt) fi if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -W -Wno-sign-compare" mycflags="$mycflags -Wno-missing-field-initializers" fi AC_MSG_CHECKING([if gcc supports -Wdeclaration-after-statement]) CFLAGS="-Wdeclaration-after-statement" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wdeclaration-after-statement" fi AC_MSG_CHECKING([if gcc supports -Wlogical-op and -Wvla]) CFLAGS="-Wlogical-op -Wvla" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wlogical-op -Wvla" fi else mycflags="$mycflags -Wall" fi if test x"$_gcc_silent_wno" = xyes ; then _gcc_psign=yes else AC_MSG_CHECKING([if gcc supports -Wno-pointer-sign]) CFLAGS="-Wno-pointer-sign" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_psign=yes],[_gcc_psign=no]) AC_MSG_RESULT($_gcc_psign) fi if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wno-pointer-sign" fi AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) CFLAGS="-Wpointer-arith" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_psign=yes,_gcc_psign=no) AC_MSG_RESULT($_gcc_psign) if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wpointer-arith" fi CFLAGS="$mycflags $mycflags_save" if test "$use_libdns" = yes; then # dirmngr/dns.{c,h} require C99 and GNU extensions. */ USE_C99_CFLAGS="-std=gnu99" fi fi AC_SUBST(USE_C99_CFLAGS) # # This is handy for debugging so the compiler doesn't rearrange # things and eliminate variables. # AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable compiler optimization]), [if test $enableval = no ; then CFLAGS=`echo $CFLAGS | sed s/-O[[1-9]]\ /-O0\ /g` fi]) # # We do not want support for the GNUPG_BUILDDIR environment variable # in a released version. However, our regression tests suite requires # this and thus we build with support for it during "make distcheck". # This configure option implements this along with the top Makefile's # AM_DISTCHECK_CONFIGURE_FLAGS. # gnupg_builddir_envvar=no AC_ARG_ENABLE(gnupg-builddir-envvar,, gnupg_builddir_envvar=$enableval) if test x"$gnupg_builddir_envvar" = x"yes"; then AC_DEFINE(ENABLE_GNUPG_BUILDDIR_ENVVAR, 1, [This is only used with "make distcheck"]) fi # # Add user CFLAGS. # CFLAGS="$CFLAGS $CFLAGS_orig" # # Decide what to build # build_scdaemon_extra="" if test "$build_scdaemon" = "yes"; then if test $have_libusb = no; then build_scdaemon_extra="without internal CCID driver" fi if test -n "$build_scdaemon_extra"; then build_scdaemon_extra="(${build_scdaemon_extra})" fi fi # # Set variables for use by automake makefiles. # AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes") AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes") AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes) AM_CONDITIONAL(NO_TRUST_MODELS, test "$use_trust_models" = no) AM_CONDITIONAL(USE_TOFU, test "$use_tofu" = yes) # # Set some defines for use gpgconf. # if test "$build_gpg" = yes ; then AC_DEFINE(BUILD_WITH_GPG,1,[Defined if GPG is to be build]) fi if test "$build_gpgsm" = yes ; then AC_DEFINE(BUILD_WITH_GPGSM,1,[Defined if GPGSM is to be build]) fi if test "$build_agent" = yes ; then AC_DEFINE(BUILD_WITH_AGENT,1,[Defined if GPG-AGENT is to be build]) fi if test "$build_scdaemon" = yes ; then AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build]) fi if test "$build_dirmngr" = yes ; then AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if SCDAEMON is to be build]) fi if test "$build_g13" = yes ; then AC_DEFINE(BUILD_WITH_G13,1,[Defined if G13 is to be build]) fi # # Define Name strings # AC_DEFINE_UNQUOTED(GNUPG_NAME, "GnuPG", [The name of the project]) AC_DEFINE_UNQUOTED(GPG_NAME, "gpg", [The name of the OpenPGP tool]) AC_DEFINE_UNQUOTED(GPG_DISP_NAME, "GnuPG", [The displayed name of gpg]) AC_DEFINE_UNQUOTED(GPGSM_NAME, "gpgsm", [The name of the S/MIME tool]) AC_DEFINE_UNQUOTED(GPGSM_DISP_NAME, "GPGSM", [The displayed name of gpgsm]) AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent]) AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent", [The displayed name of gpg-agent]) AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr]) AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr", [The displayed name of dirmngr]) AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool]) AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13]) AC_DEFINE_UNQUOTED(GPGCONF_NAME, "gpgconf", [The name of the gpgconf tool]) AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf", [The displayed name of gpgconf]) AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool]) AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent", [The name of the agent socket]) AC_DEFINE_UNQUOTED(GPG_AGENT_EXTRA_SOCK_NAME, "S.gpg-agent.extra", [The name of the agent socket for remote access]) AC_DEFINE_UNQUOTED(GPG_AGENT_BROWSER_SOCK_NAME, "S.gpg-agent.browser", [The name of the agent socket for browsers]) AC_DEFINE_UNQUOTED(GPG_AGENT_SSH_SOCK_NAME, "S.gpg-agent.ssh", [The name of the agent socket for ssh]) AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, "hkps://hkps.pool.sks-keyservers.net", [The default keyserver for dirmngr to use, if none is explicitly given]) AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix]) if test "$have_w32_system" = yes; then AC_DEFINE_UNQUOTED(GNUPG_REGISTRY_DIR, "\\\\Software\\\\GNU\\\\GnuPG", [The directory part of the W32 registry keys]) fi # # Provide information about the build. # BUILD_REVISION="mym4_revision" AC_SUBST(BUILD_REVISION) AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", [GIT commit id revision used to build this package]) changequote(,)dnl BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'` changequote([,])dnl BUILD_VERSION="${BUILD_VERSION}mym4_revision_dec" BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,` AC_SUBST(BUILD_VERSION) AC_SUBST(BUILD_FILEVERSION) AC_ARG_ENABLE([build-timestamp], AC_HELP_STRING([--enable-build-timestamp], [set an explicit build timestamp for reproducibility. (default is the current time in ISO-8601 format)]), [if test "$enableval" = "yes"; then BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date` else BUILD_TIMESTAMP="$enableval" fi BUILD_HOSTNAME="$ac_hostname"], [BUILD_TIMESTAMP="" BUILD_HOSTNAME=""]) AC_SUBST(BUILD_TIMESTAMP) AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP", [The time this package was configured for a build]) AC_SUBST(BUILD_HOSTNAME) # # Print errors here so that they are visible all # together and the user can acquire them all together. # die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libgpg-error *** (at least version $NEED_GPG_ERROR_VERSION is required.) ***]]) fi if test "$have_libgcrypt" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgcrypt to build this program. ** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libgcrypt/ *** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required). ***]]) fi if test "$have_ksba" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libksba to build this program. *** This library is for example available at *** ftp://ftp.gnupg.org/gcrypt/libksba/ *** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required). ***]]) fi if test "$gnupg_have_ldap" = yes; then if test "$have_w32ce_system" = yes; then AC_MSG_NOTICE([[ *** Note that CeGCC might be broken, a package fixing this is: *** http://files.kolab.org/local/windows-ce/ *** source/wldap32_0.1-mingw32ce.orig.tar.gz *** binary/wldap32-ce-arm-dev_0.1-1_all.deb ***]]) fi fi if test "$have_npth" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** It is now required to build with support for the *** New Portable Threads Library (nPth). Please install this *** library first. The library is for example available at *** ftp://ftp.gnupg.org/gcrypt/npth/ *** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required). ***]]) fi if test "$require_iconv" = yes; then if test "$am_func_iconv" != yes; then die=yes AC_MSG_NOTICE([[ *** *** The system does not provide a working iconv function. Please *** install a suitable library; for example GNU Libiconv which is *** available at: *** http://ftp.gnu.org/gnu/libiconv/ ***]]) fi fi if test "$use_ccid_driver" = yes; then if test "$have_libusb" != yes; then die=yes AC_MSG_NOTICE([[ *** *** You need libusb to build the internal ccid driver. Please *** install a libusb suitable for your system. ***]]) fi fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi AC_CONFIG_FILES([ m4/Makefile Makefile po/Makefile.in common/Makefile common/w32info-rc.h kbx/Makefile g10/Makefile sm/Makefile agent/Makefile scd/Makefile g13/Makefile dirmngr/Makefile tools/gpg-zip tools/Makefile doc/Makefile tests/Makefile tests/gpgscm/Makefile tests/openpgp/Makefile tests/migrations/Makefile tests/gpgsm/Makefile tests/gpgme/Makefile tests/pkits/Makefile g10/gpg.w32-manifest ]) AC_OUTPUT echo " GnuPG v${VERSION} has been configured as follows: Revision: mym4_revision (mym4_revision_dec) Platform: $PRINTABLE_OS_NAME ($host) OpenPGP: $build_gpg S/MIME: $build_gpgsm Agent: $build_agent Smartcard: $build_scdaemon $build_scdaemon_extra G13: $build_g13 Dirmngr: $build_dirmngr Gpgtar: $build_gpgtar WKS tools: $build_wks_tools Protect tool: $show_gnupg_protect_tool_pgm LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start Readline support: $gnupg_cv_have_readline LDAP support: $gnupg_have_ldap TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support " if test x"$use_regex" != xyes ; then echo " Warning: No regular expression support available. OpenPGP trust signatures won't work. gpg-check-pattern will not be built. " fi if test "x${gpg_config_script_warn}" != x; then cat <. */ #include #include #include #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "../common/ksba-io-support.h" #include "crlfetch.h" #include "certcache.h" #define MAX_NONPERM_CACHED_CERTS 1000 /* Constants used to classify search patterns. */ enum pattern_class { PATTERN_UNKNOWN = 0, PATTERN_EMAIL, PATTERN_EMAIL_SUBSTR, PATTERN_FINGERPRINT16, PATTERN_FINGERPRINT20, PATTERN_SHORT_KEYID, PATTERN_LONG_KEYID, PATTERN_SUBJECT, PATTERN_SERIALNO, PATTERN_SERIALNO_ISSUER, PATTERN_ISSUER, PATTERN_SUBSTR }; /* A certificate cache item. This consists of a the KSBA cert object and some meta data for easier lookup. We use a hash table to keep track of all items and use the (randomly distributed) first byte of the fingerprint directly as the hash which makes it pretty easy. */ struct cert_item_s { struct cert_item_s *next; /* Next item with the same hash value. */ ksba_cert_t cert; /* The KSBA cert object or NULL is this is not a valid item. */ unsigned char fpr[20]; /* The fingerprint of this object. */ char *issuer_dn; /* The malloced issuer DN. */ ksba_sexp_t sn; /* The malloced serial number */ char *subject_dn; /* The malloced subject DN - maybe NULL. */ /* If this field is set the certificate has been taken from some * configuration and shall not be flushed from the cache. */ unsigned int permanent:1; /* If this field is set the certificate is trusted. The actual * value is a (possible) combination of CERTTRUST_CLASS values. */ unsigned int trustclasses:4; }; typedef struct cert_item_s *cert_item_t; /* The actual cert cache consisting of 256 slots for items indexed by the first byte of the fingerprint. */ static cert_item_t cert_cache[256]; /* This is the global cache_lock variable. In general locking is not needed but it would take extra efforts to make sure that no indirect use of npth functions is done, so we simply lock it always. Note: We can't use static initialization, as that is not available through w32-pth. */ static npth_rwlock_t cert_cache_lock; /* Flag to track whether the cache has been initialized. */ static int initialization_done; /* Total number of non-permanent certificates. */ static unsigned int total_nonperm_certificates; #ifdef HAVE_W32_SYSTEM /* We load some functions dynamically. Provide typedefs for tehse - * fucntions. */ + * functions. */ typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE) (HCRYPTPROV hProv, LPCSTR szSubsystemProtocol); typedef PCCERT_CONTEXT (WINAPI *CERTENUMCERTIFICATESINSTORE) (HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); typedef WINBOOL (WINAPI *CERTCLOSESTORE) (HCERTSTORE hCertStore,DWORD dwFlags); #endif /*HAVE_W32_SYSTEM*/ /* Helper to do the cache locking. */ static void init_cache_lock (void) { int err; err = npth_rwlock_init (&cert_cache_lock, NULL); if (err) log_fatal (_("can't initialize certificate cache lock: %s\n"), strerror (err)); } static void acquire_cache_read_lock (void) { int err; err = npth_rwlock_rdlock (&cert_cache_lock); if (err) log_fatal (_("can't acquire read lock on the certificate cache: %s\n"), strerror (err)); } static void acquire_cache_write_lock (void) { int err; err = npth_rwlock_wrlock (&cert_cache_lock); if (err) log_fatal (_("can't acquire write lock on the certificate cache: %s\n"), strerror (err)); } static void release_cache_lock (void) { int err; err = npth_rwlock_unlock (&cert_cache_lock); if (err) log_fatal (_("can't release lock on the certificate cache: %s\n"), strerror (err)); } /* Return false if both serial numbers match. Can't be used for sorting. */ static int compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 ) { unsigned char *a = serial1; unsigned char *b = serial2; return cmp_simple_canon_sexp (a, b); } /* Return a malloced canonical S-Expression with the serial number * converted from the hex string HEXSN. Return NULL on memory * error. */ ksba_sexp_t hexsn_to_sexp (const char *hexsn) { char *buffer, *p; size_t len; char numbuf[40]; len = unhexify (NULL, hexsn); snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len); buffer = xtrymalloc (strlen (numbuf) + len + 2 ); if (!buffer) return NULL; p = stpcpy (buffer, numbuf); len = unhexify (p, hexsn); p[len] = ')'; p[len+1] = 0; return buffer; } /* Compute the fingerprint of the certificate CERT and put it into the 20 bytes large buffer DIGEST. Return address of this buffer. */ unsigned char * cert_compute_fpr (ksba_cert_t cert, unsigned char *digest) { gpg_error_t err; gcry_md_hd_t md; err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err)); err = ksba_cert_hash (cert, 0, HASH_FNC, md); if (err) { log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err)); memset (digest, 0xff, 20); /* Use a dummy value. */ } else { gcry_md_final (md); memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20); } gcry_md_close (md); return digest; } /* Cleanup one slot. This releases all resourses but keeps the actual slot in the cache marked for reuse. */ static void clean_cache_slot (cert_item_t ci) { ksba_cert_t cert; if (!ci->cert) return; /* Already cleaned. */ ksba_free (ci->sn); ci->sn = NULL; ksba_free (ci->issuer_dn); ci->issuer_dn = NULL; ksba_free (ci->subject_dn); ci->subject_dn = NULL; cert = ci->cert; ci->cert = NULL; ci->permanent = 0; ci->trustclasses = 0; ksba_cert_release (cert); } /* Put the certificate CERT into the cache. It is assumed that the * cache is locked while this function is called. * * FROM_CONFIG indicates that CERT is a permanent certificate and * should stay in the cache. IS_TRUSTED requests that the trusted * flag is set for the certificate; a value of 1 indicates the * cert is trusted due to GnuPG mechanisms, a value of 2 indicates * that it is trusted because it has been taken from the system's * store of trusted certificates. If FPR_BUFFER is not NULL the * fingerprint of the certificate will be stored there. FPR_BUFFER * needs to point to a buffer of at least 20 bytes. The fingerprint * will be stored on success or when the function returns * GPG_ERR_DUP_VALUE. */ static gpg_error_t put_cert (ksba_cert_t cert, int permanent, unsigned int trustclass, void *fpr_buffer) { unsigned char help_fpr_buffer[20], *fpr; cert_item_t ci; fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer; /* If we already reached the caching limit, drop a couple of certs * from the cache. Our dropping strategy is simple: We keep a * static index counter and use this to start looking for * certificates, then we drop 5 percent of the oldest certificates * starting at that index. For a large cache this is a fair way of * removing items. An LRU strategy would be better of course. * Because we append new entries to the head of the list and we want * to remove old ones first, we need to do this from the tail. The * implementation is not very efficient but compared to the long * time it takes to retrieve a certificate from an external resource * it seems to be reasonable. */ if (!permanent && total_nonperm_certificates >= MAX_NONPERM_CACHED_CERTS) { static int idx; cert_item_t ci_mark; int i; unsigned int drop_count; drop_count = MAX_NONPERM_CACHED_CERTS / 20; if (drop_count < 2) drop_count = 2; log_info (_("dropping %u certificates from the cache\n"), drop_count); assert (idx < 256); for (i=idx; drop_count; i = ((i+1)%256)) { ci_mark = NULL; for (ci = cert_cache[i]; ci; ci = ci->next) if (ci->cert && !ci->permanent) ci_mark = ci; if (ci_mark) { clean_cache_slot (ci_mark); drop_count--; total_nonperm_certificates--; } } if (i==idx) idx++; else idx = i; idx %= 256; } cert_compute_fpr (cert, fpr); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) return gpg_error (GPG_ERR_DUP_VALUE); /* Try to reuse an existing entry. */ for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (!ci->cert) break; if (!ci) { /* No: Create a new entry. */ ci = xtrycalloc (1, sizeof *ci); if (!ci) return gpg_error_from_errno (errno); ci->next = cert_cache[*fpr]; cert_cache[*fpr] = ci; } ksba_cert_ref (cert); ci->cert = cert; memcpy (ci->fpr, fpr, 20); ci->sn = ksba_cert_get_serial (cert); ci->issuer_dn = ksba_cert_get_issuer (cert, 0); if (!ci->issuer_dn || !ci->sn) { clean_cache_slot (ci); return gpg_error (GPG_ERR_INV_CERT_OBJ); } ci->subject_dn = ksba_cert_get_subject (cert, 0); ci->permanent = !!permanent; ci->trustclasses = trustclass; if (!permanent) total_nonperm_certificates++; return 0; } /* Load certificates from the directory DIRNAME. All certificates matching the pattern "*.crt" or "*.der" are loaded. We assume that certificates are DER encoded and not PEM encapsulated. The cache should be in a locked state when calling this function. */ static gpg_error_t load_certs_from_dir (const char *dirname, unsigned int trustclass) { gpg_error_t err; DIR *dir; struct dirent *ep; char *p; size_t n; estream_t fp; ksba_reader_t reader; ksba_cert_t cert; char *fname = NULL; dir = opendir (dirname); if (!dir) { return 0; /* We do not consider this a severe error. */ } while ( (ep=readdir (dir)) ) { p = ep->d_name; if (*p == '.' || !*p) continue; /* Skip any hidden files and invalid entries. */ n = strlen (p); if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der"))) continue; /* Not the desired "*.crt" or "*.der" pattern. */ xfree (fname); fname = make_filename (dirname, p, NULL); fp = es_fopen (fname, "rb"); if (!fp) { log_error (_("can't open '%s': %s\n"), fname, strerror (errno)); continue; } err = create_estream_ksba_reader (&reader, fp); if (err) { es_fclose (fp); continue; } err = ksba_cert_new (&cert); if (!err) err = ksba_cert_read_der (cert, reader); ksba_reader_release (reader); es_fclose (fp); if (err) { log_error (_("can't parse certificate '%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); continue; } err = put_cert (cert, 1, trustclass, NULL); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate '%s' already cached\n"), fname); else if (!err) { if (trustclass) log_info (_("trusted certificate '%s' loaded\n"), fname); else log_info (_("certificate '%s' loaded\n"), fname); if (opt.verbose) { p = get_fingerprint_hexstring_colon (cert); log_info (_(" SHA1 fingerprint = %s\n"), p); xfree (p); cert_log_name (_(" issuer ="), cert); cert_log_subject (_(" subject ="), cert); } } else log_error (_("error loading certificate '%s': %s\n"), fname, gpg_strerror (err)); ksba_cert_release (cert); } xfree (fname); closedir (dir); return 0; } /* Load certificates from FILE. The certificates are expected to be * PEM encoded so that it is possible to load several certificates. * TRUSTCLASSES is used to mark the certificates as trusted. The * cache should be in a locked state when calling this function. * NO_ERROR repalces an error message when FNAME was not found by an * information message. */ static gpg_error_t load_certs_from_file (const char *fname, unsigned int trustclasses, int no_error) { gpg_error_t err; estream_t fp = NULL; gnupg_ksba_io_t ioctx = NULL; ksba_reader_t reader; ksba_cert_t cert = NULL; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENONET && no_error) log_info (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); else log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } err = gnupg_ksba_create_reader (&ioctx, (GNUPG_KSBA_IO_AUTODETECT | GNUPG_KSBA_IO_MULTIPEM), fp, &reader); if (err) { log_error ("can't create reader: %s\n", gpg_strerror (err)); goto leave; } /* Loop to read all certificates from the file. */ do { ksba_cert_release (cert); cert = NULL; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_read_der (cert, reader); if (err) { if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; else log_error (_("can't parse certificate '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } err = put_cert (cert, 1, trustclasses, NULL); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate '%s' already cached\n"), fname); else if (err) log_error (_("error loading certificate '%s': %s\n"), fname, gpg_strerror (err)); else if (opt.verbose > 1) { char *p; log_info (_("trusted certificate '%s' loaded\n"), fname); p = get_fingerprint_hexstring_colon (cert); log_info (_(" SHA1 fingerprint = %s\n"), p); xfree (p); cert_log_name (_(" issuer ="), cert); cert_log_subject (_(" subject ="), cert); } ksba_reader_clear (reader, NULL, NULL); } while (!gnupg_ksba_reader_eof_seen (ioctx)); leave: ksba_cert_release (cert); gnupg_ksba_destroy_reader (ioctx); es_fclose (fp); return err; } #ifdef HAVE_W32_SYSTEM /* Load all certificates from the Windows store named STORENAME. All * certificates are considered to be system provided trusted * certificates. The cache should be in a locked state when calling * this function. */ static void load_certs_from_w32_store (const char *storename) { static int init_done; static CERTOPENSYSTEMSTORE pCertOpenSystemStore; static CERTENUMCERTIFICATESINSTORE pCertEnumCertificatesInStore; static CERTCLOSESTORE pCertCloseStore; gpg_error_t err; HCERTSTORE w32store; const CERT_CONTEXT *w32cert; ksba_cert_t cert = NULL; unsigned int count = 0; /* Initialize on the first use. */ if (!init_done) { static HANDLE hCrypt32; init_done = 1; hCrypt32 = LoadLibrary ("Crypt32.dll"); if (!hCrypt32) { log_error ("can't load Crypt32.dll: %s\n", w32_strerror (-1)); return; } pCertOpenSystemStore = (CERTOPENSYSTEMSTORE) GetProcAddress (hCrypt32, "CertOpenSystemStoreA"); pCertEnumCertificatesInStore = (CERTENUMCERTIFICATESINSTORE) GetProcAddress (hCrypt32, "CertEnumCertificatesInStore"); pCertCloseStore = (CERTCLOSESTORE) GetProcAddress (hCrypt32, "CertCloseStore"); if ( !pCertOpenSystemStore || !pCertEnumCertificatesInStore || !pCertCloseStore) { log_error ("can't load crypt32.dll: %s\n", "missing function"); pCertOpenSystemStore = NULL; } } if (!pCertOpenSystemStore) return; /* Not initialized. */ w32store = pCertOpenSystemStore (0, storename); if (!w32store) { log_error ("can't open certificate store '%s': %s\n", storename, w32_strerror (-1)); return; } w32cert = NULL; while ((w32cert = pCertEnumCertificatesInStore (w32store, w32cert))) { if (w32cert->dwCertEncodingType == X509_ASN_ENCODING) { ksba_cert_release (cert); cert = NULL; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, w32cert->pbCertEncoded, w32cert->cbCertEncoded); if (err) { log_error (_("can't parse certificate '%s': %s\n"), storename, gpg_strerror (err)); break; } err = put_cert (cert, 1, CERTTRUST_CLASS_SYSTEM, NULL); if (!err) count++; if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) { if (DBG_X509) log_debug (_("certificate '%s' already cached\n"), storename); } else if (err) log_error (_("error loading certificate '%s': %s\n"), storename, gpg_strerror (err)); else if (opt.verbose > 1) { char *p; log_info (_("trusted certificate '%s' loaded\n"), storename); p = get_fingerprint_hexstring_colon (cert); log_info (_(" SHA1 fingerprint = %s\n"), p); xfree (p); cert_log_name (_(" issuer ="), cert); cert_log_subject (_(" subject ="), cert); } } } ksba_cert_release (cert); pCertCloseStore (w32store, 0); if (DBG_X509) log_debug ("number of certs loaded from store '%s': %u\n", storename, count); } #endif /*HAVE_W32_SYSTEM*/ /* Load the trusted certificates provided by the system. */ static gpg_error_t load_certs_from_system (void) { #ifdef HAVE_W32_SYSTEM load_certs_from_w32_store ("ROOT"); load_certs_from_w32_store ("CA"); return 0; #else /*!HAVE_W32_SYSTEM*/ /* A list of certificate bundles to try. */ static struct { const char *name; } table[] = { #ifdef DEFAULT_TRUST_STORE_FILE { DEFAULT_TRUST_STORE_FILE } #else { "/etc/ssl/ca-bundle.pem" }, { "/etc/ssl/certs/ca-certificates.crt" }, { "/etc/pki/tls/cert.pem" }, { "/usr/local/share/certs/ca-root-nss.crt" }, { "/etc/ssl/cert.pem" } #endif /*!DEFAULT_TRUST_STORE_FILE*/ }; int idx; gpg_error_t err = 0; for (idx=0; idx < DIM (table); idx++) if (!access (table[idx].name, F_OK)) { /* Take the first available bundle. */ err = load_certs_from_file (table[idx].name, CERTTRUST_CLASS_SYSTEM, 0); break; } return err; #endif /*!HAVE_W32_SYSTEM*/ } /* Initialize the certificate cache if not yet done. */ void cert_cache_init (strlist_t hkp_cacerts) { char *fname; strlist_t sl; if (initialization_done) return; init_cache_lock (); acquire_cache_write_lock (); load_certs_from_system (); fname = make_filename_try (gnupg_sysconfdir (), "trusted-certs", NULL); if (fname) load_certs_from_dir (fname, CERTTRUST_CLASS_CONFIG); xfree (fname); fname = make_filename_try (gnupg_sysconfdir (), "extra-certs", NULL); if (fname) load_certs_from_dir (fname, 0); xfree (fname); fname = make_filename_try (gnupg_datadir (), "sks-keyservers.netCA.pem", NULL); if (fname) load_certs_from_file (fname, CERTTRUST_CLASS_HKPSPOOL, 1); xfree (fname); for (sl = hkp_cacerts; sl; sl = sl->next) load_certs_from_file (sl->d, CERTTRUST_CLASS_HKP, 0); initialization_done = 1; release_cache_lock (); cert_cache_print_stats (); } /* Deinitialize the certificate cache. With FULL set to true even the unused certificate slots are released. */ void cert_cache_deinit (int full) { cert_item_t ci, ci2; int i; if (!initialization_done) return; acquire_cache_write_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) clean_cache_slot (ci); if (full) { for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci2) { ci2 = ci->next; xfree (ci); } cert_cache[i] = NULL; } } total_nonperm_certificates = 0; initialization_done = 0; release_cache_lock (); } /* Print some statistics to the log file. */ void cert_cache_print_stats (void) { cert_item_t ci; int idx; unsigned int n_nonperm = 0; unsigned int n_permanent = 0; unsigned int n_trusted = 0; unsigned int n_trustclass_system = 0; unsigned int n_trustclass_config = 0; unsigned int n_trustclass_hkp = 0; unsigned int n_trustclass_hkpspool = 0; acquire_cache_read_lock (); for (idx = 0; idx < 256; idx++) for (ci=cert_cache[idx]; ci; ci = ci->next) if (ci->cert) { if (ci->permanent) n_permanent++; else n_nonperm++; if (ci->trustclasses) { n_trusted++; if ((ci->trustclasses & CERTTRUST_CLASS_SYSTEM)) n_trustclass_system++; if ((ci->trustclasses & CERTTRUST_CLASS_CONFIG)) n_trustclass_config++; if ((ci->trustclasses & CERTTRUST_CLASS_HKP)) n_trustclass_hkp++; if ((ci->trustclasses & CERTTRUST_CLASS_HKPSPOOL)) n_trustclass_hkpspool++; } } release_cache_lock (); log_info (_("permanently loaded certificates: %u\n"), n_permanent); log_info (_(" runtime cached certificates: %u\n"), n_nonperm); log_info (_(" trusted certificates: %u (%u,%u,%u,%u)\n"), n_trusted, n_trustclass_system, n_trustclass_config, n_trustclass_hkp, n_trustclass_hkpspool); } /* Put CERT into the certificate cache. */ gpg_error_t cache_cert (ksba_cert_t cert) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, NULL); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) log_info (_("certificate already cached\n")); else if (!err) log_info (_("certificate cached\n")); else log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Put CERT into the certificate cache and store the fingerprint of the certificate into FPR_BUFFER. If the certificate is already in the cache do not print a warning; just store the fingerprint. FPR_BUFFER needs to be at least 20 bytes. */ gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer) { gpg_error_t err; acquire_cache_write_lock (); err = put_cert (cert, 0, 0, fpr_buffer); release_cache_lock (); if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) err = 0; if (err) log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return err; } /* Return a certificate object for the given fingerprint. FPR is expected to be a 20 byte binary SHA-1 fingerprint. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byfpr (const unsigned char *fpr) { cert_item_t ci; acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } release_cache_lock (); return NULL; } /* Return a certificate object for the given fingerprint. STRING is expected to be a SHA-1 fingerprint in standard hex notation with or without colons. If no matching certificate is available in the cache NULL is returned. The caller must release a returned certificate. Note that although we are using reference counting the caller should not just compare the pointers to check for identical certificates. */ ksba_cert_t get_cert_byhexfpr (const char *string) { unsigned char fpr[20]; const char *s; int i; if (strchr (string, ':')) { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);) { if (s[2] && s[2] != ':') break; /* Invalid string. */ fpr[i++] = xtoi_2 (s); s += 2; if (i!= 20 && *s == ':') s++; } } else { for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 ) fpr[i++] = xtoi_2 (s); } if (i!=20 || *s) { log_error (_("invalid SHA1 fingerprint string '%s'\n"), string); return NULL; } return get_cert_byfpr (fpr); } /* Return the certificate matching ISSUER_DN and SERIALNO. */ ksba_cert_t get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno) { /* Simple and inefficient implementation. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn) && !compare_serialno (ci->sn, serialno)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching ISSUER_DN. SEQ should initially be set to 0 and bumped up to get the next issuer with that DN. */ ksba_cert_t get_cert_byissuer (const char *issuer_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return the certificate matching SUBJECT_DN. SEQ should initially be set to 0 and bumped up to get the next subject with that DN. */ ksba_cert_t get_cert_bysubject (const char *subject_dn, unsigned int seq) { /* Simple and very inefficient implementation and API. fixme! */ cert_item_t ci; int i; if (!subject_dn) return NULL; acquire_cache_read_lock (); for (i=0; i < 256; i++) { for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) if (!seq--) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; } } release_cache_lock (); return NULL; } /* Return a value describing the class of PATTERN. The offset of the actual string to be used for the comparison is stored at R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */ static enum pattern_class classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset) { enum pattern_class result; const char *s; int hexprefix = 0; int hexlength; *r_offset = *r_sn_offset = 0; /* Skip leading spaces. */ for(s = pattern; *s && spacep (s); s++ ) ; switch (*s) { case 0: /* Empty string is an error. */ result = PATTERN_UNKNOWN; break; case '.': /* An email address, compare from end. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '<': /* An email address. */ result = PATTERN_EMAIL; s++; break; case '@': /* Part of an email address. */ result = PATTERN_EMAIL_SUBSTR; s++; break; case '=': /* Exact compare. */ result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */ break; case '*': /* Case insensitive substring search. */ result = PATTERN_SUBSTR; s++; break; case '+': /* Compare individual words. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; case '/': /* Subject's DN. */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SUBJECT; break; case '#': /* Serial number or issuer DN. */ { const char *si; s++; if ( *s == '/') { /* An issuer's DN is indicated by "#/" */ s++; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_ISSUER; } else { /* Serialnumber + optional issuer ID. */ for (si=s; *si && *si != '/'; si++) if (!strchr("01234567890abcdefABCDEF", *si)) break; if (*si && *si != '/') result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */ else { *r_sn_offset = s - pattern; if (!*si) result = PATTERN_SERIALNO; else { s = si+1; if (!*s || spacep (s)) result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ else result = PATTERN_SERIALNO_ISSUER; } } } } break; case ':': /* Unified fingerprint. */ { const char *se, *si; int i; se = strchr (++s, ':'); if (!se) result = PATTERN_UNKNOWN; else { for (i=0, si=s; si < se; si++, i++ ) if (!strchr("01234567890abcdefABCDEF", *si)) break; if ( si < se ) result = PATTERN_UNKNOWN; /* Invalid digit. */ else if (i == 32) result = PATTERN_FINGERPRINT16; else if (i == 40) result = PATTERN_FINGERPRINT20; else result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */ } } break; case '&': /* Keygrip. */ result = PATTERN_UNKNOWN; /* Not implemented. */ break; default: if (s[0] == '0' && s[1] == 'x') { hexprefix = 1; s += 2; } hexlength = strspn(s, "0123456789abcdefABCDEF"); /* Check if a hexadecimal number is terminated by EOS or blank. */ if (hexlength && s[hexlength] && !spacep (s+hexlength)) { /* If the "0x" prefix is used a correct termination is required. */ if (hexprefix) { result = PATTERN_UNKNOWN; break; /* switch */ } hexlength = 0; /* Not a hex number. */ } if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0')) { if (hexlength == 9) s++; result = PATTERN_SHORT_KEYID; } else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0')) { if (hexlength == 17) s++; result = PATTERN_LONG_KEYID; } else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0')) { if (hexlength == 33) s++; result = PATTERN_FINGERPRINT16; } else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0')) { if (hexlength == 41) s++; result = PATTERN_FINGERPRINT20; } else if (!hexprefix) { /* The fingerprints used with X.509 are often delimited by colons, so we try to single this case out. */ result = PATTERN_UNKNOWN; hexlength = strspn (s, ":0123456789abcdefABCDEF"); if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) { int i, c; for (i=0; i < 20; i++, s += 3) { c = hextobyte(s); if (c == -1 || (i < 19 && s[2] != ':')) break; } if (i == 20) result = PATTERN_FINGERPRINT20; } if (result == PATTERN_UNKNOWN) /* Default to substring match. */ { result = PATTERN_SUBSTR; } } else /* A hex number with a prefix but with a wrong length. */ result = PATTERN_UNKNOWN; } if (result != PATTERN_UNKNOWN) *r_offset = s - pattern; return result; } /* Given PATTERN, which is a string as used by GnuPG to specify a certificate, return all matching certificates by calling the supplied function RETFNC. */ gpg_error_t get_certs_bypattern (const char *pattern, gpg_error_t (*retfnc)(void*,ksba_cert_t), void *retfnc_data) { gpg_error_t err = GPG_ERR_BUG; enum pattern_class class; size_t offset, sn_offset; const char *hexserialno; ksba_sexp_t serialno = NULL; ksba_cert_t cert = NULL; unsigned int seq; if (!pattern || !retfnc) return gpg_error (GPG_ERR_INV_ARG); class = classify_pattern (pattern, &offset, &sn_offset); hexserialno = pattern + sn_offset; pattern += offset; switch (class) { case PATTERN_UNKNOWN: err = gpg_error (GPG_ERR_INV_NAME); break; case PATTERN_FINGERPRINT20: cert = get_cert_byhexfpr (pattern); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SERIALNO_ISSUER: serialno = hexsn_to_sexp (hexserialno); if (!serialno) err = gpg_error_from_syserror (); else { cert = get_cert_bysn (pattern, serialno); err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); } break; case PATTERN_ISSUER: for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_SUBJECT: for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++) { err = retfnc (retfnc_data, cert); ksba_cert_release (cert); cert = NULL; } if (!err && !seq) err = gpg_error (GPG_ERR_NOT_FOUND); break; case PATTERN_EMAIL: case PATTERN_EMAIL_SUBSTR: case PATTERN_FINGERPRINT16: case PATTERN_SHORT_KEYID: case PATTERN_LONG_KEYID: case PATTERN_SUBSTR: case PATTERN_SERIALNO: /* Not supported. */ err = gpg_error (GPG_ERR_INV_NAME); } if (!err && cert) err = retfnc (retfnc_data, cert); ksba_cert_release (cert); xfree (serialno); return err; } /* Return the certificate matching ISSUER_DN and SERIALNO; if it is * not already in the cache, try to find it from other resources. */ ksba_cert_t find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno) { gpg_error_t err; ksba_cert_t cert; cert_fetch_context_t context = NULL; char *hexsn, *buf; /* First check whether it has already been cached. */ cert = get_cert_bysn (issuer_dn, serialno); if (cert) return cert; /* Ask back to the service requester to return the certificate. * This is because we can assume that he already used the * certificate while checking for the CRL. */ hexsn = serial_hex (serialno); if (!hexsn) { log_error ("serial_hex() failed\n"); return NULL; } buf = strconcat ("#", hexsn, "/", issuer_dn, NULL); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexsn); return NULL; } xfree (hexsn); cert = get_cert_local (ctrl, buf); xfree (buf); if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysn: certificate not returned by caller" " - doing lookup\n"); /* Retrieve the certificate from external resources. */ while (!cert) { ksba_sexp_t sn; char *issdn; if (!context) { err = ca_cert_fetch (ctrl, &context, issuer_dn); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by S/N: %s\n"), gpg_strerror (err) ); break; } issdn = ksba_cert_get_issuer (cert, 0); if (strcmp (issuer_dn, issdn)) { log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (issdn); break; } sn = ksba_cert_get_serial (cert); if (DBG_LOOKUP) { log_debug (" considering certificate (#"); dump_serial (sn); log_printf ("/"); dump_string (issdn); log_printf (")\n"); } if (!compare_serialno (serialno, sn)) { ksba_free (sn); ksba_free (issdn); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } ksba_free (sn); ksba_free (issdn); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return the certificate matching SUBJECT_DN and (if not NULL) * KEYID. If it is not already in the cache, try to find it from other * resources. Note, that the external search does not work for user * certificates because the LDAP lookup is on the caCertificate * attribute. For our purposes this is just fine. */ ksba_cert_t find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid) { gpg_error_t err; int seq; ksba_cert_t cert = NULL; cert_fetch_context_t context = NULL; ksba_sexp_t subj; /* If we have certificates from an OCSP request we first try to use * them. This is because these certificates will really be the * required ones and thus even in the case that they can't be * uniquely located by the following code we can use them. This is * for example required by Telesec certificates where a keyId is * used but the issuer certificate comes without a subject keyId! */ if (ctrl->ocsp_certs && subject_dn) { cert_item_t ci; cert_ref_t cr; int i; /* For efficiency reasons we won't use get_cert_bysubject here. */ acquire_cache_read_lock (); for (i=0; i < 256; i++) for (ci=cert_cache[i]; ci; ci = ci->next) if (ci->cert && ci->subject_dn && !strcmp (ci->subject_dn, subject_dn)) for (cr=ctrl->ocsp_certs; cr; cr = cr->next) if (!memcmp (ci->fpr, cr->fpr, 20)) { ksba_cert_ref (ci->cert); release_cache_lock (); return ci->cert; /* We use this certificate. */ } release_cache_lock (); if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n"); } /* No check whether the certificate is cached. */ for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++) { if (!keyid) break; /* No keyid requested, so return the first one found. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj) && !cmp_simple_canon_sexp (keyid, subj)) { xfree (subj); break; /* Found matching cert. */ } xfree (subj); ksba_cert_release (cert); } if (cert) return cert; /* Done. */ if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not in cache\n"); /* Ask back to the service requester to return the certificate. * This is because we can assume that he already used the * certificate while checking for the CRL. */ if (keyid) cert = get_cert_local_ski (ctrl, subject_dn, keyid); else { /* In contrast to get_cert_local_ski, get_cert_local uses any * passed pattern, so we need to make sure that an exact subject * search is done. */ char *buf; buf = strconcat ("/", subject_dn, NULL); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); return NULL; } cert = get_cert_local (ctrl, buf); xfree (buf); } if (cert) { cache_cert (cert); return cert; /* Done. */ } if (DBG_LOOKUP) log_debug ("find_cert_bysubject: certificate not returned by caller" " - doing lookup\n"); /* Locate the certificate using external resources. */ while (!cert) { char *subjdn; if (!context) { err = ca_cert_fetch (ctrl, &context, subject_dn); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err)); break; } } err = fetch_next_ksba_cert (context, &cert); if (err) { log_error (_("error fetching certificate by subject: %s\n"), gpg_strerror (err) ); break; } subjdn = ksba_cert_get_subject (cert, 0); if (strcmp (subject_dn, subjdn)) { log_info ("find_cert_bysubject: subject DN does not match\n"); ksba_cert_release (cert); cert = NULL; ksba_free (subjdn); continue; } if (DBG_LOOKUP) { log_debug (" considering certificate (/"); dump_string (subjdn); log_printf (")\n"); } ksba_free (subjdn); /* If no key ID has been provided, we return the first match. */ if (!keyid) { cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } /* With the key ID given we need to compare it. */ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) { if (!cmp_simple_canon_sexp (keyid, subj)) { ksba_free (subj); cache_cert (cert); if (DBG_LOOKUP) log_debug (" found\n"); break; /* Ready. */ } } ksba_free (subj); ksba_cert_release (cert); cert = NULL; } end_cert_fetch (context); return cert; } /* Return 0 if the certificate is a trusted certificate. Returns * GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in * case of systems errors. TRUSTCLASSES are the bitwise ORed * CERTTRUST_CLASS values to use for the check. */ gpg_error_t is_trusted_cert (ksba_cert_t cert, unsigned int trustclasses) { unsigned char fpr[20]; cert_item_t ci; cert_compute_fpr (cert, fpr); acquire_cache_read_lock (); for (ci=cert_cache[*fpr]; ci; ci = ci->next) if (ci->cert && !memcmp (ci->fpr, fpr, 20)) { if ((ci->trustclasses & trustclasses)) { /* The certificate is trusted in one of the given * TRUSTCLASSES. */ release_cache_lock (); return 0; /* Yes, it is trusted. */ } break; } release_cache_lock (); return gpg_error (GPG_ERR_NOT_TRUSTED); } /* Given the certificate CERT locate the issuer for this certificate * and return it at R_CERT. Returns 0 on success or * GPG_ERR_NOT_FOUND. */ gpg_error_t find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert) { gpg_error_t err; char *issuer_dn; ksba_cert_t issuer_cert = NULL; ksba_name_t authid; ksba_sexp_t authidno; ksba_sexp_t keyid; *r_cert = NULL; issuer_dn = ksba_cert_get_issuer (cert, 0); if (!issuer_dn) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* First we need to check whether we can return that certificate using the authorithyKeyIdentifier. */ err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno); if (err) { log_info (_("error getting authorityKeyIdentifier: %s\n"), gpg_strerror (err)); } else { const char *s = ksba_name_enum (authid, 0); if (s && *authidno) { issuer_cert = find_cert_bysn (ctrl, s, authidno); } if (!issuer_cert && keyid) { /* Not found by issuer+s/n. Now that we have an AKI * keyIdentifier look for a certificate with a matching * SKI. */ issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid); } /* Print a note so that the user does not feel too helpless when * an issuer certificate was found and gpgsm prints BAD * signature because it is not the correct one. */ if (!issuer_cert) { log_info ("issuer certificate "); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } if (authidno) { log_printf ("(#"); dump_serial (authidno); log_printf ("/"); dump_string (s); log_printf (") "); } log_printf ("not found using authorityKeyIdentifier\n"); } ksba_name_release (authid); xfree (authidno); xfree (keyid); } /* If this did not work, try just with the issuer's name and assume * that there is only one such certificate. We only look into our * cache then. */ if (err || !issuer_cert) { issuer_cert = get_cert_bysubject (issuer_dn, 0); if (issuer_cert) err = 0; } leave: if (!err && !issuer_cert) err = gpg_error (GPG_ERR_NOT_FOUND); xfree (issuer_dn); if (err) ksba_cert_release (issuer_cert); else *r_cert = issuer_cert; return err; } /* Read a list of certificates in PEM format from stream FP and store * them on success at R_CERTLIST. On error NULL is stored at R_CERT * list and an error code returned. Note that even on success an * empty list of certificates can be returned (i.e. NULL stored at * R_CERTLIST) iff the input stream has no certificates. */ gpg_error_t read_certlist_from_stream (certlist_t *r_certlist, estream_t fp) { gpg_error_t err; gnupg_ksba_io_t ioctx = NULL; ksba_reader_t reader; ksba_cert_t cert = NULL; certlist_t certlist = NULL; certlist_t cl, *cltail; *r_certlist = NULL; err = gnupg_ksba_create_reader (&ioctx, (GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM), fp, &reader); if (err) goto leave; /* Loop to read all certificates from the stream. */ cltail = &certlist; do { ksba_cert_release (cert); cert = NULL; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_read_der (cert, reader); if (err) { if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; goto leave; } /* Append the certificate to the list. We also store the * fingerprint and check whether we have a cached certificate; * in that case the cached certificate is put into the list to * take advantage of a validation result which might be stored * in the cached certificate. */ cl = xtrycalloc (1, sizeof *cl); if (!cl) { err = gpg_error_from_syserror (); goto leave; } cert_compute_fpr (cert, cl->fpr); cl->cert = get_cert_byfpr (cl->fpr); if (!cl->cert) { cl->cert = cert; cert = NULL; } *cltail = cl; cltail = &cl->next; ksba_reader_clear (reader, NULL, NULL); } while (!gnupg_ksba_reader_eof_seen (ioctx)); leave: ksba_cert_release (cert); gnupg_ksba_destroy_reader (ioctx); if (err) release_certlist (certlist); else *r_certlist = certlist; return err; } /* Release the certificate list CL. */ void release_certlist (certlist_t cl) { while (cl) { certlist_t next = cl->next; ksba_cert_release (cl->cert); cl = next; } } diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index 06351154f..a6c14cdcd 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -1,2323 +1,2323 @@ /* dns-stuff.c - DNS related code including CERT RR (rfc-4398) * Copyright (C) 2003, 2005, 2006, 2009 Free Software Foundation, Inc. * Copyright (C) 2005, 2006, 2009, 2015. 2016 Werner Koch * * 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 . */ #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # ifdef HAVE_WINSOCK2_H # include # endif # include # include #else # if HAVE_SYSTEM_RESOLVER # include # include # include # endif # include #endif #include #include /* William Ahern's DNS library, included as a source copy. */ #ifdef USE_LIBDNS # include "dns.h" #endif /* dns.c has a dns_p_free but it is not exported. We use our own * wrapper here so that we do not accidentally use xfree which would * be wrong for dns.c allocated data. */ #define dns_free(a) free ((a)) #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ # undef USE_NPTH #endif #ifdef USE_NPTH # include #endif #include "./dirmngr-err.h" #include "../common/util.h" #include "../common/host2net.h" #include "dns-stuff.h" #ifdef USE_NPTH # define my_unprotect() npth_unprotect () # define my_protect() npth_protect () #else # define my_unprotect() do { } while(0) # define my_protect() do { } while(0) #endif /* We allow the use of 0 instead of AF_UNSPEC - check this assumption. */ #if AF_UNSPEC != 0 # error AF_UNSPEC does not have the value 0 #endif /* Windows does not support the AI_ADDRCONFIG flag - use zero instead. */ #ifndef AI_ADDRCONFIG # define AI_ADDRCONFIG 0 #endif /* Not every installation has gotten around to supporting SRVs or CERTs yet... */ #ifndef T_SRV #define T_SRV 33 #endif #undef T_CERT #define T_CERT 37 /* The standard SOCKS and TOR ports. */ #define SOCKS_PORT 1080 #define TOR_PORT 9050 #define TOR_PORT2 9150 /* (Used by the Tor browser) */ /* The default nameserver used in Tor mode. */ #define DEFAULT_NAMESERVER "8.8.8.8" /* The default timeout in seconds for libdns requests. */ #define DEFAULT_TIMEOUT 30 /* Two flags to enable verbose and debug mode. */ static int opt_verbose; static int opt_debug; /* The timeout in seconds for libdns requests. */ static int opt_timeout; /* The flag to disable IPv4 access - right now this only skips * returned A records. */ static int opt_disable_ipv4; /* The flag to disable IPv6 access - right now this only skips * returned AAAA records. */ static int opt_disable_ipv6; /* If set force the use of the standard resolver. */ static int standard_resolver; /* If set use recursive resolver when available. */ static int recursive_resolver; /* If set Tor mode shall be used. */ static int tor_mode; /* A string with the nameserver IP address used with Tor. (40 should be sufficient for v6 but we add some extra for a scope.) */ static char tor_nameserver[40+20]; /* Two strings to hold the credentials presented to Tor. */ static char tor_socks_user[30]; static char tor_socks_password[20]; #ifdef USE_LIBDNS /* Libdns gobal data. */ struct libdns_s { struct dns_resolv_conf *resolv_conf; struct dns_hosts *hosts; struct dns_hints *hints; struct sockaddr_storage socks_host; } libdns; /* If this flag is set, libdns shall be reinited for the next use. */ static int libdns_reinit_pending; /* The Tor port to be used. */ static int libdns_tor_port; #endif /*USE_LIBDNS*/ /* Calling this function with YES set to True forces the use of the * standard resolver even if dirmngr has been built with support for * an alternative resolver. */ void enable_standard_resolver (int yes) { standard_resolver = yes; } /* Return true if the standard resolver is used. */ int standard_resolver_p (void) { return standard_resolver; } /* Calling this function with YES switches libdns into recursive mode. * It has no effect on the standard resolver. */ void enable_recursive_resolver (int yes) { recursive_resolver = yes; #ifdef USE_LIBDNS libdns_reinit_pending = 1; #endif } /* Return true iff the recursive resolver is used. */ int recursive_resolver_p (void) { #if USE_LIBDNS return !standard_resolver && recursive_resolver; #else return 0; #endif } /* Puts this module eternally into Tor mode. When called agained with * NEW_CIRCUIT request a new TOR circuit for the next DNS query. */ void enable_dns_tormode (int new_circuit) { if (!*tor_socks_user || new_circuit) { static unsigned int counter; gpgrt_snprintf (tor_socks_user, sizeof tor_socks_user, "dirmngr-%lu", (unsigned long)getpid ()); gpgrt_snprintf (tor_socks_password, sizeof tor_socks_password, "p%u", counter); counter++; } tor_mode = 1; } /* Disable tor mode. */ void disable_dns_tormode (void) { tor_mode = 0; } /* Set verbosity and debug mode for this module. */ void set_dns_verbose (int verbose, int debug) { opt_verbose = verbose; opt_debug = debug; } /* Set the Disable-IPv4 flag so that the name resolver does not return * A addresses. */ void set_dns_disable_ipv4 (int yes) { opt_disable_ipv4 = !!yes; } /* Set the Disable-IPv6 flag so that the name resolver does not return * AAAA addresses. */ void set_dns_disable_ipv6 (int yes) { opt_disable_ipv6 = !!yes; } /* Set the timeout for libdns requests to SECONDS. A value of 0 sets * the default timeout and values are capped at 10 minutes. */ void set_dns_timeout (int seconds) { if (!seconds) seconds = DEFAULT_TIMEOUT; else if (seconds < 1) seconds = 1; else if (seconds > 600) seconds = 600; opt_timeout = seconds; } /* Change the default IP address of the nameserver to IPADDR. The address needs to be a numerical IP address and will be used for the next DNS query. Note that this is only used in Tor mode. */ void set_dns_nameserver (const char *ipaddr) { strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER, sizeof tor_nameserver -1); tor_nameserver[sizeof tor_nameserver -1] = 0; #ifdef USE_LIBDNS libdns_reinit_pending = 1; libdns_tor_port = 0; /* Start again with the default port. */ #endif } /* Free an addressinfo linked list as returned by resolve_dns_name. */ void free_dns_addrinfo (dns_addrinfo_t ai) { while (ai) { dns_addrinfo_t next = ai->next; xfree (ai); ai = next; } } #ifndef HAVE_W32_SYSTEM /* Return H_ERRNO mapped to a gpg-error code. Will never return 0. */ static gpg_error_t get_h_errno_as_gpg_error (void) { gpg_err_code_t ec; switch (h_errno) { case HOST_NOT_FOUND: ec = GPG_ERR_NO_NAME; break; case TRY_AGAIN: ec = GPG_ERR_TRY_LATER; break; case NO_RECOVERY: ec = GPG_ERR_SERVER_FAILED; break; case NO_DATA: ec = GPG_ERR_NO_DATA; break; default: ec = GPG_ERR_UNKNOWN_ERRNO; break; } return gpg_error (ec); } #endif /*!HAVE_W32_SYSTEM*/ static gpg_error_t map_eai_to_gpg_error (int ec) { gpg_error_t err; switch (ec) { case EAI_AGAIN: err = gpg_error (GPG_ERR_EAGAIN); break; case EAI_BADFLAGS: err = gpg_error (GPG_ERR_INV_FLAG); break; case EAI_FAIL: err = gpg_error (GPG_ERR_SERVER_FAILED); break; case EAI_MEMORY: err = gpg_error (GPG_ERR_ENOMEM); break; #ifdef EAI_NODATA case EAI_NODATA: err = gpg_error (GPG_ERR_NO_DATA); break; #endif case EAI_NONAME: err = gpg_error (GPG_ERR_NO_NAME); break; case EAI_SERVICE: err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; case EAI_FAMILY: err = gpg_error (GPG_ERR_EAFNOSUPPORT); break; case EAI_SOCKTYPE: err = gpg_error (GPG_ERR_ESOCKTNOSUPPORT); break; #ifndef HAVE_W32_SYSTEM # ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY:err = gpg_error (GPG_ERR_EADDRNOTAVAIL); break; # endif case EAI_SYSTEM: err = gpg_error_from_syserror (); break; #endif default: err = gpg_error (GPG_ERR_UNKNOWN_ERRNO); break; } return err; } #ifdef USE_LIBDNS static gpg_error_t libdns_error_to_gpg_error (int serr) { gpg_err_code_t ec; switch (serr) { case 0: ec = 0; break; case DNS_ENOBUFS: ec = GPG_ERR_BUFFER_TOO_SHORT; break; case DNS_EILLEGAL: ec = GPG_ERR_INV_OBJ; break; case DNS_EORDER: ec = GPG_ERR_INV_ORDER; break; case DNS_ESECTION: ec = GPG_ERR_DNS_SECTION; break; case DNS_EUNKNOWN: ec = GPG_ERR_DNS_UNKNOWN; break; case DNS_EADDRESS: ec = GPG_ERR_DNS_ADDRESS; break; case DNS_ENOQUERY: ec = GPG_ERR_DNS_NO_QUERY; break; case DNS_ENOANSWER:ec = GPG_ERR_DNS_NO_ANSWER; break; case DNS_EFETCHED: ec = GPG_ERR_ALREADY_FETCHED; break; case DNS_ESERVICE: ec = GPG_ERR_NOT_SUPPORTED; break; case DNS_ENONAME: ec = GPG_ERR_NO_NAME; break; case DNS_EFAIL: ec = GPG_ERR_SERVER_FAILED; break; case DNS_ECONNFIN: ec = GPG_ERR_DNS_CLOSED; break; case DNS_EVERIFY: ec = GPG_ERR_DNS_VERIFY; break; default: if (serr >= 0) ec = gpg_err_code_from_errno (serr); else ec = GPG_ERR_DNS_UNKNOWN; break; } return gpg_error (ec); } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Initialize libdns. Returns 0 on success; prints a diagnostic and * returns an error code on failure. */ static gpg_error_t libdns_init (void) { gpg_error_t err; struct libdns_s ld; int derr; char *cfgstr = NULL; if (libdns.resolv_conf) return 0; /* Already initialized. */ memset (&ld, 0, sizeof ld); ld.resolv_conf = dns_resconf_open (&derr); if (!ld.resolv_conf) { err = libdns_error_to_gpg_error (derr); log_error ("failed to allocate DNS resconf object: %s\n", gpg_strerror (err)); goto leave; } if (tor_mode) { if (!*tor_nameserver) set_dns_nameserver (NULL); if (!libdns_tor_port) libdns_tor_port = TOR_PORT; cfgstr = xtryasprintf ("[%s]:53", tor_nameserver); if (!cfgstr) err = gpg_error_from_syserror (); else err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.resolv_conf->nameserver[0], cfgstr)); if (err) log_error ("failed to set nameserver '%s': %s\n", cfgstr, gpg_strerror (err)); if (err) goto leave; ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS; xfree (cfgstr); cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port); if (!cfgstr) err = gpg_error_from_syserror (); else err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.socks_host, cfgstr)); if (err) { log_error ("failed to set socks server '%s': %s\n", cfgstr, gpg_strerror (err)); goto leave; } } else { #ifdef HAVE_W32_SYSTEM ULONG ninfo_len; PFIXED_INFO ninfo; PIP_ADDR_STRING pip; int idx; ninfo_len = 2048; ninfo = xtrymalloc (ninfo_len); if (!ninfo) { err = gpg_error_from_syserror (); goto leave; } if (GetNetworkParams (ninfo, &ninfo_len)) { log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1)); err = gpg_error (GPG_ERR_GENERAL); xfree (ninfo); goto leave; } for (idx=0, pip = &(ninfo->DnsServerList); pip && idx < DIM (ld.resolv_conf->nameserver); pip = pip->Next) { if (opt_debug) log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String); err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.resolv_conf->nameserver[idx], pip->IpAddress.String)); if (err) log_error ("failed to set nameserver[%d] '%s': %s\n", idx, pip->IpAddress.String, gpg_strerror (err)); else idx++; } xfree (ninfo); #else /* Unix */ const char *fname; fname = "/etc/resolv.conf"; err = libdns_error_to_gpg_error (dns_resconf_loadpath (ld.resolv_conf, fname)); if (err) { log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); goto leave; } fname = "/etc/nsswitch.conf"; err = libdns_error_to_gpg_error (dns_nssconf_loadpath (ld.resolv_conf, fname)); if (err) { /* This is not a fatal error: nsswitch.conf is not used on * all systems; assume classic behavior instead. */ if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); if (opt_debug) log_debug ("dns: fallback resolution order, files then DNS\n"); ld.resolv_conf->lookup[0] = 'f'; ld.resolv_conf->lookup[1] = 'b'; ld.resolv_conf->lookup[2] = '\0'; err = GPG_ERR_NO_ERROR; } else if (!strchr (ld.resolv_conf->lookup, 'b')) { /* No DNS resolution type found in the list. This might be * due to systemd based systems which allow for custom * keywords which are not known to us and thus we do not - * know whether DNS is wanted or not. Becuase DNS is + * know whether DNS is wanted or not. Because DNS is * important for our infrastructure, we forcefully append * DNS to the end of the list. */ if (strlen (ld.resolv_conf->lookup)+2 < sizeof ld.resolv_conf->lookup) { if (opt_debug) log_debug ("dns: appending DNS to resolution order\n"); strcat (ld.resolv_conf->lookup, "b"); } else log_error ("failed to append DNS to resolution order\n"); } #endif /* Unix */ } ld.hosts = dns_hosts_open (&derr); if (!ld.hosts) { err = libdns_error_to_gpg_error (derr); log_error ("failed to initialize hosts file: %s\n", gpg_strerror (err)); goto leave; } { #if HAVE_W32_SYSTEM char *hosts_path = xtryasprintf ("%s\\System32\\drivers\\etc\\hosts", getenv ("SystemRoot")); if (! hosts_path) { err = gpg_error_from_syserror (); goto leave; } derr = dns_hosts_loadpath (ld.hosts, hosts_path); xfree (hosts_path); #else derr = dns_hosts_loadpath (ld.hosts, "/etc/hosts"); #endif if (derr) { err = libdns_error_to_gpg_error (derr); log_error ("failed to load hosts file: %s\n", gpg_strerror (err)); err = 0; /* Do not bail out - having no /etc/hosts is legal. */ } } /* dns_hints_local for stub mode, dns_hints_root for recursive. */ ld.hints = (recursive_resolver ? dns_hints_root (ld.resolv_conf, &derr) : dns_hints_local (ld.resolv_conf, &derr)); if (!ld.hints) { err = libdns_error_to_gpg_error (derr); log_error ("failed to load DNS hints: %s\n", gpg_strerror (err)); goto leave; } /* All fine. Make the data global. */ libdns = ld; if (opt_debug) log_debug ("dns: libdns initialized%s\n", tor_mode?" (tor mode)":""); leave: xfree (cfgstr); return err; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Deinitialize libdns. */ static void libdns_deinit (void) { struct libdns_s ld; if (!libdns.resolv_conf) return; /* Not initialized. */ ld = libdns; memset (&libdns, 0, sizeof libdns); dns_hints_close (ld.hints); dns_hosts_close (ld.hosts); dns_resconf_close (ld.resolv_conf); } #endif /*USE_LIBDNS*/ /* SIGHUP action handler for this module. With FORCE set objects are * all immediately released. */ void reload_dns_stuff (int force) { #ifdef USE_LIBDNS if (force) { libdns_deinit (); libdns_reinit_pending = 0; } else { libdns_reinit_pending = 1; libdns_tor_port = 0; /* Start again with the default port. */ } #else (void)force; #endif } #ifdef USE_LIBDNS /* * Initialize libdns if needed and open a dns_resolver context. * Returns 0 on success and stores the new context at R_RES. On * failure an error code is returned and NULL stored at R_RES. */ static gpg_error_t libdns_res_open (struct dns_resolver **r_res) { gpg_error_t err; struct dns_resolver *res; int derr; *r_res = NULL; if (libdns_reinit_pending) { libdns_reinit_pending = 0; libdns_deinit (); } err = libdns_init (); if (err) return err; if (!opt_timeout) set_dns_timeout (0); res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL, dns_opts (.socks_host = &libdns.socks_host, .socks_user = tor_socks_user, .socks_password = tor_socks_password ), &derr); if (!res) return libdns_error_to_gpg_error (derr); *r_res = res; return 0; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Helper to test whether we need to try again after having switched * the Tor port. */ static int libdns_switch_port_p (gpg_error_t err) { if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED && libdns_tor_port == TOR_PORT) { /* Switch port and try again. */ if (opt_debug) log_debug ("dns: switching from SOCKS port %d to %d\n", TOR_PORT, TOR_PORT2); libdns_tor_port = TOR_PORT2; libdns_reinit_pending = 1; return 1; } return 0; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Wrapper around dns_res_submit. */ static gpg_error_t libdns_res_submit (struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass) { return libdns_error_to_gpg_error (dns_res_submit (res, qname, qtype, qclass)); } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Standard event handling loop. */ gpg_error_t libdns_res_wait (struct dns_resolver *res) { gpg_error_t err; while ((err = libdns_error_to_gpg_error (dns_res_check (res))) && gpg_err_code (err) == GPG_ERR_EAGAIN) { if (dns_res_elapsed (res) > opt_timeout) { err = gpg_error (GPG_ERR_DNS_TIMEOUT); break; } my_unprotect (); dns_res_poll (res, 1); my_protect (); } return err; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS static gpg_error_t resolve_name_libdns (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_dai, char **r_canonname) { gpg_error_t err; dns_addrinfo_t daihead = NULL; dns_addrinfo_t dai; struct dns_resolver *res = NULL; struct dns_addrinfo *ai = NULL; struct addrinfo hints; struct addrinfo *ent; char portstr_[21]; char *portstr = NULL; char *namebuf = NULL; int derr; *r_dai = NULL; if (r_canonname) *r_canonname = NULL; memset (&hints, 0, sizeof hints); hints.ai_family = want_family; hints.ai_socktype = want_socktype; hints.ai_flags = AI_ADDRCONFIG; if (r_canonname) hints.ai_flags |= AI_CANONNAME; if (port) { snprintf (portstr_, sizeof portstr_, "%hu", port); portstr = portstr_; } err = libdns_res_open (&res); if (err) goto leave; if (is_ip_address (name)) { hints.ai_flags |= AI_NUMERICHOST; /* libdns does not grok brackets - remove them. */ if (*name == '[' && name[strlen(name)-1] == ']') { namebuf = xtrymalloc (strlen (name)); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } strcpy (namebuf, name+1); namebuf[strlen (namebuf)-1] = 0; name = namebuf; } } ai = dns_ai_open (name, portstr, 0, &hints, res, &derr); if (!ai) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Loop over all records. */ for (;;) { err = libdns_error_to_gpg_error (dns_ai_nextent (&ent, ai)); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (daihead) err = 0; /* We got some results, we're good. */ break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EAGAIN) { if (dns_ai_elapsed (ai) > opt_timeout) { err = gpg_error (GPG_ERR_DNS_TIMEOUT); goto leave; } my_unprotect (); dns_ai_poll (ai, 1); my_protect (); continue; } if (err) goto leave; if (r_canonname && ! *r_canonname && ent && ent->ai_canonname) { *r_canonname = xtrystrdup (ent->ai_canonname); if (!*r_canonname) { err = gpg_error_from_syserror (); goto leave; } /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_canonname && (*r_canonname)[strlen (*r_canonname)-1] == '.') (*r_canonname)[strlen (*r_canonname)-1] = 0; } dai = xtrymalloc (sizeof *dai); if (dai == NULL) { err = gpg_error_from_syserror (); goto leave; } dai->family = ent->ai_family; dai->socktype = ent->ai_socktype; dai->protocol = ent->ai_protocol; dai->addrlen = ent->ai_addrlen; memcpy (dai->addr, ent->ai_addr, ent->ai_addrlen); dai->next = daihead; daihead = dai; xfree (ent); } leave: dns_ai_close (ai); dns_res_close (res); if (err) { if (r_canonname) { xfree (*r_canonname); *r_canonname = NULL; } free_dns_addrinfo (daihead); } else *r_dai = daihead; xfree (namebuf); return err; } #endif /*USE_LIBDNS*/ /* Resolve a name using the standard system function. */ static gpg_error_t resolve_name_standard (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_dai, char **r_canonname) { gpg_error_t err = 0; dns_addrinfo_t daihead = NULL; dns_addrinfo_t dai; struct addrinfo *aibuf = NULL; struct addrinfo hints, *ai; char portstr[21]; int ret; *r_dai = NULL; if (r_canonname) *r_canonname = NULL; memset (&hints, 0, sizeof hints); hints.ai_family = want_family; hints.ai_socktype = want_socktype; hints.ai_flags = AI_ADDRCONFIG; if (r_canonname) hints.ai_flags |= AI_CANONNAME; if (is_ip_address (name)) hints.ai_flags |= AI_NUMERICHOST; if (port) snprintf (portstr, sizeof portstr, "%hu", port); else *portstr = 0; /* We can't use the AI_IDN flag because that does the conversion using the current locale. However, GnuPG always used UTF-8. To support IDN we would need to make use of the libidn API. */ ret = getaddrinfo (name, *portstr? portstr : NULL, &hints, &aibuf); if (ret) { aibuf = NULL; err = map_eai_to_gpg_error (ret); if (gpg_err_code (err) == GPG_ERR_NO_NAME) { /* There seems to be a bug in the glibc getaddrinfo function if the CNAME points to a long list of A and AAAA records in which case the function return NO_NAME. Let's do the CNAME redirection again. */ char *cname; if (get_dns_cname (name, &cname)) goto leave; /* Still no success. */ ret = getaddrinfo (cname, *portstr? portstr : NULL, &hints, &aibuf); xfree (cname); if (ret) { aibuf = NULL; err = map_eai_to_gpg_error (ret); goto leave; } err = 0; /* Yep, now it worked. */ } else goto leave; } if (r_canonname && aibuf && aibuf->ai_canonname) { *r_canonname = xtrystrdup (aibuf->ai_canonname); if (!*r_canonname) { err = gpg_error_from_syserror (); goto leave; } } for (ai = aibuf; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET6 && ai->ai_family != AF_INET) continue; if (opt_disable_ipv4 && ai->ai_family == AF_INET) continue; if (opt_disable_ipv6 && ai->ai_family == AF_INET6) continue; dai = xtrymalloc (sizeof *dai); dai->family = ai->ai_family; dai->socktype = ai->ai_socktype; dai->protocol = ai->ai_protocol; dai->addrlen = ai->ai_addrlen; memcpy (dai->addr, ai->ai_addr, ai->ai_addrlen); dai->next = daihead; daihead = dai; } leave: if (aibuf) freeaddrinfo (aibuf); if (err) { if (r_canonname) { xfree (*r_canonname); *r_canonname = NULL; } free_dns_addrinfo (daihead); } else *r_dai = daihead; return err; } /* This a wrapper around getaddrinfo with slightly different semantics. NAME is the name to resolve. PORT is the requested port or 0. WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM. On success the result is stored in a linked list with the head stored at the address R_AI; the caller must call gpg_addrinfo_free on this. If R_CANONNAME is not NULL the official name of the host is stored there as a malloced string; if that name is not available NULL is stored. */ gpg_error_t resolve_dns_name (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_ai, char **r_canonname) { gpg_error_t err; #ifdef USE_LIBDNS if (!standard_resolver) { err = resolve_name_libdns (name, port, want_family, want_socktype, r_ai, r_canonname); if (err && libdns_switch_port_p (err)) err = resolve_name_libdns (name, port, want_family, want_socktype, r_ai, r_canonname); } else #endif /*USE_LIBDNS*/ err = resolve_name_standard (name, port, want_family, want_socktype, r_ai, r_canonname); if (opt_debug) log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err)); return err; } #ifdef USE_LIBDNS /* Resolve an address using libdns. */ static gpg_error_t resolve_addr_libdns (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; char host[DNS_D_MAXNAME + 1]; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_ptr ptr; int derr; *r_name = NULL; /* First we turn ADDR into a DNS name (with ".arpa" suffix). */ err = 0; if (addr->ss_family == AF_INET6) { const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr; if (!dns_aaaa_arpa (host, sizeof host, (void*)&a6->sin6_addr)) err = gpg_error (GPG_ERR_INV_OBJ); } else if (addr->ss_family == AF_INET) { const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr; if (!dns_a_arpa (host, sizeof host, (void*)&a4->sin_addr)) err = gpg_error (GPG_ERR_INV_OBJ); } else err = gpg_error (GPG_ERR_EAFNOSUPPORT); if (err) goto leave; err = libdns_res_open (&res); if (err) goto leave; err = libdns_res_submit (res, host, DNS_T_PTR, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; goto leave; } /* Parse the result. */ if (!err) { struct dns_rr rr; struct dns_rr_i rri; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_PTR; if (!dns_rr_grep (&rr, 1, &rri, ans, &derr)) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } err = libdns_error_to_gpg_error (dns_ptr_parse (&ptr, &rr, ans)); if (err) goto leave; /* Copy result. */ *r_name = xtrystrdup (ptr.host); if (!*r_name) { err = gpg_error_from_syserror (); goto leave; } /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_name && (*r_name)[strlen (*r_name)-1] == '.') (*r_name)[strlen (*r_name)-1] = 0; } else /* GPG_ERR_NO_NAME */ { char *buffer, *p; int buflen; int ec; buffer = ptr.host; buflen = sizeof ptr.host; p = buffer; if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) { *p++ = '['; buflen -= 2; } ec = getnameinfo ((const struct sockaddr *)addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); if (ec) { err = map_eai_to_gpg_error (ec); goto leave; } if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) strcat (buffer, "]"); } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Resolve an address using the standard system function. */ static gpg_error_t resolve_addr_standard (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; int ec; char *buffer, *p; int buflen; *r_name = NULL; buflen = NI_MAXHOST; buffer = xtrymalloc (buflen + 2 + 1); if (!buffer) return gpg_error_from_syserror (); if ((flags & DNS_NUMERICHOST) || tor_mode) ec = EAI_NONAME; else ec = getnameinfo ((const struct sockaddr *)addr, addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD); if (!ec && *buffer == '[') ec = EAI_FAIL; /* A name may never start with a bracket. */ else if (ec == EAI_NONAME) { p = buffer; if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) { *p++ = '['; buflen -= 2; } ec = getnameinfo ((const struct sockaddr *)addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); if (!ec && addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) strcat (buffer, "]"); } if (ec) err = map_eai_to_gpg_error (ec); else { p = xtryrealloc (buffer, strlen (buffer)+1); if (!p) err = gpg_error_from_syserror (); else { buffer = p; err = 0; } } if (err) xfree (buffer); else *r_name = buffer; return err; } /* A wrapper around getnameinfo. */ gpg_error_t resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; #ifdef USE_LIBDNS /* Note that we divert to the standard resolver for NUMERICHOST. */ if (!standard_resolver && !(flags & DNS_NUMERICHOST)) { err = resolve_addr_libdns (addr, addrlen, flags, r_name); if (err && libdns_switch_port_p (err)) err = resolve_addr_libdns (addr, addrlen, flags, r_name); } else #endif /*USE_LIBDNS*/ err = resolve_addr_standard (addr, addrlen, flags, r_name); if (opt_debug) log_debug ("dns: resolve_dns_addr(): %s\n", gpg_strerror (err)); return err; } /* Check whether NAME is an IP address. Returns a true if it is * either an IPv6 or a IPv4 numerical address. The actual return * values can also be used to identify whether it is v4 or v6: The * true value will surprisingly be 4 for IPv4 and 6 for IPv6. */ int is_ip_address (const char *name) { const char *s; int ndots, dblcol, n; if (*name == '[') return 6; /* yes: A legal DNS name may not contain this character; this must be bracketed v6 address. */ if (*name == '.') return 0; /* No. A leading dot is not a valid IP address. */ /* Check whether this is a v6 address. */ ndots = n = dblcol = 0; for (s=name; *s; s++) { if (*s == ':') { ndots++; if (s[1] == ':') { ndots++; if (dblcol) return 0; /* No: Only one "::" allowed. */ dblcol++; if (s[1]) s++; } n = 0; } else if (*s == '.') goto legacy; else if (!strchr ("0123456789abcdefABCDEF", *s)) return 0; /* No: Not a hex digit. */ else if (++n > 4) return 0; /* To many digits in a group. */ } if (ndots > 7) return 0; /* No: Too many colons. */ else if (ndots > 1) return 6; /* Yes: At least 2 colons indicate an v6 address. */ legacy: /* Check whether it is legacy IP address. */ ndots = n = 0; for (s=name; *s; s++) { if (*s == '.') { if (s[1] == '.') return 0; /* No: Double dot. */ if (atoi (s+1) > 255) return 0; /* No: Ipv4 byte value too large. */ ndots++; n = 0; } else if (!strchr ("0123456789", *s)) return 0; /* No: Not a digit. */ else if (++n > 3) return 0; /* No: More than 3 digits. */ } return (ndots == 3)? 4 : 0; } /* Return true if NAME is an onion address. */ int is_onion_address (const char *name) { size_t len; len = name? strlen (name) : 0; if (len < 8 || strcmp (name + len - 6, ".onion")) return 0; /* Note that we require at least 2 characters before the suffix. */ return 1; /* Yes. */ } /* libdns version of get_dns_cert. */ #ifdef USE_LIBDNS static gpg_error_t get_dns_cert_libdns (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_rr rr; struct dns_rr_i rri; char host[DNS_D_MAXNAME + 1]; int derr; int qtype; /* Get the query type from WANT_CERTTYPE (which in general indicates * the subtype we want). */ qtype = (want_certtype < DNS_CERTTYPE_RRBASE ? T_CERT : (want_certtype - DNS_CERTTYPE_RRBASE)); err = libdns_res_open (&res); if (err) goto leave; if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) { err = gpg_error (GPG_ERR_ENAMETOOLONG); goto leave; } err = libdns_res_submit (res, name, qtype, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = qtype; err = gpg_error (GPG_ERR_NOT_FOUND); while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) { unsigned char *rp = ans->data + rr.rd.p; unsigned short len = rr.rd.len; u16 subtype; if (!len) { /* Definitely too short - skip. */ } else if (want_certtype >= DNS_CERTTYPE_RRBASE && rr.type == (want_certtype - DNS_CERTTYPE_RRBASE) && r_key) { *r_key = xtrymalloc (len); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, rp, len); *r_keylen = len; err = 0; } goto leave; } else if (want_certtype >= DNS_CERTTYPE_RRBASE) { /* We did not found the requested RR - skip. */ } else if (rr.type == T_CERT && len > 5) { /* We got a CERT type. */ subtype = buf16_to_u16 (rp); rp += 2; len -= 2; /* Skip the CERT key tag and algo which we don't need. */ rp += 3; len -= 3; if (want_certtype && want_certtype != subtype) ; /* Not the requested subtype - skip. */ else if (subtype == DNS_CERTTYPE_PGP && len && r_key && r_keylen) { /* PGP subtype */ *r_key = xtrymalloc (len); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, rp, len); *r_keylen = len; err = 0; } goto leave; } else if (subtype == DNS_CERTTYPE_IPGP && len && len < 1023 && len >= rp[0] + 1) { /* IPGP type */ *r_fprlen = rp[0]; if (*r_fprlen) { *r_fpr = xtrymalloc (*r_fprlen); if (!*r_fpr) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_fpr, rp+1, *r_fprlen); } else *r_fpr = NULL; if (len > *r_fprlen + 1) { *r_url = xtrymalloc (len - (*r_fprlen + 1) + 1); if (!*r_url) { err = gpg_error_from_syserror (); xfree (*r_fpr); *r_fpr = NULL; goto leave; } memcpy (*r_url, rp + *r_fprlen + 1, len - (*r_fprlen + 1)); (*r_url)[len - (*r_fprlen + 1)] = 0; } else *r_url = NULL; err = 0; goto leave; } else { /* Unknown subtype or record too short - skip. */ } } else { /* Not a requested type - skip. */ } } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver version of get_dns_cert. */ static gpg_error_t get_dns_cert_standard (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { #ifdef HAVE_SYSTEM_RESOLVER gpg_error_t err; unsigned char *answer; int r; u16 count; /* Allocate a 64k buffer which is the limit for an DNS response. */ answer = xtrymalloc (65536); if (!answer) return gpg_error_from_syserror (); err = gpg_error (GPG_ERR_NOT_FOUND); r = res_query (name, C_IN, (want_certtype < DNS_CERTTYPE_RRBASE ? T_CERT : (want_certtype - DNS_CERTTYPE_RRBASE)), answer, 65536); /* Not too big, not too small, no errors and at least 1 answer. */ if (r >= sizeof (HEADER) && r <= 65536 && (((HEADER *)(void *) answer)->rcode) == NOERROR && (count = ntohs (((HEADER *)(void *) answer)->ancount))) { int rc; unsigned char *pt, *emsg; emsg = &answer[r]; pt = &answer[sizeof (HEADER)]; /* Skip over the query */ rc = dn_skipname (pt, emsg); if (rc == -1) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } pt += rc + QFIXEDSZ; /* There are several possible response types for a CERT request. We're interested in the PGP (a key) and IPGP (a URI) types. Skip all others. TODO: A key is better than a URI since we've gone through all this bother to fetch it, so favor that if we have both PGP and IPGP? */ while (count-- > 0 && pt < emsg) { u16 type, class, dlen, ctype; rc = dn_skipname (pt, emsg); /* the name we just queried for */ if (rc == -1) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } pt += rc; /* Truncated message? 15 bytes takes us to the point where we start looking at the ctype. */ if ((emsg - pt) < 15) break; type = buf16_to_u16 (pt); pt += 2; class = buf16_to_u16 (pt); pt += 2; if (class != C_IN) break; /* ttl */ pt += 4; /* data length */ dlen = buf16_to_u16 (pt); pt += 2; /* Check the type and parse. */ if (want_certtype >= DNS_CERTTYPE_RRBASE && type == (want_certtype - DNS_CERTTYPE_RRBASE) && r_key) { *r_key = xtrymalloc (dlen); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, pt, dlen); *r_keylen = dlen; err = 0; } goto leave; } else if (want_certtype >= DNS_CERTTYPE_RRBASE) { /* We did not found the requested RR. */ pt += dlen; } else if (type == T_CERT) { /* We got a CERT type. */ ctype = buf16_to_u16 (pt); pt += 2; /* Skip the CERT key tag and algo which we don't need. */ pt += 3; dlen -= 5; /* 15 bytes takes us to here */ if (want_certtype && want_certtype != ctype) ; /* Not of the requested certtype. */ else if (ctype == DNS_CERTTYPE_PGP && dlen && r_key && r_keylen) { /* PGP type */ *r_key = xtrymalloc (dlen); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, pt, dlen); *r_keylen = dlen; err = 0; } goto leave; } else if (ctype == DNS_CERTTYPE_IPGP && dlen && dlen < 1023 && dlen >= pt[0] + 1) { /* IPGP type */ *r_fprlen = pt[0]; if (*r_fprlen) { *r_fpr = xtrymalloc (*r_fprlen); if (!*r_fpr) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_fpr, &pt[1], *r_fprlen); } else *r_fpr = NULL; if (dlen > *r_fprlen + 1) { *r_url = xtrymalloc (dlen - (*r_fprlen + 1) + 1); if (!*r_url) { err = gpg_error_from_syserror (); xfree (*r_fpr); *r_fpr = NULL; goto leave; } memcpy (*r_url, &pt[*r_fprlen + 1], dlen - (*r_fprlen + 1)); (*r_url)[dlen - (*r_fprlen + 1)] = '\0'; } else *r_url = NULL; err = 0; goto leave; } /* No subtype matches, so continue with the next answer. */ pt += dlen; } else { /* Not a requested type - might be a CNAME. Try next item. */ pt += dlen; } } } leave: xfree (answer); return err; #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)want_certtype; (void)r_key; (void)r_keylen; (void)r_fpr; (void)r_fprlen; (void)r_url; return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } /* Returns 0 on success or an error code. If a PGP CERT record was found, the malloced data is returned at (R_KEY, R_KEYLEN) and the other return parameters are set to NULL/0. If an IPGP CERT record was found the fingerprint is stored as an allocated block at R_FPR and its length at R_FPRLEN; an URL is allocated as a string and returned at R_URL. If WANT_CERTTYPE is 0 this function returns the first CERT found with a supported type; it is expected that only one CERT record is used. If WANT_CERTTYPE is one of the supported certtypes only records with this certtype are considered and the first found is returned. (R_KEY,R_KEYLEN) are optional. */ gpg_error_t get_dns_cert (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; if (r_key) *r_key = NULL; if (r_keylen) *r_keylen = 0; *r_fpr = NULL; *r_fprlen = 0; *r_url = NULL; #ifdef USE_LIBDNS if (!standard_resolver) { err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); if (err && libdns_switch_port_p (err)) err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); } else #endif /*USE_LIBDNS*/ err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); if (opt_debug) log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err)); return err; } static int priosort(const void *a,const void *b) { const struct srventry *sa=a,*sb=b; if(sa->priority>sb->priority) return 1; else if(sa->prioritypriority) return -1; else return 0; } /* Libdns based helper for getsrv. Note that it is expected that NULL * is stored at the address of LIST and 0 is stored at the address of * R_COUNT. */ #ifdef USE_LIBDNS static gpg_error_t getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count) { gpg_error_t err; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_rr rr; struct dns_rr_i rri; char host[DNS_D_MAXNAME + 1]; int derr; unsigned int srvcount = 0; err = libdns_res_open (&res); if (err) goto leave; if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) { err = gpg_error (GPG_ERR_ENAMETOOLONG); goto leave; } err = libdns_res_submit (res, name, DNS_T_SRV, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_SRV; while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) { struct dns_srv dsrv; struct srventry *srv; struct srventry *newlist; err = libdns_error_to_gpg_error (dns_srv_parse(&dsrv, &rr, ans)); if (err) goto leave; newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); if (!newlist) { err = gpg_error_from_syserror (); goto leave; } *list = newlist; memset (&(*list)[srvcount], 0, sizeof(struct srventry)); srv = &(*list)[srvcount]; srvcount++; srv->priority = dsrv.priority; srv->weight = dsrv.weight; srv->port = dsrv.port; mem2str (srv->target, dsrv.target, sizeof srv->target); /* Libdns appends the root zone part which is problematic for * most other functions - strip it. */ if (*srv->target && (srv->target)[strlen (srv->target)-1] == '.') (srv->target)[strlen (srv->target)-1] = 0; } *r_count = srvcount; leave: if (err) { xfree (*list); *list = NULL; } dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver based helper for getsrv. Note that it is * expected that NULL is stored at the address of LIST and 0 is stored * at the address of R_COUNT. */ static gpg_error_t getsrv_standard (const char *name, struct srventry **list, unsigned int *r_count) { #ifdef HAVE_SYSTEM_RESOLVER union { unsigned char ans[2048]; HEADER header[1]; } res; unsigned char *answer = res.ans; HEADER *header = res.header; unsigned char *pt, *emsg; int r, rc; u16 dlen; unsigned int srvcount = 0; u16 count; /* Do not allow a query using the standard resolver in Tor mode. */ if (tor_mode) return gpg_error (GPG_ERR_NOT_ENABLED); my_unprotect (); r = res_query (name, C_IN, T_SRV, answer, sizeof res.ans); my_protect (); if (r < 0) return get_h_errno_as_gpg_error (); if (r < sizeof (HEADER)) return gpg_error (GPG_ERR_SERVER_FAILED); if (r > sizeof res.ans) return gpg_error (GPG_ERR_SYSTEM_BUG); if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ emsg = &answer[r]; pt = &answer[sizeof(HEADER)]; /* Skip over the query */ rc = dn_skipname (pt, emsg); if (rc == -1) goto fail; pt += rc + QFIXEDSZ; while (count-- > 0 && pt < emsg) { struct srventry *srv; u16 type, class; struct srventry *newlist; newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); if (!newlist) goto fail; *list = newlist; memset (&(*list)[srvcount], 0, sizeof(struct srventry)); srv = &(*list)[srvcount]; srvcount++; rc = dn_skipname (pt, emsg); /* The name we just queried for. */ if (rc == -1) goto fail; pt += rc; /* Truncated message? */ if ((emsg-pt) < 16) goto fail; type = buf16_to_u16 (pt); pt += 2; /* We asked for SRV and got something else !? */ if (type != T_SRV) goto fail; class = buf16_to_u16 (pt); pt += 2; /* We asked for IN and got something else !? */ if (class != C_IN) goto fail; pt += 4; /* ttl */ dlen = buf16_to_u16 (pt); pt += 2; srv->priority = buf16_to_ushort (pt); pt += 2; srv->weight = buf16_to_ushort (pt); pt += 2; srv->port = buf16_to_ushort (pt); pt += 2; /* Get the name. 2782 doesn't allow name compression, but * dn_expand still works to pull the name out of the packet. */ rc = dn_expand (answer, emsg, pt, srv->target, sizeof srv->target); if (rc == 1 && srv->target[0] == 0) /* "." */ { xfree(*list); *list = NULL; return 0; } if (rc == -1) goto fail; pt += rc; /* Corrupt packet? */ if (dlen != rc+6) goto fail; } *r_count = srvcount; return 0; fail: xfree (*list); *list = NULL; return gpg_error (GPG_ERR_GENERAL); #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)list; (void)r_count; return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } /* Query a SRV record for SERVICE and PROTO for NAME. If SERVICE is * NULL, NAME is expected to contain the full query name. Note that * we do not return NONAME but simply store 0 at R_COUNT. On error an * error code is returned and 0 stored at R_COUNT. */ gpg_error_t get_dns_srv (const char *name, const char *service, const char *proto, struct srventry **list, unsigned int *r_count) { gpg_error_t err; char *namebuffer = NULL; unsigned int srvcount; int i; *list = NULL; *r_count = 0; srvcount = 0; /* If SERVICE is given construct the query from it and PROTO. */ if (service) { namebuffer = xtryasprintf ("_%s._%s.%s", service, proto? proto:"tcp", name); if (!namebuffer) { err = gpg_error_from_syserror (); goto leave; } name = namebuffer; } #ifdef USE_LIBDNS if (!standard_resolver) { err = getsrv_libdns (name, list, &srvcount); if (err && libdns_switch_port_p (err)) err = getsrv_libdns (name, list, &srvcount); } else #endif /*USE_LIBDNS*/ err = getsrv_standard (name, list, &srvcount); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_NAME) err = 0; goto leave; } /* Now we have an array of all the srv records. */ /* Order by priority */ qsort(*list,srvcount,sizeof(struct srventry),priosort); /* For each priority, move the zero-weighted items first. */ for (i=0; i < srvcount; i++) { int j; for (j=i;j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) { if((*list)[j].weight==0) { /* Swap j with i */ if(j!=i) { struct srventry temp; memcpy (&temp,&(*list)[j],sizeof(struct srventry)); memcpy (&(*list)[j],&(*list)[i],sizeof(struct srventry)); memcpy (&(*list)[i],&temp,sizeof(struct srventry)); } break; } } } /* Run the RFC-2782 weighting algorithm. We don't need very high quality randomness for this, so regular libc srand/rand is sufficient. */ { static int done; if (!done) { done = 1; srand (time (NULL)*getpid()); } } for (i=0; i < srvcount; i++) { int j; float prio_count=0,chose; for (j=i; j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) { prio_count+=(*list)[j].weight; (*list)[j].run_count=prio_count; } chose=prio_count*rand()/RAND_MAX; for (j=i;j %u records\n", name, srvcount); } if (!err) *r_count = srvcount; xfree (namebuffer); return err; } #ifdef USE_LIBDNS /* libdns version of get_dns_cname. */ gpg_error_t get_dns_cname_libdns (const char *name, char **r_cname) { gpg_error_t err; struct dns_resolver *res; struct dns_packet *ans = NULL; struct dns_cname cname; int derr; err = libdns_res_open (&res); if (err) goto leave; err = libdns_res_submit (res, name, DNS_T_CNAME, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; /* Parse the result into CNAME. */ err = libdns_error_to_gpg_error (dns_p_study (ans)); if (err) goto leave; if (!dns_d_cname (&cname, sizeof cname, name, strlen (name), ans, &derr)) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Copy result. */ *r_cname = xtrystrdup (cname.host); if (!*r_cname) err = gpg_error_from_syserror (); else { /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_cname && (*r_cname)[strlen (*r_cname)-1] == '.') (*r_cname)[strlen (*r_cname)-1] = 0; } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver version of get_dns_cname. */ gpg_error_t get_dns_cname_standard (const char *name, char **r_cname) { #ifdef HAVE_SYSTEM_RESOLVER gpg_error_t err; int rc; union { unsigned char ans[2048]; HEADER header[1]; } res; unsigned char *answer = res.ans; HEADER *header = res.header; unsigned char *pt, *emsg; int r; char *cname; int cnamesize = 1025; u16 count; /* Do not allow a query using the standard resolver in Tor mode. */ if (tor_mode) return -1; my_unprotect (); r = res_query (name, C_IN, T_CERT, answer, sizeof res.ans); my_protect (); if (r < 0) return get_h_errno_as_gpg_error (); if (r < sizeof (HEADER)) return gpg_error (GPG_ERR_SERVER_FAILED); if (r > sizeof res.ans) return gpg_error (GPG_ERR_SYSTEM_BUG); if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ if (count != 1) return gpg_error (GPG_ERR_SERVER_FAILED); emsg = &answer[r]; pt = &answer[sizeof(HEADER)]; rc = dn_skipname (pt, emsg); if (rc == -1) return gpg_error (GPG_ERR_SERVER_FAILED); pt += rc + QFIXEDSZ; if (pt >= emsg) return gpg_error (GPG_ERR_SERVER_FAILED); rc = dn_skipname (pt, emsg); if (rc == -1) return gpg_error (GPG_ERR_SERVER_FAILED); pt += rc + 2 + 2 + 4; if (pt+2 >= emsg) return gpg_error (GPG_ERR_SERVER_FAILED); pt += 2; /* Skip rdlen */ cname = xtrymalloc (cnamesize); if (!cname) return gpg_error_from_syserror (); rc = dn_expand (answer, emsg, pt, cname, cnamesize -1); if (rc == -1) { xfree (cname); return gpg_error (GPG_ERR_SERVER_FAILED); } *r_cname = xtryrealloc (cname, strlen (cname)+1); if (!*r_cname) { err = gpg_error_from_syserror (); xfree (cname); return err; } return 0; #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)r_cname; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } gpg_error_t get_dns_cname (const char *name, char **r_cname) { gpg_error_t err; *r_cname = NULL; #ifdef USE_LIBDNS if (!standard_resolver) { err = get_dns_cname_libdns (name, r_cname); if (err && libdns_switch_port_p (err)) err = get_dns_cname_libdns (name, r_cname); return err; } #endif /*USE_LIBDNS*/ err = get_dns_cname_standard (name, r_cname); if (opt_debug) log_debug ("get_dns_cname(%s)%s%s\n", name, err ? ": " : " -> ", err ? gpg_strerror (err) : *r_cname); return err; } diff --git a/dirmngr/dns-stuff.h b/dirmngr/dns-stuff.h index adb0b80b0..612b2e5f5 100644 --- a/dirmngr/dns-stuff.h +++ b/dirmngr/dns-stuff.h @@ -1,169 +1,169 @@ /* dns-stuff.c - DNS related code including CERT RR (rfc-4398) * Copyright (C) 2006 Free Software Foundation, Inc. * Copyright (C) 2006, 2015 Werner Koch * * 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_DIRMNGR_DNS_STUFF_H #define GNUPG_DIRMNGR_DNS_STUFF_H #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #else # include # include #endif /* * Flags used with resolve_dns_addr. */ #define DNS_NUMERICHOST 1 /* Force numeric output format. */ #define DNS_WITHBRACKET 2 /* Put brackets around numeric v6 addresses. */ /* * Constants for use with get_dns_cert. */ #define DNS_CERTTYPE_ANY 0 /* Internal catch all type. */ /* Certificate types according to RFC-4398: */ #define DNS_CERTTYPE_PKIX 1 /* X.509 as per PKIX. */ #define DNS_CERTTYPE_SPKI 2 /* SPKI certificate. */ #define DNS_CERTTYPE_PGP 3 /* OpenPGP packet. */ #define DNS_CERTTYPE_IPKIX 4 /* The URL of an X.509 data object. */ #define DNS_CERTTYPE_ISPKI 5 /* The URL of an SPKI certificate. */ #define DNS_CERTTYPE_IPGP 6 /* The fingerprint and URL of an OpenPGP packet. */ #define DNS_CERTTYPE_ACPKIX 7 /* Attribute Certificate. */ #define DNS_CERTTYPE_IACPKIX 8 /* The URL of an Attribute Certificate. */ #define DNS_CERTTYPE_URI 253 /* URI private. */ #define DNS_CERTTYPE_OID 254 /* OID private. */ /* Hacks for our implementation. */ #define DNS_CERTTYPE_RRBASE 1024 /* Base of special constants. */ #define DNS_CERTTYPE_RR61 (DNS_CERTTYPE_RRBASE + 61) struct dns_addrinfo_s; typedef struct dns_addrinfo_s *dns_addrinfo_t; struct dns_addrinfo_s { dns_addrinfo_t next; int family; int socktype; int protocol; int addrlen; struct sockaddr_storage addr[1]; }; struct srventry { unsigned short priority; unsigned short weight; unsigned short port; int run_count; char target[1025]; }; /* Set verbosity and debug mode for this module. */ void set_dns_verbose (int verbose, int debug); /* Set the Disable-IPv4 flag so that the name resolver does not return * A addresses. */ void set_dns_disable_ipv4 (int yes); /* Set the Disable-IPv6 flag so that the name resolver does not return * AAAA addresses. */ void set_dns_disable_ipv6 (int yes); /* Set the timeout for libdns requests to SECONDS. */ void set_dns_timeout (int seconds); /* Calling this function with YES set to True forces the use of the * standard resolver even if dirmngr has been built with support for * an alternative resolver. */ void enable_standard_resolver (int yes); /* Return true if the standard resolver is used. */ int standard_resolver_p (void); /* Calling this function with YES switches libdns into recursive mode. * It has no effect on the standard resolver. */ void enable_recursive_resolver (int yes); /* Return true iff the recursive resolver is used. */ int recursive_resolver_p (void); /* Put this module eternally into Tor mode. When called agained with * NEW_CIRCUIT request a new TOR circuit for the next DNS query. */ void enable_dns_tormode (int new_circuit); void disable_dns_tormode (void); /* Change the default IP address of the nameserver to IPADDR. The address needs to be a numerical IP address and will be used for the next DNS query. Note that this is only used in Tor mode. */ void set_dns_nameserver (const char *ipaddr); /* SIGHUP action handler for this module. */ void reload_dns_stuff (int force); void free_dns_addrinfo (dns_addrinfo_t ai); /* Function similar to getaddrinfo. */ gpg_error_t resolve_dns_name (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_dai, char **r_canonname); /* Function similar to getnameinfo. */ gpg_error_t resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name); /* Return true if NAME is a numerical IP address. */ int is_ip_address (const char *name); /* Return true if NAME is an onion address. */ int is_onion_address (const char *name); /* Get the canonical name for NAME. */ gpg_error_t get_dns_cname (const char *name, char **r_cname); -/* Return a CERT record or an arbitray RR. */ +/* Return a CERT record or an arbitrary RR. */ gpg_error_t get_dns_cert (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url); /* Return an array of SRV records. */ gpg_error_t get_dns_srv (const char *name, const char *service, const char *proto, struct srventry **list, unsigned int *r_count); #endif /*GNUPG_DIRMNGR_DNS_STUFF_H*/ diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 5b8f14ff8..c660cf527 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -1,11290 +1,11290 @@ /* ========================================================================== * dns.c - Recursive, Reentrant DNS Resolver. * -------------------------------------------------------------------------- * Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, subject to the * following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. * ========================================================================== */ #if HAVE_CONFIG_H #include "config.h" #elif !defined _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include /* INT_MAX */ #include /* va_list va_start va_end */ #include /* offsetof() */ #ifdef _WIN32 /* JW: This breaks our mingw build: #define uint32_t unsigned int */ #else #include /* uint32_t */ #endif #include /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */ #include /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */ #include /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */ #include /* strcasecmp(3) strncasecmp(3) */ #include /* isspace(3) isdigit(3) */ #include /* time_t time(2) difftime(3) */ #include /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */ #include /* errno EINVAL ENOENT */ #undef NDEBUG #include /* assert(3) */ #if _WIN32 #ifndef FD_SETSIZE #define FD_SETSIZE 1024 #endif #include #include typedef SOCKET socket_fd_t; #define STDCALL __stdcall #ifdef TIME_WITH_SYS_TIME #include /* gettimeofday(2) */ #endif #else typedef int socket_fd_t; #define STDCALL #include /* gettimeofday(2) */ #include /* FD_SETSIZE socklen_t */ #include /* FD_ZERO FD_SET fd_set select(2) */ #include /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */ #if defined(AF_UNIX) #include /* struct sockaddr_un */ #endif #include /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */ #include /* _POSIX_THREADS gethostname(3) close(2) */ #include /* POLLIN POLLOUT */ #include /* struct sockaddr_in struct sockaddr_in6 */ #include /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */ #include /* struct addrinfo */ #endif #include "dns.h" /* * C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p)) #define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p))) #define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p)) #define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p))) #define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p) #if defined __has_builtin #define dns_has_builtin(x) __has_builtin(x) #else #define dns_has_builtin(x) 0 #endif #if defined __has_extension #define dns_has_extension(x) __has_extension(x) #else #define dns_has_extension(x) 0 #endif #ifndef HAVE___ASSUME #define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0) #endif #ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P #define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__) #endif #ifndef HAVE___BUILTIN_UNREACHABLE #define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable)) #endif #ifndef HAVE_PRAGMA_MESSAGE #define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER) #endif /* * C O M P I L E R A N N O T A T I O N S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if __GNUC__ #define DNS_NOTUSED __attribute__((unused)) #define DNS_NORETURN __attribute__((noreturn)) #else #define DNS_NOTUSED #define DNS_NORETURN #endif #if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wmissing-field-initializers" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif /* * S T A N D A R D M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if HAVE___BUILTIN_TYPES_COMPATIBLE_P #define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b)) #else #define dns_same_type(a, b, def) (def) #endif #define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0)) /* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */ #define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; }))) #if HAVE___ASSUME #define dns_assume(cond) __assume(cond) #elif HAVE___BUILTIN_UNREACHABLE #define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) #else #define dns_assume(cond) do { (void)(cond); } while (0) #endif #ifndef lengthof #define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0])) #endif #ifndef endof #define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))]) #endif /* * M I S C E L L A N E O U S C O M P A T * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if _WIN32 || _WIN64 #define PRIuZ "Iu" #else #define PRIuZ "zu" #endif #ifndef DNS_THREAD_SAFE #if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0 #define DNS_THREAD_SAFE 1 #else #define DNS_THREAD_SAFE 0 #endif #endif #ifndef HAVE__STATIC_ASSERT #define HAVE__STATIC_ASSERT \ (dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \ __C11FEATURES__ || __STDC_VERSION__ >= 201112L) #endif #ifndef HAVE_STATIC_ASSERT #if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0) #define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */ #else #define HAVE_STATIC_ASSERT (defined static_assert) #endif #endif #if HAVE_STATIC_ASSERT #define dns_static_assert(cond, msg) static_assert(cond, msg) #elif HAVE__STATIC_ASSERT #define dns_static_assert(cond, msg) _Static_assert(cond, msg) #else #define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])] #endif /* * D E B U G M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int *dns_debug_p(void) { static int debug; return &debug; } /* dns_debug_p() */ #if DNS_DEBUG #undef DNS_DEBUG #define DNS_DEBUG dns_debug #define DNS_SAY_(fmt, ...) \ do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0) #define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n") #define DNS_HAI DNS_SAY("HAI") #define DNS_SHOW_(P, fmt, ...) do { \ if (DNS_DEBUG > 1) { \ fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \ fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \ dns_p_dump((P), stderr); \ fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \ } \ } while (0) #define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "") #else /* !DNS_DEBUG */ #undef DNS_DEBUG #define DNS_DEBUG 0 #define DNS_SAY(...) #define DNS_HAI #define DNS_SHOW(...) #endif /* DNS_DEBUG */ #define DNS_CARP(...) DNS_SAY(__VA_ARGS__) /* * V E R S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ const char *dns_vendor(void) { return DNS_VENDOR; } /* dns_vendor() */ int dns_v_rel(void) { return DNS_V_REL; } /* dns_v_rel() */ int dns_v_abi(void) { return DNS_V_ABI; } /* dns_v_abi() */ int dns_v_api(void) { return DNS_V_API; } /* dns_v_api() */ /* * E R R O R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef EPROTO # define EPROTO EPROTONOSUPPORT #endif #if _WIN32 #define DNS_EINTR WSAEINTR #define DNS_EINPROGRESS WSAEINPROGRESS #define DNS_EISCONN WSAEISCONN #define DNS_EWOULDBLOCK WSAEWOULDBLOCK #define DNS_EALREADY WSAEALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT WSAETIMEDOUT #define dns_syerr() ((int)GetLastError()) #define dns_soerr() ((int)WSAGetLastError()) #else #define DNS_EINTR EINTR #define DNS_EINPROGRESS EINPROGRESS #define DNS_EISCONN EISCONN #define DNS_EWOULDBLOCK EWOULDBLOCK #define DNS_EALREADY EALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT ETIMEDOUT #define dns_syerr() errno #define dns_soerr() errno #endif const char *dns_strerror(int error) { switch (error) { case DNS_ENOBUFS: return "DNS packet buffer too small"; case DNS_EILLEGAL: return "Illegal DNS RR name or data"; case DNS_EORDER: return "Attempt to push RR out of section order"; case DNS_ESECTION: return "Invalid section specified"; case DNS_EUNKNOWN: return "Unknown DNS error"; case DNS_EADDRESS: return "Invalid textual address form"; case DNS_ENOQUERY: return "Bad execution state (missing query packet)"; case DNS_ENOANSWER: return "Bad execution state (missing answer packet)"; case DNS_EFETCHED: return "Answer already fetched"; case DNS_ESERVICE: return "The service passed was not recognized for the specified socket type"; case DNS_ENONAME: return "The name does not resolve for the supplied parameters"; case DNS_EFAIL: return "A non-recoverable error occurred when attempting to resolve the name"; case DNS_ECONNFIN: return "Connection closed"; case DNS_EVERIFY: return "Reply failed verification"; default: return strerror(error); } /* switch() */ } /* dns_strerror() */ /* * A T O M I C R O U T I N E S * * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we * can use the preprocessor to detect API and, more importantly, ISA * support. We want to avoid linking headaches where the API depends on an * external library if the ISA (e.g. i386) doesn't support lockless * operation. * * TODO: Support C11's atomic API. Although that may require some finesse * with how we define some public types, such as dns_atomic_t and struct * dns_resolv_conf. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HAVE___ATOMIC_FETCH_ADD #define HAVE___ATOMIC_FETCH_ADD (defined __ATOMIC_RELAXED) #endif #ifndef HAVE___ATOMIC_FETCH_SUB #define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD #endif #ifndef DNS_ATOMIC_FETCH_ADD #if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_add available") #define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++) #endif #endif #ifndef DNS_ATOMIC_FETCH_SUB #if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_sub available") #define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--) #endif #endif static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_ADD(i); } /* dns_atomic_fetch_add() */ static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_SUB(i); } /* dns_atomic_fetch_sub() */ /* * C R Y P T O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * P R N G */ #ifndef DNS_RANDOM #if defined(HAVE_ARC4RANDOM) \ || defined(__OpenBSD__) \ || defined(__FreeBSD__) \ || defined(__NetBSD__) \ || defined(__APPLE__) #define DNS_RANDOM arc4random #elif __linux #define DNS_RANDOM random #else #define DNS_RANDOM rand #endif #endif #define DNS_RANDOM_arc4random 1 #define DNS_RANDOM_random 2 #define DNS_RANDOM_rand 3 #define DNS_RANDOM_RAND_bytes 4 #define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM)) #if DNS_RANDOM_OPENSSL #include #endif static unsigned dns_random_(void) { #if DNS_RANDOM_OPENSSL unsigned r; _Bool ok; ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r)); assert(ok && "1 == RAND_bytes()"); return r; #else return DNS_RANDOM(); #endif } /* dns_random_() */ dns_random_f **dns_random_p(void) { static dns_random_f *random_f = &dns_random_; return &random_f; } /* dns_random_p() */ /* * P E R M U T A T I O N G E N E R A T O R */ #define DNS_K_TEA_KEY_SIZE 16 #define DNS_K_TEA_BLOCK_SIZE 8 #define DNS_K_TEA_CYCLES 32 #define DNS_K_TEA_MAGIC 0x9E3779B9U struct dns_k_tea { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned cycles; }; /* struct dns_k_tea */ static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) { memcpy(tea->key, key, sizeof tea->key); tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES; } /* dns_k_tea_init() */ static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) { uint32_t y, z, sum, n; y = v[0]; z = v[1]; sum = 0; for (n = 0; n < tea->cycles; n++) { sum += DNS_K_TEA_MAGIC; y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]); z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]); } w[0] = y; w[1] = z; return /* void */; } /* dns_k_tea_encrypt() */ /* * Permutation generator, based on a Luby-Rackoff Feistel construction. * * Specifically, this is a generic balanced Feistel block cipher using TEA * (another block cipher) as the pseudo-random function, F. At best it's as * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or * perhaps Bernstein's Salsa20 core; I am naively trying to keep things * simple. * * The generator can create a permutation of any set of numbers, as long as * the size of the set is an even power of 2. This limitation arises either * out of an inherent property of balanced Feistel constructions, or by my * own ignorance. I'll tackle an unbalanced construction after I wrap my * head around Schneier and Kelsey's paper. * * CAVEAT EMPTOR. IANAC. */ #define DNS_K_PERMUTOR_ROUNDS 8 struct dns_k_permutor { unsigned stepi, length, limit; unsigned shift, mask, rounds; struct dns_k_tea tea; }; /* struct dns_k_permutor */ static inline unsigned dns_k_permutor_powof(unsigned n) { unsigned m, i = 0; for (m = 1; m < n; m <<= 1, i++) ;; return i; } /* dns_k_permutor_powof() */ static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned width, i; p->stepi = 0; p->length = (high - low) + 1; p->limit = high; width = dns_k_permutor_powof(p->length); width += width % 2; p->shift = width / 2; p->mask = (1U << p->shift) - 1; p->rounds = DNS_K_PERMUTOR_ROUNDS; for (i = 0; i < lengthof(key); i++) key[i] = dns_random(); dns_k_tea_init(&p->tea, key, 0); return /* void */; } /* dns_k_permutor_init() */ static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) { uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)]; memset(in, '\0', sizeof in); in[0] = k; in[1] = x; dns_k_tea_encrypt(&p->tea, in, out); return p->mask & out[0]; } /* dns_k_permutor_F() */ static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = 0; l[i] = p->mask & (n >> p->shift); r[i] = p->mask & (n >> 0); do { l[(i + 1) % 2] = r[i % 2]; r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]); i++; } while (i < p->rounds - 1); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_E() */ DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = p->rounds - 1; l[i % 2] = p->mask & (n >> p->shift); r[i % 2] = p->mask & (n >> 0); do { i--; r[i % 2] = l[(i + 1) % 2]; l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]); } while (i > 0); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_D() */ static unsigned dns_k_permutor_step(struct dns_k_permutor *p) { unsigned n; do { n = dns_k_permutor_E(p, p->stepi++); } while (n >= p->length); return n + (p->limit + 1 - p->length); } /* dns_k_permutor_step() */ /* * Simple permutation box. Useful for shuffling rrsets from an iterator. * Uses AES s-box to provide good diffusion. * * Seems to pass muster under runs test. * * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }' * library(lawstat) * runs.test(scan(file="/tmp/out")) * EOF */ static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) { static const unsigned char sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; unsigned char a, b; unsigned i; a = 0xff & (n >> 0); b = 0xff & (n >> 8); for (i = 0; i < 4; i++) { a ^= 0xff & s; a = sbox[a] ^ b; b = sbox[b] ^ a; s >>= 8; } return ((0xff00 & (a << 8)) | (0x00ff & (b << 0))); } /* dns_k_shuffle16() */ /* * S T A T E M A C H I N E R O U T I N E S * * Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the * local variable pc. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_SM_ENTER \ do { \ static const int pc0 = __LINE__; \ DNS_SM_RESTORE; \ switch (pc0 + pc) { \ case __LINE__: (void)0 #define DNS_SM_SAVE_AND_DO(do_statement) \ do { \ pc = __LINE__ - pc0; \ DNS_SM_SAVE; \ do_statement; \ case __LINE__: (void)0; \ } while (0) #define DNS_SM_YIELD(rv) \ DNS_SM_SAVE_AND_DO(return (rv)) #define DNS_SM_EXIT \ do { goto leave; } while (0) #define DNS_SM_LEAVE \ leave: (void)0; \ DNS_SM_SAVE_AND_DO(break); \ } \ } while (0) /* * U T I L I T Y R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_MAXINTERVAL 300 struct dns_clock { time_t sample, elapsed; }; /* struct dns_clock */ static void dns_begin(struct dns_clock *clk) { clk->sample = time(0); clk->elapsed = 0; } /* dns_begin() */ static time_t dns_elapsed(struct dns_clock *clk) { time_t curtime; if ((time_t)-1 == time(&curtime)) return clk->elapsed; if (curtime > clk->sample) clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL); clk->sample = curtime; return clk->elapsed; } /* dns_elapsed() */ DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) { size_t n = 0; while (*src++ && n < m) ++n; return n; } /* dns_strnlen() */ DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) { size_t len = dns_strnlen(src, max), n; if (lim > 0) { n = DNS_PP_MIN(lim - 1, len); memcpy(dst, src, n); dst[n] = '\0'; } return len; } /* dns_strnlcpy() */ #define DNS_HAVE_SOCKADDR_UN (defined AF_UNIX && !defined _WIN32) static size_t dns_af_len(int af) { static const size_t table[AF_MAX] = { [AF_INET6] = sizeof (struct sockaddr_in6), [AF_INET] = sizeof (struct sockaddr_in), #if DNS_HAVE_SOCKADDR_UN [AF_UNIX] = sizeof (struct sockaddr_un), #endif }; return table[af]; } /* dns_af_len() */ #define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family) #define dns_sa_len(sa) dns_af_len(dns_sa_family(sa)) #define DNS_SA_NOPORT &dns_sa_noport static unsigned short dns_sa_noport; static unsigned short *dns_sa_port(int af, void *sa) { switch (af) { case AF_INET6: return &((struct sockaddr_in6 *)sa)->sin6_port; case AF_INET: return &((struct sockaddr_in *)sa)->sin_port; default: return DNS_SA_NOPORT; } } /* dns_sa_port() */ static void *dns_sa_addr(int af, const void *sa, socklen_t *size) { switch (af) { case AF_INET6: { struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr; if (size) *size = sizeof *in6; return in6; } case AF_INET: { struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr; if (size) *size = sizeof *in; return in; } default: if (size) *size = 0; return 0; } } /* dns_sa_addr() */ #if DNS_HAVE_SOCKADDR_UN #define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path) #endif DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) { switch (dns_sa_family(sa)) { #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char *path = ((struct sockaddr_un *)sa)->sun_path; if (size) *size = dns_strnlen(path, DNS_SUNPATHMAX); return path; } #endif default: if (size) *size = 0; return NULL; } } /* dns_sa_path() */ static int dns_sa_cmp(void *a, void *b) { int cmp, af; if ((cmp = dns_sa_family(a) - dns_sa_family(b))) return cmp; switch ((af = dns_sa_family(a))) { case AF_INET: { struct in_addr *a4, *b4; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a4 = dns_sa_addr(af, a, NULL); b4 = dns_sa_addr(af, b, NULL); if (ntohl(a4->s_addr) < ntohl(b4->s_addr)) return -1; if (ntohl(a4->s_addr) > ntohl(b4->s_addr)) return 1; return 0; } case AF_INET6: { struct in6_addr *a6, *b6; size_t i; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a6 = dns_sa_addr(af, a, NULL); b6 = dns_sa_addr(af, b, NULL); /* XXX: do we need to use in6_clearscope()? */ for (i = 0; i < sizeof a6->s6_addr; i++) { if ((cmp = a6->s6_addr[i] - b6->s6_addr[i])) return cmp; } return 0; } #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path]; dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX); dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX); return strcmp(a_path, b_path); } #endif default: return -1; } } /* dns_sa_cmp() */ #if _WIN32 static int dns_inet_pton(int af, const void *src, void *dst) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; u.sin.sin_family = af; if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u })) return -1; switch (af) { case AF_INET6: *(struct in6_addr *)dst = u.sin6.sin6_addr; return 1; case AF_INET: *(struct in_addr *)dst = u.sin.sin_addr; return 1; default: return 0; } } /* dns_inet_pton() */ static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */ memset(&u, 0, sizeof u); u.sin.sin_family = af; switch (af) { case AF_INET6: u.sin6.sin6_addr = *(struct in6_addr *)src; break; case AF_INET: u.sin.sin_addr = *(struct in_addr *)src; break; default: return 0; } if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim)) return 0; return dst; } /* dns_inet_ntop() */ #else #define dns_inet_pton(...) inet_pton(__VA_ARGS__) #define dns_inet_ntop(...) inet_ntop(__VA_ARGS__) #endif static dns_error_t dns_pton(int af, const void *src, void *dst) { switch (dns_inet_pton(af, src, dst)) { case 1: return 0; case -1: return dns_soerr(); default: return DNS_EADDRESS; } } /* dns_pton() */ static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) { return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr(); } /* dns_ntop() */ size_t dns_strlcpy(char *dst, const char *src, size_t lim) { char *d = dst; char *e = &dst[lim]; const char *s = src; if (d < e) { do { if ('\0' == (*d++ = *s++)) return s - src - 1; } while (d < e); d[-1] = '\0'; } while (*s++ != '\0') ;; return s - src - 1; } /* dns_strlcpy() */ size_t dns_strlcat(char *dst, const char *src, size_t lim) { char *d = memchr(dst, '\0', lim); char *e = &dst[lim]; const char *s = src; const char *p; if (d && d < e) { do { if ('\0' == (*d++ = *s++)) return d - dst - 1; } while (d < e); d[-1] = '\0'; } p = s; while (*s++ != '\0') ;; return lim + (s - p - 1); } /* dns_strlcat() */ static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) { void *rp; if (nmemb > 0 && SIZE_MAX / nmemb < size) { *error = EOVERFLOW; return NULL; } if (!(rp = realloc(p, nmemb * size))) *error = (errno)? errno : EINVAL; return rp; } /* dns_reallocarray() */ #if _WIN32 static char *dns_strsep(char **sp, const char *delim) { char *p; if (!(p = *sp)) return 0; *sp += strcspn(p, delim); if (**sp != '\0') { **sp = '\0'; ++*sp; } else *sp = NULL; return p; } /* dns_strsep() */ #else #define dns_strsep(...) strsep(__VA_ARGS__) #endif #if _WIN32 #define strcasecmp(...) _stricmp(__VA_ARGS__) #define strncasecmp(...) _strnicmp(__VA_ARGS__) #endif static inline _Bool dns_isalpha(unsigned char c) { return isalpha(c); } /* dns_isalpha() */ static inline _Bool dns_isdigit(unsigned char c) { return isdigit(c); } /* dns_isdigit() */ static inline _Bool dns_isalnum(unsigned char c) { return isalnum(c); } /* dns_isalnum() */ static inline _Bool dns_isspace(unsigned char c) { return isspace(c); } /* dns_isspace() */ static inline _Bool dns_isgraph(unsigned char c) { return isgraph(c); } /* dns_isgraph() */ static int dns_poll(int fd, short events, int timeout) { fd_set rset, wset; if (!events) return 0; if (fd < 0 || (unsigned)fd >= FD_SETSIZE) return EINVAL; FD_ZERO(&rset); FD_ZERO(&wset); if (events & DNS_POLLIN) FD_SET(fd, &rset); if (events & DNS_POLLOUT) FD_SET(fd, &wset); select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL); return 0; } /* dns_poll() */ #if !_WIN32 DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) { #if DNS_THREAD_SAFE return pthread_sigmask(how, set, oset); #else return (0 == sigprocmask(how, set, oset))? 0 : errno; #endif } /* dns_sigmask() */ #endif static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) { long n = send(fd, src, len, flags); if (n < 0) { *error = dns_soerr(); return 0; } else { *error = 0; return n; } } /* dns_send() */ static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) { long n = recv(fd, dst, lim, flags); if (n < 0) { *error = dns_soerr(); return 0; } else if (n == 0) { *error = (lim > 0)? DNS_ECONNFIN : EINVAL; return 0; } else { *error = 0; return n; } } /* dns_recv() */ static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) { #if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE return dns_send(fd, src, len, flags, _error); #elif defined MSG_NOSIGNAL return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error); #elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */ /* * SIGPIPE handling similar to the approach described in * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html */ sigset_t pending, blocked, piped; size_t count; int error; sigemptyset(&pending); sigpending(&pending); if (!sigismember(&pending, SIGPIPE)) { sigemptyset(&piped); sigaddset(&piped, SIGPIPE); sigemptyset(&blocked); if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked))) goto error; } count = dns_send(fd, src, len, flags, &error); if (!sigismember(&pending, SIGPIPE)) { int saved = error; if (!count && error == EPIPE) { while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR) ;; } if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL))) goto error; error = saved; } *_error = error; return count; error: *_error = error; return 0; #else #error "unable to suppress SIGPIPE" return dns_send(fd, src, len, flags, _error); #endif } /* dns_send_nopipe() */ static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { if (0 != connect(fd, addr, addrlen)) return dns_soerr(); return 0; } /* dns_connect() */ #define DNS_FOPEN_STDFLAGS "rwabt+" static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) { char *p = dst, *pe = dst + lim; /* copy standard flags */ while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) { if (!(p < pe)) return ENOMEM; *p++ = *src++; } /* append flag to standard flags */ if (!(p < pe)) return ENOMEM; *p++ = fc; /* copy remaining mode string, including '\0' */ do { if (!(p < pe)) return ENOMEM; } while ((*p++ = *src++)); return 0; } /* dns_fopen_addflag() */ static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) { FILE *fp; char mode_cloexec[32]; int error; assert(path && mode && *mode); if (!*path) { error = EINVAL; goto error; } #if _WIN32 || _WIN64 if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N'))) goto error; if (!(fp = fopen(path, mode_cloexec))) goto syerr; #else if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e'))) goto error; if (!(fp = fopen(path, mode_cloexec))) { if (errno != EINVAL) goto syerr; if (!(fp = fopen(path, mode))) goto syerr; } #endif return fp; syerr: error = dns_syerr(); error: *_error = error; return NULL; } /* dns_fopen() */ struct dns_hxd_lines_i { int pc; size_t p; }; #define DNS_SM_RESTORE \ do { \ pc = state->pc; \ sp = src + state->p; \ se = src + len; \ } while (0) #define DNS_SM_SAVE \ do { \ state->p = sp - src; \ state->pc = pc; \ } while (0) static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) { static const unsigned char hex[] = "0123456789abcdef"; static const unsigned char tmpl[] = " | |\n"; unsigned char ln[sizeof tmpl]; const unsigned char *sp, *se; unsigned char *h, *g; unsigned i, n; int pc; DNS_SM_ENTER; while (sp < se) { memcpy(ln, tmpl, sizeof ln); h = &ln[2]; g = &ln[53]; for (n = 0; n < 2; n++) { for (i = 0; i < 8 && se - sp > 0; i++, sp++) { h[0] = hex[0x0f & (*sp >> 4)]; h[1] = hex[0x0f & (*sp >> 0)]; h += 3; *g++ = (dns_isgraph(*sp))? *sp : '.'; } h++; } n = dns_strlcpy(dst, (char *)ln, lim); DNS_SM_YIELD(n); } DNS_SM_EXIT; DNS_SM_LEAVE; return 0; } #undef DNS_SM_SAVE #undef DNS_SM_RESTORE /* * A R I T H M E T I C R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_CHECK_OVERFLOW(error, r, f, ...) \ do { \ uintmax_t _r; \ *(error) = f(&_r, __VA_ARGS__); \ *(r) = _r; \ } while (0) static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) { if (n > clamp) { *r = clamp; return ERANGE; } else { *r = n; return 0; } } /* dns_clamp_overflow() */ static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (~a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a + b, clamp); } } /* dns_add_overflow() */ static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (a > 0 && UINTMAX_MAX / a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a * b, clamp); } } /* dns_mul_overflow() */ /* * F I X E D - S I Z E D B U F F E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_B_INIT(src, n) { \ (unsigned char *)(src), \ (unsigned char *)(src), \ (unsigned char *)(src) + (n), \ } #define DNS_B_FROM(src, n) DNS_B_INIT((src), (n)) #define DNS_B_INTO(src, n) DNS_B_INIT((src), (n)) struct dns_buf { const unsigned char *base; unsigned char *p; const unsigned char *pe; dns_error_t error; size_t overflow; }; /* struct dns_buf */ static inline size_t dns_b_tell(struct dns_buf *b) { return b->p - b->base; } static inline dns_error_t dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error) { b->overflow += n; return b->error = error; } DNS_NOTUSED static struct dns_buf * dns_b_into(struct dns_buf *b, void *src, size_t n) { *b = (struct dns_buf)DNS_B_INTO(src, n); return b; } static dns_error_t dns_b_putc(struct dns_buf *b, unsigned char uc) { if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_ENOBUFS); *b->p++ = uc; return 0; } static dns_error_t dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p) { size_t pe = b->pe - b->base; if (pe <= p) return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS); *((unsigned char *)b->base + p) = uc; return 0; } static inline dns_error_t dns_b_put16(struct dns_buf *b, uint16_t u) { return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static inline dns_error_t dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p) { if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1)) return b->error; return 0; } DNS_NOTUSED static inline dns_error_t dns_b_put32(struct dns_buf *b, uint32_t u) { return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16), dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static dns_error_t dns_b_put(struct dns_buf *b, const void *src, size_t len) { size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len); memcpy(b->p, src, n); b->p += n; if (n < len) return dns_b_setoverflow(b, len - n, DNS_ENOBUFS); return 0; } static dns_error_t dns_b_puts(struct dns_buf *b, const void *src) { return dns_b_put(b, src, strlen(src)); } DNS_NOTUSED static inline dns_error_t dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width) { size_t digits, padding, overflow; uintmax_t r; unsigned char *tp, *te, tc; digits = 0; r = u; do { digits++; r /= 10; } while (r); padding = width - DNS_PP_MIN(digits, width); overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding)); while (padding--) { dns_b_putc(b, '0'); } digits = 0; tp = b->p; r = u; do { if (overflow < ++digits) dns_b_putc(b, '0' + (r % 10)); r /= 10; } while (r); te = b->p; while (tp < te) { tc = *--te; *te = *tp; *tp++ = tc; } return b->error; } static void dns_b_popc(struct dns_buf *b) { if (b->overflow && !--b->overflow) b->error = 0; if (b->p > b->base) b->p--; } static inline const char * dns_b_tolstring(struct dns_buf *b, size_t *n) { if (b->p < b->pe) { *b->p = '\0'; *n = b->p - b->base; return (const char *)b->base; } else if (b->p > b->base) { if (b->p[-1] != '\0') { dns_b_setoverflow(b, 1, DNS_ENOBUFS); b->p[-1] = '\0'; } *n = &b->p[-1] - b->base; return (const char *)b->base; } else { *n = 0; return ""; } } static inline const char * dns_b_tostring(struct dns_buf *b) { size_t n; return dns_b_tolstring(b, &n); } static inline size_t dns_b_strlen(struct dns_buf *b) { size_t n; dns_b_tolstring(b, &n); return n; } static inline size_t dns_b_strllen(struct dns_buf *b) { size_t n = dns_b_strlen(b); return n + b->overflow; } DNS_NOTUSED static const struct dns_buf * dns_b_from(const struct dns_buf *b, const void *src, size_t n) { *(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n); return b; } static inline int dns_b_getc(const struct dns_buf *_b, const int eof) { struct dns_buf *b = (struct dns_buf *)_b; if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof; return *b->p++; } static inline intmax_t dns_b_get16(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_getc(b, 0) << 8); n |= (dns_b_getc(b, 0) << 0); return (!b->overflow)? n : eof; } DNS_NOTUSED static inline intmax_t dns_b_get32(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_get16(b, 0) << 16); n |= (dns_b_get16(b, 0) << 0); return (!b->overflow)? n : eof; } static inline dns_error_t dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n) { struct dns_buf *src = (struct dns_buf *)_src; size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n); size_t src_r = n - src_n; dns_b_put(dst, src->p, src_n); src->p += src_n; if (src_r) return dns_b_setoverflow(src, src_r, DNS_EILLEGAL); return dst->error; } /* * T I M E R O U T I N E S * * Most functions still rely on the older time routines defined in the * utility routines section, above. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_TIME_C(n) UINT64_C(n) #define DNS_TIME_INF (~DNS_TIME_C(0)) typedef uint64_t dns_time_t; typedef dns_time_t dns_microseconds_t; static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) { if (a < b) { *r = DNS_TIME_C(0); return ERANGE; } else { *r = a - b; return 0; } } static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) { if (ts) { dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec); dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec); dns_time_t usec = nsec / 1000; dns_microseconds_t r; if (rup && nsec % 1000 > 0) usec++; dns_time_mul(&r, sec, DNS_TIME_C(1000000)); dns_time_add(&r, r, usec); return r; } else { return DNS_TIME_INF; } } /* dns_ts2us() */ static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) { if (tv) { ts->tv_sec = tv->tv_sec; ts->tv_nsec = tv->tv_usec * 1000; return ts; } else { return NULL; } } /* dns_tv2ts() */ static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, us / 1000000, 1); dns_b_putc(&dst, '.'); dns_b_fmtju(&dst, us % 1000000, 6); return dns_b_strllen(&dst); } /* dns_utime_print() */ /* * P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ unsigned dns_p_count(struct dns_packet *P, enum dns_section section) { unsigned count; switch (section) { case DNS_S_QD: return ntohs(dns_header(P)->qdcount); case DNS_S_AN: return ntohs(dns_header(P)->ancount); case DNS_S_NS: return ntohs(dns_header(P)->nscount); case DNS_S_AR: return ntohs(dns_header(P)->arcount); default: count = 0; if (section & DNS_S_QD) count += ntohs(dns_header(P)->qdcount); if (section & DNS_S_AN) count += ntohs(dns_header(P)->ancount); if (section & DNS_S_NS) count += ntohs(dns_header(P)->nscount); if (section & DNS_S_AR) count += ntohs(dns_header(P)->arcount); return count; } } /* dns_p_count() */ struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) { if (!P) return 0; assert(size >= offsetof(struct dns_packet, data) + 12); memset(P, 0, sizeof *P); P->size = size - offsetof(struct dns_packet, data); P->end = 12; memset(P->data, '\0', 12); return P; } /* dns_p_init() */ static struct dns_packet *dns_p_reset(struct dns_packet *P) { return dns_p_init(P, offsetof(struct dns_packet, data) + P->size); } /* dns_p_reset() */ static unsigned short dns_p_qend(struct dns_packet *P) { unsigned short qend = 12; unsigned i, count = dns_p_count(P, DNS_S_QD); for (i = 0; i < count && qend < P->end; i++) { if (P->end == (qend = dns_d_skip(qend, P))) goto invalid; if (P->end - qend < 4) goto invalid; qend += 4; } return DNS_PP_MIN(qend, P->end); invalid: return P->end; } /* dns_p_qend() */ struct dns_packet *dns_p_make(size_t len, int *error) { struct dns_packet *P; size_t size = dns_p_calcsize(len); if (!(P = dns_p_init(malloc(size), size))) *error = dns_syerr(); return P; } /* dns_p_make() */ static void dns_p_free(struct dns_packet *P) { free(P); } /* dns_p_free() */ /* convience routine to free any existing packet before storing new packet */ static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { dns_p_free(*dst); *dst = src; return src; } /* dns_p_setptr() */ static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) { dns_p_setptr(dst, *src); *src = NULL; return *dst; } /* dns_p_movptr() */ int dns_p_grow(struct dns_packet **P) { struct dns_packet *tmp; size_t size; int error; if (!*P) { if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error))) return error; return 0; } size = dns_p_sizeof(*P); size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size++; if (size > 65536) return DNS_ENOBUFS; if (!(tmp = realloc(*P, dns_p_calcsize(size)))) return dns_syerr(); tmp->size = size; *P = tmp; return 0; } /* dns_p_grow() */ struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) { if (!P) return 0; P->end = DNS_PP_MIN(P->size, P0->end); memcpy(P->data, P0->data, P->end); return P; } /* dns_p_copy() */ struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) { size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0)); struct dns_packet *M; enum dns_section section; struct dns_rr rr, mr; int error, copy; if (!A && B) { A = B; Amask = Bmask; B = 0; } merge: if (!(M = dns_p_make(bufsiz, &error))) goto error; for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) { if (A && (section & Amask)) { dns_rr_foreach(&rr, A, .section = section) { if ((error = dns_rr_copy(M, &rr, A))) goto error; } } if (B && (section & Bmask)) { dns_rr_foreach(&rr, B, .section = section) { copy = 1; dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) { if (!(copy = dns_rr_cmp(&rr, B, &mr, M))) break; } if (copy && (error = dns_rr_copy(M, &rr, B))) goto error; } } } return M; error: dns_p_setptr(&M, NULL); if (error == DNS_ENOBUFS && bufsiz < 65535) { bufsiz = DNS_PP_MIN(65535, bufsiz * 2); goto merge; } *error_ = error; return 0; } /* dns_p_merge() */ static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t); void dns_p_dictadd(struct dns_packet *P, unsigned short dn) { unsigned short lp, lptr, i; lp = dn; while (lp < P->end) { if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) { lptr = ((0x3f & P->data[lp + 0]) << 8) | ((0xff & P->data[lp + 1]) << 0); for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { if (P->dict[i] == lptr) { P->dict[i] = dn; return; } } } lp = dns_l_skip(lp, P->data, P->end); } for (i = 0; i < lengthof(P->dict); i++) { if (!P->dict[i]) { P->dict[i] = dn; break; } } } /* dns_p_dictadd() */ static inline uint16_t plus1_ns (uint16_t count_net) { uint16_t count = ntohs (count_net); count++; return htons (count); } int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) { size_t end = P->end; int error; if ((error = dns_d_push(P, dn, dnlen))) goto error; if (P->size - P->end < 4) goto nobufs; P->data[P->end++] = 0xff & (type >> 8); P->data[P->end++] = 0xff & (type >> 0); P->data[P->end++] = 0xff & (class >> 8); P->data[P->end++] = 0xff & (class >> 0); if (section == DNS_S_QD) goto update; if (P->size - P->end < 6) goto nobufs; if (type != DNS_T_OPT) ttl = DNS_PP_MIN(ttl, 0x7fffffffU); P->data[P->end++] = ttl >> 24; P->data[P->end++] = ttl >> 16; P->data[P->end++] = ttl >> 8; P->data[P->end++] = ttl >> 0; if ((error = dns_any_push(P, (union dns_any *)any, type))) goto error; update: switch (section) { case DNS_S_QD: if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.qd.base && (error = dns_p_study(P))) goto error; dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount); P->memo.qd.end = P->end; P->memo.an.base = P->end; P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AN: if (dns_p_count(P, DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.an.base && (error = dns_p_study(P))) goto error; dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount); P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_NS: if (dns_p_count(P, DNS_S_AR)) goto order; if (!P->memo.ns.base && (error = dns_p_study(P))) goto error; dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount); P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AR: if (!P->memo.ar.base && (error = dns_p_study(P))) goto error; dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount); P->memo.ar.end = P->end; if (type == DNS_T_OPT && !P->memo.opt.p) { P->memo.opt.p = end; P->memo.opt.maxudp = class; P->memo.opt.ttl = ttl; } break; default: error = DNS_ESECTION; goto error; } /* switch() */ return 0; nobufs: error = DNS_ENOBUFS; goto error; order: error = DNS_EORDER; goto error; error: P->end = end; return error; } /* dns_p_push() */ #define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0) #define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0) struct dns_p_lines_i { int pc; enum dns_section section; struct dns_rr rr; int error; }; static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) { va_list ap; int error = 0, n; va_start(ap, fmt); if ((n = vsnprintf(dst, lim, fmt, ap)) < 0) error = errno; va_end(ap); *_error = error; return DNS_PP_MAX(n, 0); } /* dns_p_lines_fmt() */ #define DNS_P_LINE(...) \ do { \ len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \ if (len == 0 && error) \ goto error; \ DNS_SM_YIELD(len); \ } while (0) static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) { int error, pc; size_t len; *_error = 0; DNS_SM_ENTER; DNS_P_LINE(";; [HEADER]\n"); DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid)); DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); while (dns_rr_grep(&state->rr, 1, I, P, &error)) { if (state->section != state->rr.section) { DNS_P_LINE("\n"); DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section), dns_p_count(P, state->rr.section)); } if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error))) goto error; dns_strlcat(dst, "\n", lim); DNS_SM_YIELD(len + 1); state->section = state->rr.section; } if (error) goto error; DNS_SM_EXIT; error: for (;;) { *_error = error; DNS_SM_YIELD(0); } DNS_SM_LEAVE; *_error = 0; return 0; } /* dns_p_lines() */ #undef DNS_P_LINE #undef DNS_SM_SAVE #undef DNS_SM_RESTORE static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { struct dns_p_lines_i lines = { 0 }; char line[sizeof (union dns_any) * 2]; size_t len; int error; while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) { if (len < sizeof line) { fwrite(line, 1, len, fp); } else { fwrite(line, 1, sizeof line - 1, fp); fputc('\n', fp); } } } /* dns_p_dump3() */ void dns_p_dump(struct dns_packet *P, FILE *fp) { dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp); } /* dns_p_dump() */ static void dns_s_unstudy(struct dns_s_memo *m) { m->base = 0; m->end = 0; } static void dns_m_unstudy(struct dns_p_memo *m) { dns_s_unstudy(&m->qd); dns_s_unstudy(&m->an); dns_s_unstudy(&m->ns); dns_s_unstudy(&m->ar); m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; } /* dns_m_unstudy() */ static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) { unsigned short count, rp; count = dns_p_count(P, section); for (rp = base; count && rp < P->end; count--) rp = dns_rr_skip(rp, P); m->base = base; m->end = rp; return 0; } /* dns_s_study() */ static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P))) goto error; if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P))) goto error; if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P))) goto error; if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P))) goto error; m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) { m->opt.p = rr.dn.p; m->opt.maxudp = rr.class; m->opt.ttl = rr.ttl; break; } return 0; error: dns_m_unstudy(m); return error; } /* dns_m_study() */ int dns_p_study(struct dns_packet *P) { return dns_m_study(&P->memo, P); } /* dns_p_study() */ enum dns_rcode dns_p_rcode(struct dns_packet *P) { return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode); } /* dns_p_rcode() */ /* * Q U E R Y P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_Q_RD 0x1 /* recursion desired */ #define DNS_Q_EDNS0 0x2 /* include OPT RR */ static dns_error_t dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags) { struct dns_packet *Q = NULL; int error; if (dns_p_movptr(&Q, _Q)) { dns_p_reset(Q); } else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) { goto error; } if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0))) goto error; dns_header(Q)->rd = !!(qflags & DNS_Q_RD); if (qflags & DNS_Q_EDNS0) { struct dns_opt opt = DNS_OPT_INIT(&opt); opt.version = 0; /* RFC 6891 version */ opt.maxudp = 4096; if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt))) goto error; } *_Q = Q; return 0; error: dns_p_free(Q); return error; } static dns_error_t dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags) { return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags); } static dns_error_t dns_q_remake(struct dns_packet **Q, int qflags) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error; assert(Q && *Q); if ((error = dns_rr_parse(&rr, 12, *Q))) return error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error))) return error; if (qlen >= sizeof qname) return DNS_EILLEGAL; return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags); } /* * D O M A I N N A M E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef DNS_D_MAXPTRS #define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */ #endif static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) { unsigned short len; unsigned nptrs = 0; retry: if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; if (lim > 0) { memcpy(dst, &data[src], DNS_PP_MIN(lim, len)); dst[DNS_PP_MIN(lim - 1, len)] = '\0'; } *nxt = src + len; return len; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: if (++nptrs > DNS_D_MAXPTRS) goto invalid; if (end - src < 2) goto invalid; src = ((0x3f & data[src + 0]) << 8) | ((0xff & data[src + 1]) << 0); goto retry; } /* switch() */ /* NOT REACHED */ invalid: *nxt = end; return 0; } /* dns_l_expand() */ static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) { unsigned short len; if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; return (len)? src + len : end; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: return end; } /* switch() */ /* NOT REACHED */ invalid: return end; } /* dns_l_skip() */ static _Bool dns_d_isanchored(const void *_src, size_t len) { const unsigned char *src = _src; return len > 0 && src[len - 1] == '.'; } /* dns_d_isanchored() */ static size_t dns_d_ndots(const void *_src, size_t len) { const unsigned char *p = _src, *pe = p + len; size_t ndots = 0; while ((p = memchr(p, '.', pe - p))) { ndots++; p++; } return ndots; } /* dns_d_ndots() */ static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) { unsigned char *dst = dst_; const unsigned char *src = src_; size_t dp = 0, sp = 0; int lc; /* trim any leading dot(s) */ while (sp < len && src[sp] == '.') sp++; for (lc = 0; sp < len; lc = src[sp++]) { /* trim extra dot(s) */ if (src[sp] == '.' && lc == '.') continue; if (dp < lim) dst[dp] = src[sp]; dp++; } if ((flags & DNS_D_ANCHOR) && lc != '.') { if (dp < lim) dst[dp] = '.'; dp++; } if (lim > 0) dst[DNS_PP_MIN(dp, lim - 1)] = '\0'; return dp; } /* dns_d_trim() */ char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) { if (flags & DNS_D_TRIM) { dns_d_trim(dst, lim, src, len, flags); } if (flags & DNS_D_ANCHOR) { dns_d_anchor(dst, lim, src, len); } else { memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0'; } return dst; } /* dns_d_init() */ size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) { if (len == 0) return 0; memmove(dst, src, DNS_PP_MIN(lim, len)); if (((const char *)src)[len - 1] != '.') { if (len < lim) ((char *)dst)[len] = '.'; len++; } if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_anchor() */ size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) { const char *dot; /* XXX: Skip any leading dot. Handles cleaving root ".". */ if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1))) return 0; len -= dot - (const char *)src; /* XXX: Unless root, skip the label's trailing dot. */ if (len > 1) { src = ++dot; len--; } else src = dot; memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_cleave() */ size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) { struct { unsigned char *b; size_t p, x; } dst, src; unsigned char ch = '.'; dst.b = dst_; dst.p = 0; dst.x = 1; src.b = (unsigned char *)src_; src.p = 0; src.x = 0; while (src.x < len) { ch = src.b[src.x]; if (ch == '.') { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x++; src.p = ++src.x; } else { if (dst.x < lim) dst.b[dst.x] = ch; dst.x++; src.x++; } } /* while() */ if (src.x > src.p) { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x; } if (dst.p > 1) { if (dst.p < lim) dst.b[dst.p] = 0x00; dst.p++; } #if 1 if (dst.p < lim) { struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b; unsigned i; a.p = 0; while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) { for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { b.p = P->dict[i]; while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) { a.y = a.x; b.y = b.x; while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) { a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim); b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end); } if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) { dst.b[a.p++] = 0xc0 | (0x3f & (b.p >> 8)); dst.b[a.p++] = (0xff & (b.p >> 0)); /* silence static analyzers */ dns_assume(a.p > 0); return a.p; } b.p = b.x; } /* while() */ } /* for() */ a.p = a.x; } /* while() */ } /* if () */ #endif if (!dst.p) *error = DNS_EILLEGAL; return dst.p; } /* dns_d_comp() */ unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) { unsigned short len; while (src < P->end) { switch (0x03 & (P->data[src] >> 6)) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src++]); if (0 == len) { /* success ==> */ return src; } else if (P->end - src > len) { src += len; break; } else goto invalid; /* NOT REACHED */ case 0x01: /* RESERVED */ goto invalid; case 0x02: /* RESERVED */ goto invalid; case 0x03: /* POINTER */ if (P->end - src < 2) goto invalid; src += 2; /* success ==> */ return src; } /* switch() */ } /* while() */ invalid: return P->end; } /* dns_d_skip() */ #include size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) { size_t dstp = 0; unsigned nptrs = 0; unsigned char len; while (src < P->end) { switch ((0x03 & (P->data[src] >> 6))) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src]); if (0 == len) { if (dstp == 0) { if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; } /* NUL terminate */ if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; /* success ==> */ return dstp; } src++; if (P->end - src < len) goto toolong; if (dstp < lim) memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp)); src += len; dstp += len; if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; nptrs = 0; continue; case 0x01: /* RESERVED */ goto reserved; case 0x02: /* RESERVED */ goto reserved; case 0x03: /* POINTER */ if (++nptrs > DNS_D_MAXPTRS) goto toolong; if (P->end - src < 2) goto toolong; src = ((0x3f & P->data[src + 0]) << 8) | ((0xff & P->data[src + 1]) << 0); continue; } /* switch() */ } /* while() */ toolong: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; reserved: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; } /* dns_d_expand() */ int dns_d_push(struct dns_packet *P, const void *dn, size_t len) { size_t lim = P->size - P->end; unsigned dp = P->end; int error = DNS_EILLEGAL; /* silence compiler */ len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error); if (len == 0) return error; if (len > lim) return DNS_ENOBUFS; P->end += len; dns_p_dictadd(P, dp); return 0; } /* dns_d_push() */ size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) { char host[DNS_D_MAXNAME + 1]; struct dns_rr_i i; struct dns_rr rr; unsigned depth; int error; if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len)) { error = ENAMETOOLONG; goto error; } for (depth = 0; depth < 7; depth++) { dns_rr_i_init(memset(&i, 0, sizeof i), P); i.section = DNS_S_ALL & ~DNS_S_QD; i.name = host; i.type = DNS_T_CNAME; if (!dns_rr_grep(&rr, 1, &i, P, &error)) break; if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P))) goto error; } return dns_strlcpy(dst, host, lim); error: *error_ = error; return 0; } /* dns_d_cname() */ /* * R E S O U R C E R E C O R D R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) { unsigned char dn[DNS_D_MAXNAME + 1]; union dns_any any; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error))) return error; else if (len >= sizeof dn) return DNS_EILLEGAL; if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q))) return error; return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any); } /* dns_rr_copy() */ int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) { unsigned short p = src; if (src >= P->end) goto invalid; rr->dn.p = p; rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p; if (P->end - p < 4) goto invalid; rr->type = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->class = ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); p += 4; if (src < dns_p_qend(P)) { rr->section = DNS_S_QUESTION; rr->ttl = 0; rr->rd.p = 0; rr->rd.len = 0; return 0; } if (P->end - p < 4) goto invalid; rr->ttl = ((0xff & P->data[p + 0]) << 24) | ((0xff & P->data[p + 1]) << 16) | ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); if (rr->type != DNS_T_OPT) rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU); p += 4; if (P->end - p < 2) goto invalid; rr->rd.len = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->rd.p = p + 2; p += 2; if (P->end - p < rr->rd.len) goto invalid; return 0; invalid: return DNS_EILLEGAL; } /* dns_rr_parse() */ static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) { unsigned short rp, rdlen; rp = dns_d_skip(src, P); if (P->end - rp < 4) return P->end - src; rp += 4; /* TYPE, CLASS */ if (rp <= dns_p_qend(P)) return rp - src; if (P->end - rp < 6) return P->end - src; rp += 6; /* TTL, RDLEN */ rdlen = ((0xff & P->data[rp - 2]) << 8) | ((0xff & P->data[rp - 1]) << 0); if (P->end - rp < rdlen) return P->end - src; rp += rdlen; return rp - src; } /* dns_rr_len() */ unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) { return src + dns_rr_len(src, P); } /* dns_rr_skip() */ static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) { enum dns_section section; unsigned count, index; unsigned short rp; if (src >= P->memo.qd.base && src < P->memo.qd.end) return DNS_S_QD; if (src >= P->memo.an.base && src < P->memo.an.end) return DNS_S_AN; if (src >= P->memo.ns.base && src < P->memo.ns.end) return DNS_S_NS; if (src >= P->memo.ar.base && src < P->memo.ar.end) return DNS_S_AR; /* NOTE: Possibly bad memoization. Try it the hard-way. */ for (rp = 12, index = 0; rp < src && rp < P->end; index++) rp = dns_rr_skip(rp, P); section = DNS_S_QD; count = dns_p_count(P, section); while (index >= count && section <= DNS_S_AR) { section <<= 1; count += dns_p_count(P, section); } return DNS_S_ALL & section; } /* dns_rr_section() */ static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_rr_parse(&rr, src, P))) return 0; return rr.type; } /* dns_rr_type() */ int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) { char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1]; union dns_any any0, any1; int cmp, error; size_t len; if ((cmp = r0->type - r1->type)) return cmp; if ((cmp = r0->class - r1->class)) return cmp; /* * FIXME: Do label-by-label comparison to handle illegally long names? */ if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error)) || len >= sizeof host0) return -1; if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error)) || len >= sizeof host1) return 1; if ((cmp = strcasecmp(host0, host1))) return cmp; if (DNS_S_QD & (r0->section | r1->section)) { if (r0->section == r1->section) return 0; return (r0->section == DNS_S_QD)? -1 : 1; } if ((error = dns_any_parse(&any0, r0, P0))) return -1; if ((error = dns_any_parse(&any1, r1, P1))) return 1; return dns_any_cmp(&any0, r0->type, &any1, r1->type); } /* dns_rr_cmp() */ static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) { struct dns_rr rr1; dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) { if (0 == dns_rr_cmp(rr0, P0, &rr1, P1)) return 1; } return 0; } /* dns_rr_exists() */ static unsigned short dns_rr_offset(struct dns_rr *rr) { return rr->dn.p; } /* dns_rr_offset() */ static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) { if (i->section && !(rr->section & i->section)) return 0; if (i->type && rr->type != i->type && i->type != DNS_T_ALL) return 0; if (i->class && rr->class != i->class && i->class != DNS_C_ANY) return 0; if (i->name) { char dn[DNS_D_MAXNAME + 1]; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error)) || len >= sizeof dn) return 0; if (0 != strcasecmp(dn, i->name)) return 0; } if (i->data && i->type && rr->section > DNS_S_QD) { union dns_any rd; int error; if ((error = dns_any_parse(&rd, rr, P))) return 0; if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type)) return 0; } return 1; } /* dns_rr_i_match() */ static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) { unsigned short rp; struct dns_rr r0, rr; int error; if ((i->section & DNS_S_QD) && P->memo.qd.base) rp = P->memo.qd.base; else if ((i->section & DNS_S_AN) && P->memo.an.base) rp = P->memo.an.base; else if ((i->section & DNS_S_NS) && P->memo.ns.base) rp = P->memo.ns.base; else if ((i->section & DNS_S_AR) && P->memo.ar.base) rp = P->memo.ar.base; else rp = 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; r0 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r0); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) < 0) r0 = rr; } return dns_rr_offset(&r0); } /* dns_rr_i_start() */ static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) { struct dns_rr r0, r1, rr; int error; if ((error = dns_rr_parse(&r0, rp, P))) return P->end; r0.section = dns_rr_section(rp, P); rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; r1 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r1); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; if (i->sort(&rr, &r1, i, P) >= 0) continue; r1 = rr; } return dns_rr_offset(&r1); } /* dns_rr_i_skip() */ int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { (void)i; (void)P; return (int)a->dn.p - (int)b->dn.p; } /* dns_rr_i_packet() */ int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; if ((cmp = a->section - b->section)) return cmp; if (a->type != b->type) return (int)a->dn.p - (int)b->dn.p; return dns_rr_cmp(a, P, b, P); } /* dns_rr_i_order() */ int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; (void)P; while (!i->state.regs[0]) i->state.regs[0] = dns_random(); if ((cmp = a->section - b->section)) return cmp; return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]); } /* dns_rr_i_shuffle() */ struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) { static const struct dns_rr_i i_initializer; (void)P; i->state = i_initializer.state; i->saved = i->state; return i; } /* dns_rr_i_init() */ unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) { unsigned count = 0; int error; switch (i->state.exec) { case 0: if (!i->sort) i->sort = &dns_rr_i_packet; i->state.next = dns_rr_i_start(i, P); i->state.exec++; /* FALL THROUGH */ case 1: while (count < lim && i->state.next < P->end) { if ((error = dns_rr_parse(rr, i->state.next, P))) goto error; rr->section = dns_rr_section(i->state.next, P); rr++; count++; i->state.count++; i->state.next = dns_rr_i_skip(i->state.next, i, P); } /* while() */ break; } /* switch() */ return count; error: *error_ = error; return count; } /* dns_rr_grep() */ size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) { struct dns_buf dst = DNS_B_INTO(_dst, lim); union dns_any any; size_t n; int error; if (rr->section == DNS_S_QD) dns_b_putc(&dst, ';'); if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error))) goto error; dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1)); if (rr->section != DNS_S_QD) { dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, rr->ttl, 0); } dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strclass(rr->class)); dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strtype(rr->type)); if (rr->section == DNS_S_QD) goto epilog; dns_b_putc(&dst, ' '); if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P))) goto error; n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type); dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p)); epilog: return dns_b_strllen(&dst); error: *_error = error; return 0; } /* dns_rr_print() */ int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) { unsigned long addr; if (rr->rd.len != 4) return DNS_EILLEGAL; addr = ((0xffU & P->data[rr->rd.p + 0]) << 24) | ((0xffU & P->data[rr->rd.p + 1]) << 16) | ((0xffU & P->data[rr->rd.p + 2]) << 8) | ((0xffU & P->data[rr->rd.p + 3]) << 0); a->addr.s_addr = htonl(addr); return 0; } /* dns_a_parse() */ int dns_a_push(struct dns_packet *P, struct dns_a *a) { unsigned long addr; if (P->size - P->end < 6) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x04; addr = ntohl(a->addr.s_addr); P->data[P->end++] = 0xffU & (addr >> 24); P->data[P->end++] = 0xffU & (addr >> 16); P->data[P->end++] = 0xffU & (addr >> 8); P->data[P->end++] = 0xffU & (addr >> 0); return 0; } /* dns_a_push() */ size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned long octets = ntohl(a->addr.s_addr); unsigned i; for (i = 0; i < 4; i++) { dns_b_fmtju(&dst, 0xff & octets, 0); dns_b_putc(&dst, '.'); octets >>= 8; } dns_b_puts(&dst, "in-addr.arpa."); return dns_b_strllen(&dst); } /* dns_a_arpa() */ int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) { if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr)) return -1; if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr)) return 1; return 0; } /* dns_a_cmp() */ size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) { char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0"; dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_a_print() */ int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) { if (rr->rd.len != sizeof aaaa->addr.s6_addr) return DNS_EILLEGAL; memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr); return 0; } /* dns_aaaa_parse() */ int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) { if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x10; memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr); P->end += sizeof aaaa->addr.s6_addr; return 0; } /* dns_aaaa_push() */ int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) { unsigned i; int cmp; for (i = 0; i < lengthof(a->addr.s6_addr); i++) { if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i]))) return cmp; } return 0; } /* dns_aaaa_cmp() */ size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned nyble; int i, j; for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) { nyble = aaaa->addr.s6_addr[i]; for (j = 0; j < 2; j++) { dns_b_putc(&dst, hex[0x0f & nyble]); dns_b_putc(&dst, '.'); nyble >>= 4; } } dns_b_puts(&dst, "ip6.arpa."); return dns_b_strllen(&dst); } /* dns_aaaa_arpa() */ size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) { char addr[INET6_ADDRSTRLEN + 1] = "::"; dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_aaaa_print() */ int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (rr->rd.len < 3) return DNS_EILLEGAL; mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8)) | (0x00ff & (P->data[rr->rd.p + 1] << 0)); if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error))) return error; else if (len >= sizeof mx->host) return DNS_EILLEGAL; return 0; } /* dns_mx_parse() */ int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) { size_t end, len; int error; if (P->size - P->end < 5) return DNS_ENOBUFS; end = P->end; P->end += 2; P->data[P->end++] = 0xff & (mx->preference >> 8); P->data[P->end++] = 0xff & (mx->preference >> 0); if ((error = dns_d_push(P, mx->host, strlen(mx->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_mx_push() */ int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) { int cmp; if ((cmp = a->preference - b->preference)) return cmp; return strcasecmp(a->host, b->host); } /* dns_mx_cmp() */ size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, mx->preference, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, mx->host); return dns_b_strllen(&dst); } /* dns_mx_print() */ size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) { return dns_strlcpy(dst, mx->host, lim); } /* dns_mx_cname() */ int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error))) return error; else if (len >= sizeof ns->host) return DNS_EILLEGAL; return 0; } /* dns_ns_parse() */ int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) { size_t end, len; int error; if (P->size - P->end < 3) return DNS_ENOBUFS; end = P->end; P->end += 2; if ((error = dns_d_push(P, ns->host, strlen(ns->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_ns_push() */ int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) { return strcasecmp(a->host, b->host); } /* dns_ns_cmp() */ size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_print() */ size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_cname() */ int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)cname, rr, P); } /* dns_cname_parse() */ int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) { return dns_ns_push(P, (struct dns_ns *)cname); } /* dns_cname_push() */ int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) { return strcasecmp(a->host, b->host); } /* dns_cname_cmp() */ size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) { return dns_ns_print(dst, lim, (struct dns_ns *)cname); } /* dns_cname_print() */ size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) { return dns_strlcpy(dst, cname->host, lim); } /* dns_cname_cname() */ int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) { struct { void *dst; size_t lim; } dn[] = { { soa->mname, sizeof soa->mname }, { soa->rname, sizeof soa->rname } }; unsigned *ts[] = { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum }; unsigned short rp; unsigned i, j, n; int error; /* MNAME / RNAME */ if ((rp = rr->rd.p) >= P->end) return DNS_EILLEGAL; for (i = 0; i < lengthof(dn); i++) { if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error))) return error; else if (n >= dn[i].lim) return DNS_EILLEGAL; if ((rp = dns_d_skip(rp, P)) >= P->end) return DNS_EILLEGAL; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { for (j = 0; j < 4; j++, rp++) { if (rp >= P->end) return DNS_EILLEGAL; *ts[i] <<= 8; *ts[i] |= (0xff & P->data[rp]); } } return 0; } /* dns_soa_parse() */ int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) { void *dn[] = { soa->mname, soa->rname }; unsigned ts[] = { (0xffffffff & soa->serial), (0x7fffffff & soa->refresh), (0x7fffffff & soa->retry), (0x7fffffff & soa->expire), (0xffffffff & soa->minimum) }; unsigned i, j; size_t end, len; int error; end = P->end; if ((P->end += 2) >= P->size) goto toolong; /* MNAME / RNAME */ for (i = 0; i < lengthof(dn); i++) { if ((error = dns_d_push(P, dn[i], strlen(dn[i])))) goto error; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { if ((P->end += 4) >= P->size) goto toolong; for (j = 1; j <= 4; j++) { P->data[P->end - j] = (0xff & ts[i]); ts[i] >>= 8; } } len = P->end - end - 2; P->data[end + 0] = (0xff & (len >> 8)); P->data[end + 1] = (0xff & (len >> 0)); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_soa_push() */ int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) { int cmp; if ((cmp = strcasecmp(a->mname, b->mname))) return cmp; if ((cmp = strcasecmp(a->rname, b->rname))) return cmp; if (a->serial > b->serial) return -1; else if (a->serial < b->serial) return 1; if (a->refresh > b->refresh) return -1; else if (a->refresh < b->refresh) return 1; if (a->retry > b->retry) return -1; else if (a->retry < b->retry) return 1; if (a->expire > b->expire) return -1; else if (a->expire < b->expire) return 1; if (a->minimum > b->minimum) return -1; else if (a->minimum < b->minimum) return 1; return 0; } /* dns_soa_cmp() */ size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_puts(&dst, soa->mname); dns_b_putc(&dst, ' '); dns_b_puts(&dst, soa->rname); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->serial, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->refresh, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->retry, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->expire, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->minimum, 0); return dns_b_strllen(&dst); } /* dns_soa_print() */ int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) { unsigned short rp; unsigned i; size_t n; int error; memset(srv, '\0', sizeof *srv); rp = rr->rd.p; if (rr->rd.len < 7) return DNS_EILLEGAL; for (i = 0; i < 2; i++, rp++) { srv->priority <<= 8; srv->priority |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->weight <<= 8; srv->weight |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->port <<= 8; srv->port |= (0xff & P->data[rp]); } if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error))) return error; else if (n >= sizeof srv->target) return DNS_EILLEGAL; return 0; } /* dns_srv_parse() */ int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) { size_t end, len; int error; end = P->end; if (P->size - P->end < 2) goto toolong; P->end += 2; if (P->size - P->end < 6) goto toolong; P->data[P->end++] = 0xff & (srv->priority >> 8); P->data[P->end++] = 0xff & (srv->priority >> 0); P->data[P->end++] = 0xff & (srv->weight >> 8); P->data[P->end++] = 0xff & (srv->weight >> 0); P->data[P->end++] = 0xff & (srv->port >> 8); P->data[P->end++] = 0xff & (srv->port >> 0); if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error))) goto error; else if (P->size - P->end < len) goto toolong; P->end += len; if (P->end > 65535) goto toolong; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_srv_push() */ int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) { int cmp; if ((cmp = a->priority - b->priority)) return cmp; /* * FIXME: We need some sort of random seed to implement the dynamic * weighting required by RFC 2782. */ if ((cmp = a->weight - b->weight)) return cmp; if ((cmp = a->port - b->port)) return cmp; return strcasecmp(a->target, b->target); } /* dns_srv_cmp() */ size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, srv->priority, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->weight, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->port, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, srv->target); return dns_b_strllen(&dst); } /* dns_srv_print() */ size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) { return dns_strlcpy(dst, srv->target, lim); } /* dns_srv_cname() */ unsigned int dns_opt_ttl(const struct dns_opt *opt) { unsigned int ttl = 0; ttl |= (0xffU & opt->rcode) << 24; ttl |= (0xffU & opt->version) << 16; ttl |= (0xffffU & opt->flags) << 0; return ttl; } /* dns_opt_ttl() */ unsigned short dns_opt_class(const struct dns_opt *opt) { return opt->maxudp; } /* dns_opt_class() */ struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) { assert(size >= offsetof(struct dns_opt, data)); opt->size = size - offsetof(struct dns_opt, data); opt->len = 0; opt->rcode = 0; opt->version = 0; opt->maxudp = 0; return opt; } /* dns_opt_init() */ static union dns_any *dns_opt_initany(union dns_any *any, size_t size) { return dns_opt_init(&any->opt, size), any; } /* dns_opt_initany() */ int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) { const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len); struct dns_buf dst = DNS_B_INTO(opt->data, opt->size); int error; opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode); opt->version = 0xff & (rr->ttl >> 16); opt->flags = 0xffff & rr->ttl; opt->maxudp = 0xffff & rr->class; while (src.p < src.pe) { int code, len; if (-1 == (code = dns_b_get16(&src, -1))) return src.error; if (-1 == (len = dns_b_get16(&src, -1))) return src.error; switch (code) { default: dns_b_put16(&dst, code); dns_b_put16(&dst, len); if ((error = dns_b_move(&dst, &src, len))) return error; break; } } return 0; } /* dns_opt_parse() */ int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) { const struct dns_buf src = DNS_B_FROM(opt->data, opt->len); struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end)); int error; /* rdata length (see below) */ if ((error = dns_b_put16(&dst, 0))) goto error; /* ... push known options here */ /* push opaque option data */ if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p)))) goto error; /* rdata length */ if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0))) goto error; #if !DNS_DEBUG_OPT_FORMERR P->end += dns_b_tell(&dst); #endif return 0; error: return error; } /* dns_opt_push() */ int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) { (void)a; (void)b; return -1; } /* dns_opt_cmp() */ size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) { struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t p; dns_b_putc(&dst, '"'); for (p = 0; p < opt->len; p++) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, opt->data[p], 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_opt_print() */ int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)ptr, rr, P); } /* dns_ptr_parse() */ int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) { return dns_ns_push(P, (struct dns_ns *)ptr); } /* dns_ptr_push() */ size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) { switch (af) { case AF_INET6: return dns_aaaa_arpa(dst, lim, addr); case AF_INET: return dns_a_arpa(dst, lim, addr); default: { struct dns_a a; a.addr.s_addr = INADDR_NONE; return dns_a_arpa(dst, lim, &a); } } } /* dns_ptr_qname() */ int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) { return strcasecmp(a->host, b->host); } /* dns_ptr_cmp() */ size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_ns_print(dst, lim, (struct dns_ns *)ptr); } /* dns_ptr_print() */ size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_strlcpy(dst, ptr->host, lim); } /* dns_ptr_cname() */ int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) { unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len; if (pe - p < 2) return DNS_EILLEGAL; fp->algo = P->data[p++]; fp->type = P->data[p++]; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_EILLEGAL; memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1); break; default: break; } /* switch() */ return 0; } /* dns_sshfp_parse() */ int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) { unsigned p = P->end, pe = P->size, n; if (pe - p < 4) return DNS_ENOBUFS; p += 2; P->data[p++] = 0xff & fp->algo; P->data[p++] = 0xff & fp->type; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_ENOBUFS; memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1); p += sizeof fp->digest.sha1; break; default: return DNS_EILLEGAL; } /* switch() */ n = p - P->end - 2; P->data[P->end++] = 0xff & (n >> 8); P->data[P->end++] = 0xff & (n >> 0); P->end = p; return 0; } /* dns_sshfp_push() */ int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) { int cmp; if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type)) return cmp; switch (a->type) { case DNS_SSHFP_SHA1: return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1); default: return 0; } /* switch() */ /* NOT REACHED */ } /* dns_sshfp_cmp() */ size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t i; dns_b_fmtju(&dst, fp->algo, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, fp->type, 0); dns_b_putc(&dst, ' '); switch (fp->type) { case DNS_SSHFP_SHA1: for (i = 0; i < sizeof fp->digest.sha1; i++) { dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]); dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]); } break; default: dns_b_putc(&dst, '0'); break; } /* switch() */ return dns_b_strllen(&dst); } /* dns_sshfp_print() */ struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) { assert(size > offsetof(struct dns_txt, data)); txt->size = size - offsetof(struct dns_txt, data); txt->len = 0; return txt; } /* dns_txt_init() */ static union dns_any *dns_txt_initany(union dns_any *any, size_t size) { /* NB: union dns_any is already initialized as struct dns_txt */ (void)size; return any; } /* dns_txt_initany() */ int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = txt->data; dst.p = 0; dst.end = txt->size; src.b = P->data; src.p = rr->rd.p; src.end = src.p + rr->rd.len; while (src.p < src.end) { n = 0xff & P->data[src.p++]; if (src.end - src.p < n || dst.end - dst.p < n) return DNS_EILLEGAL; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } txt->len = dst.p; return 0; } /* dns_txt_parse() */ int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = P->data; dst.p = P->end; dst.end = P->size; src.b = txt->data; src.p = 0; src.end = txt->len; if (dst.end - dst.p < 2) return DNS_ENOBUFS; n = txt->len + ((txt->len + 254) / 255); dst.b[dst.p++] = 0xff & (n >> 8); dst.b[dst.p++] = 0xff & (n >> 0); while (src.p < src.end) { n = DNS_PP_MIN(255, src.end - src.p); if (dst.p >= dst.end) return DNS_ENOBUFS; dst.b[dst.p++] = n; if (dst.end - dst.p < n) return DNS_ENOBUFS; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } P->end = dst.p; return 0; } /* dns_txt_push() */ int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) { (void)a; (void)b; return -1; } /* dns_txt_cmp() */ size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) { struct dns_buf src = DNS_B_FROM(txt->data, txt->len); struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; if (src.p < src.pe) { do { dns_b_putc(&dst, '"'); for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) { if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p, 3); } else { dns_b_putc(&dst, *src.p); } } dns_b_putc(&dst, '"'); dns_b_putc(&dst, ' '); } while (src.p < src.pe); dns_b_popc(&dst); } else { dns_b_putc(&dst, '"'); dns_b_putc(&dst, '"'); } return dns_b_strllen(&dst); } /* dns_txt_print() */ /* Some of the function pointers of DNS_RRTYPES are initialized with - * slighlly different fucntions, thus we can't use prototypes. */ + * slighlly different functions, thus we can't use prototypes. */ DNS_PRAGMA_PUSH #if __clang__ #pragma clang diagnostic ignored "-Wstrict-prototypes" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif static const struct dns_rrtype { enum dns_type type; const char *name; union dns_any *(*init)(union dns_any *, size_t); int (*parse)(); int (*push)(); int (*cmp)(); size_t (*print)(); size_t (*cname)(); } dns_rrtypes[] = { { DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, }, { DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, }, { DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, }, { DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, }, { DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, }, { DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, }, { DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, }, { DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, }, { DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, }, { DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, }, { DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, }, }; /* dns_rrtypes[] */ DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/ static const struct dns_rrtype *dns_rrtype(enum dns_type type) { const struct dns_rrtype *t; for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) { if (t->type == type && t->parse) { return t; } } return NULL; } /* dns_rrtype() */ union dns_any *dns_any_init(union dns_any *any, size_t size) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return (union dns_any *)dns_txt_init(&any->rdata, size); } /* dns_any_init() */ static size_t dns_any_sizeof(union dns_any *any) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return offsetof(struct dns_txt, data) + any->rdata.size; } /* dns_any_sizeof() */ static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) { return (t->init)? t->init(any, dns_any_sizeof(any)) : any; } /* dns_any_reinit() */ int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) { const struct dns_rrtype *t; if ((t = dns_rrtype(rr->type))) return t->parse(dns_any_reinit(any, t), rr, P); if (rr->rd.len > any->rdata.size) return DNS_EILLEGAL; memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len); any->rdata.len = rr->rd.len; return 0; } /* dns_any_parse() */ int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type))) return t->push(P, any); if (P->size - P->end < any->rdata.len + 2) return DNS_ENOBUFS; P->data[P->end++] = 0xff & (any->rdata.len >> 8); P->data[P->end++] = 0xff & (any->rdata.len >> 0); memcpy(&P->data[P->end], any->rdata.data, any->rdata.len); P->end += any->rdata.len; return 0; } /* dns_any_push() */ int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) { const struct dns_rrtype *t; int cmp; if ((cmp = x - y)) return cmp; if ((t = dns_rrtype(x))) return t->cmp(a, b); return -1; } /* dns_any_cmp() */ size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; struct dns_buf src, dst; if ((t = dns_rrtype(type))) return t->print(_dst, lim, any); dns_b_from(&src, any->rdata.data, any->rdata.len); dns_b_into(&dst, _dst, lim); dns_b_putc(&dst, '"'); while (src.p < src.pe) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p++, 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_any_print() */ size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type)) && t->cname) return t->cname(dst, lim, any); return 0; } /* dns_any_cname() */ /* * E V E N T T R A C I N G R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include /* DBL_MANT_DIG */ #include /* PRIu64 */ /* for default trace ID generation try to fit in lua_Number, usually double */ #define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */ #define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1))) #define DNS_TRACE_ID_PRI PRIu64 static inline dns_trace_id_t dns_trace_mkid(void) { dns_trace_id_t id = 0; unsigned r; /* return type of dns_random() */ const size_t id_bit = sizeof id * CHAR_BIT; const size_t r_bit = sizeof r * CHAR_BIT; for (size_t n = 0; n < id_bit; n += r_bit) { r = dns_random(); id <<= r_bit; id |= r; } return DNS_TRACE_ID_MASK & id; } struct dns_trace { dns_atomic_t refcount; FILE *fp; dns_trace_id_t id; struct { struct dns_trace_cname { char host[DNS_D_MAXNAME + 1]; struct sockaddr_storage addr; } base[4]; size_t p; } cnames; }; static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) { socklen_t n = sizeof *ss; if (0 != f(fd, (struct sockaddr *)ss, &n)) goto unspec; if (n > sizeof *ss) goto unspec; return; unspec: memset(ss, '\0', sizeof *ss); ss->ss_family = AF_UNSPEC; } static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) { dns_te_initname(local, fd, &getsockname); dns_te_initname(remote, fd, &getpeername); } static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) { /* NB: silence valgrind */ memset(te, '\0', offsetof(struct dns_trace_event, data)); te->type = type; return te; } int dns_trace_abi(void) { return DNS_TRACE_ABI; } struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) { static const struct dns_trace trace_initializer = { .refcount = 1 }; struct dns_trace *trace; if (!(trace = malloc(sizeof *trace))) goto syerr; *trace = trace_initializer; if (fp) { trace->fp = fp; } else if (!(fp = tmpfile())) { goto syerr; } trace->id = dns_trace_mkid(); return trace; syerr: *error = dns_syerr(); dns_trace_close(trace); return NULL; } /* dns_trace_open() */ void dns_trace_close(struct dns_trace *trace) { if (!trace || 1 != dns_trace_release(trace)) return; if (trace->fp) fclose(trace->fp); free(trace); } /* dns_trace_close() */ dns_refcount_t dns_trace_acquire(struct dns_trace *trace) { return dns_atomic_fetch_add(&trace->refcount); } /* dns_trace_acquire() */ static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) { return (trace)? dns_trace_acquire(trace), trace : NULL; } /* dns_trace_acquire_p() */ dns_refcount_t dns_trace_release(struct dns_trace *trace) { return dns_atomic_fetch_sub(&trace->refcount); } /* dns_trace_release() */ dns_trace_id_t dns_trace_id(struct dns_trace *trace) { return trace->id; } /* dns_trace_id() */ dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) { trace->id = (id)? id : dns_trace_mkid(); return trace->id; } /* dns_trace_setid() */ struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) { return dns_trace_fget(tp, trace->fp, error); } /* dns_trace_get() */ dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_fput(te, data, datasize, trace->fp); } /* dns_trace_put() */ struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) { struct timeval tv; te->id = trace->id; gettimeofday(&tv, NULL); dns_tv2ts(&te->ts, &tv); te->abi = DNS_TRACE_ABI; return te; } /* dns_trace_tag() */ static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize); } /* dns_trace_tag_and_put() */ struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) { const size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp, *te; size_t n; errno = 0; if (!(n = fread(&tmp, 1, headsize, fp))) goto none; if (n < offsetof(struct dns_trace_event, data)) goto some; if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) { *error = errno; return NULL; } *tp = te; memcpy(te, &tmp, offsetof(struct dns_trace_event, data)); if (dns_te_datasize(te)) { errno = 0; if (!(n = fread(te->data, 1, dns_te_datasize(te), fp))) goto none; if (n < dns_te_datasize(te)) goto some; } return te; none: *error = (ferror(fp))? errno : 0; return NULL; some: *error = 0; return NULL; } dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) { size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp; memcpy(&tmp, te, headsize); tmp.size = headsize + datasize; /* NB: ignore seek error as fp might not point to a regular file */ (void)fseek(fp, 0, SEEK_END); if (fwrite(&tmp, 1, headsize, fp) < headsize) return errno; if (data) if (fwrite(data, 1, datasize, fp) < datasize) return errno; if (fflush(fp)) return errno; return 0; } static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) { struct dns_trace_cname *cname; if (!trace || !trace->fp) return; cname = &trace->cnames.base[trace->cnames.p]; dns_strlcpy(cname->host, host, sizeof cname->host); memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr)); trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base); } static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) { if (!trace || !trace->fp) return NULL; /* NB: start search from the write cursor to */ for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) { if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr)) return cname->host; } return NULL; } static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_SUBMIT); dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname); te.res_submit.qtype = qtype; te.res_submit.qclass = qclass; te.res_submit.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.res_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) { struct dns_trace_event te; const char *cname; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_SUBMIT); memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr)); if ((cname = dns_trace_cname(trace, haddr))) dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname); te.so_submit.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_VERIFY); te.so_verify.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.so_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_CONNECT); dns_te_initname(&te.sys_connect.src, fd, &getsockname); memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst)); te.sys_connect.socktype = socktype; te.sys_connect.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_SEND); dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd); te.sys_send.socktype = socktype; te.sys_send.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_RECV); dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd); te.sys_recv.socktype = socktype; te.sys_recv.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_packet *packet = NULL; char *line = NULL, *p; size_t size = 1, skip = 0; struct dns_rr_i records; struct dns_p_lines_i lines; size_t len, count; int error; if (!(packet = dns_p_make(datasize, &error))) goto error; memcpy(packet->data, data, datasize); packet->end = datasize; (void)dns_p_study(packet); resize: if (!(p = dns_reallocarray(line, size, 2, &error))) goto error; line = p; size *= 2; memset(&records, 0, sizeof records); memset(&lines, 0, sizeof lines); count = 0; while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) { if (!(len < size)) { skip = count; goto resize; } else if (skip <= count) { fputs(prefix, fp); fwrite(line, 1, len, fp); } count++; } if (error) goto error; error = 0; error: free(line); dns_p_free(packet); return error; } static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; size_t len; while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) { if (len >= sizeof line) return EOVERFLOW; /* shouldn't be possible */ fputs(prefix, fp); fwrite(line, 1, len, fp); } return 0; } static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) { const void *addr; const char *path; socklen_t len; int error; if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) { char ip[INET6_ADDRSTRLEN + 1]; if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip))) return error; fprintf(fp, "%s%s\n", prefix, ip); } else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) { fprintf(fp, "%sunix:%.*s", prefix, (int)len, path); } else { return EINVAL; } return 0; } static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) { char time_s[48], elapsed_s[48]; dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0)); dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed); fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id); fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s); fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI); return 0; } static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) { fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none"); return 0; } dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) { struct dns_trace_event *te = NULL; struct { dns_trace_id_t id; dns_microseconds_t begin, elapsed; } state = { 0 }; int error; if (!trace || !trace->fp) return EINVAL; if (0 != fseek(trace->fp, 0, SEEK_SET)) goto syerr; while (dns_trace_fget(&te, trace->fp, &error)) { size_t datasize = dns_te_datasize(te); const unsigned char *data = (datasize)? te->data : NULL; if (state.id != te->id) { state.id = te->id; state.begin = dns_ts2us(&te->ts, 0); } dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin); switch(te->type) { case DNS_TE_RES_SUBMIT: fprintf(fp, "dns_res_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " qname: %s\n", te->res_submit.qname); fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype)); fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass)); dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp); break; case DNS_TE_RES_FETCH: fprintf(fp, "dns_res_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_SUBMIT: fprintf(fp, "dns_so_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " hname: %s\n", te->so_submit.hname); dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp); dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_VERIFY: fprintf(fp, "dns_so_verify:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_FETCH: fprintf(fp, "dns_so_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SYS_CONNECT: { int socktype = te->sys_connect.socktype; fprintf(fp, "dns_sys_connect:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp); break; } case DNS_TE_SYS_SEND: { int socktype = te->sys_send.socktype; fprintf(fp, "dns_sys_send:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } case DNS_TE_SYS_RECV: { int socktype = te->sys_recv.socktype; fprintf(fp, "dns_sys_recv:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } default: fprintf(fp, "unknown(0x%.2x):\n", te->type); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } } goto epilog; syerr: error = errno; error: (void)0; epilog: free(te); return error; } /* * H O S T S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hosts { struct dns_hosts_entry { char host[DNS_D_MAXNAME + 1]; char arpa[73 + 1]; int af; union { struct in_addr a4; struct in6_addr a6; } addr; _Bool alias; struct dns_hosts_entry *next; } *head, **tail; dns_atomic_t refcount; }; /* struct dns_hosts */ struct dns_hosts *dns_hosts_open(int *error) { static const struct dns_hosts hosts_initializer = { .refcount = 1 }; struct dns_hosts *hosts; if (!(hosts = malloc(sizeof *hosts))) goto syerr; *hosts = hosts_initializer; hosts->tail = &hosts->head; return hosts; syerr: *error = dns_syerr(); free(hosts); return 0; } /* dns_hosts_open() */ void dns_hosts_close(struct dns_hosts *hosts) { struct dns_hosts_entry *ent, *xnt; if (!hosts || 1 != dns_hosts_release(hosts)) return; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; free(ent); } free(hosts); return; } /* dns_hosts_close() */ dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) { return dns_atomic_fetch_add(&hosts->refcount); } /* dns_hosts_acquire() */ dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) { return dns_atomic_fetch_sub(&hosts->refcount); } /* dns_hosts_release() */ struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) { if (hosts) dns_hosts_release(hosts); return hosts; } /* dns_hosts_mortal() */ struct dns_hosts *dns_hosts_local(int *error_) { struct dns_hosts *hosts; int error; if (!(hosts = dns_hosts_open(&error))) goto error; if ((error = dns_hosts_loadpath(hosts, "/etc/hosts"))) goto error; return hosts; error: *error_ = error; dns_hosts_close(hosts); return 0; } /* dns_hosts_local() */ #define dns_hosts_issep(ch) (dns_isspace(ch)) #define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry ent; char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1]; unsigned wp, wc, skip; int ch, error; rewind(fp); do { memset(&ent, '\0', sizeof ent); wc = 0; skip = 0; do { memset(word, '\0', sizeof word); wp = 0; while (EOF != (ch = fgetc(fp)) && ch != '\n') { skip |= !!dns_hosts_iscom(ch); if (skip) continue; if (dns_hosts_issep(ch)) break; if (wp < sizeof word - 1) word[wp] = ch; wp++; } if (!wp) continue; wc++; switch (wc) { case 0: break; case 1: ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET; skip = (1 != dns_inet_pton(ent.af, word, &ent.addr)); break; default: if (!wp) break; dns_d_anchor(ent.host, sizeof ent.host, word, wp); if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2)))) return error; break; } /* switch() */ } while (ch != EOF && ch != '\n'); } while (ch != EOF); return 0; } /* dns_hosts_loadfile() */ int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_hosts_loadfile(hosts, fp); fclose(fp); return error; } /* dns_hosts_loadpath() */ int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry *ent, *xnt; char addr[INET6_ADDRSTRLEN + 1]; unsigned i; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr); fputs(addr, fp); for (i = strlen(addr); i < INET_ADDRSTRLEN; i++) fputc(' ', fp); fputc(' ', fp); fputs(ent->host, fp); fputc('\n', fp); } return 0; } /* dns_hosts_dump() */ int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) { struct dns_hosts_entry *ent; int error; if (!(ent = malloc(sizeof *ent))) goto syerr; dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host)); switch ((ent->af = af)) { case AF_INET6: memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6); dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr); break; case AF_INET: memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4); dns_a_arpa(ent->arpa, sizeof ent->arpa, addr); break; default: error = EINVAL; goto error; } /* switch() */ ent->alias = alias; ent->next = 0; *hosts->tail = ent; hosts->tail = &ent->next; return 0; syerr: error = dns_syerr(); error: free(ent); return error; } /* dns_hosts_insert() */ struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { struct dns_packet *P = dns_p_new(512); struct dns_packet *A = 0; struct dns_rr rr; struct dns_hosts_entry *ent; int error, af; char qname[DNS_D_MAXNAME + 1]; size_t qlen; if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error))) goto error; else if (qlen >= sizeof qname) goto toolong; if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0))) goto error; switch (rr.type) { case DNS_T_PTR: for (ent = hosts->head; ent; ent = ent->next) { if (ent->alias || 0 != strcasecmp(qname, ent->arpa)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host))) goto error; } break; case DNS_T_AAAA: af = AF_INET6; goto loop; case DNS_T_A: af = AF_INET; loop: for (ent = hosts->head; ent; ent = ent->next) { if (ent->af != af || 0 != strcasecmp(qname, ent->host)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr))) goto error; } break; default: break; } /* switch() */ if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; dns_p_free(A); return 0; } /* dns_hosts_query() */ /* * R E S O L V . C O N F R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_resolv_conf *dns_resconf_open(int *error) { static const struct dns_resolv_conf resconf_initializer = { .lookup = "bf", .family = { AF_INET, AF_INET6 }, .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, }, .iface = { .ss_family = AF_INET }, }; struct dns_resolv_conf *resconf; struct sockaddr_in *sin; if (!(resconf = malloc(sizeof *resconf))) goto syerr; *resconf = resconf_initializer; sin = (struct sockaddr_in *)&resconf->nameserver[0]; sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(53); #if defined(SA_LEN) sin->sin_len = sizeof *sin; #endif if (0 != gethostname(resconf->search[0], sizeof resconf->search[0])) goto syerr; dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); /* * XXX: If gethostname() returned a string without any label * separator, then search[0][0] should be NUL. */ dns_resconf_acquire(resconf); return resconf; syerr: *error = dns_syerr(); free(resconf); return 0; } /* dns_resconf_open() */ void dns_resconf_close(struct dns_resolv_conf *resconf) { if (!resconf || 1 != dns_resconf_release(resconf)) return /* void */; free(resconf); } /* dns_resconf_close() */ dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_add(&resconf->_.refcount); } /* dns_resconf_acquire() */ dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_sub(&resconf->_.refcount); } /* dns_resconf_release() */ struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) { if (resconf) dns_resconf_release(resconf); return resconf; } /* dns_resconf_mortal() */ struct dns_resolv_conf *dns_resconf_local(int *error_) { struct dns_resolv_conf *resconf; int error; if (!(resconf = dns_resconf_open(&error))) goto error; if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) { /* * NOTE: Both the glibc and BIND9 resolvers ignore a missing * /etc/resolv.conf, defaulting to a nameserver of * 127.0.0.1. See also dns_hints_insert_resconf, and the * default initialization of nameserver[0] in * dns_resconf_open. */ if (error != ENOENT) goto error; } if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) { if (error != ENOENT) goto error; } return resconf; error: *error_ = error; dns_resconf_close(resconf); return 0; } /* dns_resconf_local() */ struct dns_resolv_conf *dns_resconf_root(int *error) { struct dns_resolv_conf *resconf; if ((resconf = dns_resconf_local(error))) resconf->options.recurse = 1; return resconf; } /* dns_resconf_root() */ static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) { return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout); } /* dns_resconf_timeout() */ enum dns_resconf_keyword { DNS_RESCONF_NAMESERVER, DNS_RESCONF_DOMAIN, DNS_RESCONF_SEARCH, DNS_RESCONF_LOOKUP, DNS_RESCONF_FILE, DNS_RESCONF_BIND, DNS_RESCONF_CACHE, DNS_RESCONF_FAMILY, DNS_RESCONF_INET4, DNS_RESCONF_INET6, DNS_RESCONF_OPTIONS, DNS_RESCONF_EDNS0, DNS_RESCONF_NDOTS, DNS_RESCONF_TIMEOUT, DNS_RESCONF_ATTEMPTS, DNS_RESCONF_ROTATE, DNS_RESCONF_RECURSE, DNS_RESCONF_SMART, DNS_RESCONF_TCP, DNS_RESCONF_TCPx, DNS_RESCONF_INTERFACE, DNS_RESCONF_ZERO, DNS_RESCONF_ONE, DNS_RESCONF_ENABLE, DNS_RESCONF_ONLY, DNS_RESCONF_DISABLE, }; /* enum dns_resconf_keyword */ static enum dns_resconf_keyword dns_resconf_keyword(const char *word) { static const char *words[] = { [DNS_RESCONF_NAMESERVER] = "nameserver", [DNS_RESCONF_DOMAIN] = "domain", [DNS_RESCONF_SEARCH] = "search", [DNS_RESCONF_LOOKUP] = "lookup", [DNS_RESCONF_FILE] = "file", [DNS_RESCONF_BIND] = "bind", [DNS_RESCONF_CACHE] = "cache", [DNS_RESCONF_FAMILY] = "family", [DNS_RESCONF_INET4] = "inet4", [DNS_RESCONF_INET6] = "inet6", [DNS_RESCONF_OPTIONS] = "options", [DNS_RESCONF_EDNS0] = "edns0", [DNS_RESCONF_ROTATE] = "rotate", [DNS_RESCONF_RECURSE] = "recurse", [DNS_RESCONF_SMART] = "smart", [DNS_RESCONF_TCP] = "tcp", [DNS_RESCONF_INTERFACE] = "interface", [DNS_RESCONF_ZERO] = "0", [DNS_RESCONF_ONE] = "1", [DNS_RESCONF_ENABLE] = "enable", [DNS_RESCONF_ONLY] = "only", [DNS_RESCONF_DISABLE] = "disable", }; unsigned i; for (i = 0; i < lengthof(words); i++) { if (words[i] && 0 == strcasecmp(words[i], word)) return i; } if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1)) return DNS_RESCONF_NDOTS; if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1)) return DNS_RESCONF_TIMEOUT; if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1)) return DNS_RESCONF_ATTEMPTS; if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1)) return DNS_RESCONF_TCPx; return -1; } /* dns_resconf_keyword() */ /** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */ int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) { struct { char buf[128], *p; } addr = { "", addr.buf }; unsigned short port = 0; int ch, af = AF_INET, error; while ((ch = *src++)) { switch (ch) { case ' ': /* FALL THROUGH */ case '\t': break; case '[': break; case ']': while ((ch = *src++)) { if (dns_isdigit(ch)) { port *= 10; port += ch - '0'; } } goto inet; case ':': af = AF_INET6; /* FALL THROUGH */ default: if (addr.p < endof(addr.buf) - 1) *addr.p++ = ch; break; } /* switch() */ } /* while() */ inet: if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL)))) return error; port = (!port)? 53 : port; *dns_sa_port(af, ss) = htons(port); dns_sa_family(ss) = af; return 0; } /* dns_resconf_pton() */ #define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',') #define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { unsigned sa_count = 0; char words[6][DNS_D_MAXNAME + 1]; unsigned wp, wc, i, j, n; int ch, error; rewind(fp); do { memset(words, '\0', sizeof words); wp = 0; wc = 0; while (EOF != (ch = getc(fp)) && ch != '\n') { if (dns_resconf_issep(ch)) { if (wp > 0) { wp = 0; if (++wc >= lengthof(words)) goto skip; } } else if (dns_resconf_iscom(ch)) { skip: do { ch = getc(fp); } while (ch != EOF && ch != '\n'); break; } else if (wp < sizeof words[wc] - 1) { words[wc][wp++] = ch; } else { wp = 0; /* drop word */ goto skip; } } if (wp > 0) wc++; if (wc < 2) continue; switch (dns_resconf_keyword(words[0])) { case DNS_RESCONF_NAMESERVER: if (sa_count >= lengthof(resconf->nameserver)) continue; if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1]))) continue; sa_count++; break; case DNS_RESCONF_DOMAIN: case DNS_RESCONF_SEARCH: memset(resconf->search, '\0', sizeof resconf->search); for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++) dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i])); break; case DNS_RESCONF_LOOKUP: for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_FILE: resconf->lookup[j++] = 'f'; break; case DNS_RESCONF_BIND: resconf->lookup[j++] = 'b'; break; case DNS_RESCONF_CACHE: resconf->lookup[j++] = 'c'; break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_FAMILY: for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_INET4: resconf->family[j++] = AF_INET; break; case DNS_RESCONF_INET6: resconf->family[j++] = AF_INET6; break; default: break; } } break; case DNS_RESCONF_OPTIONS: for (i = 1; i < wc; i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_EDNS0: resconf->options.edns0 = 1; break; case DNS_RESCONF_NDOTS: for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.ndots = n; break; case DNS_RESCONF_TIMEOUT: for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.timeout = n; break; case DNS_RESCONF_ATTEMPTS: for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.attempts = n; break; case DNS_RESCONF_ROTATE: resconf->options.rotate = 1; break; case DNS_RESCONF_RECURSE: resconf->options.recurse = 1; break; case DNS_RESCONF_SMART: resconf->options.smart = 1; break; case DNS_RESCONF_TCP: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_TCPx: switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) { case DNS_RESCONF_ENABLE: resconf->options.tcp = DNS_RESCONF_TCP_ENABLE; break; case DNS_RESCONF_ONE: case DNS_RESCONF_ONLY: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_ZERO: case DNS_RESCONF_DISABLE: resconf->options.tcp = DNS_RESCONF_TCP_DISABLE; break; default: break; } /* switch() */ break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_INTERFACE: for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) { n *= 10; n += words[2][i] - '0'; } dns_resconf_setiface(resconf, words[1], n); break; default: break; } /* switch() */ } while (ch != EOF); return 0; } /* dns_resconf_loadfile() */ int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_resconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_resconf_loadpath() */ struct dns_anyconf { char *token[16]; unsigned count; char buffer[1024], *tp, *cp; }; /* struct dns_anyconf */ static void dns_anyconf_reset(struct dns_anyconf *cf) { cf->count = 0; cf->tp = cf->cp = cf->buffer; } /* dns_anyconf_reset() */ static int dns_anyconf_push(struct dns_anyconf *cf) { if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token))) return ENOMEM; *cf->cp++ = '\0'; cf->token[cf->count++] = cf->tp; cf->tp = cf->cp; return 0; } /* dns_anyconf_push() */ static void dns_anyconf_pop(struct dns_anyconf *cf) { if (cf->count > 0) { --cf->count; cf->tp = cf->cp = cf->token[cf->count]; cf->token[cf->count] = 0; } } /* dns_anyconf_pop() */ static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) { if (!(cf->cp < endof(cf->buffer))) return ENOMEM; *cf->cp++ = ch; return 0; } /* dns_anyconf_addc() */ static _Bool dns_anyconf_match(const char *pat, int mc) { _Bool match; int pc; if (*pat == '^') { match = 0; ++pat; } else { match = 1; } while ((pc = *(const unsigned char *)pat++)) { switch (pc) { case '%': if (!(pc = *(const unsigned char *)pat++)) return !match; switch (pc) { case 'a': if (dns_isalpha(mc)) return match; break; case 'd': if (dns_isdigit(mc)) return match; break; case 'w': if (dns_isalnum(mc)) return match; break; case 's': if (dns_isspace(mc)) return match; break; default: if (mc == pc) return match; break; } /* switch() */ break; default: if (mc == pc) return match; break; } /* switch() */ } /* while() */ return !match; } /* dns_anyconf_match() */ static int dns_anyconf_peek(FILE *fp) { int ch; ch = getc(fp); ungetc(ch, fp); return ch; } /* dns_anyconf_peek() */ static size_t dns_anyconf_skip(const char *pat, FILE *fp) { size_t count = 0; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { count++; continue; } ungetc(ch, fp); break; } return count; } /* dns_anyconf_skip() */ static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) { size_t len; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { if ((*error = dns_anyconf_addc(cf, ch))) return 0; continue; } else { ungetc(ch, fp); break; } } if ((len = cf->cp - cf->tp)) { if ((*error = dns_anyconf_push(cf))) return 0; return len; } else { *error = 0; return 0; } } /* dns_anyconf_scan() */ DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) { unsigned i; fprintf(fp, "tokens:"); for (i = 0; i < cf->count; i++) { fprintf(fp, " %s", cf->token[i]); } fputc('\n', fp); } /* dns_anyconf_dump() */ enum dns_nssconf_keyword { DNS_NSSCONF_INVALID = 0, DNS_NSSCONF_HOSTS = 1, DNS_NSSCONF_SUCCESS, DNS_NSSCONF_NOTFOUND, DNS_NSSCONF_UNAVAIL, DNS_NSSCONF_TRYAGAIN, DNS_NSSCONF_CONTINUE, DNS_NSSCONF_RETURN, DNS_NSSCONF_FILES, DNS_NSSCONF_DNS, DNS_NSSCONF_MDNS, DNS_NSSCONF_LAST, }; /* enum dns_nssconf_keyword */ static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) { static const char *list[] = { [DNS_NSSCONF_HOSTS] = "hosts", [DNS_NSSCONF_SUCCESS] = "success", [DNS_NSSCONF_NOTFOUND] = "notfound", [DNS_NSSCONF_UNAVAIL] = "unavail", [DNS_NSSCONF_TRYAGAIN] = "tryagain", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; unsigned i; for (i = 1; i < lengthof(list); i++) { if (list[i] && 0 == strcasecmp(list[i], word)) return i; } return DNS_NSSCONF_INVALID; } /* dns_nssconf_keyword() */ static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) { static const char map[] = { ['S'] = DNS_NSSCONF_SUCCESS, ['N'] = DNS_NSSCONF_NOTFOUND, ['U'] = DNS_NSSCONF_UNAVAIL, ['T'] = DNS_NSSCONF_TRYAGAIN, ['C'] = DNS_NSSCONF_CONTINUE, ['R'] = DNS_NSSCONF_RETURN, ['f'] = DNS_NSSCONF_FILES, ['F'] = DNS_NSSCONF_FILES, ['d'] = DNS_NSSCONF_DNS, ['D'] = DNS_NSSCONF_DNS, ['b'] = DNS_NSSCONF_DNS, ['B'] = DNS_NSSCONF_DNS, ['m'] = DNS_NSSCONF_MDNS, ['M'] = DNS_NSSCONF_MDNS, }; return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID; } /* dns_nssconf_c2k() */ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET static int dns_nssconf_k2c(int k) { static const char map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = 'S', [DNS_NSSCONF_NOTFOUND] = 'N', [DNS_NSSCONF_UNAVAIL] = 'U', [DNS_NSSCONF_TRYAGAIN] = 'T', [DNS_NSSCONF_CONTINUE] = 'C', [DNS_NSSCONF_RETURN] = 'R', [DNS_NSSCONF_FILES] = 'f', [DNS_NSSCONF_DNS] = 'b', [DNS_NSSCONF_MDNS] = 'm', }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?'; } /* dns_nssconf_k2c() */ static const char *dns_nssconf_k2s(int k) { static const char *const map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = "SUCCESS", [DNS_NSSCONF_NOTFOUND] = "NOTFOUND", [DNS_NSSCONF_UNAVAIL] = "UNAVAIL", [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : ""; } /* dns_nssconf_k2s() */ DNS_PRAGMA_POP int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { enum dns_nssconf_keyword source, status, action; char lookup[sizeof resconf->lookup] = "", *lp; struct dns_anyconf cf; size_t i; int error; while (!feof(fp) && !ferror(fp)) { dns_anyconf_reset(&cf); dns_anyconf_skip("%s", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) goto nextent; if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0])) goto nextent; dns_anyconf_pop(&cf); if (!dns_anyconf_skip(": \t", fp)) goto nextent; *(lp = lookup) = '\0'; while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_skip(" \t", fp); if ('[' == dns_anyconf_peek(fp)) { dns_anyconf_skip("[ \t", fp); for (;;) { if ('!' == dns_anyconf_peek(fp)) { dns_anyconf_skip("! \t", fp); /* FIXME: negating statuses; currently not implemented */ dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ break; } if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) break; dns_anyconf_skip("= \t", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_pop(&cf); /* discard status */ dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ break; } dns_anyconf_skip(" \t", fp); } dns_anyconf_skip("] \t", fp); } if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */ goto nextsrc; source = dns_nssconf_keyword(cf.token[0]); switch (source) { case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: case DNS_NSSCONF_FILES: *lp++ = dns_nssconf_k2c(source); break; default: goto nextsrc; } for (i = 1; i + 1 < cf.count; i += 2) { status = dns_nssconf_keyword(cf.token[i]); action = dns_nssconf_keyword(cf.token[i + 1]); switch (status) { case DNS_NSSCONF_SUCCESS: case DNS_NSSCONF_NOTFOUND: case DNS_NSSCONF_UNAVAIL: case DNS_NSSCONF_TRYAGAIN: *lp++ = dns_nssconf_k2c(status); break; default: continue; } switch (action) { case DNS_NSSCONF_CONTINUE: case DNS_NSSCONF_RETURN: break; default: action = (status == DNS_NSSCONF_SUCCESS) ? DNS_NSSCONF_RETURN : DNS_NSSCONF_CONTINUE; break; } *lp++ = dns_nssconf_k2c(action); } nextsrc: *lp = '\0'; dns_anyconf_reset(&cf); } nextent: dns_anyconf_skip("^\n", fp); } if (*lookup) strncpy(resconf->lookup, lookup, sizeof resconf->lookup); return 0; } /* dns_nssconf_loadfile() */ int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_nssconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_nssconf_loadpath() */ struct dns_nssconf_source { enum dns_nssconf_keyword source, success, notfound, unavail, tryagain; }; /* struct dns_nssconf_source */ typedef unsigned dns_nssconf_i; static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) { return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0; } /* dns_nssconf_peek() */ static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) { int source, status, action; src->source = DNS_NSSCONF_INVALID; src->success = DNS_NSSCONF_RETURN; src->notfound = DNS_NSSCONF_CONTINUE; src->unavail = DNS_NSSCONF_CONTINUE; src->tryagain = DNS_NSSCONF_CONTINUE; while ((source = dns_nssconf_peek(resconf, *state))) { source = dns_nssconf_c2k(source); ++*state; switch (source) { case DNS_NSSCONF_FILES: case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: src->source = source; break; default: continue; } while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) { status = dns_nssconf_c2k(status); action = dns_nssconf_c2k(action); switch (action) { case DNS_NSSCONF_RETURN: case DNS_NSSCONF_CONTINUE: break; default: goto done; } switch (status) { case DNS_NSSCONF_SUCCESS: src->success = action; break; case DNS_NSSCONF_NOTFOUND: src->notfound = action; break; case DNS_NSSCONF_UNAVAIL: src->unavail = action; break; case DNS_NSSCONF_TRYAGAIN: src->tryagain = action; break; default: goto done; } *state += 2; } break; } done: return src->source != DNS_NSSCONF_INVALID; } /* dns_nssconf_next() */ static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) { switch (status) { case DNS_NSSCONF_SUCCESS: if (action == DNS_NSSCONF_RETURN) return 0; break; default: if (action == DNS_NSSCONF_CONTINUE) return 0; break; } fputc(' ', fp); if (!*count) fputc('[', fp); fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action)); ++*count; return 0; } /* dns_nssconf_dump_status() */ int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { struct dns_nssconf_source src; dns_nssconf_i i = 0; fputs("hosts:", fp); while (dns_nssconf_next(&src, resconf, &i)) { unsigned n = 0; fprintf(fp, " %s", dns_nssconf_k2s(src.source)); dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp); if (n) fputc(']', fp); } fputc('\n', fp); return 0; } /* dns_nssconf_dump() */ int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) { int af = (strchr(addr, ':'))? AF_INET6 : AF_INET; int error; if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL)))) return error; *dns_sa_port(af, &resconf->iface) = htons(port); resconf->iface.ss_family = af; return 0; } /* dns_resconf_setiface() */ #define DNS_SM_RESTORE \ do { \ pc = 0xff & (*state >> 0); \ srchi = 0xff & (*state >> 8); \ ndots = 0xff & (*state >> 16); \ } while (0) #define DNS_SM_SAVE \ do { \ *state = ((0xff & pc) << 0) \ | ((0xff & srchi) << 8) \ | ((0xff & ndots) << 16); \ } while (0) size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) { unsigned pc, srchi, ndots, len; DNS_SM_ENTER; /* if FQDN then return as-is and finish */ if (dns_d_isanchored(qname, qlen)) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); DNS_SM_EXIT; } ndots = dns_d_ndots(qname, qlen); if (ndots >= resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) { struct dns_buf buf = DNS_B_INTO(dst, lim); const char *dn = resconf->search[srchi++]; dns_b_put(&buf, qname, qlen); dns_b_putc(&buf, '.'); dns_b_puts(&buf, dn); if (!dns_d_isanchored(dn, strlen(dn))) dns_b_putc(&buf, '.'); len = dns_b_strllen(&buf); DNS_SM_YIELD(len); } if (ndots < resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } DNS_SM_LEAVE; return dns_strlcpy(dst, "", lim); } /* dns_resconf_search() */ #undef DNS_SM_SAVE #undef DNS_SM_RESTORE int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { unsigned i; int af; for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; unsigned short port; dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr); port = ntohs(*dns_sa_port(af, &resconf->nameserver[i])); if (port == 53) fprintf(fp, "nameserver %s\n", addr); else fprintf(fp, "nameserver [%s]:%hu\n", addr, port); } fprintf(fp, "search"); for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++) fprintf(fp, " %s", resconf->search[i]); fputc('\n', fp); fputs("; ", fp); dns_nssconf_dump(resconf, fp); fprintf(fp, "lookup"); for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) { switch (resconf->lookup[i]) { case 'b': fprintf(fp, " bind"); break; case 'f': fprintf(fp, " file"); break; case 'c': fprintf(fp, " cache"); break; } } fputc('\n', fp); fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts); if (resconf->options.edns0) fprintf(fp, " edns0"); if (resconf->options.rotate) fprintf(fp, " rotate"); if (resconf->options.recurse) fprintf(fp, " recurse"); if (resconf->options.smart) fprintf(fp, " smart"); switch (resconf->options.tcp) { case DNS_RESCONF_TCP_ENABLE: break; case DNS_RESCONF_TCP_ONLY: fprintf(fp, " tcp"); break; case DNS_RESCONF_TCP_SOCKS: fprintf(fp, " tcp:socks"); break; case DNS_RESCONF_TCP_DISABLE: fprintf(fp, " tcp:disable"); break; } fputc('\n', fp); if ((af = resconf->iface.ss_family) != AF_UNSPEC) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr); fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface))); } return 0; } /* dns_resconf_dump() */ /* * H I N T S E R V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hints_soa { unsigned char zone[DNS_D_MAXNAME + 1]; struct { struct sockaddr_storage ss; unsigned priority; } addrs[16]; unsigned count; struct dns_hints_soa *next; }; /* struct dns_hints_soa */ struct dns_hints { dns_atomic_t refcount; struct dns_hints_soa *head; }; /* struct dns_hints */ struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) { static const struct dns_hints H_initializer; struct dns_hints *H; (void)resconf; if (!(H = malloc(sizeof *H))) goto syerr; *H = H_initializer; dns_hints_acquire(H); return H; syerr: *error = dns_syerr(); free(H); return 0; } /* dns_hints_open() */ void dns_hints_close(struct dns_hints *H) { struct dns_hints_soa *soa, *nxt; if (!H || 1 != dns_hints_release(H)) return /* void */; for (soa = H->head; soa; soa = nxt) { nxt = soa->next; free(soa); } free(H); return /* void */; } /* dns_hints_close() */ dns_refcount_t dns_hints_acquire(struct dns_hints *H) { return dns_atomic_fetch_add(&H->refcount); } /* dns_hints_acquire() */ dns_refcount_t dns_hints_release(struct dns_hints *H) { return dns_atomic_fetch_sub(&H->refcount); } /* dns_hints_release() */ struct dns_hints *dns_hints_mortal(struct dns_hints *hints) { if (hints) dns_hints_release(hints); return hints; } /* dns_hints_mortal() */ struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) { struct dns_hints *hints = 0; int error; if (resconf) dns_resconf_acquire(resconf); else if (!(resconf = dns_resconf_local(&error))) goto error; if (!(hints = dns_hints_open(resconf, &error))) goto error; error = 0; if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error) goto error; dns_resconf_close(resconf); return hints; error: *error_ = error; dns_resconf_close(resconf); dns_hints_close(hints); return 0; } /* dns_hints_local() */ struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) { static const struct { int af; char addr[INET6_ADDRSTRLEN]; } root_hints[] = { { AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */ { AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */ { AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */ { AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */ { AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */ { AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */ { AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */ { AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */ { AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */ { AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */ { AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */ { AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */ }; struct dns_hints *hints = 0; struct sockaddr_storage ss; unsigned i; int error, af; if (!(hints = dns_hints_open(resconf, &error))) goto error; for (i = 0; i < lengthof(root_hints); i++) { af = root_hints[i].af; if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL)))) goto error; *dns_sa_port(af, &ss) = htons(53); ss.ss_family = af; if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1))) goto error; } return hints; error: *error_ = error; dns_hints_close(hints); return 0; } /* dns_hints_root() */ static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) { struct dns_hints_soa *soa; for (soa = H->head; soa; soa = soa->next) { if (0 == strcasecmp(zone, (char *)soa->zone)) return soa; } return 0; } /* dns_hints_fetch() */ int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) { static const struct dns_hints_soa soa_initializer; struct dns_hints_soa *soa; unsigned i; if (!(soa = dns_hints_fetch(H, zone))) { if (!(soa = malloc(sizeof *soa))) return dns_syerr(); *soa = soa_initializer; dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone); soa->next = H->head; H->head = soa; } i = soa->count % lengthof(soa->addrs); memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa)); soa->addrs[i].priority = DNS_PP_MAX(1, priority); if (soa->count < lengthof(soa->addrs)) soa->count++; return 0; } /* dns_hints_insert() */ static _Bool dns_hints_isinaddr_any(const void *sa) { struct in_addr *addr; if (dns_sa_family(sa) != AF_INET) return 0; addr = dns_sa_addr(AF_INET, sa, NULL); return addr->s_addr == htonl(INADDR_ANY); } unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) { unsigned i, n, p; int error; for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) { union { struct sockaddr_in sin; } tmp; struct sockaddr *ns; /* * dns_resconf_open initializes nameserver[0] to INADDR_ANY. * * Traditionally the semantics of 0.0.0.0 meant the default * interface, which evolved to mean the loopback interface. * See comment block preceding resolv/res_init.c:res_init in * glibc 2.23. As of 2.23, glibc no longer translates * 0.0.0.0 despite the code comment, but it does default to * 127.0.0.1 when no nameservers are present. * * BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1. * See lib/lwres/lwconfig.c:lwres_create_addr and the * convert_zero flag. 127.0.0.1 is also the default when no * nameservers are present. */ if (dns_hints_isinaddr_any(&resconf->nameserver[i])) { memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin); tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); ns = (struct sockaddr *)&tmp.sin; } else { ns = (struct sockaddr *)&resconf->nameserver[i]; } if ((error = dns_hints_insert(H, zone, ns, p))) goto error; p += !resconf->options.rotate; } return n; error: *error_ = error; return n; } /* dns_hints_insert_resconf() */ static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) { int cmp; if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority)) return cmp; return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed); } /* dns_hints_i_cmp() */ static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned p0, p; p0 = 0; for (p = 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) < 0) p0 = p; } return p0; } /* dns_hints_i_start() */ static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned pZ, p; for (pZ = 0; pZ < soa->count; pZ++) { if (dns_hints_i_cmp(pZ, p0, i, soa) > 0) goto cont; } return soa->count; cont: for (p = pZ + 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) <= 0) continue; if (dns_hints_i_cmp(p, pZ, i, soa) >= 0) continue; pZ = p; } return pZ; } /* dns_hints_i_skip() */ static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) { static const struct dns_hints_i i_initializer; struct dns_hints_soa *soa; i->state = i_initializer.state; do { i->state.seed = dns_random(); } while (0 == i->state.seed); if ((soa = dns_hints_fetch(hints, i->zone))) { i->state.next = dns_hints_i_start(i, soa); } return i; } /* dns_hints_i_init() */ unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) { struct dns_hints_soa *soa; unsigned n; if (!(soa = dns_hints_fetch(H, i->zone))) return 0; n = 0; while (i->state.next < soa->count && n < lim) { *sa = (struct sockaddr *)&soa->addrs[i->state.next].ss; *sa_len = dns_sa_len(*sa); sa++; sa_len++; n++; i->state.next = dns_hints_i_skip(i->state.next, i, soa); } return n; } /* dns_hints_grep() */ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { struct dns_packet *A, *P; struct dns_rr rr; char zone[DNS_D_MAXNAME + 1]; size_t zlen; struct dns_hints_i i; struct sockaddr *sa; socklen_t slen; int error; if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error)) goto error; if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) goto error; else if (zlen >= sizeof zone) goto toolong; P = dns_p_new(512); dns_header(P)->qr = 1; if ((error = dns_rr_copy(P, &rr, Q))) goto error; if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local."))) goto error; do { i.zone = zone; dns_hints_i_init(&i, hints); while (dns_hints_grep(&sa, &slen, 1, &i, hints)) { int af = sa->sa_family; int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A; if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL)))) goto error; } } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen))); if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; return 0; } /* dns_hints_query() */ /** ugly hack to support specifying ports other than 53 in resolv.conf. */ static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) { struct dns_hints_soa *soa; void *addrsoa; socklen_t addrlen; unsigned short port; unsigned i; for (soa = hints->head; soa; soa = soa->next) { for (i = 0; i < soa->count; i++) { if (af != soa->addrs[i].ss.ss_family) continue; if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen))) continue; if (memcmp(addr, addrsoa, addrlen)) continue; port = *dns_sa_port(af, &soa->addrs[i].ss); return (port)? port : htons(53); } } return htons(53); } /* dns_hints_port() */ int dns_hints_dump(struct dns_hints *hints, FILE *fp) { struct dns_hints_soa *soa; char addr[INET6_ADDRSTRLEN]; unsigned i; int af, error; for (soa = hints->head; soa; soa = soa->next) { fprintf(fp, "ZONE \"%s\"\n", soa->zone); for (i = 0; i < soa->count; i++) { af = soa->addrs[i].ss.ss_family; if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr))) return error; fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss))); } } return 0; } /* dns_hints_dump() */ /* * C A C H E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) { return dns_atomic_fetch_add(&cache->_.refcount); } /* dns_cache_acquire() */ static dns_refcount_t dns_cache_release(struct dns_cache *cache) { return dns_atomic_fetch_sub(&cache->_.refcount); } /* dns_cache_release() */ static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) { (void)query; (void)cache; (void)error; return NULL; } /* dns_cache_query() */ static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) { (void)query; (void)cache; return 0; } /* dns_cache_submit() */ static int dns_cache_check(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_check() */ static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) { (void)cache; (void)error; return NULL; } /* dns_cache_fetch() */ static int dns_cache_pollfd(struct dns_cache *cache) { (void)cache; return -1; } /* dns_cache_pollfd() */ static short dns_cache_events(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_events() */ static void dns_cache_clear(struct dns_cache *cache) { (void)cache; return; } /* dns_cache_clear() */ struct dns_cache *dns_cache_init(struct dns_cache *cache) { static const struct dns_cache c_init = { .acquire = &dns_cache_acquire, .release = &dns_cache_release, .query = &dns_cache_query, .submit = &dns_cache_submit, .check = &dns_cache_check, .fetch = &dns_cache_fetch, .pollfd = &dns_cache_pollfd, .events = &dns_cache_events, .clear = &dns_cache_clear, ._ = { .refcount = 1, }, }; *cache = c_init; return cache; } /* dns_cache_init() */ void dns_cache_close(struct dns_cache *cache) { if (cache) cache->release(cache); } /* dns_cache_close() */ /* * S O C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static void dns_socketclose(int *fd, const struct dns_options *opts) { if (opts && opts->closefd.cb) opts->closefd.cb(fd, opts->closefd.arg); if (*fd != -1) { #if _WIN32 closesocket(*fd); #else close(*fd); #endif *fd = -1; } } /* dns_socketclose() */ #ifndef HAVE_IOCTLSOCKET #define HAVE_IOCTLSOCKET (_WIN32 || _WIN64) #endif #ifndef HAVE_SOCK_CLOEXEC #define HAVE_SOCK_CLOEXEC (defined SOCK_CLOEXEC) #endif #ifndef HAVE_SOCK_NONBLOCK #define HAVE_SOCK_NONBLOCK (defined SOCK_NONBLOCK) #endif #define DNS_SO_MAXTRY 7 static int dns_socket(struct sockaddr *local, int type, int *error_) { int fd = -1, flags, error; #if defined FIONBIO unsigned long opt; #endif flags = 0; #if HAVE_SOCK_CLOEXEC flags |= SOCK_CLOEXEC; #endif #if HAVE_SOCK_NONBLOCK flags |= SOCK_NONBLOCK; #endif if (-1 == (fd = socket(local->sa_family, type|flags, 0))) goto soerr; #if defined F_SETFD && !HAVE_SOCK_CLOEXEC if (-1 == fcntl(fd, F_SETFD, 1)) goto syerr; #endif #if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK if (-1 == (flags = fcntl(fd, F_GETFL))) goto syerr; if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) goto syerr; #elif defined FIONBIO && HAVE_IOCTLSOCKET opt = 1; if (0 != ioctlsocket(fd, FIONBIO, &opt)) goto soerr; #endif #if defined SO_NOSIGPIPE if (type != SOCK_DGRAM) { if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int))) goto soerr; } #endif if (local->sa_family != AF_INET && local->sa_family != AF_INET6) return fd; if (type != SOCK_DGRAM) return fd; /* * FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by * default. Though the ephemeral range is quite small on OS X * (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu * Xenial). See also RFC 6056. * * TODO: Optionally rely on the kernel to select a random port. */ if (*dns_sa_port(local->sa_family, local) == 0) { struct sockaddr_storage tmp; unsigned i, port; memcpy(&tmp, local, dns_sa_len(local)); for (i = 0; i < DNS_SO_MAXTRY; i++) { port = 1025 + (dns_random() % 64510); *dns_sa_port(tmp.ss_family, &tmp) = htons(port); if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp))) return fd; } /* NB: continue to next bind statement */ } if (0 == bind(fd, local, dns_sa_len(local))) return fd; /* FALL THROUGH */ soerr: error = dns_soerr(); goto error; #if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK) syerr: error = dns_syerr(); goto error; #endif error: *error_ = error; dns_socketclose(&fd, NULL); return -1; } /* dns_socket() */ enum { DNS_SO_UDP_INIT = 1, DNS_SO_UDP_CONN, DNS_SO_UDP_SEND, DNS_SO_UDP_RECV, DNS_SO_UDP_DONE, DNS_SO_TCP_INIT, DNS_SO_TCP_CONN, DNS_SO_TCP_SEND, DNS_SO_TCP_RECV, DNS_SO_TCP_DONE, DNS_SO_SOCKS_INIT, DNS_SO_SOCKS_CONN, DNS_SO_SOCKS_HELLO_SEND, DNS_SO_SOCKS_HELLO_RECV, DNS_SO_SOCKS_AUTH_SEND, DNS_SO_SOCKS_AUTH_RECV, DNS_SO_SOCKS_REQUEST_PREPARE, DNS_SO_SOCKS_REQUEST_SEND, DNS_SO_SOCKS_REQUEST_RECV, DNS_SO_SOCKS_REQUEST_RECV_V6, DNS_SO_SOCKS_HANDSHAKE_DONE, }; struct dns_socket { struct dns_options opts; int udp; int tcp; int *old; unsigned onum, olim; int type; struct sockaddr_storage local, remote; struct dns_k_permutor qids; struct dns_stat stat; struct dns_trace *trace; /* * NOTE: dns_so_reset() zeroes everything from here down. */ int state; unsigned short qid; char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_packet *query; size_t qout; /* During a SOCKS handshake the query is temporarily stored * here. */ struct dns_packet *query_backup; struct dns_clock elapsed; struct dns_packet *answer; size_t alen, apos; }; /* struct dns_socket */ /* * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have * a chance to recognize a state change after installing a persistent event * and where sequential descriptors with the same integer value returned * from _pollfd() would be ambiguous. See dns_so_closefds(). */ static int dns_so_closefd(struct dns_socket *so, int *fd) { int error; if (*fd == -1) return 0; if (so->opts.closefd.cb) { if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) { return error; } else if (*fd == -1) return 0; } if (!(so->onum < so->olim)) { unsigned olim = DNS_PP_MAX(4, so->olim * 2); void *old; if (!(old = realloc(so->old, sizeof so->old[0] * olim))) return dns_syerr(); so->old = old; so->olim = olim; } so->old[so->onum++] = *fd; *fd = -1; return 0; } /* dns_so_closefd() */ #define DNS_SO_CLOSE_UDP 0x01 #define DNS_SO_CLOSE_TCP 0x02 #define DNS_SO_CLOSE_OLD 0x04 #define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD) static void dns_so_closefds(struct dns_socket *so, int which) { if (DNS_SO_CLOSE_UDP & which) dns_socketclose(&so->udp, &so->opts); if (DNS_SO_CLOSE_TCP & which) dns_socketclose(&so->tcp, &so->opts); if (DNS_SO_CLOSE_OLD & which) { unsigned i; for (i = 0; i < so->onum; i++) dns_socketclose(&so->old[i], &so->opts); so->onum = 0; free(so->old); so->old = 0; so->olim = 0; } } /* dns_so_closefds() */ static void dns_so_destroy(struct dns_socket *); static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, }; *so = so_initializer; so->type = type; if (opts) so->opts = *opts; if (local) memcpy(&so->local, local, dns_sa_len(local)); if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error))) goto error; dns_k_permutor_init(&so->qids, 1, 65535); return so; error: dns_so_destroy(so); return 0; } /* dns_so_init() */ struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { struct dns_socket *so; if (!(so = malloc(sizeof *so))) goto syerr; if (!dns_so_init(so, local, type, opts, error)) goto error; return so; syerr: *error = dns_syerr(); error: dns_so_close(so); return 0; } /* dns_so_open() */ static void dns_so_destroy(struct dns_socket *so) { dns_so_reset(so); dns_so_closefds(so, DNS_SO_CLOSE_ALL); dns_trace_close(so->trace); } /* dns_so_destroy() */ void dns_so_close(struct dns_socket *so) { if (!so) return; dns_so_destroy(so); free(so); } /* dns_so_close() */ void dns_so_reset(struct dns_socket *so) { dns_p_setptr(&so->answer, NULL); memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state)); } /* dns_so_reset() */ unsigned short dns_so_mkqid(struct dns_socket *so) { return dns_k_permutor_step(&so->qids); } /* dns_so_mkqid() */ #define DNS_SO_MINBUF 768 static int dns_so_newanswer(struct dns_socket *so, size_t len) { size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF); void *p; if (!(p = realloc(so->answer, size))) return dns_syerr(); so->answer = dns_p_init(p, size); return 0; } /* dns_so_newanswer() */ int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) { struct dns_rr rr; int error = DNS_EUNKNOWN; dns_so_reset(so); if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error))) goto error; /* * NOTE: Don't bail if expansion is too long; caller may be * intentionally sending long names. However, we won't be able to * verify it on return. */ so->qtype = rr.type; so->qclass = rr.class; if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF))) goto syerr; memcpy(&so->remote, host, dns_sa_len(host)); so->query = Q; so->qout = 0; dns_begin(&so->elapsed); if (dns_header(so->query)->qid == 0) dns_header(so->query)->qid = dns_so_mkqid(so); so->qid = dns_header(so->query)->qid; so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT : (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT; so->stat.queries++; dns_trace_so_submit(so->trace, Q, host, 0); return 0; syerr: error = dns_syerr(); error: dns_so_reset(so); dns_trace_so_submit(so->trace, Q, host, error); return error; } /* dns_so_submit() */ static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error = -1; if (P->end < 12) goto reject; if (so->qid != dns_header(P)->qid) goto reject; if (!dns_p_count(P, DNS_S_QD)) goto reject; if (0 != dns_rr_parse(&rr, 12, P)) goto reject; if (rr.type != so->qtype || rr.class != so->qclass) goto reject; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error))) goto error; else if (qlen >= sizeof qname || qlen != so->qlen) goto reject; if (0 != strcasecmp(so->qname, qname)) goto reject; dns_trace_so_verify(so->trace, P, 0); return 0; reject: error = DNS_EVERIFY; error: DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error)); dns_trace_so_verify(so->trace, P, error); return error; } /* dns_so_verify() */ static _Bool dns_so_tcp_keep(struct dns_socket *so) { struct sockaddr_storage remote; if (so->tcp == -1) return 0; if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote })) return 0; return 0 == dns_sa_cmp(&remote, &so->remote); } /* dns_so_tcp_keep() */ /* Convenience functions for sending non-DNS data. */ /* Set up everything for sending LENGTH octets. Returns the buffer for the data. */ static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->qout = 2; so->query->end = length; return so->query->data; } /* Set up everything for receiving LENGTH octets. */ static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->apos = 2; so->alen = length; } /* Returns the buffer containing the received data. */ static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { return so->answer->data; } #if defined __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warray-bounds" #endif static int dns_so_tcp_send(struct dns_socket *so) { unsigned char *qsrc; size_t qend; int error; size_t n; so->query->data[-2] = 0xff & (so->query->end >> 8); so->query->data[-1] = 0xff & (so->query->end >> 0); qend = so->query->end + 2; while (so->qout < qend) { qsrc = &so->query->data[-2] + so->qout; n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error); dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error); if (error) return error; so->qout += n; so->stat.tcp.sent.bytes += n; } so->stat.tcp.sent.count++; return 0; } /* dns_so_tcp_send() */ static int dns_so_tcp_recv(struct dns_socket *so) { unsigned char *asrc; size_t aend, alen, n; int error; aend = so->alen + 2; while (so->apos < aend) { asrc = &so->answer->data[-2]; n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error); dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error); if (error) return error; so->apos += n; so->stat.tcp.rcvd.bytes += n; if (so->alen == 0 && so->apos >= 2) { alen = ((0xff & so->answer->data[-2]) << 8) | ((0xff & so->answer->data[-1]) << 0); if ((error = dns_so_newanswer(so, alen))) return error; so->alen = alen; aend = alen + 2; } } so->answer->end = so->alen; so->stat.tcp.rcvd.count++; return 0; } /* dns_so_tcp_recv() */ #if __clang__ #pragma clang diagnostic pop #endif int dns_so_check(struct dns_socket *so) { int error; size_t n; unsigned char *buffer; retry: switch (so->state) { case DNS_SO_UDP_INIT: so->state++; case DNS_SO_UDP_CONN: error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error); if (error) goto error; so->state++; case DNS_SO_UDP_SEND: n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error); dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error); if (error) goto error; so->stat.udp.sent.bytes += n; so->stat.udp.sent.count++; so->state++; case DNS_SO_UDP_RECV: n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error); dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error); if (error) goto error; so->answer->end = n; so->stat.udp.rcvd.bytes += n; so->stat.udp.rcvd.count++; if ((error = dns_so_verify(so, so->answer))) goto trash; so->state++; case DNS_SO_UDP_DONE: if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM) return 0; so->state++; case DNS_SO_TCP_INIT: if (dns_so_tcp_keep(so)) { so->state = DNS_SO_TCP_SEND; goto retry; } if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; case DNS_SO_TCP_CONN: error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error); if (error && error != DNS_EISCONN) goto error; so->state++; case DNS_SO_TCP_SEND: if ((error = dns_so_tcp_send(so))) goto error; so->state++; case DNS_SO_TCP_RECV: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; case DNS_SO_TCP_DONE: /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */ if (so->type != SOCK_STREAM) { if ((error = dns_so_closefd(so, &so->tcp))) goto error; } if ((error = dns_so_verify(so, so->answer))) goto error; return 0; case DNS_SO_SOCKS_INIT: if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; case DNS_SO_SOCKS_CONN: { unsigned char method; error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error); if (error && error != DNS_EISCONN) goto error; /* We need to do a handshake with the SOCKS server, * but the query is already in the buffer. Move it * out of the way. */ dns_p_movptr(&so->query_backup, &so->query); /* Create a new buffer for the handshake. */ dns_p_grow(&so->query); /* Negotiate method. */ buffer = dns_so_tcp_send_buffer(so, 3); buffer[0] = 5; /* RFC-1928 VER field. */ buffer[1] = 1; /* NMETHODS */ if (so->opts.socks_user) method = 2; /* Method: username/password authentication. */ else method = 0; /* Method: No authentication required. */ buffer[2] = method; so->state++; } case DNS_SO_SOCKS_HELLO_SEND: if ((error = dns_so_tcp_send(so))) goto error; dns_so_tcp_recv_expect(so, 2); so->state++; case DNS_SO_SOCKS_HELLO_RECV: { unsigned char method; if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); method = so->opts.socks_user ? 2 : 0; if (buffer[0] != 5 || buffer[1] != method) { /* Socks server returned wrong version or does not support our requested method. */ error = ENOTSUP; /* Fixme: Is there a better errno? */ goto error; } if (method == 0) { /* No authentication, go ahead and send the request. */ so->state = DNS_SO_SOCKS_REQUEST_PREPARE; goto retry; } /* Prepare username/password sub-negotiation. */ if (! so->opts.socks_password) { error = EINVAL; /* No password given. */ goto error; } else { size_t buflen, ulen, plen; ulen = strlen(so->opts.socks_user); plen = strlen(so->opts.socks_password); if (!ulen || ulen > 255 || !plen || plen > 255) { error = EINVAL; /* Credentials too long or too short. */ goto error; } buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen); buffer[0] = 1; /* VER of the sub-negotiation. */ buffer[1] = (unsigned char) ulen; buflen = 2; memcpy (buffer+buflen, so->opts.socks_user, ulen); buflen += ulen; buffer[buflen++] = (unsigned char) plen; memcpy (buffer+buflen, so->opts.socks_password, plen); } so->state++; } case DNS_SO_SOCKS_AUTH_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Skip the two length octets, and receive two octets. */ dns_so_tcp_recv_expect(so, 2); so->state++; case DNS_SO_SOCKS_AUTH_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 1) { /* SOCKS server returned wrong version. */ error = EPROTO; goto error; } if (buffer[1]) { /* SOCKS server denied access. */ error = EACCES; goto error; } so->state++; case DNS_SO_SOCKS_REQUEST_PREPARE: /* Send request details (rfc-1928, 4). */ buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10); buffer[0] = 5; /* VER */ buffer[1] = 1; /* CMD = CONNECT */ buffer[2] = 0; /* RSV */ if (so->remote.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote; buffer[3] = 4; /* ATYP = IPv6 */ memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */ memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */ } else { struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote; buffer[3] = 1; /* ATYP = IPv4 */ memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */ memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */ } so->state++; case DNS_SO_SOCKS_REQUEST_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Expect ten octets. This is the length of the * response assuming a IPv4 address is used. */ dns_so_tcp_recv_expect(so, 10); so->state++; case DNS_SO_SOCKS_REQUEST_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 5 || buffer[2] != 0) { /* Socks server returned wrong version or the reserved field is not zero. */ error = EPROTO; goto error; } if (buffer[1]) { switch (buffer[1]) { case 0x01: /* general SOCKS server failure. */ error = ENETDOWN; break; case 0x02: /* connection not allowed by ruleset. */ error = EACCES; break; case 0x03: /* Network unreachable */ error = ENETUNREACH; break; case 0x04: /* Host unreachable */ error = EHOSTUNREACH; break; case 0x05: /* Connection refused */ error = ECONNREFUSED; break; case 0x06: /* TTL expired */ error = ETIMEDOUT; break; case 0x08: /* Address type not supported */ error = EPROTONOSUPPORT; break; case 0x07: /* Command not supported */ default: error = ENOTSUP; /* Fixme: Is there a better error? */ break; } goto error; } if (buffer[3] == 1) { /* This was indeed an IPv4 address. */ so->state = DNS_SO_SOCKS_HANDSHAKE_DONE; goto retry; } if (buffer[3] != 4) { error = ENOTSUP; goto error; } /* Expect receive twelve octets. This accounts for * the remaining bytes assuming an IPv6 address is * used. */ dns_so_tcp_recv_expect(so, 12); so->state++; case DNS_SO_SOCKS_REQUEST_RECV_V6: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; case DNS_SO_SOCKS_HANDSHAKE_DONE: /* We have not way to store the actual address used by * the server. Then again, we don't really care. */ /* Restore the query. */ dns_p_movptr(&so->query, &so->query_backup); /* Reset cursors. */ so->qout = 0; so->apos = 0; so->alen = 0; /* SOCKS handshake is done. Proceed with the * lookup. */ so->state = DNS_SO_TCP_SEND; goto retry; default: error = DNS_EUNKNOWN; goto error; } /* switch() */ trash: DNS_CARP("discarding packet"); goto retry; error: switch (error) { case DNS_EINTR: goto retry; case DNS_EINPROGRESS: /* FALL THROUGH */ case DNS_EALREADY: /* FALL THROUGH */ #if DNS_EWOULDBLOCK != DNS_EAGAIN case DNS_EWOULDBLOCK: /* FALL THROUGH */ #endif error = DNS_EAGAIN; break; } /* switch() */ return error; } /* dns_so_check() */ struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) { struct dns_packet *answer; switch (so->state) { case DNS_SO_UDP_DONE: case DNS_SO_TCP_DONE: answer = so->answer; so->answer = 0; dns_trace_so_fetch(so->trace, answer, 0); return answer; default: *error = DNS_EUNKNOWN; dns_trace_so_fetch(so->trace, NULL, *error); return 0; } } /* dns_so_fetch() */ struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) { struct dns_packet *A; int error; if (!so->state) { if ((error = dns_so_submit(so, Q, host))) goto error; } if ((error = dns_so_check(so))) goto error; if (!(A = dns_so_fetch(so, &error))) goto error; dns_so_reset(so); return A; error: *error_ = error; return 0; } /* dns_so_query() */ time_t dns_so_elapsed(struct dns_socket *so) { return dns_elapsed(&so->elapsed); } /* dns_so_elapsed() */ void dns_so_clear(struct dns_socket *so) { dns_so_closefds(so, DNS_SO_CLOSE_OLD); } /* dns_so_clear() */ static int dns_so_events2(struct dns_socket *so, enum dns_events type) { int events = 0; switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_UDP_RECV: events |= DNS_POLLIN; break; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_TCP_RECV: events |= DNS_POLLIN; break; } /* switch() */ switch (type) { case DNS_LIBEVENT: return DNS_POLL2EV(events); default: return events; } /* switch() */ } /* dns_so_events2() */ int dns_so_events(struct dns_socket *so) { return dns_so_events2(so, so->opts.events); } /* dns_so_events() */ int dns_so_pollfd(struct dns_socket *so) { switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: case DNS_SO_UDP_RECV: return so->udp; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: case DNS_SO_TCP_RECV: return so->tcp; } /* switch() */ return -1; } /* dns_so_pollfd() */ int dns_so_poll(struct dns_socket *so, int timeout) { return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout); } /* dns_so_poll() */ const struct dns_stat *dns_so_stat(struct dns_socket *so) { return &so->stat; } /* dns_so_stat() */ struct dns_trace *dns_so_trace(struct dns_socket *so) { return so->trace; } /* dns_so_trace() */ void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) { struct dns_trace *otrace = so->trace; so->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); } /* dns_so_settrace() */ /* * R E S O L V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ enum dns_res_state { DNS_R_INIT, DNS_R_GLUE, DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */ DNS_R_FILE, /* Lookup in local hosts database */ DNS_R_CACHE, /* Lookup in application cache */ DNS_R_SUBMIT, DNS_R_CHECK, DNS_R_FETCH, DNS_R_BIND, /* Lookup in the network */ DNS_R_SEARCH, DNS_R_HINTS, DNS_R_ITERATE, DNS_R_FOREACH_NS, DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */ DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */ DNS_R_FOREACH_A, DNS_R_QUERY_A, DNS_R_CNAME0_A, DNS_R_CNAME1_A, DNS_R_FINISH, DNS_R_SMART0_A, DNS_R_SMART1_A, DNS_R_DONE, DNS_R_SERVFAIL, }; /* enum dns_res_state */ #define DNS_R_MAXDEPTH 8 #define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1) struct dns_resolver { struct dns_socket so; struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_hints *hints; struct dns_cache *cache; struct dns_trace *trace; dns_atomic_t refcount; /* Reset zeroes everything below here. */ char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_clock elapsed; dns_resconf_i_t search; struct dns_rr_i smart; struct dns_packet *nodata; /* answer if nothing better */ unsigned sp; struct dns_res_frame { enum dns_res_state state; int error; int which; /* (B)IND, (F)ILE; index into resconf->lookup */ int qflags; unsigned attempts; struct dns_packet *query, *answer, *hints; struct dns_rr_i hints_i, hints_j; struct dns_rr hints_ns, ans_cname; } stack[DNS_R_MAXDEPTH]; }; /* struct dns_resolver */ static int dns_res_tcp2type(int tcp) { switch (tcp) { case DNS_RESCONF_TCP_ONLY: case DNS_RESCONF_TCP_SOCKS: return SOCK_STREAM; case DNS_RESCONF_TCP_DISABLE: return SOCK_DGRAM; default: return 0; } } /* dns_res_tcp2type() */ struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) { static const struct dns_resolver R_initializer = { .refcount = 1, }; struct dns_resolver *R = 0; int type, error; /* * Grab ref count early because the caller may have passed us a mortal * reference, and we want to do the right thing if we return early * from an error. */ if (resconf) dns_resconf_acquire(resconf); if (hosts) dns_hosts_acquire(hosts); if (hints) dns_hints_acquire(hints); if (cache) dns_cache_acquire(cache); /* * Don't try to load it ourselves because a NULL object might be an * error from, say, dns_resconf_root(), and loading * dns_resconf_local() by default would create undesirable surpises. */ if (!resconf || !hosts || !hints) { if (!*_error) *_error = EINVAL; goto _error; } if (!(R = malloc(sizeof *R))) goto syerr; *R = R_initializer; type = dns_res_tcp2type(resconf->options.tcp); if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error)) goto error; R->resconf = resconf; R->hosts = hosts; R->hints = hints; R->cache = cache; return R; syerr: error = dns_syerr(); error: *_error = error; _error: dns_res_close(R); dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); dns_cache_close(cache); return 0; } /* dns_res_open() */ struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) { struct dns_resolv_conf *resconf = 0; struct dns_hosts *hosts = 0; struct dns_hints *hints = 0; struct dns_resolver *res = 0; if (!(resconf = dns_resconf_local(error))) goto epilog; if (!(hosts = dns_hosts_local(error))) goto epilog; if (!(hints = dns_hints_local(resconf, error))) goto epilog; if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error))) goto epilog; epilog: dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); return res; } /* dns_res_stub() */ static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) { (void)R; dns_p_setptr(&frame->query, NULL); dns_p_setptr(&frame->answer, NULL); dns_p_setptr(&frame->hints, NULL); } /* dns_res_frame_destroy() */ static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) { memset(frame, '\0', sizeof *frame); /* * NB: Can be invoked from dns_res_open, before R->resconf has been * initialized. */ if (R->resconf) { if (!R->resconf->options.recurse) frame->qflags |= DNS_Q_RD; if (R->resconf->options.edns0) frame->qflags |= DNS_Q_EDNS0; } } /* dns_res_frame_init() */ static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) { dns_res_frame_destroy(R, frame); dns_res_frame_init(R, frame); } /* dns_res_frame_reset() */ static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) { struct dns_packet *P = NULL; if (!(F < endof(R->stack))) return DNS_EUNKNOWN; dns_p_movptr(&P, &F->query); dns_res_frame_reset(R, F); dns_p_movptr(&F->query, &P); return dns_q_make(&F->query, qname, qtype, qclass, F->qflags); } /* dns_res_frame_prepare() */ void dns_res_reset(struct dns_resolver *R) { unsigned i; dns_so_reset(&R->so); dns_p_setptr(&R->nodata, NULL); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_destroy(R, &R->stack[i]); memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname)); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_init(R, &R->stack[i]); } /* dns_res_reset() */ void dns_res_close(struct dns_resolver *R) { if (!R || 1 < dns_res_release(R)) return; dns_res_reset(R); dns_so_destroy(&R->so); dns_hints_close(R->hints); dns_hosts_close(R->hosts); dns_resconf_close(R->resconf); dns_cache_close(R->cache); dns_trace_close(R->trace); free(R); } /* dns_res_close() */ dns_refcount_t dns_res_acquire(struct dns_resolver *R) { return dns_atomic_fetch_add(&R->refcount); } /* dns_res_acquire() */ dns_refcount_t dns_res_release(struct dns_resolver *R) { return dns_atomic_fetch_sub(&R->refcount); } /* dns_res_release() */ struct dns_resolver *dns_res_mortal(struct dns_resolver *res) { if (res) dns_res_release(res); return res; } /* dns_res_mortal() */ static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) { size_t bufsiz = P0->end + P1->end; struct dns_packet *P[3] = { P0, P1, 0 }; struct dns_rr rr[3]; int error, copy, i; enum dns_section section; retry: if (!(P[2] = dns_p_make(bufsiz, &error))) goto error; dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) { if ((error = dns_rr_copy(P[2], &rr[0], P[0]))) goto error; } for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) { for (i = 0; i < 2; i++) { dns_rr_foreach(&rr[i], P[i], .section = section) { copy = 1; dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) { if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) { copy = 0; break; } } if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) { if (error == DNS_ENOBUFS && bufsiz < 65535) { dns_p_setptr(&P[2], NULL); bufsiz = DNS_PP_MAX(65535, bufsiz * 2); goto retry; } goto error; } } /* foreach(rr) */ } /* foreach(packet) */ } /* foreach(section) */ return P[2]; error: *error_ = error; dns_p_free(P[2]); return 0; } /* dns_res_merge() */ static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { struct dns_packet *P = dns_p_new(512); char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; struct dns_rr rr; unsigned sp; int error; if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error)) || qlen >= sizeof qname) return 0; if (!(qtype = dns_rr_type(12, Q))) return 0; if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0))) return 0; for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (dns_p_count(P, DNS_S_AN) > 0) goto copy; /* Otherwise, look for a CNAME */ for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (!dns_p_count(P, DNS_S_AN)) return 0; copy: return dns_p_copy(dns_p_make(P->end, &error), P); } /* dns_res_glue() */ /* * Sort NS records by three criteria: * * 1) Whether glue is present. * 2) Whether glue record is original or of recursive lookup. * 3) Randomly shuffle records which share the above criteria. * * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will * be added during an iteration. * * FIXME: Only groks A glue, not AAAA glue. */ static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { _Bool glued[2] = { 0 }; struct dns_rr x = { 0 }, y = { 0 }; struct dns_ns ns; int cmp, error; if (!(error = dns_ns_parse(&ns, a, P))) glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); if (!(error = dns_ns_parse(&ns, b, P))) glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); if ((cmp = glued[1] - glued[0])) { return cmp; } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { return cmp; } else { return dns_rr_i_shuffle(a, b, i, P); } } /* dns_res_nameserv_cmp() */ #define dgoto(sp, i) \ do { R->stack[(sp)].state = (i); goto exec; } while (0) static int dns_res_exec(struct dns_resolver *R) { struct dns_res_frame *F; struct dns_packet *P; union { char host[DNS_D_MAXNAME + 1]; char name[DNS_D_MAXNAME + 1]; struct dns_ns ns; struct dns_cname cname; } u; size_t len; struct dns_rr rr; int error; exec: F = &R->stack[R->sp]; switch (F->state) { case DNS_R_INIT: F->state++; case DNS_R_GLUE: if (R->sp == 0) dgoto(R->sp, DNS_R_SWITCH); if (!F->query) goto noquery; if (!(F->answer = dns_res_glue(R, F->query))) dgoto(R->sp, DNS_R_SWITCH); if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) { dgoto(R->sp, DNS_R_FINISH); } dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } F->state++; case DNS_R_SWITCH: while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) { switch (R->resconf->lookup[F->which++]) { case 'b': case 'B': dgoto(R->sp, DNS_R_BIND); case 'f': case 'F': dgoto(R->sp, DNS_R_FILE); case 'c': case 'C': if (R->cache) dgoto(R->sp, DNS_R_CACHE); break; default: break; } } /* * FIXME: Examine more closely whether our logic is correct * and DNS_R_SERVFAIL is the correct default response. * * Case 1: We got here because we never got an answer on the * wire. All queries timed-out and we reached maximum * attempts count. See DNS_R_FOREACH_NS. In that case * DNS_R_SERVFAIL is the correct state, unless we want to * return DNS_ETIMEDOUT. * * Case 2: We were a stub resolver and got an unsatisfactory * answer (empty ANSWER section) which caused us to jump * back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We * return the answer returned from the wire, which we * stashed in R->nodata. * * Case 3: We reached maximum attempts count as in case #1, * but never got an authoritative response which caused us * to short-circuit. See end of DNS_R_QUERY_A case. We * should probably prepare R->nodata as in case #2. */ if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */ dns_p_movptr(&F->answer, &R->nodata); dgoto(R->sp, DNS_R_FINISH); } dgoto(R->sp, DNS_R_SERVFAIL); case DNS_R_FILE: if (R->sp > 0) { if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } else { R->search = 0; while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) { if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } } dgoto(R->sp, DNS_R_SWITCH); case DNS_R_CACHE: error = 0; if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags))) goto error; if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; F->state++; case DNS_R_SUBMIT: if ((error = R->cache->submit(F->query, R->cache))) goto error; F->state++; case DNS_R_CHECK: if ((error = R->cache->check(R->cache))) goto error; F->state++; case DNS_R_FETCH: error = 0; if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; dgoto(R->sp, DNS_R_SWITCH); case DNS_R_BIND: if (R->sp > 0) { if (!F->query) goto noquery; dgoto(R->sp, DNS_R_HINTS); } R->search = 0; F->state++; case DNS_R_SEARCH: /* * XXX: We probably should only apply the domain search * algorithm if R->sp == 0. */ if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) dgoto(R->sp, DNS_R_SWITCH); if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; F->state++; case DNS_R_HINTS: if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error))) goto error; F->state++; case DNS_R_ITERATE: dns_rr_i_init(&F->hints_i, F->hints); F->hints_i.section = DNS_S_AUTHORITY; F->hints_i.type = DNS_T_NS; F->hints_i.sort = &dns_res_nameserv_cmp; F->hints_i.args[0] = F->hints->end; F->state++; case DNS_R_FOREACH_NS: dns_rr_i_save(&F->hints_i); /* Load our next nameserver host. */ if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) { if (++F->attempts < R->resconf->options.attempts) dgoto(R->sp, DNS_R_ITERATE); dgoto(R->sp, DNS_R_SWITCH); } dns_rr_i_init(&F->hints_j, F->hints); /* Assume there are glue records */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_RESOLV0_NS: /* Have we reached our max depth? */ if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FOREACH_NS); if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_RESOLV1_NS: if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error))) goto error; else if (len >= sizeof u.host) goto toolong; dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AR; if ((error = dns_rr_copy(F->hints, &rr, F[1].answer))) goto error; dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */ } dgoto(R->sp, DNS_R_FOREACH_NS); case DNS_R_FOREACH_A: { struct dns_a a; struct sockaddr_in sin; /* * NOTE: Iterator initialized in DNS_R_FOREACH_NS because * this state is re-entrant, but we need to reset * .name to a valid pointer each time. */ if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { if (!dns_rr_i_count(&F->hints_j)) dgoto(R->sp, DNS_R_RESOLV0_NS); dgoto(R->sp, DNS_R_FOREACH_NS); } if ((error = dns_a_parse(&a, &rr, F->hints))) goto error; memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */ sin.sin_family = AF_INET; sin.sin_addr = a.addr; if (R->sp == 0) sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr); else sin.sin_port = htons(53); if (DNS_DEBUG) { char addr[INET_ADDRSTRLEN + 1]; dns_a_print(addr, sizeof addr, &a); dns_header(F->query)->qid = dns_so_mkqid(&R->so); DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); } dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin); if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin))) goto error; F->state++; } case DNS_R_QUERY_A: if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) dgoto(R->sp, DNS_R_FOREACH_A); if ((error = dns_so_check(&R->so))) goto error; if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) goto error; if (DNS_DEBUG) { DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); } if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || dns_p_rcode(F->answer) == DNS_RC_NOTIMP || dns_p_rcode(F->answer) == DNS_RC_BADVERS) { /* Temporarily disable EDNS0 and try again. */ if (F->qflags & DNS_Q_EDNS0) { F->qflags &= ~DNS_Q_EDNS0; if ((error = dns_q_remake(&F->query, F->qflags))) goto error; dgoto(R->sp, DNS_R_FOREACH_A); } } if ((error = dns_rr_parse(&rr, 12, F->query))) goto error; if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { dgoto(R->sp, DNS_R_FINISH); /* Found */ } dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } /* * XXX: The condition here should probably check whether * R->sp == 0, because DNS_R_SEARCH runs regardless of * options.recurse. See DNS_R_BIND. */ if (!R->resconf->options.recurse) { /* Make first answer our tentative answer */ if (!R->nodata) dns_p_movptr(&R->nodata, &F->answer); dgoto(R->sp, DNS_R_SEARCH); } dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { dns_p_movptr(&F->hints, &F->answer); dgoto(R->sp, DNS_R_ITERATE); } /* XXX: Should this go further up? */ if (dns_header(F->answer)->aa) dgoto(R->sp, DNS_R_FINISH); /* XXX: Should we copy F->answer to R->nodata? */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_CNAME0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FINISH); if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_CNAME1_A: if (!(P = dns_res_merge(F->answer, F[1].answer, &error))) goto error; dns_p_setptr(&F->answer, P); dgoto(R->sp, DNS_R_FINISH); case DNS_R_FINISH: if (!F->answer) goto noanswer; if (!R->resconf->options.smart || R->sp > 0) dgoto(R->sp, DNS_R_DONE); R->smart.section = DNS_S_AN; R->smart.type = R->qtype; dns_rr_i_init(&R->smart, F->answer); F->state++; case DNS_R_SMART0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_DONE); while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) { union { struct dns_ns ns; struct dns_mx mx; struct dns_srv srv; } rd; const char *qname; enum dns_type qtype; enum dns_class qclass; switch (rr.type) { case DNS_T_NS: if ((error = dns_ns_parse(&rd.ns, &rr, F->answer))) goto error; qname = rd.ns.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_MX: if ((error = dns_mx_parse(&rd.mx, &rr, F->answer))) goto error; qname = rd.mx.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_SRV: if ((error = dns_srv_parse(&rd.srv, &rr, F->answer))) goto error; qname = rd.srv.target; qtype = DNS_T_A; qclass = DNS_C_IN; break; default: continue; } /* switch() */ if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); } /* while() */ /* * NOTE: SMTP specification says to fallback to A record. * * XXX: Should we add a mock MX answer? */ if (R->qtype == DNS_T_MX && R->smart.state.count == 0) { if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN))) goto error; R->smart.state.count++; F->state++; dgoto(++R->sp, DNS_R_INIT); } dgoto(R->sp, DNS_R_DONE); case DNS_R_SMART1_A: if (!F[1].answer) goto noanswer; /* * FIXME: For CNAME chains (which are typically illegal in * this context), we should rewrite the record host name * to the original smart qname. All the user cares about * is locating that A/AAAA record. */ dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) { rr.section = DNS_S_AR; if (dns_rr_exists(&rr, F[1].answer, F->answer)) continue; while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) { if (error != DNS_ENOBUFS) goto error; if ((error = dns_p_grow(&F->answer))) goto error; } } dgoto(R->sp, DNS_R_SMART0_A); case DNS_R_DONE: if (!F->answer) goto noanswer; if (R->sp > 0) dgoto(--R->sp, F[-1].state); break; case DNS_R_SERVFAIL: if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error))) goto error; dns_header(F->answer)->qr = 1; dns_header(F->answer)->rcode = DNS_RC_SERVFAIL; if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0))) goto error; dgoto(R->sp, DNS_R_DONE); default: error = EINVAL; goto error; } /* switch () */ return 0; noquery: error = DNS_ENOQUERY; goto error; noanswer: error = DNS_ENOANSWER; goto error; toolong: error = DNS_EILLEGAL; /* FALL THROUGH */ error: return error; } /* dns_res_exec() */ #undef goto void dns_res_clear(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: R->cache->clear(R->cache); break; default: dns_so_clear(&R->so); break; } } /* dns_res_clear() */ static int dns_res_events2(struct dns_resolver *R, enum dns_events type) { int events; switch (R->stack[R->sp].state) { case DNS_R_CHECK: events = R->cache->events(R->cache); return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events; default: return dns_so_events2(&R->so, type); } } /* dns_res_events2() */ int dns_res_events(struct dns_resolver *R) { return dns_res_events2(R, R->so.opts.events); } /* dns_res_events() */ int dns_res_pollfd(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: return R->cache->pollfd(R->cache); default: return dns_so_pollfd(&R->so); } } /* dns_res_pollfd() */ time_t dns_res_timeout(struct dns_resolver *R) { time_t elapsed; switch (R->stack[R->sp].state) { #if 0 case DNS_R_QUERY_AAAA: #endif case DNS_R_QUERY_A: elapsed = dns_so_elapsed(&R->so); if (elapsed <= dns_resconf_timeout(R->resconf)) return R->resconf->options.timeout - elapsed; break; default: break; } /* switch() */ /* * NOTE: We're not in a pollable state, or the user code hasn't * called dns_res_check properly. The calling code is probably * broken. Put them into a slow-burn pattern. */ return 1; } /* dns_res_timeout() */ time_t dns_res_elapsed(struct dns_resolver *R) { return dns_elapsed(&R->elapsed); } /* dns_res_elapsed() */ int dns_res_poll(struct dns_resolver *R, int timeout) { return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout); } /* dns_res_poll() */ int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) { dns_res_reset(R); /* Don't anchor; that can conflict with searchlist generation. */ dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0); R->qtype = qtype; R->qclass = qclass; dns_begin(&R->elapsed); dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0); return 0; } /* dns_res_submit2() */ int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) { return dns_res_submit2(R, qname, strlen(qname), qtype, qclass); } /* dns_res_submit() */ int dns_res_check(struct dns_resolver *R) { int error; if (R->stack[0].state != DNS_R_DONE) { if ((error = dns_res_exec(R))) return error; } return 0; } /* dns_res_check() */ struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (R->stack[0].state != DNS_R_DONE) { error = DNS_EUNKNOWN; goto error; } if (!dns_p_movptr(&P, &R->stack[0].answer)) { error = DNS_EFETCHED; goto error; } dns_trace_res_fetch(R->trace, P, 0); return P; error: *_error = error; dns_trace_res_fetch(R->trace, NULL, error); return NULL; } /* dns_res_fetch() */ static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (!(P = dns_res_fetch(R, &error))) goto error; if ((error = dns_p_study(P))) goto error; return P; error: *_error = error; dns_p_free(P); return NULL; } /* dns_res_fetch_and_study() */ struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) { int error; if ((error = dns_res_submit(res, qname, qtype, qclass))) goto error; while ((error = dns_res_check(res))) { if (dns_res_elapsed(res) > timeout) error = DNS_ETIMEDOUT; if (error != DNS_EAGAIN) goto error; if ((error = dns_res_poll(res, 1))) goto error; } return dns_res_fetch(res, error_); error: *error_ = error; return 0; } /* dns_res_query() */ const struct dns_stat *dns_res_stat(struct dns_resolver *res) { return dns_so_stat(&res->so); } /* dns_res_stat() */ void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) { dns_hints_acquire(hints); /* acquire first in case same hints object */ dns_hints_close(res->hints); res->hints = hints; } /* dns_res_sethints() */ struct dns_trace *dns_res_trace(struct dns_resolver *res) { return res->trace; } /* dns_res_trace() */ void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) { struct dns_trace *otrace = res->trace; res->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); dns_so_settrace(&res->so, trace); } /* dns_res_settrace() */ /* * A D D R I N F O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_addrinfo { struct addrinfo hints; struct dns_resolver *res; struct dns_trace *trace; char qname[DNS_D_MAXNAME + 1]; enum dns_type qtype; unsigned short qport, port; struct { unsigned long todo; int state; int atype; enum dns_type qtype; } af; struct dns_packet *answer; struct dns_packet *glue; struct dns_rr_i i, g; struct dns_rr rr; char cname[DNS_D_MAXNAME + 1]; char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1]; int g_depth; int state; int found; struct dns_stat st; }; /* struct dns_addrinfo */ #define DNS_AI_AFMAX 32 #define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1)) static inline unsigned long dns_ai_af2index(int af) { dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch"); dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch"); return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0; } static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) { ai->af.atype = af; ai->af.qtype = qtype; ai->af.todo &= ~dns_ai_af2index(af); return af; } /* dns_ai_setaf() */ #define DNS_SM_RESTORE \ do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0) #define DNS_SM_SAVE \ do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0) static int dns_ai_nextaf(struct dns_addrinfo *ai) { int i, pc; dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0"); dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large"); dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large"); DNS_SM_ENTER; if (ai->res) { /* * NB: On OpenBSD, at least, the types of entries resolved * is the intersection of the /etc/resolv.conf families and * the families permitted by the .ai_type hint. So if * /etc/resolv.conf has "family inet4" and .ai_type * is AF_INET6, then the address ::1 will return 0 entries * even if AI_NUMERICHOST is specified in .ai_flags. */ while (i < (int)lengthof(ai->res->resconf->family)) { int af = ai->res->resconf->family[i++]; if (af == AF_UNSPEC) { DNS_SM_EXIT; } else if (af < 0 || af > DNS_AI_AFMAX) { continue; } else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) { continue; } else if (af == AF_INET) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); } else if (af == AF_INET6) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } } } else { /* * NB: If we get here than AI_NUMERICFLAGS should be set and * order shouldn't matter. */ if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } DNS_SM_LEAVE; return dns_ai_setaf(ai, AF_UNSPEC, 0); } /* dns_ai_nextaf() */ #undef DNS_SM_RESTORE #undef DNS_SM_SAVE static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) { return (ai->qtype)? ai->qtype : ai->af.qtype; } /* dns_ai_qtype() */ /* JW: This is not defined on mingw. */ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) { const char *cp = serv; unsigned long n = 0; while (*cp >= '0' && *cp <= '9' && n < 65536) { n *= 10; n += *cp++ - '0'; } if (*cp == '\0') { if (cp == serv || n >= 65536) return DNS_ESERVICE; *port = n; return 0; } if (hints->ai_flags & AI_NUMERICSERV) return DNS_ESERVICE; /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */ return DNS_ESERVICE; } /* dns_ai_parseport() */ struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) { static const struct dns_addrinfo ai_initializer; struct dns_addrinfo *ai; int error; if (res) { dns_res_acquire(res); } else if (!(hints->ai_flags & AI_NUMERICHOST)) { /* * NOTE: it's assumed that *_error is set from a previous * API function call, such as dns_res_stub(). Should change * this semantic, but it's applied elsewhere, too. */ if (!*_error) *_error = EINVAL; return NULL; } if (!(ai = malloc(sizeof *ai))) goto syerr; *ai = ai_initializer; ai->hints = *hints; ai->res = res; res = NULL; if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname)) { error = ENAMETOOLONG; goto error; } ai->qtype = qtype; ai->qport = 0; if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints))) goto error; ai->port = ai->qport; /* * FIXME: If an explicit A or AAAA record type conflicts with * .ai_family or with resconf.family (i.e. AAAA specified but * AF_INET6 not in interection of .ai_family and resconf.family), * then what? */ switch (ai->qtype) { case DNS_T_A: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case DNS_T_AAAA: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: /* 0, MX, SRV, etc */ switch (ai->hints.ai_family) { case AF_UNSPEC: ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6); break; case AF_INET: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case AF_INET6: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: break; } } return ai; syerr: error = dns_syerr(); error: *_error = error; dns_ai_close(ai); dns_res_close(res); return NULL; } /* dns_ai_open() */ void dns_ai_close(struct dns_addrinfo *ai) { if (!ai) return; dns_res_close(ai->res); dns_trace_close(ai->trace); if (ai->answer != ai->glue) dns_p_free(ai->glue); dns_p_free(ai->answer); free(ai); } /* dns_ai_close() */ static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) { union u { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_storage ss; } addr; const char *cname; size_t clen; switch (type) { case DNS_T_A: memset(&addr.sin, '\0', sizeof addr.sin); addr.sin.sin_family = AF_INET; addr.sin.sin_port = htons(ai->port); memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr); break; case DNS_T_AAAA: memset(&addr.sin6, '\0', sizeof addr.sin6); addr.sin6.sin6_family = AF_INET6; addr.sin6.sin6_port = htons(ai->port); memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr); break; default: return EINVAL; } /* switch() */ if (ai->hints.ai_flags & AI_CANONNAME) { cname = (*ai->cname)? ai->cname : ai->qname; clen = strlen(cname); } else { cname = NULL; clen = 0; } if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0)))) return dns_syerr(); memset(*ent, '\0', sizeof **ent); (*ent)->ai_family = addr.ss.ss_family; (*ent)->ai_socktype = ai->hints.ai_socktype; (*ent)->ai_protocol = ai->hints.ai_protocol; (*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr)); (*ent)->ai_addrlen = dns_sa_len(&addr); if (ai->hints.ai_flags & AI_CANONNAME) (*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1); ai->found++; return 0; } /* dns_ai_setent() */ enum dns_ai_state { DNS_AI_S_INIT, DNS_AI_S_NEXTAF, DNS_AI_S_NUMERIC, DNS_AI_S_SUBMIT, DNS_AI_S_CHECK, DNS_AI_S_FETCH, DNS_AI_S_FOREACH_I, DNS_AI_S_INIT_G, DNS_AI_S_ITERATE_G, DNS_AI_S_FOREACH_G, DNS_AI_S_SUBMIT_G, DNS_AI_S_CHECK_G, DNS_AI_S_FETCH_G, DNS_AI_S_DONE, }; /* enum dns_ai_state */ #define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0) int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) { struct dns_packet *ans, *glue; struct dns_rr rr; char qname[DNS_D_MAXNAME + 1]; union dns_any any; size_t qlen, clen; int error; *ent = 0; exec: switch (ai->state) { case DNS_AI_S_INIT: ai->state++; case DNS_AI_S_NEXTAF: if (!dns_ai_nextaf(ai)) dns_ai_goto(DNS_AI_S_DONE); ai->state++; case DNS_AI_S_NUMERIC: if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) { if (ai->af.atype == AF_INET) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_A, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) { if (ai->af.atype == AF_INET6) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_AAAA, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (ai->hints.ai_flags & AI_NUMERICHOST) dns_ai_goto(DNS_AI_S_NEXTAF); ai->state++; case DNS_AI_S_SUBMIT: assert(ai->res); if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN))) return error; ai->state++; case DNS_AI_S_CHECK: if ((error = dns_res_check(ai->res))) return error; ai->state++; case DNS_AI_S_FETCH: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = dns_p_movptr(&ai->answer, &ans); /* Search generator may have changed the qname. */ if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error))) return error; else if (qlen >= sizeof qname) return DNS_EILLEGAL; if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error)) return error; dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); dns_rr_i_init(&ai->i, ai->answer); ai->i.section = DNS_S_AN; ai->i.name = ai->i_cname; ai->i.type = dns_ai_qtype(ai); ai->i.sort = &dns_rr_i_order; ai->state++; case DNS_AI_S_FOREACH_I: if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error)) dns_ai_goto(DNS_AI_S_NEXTAF); if ((error = dns_any_parse(&any, &rr, ai->answer))) return error; ai->port = ai->qport; switch (rr.type) { case DNS_T_A: case DNS_T_AAAA: return dns_ai_setent(ent, &any, rr.type, ai); default: if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type))) dns_ai_goto(DNS_AI_S_FOREACH_I); /* * Find the "real" canonical name. Some authorities * publish aliases where an RFC defines a canonical * name. We trust that the resolver followed any * CNAME chains on it's own, regardless of whether * the "smart" option is enabled. */ if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error)) return error; if (rr.type == DNS_T_SRV) ai->port = any.srv.port; break; } /* switch() */ ai->state++; case DNS_AI_S_INIT_G: ai->g_depth = 0; ai->state++; case DNS_AI_S_ITERATE_G: dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); dns_rr_i_init(&ai->g, ai->glue); ai->g.section = DNS_S_ALL & ~DNS_S_QD; ai->g.name = ai->g_cname; ai->g.type = ai->af.qtype; ai->state++; case DNS_AI_S_FOREACH_G: if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) { if (dns_rr_i_count(&ai->g) > 0) dns_ai_goto(DNS_AI_S_FOREACH_I); else dns_ai_goto(DNS_AI_S_SUBMIT_G); } if ((error = dns_any_parse(&any, &rr, ai->glue))) return error; return dns_ai_setent(ent, &any, rr.type, ai); case DNS_AI_S_SUBMIT_G: /* skip if already queried */ if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); /* skip if we recursed (CNAME chains should have been handled in the resolver) */ if (++ai->g_depth > 1) dns_ai_goto(DNS_AI_S_FOREACH_I); if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) return error; ai->state++; case DNS_AI_S_CHECK_G: if ((error = dns_res_check(ai->res))) return error; ai->state++; case DNS_AI_S_FETCH_G: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error); dns_p_setptr(&ans, NULL); if (!glue) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = glue; if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); dns_ai_goto(DNS_AI_S_ITERATE_G); case DNS_AI_S_DONE: if (ai->found) { return ENOENT; /* TODO: Just return 0 */ } else if (ai->answer) { switch (dns_p_rcode(ai->answer)) { case DNS_RC_NOERROR: /* FALL THROUGH */ case DNS_RC_NXDOMAIN: return DNS_ENONAME; default: return DNS_EFAIL; } } else { return DNS_EFAIL; } default: return EINVAL; } /* switch() */ } /* dns_ai_nextent() */ time_t dns_ai_elapsed(struct dns_addrinfo *ai) { return (ai->res)? dns_res_elapsed(ai->res) : 0; } /* dns_ai_elapsed() */ void dns_ai_clear(struct dns_addrinfo *ai) { if (ai->res) dns_res_clear(ai->res); } /* dns_ai_clear() */ int dns_ai_events(struct dns_addrinfo *ai) { return (ai->res)? dns_res_events(ai->res) : 0; } /* dns_ai_events() */ int dns_ai_pollfd(struct dns_addrinfo *ai) { return (ai->res)? dns_res_pollfd(ai->res) : -1; } /* dns_ai_pollfd() */ time_t dns_ai_timeout(struct dns_addrinfo *ai) { return (ai->res)? dns_res_timeout(ai->res) : 0; } /* dns_ai_timeout() */ int dns_ai_poll(struct dns_addrinfo *ai, int timeout) { return (ai->res)? dns_res_poll(ai->res, timeout) : 0; } /* dns_ai_poll() */ size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) { struct dns_buf dst = DNS_B_INTO(_dst, lim); char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; dns_b_puts(&dst, "[ "); dns_b_puts(&dst, ai->qname); dns_b_puts(&dst, " IN "); if (ai->qtype) { dns_b_puts(&dst, dns_strtype(ai->qtype)); } else if (ent->ai_family == AF_INET) { dns_b_puts(&dst, dns_strtype(DNS_T_A)); } else if (ent->ai_family == AF_INET6) { dns_b_puts(&dst, dns_strtype(DNS_T_AAAA)); } else { dns_b_puts(&dst, "0"); } dns_b_puts(&dst, " ]\n"); dns_b_puts(&dst, ".ai_family = "); switch (ent->ai_family) { case AF_INET: dns_b_puts(&dst, "AF_INET"); break; case AF_INET6: dns_b_puts(&dst, "AF_INET6"); break; default: dns_b_fmtju(&dst, ent->ai_family, 0); break; } dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_socktype = "); switch (ent->ai_socktype) { case SOCK_STREAM: dns_b_puts(&dst, "SOCK_STREAM"); break; case SOCK_DGRAM: dns_b_puts(&dst, "SOCK_DGRAM"); break; default: dns_b_fmtju(&dst, ent->ai_socktype, 0); break; } dns_b_putc(&dst, '\n'); dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr); dns_b_puts(&dst, ".ai_addr = ["); dns_b_puts(&dst, addr); dns_b_puts(&dst, "]:"); dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0); dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_canonname = "); dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]"); dns_b_putc(&dst, '\n'); return dns_b_strllen(&dst); } /* dns_ai_print() */ const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) { return (ai->res)? dns_res_stat(ai->res) : &ai->st; } /* dns_ai_stat() */ struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) { return ai->trace; } /* dns_ai_trace() */ void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) { struct dns_trace *otrace = ai->trace; ai->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); if (ai->res) dns_res_settrace(ai->res, trace); } /* dns_ai_settrace() */ /* * M I S C E L L A N E O U S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static const struct { char name[16]; enum dns_section type; } dns_sections[] = { { "QUESTION", DNS_S_QUESTION }, { "QD", DNS_S_QUESTION }, { "ANSWER", DNS_S_ANSWER }, { "AN", DNS_S_ANSWER }, { "AUTHORITY", DNS_S_AUTHORITY }, { "NS", DNS_S_AUTHORITY }, { "ADDITIONAL", DNS_S_ADDITIONAL }, { "AR", DNS_S_ADDITIONAL }, }; const char *(dns_strsection)(enum dns_section section, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_sections); i++) { if (dns_sections[i].type & section) { dns_b_puts(&dst, dns_sections[i].name); section &= ~dns_sections[i].type; if (section) dns_b_putc(&dst, '|'); } } if (section || dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & section), 0); return dns_b_tostring(&dst); } /* dns_strsection() */ enum dns_section dns_isection(const char *src) { enum dns_section section = 0; char sbuf[128]; char *name, *next; unsigned i; dns_strlcpy(sbuf, src, sizeof sbuf); next = sbuf; while ((name = dns_strsep(&next, "|+, \t"))) { for (i = 0; i < lengthof(dns_sections); i++) { if (!strcasecmp(dns_sections[i].name, name)) { section |= dns_sections[i].type; break; } } } return section; } /* dns_isection() */ static const struct { char name[8]; enum dns_class type; } dns_classes[] = { { "IN", DNS_C_IN }, }; const char *(dns_strclass)(enum dns_class type, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_classes); i++) { if (dns_classes[i].type == type) { dns_b_puts(&dst, dns_classes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strclass() */ enum dns_class dns_iclass(const char *name) { unsigned i, class; for (i = 0; i < lengthof(dns_classes); i++) { if (!strcasecmp(dns_classes[i].name, name)) return dns_classes[i].type; } class = 0; while (dns_isdigit(*name)) { class *= 10; class += *name++ - '0'; } return DNS_PP_MIN(class, 0xffff); } /* dns_iclass() */ const char *(dns_strtype)(enum dns_type type, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (dns_rrtypes[i].type == type) { dns_b_puts(&dst, dns_rrtypes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strtype() */ enum dns_type dns_itype(const char *name) { unsigned i, type; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (!strcasecmp(dns_rrtypes[i].name, name)) return dns_rrtypes[i].type; } type = 0; while (dns_isdigit(*name)) { type *= 10; type += *name++ - '0'; } return DNS_PP_MIN(type, 0xffff); } /* dns_itype() */ static char dns_opcodes[16][16] = { [DNS_OP_QUERY] = "QUERY", [DNS_OP_IQUERY] = "IQUERY", [DNS_OP_STATUS] = "STATUS", [DNS_OP_NOTIFY] = "NOTIFY", [DNS_OP_UPDATE] = "UPDATE", }; static const char *dns__strcode(int code, volatile char *dst, size_t lim) { char _tmp[48] = ""; struct dns_buf tmp; size_t p; assert(lim > 0); dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0); /* copy downwards so first byte is copied last (see below) */ p = (size_t)(tmp.p - tmp.base); dst[p] = '\0'; while (p--) dst[p] = _tmp[p]; return (const char *)dst; } const char *dns_stropcode(enum dns_opcode opcode) { opcode = (unsigned)opcode % lengthof(dns_opcodes); if ('\0' == dns_opcodes[opcode][0]) return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]); return dns_opcodes[opcode]; } /* dns_stropcode() */ enum dns_opcode dns_iopcode(const char *name) { unsigned opcode; for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) { if (!strcasecmp(name, dns_opcodes[opcode])) return opcode; } opcode = 0; while (dns_isdigit(*name)) { opcode *= 10; opcode += *name++ - '0'; } return DNS_PP_MIN(opcode, 0x0f); } /* dns_iopcode() */ static char dns_rcodes[32][16] = { [DNS_RC_NOERROR] = "NOERROR", [DNS_RC_FORMERR] = "FORMERR", [DNS_RC_SERVFAIL] = "SERVFAIL", [DNS_RC_NXDOMAIN] = "NXDOMAIN", [DNS_RC_NOTIMP] = "NOTIMP", [DNS_RC_REFUSED] = "REFUSED", [DNS_RC_YXDOMAIN] = "YXDOMAIN", [DNS_RC_YXRRSET] = "YXRRSET", [DNS_RC_NXRRSET] = "NXRRSET", [DNS_RC_NOTAUTH] = "NOTAUTH", [DNS_RC_NOTZONE] = "NOTZONE", /* EDNS(0) extended RCODEs ... */ [DNS_RC_BADVERS] = "BADVERS", }; const char *dns_strrcode(enum dns_rcode rcode) { rcode = (unsigned)rcode % lengthof(dns_rcodes); if ('\0' == dns_rcodes[rcode][0]) return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]); return dns_rcodes[rcode]; } /* dns_strrcode() */ enum dns_rcode dns_ircode(const char *name) { unsigned rcode; for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) { if (!strcasecmp(name, dns_rcodes[rcode])) return rcode; } rcode = 0; while (dns_isdigit(*name)) { rcode *= 10; rcode += *name++ - '0'; } return DNS_PP_MIN(rcode, 0xfff); } /* dns_ircode() */ /* * C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if DNS_MAIN #include #include #include #include #if _WIN32 #include #endif #if !_WIN32 #include #endif struct { struct { const char *path[8]; unsigned count; } resconf, nssconf, hosts, cache; const char *qname; enum dns_type qtype; int (*sort)(); const char *trace; int verbose; struct { struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_trace *trace; } memo; struct sockaddr_storage socks_host; const char *socks_user; const char *socks_password; } MAIN = { .sort = &dns_rr_i_packet, }; static void hexdump(const unsigned char *src, size_t len, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; while (dns_hxd_lines(line, sizeof line, src, len, &lines)) { fputs(line, fp); } } /* hexdump() */ DNS_NORETURN static void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); #if _WIN32 vfprintf(stderr, fmt, ap); exit(EXIT_FAILURE); #else verrx(EXIT_FAILURE, fmt, ap); #endif } /* panic() */ #define panic_(fn, ln, fmt, ...) \ panic(fmt "%0s", (fn), (ln), __VA_ARGS__) #define panic(...) \ panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "") static void *grow(unsigned char *p, size_t size) { void *tmp; if (!(tmp = realloc(p, size))) panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno)); return tmp; } /* grow() */ static size_t add(size_t a, size_t b) { if (~a < b) panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b); return a + b; } /* add() */ static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) { size_t size = add(osize, len); *dst = grow(*dst, size); memcpy(*dst + osize, src, len); return size; } /* append() */ static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) { size_t size = osize; unsigned char buf[1024]; size_t count; while ((count = fread(buf, 1, sizeof buf, fp))) size = append(dst, size, buf, count); if (ferror(fp)) panic("%s: %s", path, dns_strerror(errno)); return size; } /* slurp() */ static struct dns_resolv_conf *resconf(void) { struct dns_resolv_conf **resconf = &MAIN.memo.resconf; const char *path; unsigned i; int error; if (*resconf) return *resconf; if (!(*resconf = dns_resconf_open(&error))) panic("dns_resconf_open: %s", dns_strerror(error)); if (!MAIN.resconf.count) MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf"; for (i = 0; i < MAIN.resconf.count; i++) { path = MAIN.resconf.path[i]; if (0 == strcmp(path, "-")) error = dns_resconf_loadfile(*resconf, stdin); else error = dns_resconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } for (i = 0; i < MAIN.nssconf.count; i++) { path = MAIN.nssconf.path[i]; if (0 == strcmp(path, "-")) error = dns_nssconf_loadfile(*resconf, stdin); else error = dns_nssconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } if (!MAIN.nssconf.count) { path = "/etc/nsswitch.conf"; if (!(error = dns_nssconf_loadpath(*resconf, path))) MAIN.nssconf.path[MAIN.nssconf.count++] = path; else if (error != ENOENT) panic("%s: %s", path, dns_strerror(error)); } return *resconf; } /* resconf() */ static struct dns_hosts *hosts(void) { struct dns_hosts **hosts = &MAIN.memo.hosts; const char *path; unsigned i; int error; if (*hosts) return *hosts; if (!MAIN.hosts.count) { MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts"; /* Explicitly test dns_hosts_local() */ if (!(*hosts = dns_hosts_local(&error))) panic("%s: %s", "/etc/hosts", dns_strerror(error)); return *hosts; } if (!(*hosts = dns_hosts_open(&error))) panic("dns_hosts_open: %s", dns_strerror(error)); for (i = 0; i < MAIN.hosts.count; i++) { path = MAIN.hosts.path[i]; if (0 == strcmp(path, "-")) error = dns_hosts_loadfile(*hosts, stdin); else error = dns_hosts_loadpath(*hosts, path); if (error) panic("%s: %s", path, dns_strerror(error)); } return *hosts; } /* hosts() */ #if DNS_CACHE #include "cache.h" static struct dns_cache *cache(void) { static struct cache *cache; const char *path; unsigned i; int error; if (cache) return cache_resi(cache); if (!MAIN.cache.count) return NULL; if (!(cache = cache_open(&error))) panic("%s: %s", MAIN.cache.path[0], dns_strerror(error)); for (i = 0; i < MAIN.cache.count; i++) { path = MAIN.cache.path[i]; if (!strcmp(path, "-")) { if ((error = cache_loadfile(cache, stdin, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } else if ((error = cache_loadpath(cache, path, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } return cache_resi(cache); } /* cache() */ #else static struct dns_cache *cache(void) { return NULL; } #endif static struct dns_trace *trace(const char *mode) { static char omode[64] = ""; struct dns_trace **trace = &MAIN.memo.trace; FILE *fp; int error; if (*trace && 0 == strcmp(omode, mode)) return *trace; if (!MAIN.trace) return NULL; if (!(fp = fopen(MAIN.trace, mode))) panic("%s: %s", MAIN.trace, strerror(errno)); dns_trace_close(*trace); if (!(*trace = dns_trace_open(fp, &error))) panic("%s: %s", MAIN.trace, dns_strerror(error)); dns_strlcpy(omode, mode, sizeof omode); return *trace; } static void print_packet(struct dns_packet *P, FILE *fp) { dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp); if (MAIN.verbose > 2) hexdump(P->data, P->end, fp); } /* print_packet() */ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { struct dns_packet *P = dns_p_new(512); struct dns_packet *Q = dns_p_new(512); enum dns_section section; struct dns_rr rr; int error; union dns_any any; char pretty[sizeof any * 2]; size_t len; P->end = fread(P->data, 1, P->size, stdin); fputs(";; [HEADER]\n", stdout); fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); section = 0; dns_rr_foreach(&rr, P, .sort = MAIN.sort) { if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) fprintf(stdout, "%s\n", pretty); dns_rr_copy(Q, &rr, P); section = rr.section; } fputs("; ; ; ; ; ; ; ;\n\n", stdout); section = 0; #if 0 dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { #else struct dns_rr rrset[32]; struct dns_rr_i *rri = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort); unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); for (unsigned i = 0; i < rrcount; i++) { rr = rrset[i]; #endif if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error))) fprintf(stdout, "%s\n", pretty); section = rr.section; } if (MAIN.verbose > 1) { fprintf(stderr, "orig:%"PRIuZ"\n", P->end); hexdump(P->data, P->end, stdout); fprintf(stderr, "copy:%"PRIuZ"\n", Q->end); hexdump(Q->data, Q->end, stdout); } return 0; } /* parse_packet() */ static int parse_domain(int argc, char *argv[]) { char *dn; dn = (argc > 1)? argv[1] : "f.l.google.com"; printf("[%s]\n", dn); dn = dns_d_new(dn); do { puts(dn); } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn))); return 0; } /* parse_domain() */ static int trim_domain(int argc, char **argv) { for (argc--, argv++; argc > 0; argc--, argv++) { char name[DNS_D_MAXNAME + 1]; dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR); puts(name); } return 0; } /* trim_domain() */ static int expand_domain(int argc, char *argv[]) { unsigned short rp = 0; unsigned char *src = NULL; unsigned char *dst; struct dns_packet *pkt; size_t lim = 0, len; int error; if (argc > 1) rp = atoi(argv[1]); len = slurp(&src, 0, stdin, "-"); if (!(pkt = dns_p_make(len, &error))) panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error)); memcpy(pkt->data, src, len); pkt->end = len; lim = 1; dst = grow(NULL, lim); while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) { lim = add(len, 1); dst = grow(dst, lim); } if (!len) panic("expand: %s", dns_strerror(error)); fwrite(dst, 1, len, stdout); fflush(stdout); free(src); free(dst); free(pkt); return 0; } /* expand_domain() */ static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); /* load it */ fputs("; SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "; %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]); fputs(";\n", stdout); dns_resconf_dump(resconf(), stdout); return 0; } /* show_resconf() */ static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "# %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]); fputs("#\n", stdout); dns_nssconf_dump(resconf(), stdout); return 0; } /* show_nssconf() */ static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; hosts(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.hosts.count; i++) fprintf(stdout, "# %s\n", MAIN.hosts.path[i]); fputs("#\n", stdout); dns_hosts_dump(hosts(), stdout); return 0; } /* show_hosts() */ static int query_hosts(int argc, char *argv[]) { struct dns_packet *Q = dns_p_new(512); struct dns_packet *A; char qname[DNS_D_MAXNAME + 1]; size_t qlen; int error; if (!MAIN.qname) MAIN.qname = (argc > 1)? argv[1] : "localhost"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; hosts(); if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) { union { struct in_addr a; struct in6_addr a6; } addr; int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(af, MAIN.qname, &addr))) panic("%s: %s", MAIN.qname, dns_strerror(error)); qlen = dns_ptr_qname(qname, sizeof qname, af, &addr); } else qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname); if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0))) panic("%s: %s", qname, dns_strerror(error)); if (!(A = dns_hosts_query(hosts(), Q, &error))) panic("%s: %s", qname, dns_strerror(error)); print_packet(A, stdout); free(A); return 0; } /* query_hosts() */ static int search_list(int argc, char *argv[]) { const char *qname = (argc > 1)? argv[1] : "f.l.google.com"; unsigned long i = 0; char name[DNS_D_MAXNAME + 1]; printf("[%s]\n", qname); while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i)) puts(name); return 0; } /* search_list() */ static int permute_set(int argc, char *argv[]) { unsigned lo, hi, i; struct dns_k_permutor p; hi = (--argc > 0)? atoi(argv[argc]) : 8; lo = (--argc > 0)? atoi(argv[argc]) : 0; fprintf(stderr, "[%u .. %u]\n", lo, hi); dns_k_permutor_init(&p, lo, hi); for (i = lo; i <= hi; i++) fprintf(stdout, "%u\n", dns_k_permutor_step(&p)); // printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i))); return 0; } /* permute_set() */ static int shuffle_16(int argc, char *argv[]) { unsigned n, r; if (--argc > 0) { n = 0xffff & atoi(argv[argc]); r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random(); fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } else { r = dns_random(); for (n = 0; n < 65536; n++) fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } return 0; } /* shuffle_16() */ static int dump_random(int argc, char *argv[]) { unsigned char b[32]; unsigned i, j, n, r; n = (argc > 1)? atoi(argv[1]) : 32; while (n) { i = 0; do { r = dns_random(); for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) { b[i] = 0xff & r; r >>= 8; } } while (i < n && i < sizeof b); hexdump(b, i, stdout); n -= i; } return 0; } /* dump_random() */ static int send_query(int argc, char *argv[]) { struct dns_packet *A, *Q = dns_p_new(512); char host[INET6_ADDRSTRLEN + 1]; struct sockaddr_storage ss; struct dns_socket *so; int error, type; if (argc > 1) { ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL)))) panic("%s: %s", argv[1], dns_strerror(error)); *dns_sa_port(ss.ss_family, &ss) = htons(53); } else memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0])); if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host)) panic("bad host address, or none provided"); if (!MAIN.qname) MAIN.qname = "ipv6.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_AAAA; if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0))) panic("dns_p_push: %s", dns_strerror(error)); dns_header(Q)->rd = 1; if (strstr(argv[0], "udp")) type = SOCK_DGRAM; else if (strstr(argv[0], "tcp")) type = SOCK_STREAM; else type = dns_res_tcp2type(resconf()->options.tcp); fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype)); if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error))) panic("dns_so_open: %s", dns_strerror(error)); while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { if (error != DNS_EAGAIN) panic("dns_so_query: %s (%d)", dns_strerror(error), error); if (dns_so_elapsed(so) > 10) panic("query timed-out"); dns_so_poll(so, 1); } print_packet(A, stdout); dns_so_close(so); return 0; } /* send_query() */ static int print_arpa(int argc, char *argv[]) { const char *ip = (argc > 1)? argv[1] : "::1"; int af = (strchr(ip, ':'))? AF_INET6 : AF_INET; union { struct in_addr a4; struct in6_addr a6; } addr; char host[DNS_D_MAXNAME + 1]; if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr)) panic("%s: invalid address", ip); fprintf(stdout, "%s\n", host); return 0; } /* print_arpa() */ static int show_hints(int argc, char *argv[]) { struct dns_hints *(*load)(struct dns_resolv_conf *, int *); const char *which, *how, *who; struct dns_hints *hints; int error; which = (argc > 1)? argv[1] : "local"; how = (argc > 2)? argv[2] : "plain"; who = (argc > 3)? argv[3] : "google.com"; load = (0 == strcmp(which, "local")) ? &dns_hints_local : &dns_hints_root; if (!(hints = load(resconf(), &error))) panic("%s: %s", argv[0], dns_strerror(error)); if (0 == strcmp(how, "plain")) { dns_hints_dump(hints, stdout); } else { struct dns_packet *query, *answer; query = dns_p_new(512); if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) panic("%s: %s", who, dns_strerror(error)); if (!(answer = dns_hints_query(hints, query, &error))) panic("%s: %s", who, dns_strerror(error)); print_packet(answer, stdout); free(answer); } dns_hints_close(hints); return 0; } /* show_hints() */ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *R; struct dns_packet *ans; const struct dns_stat *st; int error; if (!MAIN.qname) MAIN.qname = "www.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; resconf()->options.recurse = recurse; if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(.socks_host=&MAIN.socks_host, .socks_user=MAIN.socks_user, .socks_password=MAIN.socks_password), &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_res_settrace(R, trace("w+b")); if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN))) panic("%s: %s", MAIN.qname, dns_strerror(error)); while ((error = dns_res_check(R))) { if (error != DNS_EAGAIN) panic("dns_res_check: %s (%d)", dns_strerror(error), error); if (dns_res_elapsed(R) > 30) panic("query timed-out"); dns_res_poll(R, 1); } ans = dns_res_fetch(R, &error); print_packet(ans, stdout); free(ans); st = dns_res_stat(R); putchar('\n'); printf(";; queries: %"PRIuZ"\n", st->queries); printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes); printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes); printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes); printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes); dns_res_close(R); return 0; } /* resolve_query() */ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *res = NULL; struct dns_addrinfo *ai = NULL; struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; struct addrinfo *ent; char pretty[512]; int error; if (!MAIN.qname) MAIN.qname = "www.google.com"; /* NB: MAIN.qtype of 0 means obey hints.ai_family */ resconf()->options.recurse = recurse; if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_ai_settrace(ai, trace("w+b")); do { switch (error = dns_ai_nextent(&ent, ai)) { case 0: dns_ai_print(pretty, sizeof pretty, ent, ai); fputs(pretty, stdout); free(ent); break; case ENOENT: break; case DNS_EAGAIN: if (dns_ai_elapsed(ai) > 30) panic("query timed-out"); dns_ai_poll(ai, 1); break; default: panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error); } } while (error != ENOENT); dns_res_close(res); dns_ai_close(ai); return 0; } /* resolve_addrinfo() */ static int dump_trace(int argc DNS_NOTUSED, char *argv[]) { int error; if (!MAIN.trace) panic("no trace file specified"); if ((error = dns_trace_dump(trace("r"), stdout))) panic("dump_trace: %s", dns_strerror(error)); return 0; } /* dump_trace() */ static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { union { struct sockaddr sa; struct sockaddr_in sin; } port; int fd; memset(&port, 0, sizeof port); port.sin.sin_family = AF_INET; port.sin.sin_port = htons(5354); port.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0))) panic("socket: %s", strerror(errno)); if (0 != bind(fd, &port.sa, sizeof port.sa)) panic("127.0.0.1:5353: %s", dns_strerror(errno)); for (;;) { struct dns_packet *pkt = dns_p_new(512); struct sockaddr_storage ss; socklen_t slen = sizeof ss; ssize_t count; #if defined(MSG_WAITALL) /* MinGW issue */ int rflags = MSG_WAITALL; #else int rflags = 0; #endif count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen); if (!count || count < 0) panic("recvfrom: %s", strerror(errno)); pkt->end = count; dns_p_dump(pkt, stdout); (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen); } return 0; } /* echo_port() */ static int isection(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_isection(name); name = dns_strsection(type); printf("%s (%d)\n", name, type); return 0; } /* isection() */ static int iclass(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iclass(name); name = dns_strclass(type); printf("%s (%d)\n", name, type); return 0; } /* iclass() */ static int itype(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_itype(name); name = dns_strtype(type); printf("%s (%d)\n", name, type); return 0; } /* itype() */ static int iopcode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iopcode(name); name = dns_stropcode(type); printf("%s (%d)\n", name, type); return 0; } /* iopcode() */ static int ircode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_ircode(name); name = dns_strrcode(type); printf("%s (%d)\n", name, type); return 0; } /* ircode() */ #define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) } #define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__) #define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__) #define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__) #define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static const struct { const char *name; size_t size; } type[] = { SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i), SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns), SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv), SIZE(struct dns_sshfp, struct dns_txt, union dns_any), SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i), SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo), SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long) }; unsigned i, max; for (i = 0, max = 0; i < lengthof(type); i++) max = DNS_PP_MAX(max, strlen(type[i].name)); for (i = 0; i < lengthof(type); i++) printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size); return 0; } /* sizes() */ static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = { { "parse-packet", &parse_packet, "parse binary packet from stdin" }, { "parse-domain", &parse_domain, "anchor and iteratively cleave domain" }, { "trim-domain", &trim_domain, "trim and anchor domain name" }, { "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" }, { "show-resconf", &show_resconf, "show resolv.conf data" }, { "show-hosts", &show_hosts, "show hosts data" }, { "show-nssconf", &show_nssconf, "show nsswitch.conf data" }, { "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" }, { "search-list", &search_list, "generate query search list from domain" }, { "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" }, { "shuffle-16", &shuffle_16, "simple 16-bit permutation" }, { "dump-random", &dump_random, "generate random bytes" }, { "send-query", &send_query, "send query to host" }, { "send-query-udp", &send_query, "send udp query to host" }, { "send-query-tcp", &send_query, "send tcp query to host" }, { "print-arpa", &print_arpa, "print arpa. zone name of address" }, { "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" }, { "resolve-stub", &resolve_query, "resolve as stub resolver" }, { "resolve-recurse", &resolve_query, "resolve as recursive resolver" }, { "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" }, { "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" }, /* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */ { "dump-trace", &dump_trace, "dump the contents of a trace file" }, { "echo", &echo_port, "server echo mode, for nmap fuzzing" }, { "isection", &isection, "parse section string" }, { "iclass", &iclass, "parse class string" }, { "itype", &itype, "parse type string" }, { "iopcode", &iopcode, "parse opcode string" }, { "ircode", &ircode, "parse rcode string" }, { "sizes", &sizes, "print data structure sizes" }, }; static void print_usage(const char *progname, FILE *fp) { static const char *usage = " [OPTIONS] COMMAND [ARGS]\n" " -c PATH Path to resolv.conf\n" " -n PATH Path to nsswitch.conf\n" " -l PATH Path to local hosts\n" " -z PATH Path to zone cache\n" " -q QNAME Query name\n" " -t QTYPE Query type\n" " -s HOW Sort records\n" " -S ADDR Address of SOCKS server to use\n" " -P PORT Port of SOCKS server to use\n" " -A USER:PASSWORD Credentials for the SOCKS server\n" " -f PATH Path to trace file\n" " -v Be more verbose (-vv show packets; -vvv hexdump packets)\n" " -V Print version info\n" " -h Print this usage message\n" "\n"; unsigned i, n, m; fputs(progname, fp); fputs(usage, fp); for (i = 0, m = 0; i < lengthof(cmds); i++) { if (strlen(cmds[i].cmd) > m) m = strlen(cmds[i].cmd); } for (i = 0; i < lengthof(cmds); i++) { fprintf(fp, " %s ", cmds[i].cmd); for (n = strlen(cmds[i].cmd); n < m; n++) putc(' ', fp); fputs(cmds[i].help, fp); putc('\n', fp); } fputs("\nReport bugs to William Ahern \n", fp); } /* print_usage() */ static void print_version(const char *progname, FILE *fp) { fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel()); fprintf(fp, "vendor %s\n", dns_vendor()); fprintf(fp, "release %.8X\n", dns_v_rel()); fprintf(fp, "abi %.8X\n", dns_v_abi()); fprintf(fp, "api %.8X\n", dns_v_api()); } /* print_version() */ static void main_exit(void) { dns_trace_close(MAIN.memo.trace); MAIN.memo.trace = NULL; dns_hosts_close(MAIN.memo.hosts); MAIN.memo.hosts = NULL; dns_resconf_close(MAIN.memo.resconf); MAIN.memo.resconf = NULL; } /* main_exit() */ int main(int argc, char **argv) { extern int optind; extern char *optarg; const char *progname = argv[0]; unsigned i; int ch; atexit(&main_exit); while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) { switch (ch) { case 'c': assert(MAIN.resconf.count < lengthof(MAIN.resconf.path)); MAIN.resconf.path[MAIN.resconf.count++] = optarg; break; case 'n': assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path)); MAIN.nssconf.path[MAIN.nssconf.count++] = optarg; break; case 'l': assert(MAIN.hosts.count < lengthof(MAIN.hosts.path)); MAIN.hosts.path[MAIN.hosts.count++] = optarg; break; case 'z': assert(MAIN.cache.count < lengthof(MAIN.cache.path)); MAIN.cache.path[MAIN.cache.count++] = optarg; break; case 'q': MAIN.qname = optarg; break; case 't': for (i = 0; i < lengthof(dns_rrtypes); i++) { if (0 == strcasecmp(dns_rrtypes[i].name, optarg)) { MAIN.qtype = dns_rrtypes[i].type; break; } } if (MAIN.qtype) break; for (i = 0; dns_isdigit(optarg[i]); i++) { MAIN.qtype *= 10; MAIN.qtype += optarg[i] - '0'; } if (!MAIN.qtype) panic("%s: invalid query type", optarg); break; case 's': if (0 == strcasecmp(optarg, "packet")) MAIN.sort = &dns_rr_i_packet; else if (0 == strcasecmp(optarg, "shuffle")) MAIN.sort = &dns_rr_i_shuffle; else if (0 == strcasecmp(optarg, "order")) MAIN.sort = &dns_rr_i_order; else panic("%s: invalid sort method", optarg); break; case 'S': { dns_error_t error; struct dns_resolv_conf *conf = resconf(); conf->options.tcp = DNS_RESCONF_TCP_SOCKS; MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET; if ((error = dns_pton(MAIN.socks_host.ss_family, optarg, dns_sa_addr(MAIN.socks_host.ss_family, &MAIN.socks_host, NULL)))) panic("%s: %s", optarg, dns_strerror(error)); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080); break; } case 'P': if (! MAIN.socks_host.ss_family) panic("-P without prior -S"); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg)); break; case 'A': { char *password; if (! MAIN.socks_host.ss_family) panic("-A without prior -S"); if (! (password = strchr(optarg, ':'))) panic("Usage: -A USER:PASSWORD"); *password = 0; password += 1; MAIN.socks_user = optarg; MAIN.socks_password = password; break; } case 'f': MAIN.trace = optarg; break; case 'v': dns_debug = ++MAIN.verbose; break; case 'V': print_version(progname, stdout); return 0; case 'h': print_usage(progname, stdout); return 0; default: print_usage(progname, stderr); return EXIT_FAILURE; } /* switch() */ } /* while() */ argc -= optind; argv += optind; for (i = 0; i < lengthof(cmds) && argv[0]; i++) { if (0 == strcmp(cmds[i].cmd, argv[0])) return cmds[i].run(argc, argv); } print_usage(progname, stderr); return EXIT_FAILURE; } /* main() */ #endif /* DNS_MAIN */ /* * pop file-scoped compiler annotations */ #if __clang__ #pragma clang diagnostic pop #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic pop #endif diff --git a/dirmngr/http.c b/dirmngr/http.c index e74d051ec..f461e5da3 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -1,3152 +1,3152 @@ /* http.c - HTTP protocol handler * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch * Copyright (C) 2015-2017 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 contraints however: + 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. - With HTTP_USE_NTBTLS or HTTP_USE_GNUTLS support for https is provided (this also requires estream). - 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 #else /*!HAVE_W32_SYSTEM*/ # 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 "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; # define USE_TLS 1 #elif HTTP_USE_GNUTLS typedef gnutls_session_t tls_session_t; # define USE_TLS 1 #else typedef void *tls_session_t; # undef USE_TLS #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 (http_t hd, const char *httphost, const char *auth,const char *proxy, const char *srvtag,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 (const char *server, unsigned short port, unsigned int flags, const char *srvtag, 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. */ #ifdef HTTP_USE_GNUTLS gnutls_certificate_credentials_t certcred; #endif /*HTTP_USE_GNUTLS*/ #ifdef USE_TLS 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. */ #endif /*USE_TLS*/ /* 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; }; /* 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 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, (int)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, (int)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, (int)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; #if USE_NPTH return npth_read (sock->fd, buffer, size); #else return read (sock->fd, buffer, size); #endif } static ssize_t my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size) { my_socket_t sock = ptr; #if USE_NPTH return npth_write (sock->fd, buffer, size); #else return write (sock->fd, buffer, size); #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) { 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 (access (fname, F_OK)) log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (gpg_error_from_syserror())); sl = add_to_strlist (&tls_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 (); } #ifdef USE_TLS /* 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; } } #endif /*USE_TLS*/ /* 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; #ifdef USE_TLS close_tls_session (sess); #endif /*USE_TLS*/ 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_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; #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; } is_hkps_pool = (intended_hostname && !ascii_strcasecmp (intended_hostname, get_default_keyserver (1))); /* If the user has not specified a CA list, and they are looking * for the hkps pool from sks-keyservers.net, then default to * Kristian's certificate authority: */ if (!tls_ca_certlist && 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); } } /* Add configured certificates to the session. */ if ((flags & HTTP_FLAG_TRUST_DEF)) { 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 (!tls_ca_certlist && !is_hkps_pool) 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 */ } 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; #if USE_TLS leave: #endif /*USE_TLS*/ 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; } /* 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 (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 (hd, httphost, auth, proxy, srvtag, 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. */ gpg_error_t http_raw_connect (http_t *r_hd, const char *server, unsigned short port, unsigned int flags, const char *srvtag) { 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); } } /* 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 (server, port, hd->flags, srvtag, &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_with_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; /* 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); 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 = hd->uri->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 (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 (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 + strlen (uri)); if (!*ret_uri) return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); strcpy ((*ret_uri)->buffer, uri); ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls); if (ec) { xfree (*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 NO_SCHEME_CHECK 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, int no_scheme_check) { return parse_uri (ret_uri, uri, no_scheme_check, 0); } void http_release_parsed_uri (parsed_uri_t uri) { if (uri) { uri_tuple_t r, r2; 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->opaque = 0; uri->v6lit = 0; uri->onion = 0; uri->explicit_port = 0; /* A quick validity check. */ if (strspn (p, VALID_URI_CHARS) != n) 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; } #ifdef USE_TLS 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; } #endif /*USE_TLS*/ else if (!no_scheme_check) return GPG_ERR_INV_URI; /* Unsupported scheme */ p = p2; if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */ { p += 2; if ((p2 = strchr (p, '/'))) *p2++ = 0; /* 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; uri->v6lit = 1; p = p3; } else uri->host = p; 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 incudes a Nul. */ + return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */ p = p2 ? p2 : NULL; } else if (uri->is_http) return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */ 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 (http_t hd, const char *httphost, const char *auth, const char *proxy, const char *srvtag, 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; 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); } #ifdef USE_TLS if (hd->uri->use_tls && !hd->session->tls_session) { log_error ("TLS requested but no GNUTLS context available\n"); return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); } #endif /*USE_TLS*/ 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); } } server = *hd->uri->host ? hd->uri->host : "localhost"; port = hd->uri->port ? hd->uri->port : 80; /* Try to use SNI. */ #ifdef USE_TLS 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*/ } #endif /*USE_TLS*/ 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") || !strcmp (uri->scheme, "socks4")) ; else if (!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 (*uri->host ? uri->host : "localhost", uri->port ? uri->port : 80, hd->flags, srvtag, &sock); http_release_parsed_uri (uri); } else { err = connect_server (server, port, hd->flags, srvtag, &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 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 ((void*)(unsigned int)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 ((void*)(unsigned int)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; } #ifdef HTTP_USE_NTBTLS 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; } } #endif /*HTTP_USE_NTBTLS*/ while ((err = ntbtls_handshake (hd->session->tls_session))) { 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 */ #ifdef HTTP_USE_NTBTLS err = 0; /* Fixme check that the CB has been called. */ #else err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif 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) 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_with_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_with_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 implementaion. */ + 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_err_code_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_err_code_from_syserror (); /* Out of core. */ if (!maxlen) return GPG_ERR_TRUNCATED; /* Line has been truncated. */ if (!len) return GPG_ERR_EOF; if ((hd->flags & HTTP_FLAG_LOG_RESP)) log_debug_with_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_err_code_from_syserror (); /* Out of core. */ /* Note, that we can silently ignore truncated lines. */ if (!len) return GPG_ERR_EOF; /* Trim line endings of empty lines. */ if ((*line == '\r' && line[1] == '\n') || *line == '\n') *line = 0; if ((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 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 fucntion in + * 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); } /* 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. */ static gpg_error_t connect_server (const char *server, unsigned short port, unsigned int flags, const char *srvtag, 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; 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*/ /* 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 (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 (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)) continue; if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6)) 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) { 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; if (assuan_sock_connect (sock, (struct sockaddr *)ai->addr, ai->addrlen)) { last_err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } 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 const 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; } diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c index ac4964a55..8b53bd60f 100644 --- a/dirmngr/ldap-wrapper.c +++ b/dirmngr/ldap-wrapper.c @@ -1,782 +1,782 @@ /* ldap-wrapper.c - LDAP access via a wrapper process * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH * Copyright (C) 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 . */ /* We can't use LDAP directly for these reasons: 1. On some systems the LDAP library uses (indirectly) pthreads and that is not compatible with PTh. 2. It is huge library in particular if TLS comes into play. So problems with unfreed memory might turn up and we don't want this in a long running daemon. 3. There is no easy way for timeouts. In particular the timeout value does not work for DNS lookups (well, this is usual) and it seems not to work while loading a large attribute like a CRL. Having a separate process allows us to either tell the process to commit suicide or have our own housekepping function kill it after some time. The latter also allows proper cancellation of a query at any point of time. 4. Given that we are going out to the network and usually get back a long response, the fork/exec overhead is acceptable. Note that under WindowsCE the number of processes is strongly limited (32 processes including the kernel processes) and thus we don't use the process approach but implement a different wrapper in ldap-wrapper-ce.c. */ #include #include #include #include #include #include #include #include #include #include "dirmngr.h" #include "../common/exechelp.h" #include "misc.h" #include "ldap-wrapper.h" #ifdef HAVE_W32_SYSTEM #define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) #else #define pth_close(fd) close(fd) #endif #ifndef USE_LDAPWRAPPER # error This module is not expected to be build. #endif /* In case sysconf does not return a value we need to have a limit. */ #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif #define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ #define TIMERTICK_INTERVAL 2 /* To keep track of the LDAP wrapper state we use this structure. */ struct wrapper_context_s { struct wrapper_context_s *next; pid_t pid; /* The pid of the wrapper process. */ int printable_pid; /* Helper to print diagnostics after the process has been cleaned up. */ int fd; /* Connected with stdout of the ldap wrapper. */ gpg_error_t fd_error; /* Set to the gpg_error of the last read error if any. */ int log_fd; /* Connected with stderr of the ldap wrapper. */ ctrl_t ctrl; /* Connection data. */ int ready; /* Internally used to mark to be removed contexts. */ ksba_reader_t reader; /* The ksba reader object or NULL. */ char *line; /* Used to print the log lines (malloced). */ size_t linesize;/* Allocated size of LINE. */ size_t linelen; /* Use size of LINE. */ time_t stamp; /* The last time we noticed ativity. */ }; -/* We keep a global list of spawed wrapper process. A separate thread +/* We keep a global list of spawned wrapper process. A separate thread makes use of this list to log error messages and to watch out for finished processes. */ static struct wrapper_context_s *wrapper_list; /* We need to know whether we are shutting down the process. */ static int shutting_down; /* Close the pth file descriptor FD and set it to -1. */ #define SAFE_CLOSE(fd) \ do { int _fd = fd; if (_fd != -1) { close (_fd); fd = -1;} } while (0) /* Read a fixed amount of data from READER into BUFFER. */ static gpg_error_t read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) { gpg_error_t err; size_t nread; while (count) { err = ksba_reader_read (reader, buffer, count, &nread); if (err) return err; buffer += nread; count -= nread; } return 0; } /* Release the wrapper context and kill a running wrapper process. */ static void destroy_wrapper (struct wrapper_context_s *ctx) { if (ctx->pid != (pid_t)(-1)) { gnupg_kill_process (ctx->pid); gnupg_release_process (ctx->pid); } ksba_reader_release (ctx->reader); SAFE_CLOSE (ctx->fd); SAFE_CLOSE (ctx->log_fd); xfree (ctx->line); xfree (ctx); } /* Print the content of LINE to thye log stream but make sure to only print complete lines. Using NULL for LINE will flush any pending output. LINE may be modified by this function. */ static void print_log_line (struct wrapper_context_s *ctx, char *line) { char *s; size_t n; if (!line) { if (ctx->line && ctx->linelen) { log_info ("%s\n", ctx->line); ctx->linelen = 0; } return; } while ((s = strchr (line, '\n'))) { *s = 0; if (ctx->line && ctx->linelen) { log_info ("%s", ctx->line); ctx->linelen = 0; log_printf ("%s\n", line); } else log_info ("%s\n", line); line = s + 1; } n = strlen (line); if (n) { if (ctx->linelen + n + 1 >= ctx->linesize) { char *tmp; size_t newsize; newsize = ctx->linesize + ((n + 255) & ~255) + 1; tmp = (ctx->line ? xtryrealloc (ctx->line, newsize) : xtrymalloc (newsize)); if (!tmp) { log_error (_("error printing log line: %s\n"), strerror (errno)); return; } ctx->line = tmp; ctx->linesize = newsize; } memcpy (ctx->line + ctx->linelen, line, n); ctx->linelen += n; ctx->line[ctx->linelen] = 0; } } /* Read data from the log stream. Returns true if the log stream indicated EOF or error. */ static int read_log_data (struct wrapper_context_s *ctx) { int n; char line[256]; /* We must use the npth_read function for pipes, always. */ do n = npth_read (ctx->log_fd, line, sizeof line - 1); while (n < 0 && errno == EINTR); if (n <= 0) /* EOF or error. */ { if (n < 0) log_error (_("error reading log from ldap wrapper %d: %s\n"), (int)ctx->pid, strerror (errno)); print_log_line (ctx, NULL); SAFE_CLOSE (ctx->log_fd); return 1; } line[n] = 0; print_log_line (ctx, line); if (ctx->stamp != (time_t)(-1)) ctx->stamp = time (NULL); return 0; } /* This function is run by a separate thread to maintain the list of wrappers and to log error messages from these wrappers. */ void * ldap_wrapper_thread (void *dummy) { int nfds; struct wrapper_context_s *ctx; struct wrapper_context_s *ctx_prev; struct timespec abstime; struct timespec curtime; struct timespec timeout; fd_set fdset; int ret; time_t exptime; (void)dummy; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; for (;;) { int any_action = 0; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Inactivity is checked below. Nothing else to do. */ npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); FD_ZERO (&fdset); nfds = -1; for (ctx = wrapper_list; ctx; ctx = ctx->next) { if (ctx->log_fd != -1) { FD_SET (ctx->log_fd, &fdset); if (ctx->log_fd > nfds) nfds = ctx->log_fd; } } nfds++; /* FIXME: For Windows, we have to use a reader thread on the pipe that signals an event (and a npth_select_ev variant). */ ret = npth_pselect (nfds + 1, &fdset, NULL, NULL, &timeout, NULL); if (ret == -1) { if (errno != EINTR) { log_error (_("npth_select failed: %s - waiting 1s\n"), strerror (errno)); npth_sleep (1); } continue; } /* All timestamps before exptime should be considered expired. */ exptime = time (NULL); if (exptime > INACTIVITY_TIMEOUT) exptime -= INACTIVITY_TIMEOUT; /* Note that there is no need to lock the list because we always add entries at the head (with a pending event status) and thus traversing the list will even work if we have a context switch in waitpid (which should anyway only happen with Pth's hard system call mapping). */ for (ctx = wrapper_list; ctx; ctx = ctx->next) { /* Check whether there is any logging to be done. */ if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &fdset)) { if (read_log_data (ctx)) { SAFE_CLOSE (ctx->log_fd); any_action = 1; } } /* Check whether the process is still running. */ if (ctx->pid != (pid_t)(-1)) { gpg_error_t err; int status; err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, &status); if (!err) { log_info (_("ldap wrapper %d ready"), (int)ctx->pid); ctx->ready = 1; gnupg_release_process (ctx->pid); ctx->pid = (pid_t)(-1); any_action = 1; } else if (gpg_err_code (err) == GPG_ERR_GENERAL) { if (status == 10) log_info (_("ldap wrapper %d ready: timeout\n"), (int)ctx->pid); else log_info (_("ldap wrapper %d ready: exitcode=%d\n"), (int)ctx->pid, status); ctx->ready = 1; gnupg_release_process (ctx->pid); ctx->pid = (pid_t)(-1); any_action = 1; } else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) { log_error (_("waiting for ldap wrapper %d failed: %s\n"), (int)ctx->pid, gpg_strerror (err)); any_action = 1; } } /* Check whether we should terminate the process. */ if (ctx->pid != (pid_t)(-1) && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) { gnupg_kill_process (ctx->pid); ctx->stamp = (time_t)(-1); log_info (_("ldap wrapper %d stalled - killing\n"), (int)ctx->pid); /* We need to close the log fd because the cleanup loop waits for it. */ SAFE_CLOSE (ctx->log_fd); any_action = 1; } } /* If something has been printed to the log file or we got an EOF from a wrapper, we now print the list of active wrappers. */ if (any_action && DBG_LOOKUP) { log_info ("ldap worker stati:\n"); for (ctx = wrapper_list; ctx; ctx = ctx->next) log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n", ctx, (int)ctx->pid, (int)ctx->printable_pid, ctx->reader, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, (unsigned long)ctx->stamp, ctx->ready); } /* Use a separate loop to check whether ready marked wrappers may be removed. We may only do so if the ksba reader object is not anymore in use or we are in shutdown state. */ again: for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next) if (ctx->ready && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down)) { if (ctx_prev) ctx_prev->next = ctx->next; else wrapper_list = ctx->next; destroy_wrapper (ctx); /* We need to restart because destroy_wrapper might have done a context switch. */ goto again; } } /*NOTREACHED*/ return NULL; /* Make the compiler happy. */ } /* Start the reaper thread for the ldap wrapper. */ void ldap_wrapper_launch_thread (void) { static int done; npth_attr_t tattr; npth_t thread; int err; if (done) return; done = 1; npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, ldap_wrapper_thread, NULL); if (err) { log_error (_("error spawning ldap wrapper reaper thread: %s\n"), strerror (err) ); dirmngr_exit (1); } npth_setname_np (thread, "ldap-reaper"); npth_attr_destroy (&tattr); } /* Wait until all ldap wrappers have terminated. We assume that the kill has already been sent to all of them. */ void ldap_wrapper_wait_connections () { shutting_down = 1; /* FIXME: This is a busy wait. */ while (wrapper_list) npth_usleep (200); } /* This function is to be used to release a context associated with the given reader object. */ void ldap_wrapper_release_context (ksba_reader_t reader) { struct wrapper_context_s *ctx; if (!reader ) return; for (ctx=wrapper_list; ctx; ctx=ctx->next) if (ctx->reader == reader) { if (DBG_LOOKUP) log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", ctx, (int)ctx->pid, (int)ctx->printable_pid, ctx->reader, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); ctx->reader = NULL; SAFE_CLOSE (ctx->fd); if (ctx->ctrl) { ctx->ctrl->refcount--; ctx->ctrl = NULL; } if (ctx->fd_error) log_info (_("reading from ldap wrapper %d failed: %s\n"), ctx->printable_pid, gpg_strerror (ctx->fd_error)); break; } } /* Cleanup all resources held by the connection associated with CTRL. This is used after a cancel to kill running wrappers. */ void ldap_wrapper_connection_cleanup (ctrl_t ctrl) { struct wrapper_context_s *ctx; for (ctx=wrapper_list; ctx; ctx=ctx->next) if (ctx->ctrl && ctx->ctrl == ctrl) { ctx->ctrl->refcount--; ctx->ctrl = NULL; if (ctx->pid != (pid_t)(-1)) gnupg_kill_process (ctx->pid); if (ctx->fd_error) log_info (_("reading from ldap wrapper %d failed: %s\n"), ctx->printable_pid, gpg_strerror (ctx->fd_error)); } } /* This is the callback used by the ldap wrapper to feed the ksba reader with the wrappers stdout. See the description of ksba_reader_set_cb for details. */ static int reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) { struct wrapper_context_s *ctx = cb_value; size_t nleft = count; int nfds; struct timespec abstime; struct timespec curtime; struct timespec timeout; int saved_errno; fd_set fdset, read_fdset; int ret; /* FIXME: We might want to add some internal buffering because the ksba code does not do any buffering for itself (because a ksba reader may be detached from another stream to read other data and the it would be cumbersome to get back already buffered stuff). */ if (!buffer && !count && !nread) return -1; /* Rewind is not supported. */ /* If we ever encountered a read error, don't continue (we don't want to possibly overwrite the last error cause). Bail out also if the file descriptor has been closed. */ if (ctx->fd_error || ctx->fd == -1) { *nread = 0; return -1; } FD_ZERO (&fdset); FD_SET (ctx->fd, &fdset); nfds = ctx->fd + 1; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; while (nleft > 0) { int n; gpg_error_t err; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { err = dirmngr_tick (ctx->ctrl); if (err) { ctx->fd_error = err; SAFE_CLOSE (ctx->fd); return -1; } npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); read_fdset = fdset; ret = npth_pselect (nfds, &read_fdset, NULL, NULL, &timeout, NULL); saved_errno = errno; if (ret == -1 && saved_errno != EINTR) { ctx->fd_error = gpg_error_from_errno (errno); SAFE_CLOSE (ctx->fd); return -1; } if (ret <= 0) /* Timeout. Will be handled when calculating the next timeout. */ continue; /* This should not block now that select returned with a file descriptor. So it shouldn't be necessary to use npth_read (and it is slightly dangerous in the sense that a concurrent thread might (accidentially?) change the status of ctx->fd before we read. FIXME: Set ctx->fd to nonblocking? */ n = read (ctx->fd, buffer, nleft); if (n < 0) { ctx->fd_error = gpg_error_from_errno (errno); SAFE_CLOSE (ctx->fd); return -1; } else if (!n) { if (nleft == count) return -1; /* EOF. */ break; } nleft -= n; buffer += n; if (n > 0 && ctx->stamp != (time_t)(-1)) ctx->stamp = time (NULL); } *nread = count - nleft; return 0; } /* Fork and exec the LDAP wrapper and return a new libksba reader object at READER. ARGV is a NULL terminated list of arguments for the wrapper. The function returns 0 on success or an error code. Special hack to avoid passing a password through the command line which is globally visible: If the first element of ARGV is "--pass" it will be removed and instead the environment variable DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern OSes the environment is not visible to other users. For those old systems where it can't be avoided, we don't want to go into the hassle of passing the password via stdin; it's just too complicated and an LDAP password used for public directory lookups should not be that confidential. */ gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) { gpg_error_t err; pid_t pid; struct wrapper_context_s *ctx; int i; int j; const char **arg_list; const char *pgmname; int outpipe[2], errpipe[2]; /* It would be too simple to connect stderr just to our logging stream. The problem is that if we are running multi-threaded everything gets intermixed. Clearly we don't want this. So the only viable solutions are either to have another thread responsible for logging the messages or to add an option to the wrapper module to do the logging on its own. Given that we anyway need a way to reap the child process and this is best done using a general reaping thread, that thread can do the logging too. */ ldap_wrapper_launch_thread (); *reader = NULL; /* Files: We need to prepare stdin and stdout. We get stderr from the function. */ if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); else pgmname = opt.ldap_wrapper_program; /* Create command line argument array. */ for (i = 0; argv[i]; i++) ; arg_list = xtrycalloc (i + 2, sizeof *arg_list); if (!arg_list) { err = gpg_error_from_syserror (); log_error (_("error allocating memory: %s\n"), strerror (errno)); return err; } for (i = j = 0; argv[i]; i++, j++) if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) { arg_list[j] = "--env-pass"; setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); i++; } else arg_list[j] = (char*) argv[i]; ctx = xtrycalloc (1, sizeof *ctx); if (!ctx) { err = gpg_error_from_syserror (); log_error (_("error allocating memory: %s\n"), strerror (errno)); xfree (arg_list); return err; } err = gnupg_create_inbound_pipe (outpipe, NULL, 0); if (!err) { err = gnupg_create_inbound_pipe (errpipe, NULL, 0); if (err) { close (outpipe[0]); close (outpipe[1]); } } if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); xfree (arg_list); xfree (ctx); return err; } err = gnupg_spawn_process_fd (pgmname, arg_list, -1, outpipe[1], errpipe[1], &pid); xfree (arg_list); close (outpipe[1]); close (errpipe[1]); if (err) { close (outpipe[0]); close (errpipe[0]); xfree (ctx); return err; } ctx->pid = pid; ctx->printable_pid = (int) pid; ctx->fd = outpipe[0]; ctx->log_fd = errpipe[0]; ctx->ctrl = ctrl; ctrl->refcount++; ctx->stamp = time (NULL); err = ksba_reader_new (reader); if (!err) err = ksba_reader_set_cb (*reader, reader_callback, ctx); if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); destroy_wrapper (ctx); ksba_reader_release (*reader); *reader = NULL; return err; } /* Hook the context into our list of running wrappers. */ ctx->reader = *reader; ctx->next = wrapper_list; wrapper_list = ctx; if (opt.verbose) log_info ("ldap wrapper %d started (reader %p)\n", (int)ctx->pid, ctx->reader); /* Need to wait for the first byte so we are able to detect an empty output and not let the consumer see an EOF without further error indications. The CRL loading logic assumes that after return from this function, a failed search (e.g. host not found ) is indicated right away. */ { unsigned char c; err = read_buffer (*reader, &c, 1); if (err) { ldap_wrapper_release_context (*reader); ksba_reader_release (*reader); *reader = NULL; if (gpg_err_code (err) == GPG_ERR_EOF) return gpg_error (GPG_ERR_NO_DATA); else return err; } ksba_reader_unread (*reader, &c, 1); } return 0; } diff --git a/dirmngr/loadswdb.c b/dirmngr/loadswdb.c index 5a7778ddd..7791f68ee 100644 --- a/dirmngr/loadswdb.c +++ b/dirmngr/loadswdb.c @@ -1,404 +1,404 @@ /* loadswdb.c - Load the swdb file from versions.gnupg.org * Copyright (C) 2016 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 . */ #include #include #include #include #include "dirmngr.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "misc.h" #include "ks-engine.h" /* Get the time from the current swdb file and store it at R_FILEDATE * and R_VERIFIED. If the file does not exist 0 is stored at there. - * The function returns 0 on sucess or an error code. */ + * The function returns 0 on success or an error code. */ static gpg_error_t time_of_saved_swdb (const char *fname, time_t *r_filedate, time_t *r_verified) { gpg_error_t err; estream_t fp = NULL; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char *fields[2]; gnupg_isotime_t isot; time_t filedate = (time_t)(-1); time_t verified = (time_t)(-1); *r_filedate = 0; *r_verified = 0; fp = es_fopen (fname, "r"); err = fp? 0 : gpg_error_from_syserror (); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) err = 0; /* No file - assume time is the year of Unix. */ goto leave; } - /* Note that the parser uses the first occurance of a matching + /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (filedate == (time_t)(-1) && !strcmp (fields[0], ".filedate")) { if (string2isotime (isot, fields[1])) filedate = isotime2epoch (isot); } else if (verified == (time_t)(-1) && !strcmp (fields[0], ".verified")) { if (string2isotime (isot, fields[1])) verified = isotime2epoch (isot); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); goto leave; } if (filedate == (time_t)(-1) || verified == (time_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } *r_filedate = filedate; *r_verified = verified; leave: if (err) log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (line); es_fclose (fp); return err; } /* Read a file from URL and return it as an estream memory buffer at * R_FP. */ static gpg_error_t fetch_file (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; estream_t fp = NULL; estream_t httpfp = NULL; size_t nread, nwritten; char buffer[1024]; if ((err = ks_http_fetch (ctrl, url, &httpfp))) goto leave; /* We now read the data from the web server into a memory buffer. * To avoid excessive memory use in case of a ill behaving server we * put a 64 k size limit on the buffer. As of today the actual size * of the swdb.lst file is 3k. */ fp = es_fopenmem (64*1024, "rw"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } for (;;) { if (es_read (httpfp, buffer, sizeof buffer, &nread)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", es_fname_get (httpfp), gpg_strerror (err)); goto leave; } if (!nread) break; /* Ready. */ if (es_write (fp, buffer, nread, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (fp), gpg_strerror (err)); goto leave; } else if (nread != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing '%s': %s\n", es_fname_get (fp), "short write"); goto leave; } } es_rewind (fp); *r_fp = fp; fp = NULL; leave: es_fclose (httpfp); es_fclose (fp); return err; } /* Communication object for verify_status_cb. */ struct verify_status_parm_s { time_t sigtime; int anyvalid; }; static void verify_status_cb (void *opaque, const char *keyword, char *args) { struct verify_status_parm_s *parm = opaque; if (DBG_EXTPROG) log_debug ("gpgv status: %s %s\n", keyword, args); /* We care only about the first valid signature. */ if (!strcmp (keyword, "VALIDSIG") && !parm->anyvalid) { char *fields[3]; parm->anyvalid = 1; if (split_fields (args, fields, DIM (fields)) >= 3) parm->sigtime = parse_timestamp (fields[2], NULL); } } /* Load the swdb file into the current home directory. Do this onlky * when needed unless FORCE is set which will always get a new * copy. */ gpg_error_t dirmngr_load_swdb (ctrl_t ctrl, int force) { gpg_error_t err; char *fname = NULL; /* The swdb.lst file. */ char *tmp_fname = NULL; /* The temporary swdb.lst file. */ char *keyfile_fname = NULL; estream_t swdb = NULL; estream_t swdb_sig = NULL; ccparray_t ccp; const char **argv = NULL; struct verify_status_parm_s verify_status_parm = { (time_t)(-1), 0 }; estream_t outfp = NULL; time_t now = gnupg_get_time (); time_t filedate = 0; /* ".filedate" from our swdb. */ time_t verified = 0; /* ".verified" from our swdb. */ gnupg_isotime_t isotime; fname = make_filename_try (gnupg_homedir (), "swdb.lst", NULL); if (!fname) { err = gpg_error_from_syserror (); goto leave; } /* Check whether there is a need to get an update. */ if (!force) { static int not_older_than; static time_t lastcheck; if (!not_older_than) { /* To balance access to the server we use a random time from * 5 to 7 days for update checks. */ not_older_than = 5 * 86400; not_older_than += (get_uint_nonce () % (2*86400)); } if (now - lastcheck < 3600) { /* We checked our swdb file in the last hour - don't check * again to avoid unnecessary disk access. */ err = 0; goto leave; } lastcheck = now; err = time_of_saved_swdb (fname, &filedate, &verified); if (gpg_err_code (err) == GPG_ERR_INV_TIME) err = 0; /* Force reading. */ if (err) goto leave; if (filedate >= now) goto leave; /* Current or newer. */ if (now - filedate < not_older_than) goto leave; /* Our copy is pretty new (not older than 7 days). */ if (verified > now && now - verified < 3*3600) goto leave; /* We downloaded and verified in the last 3 hours. */ } /* Create the filename of the file with the keys. */ keyfile_fname = make_filename_try (gnupg_datadir (), "distsigkey.gpg", NULL); if (!keyfile_fname) { err = gpg_error_from_syserror (); goto leave; } /* Fetch the swdb from the web. */ err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst", &swdb); if (err) goto leave; err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst.sig", &swdb_sig); if (err) goto leave; /* Run gpgv. */ ccparray_init (&ccp, 0); ccparray_put (&ccp, "--enable-special-filenames"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--keyring"); ccparray_put (&ccp, keyfile_fname); ccparray_put (&ccp, "--"); ccparray_put (&ccp, "-&@INEXTRA@"); ccparray_put (&ccp, "-"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } if (DBG_EXTPROG) log_debug ("starting gpgv\n"); err = gnupg_exec_tool_stream (gnupg_module_name (GNUPG_MODULE_NAME_GPGV), argv, swdb, swdb_sig, NULL, verify_status_cb, &verify_status_parm); if (!err && verify_status_parm.sigtime == (time_t)(-1)) err = gpg_error (verify_status_parm.anyvalid? GPG_ERR_BAD_SIGNATURE /**/ : GPG_ERR_INV_TIME ); if (DBG_EXTPROG) log_debug ("gpgv finished: err=%d\n", err); if (err) goto leave; /* If our swdb is not older than the downloaded one. We don't * bother to update. */ if (!force && filedate >= verify_status_parm.sigtime) goto leave; /* Create a file name for a temporary file in the home directory. * We will later rename that file to the real name. */ { char *tmpstr; #ifdef HAVE_W32_SYSTEM tmpstr = es_bsprintf ("tmp-%u-swdb", (unsigned int)getpid ()); #else tmpstr = es_bsprintf (".#%u.swdb", (unsigned int)getpid ()); #endif if (!tmpstr) { err = gpg_error_from_syserror (); goto leave; } tmp_fname = make_filename_try (gnupg_homedir (), tmpstr, NULL); xfree (tmpstr); if (!tmp_fname) { err = gpg_error_from_syserror (); goto leave; } } outfp = es_fopen (tmp_fname, "w"); if (!outfp) { err = gpg_error_from_syserror (); log_error (_("error creating '%s': %s\n"), tmp_fname, gpg_strerror (err)); goto leave; } epoch2isotime (isotime, verify_status_parm.sigtime); es_fprintf (outfp, ".filedate %s\n", isotime); epoch2isotime (isotime, now); es_fprintf (outfp, ".verified %s\n", isotime); if (es_fseek (swdb, 0, SEEK_SET)) { err = gpg_error_from_syserror (); goto leave; } err = copy_stream (swdb, outfp); if (err) { /* Well, it might also be a reading error, but that is pretty * unlikely for a memory stream. */ log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err)); goto leave; } if (es_fclose (outfp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err)); goto leave; } outfp = NULL; err = gnupg_rename_file (tmp_fname, fname, NULL); if (err) goto leave; xfree (tmp_fname); tmp_fname = NULL; leave: es_fclose (outfp); if (tmp_fname) gnupg_remove (tmp_fname); /* This is a temporary file. */ xfree (argv); es_fclose (swdb_sig); es_fclose (swdb); xfree (keyfile_fname); xfree (tmp_fname); xfree (fname); return err; } diff --git a/dirmngr/server.c b/dirmngr/server.c index f4aeadbc4..237cb5278 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,2802 +1,2802 @@ /* server.c - LDAP and Keyserver access server * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH * Copyright (C) 2014, 2015, 2016 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 . */ #include #include #include #include #include #include #include #include #include #include #include "dirmngr.h" #include #include "crlcache.h" #include "crlfetch.h" #if USE_LDAP # include "ldapserver.h" #endif #include "ocsp.h" #include "certcache.h" #include "validate.h" #include "misc.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "ks-action.h" #include "ks-engine.h" /* (ks_hkp_print_hosttable) */ #if USE_LDAP # include "ldap-parse-uri.h" #endif #include "dns-stuff.h" #include "../common/mbox-util.h" #include "../common/zb32.h" #include "../common/server-help.h" /* To avoid DoS attacks we limit the size of a certificate to something reasonable. The DoS was actually only an issue back when Dirmngr was a system service and not a user service. */ #define MAX_CERT_LENGTH (16*1024) /* The limit for the CERTLIST inquiry. We allow for up to 20 * certificates but also take PEM encoding into account. */ #define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3) /* The same goes for OpenPGP keyblocks, but here we need to allow for much longer blocks; a 200k keyblock is not too unusual for keys with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even has 770 KiB as of 2015-08-23. To avoid adding a runtime option we now use 20MiB which should really be enough. Well, a key with several pictures could be larger (the parser as a 18MiB limit for attribute packets) but it won't be nice to the keyservers to send them such large blobs. */ #define MAX_KEYBLOCK_LENGTH (20*1024*1024) #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* Per-session LDAP servers. */ ldap_server_t ldapservers; /* Per-session list of keyservers. */ uri_item_t keyservers; /* If this flag is set to true this dirmngr process will be terminated after the end of this session. */ int stopme; /* State variable private to is_tor_running. */ int tor_state; /* If the first both flags are set the assuan logging of data lines * is suppressed. The count variable is used to show the number of * non-logged bytes. */ size_t inhibit_data_logging_count; unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; /* Accessor for the local ldapservers variable. */ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl) { if (ctrl && ctrl->server_local) return ctrl->server_local->ldapservers; else return NULL; } /* Release an uri_item_t list. */ static void release_uri_item_list (uri_item_t list) { while (list) { uri_item_t tmp = list->next; http_release_parsed_uri (list->parsed_uri); xfree (list); list = tmp; } } /* Release all configured keyserver info from CTRL. */ void release_ctrl_keyservers (ctrl_t ctrl) { if (! ctrl->server_local) return; release_uri_item_list (ctrl->server_local->keyservers); ctrl->server_local->keyservers = NULL; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* This is a wrapper around assuan_send_data which makes debugging the output in verbose mode easier. */ static gpg_error_t data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size) { ctrl_t ctrl = assuan_get_pointer (ctx); const char *buffer = buffer_arg; gpg_error_t err; /* If we do not want logging, enable it here. */ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) ctrl->server_local->inhibit_data_logging_now = 1; if (opt.verbose && buffer && size) { /* Ease reading of output by sending a physical line at each LF. */ const char *p; size_t n, nbytes; nbytes = size; do { p = memchr (buffer, '\n', nbytes); n = p ? (p - buffer) + 1 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ goto leave; } } leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count += size; } return err; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (data_line_write (ctx, buffer, size)) return -1; return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (DBG_IPC) { ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging && ctrl->server_local->inhibit_data_logging_count) log_debug ("(%zu bytes sent via D lines not shown)\n", ctrl->server_local->inhibit_data_logging_count); } if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } /* Copy the % and + escaped string S into the buffer D and replace the escape sequences. Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1. Note further that if S contains an escaped binary Nul the resulting string D will contain the 0 as well as all other characters but it will be impossible to know whether this is the original EOS or a copied Nul. */ static void strcpy_escaped_plus (char *d, const unsigned char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* This function returns true if a Tor server is running. The sattus is cached for the current connection. */ static int is_tor_running (ctrl_t ctrl) { /* Check whether we can connect to the proxy. */ if (!ctrl || !ctrl->server_local) return 0; /* Ooops. */ if (!ctrl->server_local->tor_state) { assuan_fd_t sock; sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); if (sock == ASSUAN_INVALID_FD) ctrl->server_local->tor_state = -1; /* Not running. */ else { assuan_sock_close (sock); ctrl->server_local->tor_state = 1; /* Running. */ } } return (ctrl->server_local->tor_state > 0); } /* Return an error if the assuan context does not belong to the owner of the process or to root. On error FAILTEXT is set as Assuan error string. */ static gpg_error_t check_owner_permission (assuan_context_t ctx, const char *failtext) { #ifdef HAVE_W32_SYSTEM /* Under Windows the dirmngr is always run under the control of the user. */ (void)ctx; (void)failtext; #else gpg_err_code_t ec; assuan_peercred_t cred; ec = gpg_err_code (assuan_get_peercred (ctx, &cred)); if (!ec && cred->uid && cred->uid != getuid ()) ec = GPG_ERR_EPERM; if (ec) return set_error (ec, failtext); #endif return 0; } /* Common code for get_cert_local and get_issuer_cert_local. */ static ksba_cert_t do_get_cert_local (ctrl_t ctrl, const char *name, const char *command) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command); if (!buf) rc = gpg_error_from_syserror (); else { rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); } if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), command, gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask back to return a certificate for NAME, given as a regular gpgsm * certificate identifier (e.g. fingerprint or one of the other * methods). Alternatively, NULL may be used for NAME to return the * current target certificate. Either return the certificate in a * KSBA object or NULL if it is not available. */ ksba_cert_t get_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDCERT"); } /* Ask back to return the issuing certificate for NAME, given as a * regular gpgsm certificate identifier (e.g. fingerprint or one * of the other methods). Alternatively, NULL may be used for NAME to * return the current target certificate. Either return the certificate * in a KSBA object or NULL if it is not available. */ ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_issuing_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDISSUERCERT"); } /* Ask back to return a certificate with subject NAME and a * subjectKeyIdentifier of KEYID. */ ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; char *hexkeyid; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local_ski called w/o context\n"); return NULL; } if (!name || !keyid) { log_debug ("get_cert_local_ski called with insufficient arguments\n"); return NULL; } hexkeyid = serial_hex (keyid); if (!hexkeyid) { log_debug ("serial_hex() failed\n"); return NULL; } buf = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexkeyid); return NULL; } xfree (hexkeyid); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI", gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask the client via an inquiry to check the istrusted status of the certificate specified by the hexified fingerprint HEXFPR. Returns 0 if the certificate is trusted by the client or an error code. */ gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr) { unsigned char *value; size_t valuelen; int rc; char request[100]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx || !hexfpr) return gpg_error (GPG_ERR_INV_ARG); snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr); rc = assuan_inquire (ctrl->server_local->assuan_ctx, request, &value, &valuelen, 100); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), request, gpg_strerror (rc)); return rc; } /* The expected data is: "1" or "1 cruft" (not a C-string). */ if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1))) rc = 0; else rc = gpg_error (GPG_ERR_NOT_TRUSTED); xfree (value); return rc; } /* Ask the client to return the certificate associated with the current command. This is sometimes needed because the client usually sends us just the cert ID, assuming that the request can be satisfied from the cache, where the cert ID is used as key. */ static int inquire_cert_and_load_crl (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; ksba_cert_t cert = NULL; err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0); if (err) return err; /* { */ /* FILE *fp = fopen ("foo.der", "r"); */ /* value = xmalloc (2000); */ /* valuelen = fread (value, 1, 2000, fp); */ /* fclose (fp); */ /* } */ if (!valuelen) /* No data returned; return a comprehensible error. */ return gpg_error (GPG_ERR_MISSING_CERT); err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, value, valuelen); if(err) goto leave; xfree (value); value = NULL; err = crl_cache_reload_crl (ctrl, cert); leave: ksba_cert_release (cert); xfree (value); return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "force-crl-refresh")) { int i = *value? atoi (value) : 0; ctrl->force_crl_refresh = i; } else if (!strcmp (key, "audit-events")) { int i = *value? atoi (value) : 0; ctrl->audit_events = i; } else if (!strcmp (key, "http-proxy")) { xfree (ctrl->http_proxy); if (!*value || !strcmp (value, "none")) ctrl->http_proxy = NULL; else if (!(ctrl->http_proxy = xtrystrdup (value))) err = gpg_error_from_syserror (); } else if (!strcmp (key, "honor-keyserver-url-used")) { /* Return an error if we are running in Tor mode. */ if (dirmngr_use_tor ()) err = gpg_error (GPG_ERR_FORBIDDEN); } else if (!strcmp (key, "http-crl")) { int i = *value? atoi (value) : 0; ctrl->http_no_crl = !i; } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_dns_cert[] = "DNS_CERT \n" "DNS_CERT --pka \n" "DNS_CERT --dane \n" "\n" "Return the CERT record for . is one of\n" " * Return the first record of any supported subtype\n" " PGP Return the first record of subtype PGP (3)\n" " IPGP Return the first record of subtype IPGP (6)\n" "If the content of a certificate is available (PGP) it is returned\n" "by data lines. Fingerprints and URLs are returned via status lines.\n" "In --pka mode the fingerprint and if available an URL is returned.\n" "In --dane mode the key is returned from RR type 61"; static gpg_error_t cmd_dns_cert (assuan_context_t ctx, char *line) { /* ctrl_t ctrl = assuan_get_pointer (ctx); */ gpg_error_t err = 0; int pka_mode, dane_mode; char *mbox = NULL; char *namebuf = NULL; char *encodedhash = NULL; const char *name; int certtype; char *p; void *key = NULL; size_t keylen; unsigned char *fpr = NULL; size_t fprlen; char *url = NULL; pka_mode = has_option (line, "--pka"); dane_mode = has_option (line, "--dane"); line = skip_options (line); if (pka_mode && dane_mode) { err = PARM_ERROR ("either --pka or --dane may be given"); goto leave; } if (pka_mode || dane_mode) ; /* No need to parse here - we do this later. */ else { p = strchr (line, ' '); if (!p) { err = PARM_ERROR ("missing arguments"); goto leave; } *p++ = 0; if (!strcmp (line, "*")) certtype = DNS_CERTTYPE_ANY; else if (!strcmp (line, "IPGP")) certtype = DNS_CERTTYPE_IPGP; else if (!strcmp (line, "PGP")) certtype = DNS_CERTTYPE_PGP; else { err = PARM_ERROR ("unknown subtype"); goto leave; } while (spacep (p)) p++; line = p; if (!*line) { err = PARM_ERROR ("name missing"); goto leave; } } if (pka_mode || dane_mode) { char *domain; /* Points to mbox. */ char hashbuf[32]; /* For SHA-1 and SHA-256. */ /* We lowercase ascii characters but the DANE I-D does not allow this. FIXME: Check after the release of the RFC whether to change this. */ mbox = mailbox_from_userid (line); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; if (pka_mode) { gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); encodedhash = zb32_encode (hashbuf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } namebuf = strconcat (encodedhash, "._pka.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_IPGP; } else { /* Note: The hash is truncated to 28 bytes and we lowercase the result only for aesthetic reasons. */ gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); encodedhash = bin2hex (hashbuf, 28, NULL); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } ascii_strlwr (encodedhash); namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_RR61; } } else name = line; err = get_dns_cert (name, certtype, &key, &keylen, &fpr, &fprlen, &url); if (err) goto leave; if (key) { err = data_line_write (ctx, key, keylen); if (err) goto leave; } if (fpr) { char *tmpstr; tmpstr = bin2hex (fpr, fprlen, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else { err = assuan_write_status (ctx, "FPR", tmpstr); xfree (tmpstr); } if (err) goto leave; } if (url) { err = assuan_write_status (ctx, "URL", url); if (err) goto leave; } leave: xfree (key); xfree (fpr); xfree (url); xfree (mbox); xfree (namebuf); xfree (encodedhash); return leave_cmd (ctx, err); } static const char hlp_wkd_get[] = "WKD_GET [--submission-address|--policy-flags] \n" "\n" "Return the key or other info for \n" "from the Web Key Directory."; static gpg_error_t cmd_wkd_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; char *mbox = NULL; char *domainbuf = NULL; char *domain; /* Points to mbox or domainbuf. */ char sha1buf[20]; char *uri = NULL; char *encodedhash = NULL; int opt_submission_addr; int opt_policy_flags; int no_log = 0; char portstr[20] = { 0 }; opt_submission_addr = has_option (line, "--submission-address"); opt_policy_flags = has_option (line, "--policy-flags"); line = skip_options (line); mbox = mailbox_from_userid (line); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; /* Check for SRV records. */ if (1) { struct srventry *srvs; unsigned int srvscount; size_t domainlen, targetlen; int i; err = get_dns_srv (domain, "openpgpkey", NULL, &srvs, &srvscount); if (err) goto leave; /* Find the first target which also ends in DOMAIN or is equal * to DOMAIN. */ domainlen = strlen (domain); for (i = 0; i < srvscount; i++) { log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port); targetlen = strlen (srvs[i].target); if ((targetlen > domainlen + 1 && srvs[i].target[targetlen - domainlen - 1] == '.' && !ascii_strcasecmp (srvs[i].target + targetlen - domainlen, domain)) || (targetlen == domainlen && !ascii_strcasecmp (srvs[i].target, domain))) { /* found. */ domainbuf = xtrystrdup (srvs[i].target); if (!domainbuf) { err = gpg_error_from_syserror (); xfree (srvs); goto leave; } domain = domainbuf; if (srvs[i].port) snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port); break; } } xfree (srvs); log_debug ("srv: got '%s%s'\n", domain, portstr); } gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); encodedhash = zb32_encode (sha1buf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } if (opt_submission_addr) { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/submission-address", NULL); } else if (opt_policy_flags) { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/policy", NULL); } else { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/hu/", encodedhash, NULL); no_log = 1; } if (!uri) { err = gpg_error_from_syserror (); goto leave; } /* Setup an output stream and perform the get. */ { estream_t outfp; outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { if (no_log) ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_fetch (ctrl, uri, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } } leave: xfree (uri); xfree (encodedhash); xfree (mbox); xfree (domainbuf); return leave_cmd (ctx, err); } static const char hlp_ldapserver[] = "LDAPSERVER \n" "\n" "Add a new LDAP server to the list of configured LDAP servers.\n" "DATA is in the same format as expected in the configure file."; static gpg_error_t cmd_ldapserver (assuan_context_t ctx, char *line) { #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); ldap_server_t server; ldap_server_t *last_next_p; while (spacep (line)) line++; if (*line == '\0') return leave_cmd (ctx, PARM_ERROR (_("ldapserver missing"))); server = ldapserver_parse_one (line, "", 0); if (! server) return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG)); last_next_p = &ctrl->server_local->ldapservers; while (*last_next_p) last_next_p = &(*last_next_p)->next; *last_next_p = server; return leave_cmd (ctx, 0); #else (void)line; return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED)); #endif } static const char hlp_isvalid[] = "ISVALID [--only-ocsp] [--force-default-responder]" " |\n" "\n" "This command checks whether the certificate identified by the\n" "certificate_id is valid. This is done by consulting CRLs or\n" "whatever has been configured. Note, that the returned error codes\n" "are from gpg-error.h. The command may callback using the inquire\n" "function. See the manual for details.\n" "\n" "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n" "delimited by a single dot. The first part is the SHA-1 hash of the\n" "issuer name and the second part the serial number.\n" "\n" "Alternatively the certificate's fingerprint may be given in which\n" "case an OCSP request is done before consulting the CRL.\n" "\n" "If the option --only-ocsp is given, no fallback to a CRL check will\n" "be used.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used."; static gpg_error_t cmd_isvalid (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *issuerhash, *serialno; gpg_error_t err; int did_inquire = 0; int ocsp_mode = 0; int only_ocsp; int force_default_responder; only_ocsp = has_option (line, "--only-ocsp"); force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); issuerhash = xstrdup (line); /* We need to work on a copy of the line because that same Assuan context may be used for an inquiry. That is because Assuan reuses its line buffer. */ serialno = strchr (issuerhash, '.'); if (serialno) *serialno++ = 0; else { char *endp = strchr (issuerhash, ' '); if (endp) *endp = 0; if (strlen (issuerhash) != 40) { xfree (issuerhash); return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID"))); } ocsp_mode = 1; } again: if (ocsp_mode) { /* Note, that we ignore the given issuer hash and instead rely on the current certificate semantics used with this command. */ if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder); /* Fixme: If we got no ocsp response and --only-ocsp is not used we should fall back to CRL mode. Thus we need to clear OCSP_MODE, get the issuerhash and the serialno from the current certificate and jump to again. */ } else if (only_ocsp) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else { switch (crl_cache_isvalid (ctrl, issuerhash, serialno, ctrl->force_crl_refresh)) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: if (did_inquire) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (!(err = inquire_cert_and_load_crl (ctx))) { did_inquire = 1; goto again; } break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("crl_cache_isvalid returned invalid code\n"); } } xfree (issuerhash); return leave_cmd (ctx, err); } /* If the line contains a SHA-1 fingerprint as the first argument, return the FPR vuffer on success. The function checks that the fingerprint consists of valid characters and prints and error message if it does not and returns NULL. Fingerprints are considered optional and thus no explicit error is returned. NULL is also returned if there is no fingerprint at all available. FPR must be a caller provided buffer of at least 20 bytes. Note that colons within the fingerprint are allowed to separate 2 hex digits; this allows for easier cutting and pasting using the usual fingerprint rendering. */ static unsigned char * get_fingerprint_from_line (const char *line, unsigned char *fpr) { const char *s; int i; for (s=line, i=0; *s && *s != ' '; s++ ) { if ( hexdigitp (s) && hexdigitp (s+1) ) { if ( i >= 20 ) return NULL; /* Fingerprint too long. */ fpr[i++] = xtoi_2 (s); s++; } else if ( *s != ':' ) return NULL; /* Invalid. */ } if ( i != 20 ) return NULL; /* Fingerprint to short. */ return fpr; } static const char hlp_checkcrl[] = "CHECKCRL []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by consulting the\n" "CRL responsible for this certificate. If the fingerprint has not\n" "been given or the certificate is not known, the function \n" "inquires the certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificate by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, cert); if (!err) err = crl_cache_cert_isvalid (ctrl, cert, 0); } leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_checkocsp[] = "CHECKOCSP [--force-default-responder] []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by asking an OCSP\n" "responder responsible for this certificate. The optional\n" "fingerprint may be used for a quick check in case an OCSP check has\n" "been done for this certificate recently (we always cache OCSP\n" "responses for a couple of minutes). If the fingerprint has not been\n" "given or there is no cached result, the function inquires the\n" "certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificates by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkocsp (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; int force_default_responder; force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static int lookup_cert_by_url (assuan_context_t ctx, const char *url) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char *value = NULL; size_t valuelen; /* Fetch single certificate given it's URL. */ err = fetch_cert_by_url (ctrl, url, &value, &valuelen); if (err) { log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the data, flush the buffer and then send an END. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); goto leave; } leave: return err; } /* Send the certificate, flush the buffer and then send an END. */ static gpg_error_t return_one_cert (void *opaque, ksba_cert_t cert) { assuan_context_t ctx = opaque; gpg_error_t err; const unsigned char *der; size_t derlen; der = ksba_cert_get_image (cert, &derlen); if (!der) err = gpg_error (GPG_ERR_INV_CERT_OBJ); else { err = assuan_send_data (ctx, der, derlen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); } if (err) log_error (_("error sending data: %s\n"), gpg_strerror (err)); return err; } /* Lookup certificates from the internal cache or using the ldap servers. */ static int lookup_cert_by_pattern (assuan_context_t ctx, char *line, int single, int cache_only) { gpg_error_t err = 0; char *p; strlist_t sl, list = NULL; int truncated = 0, truncation_forced = 0; int count = 0; int local_count = 0; #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value = NULL; size_t valuelen; struct ldapserver_iter ldapserver_iter; cert_fetch_context_t fetch_context; #endif /*USE_LDAP*/ int any_no_data = 0; /* Break the line down into an STRLIST */ for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_errno (errno); goto leave; } memset (sl, 0, sizeof *sl); strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } - /* First look through the internal cache. The certifcates returned + /* First look through the internal cache. The certificates returned here are not counted towards the truncation limit. */ if (single && !cache_only) ; /* Do not read from the local cache in this case. */ else { for (sl=list; sl; sl = sl->next) { err = get_certs_bypattern (sl->d, return_one_cert, ctx); if (!err) local_count++; if (!err && single) goto ready; if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (cache_only) any_no_data = 1; } else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only) { /* No real fault because the internal pattern lookup can't yet cope with all types of pattern. */ err = 0; } if (err) goto ready; } } /* Loop over all configured servers unless we want only the certificates from the cache. */ #if USE_LDAP for (ldapserver_iter_begin (&ldapserver_iter, ctrl); !cache_only && !ldapserver_iter_end_p (&ldapserver_iter) && ldapserver_iter.server->host && !truncation_forced; ldapserver_iter_next (&ldapserver_iter)) { ldap_server_t ldapserver = ldapserver_iter.server; if (DBG_LOOKUP) log_debug ("cmd_lookup: trying %s:%d base=%s\n", ldapserver->host, ldapserver->port, ldapserver->base?ldapserver->base : "[default]"); /* Fetch certificates matching pattern */ err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver); if ( gpg_err_code (err) == GPG_ERR_NO_DATA ) { if (DBG_LOOKUP) log_debug ("cmd_lookup: no data\n"); err = 0; any_no_data = 1; continue; } if (err) { log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err)); goto leave; } /* Fetch the certificates for this query. */ while (!truncation_forced) { xfree (value); value = NULL; err = fetch_next_cert (fetch_context, &value, &valuelen); if (gpg_err_code (err) == GPG_ERR_NO_DATA ) { err = 0; any_no_data = 1; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_TRUNCATED) { truncated = 1; err = 0; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; /* Ready. */ } if (!err && !value) { err = gpg_error (GPG_ERR_BUG); goto leave; } if (err) { log_error (_("fetch_next_cert failed: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (DBG_LOOKUP) log_debug ("cmd_lookup: returning one cert%s\n", truncated? " (truncated)":""); /* Send the data, flush the buffer and then send an END line as a certificate delimiter. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (++count >= opt.max_replies ) { truncation_forced = 1; log_info (_("max_replies %d exceeded\n"), opt.max_replies ); } if (single) break; } end_cert_fetch (fetch_context); } #endif /*USE_LDAP*/ ready: if (truncated || truncation_forced) { char str[50]; sprintf (str, "%d", count); assuan_write_status (ctx, "TRUNCATED", str); } if (!err && !count && !local_count && any_no_data) err = gpg_error (GPG_ERR_NO_DATA); leave: free_strlist (list); return err; } static const char hlp_lookup[] = "LOOKUP [--url] [--single] [--cache-only] \n" "\n" "Lookup certificates matching PATTERN. With --url the pattern is\n" "expected to be one URL.\n" "\n" "If --url is not given: To allow for multiple patterns (which are ORed)\n" "quoting is required: Spaces are translated to \"+\" or \"%20\";\n" "obviously this requires that the usual escape quoting rules are applied.\n" "\n" "If --url is given no special escaping is required because URLs are\n" "already escaped this way.\n" "\n" "If --single is given the first and only the first match will be\n" "returned. If --cache-only is _not_ given, no local query will be\n" "done.\n" "\n" "If --cache-only is given no external lookup is done so that only\n" "certificates from the cache may get returned."; static gpg_error_t cmd_lookup (assuan_context_t ctx, char *line) { gpg_error_t err; int lookup_url, single, cache_only; lookup_url = has_leading_option (line, "--url"); single = has_leading_option (line, "--single"); cache_only = has_leading_option (line, "--cache-only"); line = skip_options (line); if (lookup_url && cache_only) err = gpg_error (GPG_ERR_NOT_FOUND); else if (lookup_url && single) err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); else if (lookup_url) err = lookup_cert_by_url (ctx, line); else err = lookup_cert_by_pattern (ctx, line, single, cache_only); return leave_cmd (ctx, err); } static const char hlp_loadcrl[] = "LOADCRL [--url] \n" "\n" "Load the CRL in the file with name FILENAME into our cache. Note\n" "that FILENAME should be given with an absolute path because\n" "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n" "from the given URL.\n" "\n" "This command is usually used by gpgsm using the invocation \"gpgsm\n" "--call-dirmngr loadcrl \". A direct invocation of Dirmngr\n" "is not useful because gpgsm might need to callback gpgsm to ask for\n" "the CA's certificate."; static gpg_error_t cmd_loadcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int use_url = has_leading_option (line, "--url"); line = skip_options (line); if (use_url) { ksba_reader_t reader; err = crl_fetch (ctrl, line, &reader); if (err) log_error (_("fetching CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); else { err = crl_cache_insert (ctrl, line, reader); if (err) log_error (_("processing CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); crl_close_reader (reader); } } else { char *buf; buf = xtrymalloc (strlen (line)+1); if (!buf) err = gpg_error_from_syserror (); else { strcpy_escaped_plus (buf, line); err = crl_cache_load (ctrl, buf); xfree (buf); } } return leave_cmd (ctx, err); } static const char hlp_listcrls[] = "LISTCRLS\n" "\n" "List the content of all CRLs in a readable format. This command is\n" "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n" "listcrls\". It may also be used directly using \"dirmngr\n" "--list-crls\"."; static gpg_error_t cmd_listcrls (assuan_context_t ctx, char *line) { gpg_error_t err; estream_t fp; (void)line; fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = crl_cache_list (fp); es_fclose (fp); } return leave_cmd (ctx, err); } static const char hlp_cachecert[] = "CACHECERT\n" "\n" "Put a certificate into the internal cache. This command might be\n" "useful if a client knows in advance certificates required for a\n" "test and wants to make sure they get added to the internal cache.\n" "It is also helpful for debugging. To get the actual certificate,\n" "this command immediately inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_cachecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; err = cache_cert (cert); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_validate[] = "VALIDATE [--systrust] [--tls] [--no-crl]\n" "\n" "Validate a certificate using the certificate validation function\n" "used internally by dirmngr. This command is only useful for\n" "debugging. To get the actual certificate, this command immediately\n" "inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob. The option --tls modifies this by asking\n" "for list of certificates with\n" "\n" " INQUIRE CERTLIST\n" "\n" "Here the first certificate is the target certificate, the remaining\n" "certificates are suggested intermediary certificates. All certifciates\n" "need to be PEM encoded.\n" "\n" "The option --systrust changes the behaviour to include the system\n" "provided root certificates as trust anchors. The option --no-crl\n" "skips CRL checks"; static gpg_error_t cmd_validate (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; certlist_t certlist = NULL; unsigned char *value = NULL; size_t valuelen; int systrust_mode, tls_mode, no_crl; systrust_mode = has_option (line, "--systrust"); tls_mode = has_option (line, "--tls"); no_crl = has_option (line, "--no-crl"); line = skip_options (line); if (tls_mode) err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST", &value, &valuelen, MAX_CERTLIST_LENGTH); else err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else if (tls_mode) { estream_t fp; fp = es_fopenmem_init (0, "rb", value, valuelen); if (!fp) err = gpg_error_from_syserror (); else { err = read_certlist_from_stream (&certlist, fp); es_fclose (fp); if (!err && !certlist) err = gpg_error (GPG_ERR_MISSING_CERT); if (!err) { /* Extraxt the first certificate from the list. */ cert = certlist->cert; ksba_cert_ref (cert); } } } else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; if (!tls_mode) { /* If we have this certificate already in our cache, use the * cached version for validation because this will take care of * any cached results. We don't need to do this in tls mode * because this has already been done for certificate in a * certlist_t. */ unsigned char fpr[20]; ksba_cert_t tmpcert; cert_compute_fpr (cert, fpr); tmpcert = get_cert_byfpr (fpr); if (tmpcert) { ksba_cert_release (cert); cert = tmpcert; } } /* Quick hack to make verification work by inserting the supplied * certs into the cache. */ if (tls_mode && certlist) { certlist_t cl; for (cl = certlist->next; cl; cl = cl->next) cache_cert (cl->cert); } err = validate_cert_chain (ctrl, cert, NULL, (VALIDATE_FLAG_TRUST_CONFIG | (tls_mode ? VALIDATE_FLAG_TLS : 0) | (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0) | (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)), NULL); leave: ksba_cert_release (cert); release_certlist (certlist); return leave_cmd (ctx, err); } /* Parse an keyserver URI and store it in a new uri item which is returned at R_ITEM. On error return an error code. */ static gpg_error_t make_keyserver_item (const char *uri, uri_item_t *r_item) { gpg_error_t err; uri_item_t item; *r_item = NULL; item = xtrymalloc (sizeof *item + strlen (uri)); if (!item) return gpg_error_from_syserror (); item->next = NULL; item->parsed_uri = NULL; strcpy (item->uri, uri); #if USE_LDAP if (ldap_uri_p (item->uri)) err = ldap_parse_uri (&item->parsed_uri, uri); else #endif { err = http_parse_uri (&item->parsed_uri, uri, 1); } if (err) xfree (item); else *r_item = item; return err; } /* If no keyserver is stored in CTRL but a global keyserver has been set, put that global keyserver into CTRL. We need use this function to help migrate from the old gpg based keyserver configuration to the new dirmngr based configuration. */ static gpg_error_t ensure_keyserver (ctrl_t ctrl) { gpg_error_t err; uri_item_t item; uri_item_t onion_items = NULL; uri_item_t plain_items = NULL; uri_item_t ui; strlist_t sl; if (ctrl->server_local->keyservers) return 0; /* Already set for this session. */ if (!opt.keyserver) { /* No global option set. Fall back to default: */ return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER, &ctrl->server_local->keyservers); } for (sl = opt.keyserver; sl; sl = sl->next) { err = make_keyserver_item (sl->d, &item); if (err) goto leave; if (item->parsed_uri->onion) { item->next = onion_items; onion_items = item; } else { item->next = plain_items; plain_items = item; } } - /* Decide which to use. Note that the sesssion has no keyservers + /* Decide which to use. Note that the session has no keyservers yet set. */ if (onion_items && !onion_items->next && plain_items && !plain_items->next) { /* If there is just one onion and one plain keyserver given, we take only one depending on whether Tor is running or not. */ if (is_tor_running (ctrl)) { ctrl->server_local->keyservers = onion_items; onion_items = NULL; } else { ctrl->server_local->keyservers = plain_items; plain_items = NULL; } } else if (!is_tor_running (ctrl)) { /* Tor is not running. It does not make sense to add Onion addresses. */ ctrl->server_local->keyservers = plain_items; plain_items = NULL; } else { /* In all other cases add all keyservers. */ ctrl->server_local->keyservers = onion_items; onion_items = NULL; for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next) ; if (ui) ui->next = plain_items; else ctrl->server_local->keyservers = plain_items; plain_items = NULL; } leave: release_uri_item_list (onion_items); release_uri_item_list (plain_items); return err; } static const char hlp_keyserver[] = "KEYSERVER [] [|]\n" "Options are:\n" " --help\n" " --clear Remove all configured keyservers\n" " --resolve Resolve HKP host names and rotate\n" " --hosttable Print table of known hosts and pools\n" " --dead Mark as dead\n" " --alive Mark as alive\n" "\n" "If called without arguments list all configured keyserver URLs.\n" "If called with an URI add this as keyserver. Note that keyservers\n" "are configured on a per-session base. A default keyserver may already be\n" "present, thus the \"--clear\" option must be used to get full control.\n" "If \"--clear\" and an URI are used together the clear command is\n" "obviously executed first. A RESET command does not change the list\n" "of configured keyservers."; static gpg_error_t cmd_keyserver (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clear_flag, add_flag, help_flag, host_flag, resolve_flag; int dead_flag, alive_flag; uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it is always initialized. */ clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); resolve_flag = has_option (line, "--resolve"); host_flag = has_option (line, "--hosttable"); dead_flag = has_option (line, "--dead"); alive_flag = has_option (line, "--alive"); line = skip_options (line); add_flag = !!*line; if (help_flag) { err = ks_action_help (ctrl, line); goto leave; } if (resolve_flag) { err = ensure_keyserver (ctrl); if (!err) err = ks_action_resolve (ctrl, ctrl->server_local->keyservers); if (err) goto leave; } if (alive_flag && dead_flag) { err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies"); goto leave; } if (dead_flag) { err = check_owner_permission (ctx, "no permission to use --dead"); if (err) goto leave; } if (alive_flag || dead_flag) { if (!*line) { err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing"); goto leave; } err = ks_hkp_mark_host (ctrl, line, alive_flag); if (err) goto leave; } if (host_flag) { err = ks_hkp_print_hosttable (ctrl); if (err) goto leave; } if (resolve_flag || host_flag || alive_flag || dead_flag) goto leave; if (add_flag) { err = make_keyserver_item (line, &item); if (err) goto leave; } if (clear_flag) release_ctrl_keyservers (ctrl); if (add_flag) { item->next = ctrl->server_local->keyservers; ctrl->server_local->keyservers = item; } if (!add_flag && !clear_flag && !help_flag) { /* List configured keyservers. However, we first add a global keyserver. */ uri_item_t u; err = ensure_keyserver (ctrl); if (err) { assuan_set_error (ctx, err, "Bad keyserver configuration in dirmngr.conf"); goto leave; } for (u=ctrl->server_local->keyservers; u; u = u->next) dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL); } err = 0; leave: return leave_cmd (ctx, err); } static const char hlp_ks_search[] = "KS_SEARCH {}\n" "\n" "Search the configured OpenPGP keyservers (see command KEYSERVER)\n" "for keys matching PATTERN"; static gpg_error_t cmd_ks_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; /* No options for now. */ line = skip_options (line); /* Break the line down into an strlist. Each pattern is percent-plus escaped. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the search. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_search (ctrl, ctrl->server_local->keyservers, list, outfp); es_fclose (outfp); } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_get[] = "KS_GET {}\n" "\n" "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" "or an exact name indicated by the '=' prefix."; static gpg_error_t cmd_ks_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; /* No options for now. */ line = skip_options (line); /* Break the line into a strlist. Each pattern is by definition percent-plus escaped. However we only support keyids and fingerprints and thus the client has no need to apply the escaping. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_get (ctrl, ctrl->server_local->keyservers, list, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_fetch[] = "KS_FETCH \n" "\n" "Get the key(s) from URL."; static gpg_error_t cmd_ks_fetch (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; estream_t outfp; /* No options for now. */ line = skip_options (line); err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */ if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_fetch (ctrl, line, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } leave: return leave_cmd (ctx, err); } static const char hlp_ks_put[] = "KS_PUT\n" "\n" "Send a key to the configured OpenPGP keyservers. The actual key material\n" "is then requested by Dirmngr using\n" "\n" " INQUIRE KEYBLOCK\n" "\n" "The client shall respond with a binary version of the keyblock (e.g.,\n" "the output of `gpg --export KEYID'). For LDAP\n" "keyservers Dirmngr may ask for meta information of the provided keyblock\n" "using:\n" "\n" " INQUIRE KEYBLOCK_INFO\n" "\n" "The client shall respond with a colon delimited info lines (the output\n" "of 'for x in keys sigs; do gpg --list-$x --with-colons KEYID; done').\n"; static gpg_error_t cmd_ks_put (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; unsigned char *info = NULL; size_t infolen; /* No options for now. */ line = skip_options (line); err = ensure_keyserver (ctrl); if (err) goto leave; /* Ask for the key material. */ err = assuan_inquire (ctx, "KEYBLOCK", &value, &valuelen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ { err = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } /* Ask for the key meta data. Not actually needed for HKP servers - but we do it anyway to test the client implementaion. */ + but we do it anyway to test the client implementation. */ err = assuan_inquire (ctx, "KEYBLOCK_INFO", &info, &infolen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the key. */ err = ks_action_put (ctrl, ctrl->server_local->keyservers, value, valuelen, info, infolen); leave: xfree (info); xfree (value); return leave_cmd (ctx, err); } static const char hlp_loadswdb[] = "LOADSWDB [--force]\n" "\n" "Load and verify the swdb.lst from the Net."; static gpg_error_t cmd_loadswdb (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; err = dirmngr_load_swdb (ctrl, has_option (line, "--force")); return leave_cmd (ctx, err); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "tor - Return OK if running in Tor mode\n" "dnsinfo - Return info about the DNS resolver\n" "socket_name - Return the name of the socket.\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = dirmngr_get_current_socket_name (); err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "tor")) { int use_tor; use_tor = dirmngr_use_tor (); if (use_tor) { if (!is_tor_running (ctrl)) err = assuan_write_status (ctx, "NO_TOR", "Tor not running"); else err = 0; if (!err) assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled" /**/ : "- Tor mode is enforced"); } else err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled"); } else if (!strcmp (line, "dnsinfo")) { if (standard_resolver_p ()) assuan_set_okay_line (ctx, "- Forced use of System resolver (w/o Tor support)"); else { #ifdef USE_LIBDNS assuan_set_okay_line (ctx, (recursive_resolver_p () ? "- Libdns recursive resolver" : "- Libdns stub resolver")); #else assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)"); #endif } err = 0; } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killdirmngr[] = "KILLDIRMNGR\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this dirmngr process.\n"; static gpg_error_t cmd_killdirmngr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return gpg_error (GPG_ERR_EOF); } static const char hlp_reloaddirmngr[] = "RELOADDIRMNGR\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloaddirmngr (assuan_context_t ctx, char *line) { (void)ctx; (void)line; dirmngr_sighup_action (); return 0; } /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "DNS_CERT", cmd_dns_cert, hlp_dns_cert }, { "WKD_GET", cmd_wkd_get, hlp_wkd_get }, { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver }, { "ISVALID", cmd_isvalid, hlp_isvalid }, { "CHECKCRL", cmd_checkcrl, hlp_checkcrl }, { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp }, { "LOOKUP", cmd_lookup, hlp_lookup }, { "LOADCRL", cmd_loadcrl, hlp_loadcrl }, { "LISTCRLS", cmd_listcrls, hlp_listcrls }, { "CACHECERT", cmd_cachecert, hlp_cachecert }, { "VALIDATE", cmd_validate, hlp_validate }, { "KEYSERVER", cmd_keyserver, hlp_keyserver }, { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, { "KS_GET", cmd_ks_get, hlp_ks_get }, { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; #if USE_LDAP ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; return 0; } /* This function is called by our assuan log handler to test whether a * log message shall really be printed. The function must return * false to inhibit the logging of MSG. CAT gives the requested log * category. MSG might be NULL. */ int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)cat; (void)msg; if (!ctrl || !ctrl->server_local) return 1; /* Can't decide - allow logging. */ if (!ctrl->server_local->inhibit_data_logging) return 1; /* Not requested - allow logging. */ /* Disallow logging if *_now is true. */ return !ctrl->server_local->inhibit_data_logging_now; } /* Startup the server and run the main command loop. With FD = -1, use stdin/stdout. */ void start_command_handler (assuan_fd_t fd) { static const char hello[] = "Dirmngr " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl_t ctrl; ctrl = xtrycalloc (1, sizeof *ctrl); if (ctrl) ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl || !ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), strerror (errno)); xfree (ctrl); return; } dirmngr_init_default_ctrl (ctrl); rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); dirmngr_exit (2); } if (fd == ASSUAN_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), opt.config_filename? opt.config_filename : "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } #if USE_LDAP ldap_wrapper_connection_cleanup (ctrl); ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; release_ctrl_keyservers (ctrl); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) dirmngr_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { release_ctrl_ocsp_certs (ctrl); xfree (ctrl->server_local); dirmngr_deinit_default_ctrl (ctrl); xfree (ctrl); } } /* Send a status line back to the client. KEYWORD is the status keyword, the optional string arguments are blank separated added to the line, the last argument must be a NULL. */ gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, keyword); if (ctrl->server_local) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; p = buf; n = 0; while ( (text = va_arg (arg_ptr, const char *)) ) { if (n) { *p++ = ' '; n++; } for ( ; *text && n < DIM (buf)-2; n++) *p++ = *text++; } *p = 0; err = assuan_write_status (ctx, keyword, buf); } va_end (arg_ptr); return err; } /* Print a help status line. TEXTLEN gives the length of the text from TEXT to be printed. The function splits text at LFs. */ gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text) { gpg_error_t err = 0; if (ctrl->server_local) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; do { p = buf; n = 0; for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++) *p++ = *text++; if (*text == '\n') text++; *p = 0; err = assuan_write_status (ctx, "#", buf); } while (!err && *text); } return err; } /* Send a tick progress indicator back. Fixme: This is only done for the currently active channel. */ gpg_error_t dirmngr_tick (ctrl_t ctrl) { static time_t next_tick = 0; gpg_error_t err = 0; time_t now = time (NULL); if (!next_tick) { next_tick = now + 1; } else if ( now > next_tick ) { if (ctrl) { err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL); if (err) { /* Take this as in indication for a cancel request. */ err = gpg_error (GPG_ERR_CANCELED); } now = time (NULL); } next_tick = now + 1; } return err; } diff --git a/dirmngr/validate.c b/dirmngr/validate.c index 3671a8b9e..371852ba7 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -1,1198 +1,1198 @@ /* validate.c - Validate a certificate chain. * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc. * Copyright (C) 2004, 2006, 2008, 2017 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include "dirmngr.h" #include "certcache.h" #include "crlcache.h" #include "validate.h" #include "misc.h" /* Mode parameters for cert_check_usage(). */ enum cert_usage_modes { CERT_USAGE_MODE_SIGN, /* Usable for encryption. */ CERT_USAGE_MODE_ENCR, /* Usable for signing. */ CERT_USAGE_MODE_VRFY, /* Usable for verification. */ CERT_USAGE_MODE_DECR, /* Usable for decryption. */ CERT_USAGE_MODE_CERT, /* Usable for cert signing. */ CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */ CERT_USAGE_MODE_CRL /* Usable for CRL signing. */ }; /* While running the validation function we need to keep track of the certificates and the validation outcome of each. We use this type for it. */ struct chain_item_s { struct chain_item_s *next; ksba_cert_t cert; /* The certificate. */ unsigned char fpr[20]; /* Fingerprint of the certificate. */ int is_self_signed; /* This certificate is self-signed. */ int is_valid; /* The certifiate is valid except for revocations. */ }; typedef struct chain_item_s *chain_item_t; /* A couple of constants with Object Identifiers. */ static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; /* Prototypes. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); /* Make sure that the values defined in the headers are correct. We * can't use the preprocessor due to the use of enums. */ static void check_header_constants (void) { log_assert (CERTTRUST_CLASS_SYSTEM == VALIDATE_FLAG_TRUST_SYSTEM); log_assert (CERTTRUST_CLASS_CONFIG == VALIDATE_FLAG_TRUST_CONFIG); log_assert (CERTTRUST_CLASS_HKP == VALIDATE_FLAG_TRUST_HKP); log_assert (CERTTRUST_CLASS_HKPSPOOL == VALIDATE_FLAG_TRUST_HKPSPOOL); #undef X #define X (VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_TRUST_CONFIG \ | VALIDATE_FLAG_TRUST_HKP | VALIDATE_FLAG_TRUST_HKPSPOOL) #if ( X & VALIDATE_FLAG_MASK_TRUST ) != X # error VALIDATE_FLAG_MASK_TRUST is bad #endif #if ( ~X & VALIDATE_FLAG_MASK_TRUST ) # error VALIDATE_FLAG_MASK_TRUST is bad #endif #undef X } /* Check whether CERT contains critical extensions we don't know about. */ static gpg_error_t unknown_criticals (ksba_cert_t cert) { static const char *known[] = { "2.5.29.15", /* keyUsage */ "2.5.29.19", /* basic Constraints */ "2.5.29.32", /* certificatePolicies */ "2.5.29.37", /* extendedKeyUsage */ NULL }; int i, idx, crit; const char *oid; int unsupported; strlist_t sl; gpg_error_t err, rc; rc = 0; for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &crit, NULL, NULL));idx++) { if (!crit) continue; for (i=0; known[i] && strcmp (known[i],oid); i++) ; unsupported = !known[i]; /* If this critical extension is not supported, check the list of to be ignored extensions to see whether we claim that it is supported. */ if (unsupported && opt.ignored_cert_extensions) { for (sl=opt.ignored_cert_extensions; sl && strcmp (sl->d, oid); sl = sl->next) ; if (sl) unsupported = 0; } if (unsupported) { log_error (_("critical certificate extension %s is not supported"), oid); rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); } } if (err && gpg_err_code (err) != GPG_ERR_EOF) - rc = err; /* Such an error takes precendence. */ + rc = err; /* Such an error takes precedence. */ return rc; } /* Basic check for supported policies. */ static gpg_error_t check_cert_policy (ksba_cert_t cert) { static const char *allowed[] = { "2.289.9.9", NULL }; gpg_error_t err; int idx; char *p, *haystack; char *policies; int any_critical; err = ksba_cert_get_cert_policies (cert, &policies); if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 0; /* No policy given. */ if (err) return err; /* STRING is a line delimited list of certifiate policies as stored in the certificate. The line itself is colon delimited where the first field is the OID of the policy and the second field either N or C for normal or critical extension */ if (opt.verbose > 1) log_info ("certificate's policy list: %s\n", policies); /* The check is very minimal but won't give false positives */ any_critical = !!strstr (policies, ":C"); /* See whether we find ALLOWED (which is an OID) in POLICIES */ for (idx=0; allowed[idx]; idx++) { for (haystack=policies; (p=strstr (haystack, allowed[idx])); haystack = p+1) { if ( !(p == policies || p[-1] == '\n') ) continue; /* Does not match the begin of a line. */ if (p[strlen (allowed[idx])] != ':') continue; /* The length does not match. */ /* Yep - it does match: Return okay. */ ksba_free (policies); return 0; } } if (!any_critical) { log_info (_("Note: non-critical certificate policy not allowed")); err = 0; } else { log_info (_("certificate policy not allowed")); err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } ksba_free (policies); return err; } static gpg_error_t allowed_ca (ksba_cert_t cert, int *chainlen) { gpg_error_t err; int flag; err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) return err; if (!flag) { if (!is_trusted_cert (cert, CERTTRUST_CLASS_CONFIG)) { /* The German SigG Root CA's certificate does not flag itself as a CA; thus we relax this requirement if we trust a root CA. I think this is reasonable. Note, that gpgsm implements a far stricter scheme here. */ if (chainlen) *chainlen = 3; /* That is what the SigG implements. */ if (opt.verbose) log_info (_("accepting root CA not marked as a CA")); } else { log_error (_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } } return 0; } /* Helper for validate_cert_chain. */ static gpg_error_t check_revocations (ctrl_t ctrl, chain_item_t chain) { gpg_error_t err = 0; int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; chain_item_t ci; log_assert (ctrl->check_revocations_nest_level >= 0); log_assert (chain); if (ctrl->check_revocations_nest_level > 10) { log_error (_("CRL checking too deeply nested\n")); return gpg_error(GPG_ERR_BAD_CERT_CHAIN); } ctrl->check_revocations_nest_level++; for (ci=chain; ci; ci = ci->next) { assert (ci->cert); if (ci == chain) { /* It does not make sense to check the root certificate for revocations. In almost all cases this will lead to a catch-22 as the root certificate is the final trust anchor for the certificates and the CRLs. We expect the user to remove root certificates from the list of trusted certificates in case they have been revoked. */ if (opt.verbose) cert_log_name (_("not checking CRL for"), ci->cert); continue; } if (opt.verbose) cert_log_name (_("checking CRL for"), ci->cert); err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, ci->cert); if (!err) err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); } switch (gpg_err_code (err)) { case 0: err = 0; break; case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break; case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break; case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break; default: break; } } ctrl->check_revocations_nest_level--; if (err) ; else if (any_revoked) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_no_crl) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (any_crl_too_old) err = gpg_error (GPG_ERR_CRL_TOO_OLD); else err = 0; return err; } /* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN are the DNs already extracted by the caller from CERT. Returns True if this is the case. */ static int is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) { gpg_error_t err; int result = 0; ksba_sexp_t serialno; ksba_sexp_t ak_keyid; ksba_name_t ak_name; ksba_sexp_t ak_sn; const char *ak_name_str; ksba_sexp_t subj_keyid = NULL; if (!issuerdn || !subjectdn) return 0; /* No. */ if (strcmp (issuerdn, subjectdn)) return 0; /* No. */ err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 1; /* Yes. Without a authorityKeyIdentifier this needs - to be the Root certifcate (our trust anchor). */ + to be the Root certificate (our trust anchor). */ log_error ("error getting authorityKeyIdentifier: %s\n", gpg_strerror (err)); return 0; /* Well, it is broken anyway. Return No. */ } serialno = ksba_cert_get_serial (cert); if (!serialno) { log_error ("error getting serialno: %s\n", gpg_strerror (err)); goto leave; } /* Check whether the auth name's matches the issuer name+sn. If that is the case this is a root certificate. */ ak_name_str = ksba_name_enum (ak_name, 0); if (ak_name_str && !strcmp (ak_name_str, issuerdn) && !cmp_simple_canon_sexp (ak_sn, serialno)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } /* Similar for the ak_keyid. */ if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid) && !cmp_simple_canon_sexp (ak_keyid, subj_keyid)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } leave: ksba_free (subj_keyid); ksba_free (ak_keyid); ksba_name_release (ak_name); ksba_free (ak_sn); ksba_free (serialno); return result; } /* Validate the certificate CHAIN up to the trust anchor. Optionally return the closest expiration time in R_EXPTIME (this is useful for caching issues). MODE is one of the VALIDATE_MODE_* constants. Note that VALIDATE_MODE_OCSP is not used due to the removal of the system service in 2.1.15. Instead only the callback to gpgsm to validate a certificate is used. If R_TRUST_ANCHOR is not NULL and the validation would fail only because the root certificate is not trusted, the hexified fingerprint of that root certificate is stored at R_TRUST_ANCHOR and success is returned. The caller needs to free the value at R_TRUST_ANCHOR; in all other cases NULL is stored there. */ gpg_error_t validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, unsigned int flags, char **r_trust_anchor) { gpg_error_t err = 0; int depth, maxdepth; char *issuer = NULL; char *subject = NULL; ksba_cert_t subject_cert = NULL; ksba_cert_t issuer_cert = NULL; ksba_isotime_t current_time; ksba_isotime_t exptime; int any_expired = 0; int any_no_policy_match = 0; chain_item_t chain; check_header_constants (); if (r_exptime) *r_exptime = 0; *exptime = 0; if (r_trust_anchor) *r_trust_anchor = NULL; if (DBG_X509) dump_cert ("subject", cert); /* May the target certificate be used for this purpose? */ if ((flags & VALIDATE_FLAG_OCSP) && (err = check_cert_use_ocsp (cert))) return err; if ((flags & VALIDATE_FLAG_CRL) && (err = check_cert_use_crl (cert))) return err; /* If we already validated the certificate not too long ago, we can avoid the excessive computations and lookups unless the caller asked for the expiration time. */ if (!r_exptime) { size_t buflen; time_t validated_at; err = ksba_cert_get_user_data (cert, "validated_at", &validated_at, sizeof (validated_at), &buflen); if (err || buflen != sizeof (validated_at) || !validated_at) err = 0; /* Not available or other error. */ else { /* If the validation is not older than 30 minutes we are ready. */ if (validated_at < gnupg_get_time () + (30*60)) { if (opt.verbose) log_info ("certificate is good (cached)\n"); /* Note, that we can't jump to leave here as this would falsely updated the validation timestamp. */ return 0; } } } /* Get the current time. */ gnupg_get_isotime (current_time); /* We walk up the chain until we find a trust anchor. */ subject_cert = cert; maxdepth = 10; /* Sensible limit on the length of the chain. */ chain = NULL; depth = 0; for (;;) { /* Get the subject and issuer name from the current certificate. */ ksba_free (issuer); ksba_free (subject); issuer = ksba_cert_get_issuer (subject_cert, 0); subject = ksba_cert_get_subject (subject_cert, 0); if (!issuer) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Handle the notBefore and notAfter timestamps. */ { ksba_isotime_t not_before, not_after; err = ksba_cert_get_validity (subject_cert, 0, not_before); if (!err) err = ksba_cert_get_validity (subject_cert, 1, not_after); if (err) { log_error (_("certificate with invalid validity: %s"), gpg_strerror (err)); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Keep track of the nearest expiration time in EXPTIME. */ if (*not_after) { if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); } /* Check whether the certificate is already valid. */ if (*not_before && strcmp (current_time, not_before) < 0 ) { log_error (_("certificate not yet valid")); log_info ("(valid from "); dump_isotime (not_before); log_printf (")\n"); err = gpg_error (GPG_ERR_CERT_TOO_YOUNG); goto leave; } /* Now check whether the certificate has expired. */ if (*not_after && strcmp (current_time, not_after) > 0 ) { log_error (_("certificate has expired")); log_info ("(expired at "); dump_isotime (not_after); log_printf (")\n"); any_expired = 1; } } /* Do we have any critical extensions in the certificate we can't handle? */ err = unknown_criticals (subject_cert); if (err) goto leave; /* yes. */ /* Check that given policies are allowed. */ err = check_cert_policy (subject_cert); if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH) { any_no_policy_match = 1; err = 0; } else if (err) goto leave; /* Is this a self-signed certificate? */ if (is_root_cert (subject_cert, issuer, subject)) { /* Yes, this is our trust anchor. */ if (check_cert_sig (subject_cert, subject_cert) ) { log_error (_("selfsigned certificate has a BAD signature")); err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN : GPG_ERR_BAD_CERT); goto leave; } /* Is this certificate allowed to act as a CA. */ err = allowed_ca (subject_cert, NULL); if (err) goto leave; /* No. */ err = is_trusted_cert (subject_cert, (flags & VALIDATE_FLAG_MASK_TRUST)); if (!err) ; /* Yes we trust this cert. */ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED) { char *fpr; log_error (_("root certificate is not marked trusted")); fpr = get_fingerprint_hexstring (subject_cert); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); dump_cert ("issuer", subject_cert); if (r_trust_anchor) { /* Caller wants to do another trustiness check. */ *r_trust_anchor = fpr; err = 0; } else xfree (fpr); } else { log_error (_("checking trustworthiness of " "root certificate failed: %s\n"), gpg_strerror (err)); } if (err) goto leave; /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("root certificate is good but not trusted\n"); else log_info ("root certificate is good and trusted\n"); } - break; /* Okay: a self-signed certicate is an end-point. */ + break; /* Okay: a self-signed certificate is an end-point. */ } /* To avoid loops, we use an arbitrary limit on the length of the chain. */ depth++; if (depth > maxdepth) { log_error (_("certificate chain too long\n")); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Find the next cert up the tree. */ ksba_cert_release (issuer_cert); issuer_cert = NULL; err = find_issuing_cert (ctrl, subject_cert, &issuer_cert); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { log_error (_("issuer certificate not found")); log_info ("issuer certificate: #/"); dump_string (issuer); log_printf ("\n"); } else log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); /* Use a better understandable error code. */ err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } /* try_another_cert: */ if (DBG_X509) { log_debug ("got issuer's certificate:\n"); dump_cert ("issuer", issuer_cert); } /* Now check the signature of the certificate. FIXME: we should * delay this until later so that faked certificates can't be * turned into a DoS easily. */ err = check_cert_sig (issuer_cert, subject_cert); if (err) { log_error (_("certificate has a BAD signature")); #if 0 if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE) { /* We now try to find other issuer certificates which might have been used. This is required because some CAs are reusing the issuer and subject DN for new root certificates without using a authorityKeyIdentifier. */ rc = find_up (kh, subject_cert, issuer, 1); if (!rc) { ksba_cert_t tmp_cert; rc = keydb_get_cert (kh, &tmp_cert); if (rc || !compare_certs (issuer_cert, tmp_cert)) { /* The find next did not work or returned an identical certificate. We better stop here to avoid infinite checks. */ rc = gpg_error (GPG_ERR_BAD_SIGNATURE); ksba_cert_release (tmp_cert); } else { do_list (0, lm, fp, _("found another possible matching " "CA certificate - trying again")); ksba_cert_release (issuer_cert); issuer_cert = tmp_cert; goto try_another_cert; } } } #endif /* Return a more descriptive error code than the one * returned from the signature checking. */ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Check that the length of the chain is not longer than allowed * by the CA. */ { int chainlen; err = allowed_ca (issuer_cert, &chainlen); if (err) goto leave; if (chainlen >= 0 && (depth - 1) > chainlen) { log_error (_("certificate chain longer than allowed by CA (%d)"), chainlen); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } } /* May that certificate be used for certification? */ err = check_cert_use_cert (issuer_cert); if (err) goto leave; /* No. */ /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) log_info (_("certificate is good\n")); /* Now to the next level up. */ subject_cert = issuer_cert; issuer_cert = NULL; } /* Even if we have no error here we need to check whether we * encountered an error somewhere during the checks. Set the error * code to the most critical one. */ if (!err) { if (any_expired) err = gpg_error (GPG_ERR_CERT_EXPIRED); else if (any_no_policy_match) err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } if (!err && opt.verbose) { chain_item_t citem; log_info (_("certificate chain is good\n")); for (citem = chain; citem; citem = citem->next) cert_log_name (" certificate", citem->cert); } /* Now check for revocations unless CRL checks are disabled or we * are non-recursive CRL mode. */ if (!err && !(flags & VALIDATE_FLAG_NOCRLCHECK) && !((flags & VALIDATE_FLAG_CRL) && !(flags & VALIDATE_FLAG_RECURSIVE))) { /* Now that everything is fine, walk the chain and check each * certificate for revocations. * * 1. item in the chain - The root certificate. * 2. item - the CA below the root * last item - the target certificate. * * Now for each certificate in the chain check whether it has * been included in a CRL and thus be revoked. We don't do OCSP * here because this does not seem to make much sense. This * might become a recursive process and we should better cache * our validity results to avoid double work. Far worse a * catch-22 may happen for an improper setup hierarchy and we * need a way to break up such a deadlock. */ err = check_revocations (ctrl, chain); } if (!err && opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("target certificate may be valid\n"); else log_info ("target certificate is valid\n"); } else if (err && opt.verbose) log_info ("target certificate is NOT valid\n"); leave: if (!err && !(r_trust_anchor && *r_trust_anchor)) { /* With no error we can update the validation cache. We do this * for all certificates in the chain. Note that we can't use * the cache if the caller requested to check the trustiness of * the root certificate himself. Adding such a feature would * require us to also store the fingerprint of root * certificate. */ chain_item_t citem; time_t validated_at = gnupg_get_time (); for (citem = chain; citem; citem = citem->next) { err = ksba_cert_set_user_data (citem->cert, "validated_at", &validated_at, sizeof (validated_at)); if (err) { log_error ("set_user_data(validated_at) failed: %s\n", gpg_strerror (err)); err = 0; } } } if (r_exptime) gnupg_copy_time (r_exptime, exptime); ksba_free (issuer); ksba_free (subject); ksba_cert_release (issuer_cert); if (subject_cert != cert) ksba_cert_release (subject_cert); while (chain) { chain_item_t ci_next = chain->next; if (chain->cert) ksba_cert_release (chain->cert); xfree (chain); chain = ci_next; } if (err && r_trust_anchor && *r_trust_anchor) { xfree (*r_trust_anchor); *r_trust_anchor = NULL; } return err; } /* Return the public key algorithm id from the S-expression PKEY. FIXME: libgcrypt should provide such a function. Note that this implementation uses the names as used by libksba. */ static int pk_algo_from_sexp (gcry_sexp_t pkey) { gcry_sexp_t l1, l2; const char *name; size_t n; int algo; l1 = gcry_sexp_find_token (pkey, "public-key", 0); if (!l1) return 0; /* Not found. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); name = gcry_sexp_nth_data (l2, 0, &n); if (!name) algo = 0; /* Not found. */ else if (n==3 && !memcmp (name, "rsa", 3)) algo = GCRY_PK_RSA; else if (n==3 && !memcmp (name, "dsa", 3)) algo = GCRY_PK_DSA; else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) algo = GCRY_PK_RSA; else algo = 0; gcry_sexp_release (l2); return algo; } /* Check the signature on CERT using the ISSUER_CERT. This function * does only test the cryptographic signature and nothing else. It is * assumed that the ISSUER_CERT is valid. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) { gpg_error_t err; const char *algoid; gcry_md_hd_t md; int i, algo; ksba_sexp_t p; size_t n; gcry_sexp_t s_sig, s_hash, s_pkey; const char *s; char algo_name[16+1]; /* hash algorithm name converted to lower case. */ int digestlen; unsigned char *digest; /* Hash the target certificate using the algorithm from that certificate. */ algoid = ksba_cert_get_digest_algo (cert); algo = gcry_md_map_name (algoid); if (!algo) { log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_GENERAL); } s = gcry_md_algo_name (algo); for (i=0; *s && i < sizeof algo_name - 1; s++, i++) algo_name[i] = tolower (*s); algo_name[i] = 0; err = gcry_md_open (&md, algo, 0); if (err) { log_error ("md_open failed: %s\n", gpg_strerror (err)); return err; } if (DBG_HASHING) gcry_md_debug (md, "hash.cert"); err = ksba_cert_hash (cert, 1, HASH_FNC, md); if (err) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err)); gcry_md_close (md); return err; } gcry_md_final (md); /* Get the signature value out of the target certificate. */ p = ksba_cert_get_sig_val (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); return gpg_error (GPG_ERR_BUG); } if (DBG_CRYPTO) { int j; log_debug ("signature value:"); for (j=0; j < n; j++) log_printf (" %02X", p[j]); log_printf ("\n"); } err = gcry_sexp_sscan ( &s_sig, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); gcry_md_close (md); return err; } /* Get the public key from the issuer certificate. */ p = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } err = gcry_sexp_sscan ( &s_pkey, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); return err; } /* Prepare the values for signature verification. At this point we * have these values: * * S_PKEY - S-expression with the issuer's public key. * S_SIG - Signature value as given in the certificate. * MD - Finalized hash context with hash of the certificate. * ALGO_NAME - Lowercase hash algorithm name */ digestlen = gcry_md_get_algo_dlen (algo); digest = gcry_md_read (md, algo); if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA) { /* NB.: We support only SHA-1 here because we had problems back * then to get test data for DSA-2. Meanwhile DSA has been * replaced by ECDSA which we do not yet support. */ if (digestlen != 20) { log_error ("DSA requires the use of a 160 bit hash algorithm\n"); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_pkey); return gpg_error (GPG_ERR_INTERNAL); } if ( gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))", (int)digestlen, digest) ) BUG (); } else /* Not DSA - we assume RSA */ { if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", algo_name, (int)digestlen, digest) ) BUG (); } err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return err; } /* Return 0 if CERT is usable for MODE. */ static gpg_error_t check_cert_usage (ksba_cert_t cert, enum cert_usage_modes mode) { gpg_error_t err; unsigned int use; char *extkeyusages; int have_ocsp_signing = 0; err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* No policy given. */ if (!err) { unsigned int extusemask = ~0; /* Allow all. */ if (extkeyusages) { char *p, *pend; int any_critical = 0; extusemask = 0; p = extkeyusages; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; /* Only care about critical flagged usages. */ if ( *pend == 'C' ) { any_critical = 1; if ( !strcmp (p, oid_kp_serverAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_clientAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_codeSigning)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); else if ( !strcmp (p, oid_kp_emailProtection)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_timeStamping)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION); } /* This is a hack to cope with OCSP. Note that we do not yet fully comply with the requirements and that the entire CRL/OCSP checking thing should undergo a thorough review and probably redesign. */ if ( !strcmp (p, oid_kp_ocspSigning)) have_ocsp_signing = 1; if ((p = strchr (pend, '\n'))) p++; } ksba_free (extkeyusages); extkeyusages = NULL; if (!any_critical) extusemask = ~0; /* Reset to the don't care mask. */ } err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (opt.verbose && (mode == CERT_USAGE_MODE_SIGN || mode == CERT_USAGE_MODE_ENCR)) log_info (_("no key usage specified - assuming all usages\n")); use = ~0; } /* Apply extKeyUsage. */ use &= extusemask; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); ksba_free (extkeyusages); return err; } switch (mode) { case CERT_USAGE_MODE_SIGN: case CERT_USAGE_MODE_VRFY: if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION))) return 0; log_info (mode == CERT_USAGE_MODE_VRFY ? _("certificate should not have been used for signing\n") : _("certificate is not usable for signing\n")); break; case CERT_USAGE_MODE_ENCR: case CERT_USAGE_MODE_DECR: if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) return 0; log_info (mode == CERT_USAGE_MODE_DECR ? _("certificate should not have been used for encryption\n") : _("certificate is not usable for encryption\n")); break; case CERT_USAGE_MODE_CERT: if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) return 0; log_info (_("certificate should not have " "been used for certification\n")); break; case CERT_USAGE_MODE_OCSP: if (use != ~0 && (have_ocsp_signing || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN |KSBA_KEYUSAGE_CRL_SIGN)))) return 0; log_info (_("certificate should not have " "been used for OCSP response signing\n")); break; case CERT_USAGE_MODE_CRL: if ((use & (KSBA_KEYUSAGE_CRL_SIGN))) return 0; log_info (_("certificate should not have " "been used for CRL signing\n")); break; } return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return 0 if the certificate CERT is usable for certification. */ gpg_error_t check_cert_use_cert (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_CERT); } /* Return 0 if the certificate CERT is usable for signing OCSP responses. */ gpg_error_t check_cert_use_ocsp (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_OCSP); } /* Return 0 if the certificate CERT is usable for signing CRLs. */ gpg_error_t check_cert_use_crl (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_CRL); } diff --git a/doc/HACKING b/doc/HACKING index fc0c3f459..62a6f9511 100644 --- a/doc/HACKING +++ b/doc/HACKING @@ -1,432 +1,432 @@ # HACKING -*- org -*- #+TITLE: A Hacker's Guide to GnuPG #+TEXT: Some notes on GnuPG internals #+STARTUP: showall #+OPTIONS: ^:{} * How to contribute The following stuff explains some basic procedures you need to follow if you want to contribute code or documentation. ** No more ChangeLog files Do not modify any of the ChangeLog files in GnuPG. Starting on December 1st, 2011 we put change information only in the GIT commit log, and generate a top-level ChangeLog file from logs at "make dist" time. As such, there are strict requirements on the form of the commit log messages. The old ChangeLog files have all be renamed to ChangeLog-2011 ** Commit log requirements Your commit log should always start with a one-line summary, the second line should be blank, and the remaining lines are usually ChangeLog-style entries for all affected files. However, it's fine --- even recommended --- to write a few lines of prose describing the change, when the summary and ChangeLog entries don't give enough of the big picture. Omit the leading TABs that you are seeing in a "real" ChangeLog file, but keep the maximum line length at 72 or smaller, so that the generated ChangeLog lines, each with its leading TAB, will not exceed 80 columns. If you want to add text which shall not be copied to the ChangeLog, separate it by a line consisting of two dashes at the begin of a line. The one-line summary usually starts with a keyword to identify the mainly affected subsystem. If more than one keyword is required the are delimited by a comma (e.g. =scd,w32:=). Commonly found keywords are - agent :: The gpg-agent component - build :: Changes to the build system - ccid :: The CCID driver in scdaemon - common :: Code in common - dirmngr :: The dirmngr component - doc :: Documentation changes - gpg :: The gpg or gpgv components - sm :: The gpgsm component (also "gpgsm") - gpgscm :: The regression test driver - indent :: Indentation and similar changes - iobuf :: The IOBUF system in common - po :: Translations - scd :: The scdaemon component - speedo :: Speedo build system specific changes - ssh :: The ssh-agent part of the agent - tests :: The regressions tests - tools :: Other code in tools - w32 :: Windows related code - wks :: The web key service tools - yat2m :: The yat2m tool. Typo fixes and documentation updates don't need a ChangeLog entry; thus you would use a commit message like #+begin_example doc: Fix typo in a comment -- #+end_example The marker line here is important; without it the first line would appear in the ChangeLog. If you exceptionally need to have longer lines in a commit log you may do this after this scissor line: #+begin_example # ------------------------ >8 ------------------------ #+end_example (hash, blank, 24 dashes, blank, scissor, blank, 24 dashes). Note that such a comment will be removed if the git commit option =--cleanup=scissor= is used. ** License policy GnuPG is licensed under the GPLv3+ with some files under a mixed LGPLv3+/GPLv2+ license. It is thus important, that all contributed code allows for an update of the license; for example we can't accept code under the GPLv2(only). GnuPG used to have a strict policy of requiring copyright assignments to the FSF. To avoid this major organizational overhead and to allow inclusion of code, not copyrighted by the FSF, this policy has been relaxed on 2013-03-29. It is now also possible to contribute code by asserting that the contribution is in accordance to the "Libgcrypt Developer's Certificate of Origin" as found in the file "DCO". (Except for a slight wording change, this DCO is identical to the one used by the Linux kernel.) If you want to contribute code or documentation to GnuPG and you didn't sign a copyright assignment with the FSF in the past, you need to take these simple steps: - Decide which mail address you want to use. Please have your real name in the address and not a pseudonym. Anonymous contributions can only be done if you find a proxy who certifies for you. - If your employer or school might claim ownership of code written by you; you need to talk to them to make sure that you have the right to contribute under the DCO. - Send an OpenPGP signed mail to the gnupg-devel@gnupg.org mailing list from your mail address. Include a copy of the DCO as found in the official master branch. Insert your name and email address into the DCO in the same way you want to use it later. Example: Signed-off-by: Joe R. Hacker (If you really need it, you may perform simple transformations of the mail address: Replacing "@" by " at " or "." by " dot ".) - That's it. From now on you only need to add a "Signed-off-by:" line with your name and mail address to the commit message. It is recommended to send the patches using a PGP/MIME signed mail. ** Coding standards Please follow the GNU coding standards. If you are in doubt consult the existing code as an example. Do no re-indent code without a need. If you really need to do it, use a separate commit for such a change. - Only certain C99 features may be used (see below); in general stick to C90. - Please do not use C++ =//= style comments. - Do not use comments like: #+begin_src if (foo) /* Now that we know that foo is true we can call bar. */ bar (); #+end_src instead write the comment on the if line or before it. You may also use a block and put the comment inside. - Please use asterisks on the left of longer comments. This makes it easier to read without syntax highlighting, on printouts, and for blind people. - Try to fit lines into 80 columns. - Ignore signed/unsigned pointer mismatches - No arithmetic on void pointers; cast to char* first. - Do not use #+begin_src if ( 42 == foo ) #+end_src this is harder to read and modern compilers are pretty good in detecing accidential assignments. It is also suggested not to compare to 0 or NULL but to test the value direct or with a '!'; this makes it easier to see that a boolean test is done. - We use our own printf style functions like =es_printf=, and =gpgrt_asprintf= (or the =es_asprintf= macro) which implement most C99 features with the exception of =wchar_t= (which should anyway not be used). Please use them always and do not resort to those provided by libc. The rationale for using them is that we know that the format specifiers work on all platforms and that we do not need to chase platform dependent bugs. Note also that in gnupg asprintf is a macro already evaluating to gpgrt_asprintf. - It is common to have a label named "leave" for a function's cleanup and return code. This helps with freeing memory and is a convenient location to set a breakpoint for debugging. - Always use xfree() instead of free(). If it is not easy to see that the freed variable is not anymore used, explicitly set the variable to NULL. - New code shall in general use xtrymalloc or xtrycalloc and check for an error (use gpg_error_from_syserror()). - Init function local variables only if needed so that the compiler can do a better job in detecting uninitialized variables which may indicate a problem with the code. - Never init static or file local variables to 0 to make sure they end up in BSS. - Put extra parenthesis around terms with binary operators to make it clear that the binary operator was indeed intended. - Use --enable-maintainer-mode with configure so that all suitable warnings are enabled. ** Variable names Follow the GNU standards. Here are some conventions you may want to stick to (do not rename existing "wrong" uses without a goog reason). - err :: This conveys an error code of type =gpg_error_t= which is compatible to an =int=. To compare such a variable to a GPG_ERR_ constant, it is necessary to map the value like this: =gpg_err_code(err)=. - ec :: This is used for a gpg-error code which has no source part (=gpg_err_code_t=) and will eventually be used as input to =gpg_err_make=. - rc :: Used for all kind of other errors; for example system calls. The value is not compatible with gpg-error. *** C99 language features In GnuPG 2.x, but *not in 1.4* and not in most libraries, a limited set of C99 features may be used: - Variadic macros: : #define foo(a,...) bar(a, __VA_ARGS__) - The predefined macro =__func__=: : log_debug ("%s: Problem with foo\n", __func__); - Variable declaration inside a for(): : for (int i = 0; i < 5; ++) : bar (i); Although we usually make use of the =u16=, =u32=, and =u64= types, it is also possible to include == and use =int16_t=, =int32_t=, =int64_t=, =uint16_t=, =uint32_t=, and =uint64_t=. But do not use =int8_t= or =uint8_t=. ** Commit log keywords - GnuPG-bug-id :: Values are comma or space delimited bug numbers from bug.gnupg.org pertaining to this commit. - Debian-bug-id :: Same as above but from the Debian bug tracker. - CVE-id :: CVE id number pertaining to this commit. - Regression-due-to :: Commit id of the regression fixed by this commit. - Fixes-commit :: Commit id this commit fixes. - Reported-by :: Value is a name or mail address of a bug reporte. - Suggested-by :: Value is a name or mail address of someone how suggested this change. - Co-authored-by :: Name or mail address of a co-author - Some-comments-by :: Name or mail address of the author of additional comments (commit log or code). - Proofread-by :: Sometimes used by translation commits. - Signed-off-by :: Name or mail address of the developer * Windows ** How to build an installer for Windows Your best bet is to use a decent Debian System for development. You need to install a long list of tools for building. This list still needs to be compiled. However, the build process will stop if a tool is missing. GNU make is required (on non GNU systems often installed as "gmake"). The installer requires a couple of extra software to be available either as tarballs or as local git repositories. In case this file here is part of a gnupg-w32-2.*.xz complete tarball as distributed from the same place as a binary installer, all such tarballs are already included. Cd to the GnuPG source directory and use one of one of these command: - If sources are included (gnupg-w32-*.tar.xz) make -f build-aux/speedo.mk WHAT=this installer - To build from tarballs make -f build-aux/speedo.mk WHAT=release TARBALLS=TARDIR installer - To build from local GIT repos make -f build-aux/speedo.mk WHAT=git TARBALLS=TARDIR installer Note that also you need to supply tarballs with supporting libraries even if you build from git. The makefile expects only the core GnuPG software to be available as local GIT repositories. speedo.mk has the versions of the tarballs and the branch names of the git repositories. In case of problems, don't hesitate to ask on the gnupg-devel mailing for help. * Debug hints See the manual for some hints. * Standards ** RFCs 1423 Privacy Enhancement for Internet Electronic Mail: Part III: Algorithms, Modes, and Identifiers. 1489 Registration of a Cyrillic Character Set. 1750 Randomness Recommendations for Security. 1991 PGP Message Exchange Formats (obsolete) 2144 The CAST-128 Encryption Algorithm. 2279 UTF-8, a transformation format of ISO 10646. 2440 OpenPGP (obsolete). 3156 MIME Security with Pretty Good Privacy (PGP). 4880 Current OpenPGP specification. 6337 Elliptic Curve Cryptography (ECC) in OpenPGP * Various information ** Directory Layout - ./ :: Readme, configure - ./agent :: Gpg-agent and related tools - ./doc :: Documentation - ./g10 :: Gpg program here called gpg2 - ./sm :: Gpgsm program - ./jnlib :: Not used (formerly used utility functions) - ./common :: Utility functions - ./kbx :: Keybox library - ./scd :: Smartcard daemon - ./scripts :: Scripts needed by configure and others - ./dirmngr :: The directory manager ** Detailed Roadmap This list of files is not up to date! - g10/gpg.c :: Main module with option parsing and all the stuff you have to do on startup. Also has the exit handler and some helper functions. - g10/parse-packet.c :: - g10/build-packet.c :: - g10/free-packet.c :: Parsing and creating of OpenPGP message packets. - g10/getkey.c :: Key selection code - g10/pkclist.c :: Build a list of public keys - g10/skclist.c :: Build a list of secret keys - g10/keyring.c :: Keyring access functions - g10/keydb.h :: - g10/keyid.c :: Helper functions to get the keyid, fingerprint etc. - g10/trustdb.c :: Web-of-Trust computations - g10/trustdb.h :: - g10/tdbdump.c :: Export/import/list the trustdb.gpg - g10/tdbio.c :: I/O handling for the trustdb.gpg - g10/tdbio.h :: - g10/compress.c :: Filter to handle compression - g10/filter.h :: Declarations for all filter functions - g10/delkey.c :: Delete a key - g10/kbnode.c :: Helper for the kbnode_t linked list - g10/main.h :: Prototypes and some constants - g10/mainproc.c :: Message processing - g10/armor.c :: Ascii armor filter - g10/mdfilter.c :: Filter to calculate hashs - g10/textfilter.c :: Filter to handle CR/LF and trailing white space - g10/cipher.c :: En-/Decryption filter - - g10/misc.c :: Utlity functions + - g10/misc.c :: Utility functions - g10/options.h :: Structure with all the command line options and related constants - g10/openfile.c :: Create/Open Files - g10/keyserver.h :: Keyserver access dispatcher. - g10/packet.h :: Definition of OpenPGP structures. - g10/passphrase.c :: Passphrase handling code - g10/pubkey-enc.c :: Process a public key encoded packet. - g10/seckey-cert.c :: Not anymore used - - g10/seskey.c :: Make sesssion keys etc. + - g10/seskey.c :: Make session keys etc. - g10/import.c :: Import keys into our key storage. - g10/export.c :: Export keys to the OpenPGP format. - g10/sign.c :: Create signature and optionally encrypt. - g10/plaintext.c :: Process plaintext packets. - g10/decrypt-data.c :: Decrypt an encrypted data packet - g10/encrypt.c :: Main encryption driver - g10/revoke.c :: Create recovation certificates. - g10/keylist.c :: Print information about OpenPGP keys - g10/sig-check.c :: Check a signature - g10/helptext.c :: Show online help texts - g10/verify.c :: Verify signed data. - g10/decrypt.c :: Decrypt and verify data. - g10/keyedit.c :: Edit properties of a key. - g10/dearmor.c :: Armor utility. - g10/keygen.c :: Generate a key pair ** Memory allocation Use only the functions: - xmalloc - xmalloc_secure - xtrymalloc - xtrymalloc_secure - xcalloc - xcalloc_secure - xtrycalloc - xtrycalloc_secure - xrealloc - xtryrealloc - xstrdup - xtrystrdup - xfree The *secure versions allocate memory in the secure memory. That is, swapping out of this memory is avoided and is gets overwritten on free. Use this for passphrases, session keys and other sensitive material. This memory set aside for secure memory is linited to a few k. In general the function don't print a memeory message and terminate the process if there is not enough memory available. The "try" versions of the functions return NULL instead. ** Logging TODO ** Option parsing GnuPG does not use getopt or GNU getopt but functions of it's own. See util/argparse.c for details. The advantage of these functions is that it is more easy to display and maintain the help texts for the options. The same option table is also used to parse resource files. ** What is an IOBUF This is the data structure used for most I/O of gnupg. It is similar to System V Streams but much simpler. Because OpenPGP messages are nested in different ways; the use of such a system has big advantages. Here is an example, how it works: If the parser sees a packet header with a partial length, it pushes the block_filter onto the IOBUF to handle these partial length packets: from now on you don't have to worry about this. When it sees a compressed packet it pushes the uncompress filter and the next read byte is one which has already been uncompressed by this filter. Same goes for enciphered packet, plaintext packets and so on. The file g10/encode.c might be a good starting point to see how it is used - actually this is the other way: constructing messages using pushed filters but it may be easier to understand. diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi index 027bb949d..22a794316 100644 --- a/doc/dirmngr.texi +++ b/doc/dirmngr.texi @@ -1,1158 +1,1158 @@ @c Copyright (C) 2002 Klar"alvdalens Datakonsult AB @c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking DIRMNGR @chapter Invoking DIRMNGR @cindex DIRMNGR command options @cindex command options @cindex options, DIRMNGR command @manpage dirmngr.8 @ifset manverb .B dirmngr \- CRL and OCSP daemon @end ifset @mansect synopsis @ifset manverb .B dirmngr .RI [ options ] .I command .RI [ args ] @end ifset @mansect description Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing the OpenPGP keyservers. As with previous versions it is also used as a server for managing and downloading certificate revocation lists (CRLs) for X.509 certificates, downloading X.509 certificates, and providing access to OCSP providers. Dirmngr is invoked internally by @command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent} tool. @manpause @noindent @xref{Option Index},for an index to @command{DIRMNGR}'s commands and options. @mancont @menu * Dirmngr Commands:: List of all commands. * Dirmngr Options:: List of all options. * Dirmngr Configuration:: Configuration files. * Dirmngr Signals:: Use of signals. * Dirmngr Examples:: Some usage examples. * Dirmngr Protocol:: The protocol dirmngr uses. @end menu @node Dirmngr Commands @section Commands @mansect commands Commands are not distinguished from options except for the fact that only one command is allowed. @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help, -h @opindex help Print a usage message summarizing the most useful command-line options. Note that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. The default mode is to create a socket and listen for commands there. This is only used for testing. @item --daemon @opindex daemon Run in background daemon mode and listen for commands on a socket. This is the way @command{dirmngr} is started on demand by the other GnuPG components. To force starting @command{dirmngr} it is in general best to use @code{gpgconf --launch dirmngr}. @item --supervised @opindex supervised Run in the foreground, sending logs to stderr, and listening on file descriptor 3, which must already be bound to a listening socket. This is useful when running under systemd or other similar process supervision schemes. This option is not supported on Windows. @item --list-crls @opindex list-crls List the contents of the CRL cache on @code{stdout}. This is probably only useful for debugging purposes. @item --load-crl @var{file} @opindex load-crl This command requires a filename as additional argument, and it will make Dirmngr try to import the CRL in @var{file} into it's cache. Note, that this is only possible if Dirmngr is able to retrieve the CA's certificate directly by its own means. In general it is better to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command so that @code{gpgsm} can help dirmngr. @item --fetch-crl @var{url} @opindex fetch-crl This command requires an URL as additional argument, and it will make dirmngr try to retrieve and import the CRL from that @var{url} into it's cache. This is mainly useful for debugging purposes. The @command{dirmngr-client} provides the same feature for a running dirmngr. @item --shutdown @opindex shutdown This commands shuts down an running instance of Dirmngr. This command has currently no effect. @item --flush @opindex flush This command removes all CRLs from Dirmngr's cache. Client requests will thus trigger reading of fresh CRLs. @end table @mansect options @node Dirmngr Options @section Option Summary Note that all long options with the exception of @option{--options} and @option{--homedir} may also be given in the configuration file after stripping off the two leading dashes. @table @gnupgtabopt @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{dirmngr.conf} and expected in the home directory. @item --homedir @var{dir} @opindex options Set the name of the home directory to @var{dir}. This option is only effective when used on the command line. The default is the directory named @file{.gnupg} directly below the home directory of the user unless the environment variable @code{GNUPGHOME} has been set in which case its value will be used. Many kinds of data are stored within this directory. @item -v @item --verbose @opindex v @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @sc{dirmngr}, such as @option{-vv}. @item --log-file @var{file} @opindex log-file Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. Use @file{socket://} to log to socket. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or by a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug Set debugging flags. This option is only useful for debugging and its behavior may change with a new release. All flags are or-ed and may be given in C syntax (e.g. 0x0042) or as a comma separated list of flag names. To get a list of all supported flags the single word "help" can be used. @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --gnutls-debug @var{level} @opindex gnutls-debug Enable debugging of GNUTLS at @var{level}. @item --debug-wait @var{n} @opindex debug-wait When running in server mode, wait @var{n} seconds before entering the actual processing loop and print the pid. This gives time to attach a debugger. @item --disable-check-own-socket @opindex disable-check-own-socket On some platforms @command{dirmngr} is able to detect the removal of its socket file and shutdown itself. This option disable this self-test for debugging purposes. @item -s @itemx --sh @itemx -c @itemx --csh @opindex s @opindex sh @opindex c @opindex csh Format the info output in daemon mode for use with the standard Bourne shell respective the C-shell. The default is to guess it based on the environment variable @code{SHELL} which is in almost all cases sufficient. @item --force @opindex force Enabling this option forces loading of expired CRLs; this is only useful for debugging. @item --use-tor @opindex use-tor This option switches Dirmngr and thus GnuPG into ``Tor mode'' to route all network access via Tor (an anonymity network). Certain other features are disabled if this mode is active. @item --standard-resolver @opindex standard-resolver This option forces the use of the system's standard DNS resolver code. This is mainly used for debugging. Note that on Windows a standard resolver is not used and all DNS access will return the error ``Not Implemented'' if this function is used. @item --recursive-resolver @opindex recursive-resolver When possible use a recursive resolver instead of a stub resolver. @item --resolver-timeout @var{n} Set the timeout for the DNS resolver to N seconds. The default are 30 seconds. @item --allow-version-check @opindex allow-version-check Allow Dirmngr to connect to @code{https://versions.gnupg.org} to get the list of current software versions. If this option is enabled the list is retrieved in case the local copy does not exist or is older than 5 to 7 days. See the option @option{--query-swdb} of the command @command{gpgconf} for more details. Note, that regardless of this option a version check can always be triggered using this command: @example gpg-connect-agent --dirmngr 'loadswdb --force' /bye @end example @item --keyserver @var{name} @opindex keyserver Use @var{name} as your keyserver. This is the server that @command{gpg} communicates with to receive keys, send keys, and search for keys. The format of the @var{name} is a URI: `scheme:[//]keyservername[:port]' The scheme is the type of keyserver: "hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP keyservers, or "mailto" for the Graff email keyserver. Note that your particular installation of GnuPG may have other keyserver types available as well. Keyserver schemes are case-insensitive. After the keyserver name, optional keyserver configuration options may be provided. These are the same as the @option{--keyserver-options} of @command{gpg}, but apply only to this particular keyserver. Most keyservers synchronize with each other, so there is generally no need to send keys to more than one server. The keyserver @code{hkp://keys.gnupg.net} uses round robin DNS to give a different keyserver each time you use it. If exactly two keyservers are configured and only one is a Tor hidden service (.onion), Dirmngr selects the keyserver to use depending on whether Tor is locally running or not. The check for a running Tor is done for each new connection. If no keyserver is explicitly configured, dirmngr will use the built-in default of hkps://hkps.pool.sks-keyservers.net. @item --nameserver @var{ipaddr} @opindex nameserver In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS names. If the default public resolver, which is @code{8.8.8.8}, shall not be used a different one can be given using this option. Note that a numerical IP address must be given (IPv6 or IPv4) and that no error checking is done for @var{ipaddr}. @item --disable-ipv4 @item --disable-ipv6 @opindex disable-ipv4 @opindex disable-ipv6 Disable the use of all IPv4 or IPv6 addresses. @item --disable-ldap @opindex disable-ldap Entirely disables the use of LDAP. @item --disable-http @opindex disable-http Entirely disables the use of HTTP. @item --ignore-http-dp @opindex ignore-http-dp When looking for the location of a CRL, the to be tested certificate usually contains so called @dfn{CRL Distribution Point} (DP) entries which are URLs describing the way to access the CRL. The first found DP entry is used. With this option all entries using the @acronym{HTTP} scheme are ignored when looking for a suitable DP. @item --ignore-ldap-dp @opindex ignore-ldap-dp This is similar to @option{--ignore-http-dp} but ignores entries using the @acronym{LDAP} scheme. Both options may be combined resulting in ignoring DPs entirely. @item --ignore-ocsp-service-url @opindex ignore-ocsp-service-url Ignore all OCSP URLs contained in the certificate. The effect is to force the use of the default responder. @item --honor-http-proxy @opindex honor-http-proxy If the environment variable @env{http_proxy} has been set, use its value to access HTTP servers. @item --http-proxy @var{host}[:@var{port}] @opindex http-proxy @efindex http_proxy Use @var{host} and @var{port} to access HTTP servers. The use of this option overrides the environment variable @env{http_proxy} regardless whether @option{--honor-http-proxy} has been set. @item --ldap-proxy @var{host}[:@var{port}] @opindex ldap-proxy Use @var{host} and @var{port} to connect to LDAP servers. If @var{port} is omitted, port 389 (standard LDAP port) is used. This overrides any specified host and port part in a LDAP URL and will also be used if host and port have been omitted from the URL. @item --only-ldap-proxy @opindex only-ldap-proxy Never use anything else but the LDAP "proxy" as configured with @option{--ldap-proxy}. Usually @command{dirmngr} tries to use other configured LDAP server if the connection using the "proxy" failed. @item --ldapserverlist-file @var{file} @opindex ldapserverlist-file Read the list of LDAP servers to consult for CRLs and certificates from file instead of the default per-user ldap server list file. The default value for @var{file} is @file{dirmngr_ldapservers.conf}. This server list file contains one LDAP server per line in the format @sc{hostname:port:username:password:base_dn} Lines starting with a @samp{#} are comments. Note that as usual all strings entered are expected to be UTF-8 encoded. Obviously this will lead to problems if the password has originally been encoded as Latin-1. There is no other solution here than to put such a password in the binary encoding into the file (i.e. non-ascii characters won't show up readable).@footnote{The @command{gpgconf} tool might be helpful for frontends as it enables editing this configuration file using percent-escaped strings.} @item --ldaptimeout @var{secs} @opindex ldaptimeout Specify the number of seconds to wait for an LDAP query before timing out. The default is currently 100 seconds. 0 will never timeout. @item --add-servers @opindex add-servers This option makes dirmngr add any servers it discovers when validating certificates against CRLs to the internal list of servers to consult for certificates and CRLs. This option is useful when trying to validate a certificate that has a CRL distribution point that points to a server that is not already listed in the ldapserverlist. Dirmngr will always go to this server and try to download the CRL, but chances are high that the certificate used to sign the CRL is located on the same server. So if dirmngr doesn't add that new server to list, it will often not be able to verify the signature of the CRL unless the @code{--add-servers} option is used. Note: The current version of dirmngr has this option disabled by default. @item --allow-ocsp @opindex allow-ocsp This option enables OCSP support if requested by the client. OCSP requests are rejected by default because they may violate the privacy of the user; for example it is possible to track the time when a user is reading a mail. @item --ocsp-responder @var{url} @opindex ocsp-responder Use @var{url} as the default OCSP Responder if the certificate does not contain information about an assigned responder. Note, that @code{--ocsp-signer} must also be set to a valid certificate. @item --ocsp-signer @var{fpr}|@var{file} @opindex ocsp-signer Use the certificate with the fingerprint @var{fpr} to check the responses of the default OCSP Responder. Alternatively a filename can be given in which case the response is expected to be signed by one of the certificates described in that file. Any argument which contains a slash, dot or tilde is considered a filename. Usual filename expansion takes place: A tilde at the start followed by a slash is replaced by the content of @env{HOME}, no slash at start describes a relative filename which will be searched at the home directory. To make sure that the @var{file} is searched in the home directory, either prepend the name with "./" or use a name which contains a dot. If a response has been signed by a certificate described by these fingerprints no further check upon the validity of this certificate is done. The format of the @var{FILE} is a list of SHA-1 fingerprint, one per line with optional colons between the bytes. Empty lines and lines prefix with a hash mark are ignored. @item --ocsp-max-clock-skew @var{n} @opindex ocsp-max-clock-skew The number of seconds a skew between the OCSP responder and them local clock is accepted. Default is 600 (10 minutes). @item --ocsp-max-period @var{n} @opindex ocsp-max-period Seconds a response is at maximum considered valid after the time given in the thisUpdate field. Default is 7776000 (90 days). @item --ocsp-current-period @var{n} @opindex ocsp-current-period The number of seconds an OCSP response is considered valid after the time given in the NEXT_UPDATE datum. Default is 10800 (3 hours). @item --max-replies @var{n} @opindex max-replies Do not return more that @var{n} items in one query. The default is 10. @item --ignore-cert-extension @var{oid} @opindex ignore-cert-extension Add @var{oid} to the list of ignored certificate extensions. The @var{oid} is expected to be in dotted decimal form, like @code{2.5.29.3}. This option may be used more than once. Critical flagged certificate extensions matching one of the OIDs in the list are treated as if they are actually handled and thus the certificate won't be rejected due to an unknown critical extension. Use this option with care because extensions are usually flagged as critical for a reason. @item --hkp-cacert @var{file} Use the root certificates in @var{file} for verification of the TLS certificates used with @code{hkps} (keyserver access over TLS). If the file is in PEM format a suffix of @code{.pem} is expected for @var{file}. This option may be given multiple times to add more root certificates. Tilde expansion is supported. If no @code{hkp-cacert} directive is present, dirmngr will make a reasonable choice: if the keyserver in question is the special pool @code{hkps.pool.sks-keyservers.net}, it will use the bundled root certificate for that pool. Otherwise, it will use the system CAs. @end table @c @c Dirmngr Configuration @c @mansect files @node Dirmngr Configuration @section Configuration Dirmngr makes use of several directories when running in daemon mode: There are a few configuration files whih control the operation of dirmngr. By default they may all be found in the current home directory (@pxref{option --homedir}). @table @file @item dirmngr.conf @efindex dirmngr.conf This is the standard configuration file read by @command{dirmngr} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This file is also read after a @code{SIGHUP} however not all options will actually have an effect. This default name may be changed on the command line (@pxref{option --options}). You should backup this file. @item /etc/gnupg/trusted-certs This directory should be filled with certificates of Root CAs you are trusting in checking the CRLs and signing OCSP Responses. Usually these are the same certificates you use with the applications making use of dirmngr. It is expected that each of these certificate files contain exactly one @acronym{DER} encoded certificate in a file with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads those certificates on startup and when given a SIGHUP. Certificates which are not readable or do not make up a proper X.509 certificate are ignored; see the log file for details. Applications using dirmngr (e.g. gpgsm) can request these certificates to complete a trust chain in the same way as with the extra-certs directory (see below). Note that for OCSP responses the certificate specified using the option @option{--ocsp-signer} is always considered valid to sign OCSP requests. @item /etc/gnupg/extra-certs This directory may contain extra certificates which are preloaded into the internal cache on startup. Applications using dirmngr (e.g. gpgsm) can request cached certificates to complete a trust chain. This is convenient in cases you have a couple intermediate CA certificates or certificates usually used to sign OCSP responses. These certificates are first tried before going out to the net to look for them. These certificates must also be @acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}. @item ~/.gnupg/crls.d This directory is used to store cached CRLs. The @file{crls.d} part will be created by dirmngr if it does not exists but you need to make sure that the upper directory exists. @end table @manpause To be able to see what's going on you should create the configure file @file{~/gnupg/dirmngr.conf} with at least one line: @example log-file ~/dirmngr.log @end example To be able to perform OCSP requests you probably want to add the line: @example allow-ocsp @end example To make sure that new options are read and that after the installation of a new GnuPG versions the installed dirmngr is running, you may want to kill an existing dirmngr first: @example gpgconf --kill dirmngr @end example You may check the log file to see whether all desired root certificates have been loaded correctly. @c @c Dirmngr Signals @c @mansect signals @node Dirmngr Signals @section Use of signals A running @command{dirmngr} may be controlled by signals, i.e. using the @command{kill} command to send a signal to the process. Here is a list of supported signals: @table @gnupgtabopt @item SIGHUP @cpindex SIGHUP This signal flushes all internally cached CRLs as well as any cached certificates. Then the certificate cache is reinitialized as on startup. Options are re-read from the configuration file. Instead of sending this signal it is better to use @example gpgconf --reload dirmngr @end example @item SIGTERM @cpindex SIGTERM Shuts down the process but waits until all current requests are fulfilled. If the process has received 3 of these signals and requests are still pending, a shutdown is forced. You may also use @example gpgconf --kill dirmngr @end example instead of this signal @item SIGINT @cpindex SIGINT Shuts down the process immediately. @item SIGUSR1 @cpindex SIGUSR1 This prints some caching statistics to the log file. @end table @c @c Examples @c @mansect examples @node Dirmngr Examples @section Examples Here is an example on how to show dirmngr's internal table of OpenPGP keyserver addresses. The output is intended for debugging purposes and not part of a defined API. @example gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye @end example To inhibit the use of a particular host you have noticed in one of the keyserver pools, you may use @example gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye @end example The description of the @code{keyserver} command can be printed using @example gpg-connect-agent --dirmngr 'help keyserver' /bye @end example @c @c Assuan Protocol @c @manpause @node Dirmngr Protocol @section Dirmngr's Assuan Protocol Assuan is the IPC protocol used to access dirmngr. This is a description of the commands implemented by dirmngr. @menu * Dirmngr LOOKUP:: Look up a certificate via LDAP * Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP. * Dirmngr CHECKCRL:: Validate a certificate using a CRL. * Dirmngr CHECKOCSP:: Validate a certificate using OCSP. * Dirmngr CACHECERT:: Put a certificate into the internal cache. * Dirmngr VALIDATE:: Validate a certificate for debugging. @end menu @node Dirmngr LOOKUP @subsection Return the certificate(s) found Lookup certificate. To allow multiple patterns (which are ORed) quoting is required: Spaces are to be translated into "+" or into "%20"; obviously this requires that the usual escape quoting rules are applied. The server responds with: @example S: D S: END S: D S: END S: OK @end example In this example 2 certificates are returned. The server may return any number of certificates; OK will also be returned when no certificates were found. The dirmngr might return a status line @example S: S TRUNCATED @end example To indicate that the output was truncated to N items due to a limitation of the server or by an arbitrary set limit. The option @option{--url} may be used if instead of a search pattern a complete URL to the certificate is known: @example C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate @end example If the option @option{--cache-only} is given, no external lookup is done so that only certificates from the cache are returned. With the option @option{--single}, the first and only the first match will be returned. Unless option @option{--cache-only} is also used, no local lookup will be done in this case. @node Dirmngr ISVALID @subsection Validate a certificate using a CRL or OCSP @example ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr} @end example Check whether the certificate described by the @var{certid} has been revoked. Due to caching, the Dirmngr is able to answer immediately in most cases. The @var{certid} is a hex encoded string consisting of two parts, delimited by a single dot. The first part is the SHA-1 hash of the issuer name and the second part the serial number. Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be given in which case an OCSP request is done before consulting the CRL. If the option @option{--only-ocsp} is given, no fallback to a CRL check will be used. If the option @option{--force-default-responder} is given, only the default OCSP responder will be used and any other methods of obtaining an OCSP responder URL won't be used. @noindent Common return values are: @table @code @item GPG_ERR_NO_ERROR (0) This is the positive answer: The certificate is not revoked and we have an up-to-date revocation list for that certificate. If OCSP was used the responder confirmed that the certificate has not been revoked. @item GPG_ERR_CERT_REVOKED This is the negative answer: The certificate has been revoked. Either it is in a CRL and that list is up to date or an OCSP responder informed us that it has been revoked. @item GPG_ERR_NO_CRL_KNOWN No CRL is known for this certificate or the CRL is not valid or out of date. @item GPG_ERR_NO_DATA The OCSP responder returned an ``unknown'' status. This means that it is not aware of the certificate's status. @item GPG_ERR_NOT_SUPPORTED This is commonly seen if OCSP support has not been enabled in the configuration. @end table If DirMngr has not enough information about the given certificate (which is the case for not yet cached certificates), it will inquire the missing data: @example S: INQUIRE SENDCERT C: D C: END @end example A client should be aware that DirMngr may ask for more than one certificate. If Dirmngr has a certificate but the signature of the certificate could not been validated because the root certificate is not known to dirmngr as trusted, it may ask back to see whether the client trusts this the root certificate: @example S: INQUIRE ISTRUSTED C: D 1 C: END @end example Only this answer will let Dirmngr consider the certificate as valid. @node Dirmngr CHECKCRL @subsection Validate a certificate using a CRL Check whether the certificate with FINGERPRINT (SHA-1 hash of the entire X.509 certificate blob) is valid or not by consulting the CRL responsible for this certificate. If the fingerprint has not been given or the certificate is not known, the function inquires the certificate using: @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request (which should match FINGERPRINT) as a binary blob. Processing then takes place without further interaction; in particular dirmngr tries to locate other required certificate by its own mechanism which includes a local certificate store as well as a list of trusted root certificates. @noindent The return code is 0 for success; i.e. the certificate has not been revoked or one of the usual error codes from libgpg-error. @node Dirmngr CHECKOCSP @subsection Validate a certificate using OCSP @example CHECKOCSP [--force-default-responder] [@var{fingerprint}] @end example Check whether the certificate with @var{fingerprint} (the SHA-1 hash of the entire X.509 certificate blob) is valid by consulting the appropriate OCSP responder. If the fingerprint has not been given or the certificate is not known by Dirmngr, the function inquires the certificate using: @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request (which should match @var{fingerprint}) as a binary blob. Processing then takes place without further interaction; in particular dirmngr tries to locate other required certificates by its own mechanism which includes a local certificate store as well as a list of trusted root certificates. If the option @option{--force-default-responder} is given, only the default OCSP responder is used. This option is the per-command variant of the global option @option{--ignore-ocsp-service-url}. @noindent The return code is 0 for success; i.e. the certificate has not been revoked or one of the usual error codes from libgpg-error. @node Dirmngr CACHECERT @subsection Put a certificate into the internal cache Put a certificate into the internal cache. This command might be useful if a client knows in advance certificates required for a test and wants to make sure they get added to the internal cache. It is also helpful for debugging. To get the actual certificate, this command immediately inquires it using @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request as a binary blob. @noindent The return code is 0 for success; i.e. the certificate has not been successfully cached or one of the usual error codes from libgpg-error. @node Dirmngr VALIDATE @subsection Validate a certificate for debugging Validate a certificate using the certificate validation function used internally by dirmngr. This command is only useful for debugging. To get the actual certificate, this command immediately inquires it using @example S: INQUIRE TARGETCERT C: D C: END @end example Thus the caller is expected to return the certificate for the request as a binary blob. @mansect see also @ifset isman @command{gpgsm}(1), @command{dirmngr-client}(1) @end ifset @include see-also-note.texi @c @c !!! UNDER CONSTRUCTION !!! @c @c @c @section Verifying a Certificate @c @c There are several ways to request services from Dirmngr. Almost all of @c them are done using the Assuan protocol. What we describe here is the @c Assuan command CHECKCRL as used for example by the dirmnr-client tool if @c invoked as @c @c @example @c dirmngr-client foo.crt @c @end example @c @c This command will send an Assuan request to an already running Dirmngr @c instance. foo.crt is expected to be a standard X.509 certificate and @c dirmngr will receive the Assuan command @c @c @example @c CHECKCRL @var [{fingerprint}] @c @end example @c @c @var{fingerprint} is optional and expected to be the SHA-1 has of the @c DER encoding of the certificate under question. It is to be HEX @c encoded. The rationale for sending the fingerprint is that it allows @c dirmngr to reply immediately if it has already cached such a request. If @c this is not the case and no certificate has been found in dirmngr's @c internal certificate storage, dirmngr will request the certificate using @c the Assuan inquiry @c @c @example @c INQUIRE TARGETCERT @c @end example @c @c The caller (in our example dirmngr-client) is then expected to return @c the certificate for the request (which should match @var{fingerprint}) @c as a binary blob. @c @c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This @c function checks whether a CRL item exists for target certificate. These @c CRL items are kept in a database of already loaded and verified CRLs. @c This mechanism is called the CRL cache. Obviously timestamps are kept @c there with each item to cope with the expiration date of the CRL. The @c possible return values are: @code{0} to indicate that a valid CRL is @c available for the certificate and the certificate itself is not listed @c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is @c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no @c information is available. The first two codes are immediately returned to @c the caller and the processing of this request has been done. @c @c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now @c calls @code{clr_cache_reload_crl} and if this succeeds calls @c @code{crl_cache_cert_isvald) once more. All further errors are @c immediately returned to the caller. @c @c @code{crl_cache_reload_crl} is the actual heart of the CRL management. @c It locates the corresponding CRL for the target certificate, reads and @c verifies this CRL and stores it in the CRL cache. It works like this: @c @c * Loop over all crlDPs in the target certificate. @c * If the crlDP is invalid immediately terminate the loop. @c * Loop over all names in the current crlDP. @c * If the URL scheme is unknown or not enabled @c (--ignore-http-dp, --ignore-ldap-dp) continues with @c the next name. @c * @code{crl_fetch} is called to actually retrieve the CRL. @c In case of problems this name is ignore and we continue with @c the next name. Note that @code{crl_fetch} does only return @c a descriptor for the CRL for further reading so does the CRL @c does not yet end up in memory. @c * @code{crl_cache_insert} is called with that descriptor to @c actually read the CRL into the cache. See below for a @c description of this function. If there is any error (e.g. read @c problem, CRL not correctly signed or verification of signature @c not possible), this descriptor is rejected and we continue @c with the next name. If the CRL has been successfully loaded, @c the loop is terminated. @c * If no crlDP has been found in the previous loop use a default CRL. @c Note, that if any crlDP has been found but loading of the CRL failed, @c this condition is not true. @c * Try to load a CRL from all configured servers (ldapservers.conf) @c in turn. The first server returning a CRL is used. @c * @code(crl_cache_insert) is then used to actually insert the CRL @c into the cache. If this failed we give up immediately without @c checking the rest of the servers from the first step. @c * Ready. @c @c @c The @code{crl_cache_insert} function takes care of reading the bulk of @c the CRL, parsing it and checking the signature. It works like this: A @c new database file is created using a temporary file name. The CRL @c parsing machinery is started and all items of the CRL are put into @c this database file. At the end the issuer certificate of the CRL @c needs to be retrieved. Three cases are to be distinguished: @c @c a) An authorityKeyIdentifier with an issuer and serialno exits: The @c certificate is retrieved using @code{find_cert_bysn}. If @c the certificate is in the certificate cache, it is directly @c returned. Then the requester (i.e. the client who requested the @c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether @c he can provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested issuer and seriano (This is @c needed because the LDAP layer may return several certificates as @c LDAP as no standard way to retrieve by serial number). @c @c b) An authorityKeyIdentifier with a key ID exists: The certificate is @c retrieved using @code{find_cert_bysubject}. If the certificate is @c in the certificate cache, it is directly returned. Then the @c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether @c he can provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested subject and key ID. @c @c c) No authorityKeyIdentifier exits: The certificate is retrieved @c using @code{find_cert_bysubject} without the key ID argument. If @c the certificate is in the certificate cache the first one with a @c matching subject is directly returned. Then the requester is @c asked via the Assuan inquiry ``SENDCERT'' and an exact @c specification of the subject whether he can @c provide this certificate. If this succeed the returned @c certificate gets cached and returned. Note, that dirmngr does not @c verify in any way whether the expected certificate is returned. @c It is in the interest of the client to return a useful certificate @c as otherwise the service request will fail due to a bad signature. @c The last way to get the certificate is by looking it up at @c external resources. This is done using the @code{ca_cert_fetch} @c and @code{fetch_next_ksba_cert} and comparing the returned @c certificate to match the requested subject; the first certificate @c with a matching subject is then returned. @c @c If no certificate was found, the function returns with the error @c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails, @c the erro is returned. On success the @code{validate_cert_chain} is @c used to verify that the certificate is actually valid. @c @c Here we may encounter a recursive situation: @c @code{validate_cert_chain} needs to look at other certificates and @c also at CRLs to check whether these other certificates and well, the @c CRL issuer certificate itself are not revoked. FIXME: We need to make @c sure that @code{validate_cert_chain} does not try to lookup the CRL we @c are currently processing. This would be a catch-22 and may indicate a @c broken PKI. However, due to overlapping expiring times and imprecise @c clocks this may actually happen. @c @c For historical reasons the Assuan command ISVALID is a bit different @c to CHECKCRL but this is mainly due to different calling conventions. @c In the end the same fucntionality is used, albeit hidden by a couple @c of indirection and argument and result code mangling. It furthere @c ingetrages OCSP checking depending on options are the way it is @c called. GPGSM still uses this command but might eventuall switch over @c to CHECKCRL and CHECKOCSP so that ISVALID can be retired. @c @c @c @section Validating a certificate @c @c We describe here how the internal function @code{validate_cert_chain} @c works. Note that mainly testing purposes this functionality may be @c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}. @c @c The function takes the target certificate and a mode argument as @c parameters and returns an error code and optionally the closes @c expiration time of all certificates in the chain. @c @c We first check that the certificate may be used for the requested @c purpose (i.e. OCSP or CRL signing). If this is not the case @c GPG_ERR_WRONG_KEY_USAGE is returned. @c @c The next step is to find the trust anchor (root certificate) and to @c assemble the chain in memory: Starting with the target certificate, @c the expiration time is checked against the current date, unknown @c critical extensions are detected and certificate policies are matched @c (We only allow 2.289.9.9 but I have no clue about that OID and from @c where I got it - it does not even seem to be assigned - debug cruft?). @c @c Now if this certificate is a self-signed one, we have reached the @c trust anchor. In this case we check that the signature is good, the @c certificate is allowed to act as a CA, that it is a trusted one (by @c checking whether it is has been put into the trusted-certs @c configuration directory) and finally prepend into to our list @c representing the certificate chain. This steps ends then. @c @c If it is not a self-signed certificate, we check that the chain won't @c get too long (current limit is 100), if this is the case we terminate @c with the error GPG_ERR_BAD_CERT_CHAIN. @c @c Now the issuer's certificate is looked up: If an @c authorityKeyIdentifier is available, this one is used to locate the @c certificate either using issuer and serialnumber or subject DN @c (i.e. the issuer's DN) and the keyID. The functions @c @code{find_cert_bysn) and @code{find_cert_bysubject} are used @c respectively. The have already been described above under the @c description of @code{crl_cache_insert}. If no certificate was found @c or with no authorityKeyIdentifier, only the cache is consulted using @c @code{get_cert_bysubject}. The latter is done under the assumption @c that a matching certificate has explicitly been put into the @c certificate cache. If the issuer's certificate could not be found, @c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}. @c @c If the issuer's certificate has been found, the signature of the @c actual certificate is checked and in case this fails the error @c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the @c maximum chain length of the issuing certificate is checked as well as @c the capability of the certificate (i.e. whether he may be used for @c certificate signing). Then the certificate is prepended to our list @c representing the certificate chain. Finally the loop is continued now @c with the issuer's certificate as the current certificate. @c @c After the end of the loop and if no error as been encountered @c (i.e. the certificate chain has been assempled correctly), a check is @c done whether any certificate expired or a critical policy has not been @c met. In any of these cases the validation terminates with an @c appropriate error. @c @c Finally the function @code{check_revocations} is called to verify no @c certificate in the assempled chain has been revoked: This is an @c recursive process because a CRL has to be checked for each certificate @c in the chain except for the root certificate, of which we already know @c that it is trusted and we avoid checking a CRL here due to common -@c setup problems and the assumption that a revoked root certifcate has +@c setup problems and the assumption that a revoked root certificate has @c been removed from the list of trusted certificates. @c @c @c @c @c @section Looking up certificates through LDAP. @c @c This describes the LDAP layer to retrieve certificates. @c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are @c used for this. The first one starts a search and the second one is @c used to retrieve certificate after certificate. @c diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 6aab646f0..edcdf1a84 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -1,1543 +1,1543 @@ @c Copyright (C) 2002 Free Software Foundation, Inc. @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking GPG-AGENT @chapter Invoking GPG-AGENT @cindex GPG-AGENT command options @cindex command options @cindex options, GPG-AGENT command @manpage gpg-agent.1 @ifset manverb .B gpg-agent \- Secret key management for GnuPG @end ifset @mansect synopsis @ifset manverb .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .br .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-server .br .B gpg-agent .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .B \-\-daemon .RI [ command_line ] @end ifset @mansect description @command{gpg-agent} is a daemon to manage secret (private) keys independently from any protocol. It is used as a backend for @command{gpg} and @command{gpgsm} as well as for a couple of other utilities. The agent is automatically started on demand by @command{gpg}, @command{gpgsm}, @command{gpgconf}, or @command{gpg-connect-agent}. Thus there is no reason to start it manually. In case you want to use the included Secure Shell Agent you may start the agent using: @c From dkg on gnupg-devel on 2016-04-21: @c @c Here's an attempt at writing a short description of the goals of an @c isolated cryptographic agent: @c @c A cryptographic agent should control access to secret key material. @c The agent permits use of the secret key material by a supplicant @c without providing a copy of the secret key material to the supplicant. @c @c An isolated cryptographic agent separates the request for use of @c secret key material from permission for use of secret key material. @c That is, the system or process requesting use of the key (the @c "supplicant") can be denied use of the key by the owner/operator of @c the agent (the "owner"), which the supplicant has no control over. @c @c One way of enforcing this split is a per-key or per-session @c passphrase, known only by the owner, which must be supplied to the @c agent to permit the use of the secret key material. Another way is @c with an out-of-band permission mechanism (e.g. a button or GUI @c interface that the owner has access to, but the supplicant does not). @c @c The rationale for this separation is that it allows access to the @c secret key to be tightly controlled and audited, and it doesn't permit @c the supplicant to either copy the key or to override the owner's @c intentions. @example gpg-connect-agent /bye @end example @noindent If you want to manually terminate the currently-running agent, you can safely do so with: @example gpgconf --kill gpg-agent @end example @noindent @efindex GPG_TTY You should always add the following lines to your @code{.bashrc} or whatever initialization file is used for all shell invocations: @smallexample GPG_TTY=$(tty) export GPG_TTY @end smallexample @noindent It is important that this environment variable always reflects the output of the @code{tty} command. For W32 systems this option is not required. Please make sure that a proper pinentry program has been installed under the default filename (which is system dependent) or use the option @option{pinentry-program} to specify the full name of that program. It is often useful to install a symbolic link from the actual used pinentry (e.g. @file{@value{BINDIR}/pinentry-gtk}) to the expected one (e.g. @file{@value{BINDIR}/pinentry}). @manpause @noindent @xref{Option Index}, for an index to @command{GPG-AGENT}'s commands and options. @mancont @menu * Agent Commands:: List of all commands. * Agent Options:: List of all options. * Agent Configuration:: Configuration files. * Agent Signals:: Use of some signals. * Agent Examples:: Some usage examples. * Agent Protocol:: The protocol the agent uses. @end menu @mansect commands @node Agent Commands @section Commands Commands are not distinguished from options except for the fact that only one command is allowed. @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help @itemx -h @opindex help Print a usage message summarizing the most useful command-line options. Note that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. The default mode is to create a socket and listen for commands there. @item --daemon [@var{command line}] @opindex daemon Start the gpg-agent as a daemon; that is, detach it from the console and run it in the background. As an alternative you may create a new process as a child of gpg-agent: @code{gpg-agent --daemon /bin/sh}. This way you get a new shell with the environment setup properly; after you exit from this shell, gpg-agent terminates within a few seconds. @item --supervised @opindex supervised Run in the foreground, sending logs by default to stderr, and listening on provided file descriptors, which must already be bound to listening sockets. This command is useful when running under systemd or other similar process supervision schemes. This option is not supported on Windows. In --supervised mode, different file descriptors can be provided for use as different socket types (e.g. ssh, extra) as long as they are identified in the environment variable @code{LISTEN_FDNAMES} (see sd_listen_fds(3) on some Linux distributions for more information on this convention). @end table @mansect options @node Agent Options @section Option Summary @table @gnupgtabopt @anchor{option --options} @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{gpg-agent.conf} and expected in the @file{.gnupg} directory directly below the home directory of the user. @anchor{option --homedir} @include opt-homedir.texi @item -v @item --verbose @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @command{gpgsm}, such as @samp{-vv}. @item -q @item --quiet @opindex quiet Try to be as quiet as possible. @item --batch @opindex batch Don't invoke a pinentry or do any other thing requiring human interaction. @item --faked-system-time @var{epoch} @opindex faked-system-time This option is only useful for testing; it sets the system time back or forth to @var{epoch} which is the number of seconds elapsed since the year 1970. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug This option is only useful for debugging and the behavior may change at any time without notice. FLAGS are bit encoded and may be given in usual C-Syntax. The currently defined bits are: @table @code @item 0 (1) X.509 or OpenPGP protocol related data @item 1 (2) values of big number integers @item 2 (4) low level crypto operations @item 5 (32) memory allocation @item 6 (64) caching @item 7 (128) show memory statistics @item 9 (512) write hashed data to files named @code{dbgmd-000*} @item 10 (1024) trace Assuan protocol @item 12 (4096) bypass all certificate validation @end table @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --debug-wait @var{n} @opindex debug-wait When running in server mode, wait @var{n} seconds before entering the actual processing loop and print the pid. This gives time to attach a debugger. @item --debug-quick-random @opindex debug-quick-random This option inhibits the use of the very secure random quality level (Libgcrypt’s @code{GCRY_VERY_STRONG_RANDOM}) and degrades all request down to standard random quality. It is only used for testing and should not be used for any production quality keys. This option is only effective when given on the command line. On GNU/Linux, another way to quickly generate insecure keys is to use @command{rngd} to fill the kernel's entropy pool with lower quality random data. @command{rngd} is typically provided by the @command{rng-tools} package. It can be run as follows: @samp{sudo rngd -f -r /dev/urandom}. @item --debug-pinentry @opindex debug-pinentry This option enables extra debug information pertaining to the Pinentry. As of now it is only useful when used along with @code{--debug 1024}. @item --no-detach @opindex no-detach Don't detach the process from the console. This is mainly useful for debugging. @item -s @itemx --sh @itemx -c @itemx --csh @opindex sh @opindex csh @efindex SHELL Format the info output in daemon mode for use with the standard Bourne shell or the C-shell respectively. The default is to guess it based on the environment variable @code{SHELL} which is correct in almost all cases. @item --no-grab @opindex no-grab Tell the pinentry not to grab the keyboard and mouse. This option should in general not be used to avoid X-sniffing attacks. @anchor{option --log-file} @item --log-file @var{file} @opindex log-file @efindex HKCU\Software\GNU\GnuPG:DefaultLogFile Append all logging output to @var{file}. This is very helpful in seeing what the agent actually does. Use @file{socket://} to log to socket. If neither a log file nor a log file descriptor has been set on a Windows platform, the Registry entry @code{HKCU\Software\GNU\GnuPG:DefaultLogFile}, if set, is used to specify the logging output. @anchor{option --no-allow-mark-trusted} @item --no-allow-mark-trusted @opindex no-allow-mark-trusted Do not allow clients to mark keys as trusted, i.e. put them into the @file{trustlist.txt} file. This makes it harder for users to inadvertently accept Root-CA keys. @anchor{option --allow-preset-passphrase} @item --allow-preset-passphrase @opindex allow-preset-passphrase This option allows the use of @command{gpg-preset-passphrase} to seed the internal cache of @command{gpg-agent} with passphrases. @anchor{option --no-allow-loopback-pinentry} @item --no-allow-loopback-pinentry @item --allow-loopback-pinentry @opindex no-allow-loopback-pinentry @opindex allow-loopback-pinentry Disallow or allow clients to use the loopback pinentry features; see the option @option{pinentry-mode} for details. Allow is the default. The @option{--force} option of the Assuan command @command{DELETE_KEY} is also controlled by this option: The option is ignored if a loopback pinentry is disallowed. @item --no-allow-external-cache @opindex no-allow-external-cache Tell Pinentry not to enable features which use an external cache for passphrases. Some desktop environments prefer to unlock all credentials with one master password and may have installed a Pinentry which employs an additional external cache to implement such a policy. By using this option the Pinentry is advised not to make use of such a cache and instead always ask the user for the requested passphrase. @item --allow-emacs-pinentry @opindex allow-emacs-pinentry Tell Pinentry to allow features to divert the passphrase entry to a running Emacs instance. How this is exactly handled depends on the version of the used Pinentry. @item --ignore-cache-for-signing @opindex ignore-cache-for-signing This option will let @command{gpg-agent} bypass the passphrase cache for all signing operation. Note that there is also a per-session option to control this behavior but this command line option takes precedence. @item --default-cache-ttl @var{n} @opindex default-cache-ttl Set the time a cache entry is valid to @var{n} seconds. The default is 600 seconds. Each time a cache entry is accessed, the entry's timer is reset. To set an entry's maximum lifetime, use @command{max-cache-ttl}. @item --default-cache-ttl-ssh @var{n} @opindex default-cache-ttl Set the time a cache entry used for SSH keys is valid to @var{n} seconds. The default is 1800 seconds. Each time a cache entry is accessed, the entry's timer is reset. To set an entry's maximum lifetime, use @command{max-cache-ttl-ssh}. @item --max-cache-ttl @var{n} @opindex max-cache-ttl Set the maximum time a cache entry is valid to @var{n} seconds. After this time a cache entry will be expired even if it has been accessed recently or has been set using @command{gpg-preset-passphrase}. The default is 2 hours (7200 seconds). @item --max-cache-ttl-ssh @var{n} @opindex max-cache-ttl-ssh Set the maximum time a cache entry used for SSH keys is valid to @var{n} seconds. After this time a cache entry will be expired even if it has been accessed recently or has been set using @command{gpg-preset-passphrase}. The default is 2 hours (7200 seconds). @item --enforce-passphrase-constraints @opindex enforce-passphrase-constraints Enforce the passphrase constraints by not allowing the user to bypass them using the ``Take it anyway'' button. @item --min-passphrase-len @var{n} @opindex min-passphrase-len Set the minimal length of a passphrase. When entering a new passphrase shorter than this value a warning will be displayed. Defaults to 8. @item --min-passphrase-nonalpha @var{n} @opindex min-passphrase-nonalpha Set the minimal number of digits or special characters required in a passphrase. When entering a new passphrase with less than this number of digits or special characters a warning will be displayed. Defaults to 1. @item --check-passphrase-pattern @var{file} @opindex check-passphrase-pattern Check the passphrase against the pattern given in @var{file}. When entering a new passphrase matching one of these pattern a warning will be displayed. @var{file} should be an absolute filename. The default is not to use any pattern file. Security note: It is known that checking a passphrase against a list of pattern or even against a complete dictionary is not very effective to enforce good passphrases. Users will soon figure up ways to bypass such a policy. A better policy is to educate users on good security behavior and optionally to run a passphrase cracker regularly on all users passphrases to catch the very simple ones. @item --max-passphrase-days @var{n} @opindex max-passphrase-days Ask the user to change the passphrase if @var{n} days have passed since the last change. With @option{--enforce-passphrase-constraints} set the user may not bypass this check. @item --enable-passphrase-history @opindex enable-passphrase-history This option does nothing yet. @item --pinentry-invisible-char @var{char} @opindex pinentry-invisible-char This option asks the Pinentry to use @var{char} for displaying hidden characters. @var{char} must be one character UTF-8 string. A Pinentry may or may not honor this request. @item --pinentry-timeout @var{n} @opindex pinentry-timeout This option asks the Pinentry to timeout after @var{n} seconds with no user input. The default value of 0 does not ask the pinentry to timeout, however a Pinentry may use its own default timeout value in this case. A Pinentry may or may not honor this request. @item --pinentry-program @var{filename} @opindex pinentry-program Use program @var{filename} as the PIN entry. The default is installation dependent. With the default configuration the name of the default pinentry is @file{pinentry}; if that file does not exist but a @file{pinentry-basic} exist the latter is used. On a Windows platform the default is to use the first existing program from this list: @file{bin\pinentry.exe}, @file{..\Gpg4win\bin\pinentry.exe}, @file{..\Gpg4win\pinentry.exe}, @file{..\GNU\GnuPG\pinentry.exe}, @file{..\GNU\bin\pinentry.exe}, @file{bin\pinentry-basic.exe} where the file names are relative to the GnuPG installation directory. @item --pinentry-touch-file @var{filename} @opindex pinentry-touch-file By default the filename of the socket gpg-agent is listening for requests is passed to Pinentry, so that it can touch that file before exiting (it does this only in curses mode). This option changes the file passed to Pinentry to @var{filename}. The special name @code{/dev/null} may be used to completely disable this feature. Note that Pinentry will not create that file, it will only change the modification and access time. @item --scdaemon-program @var{filename} @opindex scdaemon-program Use program @var{filename} as the Smartcard daemon. The default is installation dependent and can be shown with the @command{gpgconf} command. @item --disable-scdaemon @opindex disable-scdaemon Do not make use of the scdaemon tool. This option has the effect of disabling the ability to do smartcard operations. Note, that enabling this option at runtime does not kill an already forked scdaemon. @item --disable-check-own-socket @opindex disable-check-own-socket @command{gpg-agent} employs a periodic self-test to detect a stolen socket. This usually means a second instance of @command{gpg-agent} has taken over the socket and @command{gpg-agent} will then terminate itself. This option may be used to disable this self-test for debugging purposes. @item --use-standard-socket @itemx --no-use-standard-socket @itemx --use-standard-socket-p @opindex use-standard-socket @opindex no-use-standard-socket @opindex use-standard-socket-p Since GnuPG 2.1 the standard socket is always used. These options have no more effect. The command @code{gpg-agent --use-standard-socket-p} will thus always return success. @item --display @var{string} @itemx --ttyname @var{string} @itemx --ttytype @var{string} @itemx --lc-ctype @var{string} @itemx --lc-messages @var{string} @itemx --xauthority @var{string} @opindex display @opindex ttyname @opindex ttytype @opindex lc-ctype @opindex lc-messages @opindex xauthority These options are used with the server mode to pass localization information. @item --keep-tty @itemx --keep-display @opindex keep-tty @opindex keep-display Ignore requests to change the current @code{tty} or X window system's @code{DISPLAY} variable respectively. This is useful to lock the pinentry to pop up at the @code{tty} or display you started the agent. @anchor{option --extra-socket} @item --extra-socket @var{name} @opindex extra-socket The extra socket is created by default, you may use this option to change the name of the socket. To disable the creation of the socket use ``none'' or ``/dev/null'' for @var{name}. Also listen on native gpg-agent connections on the given socket. The intended use for this extra socket is to setup a Unix domain socket forwarding from a remote machine to this socket on the local machine. A @command{gpg} running on the remote machine may then connect to the local gpg-agent and use its private keys. This enables decrypting or signing data on a remote machine without exposing the private keys to the remote machine. @anchor{option --enable-extended-key-format} @item --enable-extended-key-format @opindex enable-extended-key-format This option creates keys in the extended private key format. Changing the passphrase of a key will also convert the key to that new format. Using this option makes the private keys unreadable for gpg-agent versions before 2.1.12. The advantage of the extended private key format is that it is text based and can carry additional meta data. Note that this option also changes the key protection format to use OCB mode. @anchor{option --enable-ssh-support} @item --enable-ssh-support @itemx --enable-putty-support @opindex enable-ssh-support @opindex enable-putty-support The OpenSSH Agent protocol is always enabled, but @command{gpg-agent} will only set the @code{SSH_AUTH_SOCK} variable if this flag is given. In this mode of operation, the agent does not only implement the gpg-agent protocol, but also the agent protocol used by OpenSSH (through a separate socket). Consequently, it should be possible to use the gpg-agent as a drop-in replacement for the well known ssh-agent. SSH Keys, which are to be used through the agent, need to be added to the gpg-agent initially through the ssh-add utility. When a key is added, ssh-add will ask for the password of the provided key file and send the unprotected key material to the agent; this causes the gpg-agent to ask for a passphrase, which is to be used for encrypting the newly received key and storing it in a gpg-agent specific directory. Once a key has been added to the gpg-agent this way, the gpg-agent will be ready to use the key. Note: in case the gpg-agent receives a signature request, the user might need to be prompted for a passphrase, which is necessary for decrypting the stored key. Since the ssh-agent protocol does not contain a mechanism for telling the agent on which display/terminal it is running, gpg-agent's ssh-support will use the TTY or X display where gpg-agent has been started. To switch this display to the current one, the following command may be used: @smallexample gpg-connect-agent updatestartuptty /bye @end smallexample Although all GnuPG components try to start the gpg-agent as needed, this is not possible for the ssh support because ssh does not know about it. Thus if no GnuPG tool which accesses the agent has been run, there is no guarantee that ssh is able to use gpg-agent for authentication. To fix this you may start gpg-agent if needed using this simple command: @smallexample gpg-connect-agent /bye @end smallexample Adding the @option{--verbose} shows the progress of starting the agent. The @option{--enable-putty-support} is only available under Windows and allows the use of gpg-agent with the ssh implementation @command{putty}. This is similar to the regular ssh-agent support but makes use of Windows message queue as required by @command{putty}. @end table All the long options may also be given in the configuration file after stripping off the two leading dashes. @mansect files @node Agent Configuration @section Configuration There are a few configuration files needed for the operation of the agent. By default they may all be found in the current home directory (@pxref{option --homedir}). @table @file @item gpg-agent.conf @efindex gpg-agent.conf This is the standard configuration file read by @command{gpg-agent} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This file is also read after a @code{SIGHUP} however only a few options will actually have an effect. This default name may be changed on the command line (@pxref{option --options}). You should backup this file. @item trustlist.txt @efindex trustlist.txt This is the list of trusted keys. You should backup this file. Comment lines, indicated by a leading hash mark, as well as empty lines are ignored. To mark a key as trusted you need to enter its fingerprint followed by a space and a capital letter @code{S}. Colons may optionally be used to separate the bytes of a fingerprint; this enables cutting and pasting the fingerprint from a key listing output. If the line is prefixed with a @code{!} the key is explicitly marked as not trusted. Here is an example where two keys are marked as ultimately trusted and one as not trusted: @cartouche @smallexample # CN=Wurzel ZS 3,O=Intevation GmbH,C=DE A6935DD34EF3087973C706FC311AA2CCF733765B S # CN=PCA-1-Verwaltung-02/O=PKI-1-Verwaltung/C=DE DC:BD:69:25:48:BD:BB:7E:31:6E:BB:80:D3:00:80:35:D4:F8:A6:CD S # CN=Root-CA/O=Schlapphuete/L=Pullach/C=DE !14:56:98:D3:FE:9C:CA:5A:31:6E:BC:81:D3:11:4E:00:90:A3:44:C2 S @end smallexample @end cartouche Before entering a key into this file, you need to ensure its authenticity. How to do this depends on your organisation; your administrator might have already entered those keys which are deemed trustworthy enough into this file. Places where to look for the fingerprint of a root certificate are letters received from the CA or the website of the CA (after making 100% sure that this is indeed the website of that CA). You may want to consider disallowing interactive updates of this file by using the @ref{option --no-allow-mark-trusted}. It might even be advisable to change the permissions to read-only so that this file can't be changed inadvertently. As a special feature a line @code{include-default} will include a global list of trusted certificates (e.g. @file{@value{SYSCONFDIR}/trustlist.txt}). This global list is also used if the local list is not available. It is possible to add further flags after the @code{S} for use by the caller: @table @code @item relax @cindex relax Relax checking of some root certificate requirements. As of now this flag allows the use of root certificates with a missing basicConstraints attribute (despite that it is a MUST for CA certificates) and disables CRL checking for the root certificate. @item cm If validation of a certificate finally issued by a CA with this flag set fails, try again using the chain validation model. @end table @item sshcontrol @efindex sshcontrol This file is used when support for the secure shell agent protocol has been enabled (@pxref{option --enable-ssh-support}). Only keys present in this file are used in the SSH protocol. You should backup this file. The @command{ssh-add} tool may be used to add new entries to this file; you may also add them manually. Comment lines, indicated by a leading hash mark, as well as empty lines are ignored. An entry starts with optional whitespace, followed by the keygrip of the key given as 40 hex digits, optionally followed by the caching TTL in seconds and another optional field for arbitrary flags. A non-zero TTL overrides the global default as set by @option{--default-cache-ttl-ssh}. The only flag support is @code{confirm}. If this flag is found for a key, each use of the key will pop up a pinentry to confirm the use of that key. The flag is automatically set if a new key was loaded into @code{gpg-agent} using the option @option{-c} of the @code{ssh-add} command. The keygrip may be prefixed with a @code{!} to disable an entry. The following example lists exactly one key. Note that keys available through a OpenPGP smartcard in the active smartcard reader are implicitly added to this list; i.e. there is no need to list them. @cartouche @smallexample # Key added on: 2011-07-20 20:38:46 # Fingerprint: 5e:8d:c4:ad:e7:af:6e:27:8a:d6:13:e4:79:ad:0b:81 34B62F25E277CF13D3C6BCEBFD3F85D08F0A864B 0 confirm @end smallexample @end cartouche @item private-keys-v1.d/ @efindex private-keys-v1.d This is the directory where gpg-agent stores the private keys. Each key is stored in a file with the name made up of the keygrip and the suffix @file{key}. You should backup all files in this directory and take great care to keep this backup closed away. @end table Note that on larger installations, it is useful to put predefined files into the directory @file{@value{SYSCONFSKELDIR}} so that newly created users start up with a working configuration. For existing users the a small helper script is provided to create these files (@pxref{addgnupghome}). @c @c Agent Signals @c @mansect signals @node Agent Signals @section Use of some signals A running @command{gpg-agent} may be controlled by signals, i.e. using the @command{kill} command to send a signal to the process. Here is a list of supported signals: @table @gnupgtabopt @item SIGHUP @cpindex SIGHUP This signal flushes all cached passphrases and if the program has been started with a configuration file, the configuration file is read again. Only certain options are honored: @code{quiet}, @code{verbose}, @code{debug}, @code{debug-all}, @code{debug-level}, @code{debug-pinentry}, @code{no-grab}, @code{pinentry-program}, @code{pinentry-invisible-char}, @code{default-cache-ttl}, @code{max-cache-ttl}, @code{ignore-cache-for-signing}, @code{no-allow-external-cache}, @code{allow-emacs-pinentry}, @code{no-allow-mark-trusted}, @code{disable-scdaemon}, and @code{disable-check-own-socket}. @code{scdaemon-program} is also supported but due to the current implementation, which calls the scdaemon only once, it is not of much use unless you manually kill the scdaemon. @item SIGTERM @cpindex SIGTERM Shuts down the process but waits until all current requests are fulfilled. If the process has received 3 of these signals and requests are still pending, a shutdown is forced. @item SIGINT @cpindex SIGINT Shuts down the process immediately. @item SIGUSR1 @cpindex SIGUSR1 Dump internal information to the log file. @item SIGUSR2 @cpindex SIGUSR2 This signal is used for internal purposes. @end table @c @c Examples @c @mansect examples @node Agent Examples @section Examples It is important to set the environment variable @code{GPG_TTY} in your login shell, for example in the @file{~/.bashrc} init script: @cartouche @example export GPG_TTY=$(tty) @end example @end cartouche If you enabled the Ssh Agent Support, you also need to tell ssh about it by adding this to your init script: @cartouche @example unset SSH_AGENT_PID if [ "$@{gnupg_SSH_AUTH_SOCK_by:-0@}" -ne $$ ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" fi @end example @end cartouche @c @c Assuan Protocol @c @manpause @node Agent Protocol @section Agent's Assuan Protocol Note: this section does only document the protocol, which is used by GnuPG components; it does not deal with the ssh-agent protocol. To see the full specification of each command, use @example gpg-connect-agent 'help COMMAND' /bye @end example @noindent or just 'help' to list all available commands. @noindent The @command{gpg-agent} daemon is started on demand by the GnuPG components. To identify a key we use a thing called keygrip which is the SHA-1 hash of an canonical encoded S-Expression of the public key as used in Libgcrypt. For the purpose of this interface the keygrip is given as a hex string. The advantage of using this and not the hash of a certificate is that it will be possible to use the same keypair for different protocols, thereby saving space on the token used to keep the secret keys. The @command{gpg-agent} may send status messages during a command or when returning from a command to inform a client about the progress or result of an operation. For example, the @var{INQUIRE_MAXLEN} status message may be sent during a server inquire to inform the client of the maximum usable length of the inquired data (which should not be exceeded). @menu * Agent PKDECRYPT:: Decrypting a session key * Agent PKSIGN:: Signing a Hash * Agent GENKEY:: Generating a Key * Agent IMPORT:: Importing a Secret Key * Agent EXPORT:: Exporting a Secret Key * Agent ISTRUSTED:: Importing a Root Certificate * Agent GET_PASSPHRASE:: Ask for a passphrase * Agent CLEAR_PASSPHRASE:: Expire a cached passphrase * Agent PRESET_PASSPHRASE:: Set a passphrase for a keygrip * Agent GET_CONFIRMATION:: Ask for confirmation * Agent HAVEKEY:: Check whether a key is available * Agent LEARN:: Register a smartcard * Agent PASSWD:: Change a Passphrase * Agent UPDATESTARTUPTTY:: Change the Standard Display * Agent GETEVENTCOUNTER:: Get the Event Counters * Agent GETINFO:: Return information about the process * Agent OPTION:: Set options for the session @end menu @node Agent PKDECRYPT @subsection Decrypting a session key The client asks the server to decrypt a session key. The encrypted session key should have all information needed to select the appropriate secret key or to delegate it to a smartcard. @example SETKEY @end example Tell the server about the key to be used for decryption. If this is not used, @command{gpg-agent} may try to figure out the key by trying to decrypt the message with each key available. @example PKDECRYPT @end example The agent checks whether this command is allowed and then does an INQUIRY to get the ciphertext the client should then send the cipher text. @example S: INQUIRE CIPHERTEXT C: D (xxxxxx C: D xxxx) C: END @end example Please note that the server may send status info lines while reading the data lines from the client. The data send is a SPKI like S-Exp with this structure: @example (enc-val ( ( ) ... ( ))) @end example Where algo is a string with the name of the algorithm; see the libgcrypt documentation for a list of valid algorithms. The number and names of the parameters depend on the algorithm. The agent does return an error if there is an inconsistency. If the decryption was successful the decrypted data is returned by means of "D" lines. Here is an example session: @cartouche @smallexample C: PKDECRYPT S: INQUIRE CIPHERTEXT C: D (enc-val elg (a 349324324) C: D (b 3F444677CA))) C: END S: # session key follows S: S PADDING 0 S: D (value 1234567890ABCDEF0) S: OK decryption successful @end smallexample @end cartouche The “PADDING” status line is only send if gpg-agent can tell what kind of padding is used. As of now only the value 0 is used to indicate that the padding has been removed. @node Agent PKSIGN @subsection Signing a Hash The client asks the agent to sign a given hash value. A default key will be chosen if no key has been set. To set a key a client first uses: @example SIGKEY @end example This can be used multiple times to create multiple signature, the list of keys is reset with the next PKSIGN command or a RESET. The server tests whether the key is a valid key to sign something and responds with okay. @example SETHASH --hash=| @end example The client can use this command to tell the server about the data (which usually is a hash) to be signed. is the decimal encoded hash algorithm number as used by Libgcrypt. Either or --hash= must be given. Valid names for are: @table @code @item sha1 The SHA-1 hash algorithm @item sha256 The SHA-256 hash algorithm @item rmd160 The RIPE-MD160 hash algorithm @item md5 The old and broken MD5 hash algorithm @item tls-md5sha1 A combined hash algorithm as used by the TLS protocol. @end table @noindent The actual signing is done using @example PKSIGN @end example Options are not yet defined, but may later be used to choose among different algorithms. The agent does then some checks, asks for the passphrase and as a result the server returns the signature as an SPKI like S-expression in "D" lines: @example (sig-val ( ( ) ... ( ))) @end example The operation is affected by the option @example OPTION use-cache-for-signing=0|1 @end example The default of @code{1} uses the cache. Setting this option to @code{0} will lead @command{gpg-agent} to ignore the passphrase cache. Note, that there is also a global command line option for @command{gpg-agent} to globally disable the caching. Here is an example session: @cartouche @smallexample C: SIGKEY S: OK key available C: SIGKEY S: OK key available C: PKSIGN S: # I did ask the user whether he really wants to sign S: # I did ask the user for the passphrase S: INQUIRE HASHVAL C: D ABCDEF012345678901234 C: END S: # signature follows S: D (sig-val rsa (s 45435453654612121212)) S: OK @end smallexample @end cartouche @node Agent GENKEY @subsection Generating a Key This is used to create a new keypair and store the secret key inside the active PSE --- which is in most cases a Soft-PSE. A not-yet-defined option allows choosing the storage location. To get the secret key out of the PSE, a special export tool has to be used. @example GENKEY [--no-protection] [--preset] [] @end example Invokes the key generation process and the server will then inquire on the generation parameters, like: @example S: INQUIRE KEYPARM C: D (genkey (rsa (nbits 1024))) C: END @end example The format of the key parameters which depends on the algorithm is of the form: @example (genkey (algo (parameter_name_1 ....) .... (parameter_name_n ....))) @end example If everything succeeds, the server returns the *public key* in a SPKI like S-Expression like this: @example (public-key (rsa (n ) (e ))) @end example Here is an example session: @cartouche @smallexample C: GENKEY S: INQUIRE KEYPARM C: D (genkey (rsa (nbits 1024))) C: END S: D (public-key S: D (rsa (n 326487324683264) (e 10001))) S OK key created @end smallexample @end cartouche The @option{--no-protection} option may be used to prevent prompting for a passphrase to protect the secret key while leaving the secret key unprotected. The @option{--preset} option may be used to add the passphrase to the cache using the default cache parameters. The @option{--inq-passwd} option may be used to create the key with a supplied passphrase. When used the agent does an inquiry with the keyword @code{NEWPASSWD} to retrieve that passphrase. This option takes precedence over @option{--no-protection}; however if the client sends a empty (zero-length) passphrase, this is identical to @option{--no-protection}. @node Agent IMPORT @subsection Importing a Secret Key This operation is not yet supported by GpgAgent. Specialized tools are to be used for this. There is no actual need because we can expect that secret keys created by a 3rd party are stored on a smartcard. If we have generated the key ourselves, we do not need to import it. @node Agent EXPORT @subsection Export a Secret Key Not implemented. Should be done by an extra tool. @node Agent ISTRUSTED @subsection Importing a Root Certificate Actually we do not import a Root Cert but provide a way to validate any piece of data by storing its Hash along with a description and an identifier in the PSE. Here is the interface description: @example ISTRUSTED @end example Check whether the OpenPGP primary key or the X.509 certificate with the given fingerprint is an ultimately trusted key or a trusted Root CA certificate. The fingerprint should be given as a hexstring (without any blanks or colons or whatever in between) and may be left padded with 00 in case of an MD5 fingerprint. GPGAgent will answer with: @example OK @end example The key is in the table of trusted keys. @example ERR 304 (Not Trusted) @end example The key is not in this table. Gpg needs the entire list of trusted keys to maintain the web of trust; the following command is therefore quite helpful: @example LISTTRUSTED @end example GpgAgent returns a list of trusted keys line by line: @example S: D 000000001234454556565656677878AF2F1ECCFF P S: D 340387563485634856435645634856438576457A P S: D FEDC6532453745367FD83474357495743757435D S S: OK @end example The first item on a line is the hexified fingerprint where MD5 fingerprints are @code{00} padded to the left and the second item is a flag to indicate the type of key (so that gpg is able to only take care of PGP keys). P = OpenPGP, S = S/MIME. A client should ignore the rest of the line, so that we can extend the format in the future. Finally a client should be able to mark a key as trusted: @example MARKTRUSTED @var{fingerprint} "P"|"S" @end example The server will then pop up a window to ask the user whether she really trusts this key. For this it will probably ask for a text to be displayed like this: @example S: INQUIRE TRUSTDESC C: D Do you trust the key with the fingerprint @@FPR@@ C: D bla fasel blurb. C: END S: OK @end example Known sequences with the pattern @@foo@@ are replaced according to this table: @table @code @item @@FPR16@@ Format the fingerprint according to gpg rules for a v3 keys. @item @@FPR20@@ Format the fingerprint according to gpg rules for a v4 keys. @item @@FPR@@ Choose an appropriate format to format the fingerprint. @item @@@@ Replaced by a single @code{@@}. @end table @node Agent GET_PASSPHRASE @subsection Ask for a passphrase This function is usually used to ask for a passphrase to be used for symmetric encryption, but may also be used by programs which need special handling of passphrases. This command uses a syntax which helps clients to use the agent with minimum effort. @example GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]] \ [--qualitybar] @var{cache_id} \ [@var{error_message} @var{prompt} @var{description}] @end example @var{cache_id} is expected to be a string used to identify a cached passphrase. Use a @code{X} to bypass the cache. With no other arguments the agent returns a cached passphrase or an error. By convention either the hexified fingerprint of the key shall be used for @var{cache_id} or an arbitrary string prefixed with the name of the calling application and a colon: Like @code{gpg:somestring}. @var{error_message} is either a single @code{X} for no error message or a string to be shown as an error message like (e.g. "invalid passphrase"). Blanks must be percent escaped or replaced by @code{+}'. @var{prompt} is either a single @code{X} for a default prompt or the text to be shown as the prompt. Blanks must be percent escaped or replaced by @code{+}. @var{description} is a text shown above the entry field. Blanks must be percent escaped or replaced by @code{+}. The agent either returns with an error or with a OK followed by the hex encoded passphrase. Note that the length of the strings is implicitly limited by the maximum length of a command. If the option @option{--data} is used, the passphrase is not returned on the OK line but by regular data lines; this is the preferred method. If the option @option{--check} is used, the standard passphrase constraints checks are applied. A check is not done if the passphrase has been found in the cache. If the option @option{--no-ask} is used and the passphrase is not in the cache the user will not be asked to enter a passphrase but the error code @code{GPG_ERR_NO_DATA} is returned. If the option @option{--qualitybar} is used and a minimum passphrase length has been configured, a visual indication of the entered passphrase quality is shown. @example CLEAR_PASSPHRASE @var{cache_id} @end example may be used to invalidate the cache entry for a passphrase. The function returns with OK even when there is no cached passphrase. @node Agent CLEAR_PASSPHRASE @subsection Remove a cached passphrase Use this command to remove a cached passphrase. @example CLEAR_PASSPHRASE [--mode=normal] @end example The @option{--mode=normal} option can be used to clear a @var{cache_id} that was set by gpg-agent. @node Agent PRESET_PASSPHRASE @subsection Set a passphrase for a keygrip This command adds a passphrase to the cache for the specified @var{keygrip}. @example PRESET_PASSPHRASE [--inquire] [] @end example -The passphrase is a hexidecimal string when specified. When not specified, the +The passphrase is a hexadecimal string when specified. When not specified, the passphrase will be retrieved from the pinentry module unless the @option{--inquire} option was specified in which case the passphrase will be retrieved from the client. The @var{timeout} parameter keeps the passphrase cached for the specified number of seconds. A value of @code{-1} means infinite while @code{0} means the default (currently only a timeout of -1 is allowed, which means to never expire it). @node Agent GET_CONFIRMATION @subsection Ask for confirmation This command may be used to ask for a simple confirmation by presenting a text and 2 buttons: Okay and Cancel. @example GET_CONFIRMATION @var{description} @end example @var{description}is displayed along with a Okay and Cancel button. Blanks must be percent escaped or replaced by @code{+}. A @code{X} may be used to display confirmation dialog with a default text. The agent either returns with an error or with a OK. Note, that the length of @var{description} is implicitly limited by the maximum length of a command. @node Agent HAVEKEY @subsection Check whether a key is available This can be used to see whether a secret key is available. It does not return any information on whether the key is somehow protected. @example HAVEKEY @var{keygrips} @end example The agent answers either with OK or @code{No_Secret_Key} (208). The caller may want to check for other error codes as well. More than one keygrip may be given. In this case the command returns success if at least one of the keygrips corresponds to an available secret key. @node Agent LEARN @subsection Register a smartcard @example LEARN [--send] @end example This command is used to register a smartcard. With the @option{--send} option given the certificates are sent back. @node Agent PASSWD @subsection Change a Passphrase @example PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset] @var{keygrip} @end example This command is used to interactively change the passphrase of the key identified by the hex string @var{keygrip}. The @option{--preset} option may be used to add the new passphrase to the cache using the default cache parameters. @node Agent UPDATESTARTUPTTY @subsection Change the standard display @example UPDATESTARTUPTTY @end example Set the startup TTY and X-DISPLAY variables to the values of this session. This command is useful to direct future pinentry invocations to another screen. It is only required because there is no way in the ssh-agent protocol to convey this information. @node Agent GETEVENTCOUNTER @subsection Get the Event Counters @example GETEVENTCOUNTER @end example This function return one status line with the current values of the event counters. The event counters are useful to avoid polling by delaying a poll until something has changed. The values are decimal numbers in the range @code{0} to @code{UINT_MAX} and wrapping around to 0. The actual values should not be relied upon; they shall only be used to detect a change. The currently defined counters are: @table @code @item ANY Incremented with any change of any of the other counters. @item KEY Incremented for added or removed private keys. @item CARD Incremented for changes of the card readers stati. @end table @node Agent GETINFO @subsection Return information about the process This is a multipurpose function to return a variety of information. @example GETINFO @var{what} @end example The value of @var{what} specifies the kind of information returned: @table @code @item version Return the version of the program. @item pid Return the process id of the process. @item socket_name Return the name of the socket used to connect the agent. @item ssh_socket_name Return the name of the socket used for SSH connections. If SSH support has not been enabled the error @code{GPG_ERR_NO_DATA} will be returned. @end table @node Agent OPTION @subsection Set options for the session Here is a list of session options which are not yet described with other commands. The general syntax for an Assuan option is: @smallexample OPTION @var{key}=@var{value} @end smallexample @noindent Supported @var{key}s are: @table @code @item agent-awareness This may be used to tell gpg-agent of which gpg-agent version the client is aware of. gpg-agent uses this information to enable features which might break older clients. @item putenv Change the session's environment to be used for the Pinentry. Valid values are: @table @code @item @var{name} Delete envvar @var{name} @item @var{name}= Set envvar @var{name} to the empty string @item @var{name}=@var{value} Set envvar @var{name} to the string @var{value}. @end table @item use-cache-for-signing See Assuan command @code{PKSIGN}. @item allow-pinentry-notify This does not need any value. It is used to enable the PINENTRY_LAUNCHED inquiry. @item pinentry-mode This option is used to change the operation mode of the pinentry. The following values are defined: @table @code @item ask This is the default mode which pops up a pinentry as needed. @item cancel Instead of popping up a pinentry, return the error code @code{GPG_ERR_CANCELED}. @item error Instead of popping up a pinentry, return the error code @code{GPG_ERR_NO_PIN_ENTRY}. @item loopback Use a loopback pinentry. This fakes a pinentry by using inquiries back to the caller to ask for a passphrase. This option may only be set if the agent has been configured for that. To disable this feature use @ref{option --no-allow-loopback-pinentry}. @end table @item cache-ttl-opt-preset This option sets the cache TTL for new entries created by GENKEY and PASSWD commands when using the @option{--preset} option. It is not used a default value is used. @item s2k-count Instead of using the standard S2K count (which is computed on the fly), the given S2K count is used for new keys or when changing the passphrase of a key. Values below 65536 are considered to be 0. This option is valid for the entire session or until reset to 0. This option is useful if the key is later used on boxes which are either much slower or faster than the actual box. @end table @mansect see also @ifset isman @command{@gpgname}(1), @command{gpgsm}(1), @command{gpgconf}(1), @command{gpg-connect-agent}(1), @command{scdaemon}(1) @end ifset @include see-also-note.texi diff --git a/doc/gpgsm.texi b/doc/gpgsm.texi index 1d0083976..c3f5aacae 100644 --- a/doc/gpgsm.texi +++ b/doc/gpgsm.texi @@ -1,1629 +1,1629 @@ @c Copyright (C) 2002 Free Software Foundation, Inc. @c This is part of the GnuPG manual. @c For copying conditions, see the file gnupg.texi. @include defs.inc @node Invoking GPGSM @chapter Invoking GPGSM @cindex GPGSM command options @cindex command options @cindex options, GPGSM command @manpage gpgsm.1 @ifset manverb .B gpgsm \- CMS encryption and signing tool @end ifset @mansect synopsis @ifset manverb .B gpgsm .RB [ \-\-homedir .IR dir ] .RB [ \-\-options .IR file ] .RI [ options ] .I command .RI [ args ] @end ifset @mansect description @command{gpgsm} is a tool similar to @command{gpg} to provide digital encryption and signing services on X.509 certificates and the CMS protocol. It is mainly used as a backend for S/MIME mail processing. @command{gpgsm} includes a full featured certificate management and complies with all rules defined for the German Sphinx project. @manpause @xref{Option Index}, for an index to @command{GPGSM}'s commands and options. @mancont @menu * GPGSM Commands:: List of all commands. * GPGSM Options:: List of all options. * GPGSM Configuration:: Configuration files. * GPGSM Examples:: Some usage examples. Developer information: * Unattended Usage:: Using @command{gpgsm} from other programs. * GPGSM Protocol:: The protocol the server mode uses. @end menu @c ******************************************* @c *************** **************** @c *************** COMMANDS **************** @c *************** **************** @c ******************************************* @mansect commands @node GPGSM Commands @section Commands Commands are not distinguished from options except for the fact that only one command is allowed. @menu * General GPGSM Commands:: Commands not specific to the functionality. * Operational GPGSM Commands:: Commands to select the type of operation. * Certificate Management:: How to manage certificates. @end menu @c ******************************************* @c ********** GENERAL COMMANDS ************* @c ******************************************* @node General GPGSM Commands @subsection Commands not specific to the function @table @gnupgtabopt @item --version @opindex version Print the program version and licensing information. Note that you cannot abbreviate this command. @item --help, -h @opindex help Print a usage message summarizing the most useful command-line options. Note that you cannot abbreviate this command. @item --warranty @opindex warranty Print warranty information. Note that you cannot abbreviate this command. @item --dump-options @opindex dump-options Print a list of all available options and commands. Note that you cannot abbreviate this command. @end table @c ******************************************* @c ******** OPERATIONAL COMMANDS *********** @c ******************************************* @node Operational GPGSM Commands @subsection Commands to select the type of operation @table @gnupgtabopt @item --encrypt @opindex encrypt Perform an encryption. The keys the data is encrypted to must be set using the option @option{--recipient}. @item --decrypt @opindex decrypt Perform a decryption; the type of input is automatically determined. It may either be in binary form or PEM encoded; automatic determination of base-64 encoding is not done. @item --sign @opindex sign Create a digital signature. The key used is either the fist one found in the keybox or those set with the @option{--local-user} option. @item --verify @opindex verify Check a signature file for validity. Depending on the arguments a detached signature may also be checked. @item --server @opindex server Run in server mode and wait for commands on the @code{stdin}. @item --call-dirmngr @var{command} [@var{args}] @opindex call-dirmngr Behave as a Dirmngr client issuing the request @var{command} with the optional list of @var{args}. The output of the Dirmngr is printed stdout. Please note that file names given as arguments should have an absolute file name (i.e. commencing with @code{/}) because they are passed verbatim to the Dirmngr and the working directory of the Dirmngr might not be the same as the one of this client. Currently it is not possible to pass data via stdin to the Dirmngr. @var{command} should not contain spaces. This is command is required for certain maintaining tasks of the dirmngr where a dirmngr must be able to call back to @command{gpgsm}. See the Dirmngr manual for details. @item --call-protect-tool @var{arguments} @opindex call-protect-tool Certain maintenance operations are done by an external program call @command{gpg-protect-tool}; this is usually not installed in a directory listed in the PATH variable. This command provides a simple wrapper to access this tool. @var{arguments} are passed verbatim to this command; use @samp{--help} to get a list of supported operations. @end table @c ******************************************* @c ******* CERTIFICATE MANAGEMENT ********** @c ******************************************* @node Certificate Management @subsection How to manage the certificates and keys @table @gnupgtabopt @item --generate-key @opindex generate-key @itemx --gen-key @opindex gen-key This command allows the creation of a certificate signing request or a self-signed certificate. It is commonly used along with the @option{--output} option to save the created CSR or certificate into a file. If used with the @option{--batch} a parameter file is used to create the CSR or certificate and it is further possible to create non-self-signed certificates. @item --list-keys @itemx -k @opindex list-keys List all available certificates stored in the local key database. Note that the displayed data might be reformatted for better human readability and illegal characters are replaced by safe substitutes. @item --list-secret-keys @itemx -K @opindex list-secret-keys List all available certificates for which a corresponding a secret key is available. @item --list-external-keys @var{pattern} @opindex list-keys List certificates matching @var{pattern} using an external server. This utilizes the @code{dirmngr} service. @item --list-chain @opindex list-chain Same as @option{--list-keys} but also prints all keys making up the chain. @item --dump-cert @itemx --dump-keys @opindex dump-cert @opindex dump-keys List all available certificates stored in the local key database using a format useful mainly for debugging. @item --dump-chain @opindex dump-chain Same as @option{--dump-keys} but also prints all keys making up the chain. @item --dump-secret-keys @opindex dump-secret-keys List all available certificates for which a corresponding a secret key is available using a format useful mainly for debugging. @item --dump-external-keys @var{pattern} @opindex dump-external-keys List certificates matching @var{pattern} using an external server. This utilizes the @code{dirmngr} service. It uses a format useful mainly for debugging. @item --keydb-clear-some-cert-flags @opindex keydb-clear-some-cert-flags This is a debugging aid to reset certain flags in the key database which are used to cache certain certificate stati. It is especially useful if a bad CRL or a weird running OCSP responder did accidentally revoke certificate. There is no security issue with this command because @command{gpgsm} always make sure that the validity of a certificate is checked right before it is used. @item --delete-keys @var{pattern} @opindex delete-keys Delete the keys matching @var{pattern}. Note that there is no command to delete the secret part of the key directly. In case you need to do this, you should run the command @code{gpgsm --dump-secret-keys KEYID} before you delete the key, copy the string of hex-digits in the ``keygrip'' line and delete the file consisting of these hex-digits and the suffix @code{.key} from the @file{private-keys-v1.d} directory below our GnuPG home directory (usually @file{~/.gnupg}). @item --export [@var{pattern}] @opindex export Export all certificates stored in the Keybox or those specified by the optional @var{pattern}. Those pattern consist of a list of user ids (@pxref{how-to-specify-a-user-id}). When used along with the @option{--armor} option a few informational lines are prepended before each block. There is one limitation: As there is no commonly agreed upon way to pack more than one certificate into an ASN.1 structure, the binary export (i.e. without using @option{armor}) works only for the export of one certificate. Thus it is required to specify a @var{pattern} which yields exactly one certificate. Ephemeral certificate are only exported if all @var{pattern} are given as fingerprints or keygrips. @item --export-secret-key-p12 @var{key-id} @opindex export-secret-key-p12 Export the private key and the certificate identified by @var{key-id} in a PKCS#12 format. When used with the @code{--armor} option a few informational lines are prepended to the output. Note, that the PKCS#12 format is not very secure and this command is only provided if there is no other way to exchange the private key. (@xref{option --p12-charset}.) @item --export-secret-key-p8 @var{key-id} @itemx --export-secret-key-raw @var{key-id} @opindex export-secret-key-p8 @opindex export-secret-key-raw Export the private key of the certificate identified by @var{key-id} with any encryption stripped. The @code{...-raw} command exports in PKCS#1 format; the @code{...-p8} command exports in PKCS#8 format. When used with the @code{--armor} option a few informational lines are prepended to the output. These commands are useful to prepare a key for use on a TLS server. @item --import [@var{files}] @opindex import Import the certificates from the PEM or binary encoded files as well as from signed-only messages. This command may also be used to import a secret key from a PKCS#12 file. @item --learn-card @opindex learn-card Read information about the private keys from the smartcard and import the certificates from there. This command utilizes the @command{gpg-agent} and in turn the @command{scdaemon}. @item --change-passphrase @var{user_id} @opindex change-passphrase @itemx --passwd @var{user_id} @opindex passwd Change the passphrase of the private key belonging to the certificate specified as @var{user_id}. Note, that changing the passphrase/PIN of a smartcard is not yet supported. @end table @c ******************************************* @c *************** **************** @c *************** OPTIONS **************** @c *************** **************** @c ******************************************* @mansect options @node GPGSM Options @section Option Summary @command{GPGSM} features a bunch of options to control the exact behaviour and to change the default configuration. @menu * Configuration Options:: How to change the configuration. * Certificate Options:: Certificate related options. * Input and Output:: Input and Output. * CMS Options:: How to change how the CMS is created. * Esoteric Options:: Doing things one usually do not want to do. @end menu @c ******************************************* @c ******** CONFIGURATION OPTIONS ********** @c ******************************************* @node Configuration Options @subsection How to change the configuration These options are used to change the configuration and are usually found in the option file. @table @gnupgtabopt @anchor{gpgsm-option --options} @item --options @var{file} @opindex options Reads configuration from @var{file} instead of from the default per-user configuration file. The default configuration file is named @file{gpgsm.conf} and expected in the @file{.gnupg} directory directly below the home directory of the user. @include opt-homedir.texi @item -v @item --verbose @opindex v @opindex verbose Outputs additional information while running. You can increase the verbosity by giving several verbose commands to @command{gpgsm}, such as @samp{-vv}. @item --policy-file @var{filename} @opindex policy-file Change the default name of the policy file to @var{filename}. @item --agent-program @var{file} @opindex agent-program Specify an agent program to be used for secret key operations. The default value is determined by running the command @command{gpgconf}. Note that the pipe symbol (@code{|}) is used for a regression test suite hack and may thus not be used in the file name. @item --dirmngr-program @var{file} @opindex dirmngr-program Specify a dirmngr program to be used for @acronym{CRL} checks. The default value is @file{@value{BINDIR}/dirmngr}. @item --prefer-system-dirmngr @opindex prefer-system-dirmngr If a system wide @command{dirmngr} is running in daemon mode, first try to connect to this one. Fallback to a pipe based server if this does not work. Under Windows this option is ignored because the system dirmngr is always used. @item --disable-dirmngr Entirely disable the use of the Dirmngr. @item --no-autostart @opindex no-autostart Do not start the gpg-agent or the dirmngr if it has not yet been started and its service is required. This option is mostly useful on machines where the connection to gpg-agent has been redirected to another machines. If dirmngr is required on the remote machine, it may be started manually using @command{gpgconf --launch dirmngr}. @item --no-secmem-warning @opindex no-secmem-warning Do not print a warning when the so called "secure memory" cannot be used. @item --log-file @var{file} @opindex log-file When running in server mode, append all logging output to @var{file}. Use @file{socket://} to log to socket. @end table @c ******************************************* @c ******** CERTIFICATE OPTIONS ************ @c ******************************************* @node Certificate Options @subsection Certificate related options @table @gnupgtabopt @item --enable-policy-checks @itemx --disable-policy-checks @opindex enable-policy-checks @opindex disable-policy-checks By default policy checks are enabled. These options may be used to change it. @item --enable-crl-checks @itemx --disable-crl-checks @opindex enable-crl-checks @opindex disable-crl-checks By default the @acronym{CRL} checks are enabled and the DirMngr is used to check for revoked certificates. The disable option is most useful with an off-line network connection to suppress this check. @item --enable-trusted-cert-crl-check @itemx --disable-trusted-cert-crl-check @opindex enable-trusted-cert-crl-check @opindex disable-trusted-cert-crl-check By default the @acronym{CRL} for trusted root certificates are checked like for any other certificates. This allows a CA to revoke its own certificates voluntary without the need of putting all ever issued certificates into a CRL. The disable option may be used to switch this extra check off. Due to the caching done by the Dirmngr, there will not be any noticeable performance gain. Note, that this also disables possible OCSP checks for trusted root certificates. A more specific way of disabling this check is by adding the ``relax'' keyword to the root CA line of the @file{trustlist.txt} @item --force-crl-refresh @opindex force-crl-refresh Tell the dirmngr to reload the CRL for each request. For better performance, the dirmngr will actually optimize this by suppressing the loading for short time intervals (e.g. 30 minutes). This option is useful to make sure that a fresh CRL is available for certificates hold in the keybox. The suggested way of doing this is by using it along with the option @option{--with-validation} for a key listing command. This option should not be used in a configuration file. @item --enable-ocsp @itemx --disable-ocsp @opindex enable-ocsp @opindex disable-ocsp By default @acronym{OCSP} checks are disabled. The enable option may be used to enable OCSP checks via Dirmngr. If @acronym{CRL} checks are also enabled, CRLs will be used as a fallback if for some reason an OCSP request will not succeed. Note, that you have to allow OCSP requests in Dirmngr's configuration too (option @option{--allow-ocsp}) and configure Dirmngr properly. If you do not do so you will get the error code @samp{Not supported}. @item --auto-issuer-key-retrieve @opindex auto-issuer-key-retrieve If a required certificate is missing while validating the chain of certificates, try to load that certificate from an external location. This usually means that Dirmngr is employed to search for the certificate. Note that this option makes a "web bug" like behavior possible. LDAP server operators can see which keys you request, so by sending you a message signed by a brand new key (which you naturally will not have on your local keybox), the operator can tell both your IP address and the time when you verified the signature. @anchor{gpgsm-option --validation-model} @item --validation-model @var{name} @opindex validation-model This option changes the default validation model. The only possible values are "shell" (which is the default), "chain" which forces the use of the chain model and "steed" for a new simplified model. The chain model is also used if an option in the @file{trustlist.txt} or an attribute of the certificate requests it. However the standard model (shell) is in that case always tried first. @item --ignore-cert-extension @var{oid} @opindex ignore-cert-extension Add @var{oid} to the list of ignored certificate extensions. The @var{oid} is expected to be in dotted decimal form, like @code{2.5.29.3}. This option may be used more than once. Critical flagged certificate extensions matching one of the OIDs in the list are treated as if they are actually handled and thus the certificate will not be rejected due to an unknown critical extension. Use this option with care because extensions are usually flagged as critical for a reason. @end table @c ******************************************* @c *********** INPUT AND OUTPUT ************ @c ******************************************* @node Input and Output @subsection Input and Output @table @gnupgtabopt @item --armor @itemx -a @opindex armor Create PEM encoded output. Default is binary output. @item --base64 @opindex base64 Create Base-64 encoded output; i.e. PEM without the header lines. @item --assume-armor @opindex assume-armor Assume the input data is PEM encoded. Default is to autodetect the encoding but this is may fail. @item --assume-base64 @opindex assume-base64 Assume the input data is plain base-64 encoded. @item --assume-binary @opindex assume-binary Assume the input data is binary encoded. @anchor{option --p12-charset} @item --p12-charset @var{name} @opindex p12-charset @command{gpgsm} uses the UTF-8 encoding when encoding passphrases for PKCS#12 files. This option may be used to force the passphrase to be encoded in the specified encoding @var{name}. This is useful if the application used to import the key uses a different encoding and thus will not be able to import a file generated by @command{gpgsm}. Commonly used values for @var{name} are @code{Latin1} and @code{CP850}. Note that @command{gpgsm} itself automagically imports any file with a passphrase encoded to the most commonly used encodings. @item --default-key @var{user_id} @opindex default-key Use @var{user_id} as the standard key for signing. This key is used if no other key has been defined as a signing key. Note, that the first @option{--local-users} option also sets this key if it has not yet been set; however @option{--default-key} always overrides this. @item --local-user @var{user_id} @item -u @var{user_id} @opindex local-user Set the user(s) to be used for signing. The default is the first secret key found in the database. @item --recipient @var{name} @itemx -r @opindex recipient Encrypt to the user id @var{name}. There are several ways a user id may be given (@pxref{how-to-specify-a-user-id}). @item --output @var{file} @itemx -o @var{file} @opindex output Write output to @var{file}. The default is to write it to stdout. @anchor{gpgsm-option --with-key-data} @item --with-key-data @opindex with-key-data Displays extra information with the @code{--list-keys} commands. Especially a line tagged @code{grp} is printed which tells you the keygrip of a key. This string is for example used as the file name of the secret key. @anchor{gpgsm-option --with-validation} @item --with-validation @opindex with-validation When doing a key listing, do a full validation check for each key and print the result. This is usually a slow operation because it requires a CRL lookup and other operations. When used along with @option{--import}, a validation of the certificate to import is done and only imported if it succeeds the test. Note that this does not affect an already available certificate in the DB. This option is therefore useful to simply verify a certificate. @item --with-md5-fingerprint For standard key listings, also print the MD5 fingerprint of the certificate. @item --with-keygrip Include the keygrip in standard key listings. Note that the keygrip is always listed in @option{--with-colons} mode. @item --with-secret @opindex with-secret Include info about the presence of a secret key in public key listings done with @code{--with-colons}. @end table @c ******************************************* @c ************* CMS OPTIONS *************** @c ******************************************* @node CMS Options @subsection How to change how the CMS is created @table @gnupgtabopt @item --include-certs @var{n} @opindex include-certs Using @var{n} of -2 includes all certificate except for the root cert, -1 includes all certs, 0 does not include any certs, 1 includes only the signers cert and all other positive values include up to @var{n} certificates starting with the signer cert. The default is -2. @item --cipher-algo @var{oid} @opindex cipher-algo Use the cipher algorithm with the ASN.1 object identifier @var{oid} for encryption. For convenience the strings @code{3DES}, @code{AES} and @code{AES256} may be used instead of their OIDs. The default is @code{AES} (2.16.840.1.101.3.4.1.2). @item --digest-algo @code{name} Use @code{name} as the message digest algorithm. Usually this algorithm is deduced from the respective signing certificate. This option forces the use of the given algorithm and may lead to severe interoperability problems. @end table @c ******************************************* @c ******** ESOTERIC OPTIONS *************** @c ******************************************* @node Esoteric Options @subsection Doing things one usually do not want to do @table @gnupgtabopt @item --extra-digest-algo @var{name} @opindex extra-digest-algo Sometimes signatures are broken in that they announce a different digest algorithm than actually used. @command{gpgsm} uses a one-pass data processing model and thus needs to rely on the announced digest algorithms to properly hash the data. As a workaround this option may be used to tell @command{gpgsm} to also hash the data using the algorithm @var{name}; this slows processing down a little bit but allows verification of such broken signatures. If @command{gpgsm} prints an error like ``digest algo 8 has not been enabled'' you may want to try this option, with @samp{SHA256} for @var{name}. @item --faked-system-time @var{epoch} @opindex faked-system-time This option is only useful for testing; it sets the system time back or forth to @var{epoch} which is the number of seconds elapsed since the year 1970. Alternatively @var{epoch} may be given as a full ISO time string (e.g. "20070924T154812"). @item --with-ephemeral-keys @opindex with-ephemeral-keys Include ephemeral flagged keys in the output of key listings. Note that they are included anyway if the key specification for a listing is given as fingerprint or keygrip. @item --debug-level @var{level} @opindex debug-level Select the debug level for investigating problems. @var{level} may be a numeric value or by a keyword: @table @code @item none No debugging at all. A value of less than 1 may be used instead of the keyword. @item basic Some basic debug messages. A value between 1 and 2 may be used instead of the keyword. @item advanced More verbose debug messages. A value between 3 and 5 may be used instead of the keyword. @item expert Even more detailed messages. A value between 6 and 8 may be used instead of the keyword. @item guru All of the debug messages you can get. A value greater than 8 may be used instead of the keyword. The creation of hash tracing files is only enabled if the keyword is used. @end table How these messages are mapped to the actual debugging flags is not specified and may change with newer releases of this program. They are however carefully selected to best aid in debugging. @item --debug @var{flags} @opindex debug This option is only useful for debugging and the behaviour may change at any time without notice; using @code{--debug-levels} is the preferred method to select the debug verbosity. FLAGS are bit encoded and may be given in usual C-Syntax. The currently defined bits are: @table @code @item 0 (1) X.509 or OpenPGP protocol related data @item 1 (2) values of big number integers @item 2 (4) low level crypto operations @item 5 (32) memory allocation @item 6 (64) caching @item 7 (128) show memory statistics @item 9 (512) write hashed data to files named @code{dbgmd-000*} @item 10 (1024) trace Assuan protocol @end table Note, that all flags set using this option may get overridden by @code{--debug-level}. @item --debug-all @opindex debug-all Same as @code{--debug=0xffffffff} @item --debug-allow-core-dump @opindex debug-allow-core-dump Usually @command{gpgsm} tries to avoid dumping core by well written code and by disabling core dumps for security reasons. However, bugs are pretty durable beasts and to squash them it is sometimes useful to have a core dump. This option enables core dumps unless the Bad Thing happened before the option parsing. @item --debug-no-chain-validation @opindex debug-no-chain-validation This is actually not a debugging option but only useful as such. It lets @command{gpgsm} bypass all certificate chain validation checks. @item --debug-ignore-expiration @opindex debug-ignore-expiration This is actually not a debugging option but only useful as such. It lets @command{gpgsm} ignore all notAfter dates, this is used by the regression tests. @item --passphrase-fd @code{n} @opindex passphrase-fd Read the passphrase from file descriptor @code{n}. Only the first line will be read from file descriptor @code{n}. If you use 0 for @code{n}, the passphrase will be read from STDIN. This can only be used if only one passphrase is supplied. Note that this passphrase is only used if the option @option{--batch} has also been given. @item --pinentry-mode @code{mode} @opindex pinentry-mode Set the pinentry mode to @code{mode}. Allowed values for @code{mode} are: @table @asis @item default Use the default of the agent, which is @code{ask}. @item ask Force the use of the Pinentry. @item cancel Emulate use of Pinentry's cancel button. @item error Return a Pinentry error (``No Pinentry''). @item loopback Redirect Pinentry queries to the caller. Note that in contrast to Pinentry the user is not prompted again if he enters a bad password. @end table @item --no-common-certs-import @opindex no-common-certs-import Suppress the import of common certificates on keybox creation. @end table All the long options may also be given in the configuration file after stripping off the two leading dashes. @c ******************************************* @c *************** **************** @c *************** USER ID **************** @c *************** **************** @c ******************************************* @mansect how to specify a user id @ifset isman @include specify-user-id.texi @end ifset @c ******************************************* @c *************** **************** @c *************** FILES **************** @c *************** **************** @c ******************************************* @mansect files @node GPGSM Configuration @section Configuration files There are a few configuration files to control certain aspects of @command{gpgsm}'s operation. Unless noted, they are expected in the current home directory (@pxref{option --homedir}). @table @file @item gpgsm.conf @efindex gpgsm.conf This is the standard configuration file read by @command{gpgsm} on startup. It may contain any valid long option; the leading two dashes may not be entered and the option may not be abbreviated. This default name may be changed on the command line (@pxref{gpgsm-option --options}). You should backup this file. @item policies.txt @efindex policies.txt This is a list of allowed CA policies. This file should list the object identifiers of the policies line by line. Empty lines and lines starting with a hash mark are ignored. Policies missing in this file and not marked as critical in the certificate will print only a warning; certificates with policies marked as critical and not listed in this file will fail the signature verification. You should backup this file. For example, to allow only the policy 2.289.9.9, the file should look like this: @c man:.RS @example # Allowed policies 2.289.9.9 @end example @c man:.RE @item qualified.txt @efindex qualified.txt This is the list of root certificates used for qualified certificates. They are defined as certificates capable of creating legally binding signatures in the same way as handwritten signatures are. Comments start with a hash mark and empty lines are ignored. Lines do have a length limit but this is not a serious limitation as the format of the entries is fixed and checked by @command{gpgsm}: A non-comment line starts with optional whitespace, followed by exactly 40 hex characters, white space and a lowercased 2 letter country code. Additional data delimited with by a white space is current ignored but might late be used for other purposes. Note that even if a certificate is listed in this file, this does not mean that the certificate is trusted; in general the certificates listed in this file need to be listed also in @file{trustlist.txt}. This is a global file an installed in the data directory (e.g. @file{@value{DATADIR}/qualified.txt}). GnuPG installs a suitable file with root certificates as used in Germany. As new Root-CA certificates may be issued over time, these entries may need to be updated; new distributions of this software should come with an updated list but it is still the responsibility of the Administrator to check that this list is correct. Every time @command{gpgsm} uses a certificate for signing or verification this file will be consulted to check whether the certificate under question has ultimately been issued by one of these CAs. If this is the case the user will be informed that the verified signature represents a legally binding (``qualified'') signature. When creating a signature using such a certificate an extra prompt will be issued to let the user confirm that such a legally binding signature shall really be created. Because this software has not yet been approved for use with such certificates, appropriate notices will be shown to indicate this fact. @item help.txt @efindex help.txt This is plain text file with a few help entries used with @command{pinentry} as well as a large list of help items for @command{gpg} and @command{gpgsm}. The standard file has English help texts; to install localized versions use filenames like @file{help.LL.txt} with LL denoting the locale. GnuPG comes with a set of predefined help files in the data directory (e.g. @file{@value{DATADIR}/gnupg/help.de.txt}) and allows overriding of any help item by help files stored in the system configuration directory (e.g. @file{@value{SYSCONFDIR}/help.de.txt}). For a reference of the help file's syntax, please see the installed @file{help.txt} file. @item com-certs.pem @efindex com-certs.pem This file is a collection of common certificates used to populated a newly created @file{pubring.kbx}. An administrator may replace this file with a custom one. The format is a concatenation of PEM encoded X.509 certificates. This global file is installed in the data directory (e.g. @file{@value{DATADIR}/com-certs.pem}). @end table @c man:.RE Note that on larger installations, it is useful to put predefined files into the directory @file{/etc/skel/.gnupg/} so that newly created users start up with a working configuration. For existing users a small helper script is provided to create these files (@pxref{addgnupghome}). For internal purposes @command{gpgsm} creates and maintains a few other files; they all live in the current home directory (@pxref{option --homedir}). Only @command{gpgsm} may modify these files. @table @file @item pubring.kbx @efindex pubring.kbx This a database file storing the certificates as well as meta information. For debugging purposes the tool @command{kbxutil} may be used to show the internal structure of this file. You should backup this file. @item random_seed @efindex random_seed This content of this file is used to maintain the internal state of the random number generator across invocations. The same file is used by other programs of this software too. @item S.gpg-agent @efindex S.gpg-agent If this file exists @command{gpgsm} will first try to connect to this socket for accessing @command{gpg-agent} before starting a new @command{gpg-agent} instance. Under Windows this socket (which in reality be a plain file describing a regular TCP listening port) is the standard way of connecting the @command{gpg-agent}. @end table @c ******************************************* @c *************** **************** @c *************** EXAMPLES **************** @c *************** **************** @c ******************************************* @mansect examples @node GPGSM Examples @section Examples @example $ gpgsm -er goo@@bar.net ciphertext @end example @c ******************************************* @c *************** ************** @c *************** UNATTENDED ************** @c *************** ************** @c ******************************************* @manpause @node Unattended Usage @section Unattended Usage @command{gpgsm} is often used as a backend engine by other software. To help with this a machine interface has been defined to have an unambiguous way to do this. This is most likely used with the @code{--server} command but may also be used in the standard operation mode by using the @code{--status-fd} option. @menu * Automated signature checking:: Automated signature checking. * CSR and certificate creation:: CSR and certificate creation. @end menu @node Automated signature checking @subsection Automated signature checking It is very important to understand the semantics used with signature verification. Checking a signature is not as simple as it may sound and so the operation is a bit complicated. In most cases it is required to look at several status lines. Here is a table of all cases a signed message may have: @table @asis @item The signature is valid This does mean that the signature has been successfully verified, the certificates are all sane. However there are two subcases with important information: One of the certificates may have expired or a signature of a message itself as expired. It is a sound practise to consider such a signature still as valid but additional information should be displayed. Depending on the subcase @command{gpgsm} will issue these status codes: @table @asis @item signature valid and nothing did expire @code{GOODSIG}, @code{VALIDSIG}, @code{TRUST_FULLY} @item signature valid but at least one certificate has expired @code{EXPKEYSIG}, @code{VALIDSIG}, @code{TRUST_FULLY} @item signature valid but expired @code{EXPSIG}, @code{VALIDSIG}, @code{TRUST_FULLY} Note, that this case is currently not implemented. @end table @item The signature is invalid This means that the signature verification failed (this is an indication of a transfer error, a program error or tampering with the message). @command{gpgsm} issues one of these status codes sequences: @table @code @item @code{BADSIG} @item @code{GOODSIG}, @code{VALIDSIG} @code{TRUST_NEVER} @end table @item Error verifying a signature For some reason the signature could not be verified, i.e. it cannot be decided whether the signature is valid or invalid. A common reason for this is a missing certificate. @end table @node CSR and certificate creation @subsection CSR and certificate creation The command @option{--generate-key} may be used along with the option @option{--batch} to either create a certificate signing request (CSR) or an X.509 certificate. This is controlled by a parameter file; the format of this file is as follows: @itemize @bullet @item Text only, line length is limited to about 1000 characters. @item UTF-8 encoding must be used to specify non-ASCII characters. @item Empty lines are ignored. @item Leading and trailing while space is ignored. @item A hash sign as the first non white space character indicates a comment line. @item Control statements are indicated by a leading percent sign, the arguments are separated by white space from the keyword. @item Parameters are specified by a keyword, followed by a colon. Arguments are separated by white space. @item The first parameter must be @samp{Key-Type}, control statements may be placed anywhere. @item The order of the parameters does not matter except for @samp{Key-Type} which must be the first parameter. The parameters are only used for the generated CSR/certificate; parameters from previous sets are not used. Some syntactically checks may be performed. @item Key generation takes place when either the end of the parameter file is reached, the next @samp{Key-Type} parameter is encountered or at the control statement @samp{%commit} is encountered. @end itemize @noindent Control statements: @table @asis @item %echo @var{text} Print @var{text} as diagnostic. @item %dry-run Suppress actual key generation (useful for syntax checking). @item %commit Perform the key generation. Note that an implicit commit is done at the next @asis{Key-Type} parameter. @c %certfile <filename> @c [Not yet implemented!] @c Do not write the certificate to the keyDB but to <filename>. @c This must be given before the first @c commit to take place, duplicate specification of the same filename @c is ignored, the last filename before a commit is used. @c The filename is used until a new filename is used (at commit points) @c and all keys are written to that file. If a new filename is given, @c this file is created (and overwrites an existing one). @c Both control statements must be given. @end table @noindent General Parameters: @table @asis @item Key-Type: @var{algo} Starts a new parameter block by giving the type of the primary key. The algorithm must be capable of signing. This is a required parameter. The only supported value for @var{algo} is @samp{rsa}. @item Key-Length: @var{nbits} The requested length of a generated key in bits. Defaults to 2048. @item Key-Grip: @var{hexstring} This is optional and used to generate a CSR or certificate for an already existing key. Key-Length will be ignored when given. @item Key-Usage: @var{usage-list} Space or comma delimited list of key usage, allowed values are @samp{encrypt}, @samp{sign} and @samp{cert}. This is used to generate the keyUsage extension. Please make sure that the algorithm is capable of this usage. Default is to allow encrypt and sign. @item Name-DN: @var{subject-name} This is the Distinguished Name (DN) of the subject in RFC-2253 format. @item Name-Email: @var{string} This is an email address for the altSubjectName. This parameter is optional but may occur several times to add several email addresses to a certificate. @item Name-DNS: @var{string} The is an DNS name for the altSubjectName. This parameter is optional but may occur several times to add several DNS names to a certificate. @item Name-URI: @var{string} This is an URI for the altSubjectName. This parameter is optional but may occur several times to add several URIs to a certificate. @end table @noindent Additional parameters used to create a certificate (in contrast to a certificate signing request): @table @asis @item Serial: @var{sn} If this parameter is given an X.509 certificate will be generated. @var{sn} is expected to be a hex string representing an unsigned integer of arbitrary length. The special value @samp{random} can be used to create a 64 bit random serial number. @item Issuer-DN: @var{issuer-name} This is the DN name of the issuer in RFC-2253 format. If it is not set it will default to the subject DN and a special GnuPG extension will be included in the certificate to mark it as a standalone certificate. @item Creation-Date: @var{iso-date} @itemx Not-Before: @var{iso-date} Set the notBefore date of the certificate. Either a date like @samp{1986-04-26} or @samp{1986-04-26 12:00} or a standard ISO timestamp like @samp{19860426T042640} may be used. The time is considered to be UTC. If it is not given the current date is used. @item Expire-Date: @var{iso-date} @itemx Not-After: @var{iso-date} Set the notAfter date of the certificate. Either a date like @samp{2063-04-05} or @samp{2063-04-05 17:00} or a standard ISO timestamp like @samp{20630405T170000} may be used. The time is considered to be UTC. If it is not given a default value in the not too far future is used. @item Signing-Key: @var{keygrip} This gives the keygrip of the key used to sign the certificate. If it is not given a self-signed certificate will be created. For compatibility with future versions, it is suggested to prefix the keygrip with a @samp{&}. @item Hash-Algo: @var{hash-algo} Use @var{hash-algo} for this CSR or certificate. The supported hash algorithms are: @samp{sha1}, @samp{sha256}, @samp{sha384} and @samp{sha512}; they may also be specified with uppercase letters. The default is @samp{sha256}. @end table @c ******************************************* @c *************** ***************** @c *************** ASSSUAN ***************** @c *************** ***************** @c ******************************************* @node GPGSM Protocol @section The Protocol the Server Mode Uses Description of the protocol used to access @command{GPGSM}. @command{GPGSM} does implement the Assuan protocol and in addition provides a regular command line interface which exhibits a full client to this protocol (but uses internal linking). To start @command{gpgsm} as a server the command line the option @code{--server} must be used. Additional options are provided to select the communication method (i.e. the name of the socket). We assume that the connection has already been established; see the Assuan manual for details. @menu * GPGSM ENCRYPT:: Encrypting a message. * GPGSM DECRYPT:: Decrypting a message. * GPGSM SIGN:: Signing a message. * GPGSM VERIFY:: Verifying a message. * GPGSM GENKEY:: Generating a key. * GPGSM LISTKEYS:: List available keys. * GPGSM EXPORT:: Export certificates. * GPGSM IMPORT:: Import certificates. * GPGSM DELETE:: Delete certificates. * GPGSM GETAUDITLOG:: Retrieve an audit log. * GPGSM GETINFO:: Information about the process * GPGSM OPTION:: Session options. @end menu @node GPGSM ENCRYPT @subsection Encrypting a Message Before encryption can be done the recipient must be set using the command: @example RECIPIENT @var{userID} @end example Set the recipient for the encryption. @var{userID} should be the internal representation of the key; the server may accept any other way of specification. If this is a valid and trusted recipient the server does respond with OK, otherwise the return is an ERR with the reason why the recipient cannot be used, the encryption will then not be done for this recipient. If the policy is not to encrypt at all if not all recipients are valid, the client has to take care of this. All @code{RECIPIENT} commands are cumulative until a @code{RESET} or an successful @code{ENCRYPT} command. @example INPUT FD[=@var{n}] [--armor|--base64|--binary] @end example Set the file descriptor for the message to be encrypted to @var{n}. Obviously the pipe must be open at that point, the server establishes its own end. If the server returns an error the client should consider this session failed. If @var{n} is not given, this commands uses the last file descriptor passed to the application. @xref{fun-assuan_sendfd, ,the assuan_sendfd function,assuan,the Libassuan manual}, on how to do descriptor passing. The @code{--armor} option may be used to advice the server that the input data is in @acronym{PEM} format, @code{--base64} advices that a raw base-64 encoding is used, @code{--binary} advices of raw binary input (@acronym{BER}). If none of these options is used, the server tries to figure out the used encoding, but this may not always be correct. @example OUTPUT FD[=@var{n}] [--armor|--base64] @end example Set the file descriptor to be used for the output (i.e. the encrypted message). Obviously the pipe must be open at that point, the server establishes its own end. If the server returns an error the client should consider this session failed. The option @option{--armor} encodes the output in @acronym{PEM} format, the @option{--base64} option applies just a base-64 encoding. No option creates binary output (@acronym{BER}). The actual encryption is done using the command @example ENCRYPT @end example It takes the plaintext from the @code{INPUT} command, writes to the ciphertext to the file descriptor set with the @code{OUTPUT} command, take the recipients from all the recipients set so far. If this command fails the clients should try to delete all output currently done or otherwise mark it as invalid. @command{GPGSM} does ensure that there will not be any security problem with leftover data on the output in this case. This command should in general not fail, as all necessary checks have been done while setting the recipients. The input and output pipes are closed. @node GPGSM DECRYPT @subsection Decrypting a message Input and output FDs are set the same way as in encryption, but @code{INPUT} refers to the ciphertext and @code{OUTPUT} to the plaintext. There is no need to set recipients. @command{GPGSM} automatically strips any @acronym{S/MIME} headers from the input, so it is valid to pass an entire MIME part to the INPUT pipe. The decryption is done by using the command @example DECRYPT @end example It performs the decrypt operation after doing some check on the internal state (e.g. that all needed data has been set). Because it utilizes the GPG-Agent for the session key decryption, there is no need to ask the client for a protecting passphrase - GpgAgent takes care of this by requesting this from the user. @node GPGSM SIGN @subsection Signing a Message Signing is usually done with these commands: @example INPUT FD[=@var{n}] [--armor|--base64|--binary] @end example This tells @command{GPGSM} to read the data to sign from file descriptor @var{n}. @example OUTPUT FD[=@var{m}] [--armor|--base64] @end example Write the output to file descriptor @var{m}. If a detached signature is requested, only the signature is written. @example SIGN [--detached] @end example Sign the data set with the @code{INPUT} command and write it to the sink set by @code{OUTPUT}. With @code{--detached}, a detached signature is created (surprise). The key used for signing is the default one or the one specified in the configuration file. To get finer control over the keys, it is possible to use the command @example SIGNER @var{userID} @end example to set the signer's key. @var{userID} should be the internal representation of the key; the server may accept any other way of specification. If this is a valid and trusted recipient the server does respond with OK, otherwise the return is an ERR with the reason why the key cannot be used, the signature will then not be created using this key. If the policy is not to sign at all if not all keys are valid, the client has to take care of this. All @code{SIGNER} commands are cumulative until a @code{RESET} is done. Note that a @code{SIGN} does not reset this list of signers which is in contrast to the @code{RECIPIENT} command. @node GPGSM VERIFY @subsection Verifying a Message To verify a message the command: @example VERIFY @end example is used. It does a verify operation on the message send to the input FD. The result is written out using status lines. If an output FD was given, the signed text will be written to that. If the signature is a detached one, the server will inquire about the signed material and the client must provide it. @node GPGSM GENKEY @subsection Generating a Key This is used to generate a new keypair, store the secret part in the @acronym{PSE} and the public key in the key database. We will probably add optional commands to allow the client to select whether a hardware token is used to store the key. Configuration options to @command{GPGSM} can be used to restrict the use of this command. @example GENKEY @end example @command{GPGSM} checks whether this command is allowed and then does an INQUIRY to get the key parameters, the client should then send the key parameters in the native format: @example S: INQUIRE KEY_PARAM native C: D foo:fgfgfg C: D bar C: END @end example Please note that the server may send Status info lines while reading the data lines from the client. After this the key generation takes place and the server eventually does send an ERR or OK response. Status lines may be issued as a progress indicator. @node GPGSM LISTKEYS @subsection List available keys @anchor{gpgsm-cmd listkeys} To list the keys in the internal database or using an external key provider, the command: @example LISTKEYS @var{pattern} @end example is used. To allow multiple patterns (which are ORed during the search) quoting is required: Spaces are to be translated into "+" or into "%20"; in turn this requires that the usual escape quoting rules are done. @example LISTSECRETKEYS @var{pattern} @end example Lists only the keys where a secret key is available. The list commands are affected by the option @example OPTION list-mode=@var{mode} @end example where mode may be: @table @code @item 0 Use default (which is usually the same as 1). @item 1 List only the internal keys. @item 2 List only the external keys. @item 3 List internal and external keys. @end table Note that options are valid for the entire session. @node GPGSM EXPORT @subsection Export certificates To export certificate from the internal key database the command: @example EXPORT [--data [--armor] [--base64]] [--] @var{pattern} @end example is used. To allow multiple patterns (which are ORed) quoting is required: Spaces are to be translated into "+" or into "%20"; in turn this requires that the usual escape quoting rules are done. If the @option{--data} option has not been given, the format of the output depends on what was set with the @code{OUTPUT} command. When using @acronym{PEM} encoding a few informational lines are prepended. If the @option{--data} has been given, a target set via @code{OUTPUT} is ignored and the data is returned inline using standard @code{D}-lines. This avoids the need for an extra file descriptor. In this case the options @option{--armor} and @option{--base64} may be used in the same way as with the @code{OUTPUT} command. @node GPGSM IMPORT @subsection Import certificates To import certificates into the internal key database, the command @example IMPORT [--re-import] @end example is used. The data is expected on the file descriptor set with the @code{INPUT} command. Certain checks are performed on the certificate. Note that the code will also handle PKCS#12 files and import private keys; a helper program is used for that. With the option @option{--re-import} the input data is expected to a be a linefeed separated list of fingerprints. The command will re-import the corresponding certificates; that is they are made permanent by removing their ephemeral flag. @node GPGSM DELETE @subsection Delete certificates To delete a certificate the command @example DELKEYS @var{pattern} @end example is used. To allow multiple patterns (which are ORed) quoting is required: Spaces are to be translated into "+" or into "%20"; in turn this requires that the usual escape quoting rules are done. The certificates must be specified unambiguously otherwise an error is returned. @node GPGSM GETAUDITLOG @subsection Retrieve an audit log @anchor{gpgsm-cmd getauditlog} This command is used to retrieve an audit log. @example GETAUDITLOG [--data] [--html] @end example If @option{--data} is used, the audit log is send using D-lines instead of being sent to the file descriptor given by an @code{OUTPUT} command. If @option{--html} is used, the output is formatted as an XHTML block. This is designed to be incorporated into a HTML document. @node GPGSM GETINFO @subsection Return information about the process This is a multipurpose function to return a variety of information. @example GETINFO @var{what} @end example The value of @var{what} specifies the kind of information returned: @table @code @item version Return the version of the program. @item pid Return the process id of the process. @item agent-check Return OK if the agent is running. @item cmd_has_option @var{cmd} @var{opt} Return OK if the command @var{cmd} implements the option @var{opt}. The leading two dashes usually used with @var{opt} shall not be given. @item offline Return OK if the connection is in offline mode. This may be either due to a @code{OPTION offline=1} or due to @command{gpgsm} being started with option @option{--disable-dirmngr}. @end table @node GPGSM OPTION @subsection Session options The standard Assuan option handler supports these options. @example OPTION @var{name}[=@var{value}] @end example These @var{name}s are recognized: @table @code @item putenv Change the session's environment to be passed via gpg-agent to Pinentry. @var{value} is a string of the form @code{<KEY>[=[<STRING>]]}. If only @code{<KEY>} is given the environment variable @code{<KEY>} is removed from the session environment, if @code{<KEY>=} is given that environment variable is set to the empty string, and if @code{<STRING>} is given it is set to that string. @item display @efindex DISPLAY Set the session environment variable @code{DISPLAY} is set to @var{value}. @item ttyname @efindex GPG_TTY Set the session environment variable @code{GPG_TTY} is set to @var{value}. @item ttytype @efindex TERM Set the session environment variable @code{TERM} is set to @var{value}. @item lc-ctype @efindex LC_CTYPE Set the session environment variable @code{LC_CTYPE} is set to @var{value}. @item lc-messages @efindex LC_MESSAGES Set the session environment variable @code{LC_MESSAGES} is set to @var{value}. @item xauthority @efindex XAUTHORITY Set the session environment variable @code{XAUTHORITY} is set to @var{value}. @item pinentry-user-data @efindex PINENTRY_USER_DATA Set the session environment variable @code{PINENTRY_USER_DATA} is set to @var{value}. @item include-certs This option overrides the command line option @option{--include-certs}. A @var{value} of -2 includes all certificates except for the root certificate, -1 includes all -certicates, 0 does not include any certicates, 1 includes only the -signers certicate and all other positive values include up to +certificates, 0 does not include any certificates, 1 includes only the +signers certificate and all other positive values include up to @var{value} certificates starting with the signer cert. @item list-mode @xref{gpgsm-cmd listkeys}. @item list-to-output If @var{value} is true the output of the list commands (@pxref{gpgsm-cmd listkeys}) is written to the file descriptor set with the last @code{OUTPUT} command. If @var{value} is false the output is written via data lines; this is the default. @item with-validation If @var{value} is true for each listed certificate the validation status is printed. This may result in the download of a CRL or the user being asked about the trustworthiness of a root certificate. The default is given by a command line option (@pxref{gpgsm-option --with-validation}). @item with-secret If @var{value} is true certificates with a corresponding private key are marked by the list commands. @item validation-model This option overrides the command line option @option{validation-model} for the session. (@xref{gpgsm-option --validation-model}.) @item with-key-data This option globally enables the command line option @option{--with-key-data}. (@xref{gpgsm-option --with-key-data}.) @item enable-audit-log If @var{value} is true data to write an audit log is gathered. (@xref{gpgsm-cmd getauditlog}.) @item allow-pinentry-notify If this option is used notifications about the launch of a Pinentry are passed back to the client. @item with-ephemeral-keys If @var{value} is true ephemeral certificates are included in the output of the list commands. @item no-encrypt-to If this option is used all keys set by the command line option @option{--encrypt-to} are ignored. @item offline If @var{value} is true or @var{value} is not given all network access is disabled for this session. This is the same as the command line option @option{--disable-dirmngr}. @end table @mansect see also @ifset isman @command{gpg2}(1), @command{gpg-agent}(1) @end ifset @include see-also-note.texi diff --git a/g10/call-agent.c b/g10/call-agent.c index 0ba978774..be8c33d74 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1,2299 +1,2299 @@ /* call-agent.c - Divert GPG operations to the agent. * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. * Copyright (C) 2013-2015 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include "gpg.h" #include <assuan.h> #include "../common/util.h" #include "../common/membuf.h" #include "options.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "call-agent.h" #include "../common/status.h" #include "../common/shareddefs.h" #include "../common/host2net.h" #define CONTROL_D ('D' - 'A' + 1) static assuan_context_t agent_ctx = NULL; static int did_early_card_test; struct default_inq_parm_s { ctrl_t ctrl; assuan_context_t ctx; struct { u32 *keyid; u32 *mainkeyid; int pubkey_algo; } keyinfo; }; struct cipher_parm_s { struct default_inq_parm_s *dflt; assuan_context_t ctx; unsigned char *ciphertext; size_t ciphertextlen; }; struct writecert_parm_s { struct default_inq_parm_s *dflt; const unsigned char *certdata; size_t certdatalen; }; struct writekey_parm_s { struct default_inq_parm_s *dflt; const unsigned char *keydata; size_t keydatalen; }; struct genkey_parm_s { struct default_inq_parm_s *dflt; const char *keyparms; const char *passphrase; }; struct import_key_parm_s { struct default_inq_parm_s *dflt; const void *key; size_t keylen; }; struct cache_nonce_parm_s { char **cache_nonce_addr; char **passwd_nonce_addr; }; static gpg_error_t learn_status_cb (void *opaque, const char *line); /* If RC is not 0, write an appropriate status message. */ static void status_sc_op_failure (int rc) { switch (gpg_err_code (rc)) { case 0: break; case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: write_status_text (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: write_status_text (STATUS_SC_OP_FAILURE, "2"); break; default: write_status (STATUS_SC_OP_FAILURE); break; } } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct default_inq_parm_s *parm = opaque; if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) { err = gpg_proxy_pinentry_notify (parm->ctrl, line); if (err) log_error (_("failed to proxy %s inquiry to client\n"), "PINENTRY_LAUNCHED"); /* We do not pass errors to avoid breaking other code. */ } else if ((has_leading_keyword (line, "PASSPHRASE") || has_leading_keyword (line, "NEW_PASSPHRASE")) && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) { if (have_static_passphrase ()) { const char *s = get_static_passphrase (); err = assuan_send_data (parm->ctx, s, strlen (s)); } else { char *pw; char buf[32]; if (parm->keyinfo.keyid) emit_status_need_passphrase (parm->ctrl, parm->keyinfo.keyid, parm->keyinfo.mainkeyid, parm->keyinfo.pubkey_algo); snprintf (buf, sizeof (buf), "%u", 100); write_status_text (STATUS_INQUIRE_MAXLEN, buf); pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: ")); cpr_kill_prompt (); if (*pw == CONTROL_D && !pw[1]) err = gpg_error (GPG_ERR_CANCELED); else err = assuan_send_data (parm->ctx, pw, strlen (pw)); xfree (pw); } } else log_debug ("ignoring gpg-agent inquiry '%s'\n", line); return err; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) { gpg_error_t err; char *serverversion; const char *myversion = strusage (13); err = get_assuan_server_version (ctx, mode, &serverversion); if (err) log_error (_("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); write_status_strings (STATUS_WARNING, "server_version_mismatch 0", " ", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } /* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_agent (ctrl_t ctrl, int for_card) { int rc; (void)ctrl; /* Not yet used. */ /* Fixme: We need a context for each thread or serialize the access to the agent. */ if (agent_ctx) rc = 0; else { rc = start_new_gpg_agent (&agent_ctx, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, opt.lc_ctype, opt.lc_messages, opt.session_env, opt.autostart, opt.verbose, DBG_IPC, NULL, NULL); if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) { static int shown; if (!shown) { shown = 1; log_info (_("no gpg-agent running in this session\n")); } } else if (!rc && !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) { /* Tell the agent that we support Pinentry notifications. No error checking so that it will work also with older agents. */ assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); /* Tell the agent about what version we are aware. This is here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", NULL, NULL, NULL, NULL, NULL, NULL); /* Pass on the pinentry mode. */ if (opt.pinentry_mode) { char *tmp = xasprintf ("OPTION pinentry-mode=%s", str_pinentry_mode (opt.pinentry_mode)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting pinentry mode '%s' failed: %s\n", str_pinentry_mode (opt.pinentry_mode), gpg_strerror (rc)); write_status_error ("set_pinentry_mode", rc); } } } } if (!rc && for_card && !did_early_card_test) { /* Request the serial number of the card for an early test. */ struct agent_card_info_s info; memset (&info, 0, sizeof info); rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!rc) rc = assuan_transact (agent_ctx, "SCD SERIALNO openpgp", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (rc) { switch (gpg_err_code (rc)) { case GPG_ERR_NOT_SUPPORTED: case GPG_ERR_NO_SCDAEMON: write_status_text (STATUS_CARDCTRL, "6"); break; case GPG_ERR_OBJ_TERM_STATE: write_status_text (STATUS_CARDCTRL, "7"); break; default: write_status_text (STATUS_CARDCTRL, "4"); log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc)); break; } } if (!rc && is_status_enabled () && info.serialno) { char *buf; buf = xasprintf ("3 %s", info.serialno); write_status_text (STATUS_CARDCTRL, buf); xfree (buf); } agent_release_card_info (&info); if (!rc) did_early_card_test = 1; } return rc; } /* 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. */ static char * unescape_status_string (const unsigned char *s) { return percent_plus_unescape (s, 0xff); } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if (*s || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *s; char *p; for (s=line; hexdigitp (s); s++) ; p = xtrymalloc (s + 1 - line); if (p) { memcpy (p, line, s-line); p[s-line] = 0; } return p; } /* This is a dummy data line callback. */ static gpg_error_t dummy_data_cb (void *opaque, const void *buffer, size_t length) { (void)opaque; (void)buffer; (void)length; return 0; } /* A simple callback used to return the serialnumber of a card. */ static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return out_of_core (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Release the card info structure INFO. */ void agent_release_card_info (struct agent_card_info_s *info) { int i; if (!info) return; xfree (info->reader); info->reader = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->apptype); info->apptype = NULL; xfree (info->disp_name); info->disp_name = NULL; xfree (info->disp_lang); info->disp_lang = NULL; xfree (info->pubkey_url); info->pubkey_url = NULL; xfree (info->login_data); info->login_data = NULL; info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct agent_card_info_s *parm = opaque; const char *keyword = line; int keywordlen; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen)) { xfree (parm->reader); parm->reader = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { xfree (parm->serialno); parm->serialno = store_serialno (line); parm->is_v2 = (strlen (parm->serialno) >= 16 && xtoi_2 (parm->serialno+12) >= 2 ); } else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) { xfree (parm->apptype); parm->apptype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i=0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } xfree (buf); } } else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; buf = p = unescape_status_string (line); if (buf) { for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) { p2 = strchr (p, '='); if (p2) { *p2++ = 0; abool = (*p2 == '1'); if (!strcmp (p, "ki")) parm->extcap.ki = abool; else if (!strcmp (p, "aac")) parm->extcap.aac = abool; else if (!strcmp (p, "si")) parm->status_indicator = strtoul (p2, NULL, 10); } } xfree (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1valid = unhexify_fpr (line, parm->fpr1); else if (no == 2) parm->fpr2valid = unhexify_fpr (line, parm->fpr2); else if (no == 3) parm->fpr3valid = unhexify_fpr (line, parm->fpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen)) { int no = atoi (line); while (* line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1time = strtoul (line, NULL, 10); else if (no == 2) parm->fpr2time = strtoul (line, NULL, 10); else if (no == 3) parm->fpr3time = strtoul (line, NULL, 10); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); else if (no == 2) parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); else if (no == 3) parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); } else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) { int keyno = 0; int algo = PUBKEY_ALGO_RSA; int n = 0; sscanf (line, "%d %d %n", &keyno, &algo, &n); keyno--; if (keyno < 0 || keyno >= DIM (parm->key_attr)) return 0; parm->key_attr[keyno].algo = algo; if (algo == PUBKEY_ALGO_RSA) parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n, NULL, NULL); } else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) && strchr("1234", keyword[11])) { int no = keyword[11] - '1'; log_assert (no >= 0 && no <= 3); xfree (parm->private_do[no]); parm->private_do[no] = unescape_status_string (line); } return 0; } /* Call the scdaemon to learn about a smartcard */ int agent_scd_learn (struct agent_card_info_s *info, int force) { int rc; struct default_inq_parm_s parm; struct agent_card_info_s dummyinfo; if (!info) info = &dummyinfo; memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, force ? "LEARN --sendinfo --force" : "LEARN --sendinfo", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); /* Also try to get the key attributes. */ if (!rc) agent_scd_getattr ("KEY-ATTR", info); if (info == &dummyinfo) agent_release_card_info (info); return rc; } /* Send an APDU to the current card. On success the status word is stored at R_SW. With HEXAPDU being NULL only a RESET command is send to scd. With HEXAPDU being the string "undefined" the command "SERIALNO undefined" is send to scd. */ gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) { gpg_error_t err; /* Start the agent but not with the card flag so that we do not autoselect the openpgp application. */ err = start_agent (NULL, 0); if (err) return err; if (!hexapdu) { err = assuan_transact (agent_ctx, "SCD RESET", NULL, NULL, NULL, NULL, NULL, NULL); } else if (!strcmp (hexapdu, "undefined")) { err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", NULL, NULL, NULL, NULL, NULL, NULL); } else { char line[ASSUAN_LINELENGTH]; membuf_t mb; unsigned char *data; size_t datalen; init_membuf (&mb, 256); snprintf (line, DIM(line), "SCD APDU %s", hexapdu); err = assuan_transact (agent_ctx, line, put_membuf_cb, &mb, NULL, NULL, NULL, NULL); if (!err) { data = get_membuf (&mb, &datalen); if (!data) err = gpg_error_from_syserror (); else if (datalen < 2) /* Ooops */ err = gpg_error (GPG_ERR_CARD); else { *r_sw = buf16_to_uint (data+datalen-2); } xfree (data); } } return err; } int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s", force?"--force ": "", hexgrip, serialno, keyno, timestamp); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); if (rc) return rc; return rc; } /* Call the agent to retrieve a data object. This function returns the data in the same structure as used by the learn command. It is - allowed to update such a structure using this commmand. */ + allowed to update such a structure using this command. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); if (!*name) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "SCD GETATTR "), name); rc = start_agent (NULL, 1); if (rc) return rc; parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, learn_status_cb, info); return rc; } /* Send an setattr command to the SCdaemon. SERIALNO is not actually used here but required by gpg 1.4's implementation of this code in cardglue.c. */ int agent_scd_setattr (const char *name, const unsigned char *value, size_t valuelen, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; char *p; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); (void)serialno; if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); /* We assume that NAME does not need escaping. */ if (12 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); p = stpcpy (stpcpy (line, "SCD SETATTR "), name); *p++ = ' '; for (; valuelen; value++, valuelen--) { if (p >= line + DIM(line)-5 ) return gpg_error (GPG_ERR_TOO_LARGE); if (*value < ' ' || *value == '+' || *value == '%') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } *p = 0; rc = start_agent (NULL, 1); if (!rc) { parm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); } status_sc_op_failure (rc); return rc; } /* Handle a CERTDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END command. */ static gpg_error_t inq_writecert_parms (void *opaque, const char *line) { int rc; struct writecert_parm_s *parm = opaque; if (has_leading_keyword (line, "CERTDATA")) { rc = assuan_send_data (parm->dflt->ctx, parm->certdata, parm->certdatalen); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Send a WRITECERT command to the SCdaemon. */ int agent_scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen) { int rc; char line[ASSUAN_LINELENGTH]; struct writecert_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; memset (&parms, 0, sizeof parms); snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.certdata = certdata; parms.certdatalen = certdatalen; rc = assuan_transact (agent_ctx, line, NULL, NULL, inq_writecert_parms, &parms, NULL, NULL); return rc; } /* Handle a KEYDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_writekey_parms (void *opaque, const char *line) { int rc; struct writekey_parm_s *parm = opaque; if (has_leading_keyword (line, "KEYDATA")) { rc = assuan_send_data (parm->dflt->ctx, parm->keydata, parm->keydatalen); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Send a WRITEKEY command to the SCdaemon. */ int agent_scd_writekey (int keyno, const char *serialno, const unsigned char *keydata, size_t keydatalen) { int rc; char line[ASSUAN_LINELENGTH]; struct writekey_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); (void)serialno; rc = start_agent (NULL, 1); if (rc) return rc; memset (&parms, 0, sizeof parms); snprintf (line, DIM(line), "SCD WRITEKEY --force OPENPGP.%d", keyno); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.keydata = keydata; parms.keydatalen = keydatalen; rc = assuan_transact (agent_ctx, line, NULL, NULL, inq_writekey_parms, &parms, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Status callback for the SCD GENKEY command. */ static gpg_error_t scd_genkey_cb (void *opaque, const char *line) { u32 *createtime = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) { *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { write_status_text (STATUS_PROGRESS, line); } return 0; } /* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, the value will be passed to SCDAEMON with --timestamp option so that the key is created with this. Otherwise, timestamp was generated by SCDEAMON. On success, creation time is stored back to CREATETIME. */ int agent_scd_genkey (int keyno, int force, u32 *createtime) { int rc; char line[ASSUAN_LINELENGTH]; gnupg_isotime_t tbuf; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; if (*createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", keyno); dfltparm.ctx = agent_ctx; rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, scd_genkey_cb, createtime); status_sc_op_failure (rc); return rc; } /* Return the serial number of the card or an appropriate error. The serial number is returned as a hexstring. */ int agent_scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (NULL, 1); if (err) return err; if (!demand) strcpy (line, "SCD SERIALNO"); else snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (err) { xfree (serialno); return err; } *r_serialno = serialno; return 0; } /* Send a READCERT command to the SCdaemon. */ int agent_scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_buf = NULL; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; init_membuf (&data, 2048); snprintf (line, DIM(line), "SCD READCERT %s", certidstr); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return rc; } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error (GPG_ERR_ENOMEM); return 0; } struct card_cardlist_parm_s { int error; strlist_t list; }; /* Callback function for agent_card_cardlist. */ static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { const char *s; int n; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1) || *s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } return 0; } /* Return cardlist. */ int agent_scd_cardlist (strlist_t *result) { int err; char line[ASSUAN_LINELENGTH]; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (NULL, 1); if (err) return err; strcpy (line, "SCD GETINFO card_list"); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_strlist (parm.list); return 0; } /* Change the PIN of an OpenPGP card or reset the retry counter. CHVNO 1: Change the PIN 2: For v1 cards: Same as 1. For v2 cards: Reset the PIN using the Reset Code. 3: Change the admin PIN 101: Set a new PIN and reset the retry counter 102: For v1 cars: Same as 101. For v2 cards: Set a new Reset Code. SERIALNO is not used. */ int agent_scd_change_pin (int chvno, const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; const char *reset = ""; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); (void)serialno; if (chvno >= 100) reset = "--reset"; chvno %= 100; rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Perform a CHECKPIN operation. SERIALNO should be the serial number of the card - optionally followed by the fingerprint; however the fingerprint is ignored here. */ int agent_scd_checkpin (const char *serialno) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 1); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); status_sc_op_failure (rc); return rc; } /* Dummy function, only used by the gpg 1.4 implementation. */ void agent_clear_pin_cache (const char *sn) { (void)sn; } /* Note: All strings shall be UTF-8. On success the caller needs to free the string stored at R_PASSPHRASE. On error NULL will be stored at R_PASSPHRASE and an appropriate fpf error code returned. */ gpg_error_t agent_get_passphrase (const char *cache_id, const char *err_msg, const char *prompt, const char *desc_msg, int repeat, int check, char **r_passphrase) { int rc; char line[ASSUAN_LINELENGTH]; char *arg1 = NULL; char *arg2 = NULL; char *arg3 = NULL; char *arg4 = NULL; membuf_t data; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_passphrase = NULL; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; /* Check that the gpg-agent understands the repeat option. */ if (assuan_transact (agent_ctx, "GETINFO cmd_has_option GET_PASSPHRASE repeat", NULL, NULL, NULL, NULL, NULL, NULL)) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (cache_id && *cache_id) if (!(arg1 = percent_plus_escape (cache_id))) goto no_mem; if (err_msg && *err_msg) if (!(arg2 = percent_plus_escape (err_msg))) goto no_mem; if (prompt && *prompt) if (!(arg3 = percent_plus_escape (prompt))) goto no_mem; if (desc_msg && *desc_msg) if (!(arg4 = percent_plus_escape (desc_msg))) goto no_mem; snprintf (line, DIM(line), "GET_PASSPHRASE --data --repeat=%d%s -- %s %s %s %s", repeat, check? " --check --qualitybar":"", arg1? arg1:"X", arg2? arg2:"X", arg3? arg3:"X", arg4? arg4:"X"); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); init_membuf_secure (&data, 64); rc = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (rc) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); *r_passphrase = get_membuf (&data, NULL); if (!*r_passphrase) rc = gpg_error_from_syserror (); } return rc; no_mem: rc = gpg_error_from_syserror (); xfree (arg1); xfree (arg2); xfree (arg3); xfree (arg4); return rc; } gpg_error_t agent_clear_passphrase (const char *cache_id) { int rc; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); if (!cache_id || !*cache_id) return 0; rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id); return assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); } /* Ask the agent to pop up a confirmation dialog with the text DESC and an okay and cancel button. */ gpg_error_t gpg_agent_get_confirmation (const char *desc) { int rc; char *tmp; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); rc = start_agent (NULL, 0); if (rc) return rc; dfltparm.ctx = agent_ctx; tmp = percent_plus_escape (desc); if (!tmp) return gpg_error_from_syserror (); snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp); xfree (tmp); rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return rc; } /* Return the S2K iteration count as computed by gpg-agent. */ gpg_error_t agent_get_s2k_count (unsigned long *r_count) { gpg_error_t err; membuf_t data; char *buf; *r_count = 0; err = start_agent (NULL, 0); if (err) return err; init_membuf (&data, 32); err = assuan_transact (agent_ctx, "GETINFO s2k_count", put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) xfree (get_membuf (&data, NULL)); else { put_membuf (&data, "", 1); buf = get_membuf (&data, NULL); if (!buf) err = gpg_error_from_syserror (); else { *r_count = strtoul (buf, NULL, 10); xfree (buf); } } return err; } /* Ask the agent whether a secret key for the given public key is available. Returns 0 if available. */ gpg_error_t agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *hexgrip; err = start_agent (ctrl, 0); if (err) return err; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) return err; snprintf (line, sizeof line, "HAVEKEY %s", hexgrip); xfree (hexgrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } /* Ask the agent whether a secret key is available for any of the keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *p; kbnode_t kbctx, node; int nkeys; unsigned char grip[20]; err = start_agent (ctrl, 0); if (err) return err; err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was found in KEYBLOCK. */ p = stpcpy (line, "HAVEKEY"); for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); ) if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2)) { err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err != gpg_err_code (GPG_ERR_NO_SECKEY)) break; /* Seckey available or unexpected error - ready. */ p = stpcpy (line, "HAVEKEY"); nkeys = 0; } err = keygrip_from_pk (node->pkt->pkt.public_key, grip); if (err) return err; *p++ = ' '; bin2hex (grip, 20, p); p += 40; nkeys++; } if (!err && nkeys) err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } struct keyinfo_data_parm_s { char *serialno; int cleartext; }; static gpg_error_t keyinfo_status_cb (void *opaque, const char *line) { struct keyinfo_data_parm_s *data = opaque; int is_smartcard; char *s; if ((s = has_leading_keyword (line, "KEYINFO")) && data) { /* Parse the arguments: * 0 1 2 3 4 5 * <keygrip> <type> <serialno> <idstr> <cached> <protection> */ char *fields[6]; if (split_fields (s, fields, DIM (fields)) == 6) { is_smartcard = (fields[1][0] == 'T'); if (is_smartcard && !data->serialno && strcmp (fields[2], "-")) data->serialno = xtrystrdup (fields[2]); /* 'P' for protected, 'C' for clear */ data->cleartext = (fields[5][0] == 'C'); } } return 0; } /* Return the serial number for a secret key. If the returned serial number is NULL, the key is not stored on a smartcard. Caller needs to free R_SERIALNO. if r_cleartext is not NULL, the referenced int will be set to 1 if the agent's copy of the key is stored in the clear, or 0 otherwise */ gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno, int *r_cleartext) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0,sizeof keyinfo); *r_serialno = NULL; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); if (!err && keyinfo.serialno) { /* Sanity check for bad characters. */ if (strpbrk (keyinfo.serialno, ":\n\r")) err = GPG_ERR_INV_VALUE; } if (err) xfree (keyinfo.serialno); else { *r_serialno = keyinfo.serialno; if (r_cleartext) *r_cleartext = keyinfo.cleartext; } return err; } /* Status callback for agent_import_key, agent_export_key and agent_genkey. */ static gpg_error_t cache_nonce_status_cb (void *opaque, const char *line) { struct cache_nonce_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "CACHE_NONCE"))) { if (parm->cache_nonce_addr) { xfree (*parm->cache_nonce_addr); *parm->cache_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PASSWD_NONCE"))) { if (parm->passwd_nonce_addr) { xfree (*parm->passwd_nonce_addr); *parm->passwd_nonce_addr = xtrystrdup (s); } } else if ((s = has_leading_keyword (line, "PROGRESS"))) { if (opt.enable_progress_filter) write_status_text (STATUS_PROGRESS, s); } return 0; } /* Handle a KEYPARMS inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_genkey_parms (void *opaque, const char *line) { struct genkey_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYPARAM")) { err = assuan_send_data (parm->dflt->ctx, parm->keyparms, strlen (parm->keyparms)); } else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase) { err = assuan_send_data (parm->dflt->ctx, parm->passphrase, strlen (parm->passphrase)); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to generate a new key. KEYPARMS is the usual S-expression giving the parameters of the key. gpg-agent passes it gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not to protect the generated key. If NO_PROTECTION is not set and PASSPHRASE is not NULL the agent is requested to protect the key with that passphrase instead of asking for one. */ gpg_error_t agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr, const char *keyparms, int no_protection, const char *passphrase, gcry_sexp_t *r_pubkey) { gpg_error_t err; struct genkey_parm_s gk_parm; struct cache_nonce_parm_s cn_parm; struct default_inq_parm_s dfltparm; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (passwd_nonce_addr && *passwd_nonce_addr) ; /* A RESET would flush the passwd nonce cache. */ else { err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf (&data, 1024); gk_parm.dflt = &dfltparm; gk_parm.keyparms = keyparms; gk_parm.passphrase = passphrase; snprintf (line, sizeof line, "GENKEY%s%s%s%s%s", no_protection? " --no-protection" : passphrase ? " --inq-passwd" : /* */ "", passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, inq_genkey_parms, &gk_parm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_pubkey, NULL, buf, len); xfree (buf); } return err; } /* Call the agent to read the public key part for a given keygrip. If FROMCARD is true, the key is directly read from the current smartcard. In this case HEXKEYGRIP should be the keyID (e.g. OPENPGP.3). */ gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, unsigned char **r_pubkey) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_pubkey = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"", hexkeygrip); init_membuf (&data, 1024); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } *r_pubkey = buf; return 0; } /* Call the agent to do a sign operation using the key identified by the hex string KEYGRIP. DESC is a description of the key to be displayed if the agent needs to ask for the PIN. DIGEST and DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id used to compute the digest. If CACHE_NONCE is used the agent is advised to first try a passphrase associated with that nonce. */ gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, unsigned char *digest, size_t digestlen, int digestalgo, gcry_sexp_t *r_sigval) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *r_sigval = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (digestlen*2 + 50 > DIM(line)) return gpg_error (GPG_ERR_GENERAL); err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, DIM(line), "SIGKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, sizeof line, "SETHASH %d ", digestalgo); bin2hex (digest, digestlen, line + strlen (line)); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; init_membuf (&data, 1024); snprintf (line, sizeof line, "PKSIGN%s%s", cache_nonce? " -- ":"", cache_nonce? cache_nonce:""); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) xfree (get_membuf (&data, NULL)); else { unsigned char *buf; size_t len; buf = get_membuf (&data, &len); if (!buf) err = gpg_error_from_syserror (); else { err = gcry_sexp_sscan (r_sigval, NULL, buf, len); xfree (buf); } } return err; } /* Handle a CIPHERTEXT inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the END. */ static gpg_error_t inq_ciphertext_cb (void *opaque, const char *line) { struct cipher_parm_s *parm = opaque; int rc; if (has_leading_keyword (line, "CIPHERTEXT")) { assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->dflt->ctx, parm->ciphertext, parm->ciphertextlen); assuan_end_confidential (parm->ctx); } else rc = default_inq_cb (parm->dflt, line); return rc; } /* Check whether there is any padding info from the agent. */ static gpg_error_t padding_info_cb (void *opaque, const char *line) { int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } return 0; } /* Call the agent to do a decrypt operation using the key identified by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the success the decoded value is stored verbatim at R_BUF and its length at R_BUF; the callers needs to release it. KEYID, MAINKEYID and PUBKEY_ALGO are used to construct additional promots or status messages. The padding information is stored at R_PADDING with -1 for not known. */ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, u32 *keyid, u32 *mainkeyid, int pubkey_algo, gcry_sexp_t s_ciphertext, unsigned char **r_buf, size_t *r_buflen, int *r_padding) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t n, len; char *p, *buf, *endp; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; if (!keygrip || strlen(keygrip) != 40 || !s_ciphertext || !r_buf || !r_buflen || !r_padding) return gpg_error (GPG_ERR_INV_VALUE); *r_buf = NULL; *r_padding = -1; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; snprintf (line, sizeof line, "SETKEY %s", keygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } init_membuf_secure (&data, 1024); { struct cipher_parm_s parm; parm.dflt = &dfltparm; parm.ctx = agent_ctx; err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen); if (err) return err; err = assuan_transact (agent_ctx, "PKDECRYPT", put_membuf_cb, &data, inq_ciphertext_cb, &parm, padding_info_cb, r_padding); xfree (parm.ciphertext); } if (err) { xfree (get_membuf (&data, &len)); return err; } put_membuf (&data, "", 1); /* Make sure it is 0 terminated. */ buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); log_assert (len); /* (we forced Nul termination.) */ if (*buf != '(') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */ { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } len -= 10; /* Count only the data of the second part. */ p = buf + 8; /* Skip leading parenthesis and the value tag. */ n = strtoul (p, &endp, 10); if (!n || *endp != ':') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } endp++; if (endp-p+n > len) { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ } memmove (buf, endp, n); *r_buflen = n; *r_buf = buf; return 0; } /* Retrieve a key encryption key from the agent. With FOREXPORT true the key shall be used for export, with false for import. On success the new key is stored at R_KEY and its length at R_KEKLEN. */ gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen) { gpg_error_t err; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_kek = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, DIM(line), "KEYWRAP_KEY %s", forexport? "--export":"--import"); init_membuf_secure (&data, 64); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_kek = buf; *r_keklen = len; return 0; } /* Handle the inquiry for an IMPORT_KEY command. */ static gpg_error_t inq_import_key_parms (void *opaque, const char *line) { struct import_key_parm_s *parm = opaque; gpg_error_t err; if (has_leading_keyword (line, "KEYDATA")) { err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen); } else err = default_inq_cb (parm->dflt, line); return err; } /* Call the agent to import a key into the agent. */ gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, const void *key, size_t keylen, int unattended, int force) { gpg_error_t err; struct import_key_parm_s parm; struct cache_nonce_parm_s cn_parm; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } parm.dflt = &dfltparm; parm.key = key; parm.keylen = keylen; snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s", unattended? " --unattended":"", force? " --force":"", cache_nonce_addr && *cache_nonce_addr? " ":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, NULL, NULL, inq_import_key_parms, &parm, cache_nonce_status_cb, &cn_parm); return err; } /* Receive a secret key from the agent. HEXKEYGRIP is the hexified keygrip, DESC a prompt to be displayed with the agent's passphrase question (needs to be plus+percent escaped). if OPENPGP_PROTECTED is not zero, ensure that the key material is returned in RFC 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is not NULL the agent is advised to first try a passphrase associated with that nonce. On success the key is stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */ gpg_error_t agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int openpgp_protected, char **cache_nonce_addr, unsigned char **r_result, size_t *r_resultlen) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; membuf_t data; size_t len; unsigned char *buf; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; *r_result = NULL; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", openpgp_protected ? "--openpgp ":"", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); init_membuf_secure (&data, 1024); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = NULL; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); *r_result = buf; *r_resultlen = len; return 0; } /* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC is not NULL, display DESC instead of the default description message. If FORCE is true the agent is advised not to ask for confirmation. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int force) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } snprintf (line, DIM(line), "DELETE_KEY%s %s", force? " --force":"", hexkeygrip); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return err; } /* Ask the agent to change the passphrase of the key identified by * HEXKEYGRIP. If DESC is not NULL, display DESC instead of the * default description message. If CACHE_NONCE_ADDR is not NULL the * agent is advised to first try a passphrase associated with that * nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use * the passphrase associated with that nonce for the new passphrase. * If VERIFY is true the passphrase is only verified. */ gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify, char **cache_nonce_addr, char **passwd_nonce_addr) { gpg_error_t err; struct cache_nonce_parm_s cn_parm; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (!hexkeygrip || strlen (hexkeygrip) != 40) return gpg_error (GPG_ERR_INV_VALUE); if (desc) { snprintf (line, DIM(line), "SETKEYDESC %s", desc); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) return err; } if (verify) snprintf (line, DIM(line), "PASSWD %s%s --verify %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", hexkeygrip); else snprintf (line, DIM(line), "PASSWD %s%s %s%s %s", cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"", passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", hexkeygrip); cn_parm.cache_nonce_addr = cache_nonce_addr; cn_parm.passwd_nonce_addr = passwd_nonce_addr; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, cache_nonce_status_cb, &cn_parm); return err; } /* Return the version reported by gpg-agent. */ gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version) { gpg_error_t err; err = start_agent (ctrl, 0); if (err) return err; err = get_assuan_server_version (agent_ctx, 0, r_version); return err; } diff --git a/g10/getkey.c b/g10/getkey.c index 75b8564f3..d8c81c937 100644 --- a/g10/getkey.c +++ b/g10/getkey.c @@ -1,4286 +1,4286 @@ /* getkey.c - Get a key from the database * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2007, 2008, 2010 Free Software Foundation, Inc. * Copyright (C) 2015, 2016 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include "gpg.h" #include "../common/util.h" #include "packet.h" #include "../common/iobuf.h" #include "keydb.h" #include "options.h" #include "main.h" #include "trustdb.h" #include "../common/i18n.h" #include "keyserver-internal.h" #include "call-agent.h" #include "../common/host2net.h" #include "../common/mbox-util.h" #include "../common/status.h" #define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE #define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE #if MAX_PK_CACHE_ENTRIES < 2 #error We need the cache for key creation #endif /* Flags values returned by the lookup code. Note that the values are * directly used by the KEY_CONSIDERED status line. */ #define LOOKUP_NOT_SELECTED (1<<0) #define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */ /* A context object used by the lookup functions. */ struct getkey_ctx_s { /* Part of the search criteria: whether the search is an exact search or not. A search that is exact requires that a key or subkey meet all of the specified criteria. A search that is not exact allows selecting a different key or subkey from the keyblock that matched the critera. Further, an exact search returns the key or subkey that matched whereas a non-exact search typically returns the primary key. See finish_lookup for details. */ int exact; /* Part of the search criteria: Whether the caller only wants keys with an available secret key. This is used by getkey_next to get the next result with the same initial criteria. */ int want_secret; /* Part of the search criteria: The type of the requested key. A mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If non-zero, then for a key to match, it must implement one of the required uses. */ int req_usage; /* The database handle. */ KEYDB_HANDLE kr_handle; /* Whether we should call xfree() on the context when the context is released using getkey_end()). */ int not_allocated; /* This variable is used as backing store for strings which have their address used in ITEMS. */ strlist_t extra_list; /* Part of the search criteria: The low-level search specification as passed to keydb_search. */ int nitems; /* This must be the last element in the structure. When we allocate the structure, we allocate it so that ITEMS can hold NITEMS. */ KEYDB_SEARCH_DESC items[1]; }; #if 0 static struct { int any; int okay_count; int nokey_count; int error_count; } lkup_stats[21]; #endif typedef struct keyid_list { struct keyid_list *next; char fpr[MAX_FINGERPRINT_LEN]; u32 keyid[2]; } *keyid_list_t; #if MAX_PK_CACHE_ENTRIES typedef struct pk_cache_entry { struct pk_cache_entry *next; u32 keyid[2]; PKT_public_key *pk; } *pk_cache_entry_t; static pk_cache_entry_t pk_cache; static int pk_cache_entries; /* Number of entries in pk cache. */ static int pk_cache_disabled; #endif #if MAX_UID_CACHE_ENTRIES < 5 #error we really need the userid cache #endif typedef struct user_id_db { struct user_id_db *next; keyid_list_t keyids; int len; char name[1]; } *user_id_db_t; static user_id_db_t user_id_db; static int uid_cache_entries; /* Number of entries in uid cache. */ static void merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock); static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret, kbnode_t *ret_keyblock, kbnode_t *ret_found_key); static kbnode_t finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, unsigned int *r_flags); static void print_status_key_considered (kbnode_t keyblock, unsigned int flags); #if 0 static void print_stats () { int i; for (i = 0; i < DIM (lkup_stats); i++) { if (lkup_stats[i].any) es_fprintf (es_stderr, "lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n", i, lkup_stats[i].okay_count, lkup_stats[i].nokey_count, lkup_stats[i].error_count); } } #endif /* Cache a copy of a public key in the public key cache. PK is not * cached if caching is disabled (via getkey_disable_caches), if * PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id * from the public key (e.g., unsupported algorithm), or a key with * the key id is already in the cache. * * The public key packet is copied into the cache using * copy_public_key. Thus, any secret parts are not copied, for * instance. * * This cache is filled by get_pubkey and is read by get_pubkey and * get_pubkey_fast. */ void cache_public_key (PKT_public_key * pk) { #if MAX_PK_CACHE_ENTRIES pk_cache_entry_t ce, ce2; u32 keyid[2]; if (pk_cache_disabled) return; if (pk->flags.dont_cache) return; if (is_ELGAMAL (pk->pubkey_algo) || pk->pubkey_algo == PUBKEY_ALGO_DSA || pk->pubkey_algo == PUBKEY_ALGO_ECDSA || pk->pubkey_algo == PUBKEY_ALGO_EDDSA || pk->pubkey_algo == PUBKEY_ALGO_ECDH || is_RSA (pk->pubkey_algo)) { keyid_from_pk (pk, keyid); } else return; /* Don't know how to get the keyid. */ for (ce = pk_cache; ce; ce = ce->next) if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]) { if (DBG_CACHE) log_debug ("cache_public_key: already in cache\n"); return; } if (pk_cache_entries >= MAX_PK_CACHE_ENTRIES) { int n; /* Remove the last 50% of the entries. */ for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++) ce = ce->next; if (ce && ce != pk_cache && ce->next) { ce2 = ce->next; ce->next = NULL; ce = ce2; for (; ce; ce = ce2) { ce2 = ce->next; free_public_key (ce->pk); xfree (ce); pk_cache_entries--; } } log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES); } pk_cache_entries++; ce = xmalloc (sizeof *ce); ce->next = pk_cache; pk_cache = ce; ce->pk = copy_public_key (NULL, pk); ce->keyid[0] = keyid[0]; ce->keyid[1] = keyid[1]; #endif } /* Return a const utf-8 string with the text "[User ID not found]". This function is required so that we don't need to switch gettext's encoding temporary. */ static const char * user_id_not_found_utf8 (void) { static char *text; if (!text) text = native_to_utf8 (_("[User ID not found]")); return text; } /* Return the user ID from the given keyblock. * We use the primary uid flag which has been set by the merge_selfsigs * function. The returned value is only valid as long as the given * keyblock is not changed. */ static const char * get_primary_uid (KBNODE keyblock, size_t * uidlen) { KBNODE k; const char *s; for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data && k->pkt->pkt.user_id->flags.primary) { *uidlen = k->pkt->pkt.user_id->len; return k->pkt->pkt.user_id->name; } } s = user_id_not_found_utf8 (); *uidlen = strlen (s); return s; } static void release_keyid_list (keyid_list_t k) { while (k) { keyid_list_t k2 = k->next; xfree (k); k = k2; } } /**************** * Store the association of keyid and userid * Feed only public keys to this function. */ static void cache_user_id (KBNODE keyblock) { user_id_db_t r; const char *uid; size_t uidlen; keyid_list_t keyids = NULL; KBNODE k; for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_PUBLIC_KEY || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) { keyid_list_t a = xmalloc_clear (sizeof *a); /* Hmmm: For a long list of keyids it might be an advantage * to append the keys. */ fingerprint_from_pk (k->pkt->pkt.public_key, a->fpr, NULL); keyid_from_pk (k->pkt->pkt.public_key, a->keyid); /* First check for duplicates. */ for (r = user_id_db; r; r = r->next) { keyid_list_t b; for (b = r->keyids; b; b = b->next) { if (!memcmp (b->fpr, a->fpr, MAX_FINGERPRINT_LEN)) { if (DBG_CACHE) log_debug ("cache_user_id: already in cache\n"); release_keyid_list (keyids); xfree (a); return; } } } /* Now put it into the cache. */ a->next = keyids; keyids = a; } } if (!keyids) BUG (); /* No key no fun. */ uid = get_primary_uid (keyblock, &uidlen); if (uid_cache_entries >= MAX_UID_CACHE_ENTRIES) { /* fixme: use another algorithm to free some cache slots */ r = user_id_db; user_id_db = r->next; release_keyid_list (r->keyids); xfree (r); uid_cache_entries--; } r = xmalloc (sizeof *r + uidlen - 1); r->keyids = keyids; r->len = uidlen; memcpy (r->name, uid, r->len); r->next = user_id_db; user_id_db = r; uid_cache_entries++; } /* Disable and drop the public key cache (which is filled by cache_public_key and get_pubkey). Note: there is currently no way - to reenable this cache. */ + to re-enable this cache. */ void getkey_disable_caches () { #if MAX_PK_CACHE_ENTRIES { pk_cache_entry_t ce, ce2; for (ce = pk_cache; ce; ce = ce2) { ce2 = ce->next; free_public_key (ce->pk); xfree (ce); } pk_cache_disabled = 1; pk_cache_entries = 0; pk_cache = NULL; } #endif /* fixme: disable user id cache ? */ } void pubkey_free (pubkey_t key) { if (key) { xfree (key->pk); release_kbnode (key->keyblock); xfree (key); } } void pubkeys_free (pubkey_t keys) { while (keys) { pubkey_t next = keys->next; pubkey_free (keys); keys = next; } } -/* Returns all keys that match the search specfication SEARCH_TERMS. +/* Returns all keys that match the search specification SEARCH_TERMS. This function also checks for and warns about duplicate entries in the keydb, which can occur if the user has configured multiple keyrings or keyboxes or if a keyring or keybox was corrupted. Note: SEARCH_TERMS will not be expanded (i.e., it may not be a group). USE is the operation for which the key is required. It must be either PUBKEY_USAGE_ENC, PUBKEY_USAGE_SIG, PUBKEY_USAGE_CERT or PUBKEY_USAGE_AUTH. XXX: Currently, only PUBKEY_USAGE_ENC and PUBKEY_USAGE_SIG are implemented. INCLUDE_UNUSABLE indicates whether disabled keys are allowed. (Recipients specified with --encrypt-to and --hidden-encrypt-to may be disabled. It is possible to edit disabled keys.) SOURCE is the context in which SEARCH_TERMS was specified, e.g., "--encrypt-to", etc. If this function is called interactively, then this should be NULL. If WARN_POSSIBLY_AMBIGUOUS is set, then emits a warning if the user does not specify a long key id or a fingerprint. The results are placed in *KEYS. *KEYS must be NULL! */ gpg_error_t get_pubkeys (ctrl_t ctrl, char *search_terms, int use, int include_unusable, char *source, int warn_possibly_ambiguous, pubkey_t *r_keys) { /* We show a warning when a key appears multiple times in the DB. This can happen for two reasons: - The user has configured multiple keyrings or keyboxes. - The keyring or keybox has been corrupted in some way, e.g., a bug or a random process changing them. For each duplicate, we only want to show the key once. Hence, this list. */ static strlist_t key_dups; /* USE transformed to a string. */ char *use_str; gpg_error_t err; KEYDB_SEARCH_DESC desc; GETKEY_CTX ctx; pubkey_t results = NULL; pubkey_t r; int count; char fingerprint[2 * MAX_FINGERPRINT_LEN + 1]; if (DBG_LOOKUP) { log_debug ("\n"); log_debug ("%s: Checking %s=%s\n", __func__, source ? source : "user input", search_terms); } if (*r_keys) log_bug ("%s: KEYS should be NULL!\n", __func__); switch (use) { case PUBKEY_USAGE_ENC: use_str = "encrypt"; break; case PUBKEY_USAGE_SIG: use_str = "sign"; break; case PUBKEY_USAGE_CERT: use_str = "cetify"; break; case PUBKEY_USAGE_AUTH: use_str = "authentication"; break; default: log_bug ("%s: Bad value for USE (%d)\n", __func__, use); } if (use == PUBKEY_USAGE_CERT || use == PUBKEY_USAGE_AUTH) log_bug ("%s: use=%s is unimplemented.\n", __func__, use_str); err = classify_user_id (search_terms, &desc, 1); if (err) { log_info (_("key \"%s\" not found: %s\n"), search_terms, gpg_strerror (err)); if (!opt.quiet && source) log_info (_("(check argument of option '%s')\n"), source); goto out; } if (warn_possibly_ambiguous && ! (desc.mode == KEYDB_SEARCH_MODE_LONG_KID || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_FPR20 || desc.mode == KEYDB_SEARCH_MODE_FPR)) { log_info (_("Warning: '%s' should be a long key ID or a fingerprint\n"), search_terms); if (!opt.quiet && source) log_info (_("(check argument of option '%s')\n"), source); } /* Gather all of the results. */ ctx = NULL; count = 0; do { PKT_public_key *pk = xmalloc_clear (sizeof *pk); KBNODE kb; pk->req_usage = use; if (! ctx) err = get_pubkey_byname (ctrl, &ctx, pk, search_terms, &kb, NULL, include_unusable, 1); else err = getkey_next (ctrl, ctx, pk, &kb); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) /* No more results. */ { xfree (pk); break; } else if (err) /* An error (other than "not found"). */ { log_error (_("error looking up: %s\n"), gpg_strerror (err)); xfree (pk); break; } /* Another result! */ count ++; r = xmalloc_clear (sizeof (*r)); r->pk = pk; r->keyblock = kb; r->next = results; results = r; } while (ctx); getkey_end (ctrl, ctx); if (DBG_LOOKUP) { log_debug ("%s resulted in %d matches.\n", search_terms, count); for (r = results; r; r = r->next) log_debug (" %s\n", hexfingerprint (r->keyblock->pkt->pkt.public_key, fingerprint, sizeof (fingerprint))); } if (! results && gpg_err_code (err) == GPG_ERR_NOT_FOUND) /* No match. */ { if (DBG_LOOKUP) log_debug ("%s: '%s' not found.\n", __func__, search_terms); log_info (_("key \"%s\" not found\n"), search_terms); if (!opt.quiet && source) log_info (_("(check argument of option '%s')\n"), source); goto out; } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) /* No more matches. */ ; else if (err) /* Some other error. An error message was already printed out. Free RESULTS and continue. */ goto out; /* Check for duplicates. */ if (DBG_LOOKUP) log_debug ("%s: Checking results of %s='%s' for dups\n", __func__, source ? source : "user input", search_terms); count = 0; for (r = results; r; r = r->next) { pubkey_t *prevp; pubkey_t next; pubkey_t r2; int dups = 0; prevp = &r->next; next = r->next; while ((r2 = next)) { if (cmp_public_keys (r->keyblock->pkt->pkt.public_key, r2->keyblock->pkt->pkt.public_key) != 0) /* Not a dup. */ { prevp = &r2->next; next = r2->next; continue; } dups ++; count ++; /* Remove R2 from the list. */ *prevp = r2->next; release_kbnode (r2->keyblock); next = r2->next; xfree (r2); } if (dups) { hexfingerprint (r->keyblock->pkt->pkt.public_key, fingerprint, sizeof fingerprint); if (! strlist_find (key_dups, fingerprint)) { char fingerprint_formatted[MAX_FORMATTED_FINGERPRINT_LEN + 1]; log_info (_("Warning: %s appears in the keyring %d times\n"), format_hexfingerprint (fingerprint, fingerprint_formatted, sizeof fingerprint_formatted), 1 + dups); add_to_strlist (&key_dups, fingerprint); } } } if (DBG_LOOKUP && count) { log_debug ("After removing %d dups:\n", count); for (r = results, count = 0; r; r = r->next) log_debug (" %d: %s\n", count, hexfingerprint (r->keyblock->pkt->pkt.public_key, fingerprint, sizeof fingerprint)); } out: if (err) pubkeys_free (results); else *r_keys = results; return err; } static void pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key) { kbnode_t a = found_key ? found_key : keyblock; log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY || a->pkt->pkttype == PKT_PUBLIC_SUBKEY); copy_public_key (pk, a->pkt->pkt.public_key); } /* Return the public key with the key id KEYID and store it at PK. * The resources in *PK should be released using * release_public_key_parts(). This function also stores a copy of * the public key in the user id cache (see cache_public_key). * * If PK is NULL, this function just stores the public key in the * cache and returns the usual return code. * * PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG, * PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the * lookup function. If this is non-zero, only keys with the specified * usage will be returned. As such, it is essential that * PK->REQ_USAGE be correctly initialized! * * Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key * with the specified key id, or another error code if an error * occurs. * * If the data was not read from the cache, then the self-signed data * has definitely been merged into the public key using * merge_selfsigs. */ int get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid) { int internal = 0; int rc = 0; #if MAX_PK_CACHE_ENTRIES if (pk) { /* Try to get it from the cache. We don't do this when pk is NULL as it does not guarantee that the user IDs are cached. */ pk_cache_entry_t ce; for (ce = pk_cache; ce; ce = ce->next) { if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]) /* XXX: We don't check PK->REQ_USAGE here, but if we don't read from the cache, we do check it! */ { copy_public_key (pk, ce->pk); return 0; } } } #endif /* More init stuff. */ if (!pk) { pk = xmalloc_clear (sizeof *pk); internal++; } /* Do a lookup. */ { struct getkey_ctx_s ctx; KBNODE kb = NULL; KBNODE found_key = NULL; memset (&ctx, 0, sizeof ctx); ctx.exact = 1; /* Use the key ID exactly as given. */ ctx.not_allocated = 1; if (ctrl && ctrl->cached_getkey_kdb) { ctx.kr_handle = ctrl->cached_getkey_kdb; ctrl->cached_getkey_kdb = NULL; keydb_search_reset (ctx.kr_handle); } else { ctx.kr_handle = keydb_new (); if (!ctx.kr_handle) { rc = gpg_error_from_syserror (); goto leave; } } ctx.nitems = 1; ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; ctx.items[0].u.kid[0] = keyid[0]; ctx.items[0].u.kid[1] = keyid[1]; ctx.req_usage = pk->req_usage; rc = lookup (ctrl, &ctx, 0, &kb, &found_key); if (!rc) { pk_from_block (pk, kb, found_key); } getkey_end (ctrl, &ctx); release_kbnode (kb); } if (!rc) goto leave; rc = GPG_ERR_NO_PUBKEY; leave: if (!rc) cache_public_key (pk); if (internal) free_public_key (pk); return rc; } /* Similar to get_pubkey, but it does not take PK->REQ_USAGE into * account nor does it merge in the self-signed data. This function * also only considers primary keys. It is intended to be used as a * quick check of the key to avoid recursion. It should only be used * in very certain cases. Like get_pubkey and unlike any of the other * lookup functions, this function also consults the user id cache * (see cache_public_key). * * Return the public key in *PK. The resources in *PK should be * released using release_public_key_parts(). */ int get_pubkey_fast (PKT_public_key * pk, u32 * keyid) { int rc = 0; KEYDB_HANDLE hd; KBNODE keyblock; u32 pkid[2]; log_assert (pk); #if MAX_PK_CACHE_ENTRIES { /* Try to get it from the cache */ pk_cache_entry_t ce; for (ce = pk_cache; ce; ce = ce->next) { if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1] /* Only consider primary keys. */ && ce->pk->keyid[0] == ce->pk->main_keyid[0] && ce->pk->keyid[1] == ce->pk->main_keyid[1]) { if (pk) copy_public_key (pk, ce->pk); return 0; } } } #endif hd = keydb_new (); if (!hd) return gpg_error_from_syserror (); rc = keydb_search_kid (hd, keyid); if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { keydb_release (hd); return GPG_ERR_NO_PUBKEY; } rc = keydb_get_keyblock (hd, &keyblock); keydb_release (hd); if (rc) { log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); return GPG_ERR_NO_PUBKEY; } log_assert (keyblock && keyblock->pkt && keyblock->pkt->pkttype == PKT_PUBLIC_KEY); /* We return the primary key. If KEYID matched a subkey, then we return an error. */ keyid_from_pk (keyblock->pkt->pkt.public_key, pkid); if (keyid[0] == pkid[0] && keyid[1] == pkid[1]) copy_public_key (pk, keyblock->pkt->pkt.public_key); else rc = GPG_ERR_NO_PUBKEY; release_kbnode (keyblock); /* Not caching key here since it won't have all of the fields properly set. */ return rc; } /* Return the key block for the key with key id KEYID or NULL, if an * error occurs. Use release_kbnode() to release the key block. * * The self-signed data has already been merged into the public key * using merge_selfsigs. */ kbnode_t get_pubkeyblock (ctrl_t ctrl, u32 * keyid) { struct getkey_ctx_s ctx; int rc = 0; KBNODE keyblock = NULL; memset (&ctx, 0, sizeof ctx); /* No need to set exact here because we want the entire block. */ ctx.not_allocated = 1; ctx.kr_handle = keydb_new (); if (!ctx.kr_handle) return NULL; ctx.nitems = 1; ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; ctx.items[0].u.kid[0] = keyid[0]; ctx.items[0].u.kid[1] = keyid[1]; rc = lookup (ctrl, &ctx, 0, &keyblock, NULL); getkey_end (ctrl, &ctx); return rc ? NULL : keyblock; } /* Return the public key with the key id KEYID iff the secret key is * available and store it at PK. The resources should be released * using release_public_key_parts(). * * Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE * is passed through to the lookup function and is a mask of * PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it * must be valid! If this is non-zero, only keys with the specified * usage will be returned. * * Returns 0 on success. If a public key with the specified key id is * not found or a secret key is not available for that public key, an * error code is returned. Note: this function ignores legacy keys. * An error code is also return if an error occurs. * * The self-signed data has already been merged into the public key * using merge_selfsigs. */ gpg_error_t get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid) { gpg_error_t err; struct getkey_ctx_s ctx; kbnode_t keyblock = NULL; kbnode_t found_key = NULL; memset (&ctx, 0, sizeof ctx); ctx.exact = 1; /* Use the key ID exactly as given. */ ctx.not_allocated = 1; ctx.kr_handle = keydb_new (); if (!ctx.kr_handle) return gpg_error_from_syserror (); ctx.nitems = 1; ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; ctx.items[0].u.kid[0] = keyid[0]; ctx.items[0].u.kid[1] = keyid[1]; ctx.req_usage = pk->req_usage; err = lookup (ctrl, &ctx, 1, &keyblock, &found_key); if (!err) { pk_from_block (pk, keyblock, found_key); } getkey_end (ctrl, &ctx); release_kbnode (keyblock); if (!err) { err = agent_probe_secret_key (/*ctrl*/NULL, pk); if (err) release_public_key_parts (pk); } return err; } /* Skip unusable keys. A key is unusable if it is revoked, expired or disabled or if the selected user id is revoked or expired. */ static int skip_unusable (void *opaque, u32 * keyid, int uid_no) { ctrl_t ctrl = opaque; int unusable = 0; KBNODE keyblock; PKT_public_key *pk; keyblock = get_pubkeyblock (ctrl, keyid); if (!keyblock) { log_error ("error checking usability status of %s\n", keystr (keyid)); goto leave; } pk = keyblock->pkt->pkt.public_key; /* Is the key revoked or expired? */ if (pk->flags.revoked || pk->has_expired) unusable = 1; /* Is the user ID in question revoked or expired? */ if (!unusable && uid_no) { KBNODE node; int uids_seen = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *user_id = node->pkt->pkt.user_id; uids_seen ++; if (uids_seen != uid_no) continue; if (user_id->flags.revoked || user_id->flags.expired) unusable = 1; break; } } /* If UID_NO is non-zero, then the keyblock better have at least that many UIDs. */ log_assert (uids_seen == uid_no); } if (!unusable) unusable = pk_is_disabled (pk); leave: release_kbnode (keyblock); return unusable; } /* Search for keys matching some criteria. If RETCTX is not NULL, then the constructed context is returned in *RETCTX so that getpubkey_next can be used to get subsequent results. In this case, getkey_end() must be used to free the search context. If RETCTX is not NULL, then RET_KDBHD must be NULL. If NAMELIST is not NULL, then a search query is constructed using classify_user_id on each of the strings in the list. (Recall: the database does an OR of the terms, not an AND.) If NAMELIST is NULL, then all results are returned. If PK is not NULL, the public key of the first result is returned in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is set, it is used to filter the search results. See the documentation for finish_lookup to understand exactly how this is used. Note: The self-signed data has already been merged into the public key using merge_selfsigs. Free *PK by calling release_public_key_parts (or, if PK was allocated using xfree, you can use free_public_key, which calls release_public_key_parts(PK) and then xfree(PK)). If WANT_SECRET is set, then only keys with an available secret key (either locally or via key registered on a smartcard) are returned. If INCLUDE_UNUSABLE is set, then unusable keys (see the documentation for skip_unusable for an exact definition) are skipped unless they are looked up by key id or by fingerprint. If RET_KB is not NULL, the keyblock is returned in *RET_KB. This should be freed using release_kbnode(). If RET_KDBHD is not NULL, then the new database handle used to conduct the search is returned in *RET_KDBHD. This can be used to get subsequent results using keydb_search_next. Note: in this case, no advanced filtering is done for subsequent results (e.g., WANT_SECRET and PK->REQ_USAGE are not respected). This function returns 0 on success. Otherwise, an error code is returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY (if want_secret is set) is returned if the key is not found. */ static int key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist, PKT_public_key *pk, int want_secret, int include_unusable, KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd) { int rc = 0; int n; strlist_t r; GETKEY_CTX ctx; KBNODE help_kb = NULL; KBNODE found_key = NULL; if (retctx) { /* Reset the returned context in case of error. */ log_assert (!ret_kdbhd); /* Not allowed because the handle is stored in the context. */ *retctx = NULL; } if (ret_kdbhd) *ret_kdbhd = NULL; if (!namelist) /* No search terms: iterate over the whole DB. */ { ctx = xmalloc_clear (sizeof *ctx); ctx->nitems = 1; ctx->items[0].mode = KEYDB_SEARCH_MODE_FIRST; if (!include_unusable) { ctx->items[0].skipfnc = skip_unusable; ctx->items[0].skipfncvalue = ctrl; } } else { /* Build the search context. */ for (n = 0, r = namelist; r; r = r->next) n++; /* CTX has space for a single search term at the end. Thus, we need to allocate sizeof *CTX plus (n - 1) sizeof CTX->ITEMS. */ ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items); ctx->nitems = n; for (n = 0, r = namelist; r; r = r->next, n++) { gpg_error_t err; err = classify_user_id (r->d, &ctx->items[n], 1); if (ctx->items[n].exact) ctx->exact = 1; if (err) { xfree (ctx); return gpg_err_code (err); /* FIXME: remove gpg_err_code. */ } if (!include_unusable && ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID && ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR16 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR20 && ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR) { ctx->items[n].skipfnc = skip_unusable; ctx->items[n].skipfncvalue = ctrl; } } } ctx->want_secret = want_secret; ctx->kr_handle = keydb_new (); if (!ctx->kr_handle) { rc = gpg_error_from_syserror (); getkey_end (ctrl, ctx); return rc; } if (!ret_kb) ret_kb = &help_kb; if (pk) { ctx->req_usage = pk->req_usage; } rc = lookup (ctrl, ctx, want_secret, ret_kb, &found_key); if (!rc && pk) { pk_from_block (pk, *ret_kb, found_key); } release_kbnode (help_kb); if (retctx) /* Caller wants the context. */ *retctx = ctx; else { if (ret_kdbhd) { *ret_kdbhd = ctx->kr_handle; ctx->kr_handle = NULL; } getkey_end (ctrl, ctx); } return rc; } /* Find a public key identified by NAME. * * If name appears to be a valid RFC822 mailbox (i.e., email * address) and auto key lookup is enabled (no_akl == 0), then the * specified auto key lookup methods (--auto-key-lookup) are used to * import the key into the local keyring. Otherwise, just the local * keyring is consulted. * * If RETCTX is not NULL, then the constructed context is returned in * *RETCTX so that getpubkey_next can be used to get subsequent * results. In this case, getkey_end() must be used to free the * search context. If RETCTX is not NULL, then RET_KDBHD must be * NULL. * * If PK is not NULL, the public key of the first result is returned * in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is * passed through to the lookup function and is a mask of * PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this * is non-zero, only keys with the specified usage will be returned. * Note: The self-signed data has already been merged into the public * key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xfree, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * NAME is a string, which is turned into a search query using * classify_user_id. * * If RET_KEYBLOCK is not NULL, the keyblock is returned in * *RET_KEYBLOCK. This should be freed using release_kbnode(). * * If RET_KDBHD is not NULL, then the new database handle used to * conduct the search is returned in *RET_KDBHD. This can be used to * get subsequent results using keydb_search_next or to modify the * returned record. Note: in this case, no advanced filtering is done * for subsequent results (e.g., PK->REQ_USAGE is not respected). * Unlike RETCTX, this is always returned. * * If INCLUDE_UNUSABLE is set, then unusable keys (see the * documentation for skip_unusable for an exact definition) are * skipped unless they are looked up by key id or by fingerprint. * * If NO_AKL is set, then the auto key locate functionality is * disabled and only the local key ring is considered. Note: the * local key ring is consulted even if local is not in the * --auto-key-locate option list! * * This function returns 0 on success. Otherwise, an error code is * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY * (if want_secret is set) is returned if the key is not found. */ int get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX * retctx, PKT_public_key * pk, const char *name, KBNODE * ret_keyblock, KEYDB_HANDLE * ret_kdbhd, int include_unusable, int no_akl) { int rc; strlist_t namelist = NULL; struct akl *akl; int is_mbox; int nodefault = 0; int anylocalfirst = 0; /* If RETCTX is not NULL, then RET_KDBHD must be NULL. */ log_assert (retctx == NULL || ret_kdbhd == NULL); if (retctx) *retctx = NULL; /* Does NAME appear to be a mailbox (mail address)? */ is_mbox = is_valid_mailbox (name); /* The auto-key-locate feature works as follows: there are a number * of methods to look up keys. By default, the local keyring is * tried first. Then, each method listed in the --auto-key-locate is * tried in the order it appears. * * This can be changed as follows: * * - if nodefault appears anywhere in the list of options, then * the local keyring is not tried first, or, * * - if local appears anywhere in the list of options, then the * local keyring is not tried first, but in the order in which * it was listed in the --auto-key-locate option. * * Note: we only save the search context in RETCTX if the local * method is the first method tried (either explicitly or * implicitly). */ if (!no_akl) { /* auto-key-locate is enabled. */ /* nodefault is true if "nodefault" or "local" appear. */ for (akl = opt.auto_key_locate; akl; akl = akl->next) if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL) { nodefault = 1; break; } /* anylocalfirst is true if "local" appears before any other search methods (except "nodefault"). */ for (akl = opt.auto_key_locate; akl; akl = akl->next) if (akl->type != AKL_NODEFAULT) { if (akl->type == AKL_LOCAL) anylocalfirst = 1; break; } } if (!nodefault) { /* "nodefault" didn't occur. Thus, "local" is implicitly the * first method to try. */ anylocalfirst = 1; } if (nodefault && is_mbox) { /* Either "nodefault" or "local" (explicitly) appeared in the * auto key locate list and NAME appears to be an email address. * Don't try the local keyring. */ rc = GPG_ERR_NO_PUBKEY; } else { /* Either "nodefault" and "local" don't appear in the auto key * locate list (in which case we try the local keyring first) or * NAME does not appear to be an email address (in which case we * only try the local keyring). In this case, lookup NAME in * the local keyring. */ add_to_strlist (&namelist, name); rc = key_byname (ctrl, retctx, namelist, pk, 0, include_unusable, ret_keyblock, ret_kdbhd); } /* If the requested name resembles a valid mailbox and automatic retrieval has been enabled, we try to import the key. */ if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY && !no_akl && is_mbox) { /* NAME wasn't present in the local keyring (or we didn't try * the local keyring). Since the auto key locate feature is * enabled and NAME appears to be an email address, try the auto * locate feature. */ for (akl = opt.auto_key_locate; akl; akl = akl->next) { unsigned char *fpr = NULL; size_t fpr_len; int did_akl_local = 0; int no_fingerprint = 0; const char *mechanism = "?"; switch (akl->type) { case AKL_NODEFAULT: /* This is a dummy mechanism. */ mechanism = "None"; rc = GPG_ERR_NO_PUBKEY; break; case AKL_LOCAL: mechanism = "Local"; did_akl_local = 1; if (retctx) { getkey_end (ctrl, *retctx); *retctx = NULL; } add_to_strlist (&namelist, name); rc = key_byname (ctrl, anylocalfirst ? retctx : NULL, namelist, pk, 0, include_unusable, ret_keyblock, ret_kdbhd); break; case AKL_CERT: mechanism = "DNS CERT"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len); glo_ctrl.in_auto_key_retrieve--; break; case AKL_PKA: mechanism = "PKA"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len); glo_ctrl.in_auto_key_retrieve--; break; case AKL_DANE: mechanism = "DANE"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len); glo_ctrl.in_auto_key_retrieve--; break; case AKL_WKD: mechanism = "WKD"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len); glo_ctrl.in_auto_key_retrieve--; break; case AKL_LDAP: mechanism = "LDAP"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len); glo_ctrl.in_auto_key_retrieve--; break; case AKL_KEYSERVER: /* Strictly speaking, we don't need to only use a valid * mailbox for the getname search, but it helps cut down * on the problem of searching for something like "john" * and getting a whole lot of keys back. */ if (keyserver_any_configured (ctrl)) { mechanism = "keyserver"; glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len, opt.keyserver); glo_ctrl.in_auto_key_retrieve--; } else { mechanism = "Unconfigured keyserver"; rc = GPG_ERR_NO_PUBKEY; } break; case AKL_SPEC: { struct keyserver_spec *keyserver; mechanism = akl->spec->uri; keyserver = keyserver_match (akl->spec); glo_ctrl.in_auto_key_retrieve++; rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len, keyserver); glo_ctrl.in_auto_key_retrieve--; } break; } /* Use the fingerprint of the key that we actually fetched. * This helps prevent problems where the key that we fetched * doesn't have the same name that we used to fetch it. In * the case of CERT and PKA, this is an actual security * requirement as the URL might point to a key put in by an * attacker. By forcing the use of the fingerprint, we * won't use the attacker's key here. */ if (!rc && fpr) { char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1]; log_assert (fpr_len <= MAX_FINGERPRINT_LEN); free_strlist (namelist); namelist = NULL; bin2hex (fpr, fpr_len, fpr_string); if (opt.verbose) log_info ("auto-key-locate found fingerprint %s\n", fpr_string); add_to_strlist (&namelist, fpr_string); } else if (!rc && !fpr && !did_akl_local) { /* The acquisition method said no failure occurred, but * it didn't return a fingerprint. That's a failure. */ no_fingerprint = 1; rc = GPG_ERR_NO_PUBKEY; } xfree (fpr); fpr = NULL; if (!rc && !did_akl_local) { /* There was no error and we didn't do a local lookup. * This means that we imported a key into the local * keyring. Try to read the imported key from the * keyring. */ if (retctx) { getkey_end (ctrl, *retctx); *retctx = NULL; } rc = key_byname (ctrl, anylocalfirst ? retctx : NULL, namelist, pk, 0, include_unusable, ret_keyblock, ret_kdbhd); } if (!rc) { /* Key found. */ log_info (_("automatically retrieved '%s' via %s\n"), name, mechanism); break; } if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY || opt.verbose || no_fingerprint) log_info (_("error retrieving '%s' via %s: %s\n"), name, mechanism, no_fingerprint ? _("No fingerprint") : gpg_strerror (rc)); } } if (rc && retctx) { getkey_end (ctrl, *retctx); *retctx = NULL; } if (retctx && *retctx) { log_assert (!(*retctx)->extra_list); (*retctx)->extra_list = namelist; } else free_strlist (namelist); return rc; } /* Comparison machinery for get_best_pubkey_byname. */ /* First we have a struct to cache computed information about the key * in question. */ struct pubkey_cmp_cookie { int valid; /* Is this cookie valid? */ PKT_public_key key; /* The key. */ PKT_user_id *uid; /* The matching UID packet. */ unsigned int validity; /* Computed validity of (KEY, UID). */ u32 creation_time; /* Creation time of the newest subkey capable of encryption. */ }; /* Then we have a series of helper functions. */ static int key_is_ok (const PKT_public_key *key) { return (! key->has_expired && ! key->flags.revoked && key->flags.valid && ! key->flags.disabled); } static int uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid) { return key_is_ok (key) && ! uid->flags.revoked; } static int subkey_is_ok (const PKT_public_key *sub) { return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled; } /* Finally this function compares a NEW key to the former candidate * OLD. Returns < 0 if the old key is worse, > 0 if the old key is * better, == 0 if it is a tie. */ static int pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old, struct pubkey_cmp_cookie *new, KBNODE new_keyblock) { kbnode_t n; new->creation_time = 0; for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY); n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY)) { PKT_public_key *sub = n->pkt->pkt.public_key; if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0) continue; if (! subkey_is_ok (sub)) continue; if (sub->timestamp > new->creation_time) new->creation_time = sub->timestamp; } for (n = find_next_kbnode (new_keyblock, PKT_USER_ID); n; n = find_next_kbnode (n, PKT_USER_ID)) { PKT_user_id *uid = n->pkt->pkt.user_id; char *mbox = mailbox_from_userid (uid->name); int match = mbox ? strcasecmp (name, mbox) == 0 : 0; xfree (mbox); if (! match) continue; new->uid = scopy_user_id (uid); new->validity = get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK; new->valid = 1; if (! old->valid) return -1; /* No OLD key. */ if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid)) return -1; /* Validity of the NEW key is better. */ if (old->validity < new->validity) return -1; /* Validity of the NEW key is better. */ if (old->validity == new->validity && uid_is_ok (&new->key, uid) && old->creation_time < new->creation_time) return -1; /* Both keys are of the same validity, but the NEW key is newer. */ } /* Stick with the OLD key. */ return 1; } /* This function works like get_pubkey_byname, but if the name * resembles a mail address, the results are ranked and only the best * result is returned. */ int get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk, const char *name, KBNODE *ret_keyblock, int include_unusable, int no_akl) { int rc; struct getkey_ctx_s *ctx = NULL; if (retctx) *retctx = NULL; rc = get_pubkey_byname (ctrl, &ctx, pk, name, ret_keyblock, NULL, include_unusable, no_akl); if (rc) { if (ctx) getkey_end (ctrl, ctx); return rc; } if (is_valid_mailbox (name) && ctx) { /* Rank results and return only the most relevant key. */ struct pubkey_cmp_cookie best = { 0 }; struct pubkey_cmp_cookie new; kbnode_t new_keyblock; while (getkey_next (ctrl, ctx, &new.key, &new_keyblock) == 0) { int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock); release_kbnode (new_keyblock); if (diff < 0) { /* New key is better. */ release_public_key_parts (&best.key); free_user_id (best.uid); best = new; } else if (diff > 0) { /* Old key is better. */ release_public_key_parts (&new.key); free_user_id (new.uid); new.uid = NULL; } else { /* A tie. Keep the old key. */ release_public_key_parts (&new.key); free_user_id (new.uid); new.uid = NULL; } } getkey_end (ctrl, ctx); ctx = NULL; free_user_id (best.uid); best.uid = NULL; if (best.valid) { if (retctx || ret_keyblock) { ctx = xtrycalloc (1, sizeof **retctx); if (! ctx) rc = gpg_error_from_syserror (); else { ctx->kr_handle = keydb_new (); if (! ctx->kr_handle) { xfree (ctx); if (retctx) *retctx = NULL; rc = gpg_error_from_syserror (); } else { u32 *keyid = pk_keyid (&best.key); ctx->exact = 1; ctx->nitems = 1; ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID; ctx->items[0].u.kid[0] = keyid[0]; ctx->items[0].u.kid[1] = keyid[1]; if (ret_keyblock) { release_kbnode (*ret_keyblock); *ret_keyblock = NULL; rc = getkey_next (ctrl, ctx, NULL, ret_keyblock); } } } } if (pk) *pk = best.key; else release_public_key_parts (&best.key); } } if (rc && ctx) { getkey_end (ctrl, ctx); ctx = NULL; } if (retctx && ctx) *retctx = ctx; else getkey_end (ctrl, ctx); return rc; } /* Get a public key from a file. * * PK is the buffer to store the key. The caller needs to make sure * that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to * the lookup function and is a mask of PUBKEY_USAGE_SIG, * PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only * keys with the specified usage will be returned. * * FNAME is the file name. That file should contain exactly one * keyblock. * * This function returns 0 on success. Otherwise, an error code is * returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key * is not found. * * The self-signed data has already been merged into the public key * using merge_selfsigs. The caller must release the content of PK by * calling release_public_key_parts (or, if PK was malloced, using * free_public_key). */ gpg_error_t get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname) { gpg_error_t err; kbnode_t keyblock; kbnode_t found_key; unsigned int infoflags; err = read_key_from_file (ctrl, fname, &keyblock); if (!err) { /* Warning: node flag bits 0 and 1 should be preserved by * merge_selfsigs. FIXME: Check whether this still holds. */ merge_selfsigs (ctrl, keyblock); found_key = finish_lookup (keyblock, pk->req_usage, 0, &infoflags); print_status_key_considered (keyblock, infoflags); if (found_key) pk_from_block (pk, keyblock, found_key); else err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); } release_kbnode (keyblock); return err; } /* Lookup a key with the specified fingerprint. * * If PK is not NULL, the public key of the first result is returned * in *PK. Note: this function does an exact search and thus the * returned public key may be a subkey rather than the primary key. * Note: The self-signed data has already been merged into the public * key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xfree, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * If PK->REQ_USAGE is set, it is used to filter the search results. * (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the * documentation for finish_lookup to understand exactly how this is * used. * * If R_KEYBLOCK is not NULL, then the first result's keyblock is * returned in *R_KEYBLOCK. This should be freed using * release_kbnode(). * * FPRINT is a byte array whose contents is the fingerprint to use as * the search term. FPRINT_LEN specifies the length of the * fingerprint (in bytes). Currently, only 16 and 20-byte * fingerprints are supported. * * FIXME: We should replace this with the _byname function. This can * be done by creating a userID conforming to the unified fingerprint * style. */ int get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, const byte * fprint, size_t fprint_len) { int rc; if (r_keyblock) *r_keyblock = NULL; if (fprint_len == 20 || fprint_len == 16) { struct getkey_ctx_s ctx; KBNODE kb = NULL; KBNODE found_key = NULL; memset (&ctx, 0, sizeof ctx); ctx.exact = 1; ctx.not_allocated = 1; ctx.kr_handle = keydb_new (); if (!ctx.kr_handle) return gpg_error_from_syserror (); ctx.nitems = 1; ctx.items[0].mode = fprint_len == 16 ? KEYDB_SEARCH_MODE_FPR16 : KEYDB_SEARCH_MODE_FPR20; memcpy (ctx.items[0].u.fpr, fprint, fprint_len); rc = lookup (ctrl, &ctx, 0, &kb, &found_key); if (!rc && pk) pk_from_block (pk, kb, found_key); if (!rc && r_keyblock) { *r_keyblock = kb; kb = NULL; } release_kbnode (kb); getkey_end (ctrl, &ctx); } else rc = GPG_ERR_GENERAL; /* Oops */ return rc; } /* This function is similar to get_pubkey_byfprint, but it doesn't * merge the self-signed data into the public key and subkeys or into * the user ids. It also doesn't add the key to the user id cache. * Further, this function ignores PK->REQ_USAGE. * * This function is intended to avoid recursion and, as such, should * only be used in very specific situations. * * Like get_pubkey_byfprint, PK may be NULL. In that case, this * function effectively just checks for the existence of the key. */ int get_pubkey_byfprint_fast (PKT_public_key * pk, const byte * fprint, size_t fprint_len) { int rc = 0; KEYDB_HANDLE hd; KBNODE keyblock; byte fprbuf[MAX_FINGERPRINT_LEN]; int i; for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++) fprbuf[i] = fprint[i]; while (i < MAX_FINGERPRINT_LEN) fprbuf[i++] = 0; hd = keydb_new (); if (!hd) return gpg_error_from_syserror (); rc = keydb_search_fpr (hd, fprbuf); if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { keydb_release (hd); return GPG_ERR_NO_PUBKEY; } rc = keydb_get_keyblock (hd, &keyblock); keydb_release (hd); if (rc) { log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); return GPG_ERR_NO_PUBKEY; } log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY || keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY); if (pk) copy_public_key (pk, keyblock->pkt->pkt.public_key); release_kbnode (keyblock); /* Not caching key here since it won't have all of the fields properly set. */ return 0; } const char * parse_def_secret_key (ctrl_t ctrl) { KEYDB_HANDLE hd = NULL; strlist_t t; static int warned; for (t = opt.def_secret_key; t; t = t->next) { gpg_error_t err; KEYDB_SEARCH_DESC desc; KBNODE kb; KBNODE node; err = classify_user_id (t->d, &desc, 1); if (err) { log_error (_("secret key \"%s\" not found: %s\n"), t->d, gpg_strerror (err)); if (!opt.quiet) log_info (_("(check argument of option '%s')\n"), "--default-key"); continue; } if (! hd) { hd = keydb_new (); if (!hd) return NULL; } else keydb_search_reset (hd); err = keydb_search (hd, &desc, 1, NULL); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) continue; if (err) { log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err)); t = NULL; break; } err = keydb_get_keyblock (hd, &kb); if (err) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); continue; } merge_selfsigs (ctrl, kb); err = gpg_error (GPG_ERR_NO_SECKEY); node = kb; do { PKT_public_key *pk = node->pkt->pkt.public_key; /* Check that the key has the signing capability. */ if (! (pk->pubkey_usage & PUBKEY_USAGE_SIG)) continue; /* Check if the key is valid. */ if (pk->flags.revoked) { if (DBG_LOOKUP) log_debug ("not using %s as default key, %s", keystr_from_pk (pk), "revoked"); continue; } if (pk->has_expired) { if (DBG_LOOKUP) log_debug ("not using %s as default key, %s", keystr_from_pk (pk), "expired"); continue; } if (pk_is_disabled (pk)) { if (DBG_LOOKUP) log_debug ("not using %s as default key, %s", keystr_from_pk (pk), "disabled"); continue; } err = agent_probe_secret_key (ctrl, pk); if (! err) /* This is a valid key. */ break; } while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))); release_kbnode (kb); if (err) { if (! warned && ! opt.quiet) { log_info (_("Warning: not using '%s' as default key: %s\n"), t->d, gpg_strerror (GPG_ERR_NO_SECKEY)); print_reported_error (err, GPG_ERR_NO_SECKEY); } } else { if (! warned && ! opt.quiet) log_info (_("using \"%s\" as default secret key for signing\n"), t->d); break; } } if (! warned && opt.def_secret_key && ! t) log_info (_("all values passed to '%s' ignored\n"), "--default-key"); warned = 1; if (hd) keydb_release (hd); if (t) return t->d; return NULL; } /* Look up a secret key. * * If PK is not NULL, the public key of the first result is returned * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is * set, it is used to filter the search results. See the * documentation for finish_lookup to understand exactly how this is * used. Note: The self-signed data has already been merged into the * public key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xfree, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * If --default-key was set, then the specified key is looked up. (In * this case, the default key is returned even if it is considered * unusable. See the documentation for skip_unusable for exactly what * this means.) * * Otherwise, this initiates a DB scan that returns all keys that are * usable (see previous paragraph for exactly what usable means) and * for which a secret key is available. * * This function returns the first match. Additional results can be * returned using getkey_next. */ gpg_error_t get_seckey_default (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; strlist_t namelist = NULL; int include_unusable = 1; const char *def_secret_key = parse_def_secret_key (ctrl); if (def_secret_key) add_to_strlist (&namelist, def_secret_key); else include_unusable = 0; err = key_byname (ctrl, NULL, namelist, pk, 1, include_unusable, NULL, NULL); free_strlist (namelist); return err; } /* Search for keys matching some criteria. * * If RETCTX is not NULL, then the constructed context is returned in * *RETCTX so that getpubkey_next can be used to get subsequent * results. In this case, getkey_end() must be used to free the * search context. If RETCTX is not NULL, then RET_KDBHD must be * NULL. * * If PK is not NULL, the public key of the first result is returned * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is * set, it is used to filter the search results. See the * documentation for finish_lookup to understand exactly how this is * used. Note: The self-signed data has already been merged into the * public key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xfree, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * If NAMES is not NULL, then a search query is constructed using * classify_user_id on each of the strings in the list. (Recall: the * database does an OR of the terms, not an AND.) If NAMES is * NULL, then all results are returned. * * If WANT_SECRET is set, then only keys with an available secret key * (either locally or via key registered on a smartcard) are returned. * * This function does not skip unusable keys (see the documentation * for skip_unusable for an exact definition). * * If RET_KEYBLOCK is not NULL, the keyblock is returned in * *RET_KEYBLOCK. This should be freed using release_kbnode(). * * This function returns 0 on success. Otherwise, an error code is * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY * (if want_secret is set) is returned if the key is not found. */ gpg_error_t getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, strlist_t names, int want_secret, kbnode_t *ret_keyblock) { return key_byname (ctrl, retctx, names, pk, want_secret, 1, ret_keyblock, NULL); } /* Search for one key matching some criteria. * * If RETCTX is not NULL, then the constructed context is returned in * *RETCTX so that getpubkey_next can be used to get subsequent * results. In this case, getkey_end() must be used to free the * search context. If RETCTX is not NULL, then RET_KDBHD must be * NULL. * * If PK is not NULL, the public key of the first result is returned * in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is * set, it is used to filter the search results. See the * documentation for finish_lookup to understand exactly how this is * used. Note: The self-signed data has already been merged into the * public key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xfree, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * If NAME is not NULL, then a search query is constructed using * classify_user_id on the string. In this case, even unusable keys * (see the documentation for skip_unusable for an exact definition of * unusable) are returned. Otherwise, if --default-key was set, then * that key is returned (even if it is unusable). If neither of these * conditions holds, then the first usable key is returned. * * If WANT_SECRET is set, then only keys with an available secret key * (either locally or via key registered on a smartcard) are returned. * * This function does not skip unusable keys (see the documentation * for skip_unusable for an exact definition). * * If RET_KEYBLOCK is not NULL, the keyblock is returned in * *RET_KEYBLOCK. This should be freed using release_kbnode(). * * This function returns 0 on success. Otherwise, an error code is * returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY * (if want_secret is set) is returned if the key is not found. * * FIXME: We also have the get_pubkey_byname function which has a * different semantic. Should be merged with this one. */ gpg_error_t getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, const char *name, int want_secret, kbnode_t *ret_keyblock) { gpg_error_t err; strlist_t namelist = NULL; int with_unusable = 1; const char *def_secret_key = NULL; if (want_secret && !name) def_secret_key = parse_def_secret_key (ctrl); if (want_secret && !name && def_secret_key) add_to_strlist (&namelist, def_secret_key); else if (name) add_to_strlist (&namelist, name); else with_unusable = 0; err = key_byname (ctrl, retctx, namelist, pk, want_secret, with_unusable, ret_keyblock, NULL); /* FIXME: Check that we really return GPG_ERR_NO_SECKEY if WANT_SECRET has been used. */ free_strlist (namelist); return err; } /* Return the next search result. * * If PK is not NULL, the public key of the next result is returned in * *PK. Note: The self-signed data has already been merged into the * public key using merge_selfsigs. Free *PK by calling * release_public_key_parts (or, if PK was allocated using xmalloc, you * can use free_public_key, which calls release_public_key_parts(PK) * and then xfree(PK)). * * RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire * found keyblock is returned which must be released with * release_kbnode. If the function returns an error NULL is stored at * RET_KEYBLOCK. * * The self-signed data has already been merged into the public key * using merge_selfsigs. */ gpg_error_t getkey_next (ctrl_t ctrl, getkey_ctx_t ctx, PKT_public_key *pk, kbnode_t *ret_keyblock) { int rc; /* Fixme: Make sure this is proper gpg_error */ KBNODE keyblock = NULL; KBNODE found_key = NULL; /* We need to disable the caching so that for an exact key search we won't get the result back from the cache and thus end up in an endless loop. The endless loop can occur, because the cache is used without respecting the current file pointer! */ keydb_disable_caching (ctx->kr_handle); /* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the * caller wants PK, but not RET_KEYBLOCK, we need hand in our own * keyblock. */ if (pk && ret_keyblock == NULL) ret_keyblock = &keyblock; rc = lookup (ctrl, ctx, ctx->want_secret, ret_keyblock, pk ? &found_key : NULL); if (!rc && pk) { log_assert (found_key); pk_from_block (pk, NULL, found_key); release_kbnode (keyblock); } return rc; } /* Release any resources used by a key listing context. This must be * called on the context returned by, e.g., getkey_byname. */ void getkey_end (ctrl_t ctrl, getkey_ctx_t ctx) { if (ctx) { if (ctrl && !ctrl->cached_getkey_kdb) ctrl->cached_getkey_kdb = ctx->kr_handle; else keydb_release (ctx->kr_handle); free_strlist (ctx->extra_list); if (!ctx->not_allocated) xfree (ctx); } } /************************************************ ************* Merging stuff ******************** ************************************************/ /* Set the mainkey_id fields for all keys in KEYBLOCK. This is * usually done by merge_selfsigs but at some places we only need the * main_kid not a full merge. The function also guarantees that all * pk->keyids are computed. */ void setup_main_keyids (kbnode_t keyblock) { u32 kid[2], mainkid[2]; kbnode_t kbctx, node; PKT_public_key *pk; if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY) BUG (); pk = keyblock->pkt->pkt.public_key; keyid_from_pk (pk, mainkid); for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) { if (!(node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) continue; pk = node->pkt->pkt.public_key; keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */ if (!pk->main_keyid[0] && !pk->main_keyid[1]) { pk->main_keyid[0] = mainkid[0]; pk->main_keyid[1] = mainkid[1]; } } } /* KEYBLOCK corresponds to a public key block. This function merges * much of the information from the self-signed data into the public * key, public subkey and user id data structures. If you use the * high-level search API (e.g., get_pubkey) for looking up key blocks, * then you don't need to call this function. This function is * useful, however, if you change the keyblock, e.g., by adding or * removing a self-signed data packet. */ void merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock) { if (!keyblock) ; else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) merge_selfsigs (ctrl, keyblock); else log_debug ("FIXME: merging secret key blocks is not anymore available\n"); } static int parse_key_usage (PKT_signature * sig) { int key_usage = 0; const byte *p; size_t n; byte flags; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_FLAGS, &n); if (p && n) { /* First octet of the keyflags. */ flags = *p; if (flags & 1) { key_usage |= PUBKEY_USAGE_CERT; flags &= ~1; } if (flags & 2) { key_usage |= PUBKEY_USAGE_SIG; flags &= ~2; } /* We do not distinguish between encrypting communications and encrypting storage. */ if (flags & (0x04 | 0x08)) { key_usage |= PUBKEY_USAGE_ENC; flags &= ~(0x04 | 0x08); } if (flags & 0x20) { key_usage |= PUBKEY_USAGE_AUTH; flags &= ~0x20; } if (flags) key_usage |= PUBKEY_USAGE_UNKNOWN; if (!key_usage) key_usage |= PUBKEY_USAGE_NONE; } else if (p) /* Key flags of length zero. */ key_usage |= PUBKEY_USAGE_NONE; /* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a capability that we do not handle. This serves to distinguish between a zero key usage which we handle as the default capabilities for that algorithm, and a usage that we do not handle. Likewise we use PUBKEY_USAGE_NONE to indicate that key_flags have been given but they do not specify any usage. */ return key_usage; } /* Apply information from SIGNODE (which is the valid self-signature * associated with that UID) to the UIDNODE: * - weather the UID has been revoked * - assumed creation date of the UID * - temporary store the keyflags here * - temporary store the key expiration time here * - mark whether the primary user ID flag hat been set. * - store the preferences */ static void fixup_uidnode (KBNODE uidnode, KBNODE signode, u32 keycreated) { PKT_user_id *uid = uidnode->pkt->pkt.user_id; PKT_signature *sig = signode->pkt->pkt.signature; const byte *p, *sym, *hash, *zip; size_t n, nsym, nhash, nzip; sig->flags.chosen_selfsig = 1;/* We chose this one. */ uid->created = 0; /* Not created == invalid. */ if (IS_UID_REV (sig)) { uid->flags.revoked = 1; return; /* Has been revoked. */ } else uid->flags.revoked = 0; uid->expiredate = sig->expiredate; if (sig->flags.expired) { uid->flags.expired = 1; return; /* Has expired. */ } else uid->flags.expired = 0; uid->created = sig->timestamp; /* This one is okay. */ uid->selfsigversion = sig->version; /* If we got this far, it's not expired :) */ uid->flags.expired = 0; /* Store the key flags in the helper variable for later processing. */ uid->help_key_usage = parse_key_usage (sig); /* Ditto for the key expiration. */ p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); if (p && buf32_to_u32 (p)) uid->help_key_expire = keycreated + buf32_to_u32 (p); else uid->help_key_expire = 0; /* Set the primary user ID flag - we will later wipe out some * of them to only have one in our keyblock. */ uid->flags.primary = 0; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL); if (p && *p) uid->flags.primary = 2; /* We could also query this from the unhashed area if it is not in * the hased area and then later try to decide which is the better * there should be no security problem with this. * For now we only look at the hashed one. */ /* Now build the preferences list. These must come from the hashed section so nobody can modify the ciphers a key is willing to accept. */ p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM, &n); sym = p; nsym = p ? n : 0; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH, &n); hash = p; nhash = p ? n : 0; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR, &n); zip = p; nzip = p ? n : 0; if (uid->prefs) xfree (uid->prefs); n = nsym + nhash + nzip; if (!n) uid->prefs = NULL; else { uid->prefs = xmalloc (sizeof (*uid->prefs) * (n + 1)); n = 0; for (; nsym; nsym--, n++) { uid->prefs[n].type = PREFTYPE_SYM; uid->prefs[n].value = *sym++; } for (; nhash; nhash--, n++) { uid->prefs[n].type = PREFTYPE_HASH; uid->prefs[n].value = *hash++; } for (; nzip; nzip--, n++) { uid->prefs[n].type = PREFTYPE_ZIP; uid->prefs[n].value = *zip++; } uid->prefs[n].type = PREFTYPE_NONE; /* End of list marker */ uid->prefs[n].value = 0; } /* See whether we have the MDC feature. */ uid->flags.mdc = 0; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES, &n); if (p && n && (p[0] & 0x01)) uid->flags.mdc = 1; /* And the keyserver modify flag. */ uid->flags.ks_modify = 1; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS, &n); if (p && n && (p[0] & 0x80)) uid->flags.ks_modify = 0; } static void sig_to_revoke_info (PKT_signature * sig, struct revoke_info *rinfo) { rinfo->date = sig->timestamp; rinfo->algo = sig->pubkey_algo; rinfo->keyid[0] = sig->keyid[0]; rinfo->keyid[1] = sig->keyid[1]; } /* Given a keyblock, parse the key block and extract various pieces of information and save them with the primary key packet and the user id packets. For instance, some information is stored in signature packets. We find the latest such valid packet (since the user can change that information) and copy its contents into the PKT_public_key. Note that R_REVOKED may be set to 0, 1 or 2. This function fills in the following fields in the primary key's keyblock: main_keyid (computed) revkey / numrevkeys (derived from self signed key data) flags.valid (whether we have at least 1 self-sig) flags.maybe_revoked (whether a designed revoked the key, but we are missing the key to check the sig) selfsigversion (highest version of any valid self-sig) pubkey_usage (derived from most recent self-sig or most recent user id) has_expired (various sources) expiredate (various sources) See the documentation for fixup_uidnode for how the user id packets are modified. In addition to that the primary user id's is_primary field is set to 1 and the other user id's is_primary are set to 0. */ static void merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked, struct revoke_info *rinfo) { PKT_public_key *pk = NULL; KBNODE k; u32 kid[2]; u32 sigdate, uiddate, uiddate2; KBNODE signode, uidnode, uidnode2; u32 curtime = make_timestamp (); unsigned int key_usage = 0; u32 keytimestamp = 0; u32 key_expire = 0; int key_expire_seen = 0; byte sigversion = 0; *r_revoked = 0; memset (rinfo, 0, sizeof (*rinfo)); /* Section 11.1 of RFC 4880 determines the order of packets within a message. There are three sections, which must occur in the following order: the public key, the user ids and user attributes and the subkeys. Within each section, each primary packet (e.g., a user id packet) is followed by one or more signature packets, which modify that packet. */ /* According to Section 11.1 of RFC 4880, the public key must be the first packet. */ if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY) /* parse_keyblock_image ensures that the first packet is the public key. */ BUG (); pk = keyblock->pkt->pkt.public_key; keytimestamp = pk->timestamp; keyid_from_pk (pk, kid); pk->main_keyid[0] = kid[0]; pk->main_keyid[1] = kid[1]; if (pk->version < 4) { /* Before v4 the key packet itself contains the expiration date * and there was no way to change it, so we start with the one * from the key packet. */ key_expire = pk->max_expiredate; key_expire_seen = 1; } /* First pass: - Find the latest direct key self-signature. We assume that the newest one overrides all others. - Determine whether the key has been revoked. - Gather all revocation keys (unlike other data, we don't just take them from the latest self-signed packet). - Determine max (sig[...]->version). */ /* Reset this in case this key was already merged. */ xfree (pk->revkey); pk->revkey = NULL; pk->numrevkeys = 0; signode = NULL; sigdate = 0; /* Helper variable to find the latest signature. */ /* According to Section 11.1 of RFC 4880, the public key comes first and is immediately followed by any signature packets that modify it. */ for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID && k->pkt->pkttype != PKT_ATTRIBUTE && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = k->pkt->pkt.signature; if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1]) /* Self sig. */ { if (check_key_signature (ctrl, keyblock, k, NULL)) ; /* Signature did not verify. */ else if (IS_KEY_REV (sig)) { /* Key has been revoked - there is no way to * override such a revocation, so we theoretically * can stop now. We should not cope with expiration * times for revocations here because we have to * assume that an attacker can generate all kinds of * signatures. However due to the fact that the key * has been revoked it does not harm either and by * continuing we gather some more info on that * key. */ *r_revoked = 1; sig_to_revoke_info (sig, rinfo); } else if (IS_KEY_SIG (sig)) { /* Add the indicated revocations keys from all signatures not just the latest. We do this because you need multiple 1F sigs to properly handle revocation keys (PGP does it this way, and a revocation key could be sensitive and hence in a different signature). */ if (sig->revkey) { int i; pk->revkey = xrealloc (pk->revkey, sizeof (struct revocation_key) * (pk->numrevkeys + sig->numrevkeys)); for (i = 0; i < sig->numrevkeys; i++) memcpy (&pk->revkey[pk->numrevkeys++], &sig->revkey[i], sizeof (struct revocation_key)); } if (sig->timestamp >= sigdate) /* This is the latest signature so far. */ { if (sig->flags.expired) ; /* Signature has expired - ignore it. */ else { sigdate = sig->timestamp; signode = k; if (sig->version > sigversion) sigversion = sig->version; } } } } } } /* Remove dupes from the revocation keys. */ if (pk->revkey) { int i, j, x, changed = 0; for (i = 0; i < pk->numrevkeys; i++) { for (j = i + 1; j < pk->numrevkeys; j++) { if (memcmp (&pk->revkey[i], &pk->revkey[j], sizeof (struct revocation_key)) == 0) { /* remove j */ for (x = j; x < pk->numrevkeys - 1; x++) pk->revkey[x] = pk->revkey[x + 1]; pk->numrevkeys--; j--; changed = 1; } } } if (changed) pk->revkey = xrealloc (pk->revkey, pk->numrevkeys * sizeof (struct revocation_key)); } if (signode) /* SIGNODE is the 1F signature packet with the latest creation time. Extract some information from it. */ { /* Some information from a direct key signature take precedence * over the same information given in UID sigs. */ PKT_signature *sig = signode->pkt->pkt.signature; const byte *p; key_usage = parse_key_usage (sig); p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); if (p && buf32_to_u32 (p)) { key_expire = keytimestamp + buf32_to_u32 (p); key_expire_seen = 1; } /* Mark that key as valid: One direct key signature should * render a key as valid. */ pk->flags.valid = 1; } /* Pass 1.5: Look for key revocation signatures that were not made by the key (i.e. did a revocation key issue a revocation for us?). Only bother to do this if there is a revocation key in the first place and we're not revoked already. */ if (!*r_revoked && pk->revkey) for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next) { if (k->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = k->pkt->pkt.signature; if (IS_KEY_REV (sig) && (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1])) { int rc = check_revocation_keys (ctrl, pk, sig); if (rc == 0) { *r_revoked = 2; sig_to_revoke_info (sig, rinfo); /* Don't continue checking since we can't be any more revoked than this. */ break; } else if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY) pk->flags.maybe_revoked = 1; /* A failure here means the sig did not verify, was not issued by a revocation key, or a revocation key loop was broken. If a revocation key isn't findable, however, the key might be revoked and we don't know it. */ /* TODO: In the future handle subkey and cert revocations? PGP doesn't, but it's in 2440. */ } } } /* Second pass: Look at the self-signature of all user IDs. */ /* According to RFC 4880 section 11.1, user id and attribute packets are in the second section, after the public key packet and before the subkey packets. */ signode = uidnode = NULL; sigdate = 0; /* Helper variable to find the latest signature in one UID. */ for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE) /* New user id packet. */ { if (uidnode && signode) /* Apply the data from the most recent self-signed packet to the preceding user id packet. */ { fixup_uidnode (uidnode, signode, keytimestamp); pk->flags.valid = 1; } /* Clear SIGNODE. The only relevant self-signed data for UIDNODE follows it. */ if (k->pkt->pkttype == PKT_USER_ID) uidnode = k; else uidnode = NULL; signode = NULL; sigdate = 0; } else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode) { PKT_signature *sig = k->pkt->pkt.signature; if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1]) { if (check_key_signature (ctrl, keyblock, k, NULL)) ; /* signature did not verify */ else if ((IS_UID_SIG (sig) || IS_UID_REV (sig)) && sig->timestamp >= sigdate) { /* Note: we allow invalidation of cert revocations * by a newer signature. An attacker can't use this * because a key should be revoked with a key revocation. * The reason why we have to allow for that is that at * one time an email address may become invalid but later * the same email address may become valid again (hired, * fired, hired again). */ sigdate = sig->timestamp; signode = k; signode->pkt->pkt.signature->flags.chosen_selfsig = 0; if (sig->version > sigversion) sigversion = sig->version; } } } } if (uidnode && signode) { fixup_uidnode (uidnode, signode, keytimestamp); pk->flags.valid = 1; } /* If the key isn't valid yet, and we have --allow-non-selfsigned-uid set, then force it valid. */ if (!pk->flags.valid && opt.allow_non_selfsigned_uid) { if (opt.verbose) log_info (_("Invalid key %s made valid by" " --allow-non-selfsigned-uid\n"), keystr_from_pk (pk)); pk->flags.valid = 1; } /* The key STILL isn't valid, so try and find an ultimately trusted signature. */ if (!pk->flags.valid) { uidnode = NULL; for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID) uidnode = k; else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode) { PKT_signature *sig = k->pkt->pkt.signature; if (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1]) { PKT_public_key *ultimate_pk; ultimate_pk = xmalloc_clear (sizeof (*ultimate_pk)); /* We don't want to use the full get_pubkey to avoid infinite recursion in certain cases. There is no reason to check that an ultimately trusted key is still valid - if it has been revoked the user should also remove the ultimate trust flag. */ if (get_pubkey_fast (ultimate_pk, sig->keyid) == 0 && check_key_signature2 (ctrl, keyblock, k, ultimate_pk, NULL, NULL, NULL, NULL) == 0 && get_ownertrust (ctrl, ultimate_pk) == TRUST_ULTIMATE) { free_public_key (ultimate_pk); pk->flags.valid = 1; break; } free_public_key (ultimate_pk); } } } } /* Record the highest selfsig version so we know if this is a v3 key through and through, or a v3 key with a v4 selfsig somewhere. This is useful in a few places to know if the key must be treated as PGP2-style or OpenPGP-style. Note that a selfsig revocation with a higher version number will also raise this value. This is okay since such a revocation must be issued by the user (i.e. it cannot be issued by someone else to modify the key behavior.) */ pk->selfsigversion = sigversion; /* Now that we had a look at all user IDs we can now get some information * from those user IDs. */ if (!key_usage) { /* Find the latest user ID with key flags set. */ uiddate = 0; /* Helper to find the latest user ID. */ for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = k->pkt->pkt.user_id; if (uid->help_key_usage && uid->created > uiddate) { key_usage = uid->help_key_usage; uiddate = uid->created; } } } } if (!key_usage) { /* No key flags at all: get it from the algo. */ key_usage = openpgp_pk_algo_usage (pk->pubkey_algo); } else { /* Check that the usage matches the usage as given by the algo. */ int x = openpgp_pk_algo_usage (pk->pubkey_algo); if (x) /* Mask it down to the actual allowed usage. */ key_usage &= x; } /* Whatever happens, it's a primary key, so it can certify. */ pk->pubkey_usage = key_usage | PUBKEY_USAGE_CERT; if (!key_expire_seen) { /* Find the latest valid user ID with a key expiration set * Note, that this may be a different one from the above because * some user IDs may have no expiration date set. */ uiddate = 0; for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = k->pkt->pkt.user_id; if (uid->help_key_expire && uid->created > uiddate) { key_expire = uid->help_key_expire; uiddate = uid->created; } } } } /* Currently only v3 keys have a maximum expiration date, but I'll bet v5 keys get this feature again. */ if (key_expire == 0 || (pk->max_expiredate && key_expire > pk->max_expiredate)) key_expire = pk->max_expiredate; pk->has_expired = key_expire >= curtime ? 0 : key_expire; pk->expiredate = key_expire; /* Fixme: we should see how to get rid of the expiretime fields but * this needs changes at other places too. */ /* And now find the real primary user ID and delete all others. */ uiddate = uiddate2 = 0; uidnode = uidnode2 = NULL; for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data) { PKT_user_id *uid = k->pkt->pkt.user_id; if (uid->flags.primary) { if (uid->created > uiddate) { uiddate = uid->created; uidnode = k; } else if (uid->created == uiddate && uidnode) { /* The dates are equal, so we need to do a different (and arbitrary) comparison. This should rarely, if ever, happen. It's good to try and guarantee that two different GnuPG users with two different keyrings at least pick the same primary. */ if (cmp_user_ids (uid, uidnode->pkt->pkt.user_id) > 0) uidnode = k; } } else { if (uid->created > uiddate2) { uiddate2 = uid->created; uidnode2 = k; } else if (uid->created == uiddate2 && uidnode2) { if (cmp_user_ids (uid, uidnode2->pkt->pkt.user_id) > 0) uidnode2 = k; } } } } if (uidnode) { for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data) { PKT_user_id *uid = k->pkt->pkt.user_id; if (k != uidnode) uid->flags.primary = 0; } } } else if (uidnode2) { /* None is flagged primary - use the latest user ID we have, and disambiguate with the arbitrary packet comparison. */ uidnode2->pkt->pkt.user_id->flags.primary = 1; } else { /* None of our uids were self-signed, so pick the one that sorts first to be the primary. This is the best we can do here since there are no self sigs to date the uids. */ uidnode = NULL; for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data) { if (!uidnode) { uidnode = k; uidnode->pkt->pkt.user_id->flags.primary = 1; continue; } else { if (cmp_user_ids (k->pkt->pkt.user_id, uidnode->pkt->pkt.user_id) > 0) { uidnode->pkt->pkt.user_id->flags.primary = 0; uidnode = k; uidnode->pkt->pkt.user_id->flags.primary = 1; } else k->pkt->pkt.user_id->flags.primary = 0; /* just to be safe */ } } } } } /* Convert a buffer to a signature. Useful for 0x19 embedded sigs. Caller must free the signature when they are done. */ static PKT_signature * buf_to_sig (const byte * buf, size_t len) { PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature)); IOBUF iobuf = iobuf_temp_with_content (buf, len); int save_mode = set_packet_list_mode (0); if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0) { xfree (sig); sig = NULL; } set_packet_list_mode (save_mode); iobuf_close (iobuf); return sig; } /* Use the self-signed data to fill in various fields in subkeys. KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in. Sets the following fields on the subkey: main_keyid flags.valid if the subkey has a valid self-sig binding flags.revoked flags.backsig pubkey_usage has_expired expired_date On this subkey's most revent valid self-signed packet, the following field is set: flags.chosen_selfsig */ static void merge_selfsigs_subkey (ctrl_t ctrl, kbnode_t keyblock, kbnode_t subnode) { PKT_public_key *mainpk = NULL, *subpk = NULL; PKT_signature *sig; KBNODE k; u32 mainkid[2]; u32 sigdate = 0; KBNODE signode; u32 curtime = make_timestamp (); unsigned int key_usage = 0; u32 keytimestamp = 0; u32 key_expire = 0; const byte *p; if (subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY) BUG (); mainpk = keyblock->pkt->pkt.public_key; if (mainpk->version < 4) return;/* (actually this should never happen) */ keyid_from_pk (mainpk, mainkid); subpk = subnode->pkt->pkt.public_key; keytimestamp = subpk->timestamp; subpk->flags.valid = 0; subpk->flags.exact = 0; subpk->main_keyid[0] = mainpk->main_keyid[0]; subpk->main_keyid[1] = mainpk->main_keyid[1]; /* Find the latest key binding self-signature. */ signode = NULL; sigdate = 0; /* Helper to find the latest signature. */ for (k = subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_SIGNATURE) { sig = k->pkt->pkt.signature; if (sig->keyid[0] == mainkid[0] && sig->keyid[1] == mainkid[1]) { if (check_key_signature (ctrl, keyblock, k, NULL)) ; /* Signature did not verify. */ else if (IS_SUBKEY_REV (sig)) { /* Note that this means that the date on a revocation sig does not matter - even if the binding sig is dated after the revocation sig, the subkey is still marked as revoked. This seems ok, as it is just as easy to make new subkeys rather than re-sign old ones as the problem is in the distribution. Plus, PGP (7) does this the same way. */ subpk->flags.revoked = 1; sig_to_revoke_info (sig, &subpk->revoked); /* Although we could stop now, we continue to * figure out other information like the old expiration * time. */ } else if (IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate) { if (sig->flags.expired) ; /* Signature has expired - ignore it. */ else { sigdate = sig->timestamp; signode = k; signode->pkt->pkt.signature->flags.chosen_selfsig = 0; } } } } } /* No valid key binding. */ if (!signode) return; sig = signode->pkt->pkt.signature; sig->flags.chosen_selfsig = 1; /* So we know which selfsig we chose later. */ key_usage = parse_key_usage (sig); if (!key_usage) { /* No key flags at all: get it from the algo. */ key_usage = openpgp_pk_algo_usage (subpk->pubkey_algo); } else { /* Check that the usage matches the usage as given by the algo. */ int x = openpgp_pk_algo_usage (subpk->pubkey_algo); if (x) /* Mask it down to the actual allowed usage. */ key_usage &= x; } subpk->pubkey_usage = key_usage; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); if (p && buf32_to_u32 (p)) key_expire = keytimestamp + buf32_to_u32 (p); else key_expire = 0; subpk->has_expired = key_expire >= curtime ? 0 : key_expire; subpk->expiredate = key_expire; /* Algo doesn't exist. */ if (openpgp_pk_test_algo (subpk->pubkey_algo)) return; subpk->flags.valid = 1; /* Find the most recent 0x19 embedded signature on our self-sig. */ if (!subpk->flags.backsig) { int seq = 0; size_t n; PKT_signature *backsig = NULL; sigdate = 0; /* We do this while() since there may be other embedded signatures in the future. We only want 0x19 here. */ while ((p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_SIGNATURE, &n, &seq, NULL))) if (n > 3 && ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19))) { PKT_signature *tempsig = buf_to_sig (p, n); if (tempsig) { if (tempsig->timestamp > sigdate) { if (backsig) free_seckey_enc (backsig); backsig = tempsig; sigdate = backsig->timestamp; } else free_seckey_enc (tempsig); } } seq = 0; /* It is safe to have this in the unhashed area since the 0x19 is located on the selfsig for convenience, not security. */ while ((p = enum_sig_subpkt (sig->unhashed, SIGSUBPKT_SIGNATURE, &n, &seq, NULL))) if (n > 3 && ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19))) { PKT_signature *tempsig = buf_to_sig (p, n); if (tempsig) { if (tempsig->timestamp > sigdate) { if (backsig) free_seckey_enc (backsig); backsig = tempsig; sigdate = backsig->timestamp; } else free_seckey_enc (tempsig); } } if (backsig) { /* At this point, backsig contains the most recent 0x19 sig. Let's see if it is good. */ /* 2==valid, 1==invalid, 0==didn't check */ if (check_backsig (mainpk, subpk, backsig) == 0) subpk->flags.backsig = 2; else subpk->flags.backsig = 1; free_seckey_enc (backsig); } } } /* Merge information from the self-signatures with the public key, subkeys and user ids to make using them more easy. See documentation for merge_selfsigs_main, merge_selfsigs_subkey and fixup_uidnode for exactly which fields are updated. */ static void merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock) { KBNODE k; int revoked; struct revoke_info rinfo; PKT_public_key *main_pk; prefitem_t *prefs; unsigned int mdc_feature; if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY) { if (keyblock->pkt->pkttype == PKT_SECRET_KEY) { log_error ("expected public key but found secret key " "- must stop\n"); /* We better exit here because a public key is expected at other places too. FIXME: Figure this out earlier and don't get to here at all */ g10_exit (1); } BUG (); } merge_selfsigs_main (ctrl, keyblock, &revoked, &rinfo); /* Now merge in the data from each of the subkeys. */ for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_PUBLIC_SUBKEY) { merge_selfsigs_subkey (ctrl, keyblock, k); } } main_pk = keyblock->pkt->pkt.public_key; if (revoked || main_pk->has_expired || !main_pk->flags.valid) { /* If the primary key is revoked, expired, or invalid we * better set the appropriate flags on that key and all * subkeys. */ for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_PUBLIC_KEY || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) { PKT_public_key *pk = k->pkt->pkt.public_key; if (!main_pk->flags.valid) pk->flags.valid = 0; if (revoked && !pk->flags.revoked) { pk->flags.revoked = revoked; memcpy (&pk->revoked, &rinfo, sizeof (rinfo)); } if (main_pk->has_expired) pk->has_expired = main_pk->has_expired; } } return; } /* Set the preference list of all keys to those of the primary real * user ID. Note: we use these preferences when we don't know by * which user ID the key has been selected. * fixme: we should keep atoms of commonly used preferences or * use reference counting to optimize the preference lists storage. * FIXME: it might be better to use the intersection of * all preferences. * Do a similar thing for the MDC feature flag. */ prefs = NULL; mdc_feature = 0; for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next) { if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data && k->pkt->pkt.user_id->flags.primary) { prefs = k->pkt->pkt.user_id->prefs; mdc_feature = k->pkt->pkt.user_id->flags.mdc; break; } } for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_PUBLIC_KEY || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) { PKT_public_key *pk = k->pkt->pkt.public_key; if (pk->prefs) xfree (pk->prefs); pk->prefs = copy_prefs (prefs); pk->flags.mdc = mdc_feature; } } } /* See whether the key satisfies any additional requirements specified * in CTX. If so, return the node of an appropriate key or subkey. * Otherwise, return NULL if there was no appropriate key. * * Note that we do not return a reference, i.e. the result must not be * freed using 'release_kbnode'. * * In case the primary key is not required, select a suitable subkey. * We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or * we are in PGP6 or PGP7 mode and PUBKEY_USAGE_SIG is set in * REQ_USAGE. * * If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT * are set in REQ_USAGE, we filter by the key's function. Concretely, * if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only * return a key if it is (at least) either a signing or a * certification key. * * If REQ_USAGE is set, then we reject any keys that are not good * (i.e., valid, not revoked, not expired, etc.). This allows the * getkey functions to be used for plain key listings. * * Sets the matched key's user id field (pk->user_id) to the user id * that matched the low-level search criteria or NULL. * * If R_FLAGS is not NULL set certain flags for more detailed error * reporting. Used flags are: * * - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have * been revoked. * - LOOKUP_NOT_SELECTED :: No suitable key found * * This function needs to handle several different cases: * * 1. No requested usage and no primary key requested * Examples for this case are that we have a keyID to be used * for decrytion or verification. * 2. No usage but primary key requested * This is the case for all functions which work on an * entire keyblock, e.g. for editing or listing * 3. Usage and primary key requested * FIXME * 4. Usage but no primary key requested * FIXME * */ static kbnode_t finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact, unsigned int *r_flags) { kbnode_t k; /* If WANT_EXACT is set, the key or subkey that actually matched the low-level search criteria. */ kbnode_t foundk = NULL; /* The user id (if any) that matched the low-level search criteria. */ PKT_user_id *foundu = NULL; u32 latest_date; kbnode_t latest_key; PKT_public_key *pk; int req_prim; u32 curtime = make_timestamp (); if (r_flags) *r_flags = 0; #define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT) req_usage &= USAGE_MASK; /* Request the primary if we're certifying another key, and also if * signing data while --pgp6 or --pgp7 is on since pgp 6 and 7 do * not understand signatures made by a signing subkey. PGP 8 does. */ req_prim = ((req_usage & PUBKEY_USAGE_CERT) || ((PGP6 || PGP7) && (req_usage & PUBKEY_USAGE_SIG))); log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); /* For an exact match mark the primary or subkey that matched the low-level search criteria. */ if (want_exact) { for (k = keyblock; k; k = k->next) { if ((k->flag & 1)) { log_assert (k->pkt->pkttype == PKT_PUBLIC_KEY || k->pkt->pkttype == PKT_PUBLIC_SUBKEY); foundk = k; pk = k->pkt->pkt.public_key; pk->flags.exact = 1; break; } } } /* Get the user id that matched that low-level search criteria. */ for (k = keyblock; k; k = k->next) { if ((k->flag & 2)) { log_assert (k->pkt->pkttype == PKT_USER_ID); foundu = k->pkt->pkt.user_id; break; } } if (DBG_LOOKUP) log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n", (ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL), foundk ? "one" : "all", req_usage); if (!req_usage) { latest_key = foundk ? foundk : keyblock; goto found; } latest_date = 0; latest_key = NULL; /* Set LATEST_KEY to the latest (the one with the most recent * timestamp) good (valid, not revoked, not expired, etc.) subkey. * * Don't bother if we are only looking for a primary key or we need * an exact match and the exact match is not a subkey. */ if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY)) ; else { kbnode_t nextk; int n_subkeys = 0; int n_revoked_or_expired = 0; /* Either start a loop or check just this one subkey. */ for (k = foundk ? foundk : keyblock; k; k = nextk) { if (foundk) { /* If FOUNDK is not NULL, then only consider that exact key, i.e., don't iterate. */ nextk = NULL; } else nextk = k->next; if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY) continue; pk = k->pkt->pkt.public_key; if (DBG_LOOKUP) log_debug ("\tchecking subkey %08lX\n", (ulong) keyid_from_pk (pk, NULL)); if (!pk->flags.valid) { if (DBG_LOOKUP) log_debug ("\tsubkey not valid\n"); continue; } if (!((pk->pubkey_usage & USAGE_MASK) & req_usage)) { if (DBG_LOOKUP) log_debug ("\tusage does not match: want=%x have=%x\n", req_usage, pk->pubkey_usage); continue; } n_subkeys++; if (pk->flags.revoked) { if (DBG_LOOKUP) log_debug ("\tsubkey has been revoked\n"); n_revoked_or_expired++; continue; } if (pk->has_expired) { if (DBG_LOOKUP) log_debug ("\tsubkey has expired\n"); n_revoked_or_expired++; continue; } if (pk->timestamp > curtime && !opt.ignore_valid_from) { if (DBG_LOOKUP) log_debug ("\tsubkey not yet valid\n"); continue; } if (DBG_LOOKUP) log_debug ("\tsubkey might be fine\n"); /* In case a key has a timestamp of 0 set, we make sure that it is used. A better change would be to compare ">=" but that might also change the selected keys and is as such a more intrusive change. */ if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) { latest_date = pk->timestamp; latest_key = k; } } if (n_subkeys == n_revoked_or_expired && r_flags) *r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED; } /* Check if the primary key is ok (valid, not revoke, not expire, * matches requested usage) if: * * - we didn't find an appropriate subkey and we're not doing an * exact search, * * - we're doing an exact match and the exact match was the * primary key, or, * * - we're just considering the primary key. */ if ((!latest_key && !want_exact) || foundk == keyblock || req_prim) { if (DBG_LOOKUP && !foundk && !req_prim) log_debug ("\tno suitable subkeys found - trying primary\n"); pk = keyblock->pkt->pkt.public_key; if (!pk->flags.valid) { if (DBG_LOOKUP) log_debug ("\tprimary key not valid\n"); } else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage)) { if (DBG_LOOKUP) log_debug ("\tprimary key usage does not match: " "want=%x have=%x\n", req_usage, pk->pubkey_usage); } else if (pk->flags.revoked) { if (DBG_LOOKUP) log_debug ("\tprimary key has been revoked\n"); } else if (pk->has_expired) { if (DBG_LOOKUP) log_debug ("\tprimary key has expired\n"); } else /* Okay. */ { if (DBG_LOOKUP) log_debug ("\tprimary key may be used\n"); latest_key = keyblock; } } if (!latest_key) { if (DBG_LOOKUP) log_debug ("\tno suitable key found - giving up\n"); if (r_flags) *r_flags |= LOOKUP_NOT_SELECTED; return NULL; /* Not found. */ } found: if (DBG_LOOKUP) log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL)); if (latest_key) { pk = latest_key->pkt->pkt.public_key; free_user_id (pk->user_id); pk->user_id = scopy_user_id (foundu); } if (latest_key != keyblock && opt.verbose) { char *tempkeystr = xstrdup (keystr_from_pk (latest_key->pkt->pkt.public_key)); log_info (_("using subkey %s instead of primary key %s\n"), tempkeystr, keystr_from_pk (keyblock->pkt->pkt.public_key)); xfree (tempkeystr); } cache_user_id (keyblock); return latest_key ? latest_key : keyblock; /* Found. */ } /* Print a KEY_CONSIDERED status line. */ static void print_status_key_considered (kbnode_t keyblock, unsigned int flags) { char hexfpr[2*MAX_FINGERPRINT_LEN + 1]; kbnode_t node; char flagbuf[20]; if (!is_status_enabled ()) return; for (node=keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) break; if (!node) { log_error ("%s: keyblock w/o primary key\n", __func__); return; } hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr); snprintf (flagbuf, sizeof flagbuf, " %u", flags); write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL); } /* A high-level function to lookup keys. This function builds on top of the low-level keydb API. It first searches the database using the description stored in CTX->ITEMS, then it filters the results using CTX and, finally, if WANT_SECRET is set, it ignores any keys for which no secret key is available. Unlike the low-level search functions, this function also merges all of the self-signed data into the keys, subkeys and user id packets (see the merge_selfsigs for details). On success the key's keyblock is stored at *RET_KEYBLOCK, and the specific subkey is stored at *RET_FOUND_KEY. Note that we do not return a reference in *RET_FOUND_KEY, i.e. the result must not be freed using 'release_kbnode', and it is only valid until *RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not NULL, then RET_KEYBLOCK must not be NULL. */ static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret, kbnode_t *ret_keyblock, kbnode_t *ret_found_key) { int rc; int no_suitable_key = 0; KBNODE keyblock = NULL; KBNODE found_key = NULL; unsigned int infoflags; log_assert (ret_found_key == NULL || ret_keyblock != NULL); if (ret_keyblock) *ret_keyblock = NULL; for (;;) { rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL); if (rc) break; /* If we are iterating over the entire database, then we need to change from KEYDB_SEARCH_MODE_FIRST, which does an implicit reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next record. */ if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST) ctx->items->mode = KEYDB_SEARCH_MODE_NEXT; rc = keydb_get_keyblock (ctx->kr_handle, &keyblock); if (rc) { log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); goto skip; } if (want_secret && agent_probe_any_secret_key (NULL, keyblock)) goto skip; /* No secret key available. */ /* Warning: node flag bits 0 and 1 should be preserved by * merge_selfsigs. */ merge_selfsigs (ctrl, keyblock); found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact, &infoflags); print_status_key_considered (keyblock, infoflags); if (found_key) { no_suitable_key = 0; goto found; } else { no_suitable_key = 1; } skip: /* Release resources and continue search. */ release_kbnode (keyblock); keyblock = NULL; /* The keyblock cache ignores the current "file position". Thus, if we request the next result and the cache matches (and it will since it is what we just looked for), we'll get the same entry back! We can avoid this infinite loop by disabling the cache. */ keydb_disable_caching (ctx->kr_handle); } found: if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); if (!rc) { if (ret_keyblock) { *ret_keyblock = keyblock; /* Return the keyblock. */ keyblock = NULL; } } else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key) rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY; else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY; release_kbnode (keyblock); if (ret_found_key) { if (! rc) *ret_found_key = found_key; else *ret_found_key = NULL; } return rc; } /* Enumerate some secret keys (specifically, those specified with * --default-key and --try-secret-key). Use the following procedure: * * 1) Initialize a void pointer to NULL * 2) Pass a reference to this pointer to this function (content) * and provide space for the secret key (sk) * 3) Call this function as long as it does not return an error (or * until you are done). The error code GPG_ERR_EOF indicates the * end of the listing. * 4) Call this function a last time with SK set to NULL, * so that can free it's context. * * In pseudo-code: * * void *ctx = NULL; * PKT_public_key *sk = xmalloc_clear (sizeof (*sk)); * * while ((err = enum_secret_keys (&ctx, sk))) * { // Process SK. * if (done) * break; * free_public_key (sk); * sk = xmalloc_clear (sizeof (*sk)); * } * * // Release any resources used by CTX. * enum_secret_keys (&ctx, NULL); * free_public_key (sk); * * if (gpg_err_code (err) != GPG_ERR_EOF) * ; // An error occurred. */ gpg_error_t enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *sk) { gpg_error_t err = 0; const char *name; kbnode_t keyblock; struct { int eof; int state; strlist_t sl; kbnode_t keyblock; kbnode_t node; getkey_ctx_t ctx; } *c = *context; if (!c) { /* Make a new context. */ c = xtrycalloc (1, sizeof *c); if (!c) return gpg_error_from_syserror (); *context = c; } if (!sk) { /* Free the context. */ release_kbnode (c->keyblock); getkey_end (ctrl, c->ctx); xfree (c); *context = NULL; return 0; } if (c->eof) return gpg_error (GPG_ERR_EOF); for (;;) { /* Loop until we have a keyblock. */ while (!c->keyblock) { /* Loop over the list of secret keys. */ do { name = NULL; keyblock = NULL; switch (c->state) { case 0: /* First try to use the --default-key. */ name = parse_def_secret_key (ctrl); c->state = 1; break; case 1: /* Init list of keys to try. */ c->sl = opt.secret_keys_to_try; c->state++; break; case 2: /* Get next item from list. */ if (c->sl) { name = c->sl->d; c->sl = c->sl->next; } else c->state++; break; case 3: /* Init search context to enum all secret keys. */ err = getkey_bynames (ctrl, &c->ctx, NULL, NULL, 1, &keyblock); if (err) { release_kbnode (keyblock); keyblock = NULL; getkey_end (ctrl, c->ctx); c->ctx = NULL; } c->state++; break; case 4: /* Get next item from the context. */ if (c->ctx) { err = getkey_next (ctrl, c->ctx, NULL, &keyblock); if (err) { release_kbnode (keyblock); keyblock = NULL; getkey_end (ctrl, c->ctx); c->ctx = NULL; } } else c->state++; break; default: /* No more names to check - stop. */ c->eof = 1; return gpg_error (GPG_ERR_EOF); } } while ((!name || !*name) && !keyblock); if (keyblock) c->node = c->keyblock = keyblock; else { err = getkey_byname (ctrl, NULL, NULL, name, 1, &c->keyblock); if (err) { /* getkey_byname might return a keyblock even in the error case - I have not checked. Thus better release it. */ release_kbnode (c->keyblock); c->keyblock = NULL; } else c->node = c->keyblock; } } /* Get the next key from the current keyblock. */ for (; c->node; c->node = c->node->next) { if (c->node->pkt->pkttype == PKT_PUBLIC_KEY || c->node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { copy_public_key (sk, c->node->pkt->pkt.public_key); c->node = c->node->next; return 0; /* Found. */ } } /* Dispose the keyblock and continue. */ release_kbnode (c->keyblock); c->keyblock = NULL; } } /********************************************* *********** User ID printing helpers ******* *********************************************/ /* Return a string with a printable representation of the user_id. * this string must be freed by xfree. */ static char * get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode, size_t *r_len) { user_id_db_t r; keyid_list_t a; int pass = 0; char *p; /* Try it two times; second pass reads from the database. */ do { for (r = user_id_db; r; r = r->next) { for (a = r->keyids; a; a = a->next) { if (a->keyid[0] == keyid[0] && a->keyid[1] == keyid[1]) { if (mode == 2) { /* An empty string as user id is possible. Make sure that the malloc allocates one byte and does not bail out. */ p = xmalloc (r->len? r->len : 1); memcpy (p, r->name, r->len); if (r_len) *r_len = r->len; } else { if (mode) p = xasprintf ("%08lX%08lX %.*s", (ulong) keyid[0], (ulong) keyid[1], r->len, r->name); else p = xasprintf ("%s %.*s", keystr (keyid), r->len, r->name); if (r_len) *r_len = strlen (p); } return p; } } } } while (++pass < 2 && !get_pubkey (ctrl, NULL, keyid)); if (mode == 2) p = xstrdup (user_id_not_found_utf8 ()); else if (mode) p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]); else p = xasprintf ("%s [?]", keystr (keyid)); if (r_len) *r_len = strlen (p); return p; } char * get_user_id_string_native (ctrl_t ctrl, u32 * keyid) { char *p = get_user_id_string (ctrl, keyid, 0, NULL); char *p2 = utf8_to_native (p, strlen (p), 0); xfree (p); return p2; } char * get_long_user_id_string (ctrl_t ctrl, u32 * keyid) { return get_user_id_string (ctrl, keyid, 1, NULL); } /* Please try to use get_user_byfpr instead of this one. */ char * get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn) { return get_user_id_string (ctrl, keyid, 2, rn); } /* Please try to use get_user_id_byfpr_native instead of this one. */ char * get_user_id_native (ctrl_t ctrl, u32 *keyid) { size_t rn; char *p = get_user_id (ctrl, keyid, &rn); char *p2 = utf8_to_native (p, rn, 0); xfree (p); return p2; } /* Return the user id for a key designated by its fingerprint, FPR, which must be MAX_FINGERPRINT_LEN bytes in size. Note: the returned string, which must be freed using xfree, may not be NUL terminated. To determine the length of the string, you must use *RN. */ char * get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn) { user_id_db_t r; char *p; int pass = 0; /* Try it two times; second pass reads from the database. */ do { for (r = user_id_db; r; r = r->next) { keyid_list_t a; for (a = r->keyids; a; a = a->next) { if (!memcmp (a->fpr, fpr, MAX_FINGERPRINT_LEN)) { /* An empty string as user id is possible. Make sure that the malloc allocates one byte and does not bail out. */ p = xmalloc (r->len? r->len : 1); memcpy (p, r->name, r->len); *rn = r->len; return p; } } } } while (++pass < 2 && !get_pubkey_byfprint (ctrl, NULL, NULL, fpr, MAX_FINGERPRINT_LEN)); p = xstrdup (user_id_not_found_utf8 ()); *rn = strlen (p); return p; } /* Like get_user_id_byfpr, but convert the string to the native encoding. The returned string needs to be freed. Unlike get_user_id_byfpr, the returned string is NUL terminated. */ char * get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr) { size_t rn; char *p = get_user_id_byfpr (ctrl, fpr, &rn); char *p2 = utf8_to_native (p, rn, 0); xfree (p); return p2; } /* Return the database handle used by this context. The context still owns the handle. */ KEYDB_HANDLE get_ctx_handle (GETKEY_CTX ctx) { return ctx->kr_handle; } static void free_akl (struct akl *akl) { if (! akl) return; if (akl->spec) free_keyserver_spec (akl->spec); xfree (akl); } void release_akl (void) { while (opt.auto_key_locate) { struct akl *akl2 = opt.auto_key_locate; opt.auto_key_locate = opt.auto_key_locate->next; free_akl (akl2); } } /* Returns false on error. */ int parse_auto_key_locate (char *options) { char *tok; while ((tok = optsep (&options))) { struct akl *akl, *check, *last = NULL; int dupe = 0; if (tok[0] == '\0') continue; akl = xmalloc_clear (sizeof (*akl)); if (ascii_strcasecmp (tok, "clear") == 0) { xfree (akl); free_akl (opt.auto_key_locate); opt.auto_key_locate = NULL; continue; } else if (ascii_strcasecmp (tok, "nodefault") == 0) akl->type = AKL_NODEFAULT; else if (ascii_strcasecmp (tok, "local") == 0) akl->type = AKL_LOCAL; else if (ascii_strcasecmp (tok, "ldap") == 0) akl->type = AKL_LDAP; else if (ascii_strcasecmp (tok, "keyserver") == 0) akl->type = AKL_KEYSERVER; else if (ascii_strcasecmp (tok, "cert") == 0) akl->type = AKL_CERT; else if (ascii_strcasecmp (tok, "pka") == 0) akl->type = AKL_PKA; else if (ascii_strcasecmp (tok, "dane") == 0) akl->type = AKL_DANE; else if (ascii_strcasecmp (tok, "wkd") == 0) akl->type = AKL_WKD; else if ((akl->spec = parse_keyserver_uri (tok, 1))) akl->type = AKL_SPEC; else { free_akl (akl); return 0; } /* We must maintain the order the user gave us */ for (check = opt.auto_key_locate; check; last = check, check = check->next) { /* Check for duplicates */ if (check->type == akl->type && (akl->type != AKL_SPEC || (akl->type == AKL_SPEC && strcmp (check->spec->uri, akl->spec->uri) == 0))) { dupe = 1; free_akl (akl); break; } } if (!dupe) { if (last) last->next = akl; else opt.auto_key_locate = akl; } } return 1; } /* Returns true if a secret key is available for the public key with key id KEYID; returns false if not. This function ignores legacy keys. Note: this is just a fast check and does not tell us whether the secret key is valid; this check merely indicates whether there is some secret key with the specified key id. */ int have_secret_key_with_kid (u32 *keyid) { gpg_error_t err; KEYDB_HANDLE kdbhd; KEYDB_SEARCH_DESC desc; kbnode_t keyblock; kbnode_t node; int result = 0; kdbhd = keydb_new (); if (!kdbhd) return 0; memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_LONG_KID; desc.u.kid[0] = keyid[0]; desc.u.kid[1] = keyid[1]; while (!result) { err = keydb_search (kdbhd, &desc, 1, NULL); if (err) break; err = keydb_get_keyblock (kdbhd, &keyblock); if (err) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); break; } for (node = keyblock; node; node = node->next) { /* Bit 0 of the flags is set if the search found the key using that key or subkey. Note: a search will only ever match a single key or subkey. */ if ((node->flag & 1)) { log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY); if (!agent_probe_secret_key (NULL, node->pkt->pkt.public_key)) result = 1; /* Secret key available. */ else result = 0; break; } } release_kbnode (keyblock); } keydb_release (kdbhd); return result; } diff --git a/g10/gpg.c b/g10/gpg.c index d193cb21d..ecddb20d0 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1,5360 +1,5360 @@ /* gpg.c - The GnuPG utility (main for gpg) * Copyright (C) 1998-2011 Free Software Foundation, Inc. * Copyright (C) 1997-2017 Werner Koch * Copyright (C) 2015-2017 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <unistd.h> #ifdef HAVE_STAT #include <sys/stat.h> /* for stat() */ #endif #include <fcntl.h> #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include <winsock2.h> # endif # include <windows.h> #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include <assuan.h> #include "../common/iobuf.h" #include "../common/util.h" #include "packet.h" #include "../common/membuf.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/status.h" #include "keyserver-internal.h" #include "exec.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "call-dirmngr.h" #include "tofu.h" #include "../common/init.h" #include "../common/mbox-util.h" #include "../common/shareddefs.h" #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 #endif #else #define MY_O_BINARY 0 #endif #ifdef __MINGW32__ int _dowildcard = -1; #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', oRecipientFile = 'f', oHiddenRecipientFile = 'F', oInteractive = 'i', aListKeys = 'k', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', oHiddenRecipient = 'R', aSign = 's', oTextmodeShort= 't', oLocalUser = 'u', oVerbose = 'v', oCompress = 'z', oSetNotation = 'N', aListSecretKeys = 'K', oBatch = 500, oMaxOutput, oInputSizeHint, oSigNotation, oCertNotation, oShowNotation, oNoShowNotation, aEncrFiles, aEncrSym, aDecryptFiles, aClearsign, aStore, aQuickKeygen, aFullKeygen, aKeygen, aSignEncr, aSignEncrSym, aSignSym, aSignKey, aLSignKey, aQuickSignKey, aQuickLSignKey, aQuickAddUid, aQuickAddKey, aQuickRevUid, aQuickSetExpire, aQuickSetPrimaryUid, aListConfig, aListGcryptConfig, aGPGConfList, aGPGConfTest, aListPackets, aEditKey, aDeleteKeys, aDeleteSecretKeys, aDeleteSecretAndPublicKeys, aImport, aFastImport, aVerify, aVerifyFiles, aListSigs, aSendKeys, aRecvKeys, aLocateKeys, aSearchKeys, aRefreshKeys, aFetchKeys, aExport, aExportSecret, aExportSecretSub, aExportSshKey, aCheckKeys, aGenRevoke, aDesigRevoke, aPrimegen, aPrintMD, aPrintMDs, aCheckTrustDB, aUpdateTrustDB, aFixTrustDB, aListTrustDB, aListTrustPath, aExportOwnerTrust, aImportOwnerTrust, aDeArmor, aEnArmor, aGenRandom, aRebuildKeydbCaches, aCardStatus, aCardEdit, aChangePIN, aPasswd, aServer, aTOFUPolicy, oMimemode, oTextmode, oNoTextmode, oExpert, oNoExpert, oDefSigExpire, oAskSigExpire, oNoAskSigExpire, oDefCertExpire, oAskCertExpire, oNoAskCertExpire, oDefCertLevel, oMinCertLevel, oAskCertLevel, oNoAskCertLevel, oFingerprint, oWithFingerprint, oWithSubkeyFingerprint, oWithICAOSpelling, oWithKeygrip, oWithSecret, oWithWKDHash, oWithColons, oWithKeyData, oWithTofuInfo, oWithSigList, oWithSigCheck, oAnswerYes, oAnswerNo, oKeyring, oPrimaryKeyring, oSecretKeyring, oShowKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oTrySecretKey, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugIOLBF, oStatusFD, oStatusFile, oAttributeFD, oAttributeFile, oEmitVersion, oNoEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oCompliance, oGnuPG, oRFC2440, oRFC4880, oRFC4880bis, oOpenPGP, oPGP6, oPGP7, oPGP8, oDE_VS, oRFC2440Text, oNoRFC2440Text, oCipherAlgo, oDigestAlgo, oCertDigestAlgo, oCompressAlgo, oCompressLevel, oBZ2CompressLevel, oBZ2DecompressLowmem, oPassphrase, oPassphraseFD, oPassphraseFile, oPassphraseRepeat, oPinentryMode, oCommandFD, oCommandFile, oQuickRandom, oNoVerbose, oTrustDBName, oNoSecmemWarn, oRequireSecmem, oNoRequireSecmem, oNoPermissionWarn, oNoMDCWarn, oNoArmor, oNoDefKeyring, oNoKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oSkipVerify, oSkipHiddenRecipients, oNoSkipHiddenRecipients, oAlwaysTrust, oTrustModel, oForceOwnertrust, oSetFilename, oForYourEyesOnly, oNoForYourEyesOnly, oSetPolicyURL, oSigPolicyURL, oCertPolicyURL, oShowPolicyURL, oNoShowPolicyURL, oSigKeyserverURL, oUseEmbeddedFilename, oNoUseEmbeddedFilename, oComment, oDefaultComment, oNoComments, oThrowKeyids, oNoThrowKeyids, oShowPhotos, oNoShowPhotos, oPhotoViewer, oForceMDC, oNoForceMDC, oDisableMDC, oNoDisableMDC, oS2KMode, oS2KDigest, oS2KCipher, oS2KCount, oDisplayCharset, oNotDashEscaped, oEscapeFrom, oNoEscapeFrom, oLockOnce, oLockMultiple, oLockNever, oKeyServer, oKeyServerOptions, oImportOptions, oImportFilter, oExportOptions, oExportFilter, oListOptions, oVerifyOptions, oTempDir, oExecPath, oEncryptTo, oHiddenEncryptTo, oNoEncryptTo, oEncryptToDefaultKey, oLoggerFD, oLoggerFile, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, oDisablePubkeyAlgo, oAllowNonSelfsignedUID, oNoAllowNonSelfsignedUID, oAllowFreeformUID, oNoAllowFreeformUID, oAllowSecretKeyImport, oEnableSpecialFilenames, oNoLiteral, oSetFilesize, oHonorHttpProxy, oFastListMode, oListOnly, oIgnoreTimeConflict, oIgnoreValidFrom, oIgnoreCrcError, oIgnoreMDCError, oShowSessionKey, oOverrideSessionKey, oOverrideSessionKeyFD, oNoRandomSeedFile, oAutoKeyRetrieve, oNoAutoKeyRetrieve, oUseAgent, oNoUseAgent, oGpgAgentInfo, oMergeOnly, oTryAllSecrets, oTrustedKey, oNoExpensiveTrustChecks, oFixedListMode, oLegacyListMode, oNoSigCache, oAutoCheckTrustDB, oNoAutoCheckTrustDB, oPreservePermissions, oDefaultPreferenceList, oDefaultKeyserverURL, oPersonalCipherPreferences, oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, oDirmngrProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oGroup, oUnGroup, oNoGroups, oStrict, oNoStrict, oMangleDosFilenames, oNoMangleDosFilenames, oEnableProgressFilter, oMultifile, oKeyidFormat, oExitOnStatusWriteError, oLimitCardInsertTries, oReaderPort, octapiDriver, opcscDriver, oDisableCCID, oRequireCrossCert, oNoRequireCrossCert, oAutoKeyLocate, oNoAutoKeyLocate, oAllowMultisigVerification, oEnableLargeRSA, oDisableLargeRSA, oEnableDSA2, oDisableDSA2, oAllowMultipleMessages, oNoAllowMultipleMessages, oAllowWeakDigestAlgos, oFakedSystemTime, oNoAutostart, oPrintPKARecords, oPrintDANERecords, oTOFUDefaultPolicy, oTOFUDBFormat, oDefaultNewKeyAlgo, oWeakDigest, oUnwrap, oOnlySignTextIDs, oDisableSignerUID, oSender, oNoop }; static ARGPARSE_OPTS opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aSign, "sign", N_("make a signature")), ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")), ARGPARSE_c (aClearsign, "clearsign", "@"), ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), ARGPARSE_c (aEncrFiles, "encrypt-files", "@"), ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")), ARGPARSE_c (aStore, "store", "@"), ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"), ARGPARSE_c (aVerify, "verify" , N_("verify a signature")), ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ), ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), ARGPARSE_c (aListKeys, "list-public-keys", "@" ), ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")), ARGPARSE_c (aListSigs, "list-sigs", "@"), ARGPARSE_c (aCheckKeys, "check-signatures", N_("list and check key signatures")), ARGPARSE_c (aCheckKeys, "check-sigs", "@"), ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")), ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")), ARGPARSE_c (aKeygen, "gen-key", "@"), ARGPARSE_c (aQuickKeygen, "quick-generate-key" , N_("quickly generate a new key pair")), ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"), ARGPARSE_c (aQuickAddUid, "quick-add-uid", N_("quickly add a new user-id")), ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"), ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"), ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), ARGPARSE_c (aQuickRevUid, "quick-revoke-uid", N_("quickly revoke a user-id")), ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"), ARGPARSE_c (aQuickSetExpire, "quick-set-expire", N_("quickly set a new expiration date")), ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"), ARGPARSE_c (aFullKeygen, "full-generate-key" , N_("full featured key pair generation")), ARGPARSE_c (aFullKeygen, "full-gen-key", "@"), ARGPARSE_c (aGenRevoke, "generate-revocation", N_("generate a revocation certificate")), ARGPARSE_c (aGenRevoke, "gen-revoke", "@"), ARGPARSE_c (aDeleteKeys,"delete-keys", N_("remove keys from the public keyring")), ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys", N_("remove keys from the secret keyring")), ARGPARSE_c (aQuickSignKey, "quick-sign-key" , N_("quickly sign a key")), ARGPARSE_c (aQuickLSignKey, "quick-lsign-key", N_("quickly sign a key locally")), ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")), ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")), ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")), ARGPARSE_c (aEditKey, "key-edit" ,"@"), ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), ARGPARSE_c (aPasswd, "passwd", "@"), ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"), ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ), ARGPARSE_c (aExport, "export" , N_("export keys") ), ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ), ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ), ARGPARSE_c (aRecvKeys, "recv-keys" , "@"), ARGPARSE_c (aSearchKeys, "search-keys" , N_("search for keys on a keyserver") ), ARGPARSE_c (aRefreshKeys, "refresh-keys", N_("update all keys from a keyserver")), ARGPARSE_c (aLocateKeys, "locate-keys", "@"), ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ), ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ), ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ), ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ), ARGPARSE_c (aImport, "import", N_("import/merge keys")), ARGPARSE_c (aFastImport, "fast-import", "@"), #ifdef ENABLE_CARD_SUPPORT ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")), ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")), ARGPARSE_c (aCardEdit, "card-edit", "@"), ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")), #endif ARGPARSE_c (aListConfig, "list-config", "@"), ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ), ARGPARSE_c (aListPackets, "list-packets","@"), #ifndef NO_TRUST_MODELS ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"), ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"), ARGPARSE_c (aUpdateTrustDB,"update-trustdb", N_("update the trust database")), ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"), ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"), #endif ARGPARSE_c (aDeArmor, "dearmor", "@"), ARGPARSE_c (aDeArmor, "dearmour", "@"), ARGPARSE_c (aEnArmor, "enarmor", "@"), ARGPARSE_c (aEnArmor, "enarmour", "@"), ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")), ARGPARSE_c (aPrimegen, "gen-prime", "@" ), ARGPARSE_c (aGenRandom,"gen-random", "@" ), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aTOFUPolicy, "tofu-policy", N_("|VALUE|set the TOFU policy for a key")), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_n (oArmor, "armour", "@"), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"), ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"), ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"), ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */ ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"), ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"), ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), ARGPARSE_s_s (oTempDir, "temp-directory", "@"), ARGPARSE_s_s (oExecPath, "exec-path", "@"), ARGPARSE_s_s (oEncryptTo, "encrypt-to", "@"), ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"), ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"), ARGPARSE_s_s (oLocalUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oSender, "sender", "@"), ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"), ARGPARSE_s_i (oCompress, NULL, N_("|N|set compress level to N (0 disables)")), ARGPARSE_s_i (oCompressLevel, "compress-level", "@"), ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"), ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"), ARGPARSE_s_n (oMimemode, "mimemode", "@"), ARGPARSE_s_n (oTextmodeShort, NULL, "@"), ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")), ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"), ARGPARSE_s_n (oExpert, "expert", "@"), ARGPARSE_s_n (oNoExpert, "no-expert", "@"), ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"), ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"), ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"), ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"), ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"), ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"), ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"), ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"), ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_p_u (oMaxOutput, "max-output", "@"), ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", "@"), ARGPARSE_s_n (oNoTTY, "no-tty", "@"), ARGPARSE_s_n (oForceMDC, "force-mdc", "@"), ARGPARSE_s_n (oNoForceMDC, "no-force-mdc", "@"), ARGPARSE_s_n (oDisableMDC, "disable-mdc", "@"), ARGPARSE_s_n (oNoDisableMDC, "no-disable-mdc", "@"), ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", "@"), ARGPARSE_s_n (oAnswerNo, "no", "@"), ARGPARSE_s_s (oKeyring, "keyring", "@"), ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"), ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"), ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"), ARGPARSE_s_s (oDefaultKey, "default-key", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"), ARGPARSE_s_s (oImportOptions, "import-options", "@"), ARGPARSE_s_s (oImportFilter, "import-filter", "@"), ARGPARSE_s_s (oExportOptions, "export-options", "@"), ARGPARSE_s_s (oExportFilter, "export-filter", "@"), ARGPARSE_s_s (oListOptions, "list-options", "@"), ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"), ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"), ARGPARSE_s_s (oDisplayCharset, "charset", "@"), ARGPARSE_s_s (oOptions, "options", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", "@"), ARGPARSE_s_s (oStatusFile, "status-file", "@"), ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"), ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"), ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"), ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"), ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ), ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"), ARGPARSE_s_s (oLoadExtension, "load-extension", "@"), /* Dummy. */ ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_s_n (oGnuPG, "gnupg", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"), ARGPARSE_s_n (oRFC2440, "rfc2440", "@"), ARGPARSE_s_n (oRFC4880, "rfc4880", "@"), ARGPARSE_s_n (oRFC4880bis, "rfc4880bis", "@"), ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")), ARGPARSE_s_n (oPGP6, "pgp6", "@"), ARGPARSE_s_n (oPGP7, "pgp7", "@"), ARGPARSE_s_n (oPGP8, "pgp8", "@"), ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"), ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"), ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"), ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"), ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"), ARGPARSE_s_i (oS2KCount, "s2k-count", "@"), ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"), ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"), ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"), ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"), ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */ ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"), ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"), ARGPARSE_s_n (oShowPhotos, "show-photos", "@"), ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"), ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"), ARGPARSE_s_s (oSetNotation, "set-notation", "@"), ARGPARSE_s_s (oSigNotation, "sig-notation", "@"), ARGPARSE_s_s (oCertNotation, "cert-notation", "@"), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_group (303, N_("@\nExamples:\n\n" " -se -r Bob [file] sign and encrypt for user Bob\n" " --clear-sign [file] make a clear text signature\n" " --detach-sign [file] make a detached signature\n" " --list-keys [names] show keys\n" " --fingerprint [names] show fingerprints\n")), /* More hidden commands and options. */ ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */ #ifndef NO_TRUST_MODELS ARGPARSE_c (aListTrustDB, "list-trustdb", "@"), #endif /* Not yet used: ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */ ARGPARSE_c (aDeleteSecretAndPublicKeys, "delete-secret-and-public-keys", "@"), ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"), ARGPARSE_s_s (oPassphrase, "passphrase", "@"), ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"), ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"), ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), ARGPARSE_s_i (oCommandFD, "command-fd", "@"), ARGPARSE_s_s (oCommandFile, "command-file", "@"), ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), #ifndef NO_TRUST_MODELS ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"), ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"), ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"), ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"), #endif ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"), ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"), ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"), ARGPARSE_s_n (oNoMDCWarn, "no-mdc-warning", "@"), ARGPARSE_s_n (oNoArmor, "no-armor", "@"), ARGPARSE_s_n (oNoArmor, "no-armour", "@"), ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_n (oNoOptions, "no-options", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_n (oNoBatch, "no-batch", "@"), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"), ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"), ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"), ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */ ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */ ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */ ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"), ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */ #ifndef NO_TRUST_MODELS ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"), #endif ARGPARSE_s_s (oTrustModel, "trust-model", "@"), ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"), ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"), ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"), ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"), ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"), ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"), ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"), ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"), ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"), ARGPARSE_s_n (oShowNotation, "show-notation", "@"), ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"), ARGPARSE_s_s (oComment, "comment", "@"), ARGPARSE_s_n (oDefaultComment, "default-comment", "@"), ARGPARSE_s_n (oNoComments, "no-comments", "@"), ARGPARSE_s_n (oEmitVersion, "emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */ ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"), ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"), ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"), ARGPARSE_s_n (oLockOnce, "lock-once", "@"), ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"), ARGPARSE_s_n (oLockNever, "lock-never", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_s (oLoggerFile, "log-file", "@"), ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */ ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"), ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"), ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"), ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"), ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"), ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"), ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"), ARGPARSE_s_n (oNoLiteral, "no-literal", "@"), ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"), ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"), ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"), ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"), ARGPARSE_s_n (oListOnly, "list-only", "@"), ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"), ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"), ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"), ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"), ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"), ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"), ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"), ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"), ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"), ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ), ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"), ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"), ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"), ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"), ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"), ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"), ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-preferences", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_s (oWeakDigest, "weak-digest","@"), ARGPARSE_s_n (oUnwrap, "unwrap", "@"), ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"), /* Aliases. I constantly mistype these, and assume other people do as well. */ ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"), ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-prefs", "@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), 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 (oGroup, "group", "@"), ARGPARSE_s_s (oUnGroup, "ungroup", "@"), ARGPARSE_s_n (oNoGroups, "no-groups", "@"), ARGPARSE_s_n (oStrict, "strict", "@"), ARGPARSE_s_n (oNoStrict, "no-strict", "@"), ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"), ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"), ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"), ARGPARSE_s_n (oMultifile, "multifile", "@"), ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"), ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"), ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"), ARGPARSE_s_n (oAllowMultisigVerification, "allow-multisig-verification", "@"), ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"), ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"), ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"), ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"), ARGPARSE_s_n (oAllowMultipleMessages, "allow-multiple-messages", "@"), ARGPARSE_s_n (oNoAllowMultipleMessages, "no-allow-multiple-messages", "@"), ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"), ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"), /* These two are aliases to help users of the PGP command line product use gpg with minimal pain. Many commands are common already as they seem to have borrowed commands from us. Now I'm returning the favor. */ ARGPARSE_s_s (oLocalUser, "sign-with", "@"), ARGPARSE_s_s (oRecipient, "user", "@"), ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"), ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"), /* New options. Fixme: Should go more to the top. */ ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", "@"), ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), /* Dummy options with warnings. */ ARGPARSE_s_n (oUseAgent, "use-agent", "@"), ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"), ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"), ARGPARSE_s_s (oReaderPort, "reader-port", "@"), ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"), ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"), ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"), ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"), /* Dummy options. */ ARGPARSE_s_n (oNoop, "sk-comments", "@"), ARGPARSE_s_n (oNoop, "no-sk-comments", "@"), ARGPARSE_s_n (oNoop, "compress-keys", "@"), ARGPARSE_s_n (oNoop, "compress-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v4-certs", "@"), ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_PACKET_VALUE , "packet" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_FILTER_VALUE , "filter" }, { DBG_IOBUF_VALUE , "iobuf" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_TRUST_VALUE , "trust" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; #ifdef ENABLE_SELINUX_HACKS #define ALWAYS_ADD_KEYRINGS 1 #else #define ALWAYS_ADD_KEYRINGS 0 #endif int g10_errors_seen = 0; static int utf8_strings = 0; static int maybe_setuid = 1; static char *build_list( const char *text, char letter, const char *(*mapf)(int), int (*chkf)(int) ); static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void print_mds( const char *fname, int algo ); static void add_notation_data( const char *string, int which ); static void add_policy_url( const char *string, int which ); static void add_keyserver_url( const char *string, int which ); static void emergency_cleanup (void); static void read_sessionkey_from_fd (int fd); 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 int build_list_pk_test_algo (int algo) { /* Show only one "RSA" string. If RSA_E or RSA_S is available RSA is also available. */ if (algo == PUBKEY_ALGO_RSA_E || algo == PUBKEY_ALGO_RSA_S) return GPG_ERR_DIGEST_ALGO; return openpgp_pk_test_algo (algo); } static const char * build_list_pk_algo_name (int algo) { return openpgp_pk_algo_name (algo); } static int build_list_cipher_test_algo (int algo) { return openpgp_cipher_test_algo (algo); } static const char * build_list_cipher_algo_name (int algo) { return openpgp_cipher_algo_name (algo); } static int build_list_md_test_algo (int algo) { /* By default we do not accept MD5 based signatures. To avoid confusion we do not announce support for it either. */ if (algo == DIGEST_ALGO_MD5) return GPG_ERR_DIGEST_ALGO; return openpgp_md_test_algo (algo); } static const char * build_list_md_algo_name (int algo) { return openpgp_md_algo_name (algo); } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers, *zips, *ver_gcry; const char *p; switch( level ) { case 11: p = "@GPG@ (@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; #ifdef IS_DEVELOPMENT_VERSION case 25: p="NOTE: THIS IS A DEVELOPMENT VERSION!"; break; case 26: p="It is only intended for test purposes and should NOT be"; break; case 27: p="used in a production environment or with production keys!"; break; #endif case 1: case 40: p = _("Usage: @GPG@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @GPG@ [options] [files]\n" "Sign, check, encrypt or decrypt\n" "Default operation depends on the input data\n"); break; case 31: p = "\nHome: "; break; #ifndef __riscos__ case 32: p = gnupg_homedir (); break; #else /* __riscos__ */ case 32: p = make_filename(gnupg_homedir (), NULL); break; #endif /* __riscos__ */ case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!pubkeys) pubkeys = build_list (_("Pubkey: "), 1, build_list_pk_algo_name, build_list_pk_test_algo ); p = pubkeys; break; case 35: if( !ciphers ) ciphers = build_list(_("Cipher: "), 'S', build_list_cipher_algo_name, build_list_cipher_test_algo ); p = ciphers; break; case 36: if( !digests ) digests = build_list(_("Hash: "), 'H', build_list_md_algo_name, build_list_md_test_algo ); p = digests; break; case 37: if( !zips ) zips = build_list(_("Compression: "),'Z', compress_algo_to_string, check_compress_algo); p = zips; break; default: p = NULL; } return p; } static char * build_list (const char *text, char letter, const char * (*mapf)(int), int (*chkf)(int)) { membuf_t mb; int indent; int i, j, len; const char *s; char *string; if (maybe_setuid) gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ indent = utf8_charcount (text, -1); len = 0; init_membuf (&mb, 512); for (i=0; i <= 110; i++ ) { if (!chkf (i) && (s = mapf (i))) { if (mb.len - len > 60) { put_membuf_str (&mb, ",\n"); len = mb.len; for (j=0; j < indent; j++) put_membuf_str (&mb, " "); } else if (mb.len) put_membuf_str (&mb, ", "); else put_membuf_str (&mb, text); put_membuf_str (&mb, s); if (opt.verbose && letter) { char num[20]; if (letter == 1) snprintf (num, sizeof num, " (%d)", i); else snprintf (num, sizeof num, " (%c%d)", letter, i); put_membuf_str (&mb, num); } } } if (mb.len) put_membuf_str (&mb, "\n"); put_membuf (&mb, "", 1); string = get_membuf (&mb, NULL); return xrealloc (string, strlen (string)+1); } static void wrong_args( const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text); g10_exit(2); } static char * make_username( const char *string ) { char *p; if( utf8_strings ) p = xstrdup(string); else p = native_to_utf8( string ); return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* 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_MEMSTAT_VALUE; else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE; else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE |DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_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); g10_exit (2); } if ((opt.debug & DBG_MEMORY_VALUE)) memory_debug_mode = 1; if ((opt.debug & DBG_MEMSTAT_VALUE)) memory_stat_debug_mode = 1; if (DBG_MPI) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (DBG_CRYPTO) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); if ((opt.debug & DBG_IOBUF_VALUE)) iobuf_debug_mode = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } /* We set the screen dimensions for UI purposes. Do not allow screens smaller than 80x24 for the sake of simplicity. */ static void set_screen_dimensions(void) { #ifndef HAVE_W32_SYSTEM char *str; str=getenv("COLUMNS"); if(str) opt.screen_columns=atoi(str); str=getenv("LINES"); if(str) opt.screen_lines=atoi(str); #endif if(opt.screen_columns<80 || opt.screen_columns>255) opt.screen_columns=80; if(opt.screen_lines<24 || opt.screen_lines>255) opt.screen_lines=24; } /* Helper to open a file FNAME either for reading or writing to be used with --status-file etc functions. Not generally useful but it avoids the riscos specific functions and well some Windows people might like it too. Prints an error message and returns -1 on error. On success the file descriptor is returned. */ static int open_info_file (const char *fname, int for_write, int binary) { #ifdef __riscos__ return riscos_fdopenfile (fname, for_write); #elif defined (ENABLE_SELINUX_HACKS) /* We can't allow these even when testing for a secured filename because files to be secured might not yet been secured. This is similar to the option file but in that case it is unlikely that sensitive information may be retrieved by means of error messages. */ (void)fname; (void)for_write; (void)binary; return -1; #else int fd; if (binary) binary = MY_O_BINARY; /* if (is_secured_filename (fname)) */ /* { */ /* fd = -1; */ /* gpg_err_set_errno (EPERM); */ /* } */ /* else */ /* { */ do { if (for_write) fd = open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); else fd = open (fname, O_RDONLY | binary); } while (fd == -1 && errno == EINTR); /* } */ if ( fd == -1) log_error ( for_write? _("can't create '%s': %s\n") : _("can't open '%s': %s\n"), fname, strerror(errno)); return fd; #endif } static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ) { enum cmd_and_opt_values cmd = *ret_cmd; if( !cmd || cmd == new_cmd ) cmd = new_cmd; else if( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if( cmd == aSign && new_cmd == aSym ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aSign ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aEncr ) cmd = aEncrSym; else if( cmd == aEncr && new_cmd == aSym ) cmd = aEncrSym; else if (cmd == aSignEncr && new_cmd == aSym) cmd = aSignEncrSym; else if (cmd == aSignSym && new_cmd == aEncr) cmd = aSignEncrSym; else if (cmd == aEncrSym && new_cmd == aSign) cmd = aSignEncrSym; else if( ( cmd == aSign && new_cmd == aClearsign ) || ( cmd == aClearsign && new_cmd == aSign ) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); g10_exit(2); } *ret_cmd = cmd; } static void add_group(char *string) { char *name,*value; struct groupitem *item; /* Break off the group name */ name=strsep(&string,"="); if(string==NULL) { log_error(_("no = sign found in group definition '%s'\n"),name); return; } trim_trailing_ws(name,strlen(name)); /* Does this group already exist? */ for(item=opt.grouplist;item;item=item->next) if(strcasecmp(item->name,name)==0) break; if(!item) { item=xmalloc(sizeof(struct groupitem)); item->name=name; item->next=opt.grouplist; item->values=NULL; opt.grouplist=item; } /* Break apart the values */ while ((value= strsep(&string," \t"))) { if (*value) add_to_strlist2(&item->values,value,utf8_strings); } } static void rm_group(char *name) { struct groupitem *item,*last=NULL; trim_trailing_ws(name,strlen(name)); for(item=opt.grouplist;item;last=item,item=item->next) { if(strcasecmp(item->name,name)==0) { if(last) last->next=item->next; else opt.grouplist=item->next; free_strlist(item->values); xfree(item); break; } } } /* We need to check three things. 0) The homedir. It must be x00, a directory, and owned by the user. 1) The options/gpg.conf file. Okay unless it or its containing directory is group or other writable or not owned by us. Disable exec in this case. 2) Extensions. Same as #1. Returns true if the item is unsafe. */ static int check_permissions (const char *path, int item) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) static int homedir_cache=-1; char *tmppath,*dir; struct stat statbuf,dirbuf; int homedir=0,ret=0,checkonly=0; int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0; if(opt.no_perm_warn) return 0; log_assert(item==0 || item==1 || item==2); /* extensions may attach a path */ if(item==2 && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(gnupg_libdir (),path,NULL); } else tmppath=xstrdup(path); /* If the item is located in the homedir, but isn't the homedir, don't continue if we already checked the homedir itself. This is to avoid user confusion with an extra options file warning which could be rectified if the homedir itself had proper permissions. */ if(item!=0 && homedir_cache>-1 && !ascii_strncasecmp (gnupg_homedir (), tmppath, strlen (gnupg_homedir ()))) { ret=homedir_cache; goto end; } /* It's okay if the file or directory doesn't exist */ if(stat(tmppath,&statbuf)!=0) { ret=0; goto end; } /* Now check the enclosing directory. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir=make_dirname(tmppath); if(stat(dir,&dirbuf)!=0 || !S_ISDIR(dirbuf.st_mode)) { /* Weird error */ ret=1; goto end; } xfree(dir); /* Assume failure */ ret=1; if(item==0) { /* The homedir must be x00, a directory, and owned by the user. */ if(S_ISDIR(statbuf.st_mode)) { if(statbuf.st_uid==getuid()) { if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=1; } else own=1; homedir_cache=ret; } } else if(item==1 || item==2) { /* The options or extension file. Okay unless it or its containing directory is group or other writable or not owned by us or root. */ if(S_ISREG(statbuf.st_mode)) { if(statbuf.st_uid==getuid() || statbuf.st_uid==0) { if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0) { /* it's not writable, so make sure the enclosing directory is also not writable */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0) ret=0; else enc_dir_perm=1; } else enc_dir_own=1; } else { /* it's writable, so the enclosing directory had better not let people get to it. */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=enc_dir_perm=1; /* unclear which one to fix! */ } else enc_dir_own=1; } } else own=1; } } else BUG(); if(!checkonly) { if(own) { if(item==0) log_info(_("WARNING: unsafe ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe ownership on" " extension '%s'\n"),tmppath); } if(perm) { if(item==0) log_info(_("WARNING: unsafe permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe permissions on" " extension '%s'\n"),tmppath); } if(enc_dir_own) { if(item==0) log_info(_("WARNING: unsafe enclosing directory ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory ownership on" " extension '%s'\n"),tmppath); } if(enc_dir_perm) { if(item==0) log_info(_("WARNING: unsafe enclosing directory permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory permissions on" " extension '%s'\n"),tmppath); } } end: xfree(tmppath); if(homedir) homedir_cache=ret; return ret; #else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ (void)path; (void)item; return 0; #endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ } /* Print the OpenPGP defined algo numbers. */ static void print_algo_numbers(int (*checker)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%d",i); } } } static void print_algo_names(int (*checker)(int),const char *(*mapper)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%s",mapper(i)); } } } /* In the future, we can do all sorts of interesting configuration output here. For now, just give "group" as the Enigmail folks need it, and pubkey, cipher, hash, and compress as they may be useful for frontends. */ static void list_config(char *items) { int show_all = !items; char *name = NULL; const char *s; struct groupitem *giter; int first, iter; if(!opt.with_colons) return; while(show_all || (name=strsep(&items," "))) { int any=0; if(show_all || ascii_strcasecmp(name,"group")==0) { for (giter = opt.grouplist; giter; giter = giter->next) { strlist_t sl; es_fprintf (es_stdout, "cfg:group:"); es_write_sanitized (es_stdout, giter->name, strlen(giter->name), ":", NULL); es_putc (':', es_stdout); for(sl=giter->values; sl; sl=sl->next) { es_write_sanitized (es_stdout, sl->d, strlen (sl->d), ":;", NULL); if(sl->next) es_printf(";"); } es_printf("\n"); } any=1; } if(show_all || ascii_strcasecmp(name,"version")==0) { es_printf("cfg:version:"); es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkey")==0) { es_printf ("cfg:pubkey:"); print_algo_numbers (build_list_pk_test_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkeyname")==0) { es_printf ("cfg:pubkeyname:"); print_algo_names (build_list_pk_test_algo, build_list_pk_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"cipher")==0) { es_printf ("cfg:cipher:"); print_algo_numbers (build_list_cipher_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp (name,"ciphername")) { es_printf ("cfg:ciphername:"); print_algo_names (build_list_cipher_test_algo, build_list_cipher_algo_name); es_printf ("\n"); any = 1; } if(show_all || ascii_strcasecmp(name,"digest")==0 || ascii_strcasecmp(name,"hash")==0) { es_printf ("cfg:digest:"); print_algo_numbers (build_list_md_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"digestname") || !ascii_strcasecmp(name,"hashname")) { es_printf ("cfg:digestname:"); print_algo_names (build_list_md_test_algo, build_list_md_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"compress")==0) { es_printf ("cfg:compress:"); print_algo_numbers(check_compress_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp (name, "compressname") == 0) { es_printf ("cfg:compressname:"); print_algo_names (check_compress_algo, compress_algo_to_string); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"ccid-reader-id")) { /* We ignore this for GnuPG 1.4 backward compatibility. */ any=1; } if (show_all || !ascii_strcasecmp (name,"curve")) { es_printf ("cfg:curve:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0) es_printf ("%s%s", first?"":";", s); es_printf ("\n"); any=1; } /* Curve OIDs are rarely useful and thus only printed if requested. */ if (name && !ascii_strcasecmp (name,"curveoid")) { es_printf ("cfg:curveoid:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0) { s = openpgp_curve_to_oid (s, NULL); es_printf ("%s%s", first?"":";", s? s:"[?]"); } es_printf ("\n"); any=1; } if(show_all) break; if(!any) log_error(_("unknown configuration item '%s'\n"),name); } } /* List options and default values in the GPG Conf format. This is a new tool distributed with gnupg 1.9.x but we also want some limited support in older gpg versions. The output is the name of the configuration file and a list of options available for editing by gpgconf. */ static void gpgconf_list (const char *configfile) { char *configfile_esc = percent_escape (configfile, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, GPG_NAME, GC_OPT_FLAG_DEFAULT, configfile_esc ? configfile_esc : "/dev/null"); es_printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("keyserver:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("default-key:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("try-secret-key:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("auto-key-locate:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("auto-key-retrieve:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("group:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg"); es_printf ("default-new-key-algo:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("trust-model:%lu:\n", GC_OPT_FLAG_NONE); /* The next one is an info only item and should match the macros at the top of keygen.c */ es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, get_default_pubkey_algo ()); xfree (configfile_esc); } static int parse_subpacket_list(char *list) { char *tok; byte subpackets[128],i; int count=0; if(!list) { /* No arguments means all subpackets */ memset(subpackets+1,1,sizeof(subpackets)-1); count=127; } else { memset(subpackets,0,sizeof(subpackets)); /* Merge with earlier copy */ if(opt.show_subpackets) { byte *in; for(in=opt.show_subpackets;*in;in++) { if(*in>127 || *in<1) BUG(); if(!subpackets[*in]) count++; subpackets[*in]=1; } } while((tok=strsep(&list," ,"))) { if(!*tok) continue; i=atoi(tok); if(i>127 || i<1) return 0; if(!subpackets[i]) count++; subpackets[i]=1; } } xfree(opt.show_subpackets); opt.show_subpackets=xmalloc(count+1); opt.show_subpackets[count--]=0; for(i=1;i<128 && count>=0;i++) if(subpackets[i]) opt.show_subpackets[count--]=i; return 1; } static int parse_list_options(char *str) { char *subpackets=""; /* something that isn't NULL */ struct parse_options lopts[]= { {"show-photos",LIST_SHOW_PHOTOS,NULL, N_("display photo IDs during key listings")}, {"show-usage",LIST_SHOW_USAGE,NULL, N_("show key usage information during key listings")}, {"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature listings")}, {"show-notations",LIST_SHOW_NOTATIONS,NULL, N_("show all notations during signature listings")}, {"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature listings")}, {"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature listings")}, {"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature listings")}, {"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during key listings")}, {"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in key listings")}, {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, N_("show revoked and expired subkeys in key listings")}, {"show-keyring",LIST_SHOW_KEYRING,NULL, N_("show the keyring name in key listings")}, {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, N_("show expiration dates during signature listings")}, {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, {NULL,0,NULL,NULL} }; /* C99 allows for non-constant initializers, but we'd like to compile everywhere, so fill in the show-sig-subpackets argument here. Note that if the parse_options array changes, we'll have to change the subscript here. */ lopts[13].value=&subpackets; if(parse_options(str,&opt.list_options,lopts,1)) { if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS) { /* Unset so users can pass multiple lists in. */ opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS; if(!parse_subpacket_list(subpackets)) return 0; } else if(subpackets==NULL && opt.show_subpackets) { /* User did 'no-show-subpackets' */ xfree(opt.show_subpackets); opt.show_subpackets=NULL; } return 1; } else return 0; } /* Collapses argc/argv into a single string that must be freed */ static char * collapse_args(int argc,char *argv[]) { char *str=NULL; int i,first=1,len=0; for(i=0;i<argc;i++) { len+=strlen(argv[i])+2; str=xrealloc(str,len); if(first) { str[0]='\0'; first=0; } else strcat(str," "); strcat(str,argv[i]); } return str; } #ifndef NO_TRUST_MODELS static void parse_trust_model(const char *model) { if(ascii_strcasecmp(model,"pgp")==0) opt.trust_model=TM_PGP; else if(ascii_strcasecmp(model,"classic")==0) opt.trust_model=TM_CLASSIC; else if(ascii_strcasecmp(model,"always")==0) opt.trust_model=TM_ALWAYS; else if(ascii_strcasecmp(model,"direct")==0) opt.trust_model=TM_DIRECT; #ifdef USE_TOFU else if(ascii_strcasecmp(model,"tofu")==0) opt.trust_model=TM_TOFU; else if(ascii_strcasecmp(model,"tofu+pgp")==0) opt.trust_model=TM_TOFU_PGP; #endif /*USE_TOFU*/ else if(ascii_strcasecmp(model,"auto")==0) opt.trust_model=TM_AUTO; else log_error("unknown trust model '%s'\n",model); } #endif /*NO_TRUST_MODELS*/ static int parse_tofu_policy (const char *policystr) { #ifdef USE_TOFU struct { const char *keyword; int policy; } list[] = { { "auto", TOFU_POLICY_AUTO }, { "good", TOFU_POLICY_GOOD }, { "unknown", TOFU_POLICY_UNKNOWN }, { "bad", TOFU_POLICY_BAD }, { "ask", TOFU_POLICY_ASK } }; int i; if (!ascii_strcasecmp (policystr, "help")) { log_info (_("valid values for option '%s':\n"), "--tofu-policy"); for (i=0; i < DIM (list); i++) log_info (" %s\n", list[i].keyword); g10_exit (1); } for (i=0; i < DIM (list); i++) if (!ascii_strcasecmp (policystr, list[i].keyword)) return list[i].policy; #endif /*USE_TOFU*/ log_error (_("unknown TOFU policy '%s'\n"), policystr); if (!opt.quiet) log_info (_("(use \"help\" to list choices)\n")); g10_exit (1); } /* Parse the value of --compliance. */ static int parse_compliance_option (const char *string) { struct { const char *keyword; enum cmd_and_opt_values option; } list[] = { { "gnupg", oGnuPG }, { "openpgp", oOpenPGP }, { "rfc4880bis", oRFC4880bis }, { "rfc4880", oRFC4880 }, { "rfc2440", oRFC2440 }, { "pgp6", oPGP6 }, { "pgp7", oPGP7 }, { "pgp8", oPGP8 }, { "de-vs", oDE_VS } }; int i; if (!ascii_strcasecmp (string, "help")) { log_info (_("valid values for option '%s':\n"), "--compliance"); for (i=0; i < DIM (list); i++) log_info (" %s\n", list[i].keyword); g10_exit (1); } for (i=0; i < DIM (list); i++) if (!ascii_strcasecmp (string, list[i].keyword)) return list[i].option; log_error (_("invalid value for option '%s'\n"), "--compliance"); if (!opt.quiet) log_info (_("(use \"help\" to list choices)\n")); g10_exit (1); } -/* Helper to set compliance related options. This is a separte +/* Helper to set compliance related options. This is a separate * function so that it can also be used by the --compliance option * parser. */ static void set_compliance_option (enum cmd_and_opt_values option) { switch (option) { case oRFC4880bis: opt.flags.rfc4880bis = 1; /* fall through. */ case oOpenPGP: case oRFC4880: /* This is effectively the same as RFC2440, but with "--enable-dsa2 --no-rfc2440-text --escape-from-lines --require-cross-certification". */ opt.compliance = CO_RFC4880; opt.flags.dsa2 = 1; opt.flags.require_cross_cert = 1; opt.rfc2440_text = 0; opt.allow_non_selfsigned_uid = 1; opt.allow_freeform_uid = 1; opt.escape_from = 1; opt.not_dash_escaped = 0; opt.def_cipher_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_digest_algo = DIGEST_ALGO_SHA1; opt.s2k_cipher_algo = CIPHER_ALGO_3DES; break; case oRFC2440: opt.compliance = CO_RFC2440; opt.flags.dsa2 = 0; opt.rfc2440_text = 1; opt.allow_non_selfsigned_uid = 1; opt.allow_freeform_uid = 1; opt.escape_from = 0; opt.not_dash_escaped = 0; opt.def_cipher_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_digest_algo = DIGEST_ALGO_SHA1; opt.s2k_cipher_algo = CIPHER_ALGO_3DES; break; case oPGP6: opt.compliance = CO_PGP6; break; case oPGP7: opt.compliance = CO_PGP7; break; case oPGP8: opt.compliance = CO_PGP8; break; case oGnuPG: opt.compliance = CO_GNUPG; break; case oDE_VS: set_compliance_option (oOpenPGP); opt.compliance = CO_DE_VS; /* Fixme: Change other options. */ break; default: BUG (); } } /* This function called to initialized a new control object. It is assumed that this object has been zeroed out before calling this function. */ static void gpg_init_default_ctrl (ctrl_t ctrl) { ctrl->magic = SERVER_CONTROL_MAGIC; } /* This function is called to deinitialize a control object. It is not deallocated. */ static void gpg_deinit_default_ctrl (ctrl_t ctrl) { #ifdef USE_TOFU tofu_closedbs (ctrl); #endif gpg_dirmngr_deinit_session_data (ctrl); keydb_release (ctrl->cached_getkey_kdb); } char * get_default_configname (void) { char *configname = NULL; char *name = xstrdup (GPG_NAME EXTSEP_S "conf-" SAFE_VERSION); char *ver = &name[strlen (GPG_NAME EXTSEP_S "conf-")]; do { if (configname) { char *tok; xfree (configname); configname = NULL; if ((tok = strrchr (ver, SAFE_VERSION_DASH))) *tok='\0'; else if ((tok = strrchr (ver, SAFE_VERSION_DOT))) *tok='\0'; else break; } configname = make_filename (gnupg_homedir (), name, NULL); } while (access (configname, R_OK)); xfree(name); if (! configname) configname = make_filename (gnupg_homedir (), GPG_NAME EXTSEP_S "conf", NULL); if (! access (configname, R_OK)) { /* Print a warning when both config files are present. */ char *p = make_filename (gnupg_homedir (), "options", NULL); if (! access (p, R_OK)) log_info (_("Note: old default options file '%s' ignored\n"), p); xfree (p); } else { /* Use the old default only if it exists. */ char *p = make_filename (gnupg_homedir (), "options", NULL); if (!access (p, R_OK)) { xfree (configname); configname = p; } else xfree (p); } return configname; } int main (int argc, char **argv) { ARGPARSE_ARGS pargs; IOBUF a; int rc=0; int orig_argc; char **orig_argv; const char *fname; char *username; int may_coredump; strlist_t sl; strlist_t remusr = NULL; strlist_t locusr = NULL; strlist_t nrings = NULL; armor_filter_context_t *afx = NULL; int detached_sig = 0; FILE *configfp = NULL; char *configname = NULL; char *save_configname = NULL; char *default_configname = NULL; unsigned configlineno; int parse_debug = 0; int default_config = 1; int default_keyring = 1; int greeting = 0; int nogreeting = 0; char *logfile = NULL; int use_random_seed = 1; enum cmd_and_opt_values cmd = 0; const char *debug_level = NULL; #ifndef NO_TRUST_MODELS const char *trustdb_name = NULL; #endif /*!NO_TRUST_MODELS*/ char *def_cipher_string = NULL; char *def_digest_string = NULL; char *compress_algo_string = NULL; char *cert_digest_string = NULL; char *s2k_cipher_string = NULL; char *s2k_digest_string = NULL; char *pers_cipher_list = NULL; char *pers_digest_list = NULL; char *pers_compress_list = NULL; int eyes_only=0; int multifile=0; int pwfd = -1; int ovrseskeyfd = -1; int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */ int any_explicit_recipient = 0; int require_secmem = 0; int got_secmem = 0; struct assuan_malloc_hooks malloc_hooks; ctrl_t ctrl; static int print_dane_records; static int print_pka_records; #ifdef __riscos__ opt.lock_once = 1; #endif /* __riscos__ */ /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to secmem_init() somewhere after the option parsing. */ early_system_init (); gnupg_reopen_std (GPG_NAME); trap_unaligned (); gnupg_rl_initialize (); set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Use our own logging handler for Libcgrypt. */ setup_libgcrypt_logging (); /* Put random number into secure memory */ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps(); gnupg_init_signals (0, emergency_cleanup); dotlock_create (NULL, 0); /* Register lock file cleanup. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); opt.command_fd = -1; /* no command fd */ opt.compress_level = -1; /* defaults to standard compress level */ opt.bz2_compress_level = -1; /* defaults to standard compress level */ /* note: if you change these lines, look at oOpenPGP */ opt.def_cipher_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */ opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_count = 0; /* Auto-calibrate when needed. */ opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO; opt.completes_needed = 1; opt.marginals_needed = 3; opt.max_cert_depth = 5; opt.escape_from = 1; opt.flags.require_cross_cert = 1; opt.import_options = 0; opt.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.import_options = IMPORT_REPAIR_PKS_SUBKEY_BUG; opt.keyserver_options.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.options = KEYSERVER_HONOR_PKA_RECORD; opt.verify_options = (LIST_SHOW_UID_VALIDITY | VERIFY_SHOW_POLICY_URLS | VERIFY_SHOW_STD_NOTATIONS | VERIFY_SHOW_KEYSERVER_URLS); opt.list_options = (LIST_SHOW_UID_VALIDITY | LIST_SHOW_USAGE); #ifdef NO_TRUST_MODELS opt.trust_model = TM_ALWAYS; #else opt.trust_model = TM_AUTO; #endif opt.tofu_default_policy = TOFU_POLICY_AUTO; opt.mangle_dos_filenames = 0; opt.min_cert_level = 2; set_screen_dimensions (); opt.keyid_format = KF_NONE; opt.def_sig_expire = "0"; opt.def_cert_expire = "0"; gnupg_set_homedir (NULL); opt.passphrase_repeat = 1; opt.emit_version = 0; opt.weak_digests = NULL; additional_weak_digest("MD5"); /* Check whether we have a config file on the command line. */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while( arg_parse( &pargs, opts) ) { if( pargs.r_opt == oDebug || pargs.r_opt == oDebugAll ) parse_debug++; else if (pargs.r_opt == oDebugIOLBF) es_setvbuf (es_stdout, NULL, _IOLBF, 0); else if( pargs.r_opt == oOptions ) { /* yes there is one, so we do not try the default one, but * read the option file when it is encountered at the commandline */ default_config = 0; } else if( pargs.r_opt == oNoOptions ) { default_config = 0; /* --no-options */ opt.no_homedir_creation = 1; } else if( pargs.r_opt == oHomedir ) gnupg_set_homedir (pargs.r.ret_str); else if( pargs.r_opt == oNoPermissionWarn ) opt.no_perm_warn=1; else if (pargs.r_opt == oStrict ) { /* Not used */ } else if (pargs.r_opt == oNoStrict ) { /* Not used */ } } #ifdef HAVE_DOSISH_SYSTEM if ( strchr (gnupg_homedir (), '\\') ) { char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1); const char *s; for (d=buf, s = gnupg_homedir (); *s; s++) { *d++ = *s == '\\'? '/': *s; #ifdef HAVE_W32_SYSTEM if (s[1] && IsDBCSLeadByte (*s)) *d++ = *++s; #endif } *d = 0; gnupg_set_homedir (buf); } #endif /* Initialize the secure memory. */ if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0)) got_secmem = 1; #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) /* There should be no way to get to this spot while still carrying setuid privs. Just in case, bomb out if we are. */ if ( getuid () != geteuid () ) BUG (); #endif maybe_setuid = 0; /* Okay, we are now working under our real uid */ /* malloc hooks go here ... */ 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); setup_libassuan_logging (&opt.debug, NULL); /* Try for a version specific config file first */ default_configname = get_default_configname (); if (default_config) configname = xstrdup (default_configname); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; /* By this point we have a homedir, and cannot change it. */ check_permissions (gnupg_homedir (), 0); next_pass: if( configname ) { if(check_permissions(configname,1)) { /* If any options file is unsafe, then disable any external programs for keyserver calls or photo IDs. Since the external program to call is set in the options file, a unsafe options file can lead to an arbitrary program being run. */ opt.exec_disable=1; } configlineno = 0; configfp = fopen( configname, "r" ); if (configfp && is_secured_file (fileno (configfp))) { fclose (configfp); configfp = NULL; gpg_err_set_errno (EPERM); } if( !configfp ) { if( default_config ) { if( parse_debug ) log_info(_("Note: no default option file '%s'\n"), configname ); } else { log_error(_("option file '%s': %s\n"), configname, strerror(errno) ); g10_exit(2); } xfree(configname); configname = NULL; } if( parse_debug && configname ) log_info(_("reading options from '%s'\n"), configname ); default_config = 0; } while( optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { switch( pargs.r_opt ) { case aListConfig: case aListGcryptConfig: case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); /* Do not register a keyring for these commands. */ default_keyring = -1; break; case aCheckKeys: case aListPackets: case aImport: case aFastImport: case aSendKeys: case aRecvKeys: case aSearchKeys: case aRefreshKeys: case aFetchKeys: case aExport: #ifdef ENABLE_CARD_SUPPORT case aCardStatus: case aCardEdit: case aChangePIN: #endif /* ENABLE_CARD_SUPPORT*/ case aListKeys: case aLocateKeys: case aListSigs: case aExportSecret: case aExportSecretSub: case aExportSshKey: case aSym: case aClearsign: case aGenRevoke: case aDesigRevoke: case aPrimegen: case aGenRandom: case aPrintMD: case aPrintMDs: case aListTrustDB: case aCheckTrustDB: case aUpdateTrustDB: case aFixTrustDB: case aListTrustPath: case aDeArmor: case aEnArmor: case aSign: case aQuickSignKey: case aQuickLSignKey: case aSignKey: case aLSignKey: case aStore: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickRevUid: case aQuickSetExpire: case aQuickSetPrimaryUid: case aExportOwnerTrust: case aImportOwnerTrust: case aRebuildKeydbCaches: set_cmd (&cmd, pargs.r_opt); break; case aKeygen: case aFullKeygen: case aEditKey: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aDeleteKeys: case aPasswd: set_cmd (&cmd, pargs.r_opt); greeting=1; break; case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break; case aDecryptFiles: multifile=1; /* fall through */ case aDecrypt: set_cmd( &cmd, aDecrypt); break; case aEncrFiles: multifile=1; /* fall through */ case aEncr: set_cmd( &cmd, aEncr); break; case aVerifyFiles: multifile=1; /* fall through */ case aVerify: set_cmd( &cmd, aVerify); break; case aServer: set_cmd (&cmd, pargs.r_opt); opt.batch = 1; break; case aTOFUPolicy: set_cmd (&cmd, pargs.r_opt); break; case oArmor: opt.armor = 1; opt.no_armor=0; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break; case oInputSizeHint: opt.input_size_hint = string_to_u64 (pargs.r.ret_str); break; case oQuiet: opt.quiet = 1; break; case oNoTTY: tty_no_terminal(1); break; case oDryRun: opt.dry_run = 1; break; case oInteractive: opt.interactive = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_options|=LIST_SHOW_UNUSABLE_UIDS; opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS; break; case oBatch: opt.batch = 1; nogreeting = 1; break; case oUseAgent: /* Dummy. */ break; case oNoUseAgent: obsolete_option (configname, configlineno, "no-use-agent"); break; case oGpgAgentInfo: obsolete_option (configname, configlineno, "gpg-agent-info"); break; case oReaderPort: obsolete_scdaemon_option (configname, configlineno, "reader-port"); break; case octapiDriver: obsolete_scdaemon_option (configname, configlineno, "ctapi-driver"); break; case opcscDriver: obsolete_scdaemon_option (configname, configlineno, "pcsc-driver"); break; case oDisableCCID: obsolete_scdaemon_option (configname, configlineno, "disable-ccid"); break; case oHonorHttpProxy: obsolete_option (configname, configlineno, "honor-http-proxy"); break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; case oPrimaryKeyring: sl = append_to_strlist (&nrings, pargs.r.ret_str); sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY; break; case oShowKeyring: deprecated_warning(configname,configlineno,"--show-keyring", "--list-options ","show-keyring"); opt.list_options|=LIST_SHOW_KEYRING; 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 oDebugIOLBF: break; /* Already set in pre-parse step. */ case oStatusFD: set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); break; case oStatusFile: set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) ); break; case oAttributeFD: set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); break; case oAttributeFile: set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) ); break; case oLoggerFD: log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oLoggerFile: logfile = pargs.r.ret_str; break; case oWithFingerprint: opt.with_fingerprint = 1; opt.fingerprint++; break; case oWithSubkeyFingerprint: opt.with_subkey_fingerprint = 1; break; case oWithICAOSpelling: opt.with_icao_spelling = 1; break; case oFingerprint: opt.fingerprint++; fpr_maybe_cmd = 1; break; case oWithKeygrip: opt.with_keygrip = 1; break; case oWithSecret: opt.with_secret = 1; break; case oWithWKDHash: opt.with_wkd_hash = 1; break; case oSecretKeyring: /* Ignore this old option. */ break; case oOptions: /* config files may not be nested (silently ignore them) */ if( !configfp ) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoArmor: opt.no_armor=1; opt.armor=0; break; case oNoDefKeyring: if (default_keyring > 0) default_keyring = 0; break; case oNoKeyring: default_keyring = -1; break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_sigs=0; break; case oQuickRandom: gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); break; case oEmitVersion: opt.emit_version++; break; case oNoEmitVersion: opt.emit_version=0; break; case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break; case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break; case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break; #ifndef NO_TRUST_MODELS case oTrustDBName: trustdb_name = pargs.r.ret_str; break; #endif /*!NO_TRUST_MODELS*/ case oDefaultKey: sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configfp) sl->flags |= PK_LIST_CONFIG; break; case oDefRecipient: if( *pargs.r.ret_str ) { xfree (opt.def_recipient); opt.def_recipient = make_username(pargs.r.ret_str); } break; case oDefRecipientSelf: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oNoOptions: opt.no_homedir_creation = 1; break; /* no-options */ case oHomedir: break; case oNoBatch: opt.batch = 0; break; case oWithTofuInfo: opt.with_tofu_info = 1; break; case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/ case oWithColons: opt.with_colons=':'; break; case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/ case oWithSigList: opt.list_sigs = 1; break; case oSkipVerify: opt.skip_verify=1; break; case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break; case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break; case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; #ifndef NO_TRUST_MODELS /* There are many programs (like mutt) that call gpg with --always-trust so keep this option around for a long time. */ case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break; case oTrustModel: parse_trust_model(pargs.r.ret_str); break; #endif /*!NO_TRUST_MODELS*/ case oTOFUDefaultPolicy: opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str); break; case oTOFUDBFormat: obsolete_option (configname, configlineno, "tofu-db-format"); break; case oForceOwnertrust: log_info(_("Note: %s is not for normal use!\n"), "--force-ownertrust"); opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str); if(opt.force_ownertrust==-1) { log_error("invalid ownertrust '%s'\n",pargs.r.ret_str); opt.force_ownertrust=0; } break; case oLoadExtension: /* Dummy so that gpg 1.4 conf files can work. Should eventually be removed. */ break; case oCompliance: set_compliance_option (parse_compliance_option (pargs.r.ret_str)); break; case oOpenPGP: case oRFC2440: case oRFC4880: case oRFC4880bis: case oPGP6: case oPGP7: case oPGP8: case oGnuPG: set_compliance_option (pargs.r_opt); break; case oRFC2440Text: opt.rfc2440_text=1; break; case oNoRFC2440Text: opt.rfc2440_text=0; break; case oSetFilename: if(utf8_strings) opt.set_filename = pargs.r.ret_str; else opt.set_filename = native_to_utf8(pargs.r.ret_str); break; case oForYourEyesOnly: eyes_only = 1; break; case oNoForYourEyesOnly: eyes_only = 0; break; case oSetPolicyURL: add_policy_url(pargs.r.ret_str,0); add_policy_url(pargs.r.ret_str,1); break; case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break; case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break; case oShowPolicyURL: deprecated_warning(configname,configlineno,"--show-policy-url", "--list-options ","show-policy-urls"); deprecated_warning(configname,configlineno,"--show-policy-url", "--verify-options ","show-policy-urls"); opt.list_options|=LIST_SHOW_POLICY_URLS; opt.verify_options|=VERIFY_SHOW_POLICY_URLS; break; case oNoShowPolicyURL: deprecated_warning(configname,configlineno,"--no-show-policy-url", "--list-options ","no-show-policy-urls"); deprecated_warning(configname,configlineno,"--no-show-policy-url", "--verify-options ","no-show-policy-urls"); opt.list_options&=~LIST_SHOW_POLICY_URLS; opt.verify_options&=~VERIFY_SHOW_POLICY_URLS; break; case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break; case oUseEmbeddedFilename: opt.flags.use_embedded_filename=1; break; case oNoUseEmbeddedFilename: opt.flags.use_embedded_filename=0; break; case oComment: if(pargs.r.ret_str[0]) append_to_strlist(&opt.comments,pargs.r.ret_str); break; case oDefaultComment: deprecated_warning(configname,configlineno, "--default-comment","--no-comments",""); /* fall through */ case oNoComments: free_strlist(opt.comments); opt.comments=NULL; break; case oThrowKeyids: opt.throw_keyids = 1; break; case oNoThrowKeyids: opt.throw_keyids = 0; break; case oShowPhotos: deprecated_warning(configname,configlineno,"--show-photos", "--list-options ","show-photos"); deprecated_warning(configname,configlineno,"--show-photos", "--verify-options ","show-photos"); opt.list_options|=LIST_SHOW_PHOTOS; opt.verify_options|=VERIFY_SHOW_PHOTOS; break; case oNoShowPhotos: deprecated_warning(configname,configlineno,"--no-show-photos", "--list-options ","no-show-photos"); deprecated_warning(configname,configlineno,"--no-show-photos", "--verify-options ","no-show-photos"); opt.list_options&=~LIST_SHOW_PHOTOS; opt.verify_options&=~VERIFY_SHOW_PHOTOS; break; case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break; case oForceMDC: opt.force_mdc = 1; break; case oNoForceMDC: opt.force_mdc = 0; break; case oDisableMDC: opt.disable_mdc = 1; break; case oNoDisableMDC: opt.disable_mdc = 0; break; case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break; case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break; case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break; case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break; case oS2KCount: if (pargs.r.ret_int) opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int); else opt.s2k_count = 0; /* Auto-calibrate when needed. */ break; case oRecipient: case oHiddenRecipient: case oRecipientFile: case oHiddenRecipientFile: /* Store the recipient. Note that we also store the * option as private data in the flags. This is achieved * by shifting the option value to the left so to keep * enough space for the flags. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configfp) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenRecipient || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_HIDDEN; if (pargs.r_opt == oRecipientFile || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_FROM_FILE; any_explicit_recipient = 1; break; case oEncryptTo: case oHiddenEncryptTo: /* Store an additional recipient. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO); if (configfp) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenEncryptTo) sl->flags |= PK_LIST_HIDDEN; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptToDefaultKey: opt.encrypt_to_default_key = configfp ? 2 : 1; break; case oTrySecretKey: add_to_strlist2 (&opt.secret_keys_to_try, pargs.r.ret_str, utf8_strings); break; case oMimemode: opt.mimemode = opt.textmode = 1; break; case oTextmodeShort: opt.textmode = 2; break; case oTextmode: opt.textmode=1; break; case oNoTextmode: opt.textmode=opt.mimemode=0; break; case oExpert: opt.expert = 1; break; case oNoExpert: opt.expert = 0; break; case oDefSigExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_sig_expire=pargs.r.ret_str; } break; case oAskSigExpire: opt.ask_sig_expire = 1; break; case oNoAskSigExpire: opt.ask_sig_expire = 0; break; case oDefCertExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_cert_expire=pargs.r.ret_str; } break; case oAskCertExpire: opt.ask_cert_expire = 1; break; case oNoAskCertExpire: opt.ask_cert_expire = 0; break; case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break; case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break; case oAskCertLevel: opt.ask_cert_level = 1; break; case oNoAskCertLevel: opt.ask_cert_level = 0; break; case oLocalUser: /* store the local users */ sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configfp) sl->flags |= PK_LIST_CONFIG; break; case oSender: { char *mbox = mailbox_from_userid (pargs.r.ret_str); if (!mbox) log_error (_("\"%s\" is not a proper mail address\n"), pargs.r.ret_str); else { add_to_strlist (&opt.sender_list, mbox); xfree (mbox); } } break; case oCompress: /* this is the -z command line option */ opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int; break; case oCompressLevel: opt.compress_level = pargs.r.ret_int; break; case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break; case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break; case oPassphrase: set_passphrase_from_string(pargs.r.ret_str); break; case oPassphraseFD: pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); break; case oPassphraseFile: pwfd = open_info_file (pargs.r.ret_str, 0, 1); break; case oPassphraseRepeat: opt.passphrase_repeat = pargs.r.ret_int; break; case oPinentryMode: opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); if (opt.pinentry_mode == -1) log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); break; case oCommandFD: opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); if (! gnupg_fd_valid (opt.command_fd)) log_fatal ("command-fd is invalid: %s\n", strerror (errno)); break; case oCommandFile: opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1); break; case oCipherAlgo: def_cipher_string = xstrdup(pargs.r.ret_str); break; case oDigestAlgo: def_digest_string = xstrdup(pargs.r.ret_str); break; case oCompressAlgo: /* If it is all digits, stick a Z in front of it for later. This is for backwards compatibility with versions that took the compress algorithm number. */ { char *pt=pargs.r.ret_str; while(*pt) { if (!isascii (*pt) || !isdigit (*pt)) break; pt++; } if(*pt=='\0') { compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2); strcpy(compress_algo_string,"Z"); strcat(compress_algo_string,pargs.r.ret_str); } else compress_algo_string = xstrdup(pargs.r.ret_str); } break; case oCertDigestAlgo: cert_digest_string = xstrdup(pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oRequireSecmem: require_secmem=1; break; case oNoRequireSecmem: require_secmem=0; break; case oNoPermissionWarn: opt.no_perm_warn=1; break; case oNoMDCWarn: opt.no_mdc_warn=1; break; case oDisplayCharset: if( set_native_charset( pargs.r.ret_str ) ) log_error(_("'%s' is not a valid character set\n"), pargs.r.ret_str); break; case oNotDashEscaped: opt.not_dash_escaped = 1; break; case oEscapeFrom: opt.escape_from = 1; break; case oNoEscapeFrom: opt.escape_from = 0; break; case oLockOnce: opt.lock_once = 1; break; case oLockNever: dotlock_disable (); break; case oLockMultiple: #ifndef __riscos__ opt.lock_once = 0; #else /* __riscos__ */ riscos_not_implemented("lock-multiple"); #endif /* __riscos__ */ break; case oKeyServer: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str, 0); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else { /* We only support a single keyserver. Later ones override earlier ones. (Since we parse the config file first and then the command line arguments, the command line takes precedence.) */ if (opt.keyserver) free_keyserver_spec (opt.keyserver); opt.keyserver = keyserver; } } break; case oKeyServerOptions: if(!parse_keyserver_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid keyserver options\n"), configname,configlineno); else log_error(_("invalid keyserver options\n")); } break; case oImportOptions: if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1)) { if(configname) log_error(_("%s:%d: invalid import options\n"), configname,configlineno); else log_error(_("invalid import options\n")); } break; case oImportFilter: rc = parse_and_set_import_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oExportOptions: if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1)) { if(configname) log_error(_("%s:%d: invalid export options\n"), configname,configlineno); else log_error(_("invalid export options\n")); } break; case oExportFilter: rc = parse_and_set_export_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oListOptions: if(!parse_list_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid list options\n"), configname,configlineno); else log_error(_("invalid list options\n")); } break; case oVerifyOptions: { struct parse_options vopts[]= { {"show-photos",VERIFY_SHOW_PHOTOS,NULL, N_("display photo IDs during signature verification")}, {"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature verification")}, {"show-notations",VERIFY_SHOW_NOTATIONS,NULL, N_("show all notations during signature verification")}, {"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature verification")}, {"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature verification")}, {"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature verification")}, {"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during signature verification")}, {"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in signature verification")}, {"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL, N_("show only the primary user ID in signature verification")}, {"pka-lookups",VERIFY_PKA_LOOKUPS,NULL, N_("validate signatures with PKA data")}, {"pka-trust-increase",VERIFY_PKA_TRUST_INCREASE,NULL, N_("elevate the trust of signatures with valid PKA data")}, {NULL,0,NULL,NULL} }; if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1)) { if(configname) log_error(_("%s:%d: invalid verify options\n"), configname,configlineno); else log_error(_("invalid verify options\n")); } } break; case oTempDir: opt.temp_dir=pargs.r.ret_str; break; case oExecPath: if(set_exec_path(pargs.r.ret_str)) log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str); else opt.exec_path_set=1; break; case oSetNotation: add_notation_data( pargs.r.ret_str, 0 ); add_notation_data( pargs.r.ret_str, 1 ); break; case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break; case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break; case oShowNotation: deprecated_warning(configname,configlineno,"--show-notation", "--list-options ","show-notations"); deprecated_warning(configname,configlineno,"--show-notation", "--verify-options ","show-notations"); opt.list_options|=LIST_SHOW_NOTATIONS; opt.verify_options|=VERIFY_SHOW_NOTATIONS; break; case oNoShowNotation: deprecated_warning(configname,configlineno,"--no-show-notation", "--list-options ","no-show-notations"); deprecated_warning(configname,configlineno,"--no-show-notation", "--verify-options ","no-show-notations"); opt.list_options&=~LIST_SHOW_NOTATIONS; opt.verify_options&=~VERIFY_SHOW_NOTATIONS; break; case oUtf8Strings: utf8_strings = 1; break; case oNoUtf8Strings: utf8_strings = 0; break; case oDisableCipherAlgo: { int algo = string_to_cipher_algo (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oNoSigCache: opt.no_sig_cache = 1; break; case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break; case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break; case oAllowFreeformUID: opt.allow_freeform_uid = 1; break; case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break; case oNoLiteral: opt.no_literal = 1; break; case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break; case oFastListMode: opt.fast_list_mode = 1; break; case oFixedListMode: /* Dummy */ break; case oLegacyListMode: opt.legacy_list_mode = 1; break; case oPrintPKARecords: print_pka_records = 1; break; case oPrintDANERecords: print_dane_records = 1; break; case oListOnly: opt.list_only=1; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; case oIgnoreCrcError: opt.ignore_crc_error = 1; break; case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oAutoKeyRetrieve: case oNoAutoKeyRetrieve: if(pargs.r_opt==oAutoKeyRetrieve) opt.keyserver_options.options|=KEYSERVER_AUTO_KEY_RETRIEVE; else opt.keyserver_options.options&=~KEYSERVER_AUTO_KEY_RETRIEVE; break; case oShowSessionKey: opt.show_session_key = 1; break; case oOverrideSessionKey: opt.override_session_key = pargs.r.ret_str; break; case oOverrideSessionKeyFD: ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); break; case oMergeOnly: deprecated_warning(configname,configlineno,"--merge-only", "--import-options ","merge-only"); opt.import_options|=IMPORT_MERGE_ONLY; break; case oAllowSecretKeyImport: /* obsolete */ break; case oTryAllSecrets: opt.try_all_secrets = 1; break; case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break; case oEnableSpecialFilenames: enable_special_filenames (); break; case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break; case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break; case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break; case oPreservePermissions: opt.preserve_permissions=1; break; case oDefaultPreferenceList: opt.def_preference_list = pargs.r.ret_str; break; case oDefaultKeyserverURL: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str,1 ); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else free_keyserver_spec (keyserver); opt.def_keyserver_url = pargs.r.ret_str; } break; case oPersonalCipherPreferences: pers_cipher_list=pargs.r.ret_str; break; case oPersonalDigestPreferences: pers_digest_list=pargs.r.ret_str; break; case oPersonalCompressPreferences: pers_compress_list=pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oWeakDigest: additional_weak_digest(pargs.r.ret_str); break; case oUnwrap: opt.unwrap_encryption = 1; break; case oOnlySignTextIDs: opt.only_sign_text_ids = 1; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs.r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs.r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs.r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; case oGroup: add_group(pargs.r.ret_str); break; case oUnGroup: rm_group(pargs.r.ret_str); break; case oNoGroups: while(opt.grouplist) { struct groupitem *iter=opt.grouplist; free_strlist(iter->values); opt.grouplist=opt.grouplist->next; xfree(iter); } break; case oStrict: case oNoStrict: /* Not used */ break; case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break; case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break; case oEnableProgressFilter: opt.enable_progress_filter = 1; break; case oMultifile: multifile=1; break; case oKeyidFormat: if(ascii_strcasecmp(pargs.r.ret_str,"short")==0) opt.keyid_format=KF_SHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0) opt.keyid_format=KF_LONG; else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0) opt.keyid_format=KF_0xSHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0) opt.keyid_format=KF_0xLONG; else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0) opt.keyid_format = KF_NONE; else log_error("unknown keyid-format '%s'\n",pargs.r.ret_str); break; case oExitOnStatusWriteError: opt.exit_on_status_write_error = 1; break; case oLimitCardInsertTries: opt.limit_card_insert_tries = pargs.r.ret_int; break; case oRequireCrossCert: opt.flags.require_cross_cert=1; break; case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break; case oAutoKeyLocate: if(!parse_auto_key_locate(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid auto-key-locate list\n"), configname,configlineno); else log_error(_("invalid auto-key-locate list\n")); } break; case oNoAutoKeyLocate: release_akl(); break; case oEnableLargeRSA: #if SECMEM_BUFFER_SIZE >= 65536 opt.flags.large_rsa=1; #else if (configname) log_info("%s:%d: WARNING: gpg not built with large secure " "memory buffer. Ignoring enable-large-rsa\n", configname,configlineno); else log_info("WARNING: gpg not built with large secure " "memory buffer. Ignoring --enable-large-rsa\n"); #endif /* SECMEM_BUFFER_SIZE >= 65536 */ break; case oDisableLargeRSA: opt.flags.large_rsa=0; break; case oEnableDSA2: opt.flags.dsa2=1; break; case oDisableDSA2: opt.flags.dsa2=0; break; case oAllowMultisigVerification: case oAllowMultipleMessages: opt.flags.allow_multiple_messages=1; break; case oNoAllowMultipleMessages: opt.flags.allow_multiple_messages=0; break; case oAllowWeakDigestAlgos: opt.flags.allow_weak_digest_algos = 1; break; case oFakedSystemTime: { size_t len = strlen (pargs.r.ret_str); int freeze = 0; time_t faked_time; if (len > 0 && pargs.r.ret_str[len-1] == '!') { freeze = 1; pargs.r.ret_str[len-1] = '\0'; } 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, freeze); } break; case oNoAutostart: opt.autostart = 0; break; case oDefaultNewKeyAlgo: opt.def_new_key_algo = pargs.r.ret_str; break; case oNoop: break; default: pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Remember the first config file name. */ if (!save_configname) save_configname = configname; else xfree(configname); configname = NULL; goto next_pass; } xfree(configname); configname = NULL; if (log_get_errorcount (0)) g10_exit(2); /* The command --gpgconf-list is pretty simple and may be called directly after the option parsing. */ if (cmd == aGPGConfList) { gpgconf_list (save_configname ? save_configname : default_configname); g10_exit (0); } xfree (save_configname); xfree (default_configname); if (print_dane_records) log_error ("invalid option \"%s\"; use \"%s\" instead\n", "--print-dane-records", "--export-options export-dane"); if (print_pka_records) log_error ("invalid option \"%s\"; use \"%s\" instead\n", "--print-pks-records", "--export-options export-pka"); if (log_get_errorcount (0)) g10_exit(2); if( nogreeting ) greeting = 0; if( greeting ) { es_fprintf (es_stderr, "%s %s; %s\n", strusage(11), strusage(13), strusage(14) ); es_fprintf (es_stderr, "%s\n", strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { const char *s; if((s=strusage(25))) log_info("%s\n",s); if((s=strusage(26))) log_info("%s\n",s); if((s=strusage(27))) log_info("%s\n",s); } #endif /* FIXME: We should use logging to a file only in server mode; however we have not yet implemetyed that. Thus we try to get away with --batch as indication for logging to file required. */ if (logfile && opt.batch) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (opt.verbose > 2) log_info ("using character set '%s'\n", get_native_charset ()); if( may_coredump && !opt.quiet ) log_info(_("WARNING: program may create a core file!\n")); if (opt.flags.rfc4880bis) log_info ("WARNING: using experimental features from RFC4880bis!\n"); else { opt.mimemode = 0; /* This will use text mode instead. */ } if (eyes_only) { if (opt.set_filename) log_info(_("WARNING: %s overrides %s\n"), "--for-your-eyes-only","--set-filename"); opt.set_filename="_CONSOLE"; } if (opt.no_literal) { log_info(_("Note: %s is not for normal use!\n"), "--no-literal"); if (opt.textmode) log_error(_("%s not allowed with %s!\n"), "--textmode", "--no-literal" ); if (opt.set_filename) log_error(_("%s makes no sense with %s!\n"), eyes_only?"--for-your-eyes-only":"--set-filename", "--no-literal" ); } if (opt.set_filesize) log_info(_("Note: %s is not for normal use!\n"), "--set-filesize"); if( opt.batch ) tty_batchmode( 1 ); 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"); } /* 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]); } gcry_control (GCRYCTL_RESUME_SECMEM_WARN); if(require_secmem && !got_secmem) { log_info(_("will not run with insecure memory due to %s\n"), "--require-secmem"); g10_exit(2); } set_debug (debug_level); if (DBG_CLOCK) log_clock ("start"); /* Do these after the switch(), so they can override settings. */ if(PGP6) { /* That does not anymore work because we have no more support for v3 signatures. */ opt.disable_mdc=1; opt.escape_from=1; opt.ask_sig_expire=0; } else if(PGP7) { /* That does not anymore work because we have no more support for v3 signatures. */ opt.escape_from=1; opt.ask_sig_expire=0; } else if(PGP8) { opt.escape_from=1; } if( def_cipher_string ) { opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string); xfree(def_cipher_string); def_cipher_string = NULL; if ( openpgp_cipher_test_algo (opt.def_cipher_algo) ) log_error(_("selected cipher algorithm is invalid\n")); } if( def_digest_string ) { opt.def_digest_algo = string_to_digest_algo (def_digest_string); xfree(def_digest_string); def_digest_string = NULL; if ( openpgp_md_test_algo (opt.def_digest_algo) ) log_error(_("selected digest algorithm is invalid\n")); } if( compress_algo_string ) { opt.compress_algo = string_to_compress_algo(compress_algo_string); xfree(compress_algo_string); compress_algo_string = NULL; if( check_compress_algo(opt.compress_algo) ) log_error(_("selected compression algorithm is invalid\n")); } if( cert_digest_string ) { opt.cert_digest_algo = string_to_digest_algo (cert_digest_string); xfree(cert_digest_string); cert_digest_string = NULL; if (openpgp_md_test_algo(opt.cert_digest_algo)) log_error(_("selected certification digest algorithm is invalid\n")); } if( s2k_cipher_string ) { opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string); xfree(s2k_cipher_string); s2k_cipher_string = NULL; if (openpgp_cipher_test_algo (opt.s2k_cipher_algo)) log_error(_("selected cipher algorithm is invalid\n")); } if( s2k_digest_string ) { opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string); xfree(s2k_digest_string); s2k_digest_string = NULL; if (openpgp_md_test_algo(opt.s2k_digest_algo)) log_error(_("selected digest algorithm is invalid\n")); } if( opt.completes_needed < 1 ) log_error(_("completes-needed must be greater than 0\n")); if( opt.marginals_needed < 2 ) log_error(_("marginals-needed must be greater than 1\n")); if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 ) log_error(_("max-cert-depth must be in the range from 1 to 255\n")); if(opt.def_cert_level<0 || opt.def_cert_level>3) log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n")); if( opt.min_cert_level < 1 || opt.min_cert_level > 3 ) log_error(_("invalid min-cert-level; must be 1, 2, or 3\n")); switch( opt.s2k_mode ) { case 0: log_info(_("Note: simple S2K mode (0) is strongly discouraged\n")); break; case 1: case 3: break; default: log_error(_("invalid S2K mode; must be 0, 1 or 3\n")); } /* This isn't actually needed, but does serve to error out if the string is invalid. */ if(opt.def_preference_list && keygen_set_std_prefs(opt.def_preference_list,0)) log_error(_("invalid default preferences\n")); if(pers_cipher_list && keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM)) log_error(_("invalid personal cipher preferences\n")); if(pers_digest_list && keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH)) log_error(_("invalid personal digest preferences\n")); if(pers_compress_list && keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); /* We don't support all possible commands with multifile yet */ if(multifile) { char *cmdname; switch(cmd) { case aSign: cmdname="--sign"; break; case aSignEncr: cmdname="--sign --encrypt"; break; case aClearsign: cmdname="--clear-sign"; break; case aDetachedSign: cmdname="--detach-sign"; break; case aSym: cmdname="--symmetric"; break; case aEncrSym: cmdname="--symmetric --encrypt"; break; case aStore: cmdname="--store"; break; default: cmdname=NULL; break; } if(cmdname) log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile"); } if( log_get_errorcount(0) ) g10_exit(2); if(opt.compress_level==0) opt.compress_algo=COMPRESS_ALGO_NONE; /* Check our chosen algorithms against the list of legal algorithms. */ if(!GNUPG) { const char *badalg=NULL; preftype_t badtype=PREFTYPE_NONE; if(opt.def_cipher_algo && !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL)) { badalg = openpgp_cipher_algo_name (opt.def_cipher_algo); badtype = PREFTYPE_SYM; } else if(opt.def_digest_algo && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.def_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.cert_digest_algo && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.cert_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.compress_algo!=-1 && !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL)) { badalg = compress_algo_to_string(opt.compress_algo); badtype = PREFTYPE_ZIP; } if(badalg) { switch(badtype) { case PREFTYPE_SYM: log_info(_("you may not use cipher algorithm '%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; case PREFTYPE_HASH: log_info(_("you may not use digest algorithm '%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; case PREFTYPE_ZIP: log_info(_("you may not use compression algorithm '%s'" " while in %s mode\n"), badalg,compliance_option_string()); break; default: BUG(); } compliance_failure(); } } /* Set the random seed file. */ if( use_random_seed ) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL ); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); if (!access (p, F_OK)) register_secured_file (p); xfree(p); } /* If there is no command but the --fingerprint is given, default to the --list-keys command. */ if (!cmd && fpr_maybe_cmd) { set_cmd (&cmd, aListKeys); } if( opt.verbose > 1 ) set_packet_list_mode(1); /* Add the keyrings, but not for some special commands. We always * need to add the keyrings if we are running under SELinux, this * is so that the rings are added to the list of secured files. * We do not add any keyring if --no-keyring has been used. */ if (default_keyring >= 0 && (ALWAYS_ADD_KEYRINGS || (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest))) { if (!nrings || default_keyring > 0) /* Add default ring. */ keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, KEYDB_RESOURCE_FLAG_DEFAULT); for (sl = nrings; sl; sl = sl->next ) keydb_add_resource (sl->d, sl->flags); } FREE_STRLIST(nrings); if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) /* In loopback mode, never ask for the password multiple times. */ { opt.passphrase_repeat = 0; } if (cmd == aGPGConfTest) g10_exit(0); if (pwfd != -1) /* Read the passphrase now. */ read_passphrase_from_fd (pwfd); if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */ read_sessionkey_from_fd (ovrseskeyfd); fname = argc? *argv : NULL; if(fname && utf8_strings) opt.flags.utf8_filename=1; ctrl = xcalloc (1, sizeof *ctrl); gpg_init_default_ctrl (ctrl); #ifndef NO_TRUST_MODELS switch (cmd) { case aPrimegen: case aPrintMD: case aPrintMDs: case aGenRandom: case aDeArmor: case aEnArmor: case aListConfig: case aListGcryptConfig: break; case aFixTrustDB: case aExportOwnerTrust: rc = setup_trustdb (0, trustdb_name); break; case aListTrustDB: rc = setup_trustdb (argc? 1:0, trustdb_name); break; case aKeygen: case aFullKeygen: case aQuickKeygen: rc = setup_trustdb (1, trustdb_name); break; default: /* If we are using TM_ALWAYS, we do not need to create the trustdb. */ rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name); break; } if (rc) log_error (_("failed to initialize the TrustDB: %s\n"), gpg_strerror (rc)); #endif /*!NO_TRUST_MODELS*/ switch (cmd) { case aStore: case aSym: case aSign: case aSignSym: case aClearsign: if (!opt.quiet && any_explicit_recipient) log_info (_("WARNING: recipients (-r) given " "without using public key encryption\n")); break; default: break; } /* Check for certain command whether we need to migrate a secring.gpg to the gpg-agent. */ switch (cmd) { case aListSecretKeys: case aSign: case aSignEncr: case aSignEncrSym: case aSignSym: case aClearsign: case aDecrypt: case aSignKey: case aLSignKey: case aEditKey: case aPasswd: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickRevUid: case aQuickSetPrimaryUid: case aFullKeygen: case aKeygen: case aImport: case aExportSecret: case aExportSecretSub: case aGenRevoke: case aDesigRevoke: case aCardEdit: case aChangePIN: migrate_secring (ctrl); break; case aListKeys: if (opt.with_secret) migrate_secring (ctrl); break; default: break; } /* The command dispatcher. */ switch( cmd ) { case aServer: gpg_server (ctrl); break; case aStore: /* only store the file */ if( argc > 1 ) wrong_args("--store [filename]"); if( (rc = encrypt_store(fname)) ) { write_status_failure ("store", rc); log_error ("storing '%s' failed: %s\n", print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aSym: /* encrypt the given file only with the symmetric cipher */ if( argc > 1 ) wrong_args("--symmetric [filename]"); if( (rc = encrypt_symmetric(fname)) ) { write_status_failure ("symencrypt", rc); log_error (_("symmetric encryption of '%s' failed: %s\n"), print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aEncr: /* encrypt the given file */ if(multifile) encrypt_crypt_files (ctrl, argc, argv, remusr); else { if( argc > 1 ) wrong_args("--encrypt [filename]"); if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aEncrSym: /* This works with PGP 8 in the sense that it acts just like a symmetric message. It doesn't work at all with 2 or 6. It might work with 7, but alas, I don't have a copy to test with right now. */ if( argc > 1 ) wrong_args("--symmetric --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --encrypt" " with --s2k-mode 0\n")); else if(PGP6 || PGP7) log_error(_("you cannot use --symmetric --encrypt" " while in %s mode\n"),compliance_option_string()); else { if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error ("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aSign: /* sign the given file */ sl = NULL; if( detached_sig ) { /* sign all files */ for( ; argc; argc--, argv++ ) add_to_strlist( &sl, *argv ); } else { if( argc > 1 ) wrong_args("--sign [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } } if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL))) { write_status_failure ("sign", rc); log_error ("signing failed: %s\n", gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncr: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--sign --encrypt [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncrSym: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--symmetric --sign --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --sign --encrypt" " with --s2k-mode 0\n")); else if(PGP6 || PGP7) log_error(_("you cannot use --symmetric --sign --encrypt" " while in %s mode\n"),compliance_option_string()); else { if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 2, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: symmetric+sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); } break; case aSignSym: /* sign and conventionally encrypt the given file */ if (argc > 1) wrong_args("--sign --symmetric [filename]"); rc = sign_symencrypt_file (ctrl, fname, locusr); if (rc) { write_status_failure ("sign-symencrypt", rc); log_error("%s: sign+symmetric failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aClearsign: /* make a clearsig */ if( argc > 1 ) wrong_args("--clear-sign [filename]"); if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) ) { write_status_failure ("sign", rc); log_error("%s: clear-sign failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aVerify: if (multifile) { if ((rc = verify_files (ctrl, argc, argv))) log_error("verify files failed: %s\n", gpg_strerror (rc) ); } else { if ((rc = verify_signatures (ctrl, argc, argv))) log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); } if (rc) write_status_failure ("verify", rc); break; case aDecrypt: if (multifile) decrypt_messages (ctrl, argc, argv); else { if( argc > 1 ) wrong_args("--decrypt [filename]"); if( (rc = decrypt_message (ctrl, fname) )) { write_status_failure ("decrypt", rc); log_error("decrypt_message failed: %s\n", gpg_strerror (rc) ); } } break; case aQuickSignKey: case aQuickLSignKey: { const char *fpr; if (argc < 1) wrong_args ("--quick-[l]sign-key fingerprint [userids]"); fpr = *argv++; argc--; sl = NULL; for( ; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey)); free_strlist (sl); } break; case aSignKey: if( argc != 1 ) wrong_args("--sign-key user-id"); /* fall through */ case aLSignKey: if( argc != 1 ) wrong_args("--lsign-key user-id"); /* fall through */ sl=NULL; if(cmd==aSignKey) append_to_strlist(&sl,"sign"); else if(cmd==aLSignKey) append_to_strlist(&sl,"lsign"); else BUG(); append_to_strlist( &sl, "save" ); username = make_username( fname ); keyedit_menu (ctrl, username, locusr, sl, 0, 0 ); xfree(username); free_strlist(sl); break; case aEditKey: /* Edit a key signature */ if( !argc ) wrong_args("--edit-key user-id [commands]"); username = make_username( fname ); if( argc > 1 ) { sl = NULL; for( argc--, argv++ ; argc; argc--, argv++ ) append_to_strlist( &sl, *argv ); keyedit_menu (ctrl, username, locusr, sl, 0, 1 ); free_strlist(sl); } else keyedit_menu (ctrl, username, locusr, NULL, 0, 1 ); xfree(username); break; case aPasswd: if (argc != 1) wrong_args("--change-passphrase <user-id>"); else { username = make_username (fname); keyedit_passwd (ctrl, username); xfree (username); } break; case aDeleteKeys: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: sl = NULL; /* I'm adding these in reverse order as add_to_strlist2 reverses them again, and it's easier to understand in the proper order :) */ for( ; argc; argc-- ) add_to_strlist2( &sl, argv[argc-1], utf8_strings ); delete_keys (ctrl, sl, cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys); free_strlist(sl); break; case aCheckKeys: opt.check_sigs = 1; case aListSigs: opt.list_sigs = 1; case aListKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list (ctrl, sl, 0); free_strlist(sl); break; case aListSecretKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); secret_key_list (ctrl, sl); free_strlist(sl); break; case aLocateKeys: sl = NULL; for (; argc; argc--, argv++) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list (ctrl, sl, 1); free_strlist (sl); break; case aQuickKeygen: { const char *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]"); username = make_username (fname); argv++, argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire); xfree (username); } break; case aKeygen: /* generate a key */ if( opt.batch ) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else { if (opt.command_fd != -1 && argc) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); opt.batch = 1; generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else if (argc) wrong_args ("--generate-key"); else generate_keypair (ctrl, 0, NULL, NULL, 0); } break; case aFullKeygen: /* Generate a key with all options. */ if (opt.batch) { if (argc > 1) wrong_args ("--full-generate-key [parameterfile]"); generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0); } else { if (argc) wrong_args("--full-generate-key"); generate_keypair (ctrl, 1, NULL, NULL, 0); } break; case aQuickAddUid: { const char *uid, *newuid; if (argc != 2) wrong_args ("--quick-add-uid USER-ID NEW-USER-ID"); uid = *argv++; argc--; newuid = *argv++; argc--; keyedit_quick_adduid (ctrl, uid, newuid); } break; case aQuickAddKey: { const char *x_fpr, *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]"); x_fpr = *argv++; argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire); } break; case aQuickRevUid: { const char *uid, *uidtorev; if (argc != 2) wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE"); uid = *argv++; argc--; uidtorev = *argv++; argc--; keyedit_quick_revuid (ctrl, uid, uidtorev); } break; case aQuickSetExpire: { const char *x_fpr, *x_expire; if (argc != 2) wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE"); x_fpr = *argv++; argc--; x_expire = *argv++; argc--; keyedit_quick_set_expire (ctrl, x_fpr, x_expire); } break; case aQuickSetPrimaryUid: { const char *uid, *primaryuid; if (argc != 2) wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID"); uid = *argv++; argc--; primaryuid = *argv++; argc--; keyedit_quick_set_primary (ctrl, uid, primaryuid); } break; case aFastImport: opt.import_options |= IMPORT_FAST; case aImport: import_keys (ctrl, argc? argv:NULL, argc, NULL, opt.import_options); break; /* TODO: There are a number of command that use this same "make strlist, call function, report error, free strlist" pattern. Join them together here and avoid all that duplicated code. */ case aExport: case aSendKeys: case aRecvKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); if( cmd == aSendKeys ) rc = keyserver_export (ctrl, sl ); else if( cmd == aRecvKeys ) rc = keyserver_import (ctrl, sl ); else { export_stats_t stats = export_new_stats (); rc = export_pubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } if(rc) { if(cmd==aSendKeys) { write_status_failure ("send-keys", rc); log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc)); } else if(cmd==aRecvKeys) { write_status_failure ("recv-keys", rc); log_error (_("keyserver receive failed: %s\n"), gpg_strerror (rc)); } else { write_status_failure ("export", rc); log_error (_("key export failed: %s\n"), gpg_strerror (rc)); } } free_strlist(sl); break; case aExportSshKey: if (argc != 1) wrong_args ("--export-ssh-key <user-id>"); rc = export_ssh_key (ctrl, argv[0]); if (rc) { write_status_failure ("export-ssh-key", rc); log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc)); } break; case aSearchKeys: sl = NULL; for (; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); rc = keyserver_search (ctrl, sl); if (rc) { write_status_failure ("search-keys", rc); log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc)); } free_strlist (sl); break; case aRefreshKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_refresh (ctrl, sl); if(rc) { write_status_failure ("refresh-keys", rc); log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc)); } free_strlist(sl); break; case aFetchKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_fetch (ctrl, sl); if(rc) { write_status_failure ("fetch-keys", rc); log_error ("key fetch failed: %s\n",gpg_strerror (rc)); } free_strlist(sl); break; case aExportSecret: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_seckeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aExportSecretSub: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_secsubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aGenRevoke: if( argc != 1 ) wrong_args("--generate-revocation user-id"); username = make_username(*argv); gen_revoke (ctrl, username ); xfree( username ); break; case aDesigRevoke: if (argc != 1) wrong_args ("--generate-designated-revocation user-id"); username = make_username (*argv); gen_desig_revoke (ctrl, username, locusr); xfree (username); break; case aDeArmor: if( argc > 1 ) wrong_args("--dearmor [file]"); rc = dearmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("dearmor", rc); log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc)); } break; case aEnArmor: if( argc > 1 ) wrong_args("--enarmor [file]"); rc = enarmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("enarmor", rc); log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc)); } break; case aPrimegen: #if 0 /*FIXME*/ { int mode = argc < 2 ? 0 : atoi(*argv); if( mode == 1 && argc == 2 ) { mpi_print (es_stdout, generate_public_prime( atoi(argv[1]) ), 1); } else if( mode == 2 && argc == 3 ) { mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), NULL,NULL ), 1); } else if( mode == 3 && argc == 3 ) { MPI *factors; mpi_print (es_stdout, generate_elg_prime( 1, atoi(argv[1]), atoi(argv[2]), NULL,&factors ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, factors[0], 1 ); /* print q */ } else if( mode == 4 && argc == 3 ) { MPI g = mpi_alloc(1); mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), g, NULL ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, g, 1 ); mpi_free (g); } else wrong_args("--gen-prime mode bits [qbits] "); es_putc ('\n', es_stdout); } #endif wrong_args("--gen-prime not yet supported "); break; case aGenRandom: { int level = argc ? atoi(*argv):0; int count = argc > 1 ? atoi(argv[1]): 0; int endless = !count; if( argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0 ) wrong_args("--gen-random 0|1|2 [count]"); while( endless || count ) { byte *p; /* Wee need a multiple of 3, so that in case of armored output we get a correct string. No linefolding is done, as it is best to levae this to other tools */ size_t n = !endless && count < 99? count : 99; p = gcry_random_bytes (n, level); #ifdef HAVE_DOSISH_SYSTEM setmode ( fileno(stdout), O_BINARY ); #endif if (opt.armor) { char *tmp = make_radix64_string (p, n); es_fputs (tmp, es_stdout); xfree (tmp); if (n%3 == 1) es_putc ('=', es_stdout); if (n%3) es_putc ('=', es_stdout); } else { es_fwrite( p, n, 1, es_stdout ); } xfree(p); if( !endless ) count -= n; } if (opt.armor) es_putc ('\n', es_stdout); } break; case aPrintMD: if( argc < 1) wrong_args("--print-md algo [files]"); { int all_algos = (**argv=='*' && !(*argv)[1]); int algo = all_algos? 0 : gcry_md_map_name (*argv); if( !algo && !all_algos ) log_error(_("invalid hash algorithm '%s'\n"), *argv ); else { argc--; argv++; if( !argc ) print_mds(NULL, algo); else { for(; argc; argc--, argv++ ) print_mds(*argv, algo); } } } break; case aPrintMDs: /* old option */ if( !argc ) print_mds(NULL,0); else { for(; argc; argc--, argv++ ) print_mds(*argv,0); } break; #ifndef NO_TRUST_MODELS case aListTrustDB: if( !argc ) list_trustdb (ctrl, es_stdout, NULL); else { for( ; argc; argc--, argv++ ) list_trustdb (ctrl, es_stdout, *argv ); } break; case aUpdateTrustDB: if( argc ) wrong_args("--update-trustdb"); update_trustdb (ctrl); break; case aCheckTrustDB: /* Old versions allowed for arguments - ignore them */ check_trustdb (ctrl); break; case aFixTrustDB: how_to_fix_the_trustdb (); break; case aListTrustPath: if( !argc ) wrong_args("--list-trust-path <user-ids>"); for( ; argc; argc--, argv++ ) { username = make_username( *argv ); list_trust_path( username ); xfree(username); } break; case aExportOwnerTrust: if( argc ) wrong_args("--export-ownertrust"); export_ownertrust (ctrl); break; case aImportOwnerTrust: if( argc > 1 ) wrong_args("--import-ownertrust [file]"); import_ownertrust (ctrl, argc? *argv:NULL ); break; #endif /*!NO_TRUST_MODELS*/ case aRebuildKeydbCaches: if (argc) wrong_args ("--rebuild-keydb-caches"); keydb_rebuild_caches (ctrl, 1); break; #ifdef ENABLE_CARD_SUPPORT case aCardStatus: if (argc == 0) card_status (ctrl, es_stdout, NULL); else if (argc == 1) card_status (ctrl, es_stdout, *argv); else wrong_args ("--card-status [serialno]"); break; case aCardEdit: if (argc) { sl = NULL; for (argc--, argv++ ; argc; argc--, argv++) append_to_strlist (&sl, *argv); card_edit (ctrl, sl); free_strlist (sl); } else card_edit (ctrl, NULL); break; case aChangePIN: if (!argc) change_pin (0,1); else if (argc == 1) change_pin (atoi (*argv),1); else wrong_args ("--change-pin [no]"); break; #endif /* ENABLE_CARD_SUPPORT*/ case aListConfig: { char *str=collapse_args(argc,argv); list_config(str); xfree(str); } break; case aListGcryptConfig: /* Fixme: It would be nice to integrate that with --list-config but unfortunately there is no way yet to have libgcrypt print it to an estream for further parsing. */ gcry_control (GCRYCTL_PRINT_CONFIG, stdout); break; case aTOFUPolicy: #ifdef USE_TOFU { int policy; int i; KEYDB_HANDLE hd; if (argc < 2) wrong_args ("--tofu-policy POLICY KEYID [KEYID...]"); policy = parse_tofu_policy (argv[0]); hd = keydb_new (); if (! hd) g10_exit (1); tofu_begin_batch_update (ctrl); for (i = 1; i < argc; i ++) { KEYDB_SEARCH_DESC desc; kbnode_t kb; rc = classify_user_id (argv[i], &desc, 0); if (rc) { log_error (_("error parsing key specification '%s': %s\n"), argv[i], gpg_strerror (rc)); g10_exit (1); } if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID || desc.mode == KEYDB_SEARCH_MODE_LONG_KID || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_FPR20 || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { log_error (_("'%s' does not appear to be a valid" " key ID, fingerprint or keygrip\n"), argv[i]); g10_exit (1); } rc = keydb_search_reset (hd); if (rc) { /* This should not happen, thus no need to tranalate the string. */ log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc)); g10_exit (1); } rc = keydb_search (hd, &desc, 1, NULL); if (rc) { log_error (_("key \"%s\" not found: %s\n"), argv[i], gpg_strerror (rc)); g10_exit (1); } rc = keydb_get_keyblock (hd, &kb); if (rc) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc)); g10_exit (1); } merge_keys_and_selfsig (ctrl, kb); if (tofu_set_policy (ctrl, kb, policy)) g10_exit (1); release_kbnode (kb); } tofu_end_batch_update (ctrl); keydb_release (hd); } #endif /*USE_TOFU*/ break; default: if (!opt.quiet) log_info (_("WARNING: no command supplied." " Trying to guess what you mean ...\n")); /*FALLTHU*/ case aListPackets: if( argc > 1 ) wrong_args("[filename]"); /* Issue some output for the unix newbie */ if (!fname && !opt.outfile && gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)) && gnupg_isatty (fileno (stderr))) log_info(_("Go ahead and type your message ...\n")); a = iobuf_open(fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if( !a ) log_error(_("can't open '%s'\n"), print_fname_stdin(fname)); else { if( !opt.no_armor ) { if( use_armor_filter( a ) ) { afx = new_armor_context (); push_armor_filter (afx, a); } } if( cmd == aListPackets ) { opt.list_packets=1; set_packet_list_mode(1); } rc = proc_packets (ctrl, NULL, a ); if( rc ) { write_status_failure ("-", rc); log_error ("processing message failed: %s\n", gpg_strerror (rc)); } iobuf_close(a); } break; } /* cleanup */ gpg_deinit_default_ctrl (ctrl); xfree (ctrl); release_armor_context (afx); FREE_STRLIST(remusr); FREE_STRLIST(locusr); g10_exit(0); return 8; /*NEVER REACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void g10_exit( int rc ) { gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (DBG_CLOCK) log_clock ("stop"); if ( (opt.debug & DBG_MEMSTAT_VALUE) ) { keydb_dump_stats (); sig_check_dump_stats (); gcry_control (GCRYCTL_DUMP_MEMORY_STATS); gcry_control (GCRYCTL_DUMP_RANDOM_STATS); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; exit (rc); } /* Pretty-print hex hashes. This assumes at least an 80-character display, but there are a few other similar assumptions in the display code. */ static void print_hex (gcry_md_hd_t md, int algo, const char *fname) { int i,n,count,indent=0; const byte *p; if (fname) indent = es_printf("%s: ",fname); if (indent>40) { es_printf ("\n"); indent=0; } if (algo==DIGEST_ALGO_RMD160) indent += es_printf("RMD160 = "); else if (algo>0) indent += es_printf("%6s = ", gcry_md_algo_name (algo)); else algo = abs(algo); count = indent; p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); count += es_printf ("%02X",*p++); for(i=1;i<n;i++,p++) { if(n==16) { if(count+2>79) { es_printf ("\n%*s",indent," "); count = indent; } else count += es_printf(" "); if (!(i%8)) count += es_printf(" "); } else if (n==20) { if(!(i%2)) { if(count+4>79) { es_printf ("\n%*s",indent," "); count=indent; } else count += es_printf(" "); } if (!(i%10)) count += es_printf(" "); } else { if(!(i%4)) { if (count+8>79) { es_printf ("\n%*s",indent," "); count=indent; } else count += es_printf(" "); } } count += es_printf("%02X",*p); } es_printf ("\n"); } static void print_hashline( gcry_md_hd_t md, int algo, const char *fname ) { int i, n; const byte *p; if ( fname ) { for (p = fname; *p; p++ ) { if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) es_printf ("%%%02X", *p ); else es_putc (*p, es_stdout); } } es_putc (':', es_stdout); es_printf ("%d:", algo); p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); for(i=0; i < n ; i++, p++ ) es_printf ("%02X", *p); es_fputs (":\n", es_stdout); } static void print_mds( const char *fname, int algo ) { estream_t fp; char buf[1024]; size_t n; gcry_md_hd_t md; if (!fname) { fp = es_stdin; es_set_binary (fp); } else { fp = es_fopen (fname, "rb" ); if (fp && is_secured_file (es_fileno (fp))) { es_fclose (fp); fp = NULL; gpg_err_set_errno (EPERM); } } if (!fp) { log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); return; } gcry_md_open (&md, 0, 0); if (algo) gcry_md_enable (md, algo); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) gcry_md_enable (md, GCRY_MD_MD5); gcry_md_enable (md, GCRY_MD_SHA1); if (!gcry_md_test_algo (GCRY_MD_RMD160)) gcry_md_enable (md, GCRY_MD_RMD160); if (!gcry_md_test_algo (GCRY_MD_SHA224)) gcry_md_enable (md, GCRY_MD_SHA224); if (!gcry_md_test_algo (GCRY_MD_SHA256)) gcry_md_enable (md, GCRY_MD_SHA256); if (!gcry_md_test_algo (GCRY_MD_SHA384)) gcry_md_enable (md, GCRY_MD_SHA384); if (!gcry_md_test_algo (GCRY_MD_SHA512)) gcry_md_enable (md, GCRY_MD_SHA512); } while ((n=es_fread (buf, 1, DIM(buf), fp))) gcry_md_write (md, buf, n); if (es_ferror(fp)) log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno)); else { gcry_md_final (md); if (opt.with_colons) { if ( algo ) print_hashline (md, algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hashline( md, GCRY_MD_MD5, fname ); print_hashline( md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hashline( md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hashline (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hashline( md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hashline ( md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hashline ( md, GCRY_MD_SHA512, fname ); } } else { if (algo) print_hex (md, -algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hex (md, GCRY_MD_MD5, fname); print_hex (md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hex (md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hex (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hex (md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hex (md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hex (md, GCRY_MD_SHA512, fname ); } } } gcry_md_close (md); if (fp != es_stdin) es_fclose (fp); } /**************** * Check the supplied name,value string and add it to the notation * data to be used for signatures. which==0 for sig notations, and 1 * for cert notations. */ static void add_notation_data( const char *string, int which ) { struct notation *notation; notation=string_to_notation(string,utf8_strings); if(notation) { if(which) { notation->next=opt.cert_notations; opt.cert_notations=notation; } else { notation->next=opt.sig_notations; opt.sig_notations=notation; } } } static void add_policy_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;i<strlen(string);i++) if( !isascii (string[i]) || iscntrl(string[i])) break; if(i==0 || i<strlen(string)) { if(which) log_error(_("the given certification policy URL is invalid\n")); else log_error(_("the given signature policy URL is invalid\n")); } if(which) sl=add_to_strlist( &opt.cert_policy_url, string ); else sl=add_to_strlist( &opt.sig_policy_url, string ); if(critical) sl->flags |= 1; } static void add_keyserver_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;i<strlen(string);i++) if( !isascii (string[i]) || iscntrl(string[i])) break; if(i==0 || i<strlen(string)) { if(which) BUG(); else log_error(_("the given preferred keyserver URL is invalid\n")); } if(which) BUG(); else sl=add_to_strlist( &opt.sig_keyserver_url, string ); if(critical) sl->flags |= 1; } static void read_sessionkey_from_fd (int fd) { int i, len; char *line; if (! gnupg_fd_valid (fd)) log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno)); for (line = NULL, i = len = 100; ; i++ ) { if (i >= len-1 ) { char *tmp = line; len += 100; line = xmalloc_secure (len); if (tmp) { memcpy (line, tmp, i); xfree (tmp); } else i=0; } if (read (fd, line + i, 1) != 1 || line[i] == '\n') break; } line[i] = 0; log_debug ("seskey: %s\n", line); gpgrt_annotate_leaked_object (line); opt.override_session_key = line; } diff --git a/g10/import.c b/g10/import.c index ba1c44a43..a942c2e5f 100644 --- a/g10/import.c +++ b/g10/import.c @@ -1,3429 +1,3429 @@ /* import.c - import a key into our key storage. * Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc. * Copyright (C) 2014, 2016 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "keydb.h" #include "../common/util.h" #include "trustdb.h" #include "main.h" #include "../common/i18n.h" #include "../common/ttyio.h" #include "../common/recsel.h" #include "keyserver-internal.h" #include "call-agent.h" #include "../common/membuf.h" #include "../common/init.h" #include "../common/mbox-util.h" struct import_stats_s { ulong count; ulong no_user_id; ulong imported; ulong n_uids; ulong n_sigs; ulong n_subk; ulong unchanged; ulong n_revoc; ulong secret_read; ulong secret_imported; ulong secret_dups; ulong skipped_new_keys; ulong not_imported; ulong n_sigs_cleaned; ulong n_uids_cleaned; ulong v3keys; /* Number of V3 keys seen. */ }; /* Node flag to indicate that a user ID or a subkey has a * valid self-signature. */ #define NODE_GOOD_SELFSIG 1 /* Node flag to indicate that a user ID or subkey has * an invalid self-signature. */ #define NODE_BAD_SELFSIG 2 /* Node flag to indicate that the node shall be deleted. */ #define NODE_DELETION_MARK 4 /* A node flag used to temporary mark a node. */ #define NODE_FLAG_A 8 /* An object and a global instance to store selectors created from * --import-filter keep-uid=EXPR. * --import-filter drop-sig=EXPR. * * FIXME: We should put this into the CTRL object but that requires a * lot more changes right now. For now we use save and restore * function to temporary change them. */ /* Definition of the import filters. */ struct import_filter_s { recsel_expr_t keep_uid; recsel_expr_t drop_sig; }; /* The current instance. */ struct import_filter_s import_filter; static int import (ctrl_t ctrl, IOBUF inp, const char* fname, struct import_stats_s *stats, unsigned char **fpr, size_t *fpr_len, unsigned int options, import_screener_t screener, void *screener_arg); static int read_block (IOBUF a, int with_meta, PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys); static void revocation_present (ctrl_t ctrl, kbnode_t keyblock); static int import_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg); static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, import_screener_t screener, void *screener_arg); static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, struct import_stats_s *stats); static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self); static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, unsigned int options); static int any_uid_left (kbnode_t keyblock); static int merge_blocks (ctrl_t ctrl, kbnode_t keyblock_orig, kbnode_t keyblock, u32 *keyid, int *n_uids, int *n_sigs, int *n_subk ); static int append_uid (kbnode_t keyblock, kbnode_t node, int *n_sigs); static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs); static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs); static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs); static void release_import_filter (import_filter_t filt) { recsel_release (filt->keep_uid); filt->keep_uid = NULL; recsel_release (filt->drop_sig); filt->drop_sig = NULL; } static void cleanup_import_globals (void) { release_import_filter (&import_filter); } int parse_import_options(char *str,unsigned int *options,int noisy) { struct parse_options import_opts[]= { {"import-local-sigs",IMPORT_LOCAL_SIGS,NULL, N_("import signatures that are marked as local-only")}, {"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL, N_("repair damage from the pks keyserver during import")}, {"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL, N_("do not clear the ownertrust values during import")}, {"fast-import",IMPORT_FAST,NULL, N_("do not update the trustdb after import")}, {"import-show",IMPORT_SHOW,NULL, N_("show key during import")}, {"merge-only",IMPORT_MERGE_ONLY,NULL, N_("only accept updates to existing keys")}, {"import-clean",IMPORT_CLEAN,NULL, N_("remove unusable parts from key after import")}, {"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL, N_("remove as much as possible from key after import")}, {"import-export", IMPORT_EXPORT, NULL, N_("run import filters and export key immediately")}, {"restore", IMPORT_RESTORE, NULL, N_("assume the GnuPG key backup format")}, {"import-restore", IMPORT_RESTORE, NULL, NULL}, /* Aliases for backward compatibility */ {"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL}, {"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL}, /* dummy */ {"import-unusable-sigs",0,NULL,NULL}, {"import-clean-sigs",0,NULL,NULL}, {"import-clean-uids",0,NULL,NULL}, {"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to the new design. */ {NULL,0,NULL,NULL} }; int rc; rc = parse_options (str, options, import_opts, noisy); if (rc && (*options & IMPORT_RESTORE)) { /* Alter other options we want or don't want for restore. */ *options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST); *options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN | IMPORT_REPAIR_PKS_SUBKEY_BUG | IMPORT_MERGE_ONLY); } return rc; } /* Parse and set an import filter from string. STRING has the format * "NAME=EXPR" with NAME being the name of the filter. Spaces before * and after NAME are not allowed. If this function is all called * several times all expressions for the same NAME are concatenated. * Supported filter names are: * * - keep-uid :: If the expression evaluates to true for a certain * user ID packet, that packet and all it dependencies * will be imported. The expression may use these * variables: * * - uid :: The entire user ID. * - mbox :: The mail box part of the user ID. * - primary :: Evaluate to true for the primary user ID. */ gpg_error_t parse_and_set_import_filter (const char *string) { gpg_error_t err; /* Auto register the cleanup function. */ register_mem_cleanup_func (cleanup_import_globals); if (!strncmp (string, "keep-uid=", 9)) err = recsel_parse_expr (&import_filter.keep_uid, string+9); else if (!strncmp (string, "drop-sig=", 9)) err = recsel_parse_expr (&import_filter.drop_sig, string+9); else err = gpg_error (GPG_ERR_INV_NAME); return err; } /* Save the current import filters, return them, and clear the current * filters. Returns NULL on error and sets ERRNO. */ import_filter_t save_and_clear_import_filter (void) { import_filter_t filt; filt = xtrycalloc (1, sizeof *filt); if (!filt) return NULL; *filt = import_filter; memset (&import_filter, 0, sizeof import_filter); return filt; } /* Release the current import filters and restore them from NEWFILT. * Ownership of NEWFILT is moved to this function. */ void restore_import_filter (import_filter_t filt) { if (filt) { release_import_filter (&import_filter); import_filter = *filt; xfree (filt); } } import_stats_t import_new_stats_handle (void) { return xmalloc_clear ( sizeof (struct import_stats_s) ); } void import_release_stats_handle (import_stats_t p) { xfree (p); } /* Read a key from a file. Only the first key in the file is * considered and stored at R_KEYBLOCK. FNAME is the name of the * file. */ gpg_error_t read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock) { gpg_error_t err; iobuf_t inp; PACKET *pending_pkt = NULL; kbnode_t keyblock = NULL; u32 keyid[2]; int v3keys; /* Dummy */ int non_self; /* Dummy */ (void)ctrl; *r_keyblock = NULL; inp = iobuf_open (fname); if (!inp) err = gpg_error_from_syserror (); else if (is_secured_file (iobuf_get_fd (inp))) { iobuf_close (inp); inp = NULL; err = gpg_error (GPG_ERR_EPERM); } else err = 0; if (err) { log_error (_("can't open '%s': %s\n"), iobuf_is_pipe_filename (fname)? "[stdin]": fname, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } /* Push the armor filter. */ { armor_filter_context_t *afx; afx = new_armor_context (); afx->only_keyblocks = 1; push_armor_filter (afx, inp); release_armor_context (afx); } /* Read the first non-v3 keyblock. */ while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) { if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) break; log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype); release_kbnode (keyblock); keyblock = NULL; } if (err) { if (gpg_err_code (err) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), iobuf_is_pipe_filename (fname)? "[stdin]": fname, gpg_strerror (err)); goto leave; } keyid_from_pk (keyblock->pkt->pkt.public_key, keyid); if (!find_next_kbnode (keyblock, PKT_USER_ID)) { err = gpg_error (GPG_ERR_NO_USER_ID); goto leave; } collapse_uids (&keyblock); clear_kbnode_flags (keyblock); if (chk_self_sigs (ctrl, keyblock, keyid, &non_self)) { err = gpg_error (GPG_ERR_INV_KEYRING); goto leave; } if (!delete_inv_parts (ctrl, keyblock, keyid, 0) ) { err = gpg_error (GPG_ERR_NO_USER_ID); goto leave; } *r_keyblock = keyblock; keyblock = NULL; leave: if (inp) { iobuf_close (inp); /* Must invalidate that ugly cache to actually close the file. */ iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); } release_kbnode (keyblock); /* FIXME: Do we need to free PENDING_PKT ? */ return err; } /* * Import the public keys from the given filename. Input may be armored. * This function rejects all keys which are not validly self signed on at * least one userid. Only user ids which are self signed will be imported. * Other signatures are not checked. * * Actually this function does a merge. It works like this: * * - get the keyblock * - check self-signatures and remove all userids and their signatures * without/invalid self-signatures. * - reject the keyblock, if we have no valid userid. * - See whether we have this key already in one of our pubrings. * If not, simply add it to the default keyring. * - Compare the key and the self-signatures of the new and the one in * our keyring. If they are different something weird is going on; * ask what to do. * - See whether we have only non-self-signature on one user id; if not * ask the user what to do. * - compare the signatures: If we already have this signature, check * that they compare okay; if not, issue a warning and ask the user. * (consider looking at the timestamp and use the newest?) * - Simply add the signature. Can't verify here because we may not have * the signature's public key yet; verification is done when putting it * into the trustdb, which is done automagically as soon as this pubkey * is used. * - Proceed with next signature. * * Key revocation certificates have special handling. */ static int import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames, import_stats_t stats_handle, unsigned char **fpr, size_t *fpr_len, unsigned int options, import_screener_t screener, void *screener_arg) { int i; int rc = 0; struct import_stats_s *stats = stats_handle; if (!stats) stats = import_new_stats_handle (); if (inp) { rc = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options, screener, screener_arg); } else { if (!fnames && !nnames) nnames = 1; /* Ohh what a ugly hack to jump into the loop */ for (i=0; i < nnames; i++) { const char *fname = fnames? fnames[i] : NULL; IOBUF inp2 = iobuf_open(fname); if (!fname) fname = "[stdin]"; if (inp2 && is_secured_file (iobuf_get_fd (inp2))) { iobuf_close (inp2); inp2 = NULL; gpg_err_set_errno (EPERM); } if (!inp2) log_error (_("can't open '%s': %s\n"), fname, strerror (errno)); else { rc = import (ctrl, inp2, fname, stats, fpr, fpr_len, options, screener, screener_arg); iobuf_close (inp2); /* Must invalidate that ugly cache to actually close it. */ iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); if (rc) log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (rc) ); } if (!fname) break; } } if (!stats_handle) { import_print_stats (stats); import_release_stats_handle (stats); } /* If no fast import and the trustdb is dirty (i.e. we added a key or userID that had something other than a selfsig, a signature that was other than a selfsig, or any revocation), then update/check the trustdb if the user specified by setting interactive or by not setting no-auto-check-trustdb */ if (!(options & IMPORT_FAST)) check_or_update_trustdb (ctrl); return rc; } void import_keys (ctrl_t ctrl, char **fnames, int nnames, import_stats_t stats_handle, unsigned int options ) { import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle, NULL, NULL, options, NULL, NULL); } int import_keys_stream (ctrl_t ctrl, IOBUF inp, import_stats_t stats_handle, unsigned char **fpr, size_t *fpr_len, unsigned int options) { return import_keys_internal (ctrl, inp, NULL, 0, stats_handle, fpr, fpr_len, options, NULL, NULL); } /* Variant of import_keys_stream reading from an estream_t. */ int import_keys_es_stream (ctrl_t ctrl, estream_t fp, import_stats_t stats_handle, unsigned char **fpr, size_t *fpr_len, unsigned int options, import_screener_t screener, void *screener_arg) { int rc; iobuf_t inp; inp = iobuf_esopen (fp, "rb", 1); if (!inp) { rc = gpg_error_from_syserror (); log_error ("iobuf_esopen failed: %s\n", gpg_strerror (rc)); return rc; } rc = import_keys_internal (ctrl, inp, NULL, 0, stats_handle, fpr, fpr_len, options, screener, screener_arg); iobuf_close (inp); return rc; } static int import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats, unsigned char **fpr,size_t *fpr_len, unsigned int options, import_screener_t screener, void *screener_arg) { PACKET *pending_pkt = NULL; kbnode_t keyblock = NULL; /* Need to initialize because gcc can't grasp the return semantics of read_block. */ int rc = 0; int v3keys; getkey_disable_caches (); if (!opt.no_armor) /* Armored reading is not disabled. */ { armor_filter_context_t *afx; afx = new_armor_context (); afx->only_keyblocks = 1; push_armor_filter (afx, inp); release_armor_context (afx); } while (!(rc = read_block (inp, !!(options & IMPORT_RESTORE), &pending_pkt, &keyblock, &v3keys))) { stats->v3keys += v3keys; if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY) rc = import_one (ctrl, keyblock, stats, fpr, fpr_len, options, 0, 0, screener, screener_arg); else if (keyblock->pkt->pkttype == PKT_SECRET_KEY) rc = import_secret_one (ctrl, keyblock, stats, opt.batch, options, 0, screener, screener_arg); else if (keyblock->pkt->pkttype == PKT_SIGNATURE && keyblock->pkt->pkt.signature->sig_class == 0x20 ) rc = import_revoke_cert (ctrl, keyblock, stats); else { log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype); } release_kbnode (keyblock); /* fixme: we should increment the not imported counter but this does only make sense if we keep on going despite of errors. For now we do this only if the imported key is too large. */ if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE && gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX) { stats->not_imported++; } else if (rc) break; if (!(++stats->count % 100) && !opt.quiet) log_info (_("%lu keys processed so far\n"), stats->count ); } stats->v3keys += v3keys; if (rc == -1) rc = 0; else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc)); return rc; } /* Helper to migrate secring.gpg to GnuPG 2.1. */ gpg_error_t import_old_secring (ctrl_t ctrl, const char *fname) { gpg_error_t err; iobuf_t inp; PACKET *pending_pkt = NULL; kbnode_t keyblock = NULL; /* Need to initialize because gcc can't grasp the return semantics of read_block. */ struct import_stats_s *stats; int v3keys; inp = iobuf_open (fname); if (inp && is_secured_file (iobuf_get_fd (inp))) { iobuf_close (inp); inp = NULL; gpg_err_set_errno (EPERM); } if (!inp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } getkey_disable_caches(); stats = import_new_stats_handle (); while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys))) { if (keyblock->pkt->pkttype == PKT_SECRET_KEY) err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1, NULL, NULL); release_kbnode (keyblock); if (err) break; } import_release_stats_handle (stats); if (err == -1) err = 0; else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING) log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); else if (err) log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err)); iobuf_close (inp); iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname); return err; } void import_print_stats (import_stats_t stats) { if (!opt.quiet) { log_info(_("Total number processed: %lu\n"), stats->count + stats->v3keys); if (stats->v3keys) log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys); if (stats->skipped_new_keys ) log_info(_(" skipped new keys: %lu\n"), stats->skipped_new_keys ); if (stats->no_user_id ) log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id ); if (stats->imported) { log_info(_(" imported: %lu"), stats->imported ); log_printf ("\n"); } if (stats->unchanged ) log_info(_(" unchanged: %lu\n"), stats->unchanged ); if (stats->n_uids ) log_info(_(" new user IDs: %lu\n"), stats->n_uids ); if (stats->n_subk ) log_info(_(" new subkeys: %lu\n"), stats->n_subk ); if (stats->n_sigs ) log_info(_(" new signatures: %lu\n"), stats->n_sigs ); if (stats->n_revoc ) log_info(_(" new key revocations: %lu\n"), stats->n_revoc ); if (stats->secret_read ) log_info(_(" secret keys read: %lu\n"), stats->secret_read ); if (stats->secret_imported ) log_info(_(" secret keys imported: %lu\n"), stats->secret_imported ); if (stats->secret_dups ) log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups ); if (stats->not_imported ) log_info(_(" not imported: %lu\n"), stats->not_imported ); if (stats->n_sigs_cleaned) log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned); if (stats->n_uids_cleaned) log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned); } if (is_status_enabled ()) { char buf[15*20]; snprintf (buf, sizeof buf, "%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", stats->count + stats->v3keys, stats->no_user_id, stats->imported, stats->unchanged, stats->n_uids, stats->n_subk, stats->n_sigs, stats->n_revoc, stats->secret_read, stats->secret_imported, stats->secret_dups, stats->skipped_new_keys, stats->not_imported, stats->v3keys ); write_status_text (STATUS_IMPORT_RES, buf); } } /* Return true if PKTTYPE is valid in a keyblock. */ static int valid_keyblock_packet (int pkttype) { switch (pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_SECRET_KEY: case PKT_SECRET_SUBKEY: case PKT_SIGNATURE: case PKT_USER_ID: case PKT_ATTRIBUTE: case PKT_RING_TRUST: return 1; default: return 0; } } /**************** * Read the next keyblock from stream A. * Meta data (ring trust packets) are only considered of WITH_META is set. - * PENDING_PKT should be initialzed to NULL and not changed by the caller. + * PENDING_PKT should be initialized to NULL and not changed by the caller. * Return: 0 = okay, -1 no more blocks or another errorcode. * The int at at R_V3KEY counts the number of unsupported v3 * keyblocks. */ static int read_block( IOBUF a, int with_meta, PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys) { int rc; struct parse_packet_ctx_s parsectx; PACKET *pkt; kbnode_t root = NULL; int in_cert, in_v3key; *r_v3keys = 0; if (*pending_pkt) { root = new_kbnode( *pending_pkt ); *pending_pkt = NULL; in_cert = 1; } else in_cert = 0; pkt = xmalloc (sizeof *pkt); init_packet (pkt); init_parse_packet (&parsectx, a); if (!with_meta) parsectx.skip_meta = 1; in_v3key = 0; while ((rc=parse_packet (&parsectx, pkt)) != -1) { if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY && (pkt->pkttype == PKT_PUBLIC_KEY || pkt->pkttype == PKT_SECRET_KEY))) { in_v3key = 1; ++*r_v3keys; free_packet (pkt, &parsectx); init_packet (pkt); continue; } else if (rc ) /* (ignore errors) */ { if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) ; /* Do not show a diagnostic. */ else { log_error("read_block: read error: %s\n", gpg_strerror (rc) ); rc = GPG_ERR_INV_KEYRING; goto ready; } free_packet (pkt, &parsectx); init_packet(pkt); continue; } if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY || pkt->pkttype == PKT_SECRET_KEY)) { free_packet (pkt, &parsectx); init_packet(pkt); continue; } in_v3key = 0; if (!root && pkt->pkttype == PKT_SIGNATURE && pkt->pkt.signature->sig_class == 0x20 ) { /* This is a revocation certificate which is handled in a * special way. */ root = new_kbnode( pkt ); pkt = NULL; goto ready; } /* Make a linked list of all packets. */ switch (pkt->pkttype) { case PKT_COMPRESSED: if (check_compress_algo (pkt->pkt.compressed->algorithm)) { rc = GPG_ERR_COMPR_ALGO; goto ready; } else { compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx ); pkt->pkt.compressed->buf = NULL; push_compress_filter2(a,cfx,pkt->pkt.compressed->algorithm,1); } free_packet (pkt, &parsectx); init_packet(pkt); break; case PKT_RING_TRUST: /* Skip those packets unless we are in restore mode. */ if ((opt.import_options & IMPORT_RESTORE)) goto x_default; free_packet (pkt, &parsectx); init_packet(pkt); break; case PKT_PUBLIC_KEY: case PKT_SECRET_KEY: if (in_cert ) /* Store this packet. */ { *pending_pkt = pkt; pkt = NULL; goto ready; } in_cert = 1; default: x_default: if (in_cert && valid_keyblock_packet (pkt->pkttype)) { if (!root ) root = new_kbnode (pkt); else add_kbnode (root, new_kbnode (pkt)); pkt = xmalloc (sizeof *pkt); } init_packet(pkt); break; } } ready: if (rc == -1 && root ) rc = 0; if (rc ) release_kbnode( root ); else *ret_root = root; free_packet (pkt, &parsectx); deinit_parse_packet (&parsectx); xfree( pkt ); return rc; } /* Walk through the subkeys on a pk to find if we have the PKS disease: multiple subkeys with their binding sigs stripped, and the sig for the first subkey placed after the last subkey. That is, instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have "pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2 and sub3, as they are already lost, but we can try and rescue sub1 by reordering the keyblock so that it reads "pk uid sig sub1 bind1 sub2 sub3". Returns TRUE if the keyblock was modified. */ static int fix_pks_corruption (ctrl_t ctrl, kbnode_t keyblock) { int changed = 0; int keycount = 0; kbnode_t node; kbnode_t last = NULL; kbnode_t sknode=NULL; /* First determine if we have the problem at all. Look for 2 or more subkeys in a row, followed by a single binding sig. */ for (node=keyblock; node; last=node, node=node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { keycount++; if(!sknode) sknode=node; } else if (node->pkt->pkttype == PKT_SIGNATURE && node->pkt->pkt.signature->sig_class == 0x18 && keycount >= 2 && !node->next) { /* We might have the problem, as this key has two subkeys in a row without any intervening packets. */ /* Sanity check */ if (!last) break; /* Temporarily attach node to sknode. */ node->next = sknode->next; sknode->next = node; last->next = NULL; /* Note we aren't checking whether this binding sig is a selfsig. This is not necessary here as the subkey and binding sig will be rejected later if that is the case. */ if (check_key_signature (ctrl, keyblock,node,NULL)) { /* Not a match, so undo the changes. */ sknode->next = node->next; last->next = node; node->next = NULL; break; } else { /* Mark it good so we don't need to check it again */ sknode->flag |= NODE_GOOD_SELFSIG; changed = 1; break; } } else keycount = 0; } return changed; } /* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus direct key signatures. A side effect of this was that a later import of the same good direct key signatures was not possible because the cmp_signature check in merge_blocks considered them equal. Although direct key signatures are now checked during import, there might still be bogus signatures sitting in a keyring. We need to detect and delete them before doing a merge. This function returns the number of removed sigs. */ static int fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid) { gpg_error_t err; kbnode_t node; int count = 0; for (node = keyblock->next; node; node=node->next) { if (node->pkt->pkttype == PKT_USER_ID) break; if (node->pkt->pkttype == PKT_SIGNATURE && IS_KEY_SIG (node->pkt->pkt.signature)) { err = check_key_signature (ctrl, keyblock, node, NULL); if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO ) { /* If we don't know the error, we can't decide; this is not a problem because cmp_signature can't compare the signature either. */ log_info ("key %s: invalid direct key signature removed\n", keystr (keyid)); delete_kbnode (node); count++; } } } return count; } static void print_import_ok (PKT_public_key *pk, unsigned int reason) { byte array[MAX_FINGERPRINT_LEN], *s; char buf[MAX_FINGERPRINT_LEN*2+30], *p; size_t i, n; snprintf (buf, sizeof buf, "%u ", reason); p = buf + strlen (buf); fingerprint_from_pk (pk, array, &n); s = array; for (i=0; i < n ; i++, s++, p += 2) sprintf (p, "%02X", *s); write_status_text (STATUS_IMPORT_OK, buf); } static void print_import_check (PKT_public_key * pk, PKT_user_id * id) { char * buf; byte fpr[24]; u32 keyid[2]; size_t i, n; size_t pos = 0; buf = xmalloc (17+41+id->len+32); keyid_from_pk (pk, keyid); sprintf (buf, "%08X%08X ", keyid[0], keyid[1]); pos = 17; fingerprint_from_pk (pk, fpr, &n); for (i = 0; i < n; i++, pos += 2) sprintf (buf+pos, "%02X", fpr[i]); strcat (buf, " "); strcat (buf, id->name); write_status_text (STATUS_IMPORT_CHECK, buf); xfree (buf); } static void check_prefs_warning(PKT_public_key *pk) { log_info(_("WARNING: key %s contains preferences for unavailable\n" "algorithms on these user IDs:\n"), keystr_from_pk(pk)); } static void check_prefs (ctrl_t ctrl, kbnode_t keyblock) { kbnode_t node; PKT_public_key *pk; int problem=0; merge_keys_and_selfsig (ctrl, keyblock); pk=keyblock->pkt->pkt.public_key; for(node=keyblock;node;node=node->next) { if(node->pkt->pkttype==PKT_USER_ID && node->pkt->pkt.user_id->created && node->pkt->pkt.user_id->prefs) { PKT_user_id *uid = node->pkt->pkt.user_id; prefitem_t *prefs = uid->prefs; char *user = utf8_to_native(uid->name,strlen(uid->name),0); for(;prefs->type;prefs++) { char num[10]; /* prefs->value is a byte, so we're over safe here */ sprintf(num,"%u",prefs->value); if(prefs->type==PREFTYPE_SYM) { if (openpgp_cipher_test_algo (prefs->value)) { const char *algo = (openpgp_cipher_test_algo (prefs->value) ? num : openpgp_cipher_algo_name (prefs->value)); if(!problem) check_prefs_warning(pk); log_info(_(" \"%s\": preference for cipher" " algorithm %s\n"), user, algo); problem=1; } } else if(prefs->type==PREFTYPE_HASH) { if(openpgp_md_test_algo(prefs->value)) { const char *algo = (gcry_md_test_algo (prefs->value) ? num : gcry_md_algo_name (prefs->value)); if(!problem) check_prefs_warning(pk); log_info(_(" \"%s\": preference for digest" " algorithm %s\n"), user, algo); problem=1; } } else if(prefs->type==PREFTYPE_ZIP) { if(check_compress_algo (prefs->value)) { const char *algo=compress_algo_to_string(prefs->value); if(!problem) check_prefs_warning(pk); log_info(_(" \"%s\": preference for compression" " algorithm %s\n"),user,algo?algo:num); problem=1; } } } xfree(user); } } if(problem) { log_info(_("it is strongly suggested that you update" " your preferences and\n")); log_info(_("re-distribute this key to avoid potential algorithm" " mismatch problems\n")); if(!opt.batch) { strlist_t sl = NULL; strlist_t locusr = NULL; size_t fprlen=0; byte fpr[MAX_FINGERPRINT_LEN], *p; char username[(MAX_FINGERPRINT_LEN*2)+1]; unsigned int i; p = fingerprint_from_pk (pk,fpr,&fprlen); for(i=0;i<fprlen;i++,p++) sprintf(username+2*i,"%02X",*p); add_to_strlist(&locusr,username); append_to_strlist(&sl,"updpref"); append_to_strlist(&sl,"save"); keyedit_menu (ctrl, username, locusr, sl, 1, 1 ); free_strlist(sl); free_strlist(locusr); } else if(!opt.quiet) log_info(_("you can update your preferences with:" " gpg --edit-key %s updpref save\n"),keystr_from_pk(pk)); } } /* Helper for apply_*_filter in import.c and export.c. */ const char * impex_filter_getval (void *cookie, const char *propname) { /* FIXME: Malloc our static buffers and access them via PARM. */ struct impex_filter_parm_s *parm = cookie; ctrl_t ctrl = parm->ctrl; kbnode_t node = parm->node; static char numbuf[20]; const char *result; log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); if (node->pkt->pkttype == PKT_USER_ID || node->pkt->pkttype == PKT_ATTRIBUTE) { PKT_user_id *uid = node->pkt->pkt.user_id; if (!strcmp (propname, "uid")) result = uid->name; else if (!strcmp (propname, "mbox")) { if (!uid->mbox) { uid->mbox = mailbox_from_userid (uid->name); } result = uid->mbox; } else if (!strcmp (propname, "primary")) { result = uid->flags.primary? "1":"0"; } else if (!strcmp (propname, "expired")) { result = uid->flags.expired? "1":"0"; } else if (!strcmp (propname, "revoked")) { result = uid->flags.revoked? "1":"0"; } else result = NULL; } else if (node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (!strcmp (propname, "sig_created")) { snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp); result = numbuf; } else if (!strcmp (propname, "sig_created_d")) { result = datestr_from_sig (sig); } else if (!strcmp (propname, "sig_algo")) { snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo); result = numbuf; } else if (!strcmp (propname, "sig_digest_algo")) { snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo); result = numbuf; } else if (!strcmp (propname, "expired")) { result = sig->flags.expired? "1":"0"; } else result = NULL; } else if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { PKT_public_key *pk = node->pkt->pkt.public_key; if (!strcmp (propname, "secret")) { result = (node->pkt->pkttype == PKT_SECRET_KEY || node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0"; } else if (!strcmp (propname, "key_algo")) { snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo); result = numbuf; } else if (!strcmp (propname, "key_created")) { snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp); result = numbuf; } else if (!strcmp (propname, "key_created_d")) { result = datestr_from_pk (pk); } else if (!strcmp (propname, "expired")) { result = pk->has_expired? "1":"0"; } else if (!strcmp (propname, "revoked")) { result = pk->flags.revoked? "1":"0"; } else if (!strcmp (propname, "disabled")) { result = pk_is_disabled (pk)? "1":"0"; } else result = NULL; } else result = NULL; return result; } /* * Apply the keep-uid filter to the keyblock. The deleted nodes are * marked and thus the caller should call commit_kbnode afterwards. * KEYBLOCK must not have any blocks marked as deleted. */ static void apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) { kbnode_t node; struct impex_filter_parm_s parm; parm.ctrl = ctrl; for (node = keyblock->next; node; node = node->next ) { if (node->pkt->pkttype == PKT_USER_ID) { parm.node = node; if (!recsel_select (selector, impex_filter_getval, &parm)) { /* log_debug ("keep-uid: deleting '%s'\n", */ /* node->pkt->pkt.user_id->name); */ /* The UID packet and all following packets up to the * next UID or a subkey. */ delete_kbnode (node); for (; node->next && node->next->pkt->pkttype != PKT_USER_ID && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; node = node->next) delete_kbnode (node->next); } /* else */ /* log_debug ("keep-uid: keeping '%s'\n", */ /* node->pkt->pkt.user_id->name); */ } } } /* * Apply the drop-sig filter to the keyblock. The deleted nodes are * marked and thus the caller should call commit_kbnode afterwards. * KEYBLOCK must not have any blocks marked as deleted. */ static void apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) { kbnode_t node; int active = 0; u32 main_keyid[2]; PKT_signature *sig; struct impex_filter_parm_s parm; parm.ctrl = ctrl; keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid); /* Loop over all signatures for user id and attribute packets which * are not self signatures. */ for (node = keyblock->next; node; node = node->next ) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) break; /* ready. */ if (node->pkt->pkttype == PKT_USER_ID || node->pkt->pkttype == PKT_ATTRIBUTE) active = 1; if (!active) continue; if (node->pkt->pkttype != PKT_SIGNATURE) continue; sig = node->pkt->pkt.signature; if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1]) continue; /* Skip self-signatures. */ if (IS_UID_SIG(sig) || IS_UID_REV(sig)) { parm.node = node; if (recsel_select (selector, impex_filter_getval, &parm)) delete_kbnode (node); } } } /* * Try to import one keyblock. Return an error only in serious cases, * but never for an invalid keyblock. It uses log_error to increase * the internal errorcount, so that invalid input can be detected by * programs which called gpg. If SILENT is no messages are printed - * even most error messages are suppressed. */ static int import_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, unsigned char **fpr, size_t *fpr_len, unsigned int options, int from_sk, int silent, import_screener_t screener, void *screener_arg) { PKT_public_key *pk; PKT_public_key *pk_orig = NULL; kbnode_t node, uidnode; kbnode_t keyblock_orig = NULL; byte fpr2[MAX_FINGERPRINT_LEN]; size_t fpr2len; u32 keyid[2]; int rc = 0; int new_key = 0; int mod_key = 0; int same_key = 0; int non_self = 0; size_t an; char pkstrbuf[PUBKEY_STRING_SIZE]; int merge_keys_done = 0; int any_filter = 0; /* Get the key and print some info about it. */ node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); if (!node ) BUG(); pk = node->pkt->pkt.public_key; fingerprint_from_pk (pk, fpr2, &fpr2len); for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++) fpr2[an] = 0; keyid_from_pk( pk, keyid ); uidnode = find_next_kbnode( keyblock, PKT_USER_ID ); if (opt.verbose && !opt.interactive && !silent) { log_info( "pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk(pk) ); if (uidnode) print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len ); log_printf ("\n"); } if (!uidnode ) { if (!silent) log_error( _("key %s: no user ID\n"), keystr_from_pk(pk)); return 0; } if (screener && screener (keyblock, screener_arg)) { log_error (_("key %s: %s\n"), keystr_from_pk (pk), _("rejected by import screener")); return 0; } if (opt.interactive && !silent) { if (is_status_enabled()) print_import_check (pk, uidnode->pkt->pkt.user_id); merge_keys_and_selfsig (ctrl, keyblock); tty_printf ("\n"); show_basic_key_info (ctrl, keyblock); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("import.okay", "Do you want to import this key? (y/N) ")) return 0; } collapse_uids(&keyblock); /* Clean the key that we're about to import, to cut down on things that we have to clean later. This has no practical impact on the end result, but does result in less logging which might confuse the user. */ if (options&IMPORT_CLEAN) clean_key (ctrl, keyblock, opt.verbose, (options&IMPORT_MINIMAL), NULL, NULL); clear_kbnode_flags( keyblock ); if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG) && fix_pks_corruption (ctrl, keyblock) && opt.verbose) log_info (_("key %s: PKS subkey corruption repaired\n"), keystr_from_pk(pk)); if (chk_self_sigs (ctrl, keyblock, keyid, &non_self)) return 0; /* Invalid keyblock - error already printed. */ /* If we allow such a thing, mark unsigned uids as valid */ if (opt.allow_non_selfsigned_uid) { for (node=keyblock; node; node = node->next ) if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODE_GOOD_SELFSIG) && !(node->flag & NODE_BAD_SELFSIG) ) { char *user=utf8_to_native(node->pkt->pkt.user_id->name, node->pkt->pkt.user_id->len,0); /* Fake a good signature status for the user id. */ node->flag |= NODE_GOOD_SELFSIG; log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"), keystr_from_pk(pk),user); xfree(user); } } if (!delete_inv_parts (ctrl, keyblock, keyid, options ) ) { if (!silent) { log_error( _("key %s: no valid user IDs\n"), keystr_from_pk(pk)); if (!opt.quiet ) log_info(_("this may be caused by a missing self-signature\n")); } stats->no_user_id++; return 0; } /* Get rid of deleted nodes. */ commit_kbnode (&keyblock); /* Apply import filter. */ if (import_filter.keep_uid) { apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid); commit_kbnode (&keyblock); any_filter = 1; } if (import_filter.drop_sig) { apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig); commit_kbnode (&keyblock); any_filter = 1; } /* If we ran any filter we need to check that at least one user id * is left in the keyring. Note that we do not use log_error in * this case. */ if (any_filter && !any_uid_left (keyblock)) { if (!opt.quiet ) log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk)); stats->no_user_id++; return 0; } /* Show the key in the form it is merged or inserted. We skip this * if "import-export" is also active without --armor or the output * file has explicily been given. */ if ((options & IMPORT_SHOW) && !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile)) { merge_keys_and_selfsig (ctrl, keyblock); merge_keys_done = 1; /* Note that we do not want to show the validity because the key * has not yet imported. */ list_keyblock_direct (ctrl, keyblock, 0, 0, 1, 1); es_fflush (es_stdout); } /* Write the keyblock to the output and do not actually import. */ if ((options & IMPORT_EXPORT)) { if (!merge_keys_done) { merge_keys_and_selfsig (ctrl, keyblock); merge_keys_done = 1; } rc = write_keyblock_to_output (keyblock, opt.armor, opt.export_options); goto leave; } if (opt.dry_run) goto leave; /* Do we have this key already in one of our pubrings ? */ pk_orig = xmalloc_clear( sizeof *pk_orig ); rc = get_pubkey_byfprint_fast (pk_orig, fpr2, fpr2len); if (rc && gpg_err_code (rc) != GPG_ERR_NO_PUBKEY && gpg_err_code (rc) != GPG_ERR_UNUSABLE_PUBKEY ) { if (!silent) log_error (_("key %s: public key not found: %s\n"), keystr(keyid), gpg_strerror (rc)); } else if ( rc && (opt.import_options&IMPORT_MERGE_ONLY) ) { if (opt.verbose && !silent ) log_info( _("key %s: new key - skipped\n"), keystr(keyid)); rc = 0; stats->skipped_new_keys++; } else if (rc ) /* Insert this key. */ { KEYDB_HANDLE hd; hd = keydb_new (); if (!hd) return gpg_error_from_syserror (); rc = keydb_locate_writable (hd); if (rc) { log_error (_("no writable keyring found: %s\n"), gpg_strerror (rc)); keydb_release (hd); return GPG_ERR_GENERAL; } if (opt.verbose > 1 ) log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) ); rc = keydb_insert_keyblock (hd, keyblock ); if (rc) log_error (_("error writing keyring '%s': %s\n"), keydb_get_resource_name (hd), gpg_strerror (rc)); else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST)) { /* This should not be possible since we delete the ownertrust when a key is deleted, but it can happen if the keyring and trustdb are out of sync. It can also be made to happen with the trusted-key command and by importing and locally exported key. */ clear_ownertrusts (ctrl, pk); if (non_self) revalidation_mark (ctrl); } keydb_release (hd); /* We are ready. */ if (!opt.quiet && !silent) { char *p = get_user_id_byfpr_native (ctrl, fpr2); log_info (_("key %s: public key \"%s\" imported\n"), keystr(keyid), p); xfree(p); } if (is_status_enabled()) { char *us = get_long_user_id_string (ctrl, keyid); write_status_text( STATUS_IMPORTED, us ); xfree(us); print_import_ok (pk, 1); } stats->imported++; new_key = 1; } else /* merge */ { KEYDB_HANDLE hd; int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned; /* Compare the original against the new key; just to be sure nothing * weird is going on */ if (cmp_public_keys( pk_orig, pk ) ) { if (!silent) log_error( _("key %s: doesn't match our copy\n"),keystr(keyid)); goto leave; } /* Now read the original keyblock again so that we can use that handle for updating the keyblock. */ hd = keydb_new (); if (!hd) { rc = gpg_error_from_syserror (); goto leave; } keydb_disable_caching (hd); rc = keydb_search_fpr (hd, fpr2); if (rc ) { log_error (_("key %s: can't locate original keyblock: %s\n"), keystr(keyid), gpg_strerror (rc)); keydb_release (hd); goto leave; } rc = keydb_get_keyblock (hd, &keyblock_orig); if (rc) { log_error (_("key %s: can't read original keyblock: %s\n"), keystr(keyid), gpg_strerror (rc)); keydb_release (hd); goto leave; } /* Make sure the original direct key sigs are all sane. */ n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid); if (n_sigs_cleaned) commit_kbnode (&keyblock_orig); /* and try to merge the block */ clear_kbnode_flags( keyblock_orig ); clear_kbnode_flags( keyblock ); n_uids = n_sigs = n_subk = n_uids_cleaned = 0; rc = merge_blocks (ctrl, keyblock_orig, keyblock, keyid, &n_uids, &n_sigs, &n_subk ); if (rc ) { keydb_release (hd); goto leave; } if ((options & IMPORT_CLEAN)) clean_key (ctrl, keyblock_orig, opt.verbose, (options&IMPORT_MINIMAL), &n_uids_cleaned,&n_sigs_cleaned); if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned) { mod_key = 1; /* KEYBLOCK_ORIG has been updated; write */ rc = keydb_update_keyblock (ctrl, hd, keyblock_orig); if (rc) log_error (_("error writing keyring '%s': %s\n"), keydb_get_resource_name (hd), gpg_strerror (rc) ); else if (non_self) revalidation_mark (ctrl); /* We are ready. */ if (!opt.quiet && !silent) { char *p = get_user_id_byfpr_native (ctrl, fpr2); if (n_uids == 1 ) log_info( _("key %s: \"%s\" 1 new user ID\n"), keystr(keyid),p); else if (n_uids ) log_info( _("key %s: \"%s\" %d new user IDs\n"), keystr(keyid),p,n_uids); if (n_sigs == 1 ) log_info( _("key %s: \"%s\" 1 new signature\n"), keystr(keyid), p); else if (n_sigs ) log_info( _("key %s: \"%s\" %d new signatures\n"), keystr(keyid), p, n_sigs ); if (n_subk == 1 ) log_info( _("key %s: \"%s\" 1 new subkey\n"), keystr(keyid), p); else if (n_subk ) log_info( _("key %s: \"%s\" %d new subkeys\n"), keystr(keyid), p, n_subk ); if (n_sigs_cleaned==1) log_info(_("key %s: \"%s\" %d signature cleaned\n"), keystr(keyid),p,n_sigs_cleaned); else if (n_sigs_cleaned) log_info(_("key %s: \"%s\" %d signatures cleaned\n"), keystr(keyid),p,n_sigs_cleaned); if (n_uids_cleaned==1) log_info(_("key %s: \"%s\" %d user ID cleaned\n"), keystr(keyid),p,n_uids_cleaned); else if (n_uids_cleaned) log_info(_("key %s: \"%s\" %d user IDs cleaned\n"), keystr(keyid),p,n_uids_cleaned); xfree(p); } stats->n_uids +=n_uids; stats->n_sigs +=n_sigs; stats->n_subk +=n_subk; stats->n_sigs_cleaned +=n_sigs_cleaned; stats->n_uids_cleaned +=n_uids_cleaned; if (is_status_enabled () && !silent) print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0))); } else { same_key = 1; if (is_status_enabled ()) print_import_ok (pk, 0); if (!opt.quiet && !silent) { char *p = get_user_id_byfpr_native (ctrl, fpr2); log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p); xfree(p); } stats->unchanged++; } keydb_release (hd); hd = NULL; } leave: if (mod_key || new_key || same_key) { /* A little explanation for this: we fill in the fingerprint when importing keys as it can be useful to know the fingerprint in certain keyserver-related cases (a keyserver asked for a particular name, but the key doesn't have that name). However, in cases where we're importing more than one key at a time, we cannot know which key to fingerprint. In these cases, rather than guessing, we do not fingerprinting at all, and we must hope the user ID on the keys are useful. Note that we need to do this for new keys, merged keys and even for unchanged keys. This is required because for example the --auto-key-locate feature may import an already imported key and needs to know the fingerprint of the key in all cases. */ if (fpr) { xfree (*fpr); /* Note that we need to compare against 0 here because COUNT gets only incremented after returning from this function. */ if (!stats->count) *fpr = fingerprint_from_pk (pk, NULL, fpr_len); else *fpr = NULL; } } /* Now that the key is definitely incorporated into the keydb, we need to check if a designated revocation is present or if the prefs are not rational so we can warn the user. */ if (mod_key) { revocation_present (ctrl, keyblock_orig); if (!from_sk && have_secret_key_with_kid (keyid)) check_prefs (ctrl, keyblock_orig); } else if (new_key) { revocation_present (ctrl, keyblock); if (!from_sk && have_secret_key_with_kid (keyid)) check_prefs (ctrl, keyblock); } release_kbnode( keyblock_orig ); free_public_key( pk_orig ); return rc; } /* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The function prints diagnostics and returns an error code. If BATCH is true the secret keys are stored by gpg-agent in the transfer format (i.e. no re-protection and aksing for passphrases). */ gpg_error_t transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats, kbnode_t sec_keyblock, int batch, int force) { gpg_error_t err = 0; void *kek = NULL; size_t keklen; kbnode_t ctx = NULL; kbnode_t node; PKT_public_key *main_pk, *pk; struct seckey_info *ski; int nskey; membuf_t mbuf; int i, j; void *format_args[2*PUBKEY_MAX_NSKEY]; gcry_sexp_t skey, prot, tmpsexp; gcry_sexp_t curve = NULL; unsigned char *transferkey = NULL; size_t transferkeylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; char *cache_nonce = NULL; int stub_key_skipped = 0; /* Get the current KEK. */ err = agent_keywrap_key (ctrl, 0, &kek, &keklen); if (err) { log_error ("error getting the KEK: %s\n", gpg_strerror (err)); goto leave; } /* Prepare a cipher context. */ err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (!err) err = gcry_cipher_setkey (cipherhd, kek, keklen); if (err) goto leave; xfree (kek); kek = NULL; main_pk = NULL; while ((node = walk_kbnode (sec_keyblock, &ctx, 0))) { if (node->pkt->pkttype != PKT_SECRET_KEY && node->pkt->pkttype != PKT_SECRET_SUBKEY) continue; pk = node->pkt->pkt.public_key; if (!main_pk) main_pk = pk; /* Make sure the keyids are available. */ keyid_from_pk (pk, NULL); if (node->pkt->pkttype == PKT_SECRET_KEY) { pk->main_keyid[0] = pk->keyid[0]; pk->main_keyid[1] = pk->keyid[1]; } else { pk->main_keyid[0] = main_pk->keyid[0]; pk->main_keyid[1] = main_pk->keyid[1]; } ski = pk->seckey_info; if (!ski) BUG (); if (stats) { stats->count++; stats->secret_read++; } /* We ignore stub keys. The way we handle them in other parts of the code is by asking the agent whether any secret key is available for a given keyblock and then concluding that we have a secret key; all secret (sub)keys of the keyblock the agent does not know of are then stub keys. This works also for card stub keys. The learn command or the card-status command may be used to check with the agent whether a card has been inserted and a stub key is in turn generated by the agent. */ if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002) { stub_key_skipped = 1; continue; } /* Convert our internal secret key object into an S-expression. */ nskey = pubkey_get_nskey (pk->pubkey_algo); if (!nskey || nskey > PUBKEY_MAX_NSKEY) { err = gpg_error (GPG_ERR_BAD_SECKEY); log_error ("internal error: %s\n", gpg_strerror (err)); goto leave; } init_membuf (&mbuf, 50); put_membuf_str (&mbuf, "(skey"); if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA || pk->pubkey_algo == PUBKEY_ALGO_EDDSA || pk->pubkey_algo == PUBKEY_ALGO_ECDH) { /* The ECC case. */ char *curvestr = openpgp_oid_to_str (pk->pkey[0]); if (!curvestr) err = gpg_error_from_syserror (); else { const char *curvename = openpgp_oid_to_curve (curvestr, 1); gcry_sexp_release (curve); err = gcry_sexp_build (&curve, NULL, "(curve %s)", curvename?curvename:curvestr); xfree (curvestr); if (!err) { j = 0; /* Append the public key element Q. */ put_membuf_str (&mbuf, " _ %m"); format_args[j++] = pk->pkey + 1; /* Append the secret key element D. For ECDH we skip PKEY[2] because this holds the KEK which is not needed by gpg-agent. */ i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2; if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)) put_membuf_str (&mbuf, " e %m"); else put_membuf_str (&mbuf, " _ %m"); format_args[j++] = pk->pkey + i; } } } else { /* Standard case for the old (non-ECC) algorithms. */ for (i=j=0; i < nskey; i++) { if (!pk->pkey[i]) continue; /* Protected keys only have NPKEY+1 elements. */ if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)) put_membuf_str (&mbuf, " e %m"); else put_membuf_str (&mbuf, " _ %m"); format_args[j++] = pk->pkey + i; } } put_membuf_str (&mbuf, ")"); put_membuf (&mbuf, "", 1); if (err) xfree (get_membuf (&mbuf, NULL)); else { char *format = get_membuf (&mbuf, NULL); if (!format) err = gpg_error_from_syserror (); else err = gcry_sexp_build_array (&skey, NULL, format, format_args); xfree (format); } if (err) { log_error ("error building skey array: %s\n", gpg_strerror (err)); goto leave; } if (ski->is_protected) { char countbuf[35]; /* Note that the IVLEN may be zero if we are working on a dummy key. We can't express that in an S-expression and thus we send dummy data for the IV. */ snprintf (countbuf, sizeof countbuf, "%lu", (unsigned long)ski->s2k.count); err = gcry_sexp_build (&prot, NULL, " (protection %s %s %b %d %s %b %s)\n", ski->sha1chk? "sha1":"sum", openpgp_cipher_algo_name (ski->algo), ski->ivlen? (int)ski->ivlen:1, ski->ivlen? ski->iv: (const unsigned char*)"X", ski->s2k.mode, openpgp_md_algo_name (ski->s2k.hash_algo), (int)sizeof (ski->s2k.salt), ski->s2k.salt, countbuf); } else err = gcry_sexp_build (&prot, NULL, " (protection none)\n"); tmpsexp = NULL; xfree (transferkey); transferkey = NULL; if (!err) err = gcry_sexp_build (&tmpsexp, NULL, "(openpgp-private-key\n" " (version %d)\n" " (algo %s)\n" " %S%S\n" " (csum %d)\n" " %S)\n", pk->version, openpgp_pk_algo_name (pk->pubkey_algo), curve, skey, (int)(unsigned long)ski->csum, prot); gcry_sexp_release (skey); gcry_sexp_release (prot); if (!err) err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen); gcry_sexp_release (tmpsexp); if (err) { log_error ("error building transfer key: %s\n", gpg_strerror (err)); goto leave; } /* Wrap the key. */ wrappedkeylen = transferkeylen + 8; xfree (wrappedkey); wrappedkey = xtrymalloc (wrappedkeylen); if (!wrappedkey) err = gpg_error_from_syserror (); else err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, transferkey, transferkeylen); if (err) goto leave; xfree (transferkey); transferkey = NULL; /* Send the wrapped key to the agent. */ { char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1); err = agent_import_key (ctrl, desc, &cache_nonce, wrappedkey, wrappedkeylen, batch, force); xfree (desc); } if (!err) { if (opt.verbose) log_info (_("key %s: secret key imported\n"), keystr_from_pk_with_sub (main_pk, pk)); if (stats) stats->secret_imported++; } else if ( gpg_err_code (err) == GPG_ERR_EEXIST ) { if (opt.verbose) log_info (_("key %s: secret key already exists\n"), keystr_from_pk_with_sub (main_pk, pk)); err = 0; if (stats) stats->secret_dups++; } else { log_error (_("key %s: error sending to agent: %s\n"), keystr_from_pk_with_sub (main_pk, pk), gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) break; /* Don't try the other subkeys. */ } } if (!err && stub_key_skipped) /* We need to notify user how to migrate stub keys. */ err = gpg_error (GPG_ERR_NOT_PROCESSED); leave: gcry_sexp_release (curve); xfree (cache_nonce); xfree (wrappedkey); xfree (transferkey); gcry_cipher_close (cipherhd); xfree (kek); return err; } /* Walk a secret keyblock and produce a public keyblock out of it. Returns a new node or NULL on error. */ static kbnode_t sec_to_pub_keyblock (kbnode_t sec_keyblock) { kbnode_t pub_keyblock = NULL; kbnode_t ctx = NULL; kbnode_t secnode, pubnode; while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0))) { if (secnode->pkt->pkttype == PKT_SECRET_KEY || secnode->pkt->pkttype == PKT_SECRET_SUBKEY) { /* Make a public key. */ PACKET *pkt; PKT_public_key *pk; pkt = xtrycalloc (1, sizeof *pkt); pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL; if (!pk) { xfree (pkt); release_kbnode (pub_keyblock); return NULL; } if (secnode->pkt->pkttype == PKT_SECRET_KEY) pkt->pkttype = PKT_PUBLIC_KEY; else pkt->pkttype = PKT_PUBLIC_SUBKEY; pkt->pkt.public_key = pk; pubnode = new_kbnode (pkt); } else { pubnode = clone_kbnode (secnode); } if (!pub_keyblock) pub_keyblock = pubnode; else add_kbnode (pub_keyblock, pubnode); } return pub_keyblock; } /**************** * Ditto for secret keys. Handling is simpler than for public keys. * We allow secret key importing only when allow is true, this is so * that a secret key can not be imported accidentally and thereby tampering * with the trust calculation. */ static int import_secret_one (ctrl_t ctrl, kbnode_t keyblock, struct import_stats_s *stats, int batch, unsigned int options, int for_migration, import_screener_t screener, void *screener_arg) { PKT_public_key *pk; struct seckey_info *ski; kbnode_t node, uidnode; u32 keyid[2]; int rc = 0; int nr_prev; kbnode_t pub_keyblock; char pkstrbuf[PUBKEY_STRING_SIZE]; /* Get the key and print some info about it */ node = find_kbnode (keyblock, PKT_SECRET_KEY); if (!node) BUG (); pk = node->pkt->pkt.public_key; keyid_from_pk (pk, keyid); uidnode = find_next_kbnode (keyblock, PKT_USER_ID); if (screener && screener (keyblock, screener_arg)) { log_error (_("secret key %s: %s\n"), keystr_from_pk (pk), _("rejected by import screener")); return 0; } if (opt.verbose && !for_migration) { log_info ("sec %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk), datestr_from_pk (pk)); if (uidnode) print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len); log_printf ("\n"); } stats->secret_read++; if ((options & IMPORT_NO_SECKEY)) { if (!for_migration) log_error (_("importing secret keys not allowed\n")); return 0; } if (!uidnode) { if (!for_migration) log_error( _("key %s: no user ID\n"), keystr_from_pk (pk)); return 0; } ski = pk->seckey_info; if (!ski) { /* Actually an internal error. */ log_error ("key %s: secret key info missing\n", keystr_from_pk (pk)); return 0; } /* A quick check to not import keys with an invalid protection cipher algorithm (only checks the primary key, though). */ if (ski->algo > 110) { if (!for_migration) log_error (_("key %s: secret key with invalid cipher %d" " - skipped\n"), keystr_from_pk (pk), ski->algo); return 0; } #ifdef ENABLE_SELINUX_HACKS if (1) { /* We don't allow importing secret keys because that may be used to put a secret key into the keyring and the user might later be tricked into signing stuff with that key. */ log_error (_("importing secret keys not allowed\n")); return 0; } #endif clear_kbnode_flags (keyblock); nr_prev = stats->skipped_new_keys; /* Make a public key out of the key. */ pub_keyblock = sec_to_pub_keyblock (keyblock); if (!pub_keyblock) log_error ("key %s: failed to create public key from secret key\n", keystr_from_pk (pk)); else { /* Note that this outputs an IMPORT_OK status message for the public key block, and below we will output another one for the secret keys. FIXME? */ import_one (ctrl, pub_keyblock, stats, NULL, NULL, options, 1, for_migration, screener, screener_arg); /* Fixme: We should check for an invalid keyblock and cancel the secret key import in this case. */ release_kbnode (pub_keyblock); /* At least we cancel the secret key import when the public key import was skipped due to MERGE_ONLY option and a new key. */ if (stats->skipped_new_keys <= nr_prev) { /* Read the keyblock again to get the effects of a merge. */ /* Fixme: we should do this based on the fingerprint or even better let import_one return the merged keyblock. */ node = get_pubkeyblock (ctrl, keyid); if (!node) log_error ("key %s: failed to re-lookup public key\n", keystr_from_pk (pk)); else { gpg_error_t err; /* transfer_secret_keys collects subkey stats. */ struct import_stats_s subkey_stats = {0}; err = transfer_secret_keys (ctrl, &subkey_stats, keyblock, batch, 0); if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED) { /* TRANSLATORS: For smartcard, each private key on host has a reference (stub) to a smartcard and actual private key data is stored on the card. A single smartcard can have up to three private key data. Importing private key stub is always skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED. Instead, user should be suggested to run 'gpg --card-status', then, references to a card will be automatically created again. */ log_info (_("To migrate '%s', with each smartcard, " "run: %s\n"), "secring.gpg", "gpg --card-status"); err = 0; } if (!err) { int status = 16; if (!opt.quiet) log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk)); if (subkey_stats.secret_imported) { status |= 1; stats->secret_imported += 1; } if (subkey_stats.secret_dups) stats->secret_dups += 1; if (is_status_enabled ()) print_import_ok (pk, status); check_prefs (ctrl, node); } release_kbnode (node); } } } return rc; } /**************** * Import a revocation certificate; this is a single signature packet. */ static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, struct import_stats_s *stats) { PKT_public_key *pk = NULL; kbnode_t onode; kbnode_t keyblock = NULL; KEYDB_HANDLE hd = NULL; u32 keyid[2]; int rc = 0; log_assert (!node->next ); log_assert (node->pkt->pkttype == PKT_SIGNATURE ); log_assert (node->pkt->pkt.signature->sig_class == 0x20 ); keyid[0] = node->pkt->pkt.signature->keyid[0]; keyid[1] = node->pkt->pkt.signature->keyid[1]; pk = xmalloc_clear( sizeof *pk ); rc = get_pubkey (ctrl, pk, keyid ); if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY ) { log_error(_("key %s: no public key -" " can't apply revocation certificate\n"), keystr(keyid)); rc = 0; goto leave; } else if (rc ) { log_error(_("key %s: public key not found: %s\n"), keystr(keyid), gpg_strerror (rc)); goto leave; } /* Read the original keyblock. */ hd = keydb_new (); if (!hd) { rc = gpg_error_from_syserror (); goto leave; } { byte afp[MAX_FINGERPRINT_LEN]; size_t an; fingerprint_from_pk (pk, afp, &an); while (an < MAX_FINGERPRINT_LEN) afp[an++] = 0; rc = keydb_search_fpr (hd, afp); } if (rc) { log_error (_("key %s: can't locate original keyblock: %s\n"), keystr(keyid), gpg_strerror (rc)); goto leave; } rc = keydb_get_keyblock (hd, &keyblock ); if (rc) { log_error (_("key %s: can't read original keyblock: %s\n"), keystr(keyid), gpg_strerror (rc)); goto leave; } /* it is okay, that node is not in keyblock because * check_key_signature works fine for sig_class 0x20 in this * special case. */ rc = check_key_signature (ctrl, keyblock, node, NULL); if (rc ) { log_error( _("key %s: invalid revocation certificate" ": %s - rejected\n"), keystr(keyid), gpg_strerror (rc)); goto leave; } /* check whether we already have this */ for(onode=keyblock->next; onode; onode=onode->next ) { if (onode->pkt->pkttype == PKT_USER_ID ) break; else if (onode->pkt->pkttype == PKT_SIGNATURE && !cmp_signatures(node->pkt->pkt.signature, onode->pkt->pkt.signature)) { rc = 0; goto leave; /* yes, we already know about it */ } } /* insert it */ insert_kbnode( keyblock, clone_kbnode(node), 0 ); /* and write the keyblock back */ rc = keydb_update_keyblock (ctrl, hd, keyblock ); if (rc) log_error (_("error writing keyring '%s': %s\n"), keydb_get_resource_name (hd), gpg_strerror (rc) ); keydb_release (hd); hd = NULL; /* we are ready */ if (!opt.quiet ) { char *p=get_user_id_native (ctrl, keyid); log_info( _("key %s: \"%s\" revocation certificate imported\n"), keystr(keyid),p); xfree(p); } stats->n_revoc++; /* If the key we just revoked was ultimately trusted, remove its ultimate trust. This doesn't stop the user from putting the ultimate trust back, but is a reasonable solution for now. */ if (get_ownertrust (ctrl, pk) == TRUST_ULTIMATE) clear_ownertrusts (ctrl, pk); revalidation_mark (ctrl); leave: keydb_release (hd); release_kbnode( keyblock ); free_public_key( pk ); return rc; } /* Loop over the keyblock and check all self signatures. On return * the following bis in the node flags are set: * * - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature * - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature * - NODE_DELETION_MARK :: This node shall be deleted * * NON_SELF is set to true if there are any sigs other than self-sigs * in this keyblock. * * Returns 0 on success or -1 (but not an error code) if the keyblock * is invalid. */ static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self) { kbnode_t n, knode = NULL; PKT_signature *sig; int rc; u32 bsdate=0, rsdate=0; kbnode_t bsnode = NULL, rsnode = NULL; for (n=keyblock; (n = find_next_kbnode (n, 0)); ) { if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY) { knode = n; bsdate = 0; rsdate = 0; bsnode = NULL; rsnode = NULL; continue; } if ( n->pkt->pkttype != PKT_SIGNATURE ) continue; sig = n->pkt->pkt.signature; if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] ) { *non_self = 1; continue; } /* This just caches the sigs for later use. That way we import a fully-cached key which speeds things up. */ if (!opt.no_sig_cache) check_key_signature (ctrl, keyblock, n, NULL); if ( IS_UID_SIG(sig) || IS_UID_REV(sig) ) { kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID ); if ( !unode ) { log_error( _("key %s: no user ID for signature\n"), keystr(keyid)); return -1; /* The complete keyblock is invalid. */ } /* If it hasn't been marked valid yet, keep trying. */ if (!(unode->flag & NODE_GOOD_SELFSIG)) { rc = check_key_signature (ctrl, keyblock, n, NULL); if ( rc ) { if ( opt.verbose ) { char *p = utf8_to_native (unode->pkt->pkt.user_id->name, strlen (unode->pkt->pkt.user_id->name),0); log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ? _("key %s: unsupported public key " "algorithm on user ID \"%s\"\n"): _("key %s: invalid self-signature " "on user ID \"%s\"\n"), keystr (keyid),p); xfree (p); } } else unode->flag |= NODE_GOOD_SELFSIG; } } else if (IS_KEY_SIG (sig)) { rc = check_key_signature (ctrl, keyblock, n, NULL); if ( rc ) { if (opt.verbose) log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? _("key %s: unsupported public key algorithm\n"): _("key %s: invalid direct key signature\n"), keystr (keyid)); n->flag |= NODE_DELETION_MARK; } } else if ( IS_SUBKEY_SIG (sig) ) { /* Note that this works based solely on the timestamps like the rest of gpg. If the standard gets revocation targets, this may need to be revised. */ if ( !knode ) { if (opt.verbose) log_info (_("key %s: no subkey for key binding\n"), keystr (keyid)); n->flag |= NODE_DELETION_MARK; } else { rc = check_key_signature (ctrl, keyblock, n, NULL); if ( rc ) { if (opt.verbose) log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? _("key %s: unsupported public key" " algorithm\n"): _("key %s: invalid subkey binding\n"), keystr (keyid)); n->flag |= NODE_DELETION_MARK; } else { /* It's valid, so is it newer? */ if (sig->timestamp >= bsdate) { knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */ if (bsnode) { /* Delete the last binding sig since this one is newer */ bsnode->flag |= NODE_DELETION_MARK; if (opt.verbose) log_info (_("key %s: removed multiple subkey" " binding\n"),keystr(keyid)); } bsnode = n; bsdate = sig->timestamp; } else n->flag |= NODE_DELETION_MARK; /* older */ } } } else if ( IS_SUBKEY_REV (sig) ) { /* We don't actually mark the subkey as revoked right now, so just check that the revocation sig is the most recent valid one. Note that we don't care if the binding sig is newer than the revocation sig. See the comment in getkey.c:merge_selfsigs_subkey for more. */ if ( !knode ) { if (opt.verbose) log_info (_("key %s: no subkey for key revocation\n"), keystr(keyid)); n->flag |= NODE_DELETION_MARK; } else { rc = check_key_signature (ctrl, keyblock, n, NULL); if ( rc ) { if(opt.verbose) log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ? _("key %s: unsupported public" " key algorithm\n"): _("key %s: invalid subkey revocation\n"), keystr(keyid)); n->flag |= NODE_DELETION_MARK; } else { /* It's valid, so is it newer? */ if (sig->timestamp >= rsdate) { if (rsnode) { /* Delete the last revocation sig since this one is newer. */ rsnode->flag |= NODE_DELETION_MARK; if (opt.verbose) log_info (_("key %s: removed multiple subkey" " revocation\n"),keystr(keyid)); } rsnode = n; rsdate = sig->timestamp; } else n->flag |= NODE_DELETION_MARK; /* older */ } } } } return 0; } /* Delete all parts which are invalid and those signatures whose - * public key algorithm is not available in this implemenation; but + * public key algorithm is not available in this implementation; but * consider RSA as valid, because parse/build_packets knows about it. * * Returns: True if at least one valid user-id is left over. */ static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, unsigned int options) { kbnode_t node; int nvalid=0, uid_seen=0, subkey_seen=0; for (node=keyblock->next; node; node = node->next ) { if (node->pkt->pkttype == PKT_USER_ID) { uid_seen = 1; if ((node->flag & NODE_BAD_SELFSIG) || !(node->flag & NODE_GOOD_SELFSIG)) { if (opt.verbose ) { char *p=utf8_to_native(node->pkt->pkt.user_id->name, node->pkt->pkt.user_id->len,0); log_info( _("key %s: skipped user ID \"%s\"\n"), keystr(keyid),p); xfree(p); } delete_kbnode( node ); /* the user-id */ /* and all following packets up to the next user-id */ while (node->next && node->next->pkt->pkttype != PKT_USER_ID && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){ delete_kbnode( node->next ); node = node->next; } } else nvalid++; } else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { if ((node->flag & NODE_BAD_SELFSIG) || !(node->flag & NODE_GOOD_SELFSIG)) { if (opt.verbose ) log_info( _("key %s: skipped subkey\n"),keystr(keyid)); delete_kbnode( node ); /* the subkey */ /* and all following signature packets */ while (node->next && node->next->pkt->pkttype == PKT_SIGNATURE ) { delete_kbnode( node->next ); node = node->next; } } else subkey_seen = 1; } else if (node->pkt->pkttype == PKT_SIGNATURE && openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo) && node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA ) { delete_kbnode( node ); /* build_packet() can't handle this */ } else if (node->pkt->pkttype == PKT_SIGNATURE && !node->pkt->pkt.signature->flags.exportable && !(options&IMPORT_LOCAL_SIGS) && !have_secret_key_with_kid (node->pkt->pkt.signature->keyid)) { /* here we violate the rfc a bit by still allowing * to import non-exportable signature when we have the * the secret key used to create this signature - it * seems that this makes sense */ if(opt.verbose) log_info( _("key %s: non exportable signature" " (class 0x%02X) - skipped\n"), keystr(keyid), node->pkt->pkt.signature->sig_class ); delete_kbnode( node ); } else if (node->pkt->pkttype == PKT_SIGNATURE && node->pkt->pkt.signature->sig_class == 0x20) { if (uid_seen ) { if(opt.verbose) log_info( _("key %s: revocation certificate" " at wrong place - skipped\n"),keystr(keyid)); delete_kbnode( node ); } else { /* If the revocation cert is from a different key than the one we're working on don't check it - it's probably from a revocation key and won't be verifiable with this key anyway. */ if(node->pkt->pkt.signature->keyid[0]==keyid[0] && node->pkt->pkt.signature->keyid[1]==keyid[1]) { int rc = check_key_signature (ctrl, keyblock, node, NULL); if (rc ) { if(opt.verbose) log_info( _("key %s: invalid revocation" " certificate: %s - skipped\n"), keystr(keyid), gpg_strerror (rc)); delete_kbnode( node ); } } } } else if (node->pkt->pkttype == PKT_SIGNATURE && (node->pkt->pkt.signature->sig_class == 0x18 || node->pkt->pkt.signature->sig_class == 0x28) && !subkey_seen ) { if(opt.verbose) log_info( _("key %s: subkey signature" " in wrong place - skipped\n"), keystr(keyid)); delete_kbnode( node ); } else if (node->pkt->pkttype == PKT_SIGNATURE && !IS_CERT(node->pkt->pkt.signature)) { if(opt.verbose) log_info(_("key %s: unexpected signature class (0x%02X) -" " skipped\n"),keystr(keyid), node->pkt->pkt.signature->sig_class); delete_kbnode(node); } else if ((node->flag & NODE_DELETION_MARK)) delete_kbnode( node ); } /* note: because keyblock is the public key, it is never marked * for deletion and so keyblock cannot change */ commit_kbnode( &keyblock ); return nvalid; } /* This function returns true if any UID is left in the keyring. */ static int any_uid_left (kbnode_t keyblock) { kbnode_t node; for (node=keyblock->next; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) return 1; return 0; } /**************** * It may happen that the imported keyblock has duplicated user IDs. * We check this here and collapse those user IDs together with their * sigs into one. * Returns: True if the keyblock has changed. */ int collapse_uids( kbnode_t *keyblock ) { kbnode_t uid1; int any=0; for(uid1=*keyblock;uid1;uid1=uid1->next) { kbnode_t uid2; if(is_deleted_kbnode(uid1)) continue; if(uid1->pkt->pkttype!=PKT_USER_ID) continue; for(uid2=uid1->next;uid2;uid2=uid2->next) { if(is_deleted_kbnode(uid2)) continue; if(uid2->pkt->pkttype!=PKT_USER_ID) continue; if(cmp_user_ids(uid1->pkt->pkt.user_id, uid2->pkt->pkt.user_id)==0) { /* We have a duplicated uid */ kbnode_t sig1,last; any=1; /* Now take uid2's signatures, and attach them to uid1 */ for(last=uid2;last->next;last=last->next) { if(is_deleted_kbnode(last)) continue; if(last->next->pkt->pkttype==PKT_USER_ID || last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY || last->next->pkt->pkttype==PKT_SECRET_SUBKEY) break; } /* Snip out uid2 */ (find_prev_kbnode(*keyblock,uid2,0))->next=last->next; /* Now put uid2 in place as part of uid1 */ last->next=uid1->next; uid1->next=uid2; delete_kbnode(uid2); /* Now dedupe uid1 */ for(sig1=uid1->next;sig1;sig1=sig1->next) { kbnode_t sig2; if(is_deleted_kbnode(sig1)) continue; if(sig1->pkt->pkttype==PKT_USER_ID || sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY || sig1->pkt->pkttype==PKT_SECRET_SUBKEY) break; if(sig1->pkt->pkttype!=PKT_SIGNATURE) continue; for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next) { if(is_deleted_kbnode(sig2)) continue; if(sig2->pkt->pkttype==PKT_USER_ID || sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY || sig2->pkt->pkttype==PKT_SECRET_SUBKEY) break; if(sig2->pkt->pkttype!=PKT_SIGNATURE) continue; if(cmp_signatures(sig1->pkt->pkt.signature, sig2->pkt->pkt.signature)==0) { /* We have a match, so delete the second signature */ delete_kbnode(sig2); sig2=last; } } } } } } commit_kbnode(keyblock); if(any && !opt.quiet) { const char *key="???"; if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) ) key = keystr_from_pk (uid1->pkt->pkt.public_key); else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) ) key = keystr_from_pk (uid1->pkt->pkt.public_key); log_info (_("key %s: duplicated user ID detected - merged\n"), key); } return any; } /* Check for a 0x20 revocation from a revocation key that is not present. This may be called without the benefit of merge_xxxx so you can't rely on pk->revkey and friends. */ static void revocation_present (ctrl_t ctrl, kbnode_t keyblock) { kbnode_t onode, inode; PKT_public_key *pk = keyblock->pkt->pkt.public_key; for(onode=keyblock->next;onode;onode=onode->next) { /* If we reach user IDs, we're done. */ if(onode->pkt->pkttype==PKT_USER_ID) break; if(onode->pkt->pkttype==PKT_SIGNATURE && onode->pkt->pkt.signature->sig_class==0x1F && onode->pkt->pkt.signature->revkey) { int idx; PKT_signature *sig=onode->pkt->pkt.signature; for(idx=0;idx<sig->numrevkeys;idx++) { u32 keyid[2]; keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr, MAX_FINGERPRINT_LEN, keyid); for(inode=keyblock->next;inode;inode=inode->next) { /* If we reach user IDs, we're done. */ if(inode->pkt->pkttype==PKT_USER_ID) break; if(inode->pkt->pkttype==PKT_SIGNATURE && inode->pkt->pkt.signature->sig_class==0x20 && inode->pkt->pkt.signature->keyid[0]==keyid[0] && inode->pkt->pkt.signature->keyid[1]==keyid[1]) { /* Okay, we have a revocation key, and a revocation issued by it. Do we have the key itself? */ int rc; rc=get_pubkey_byfprint_fast (NULL,sig->revkey[idx].fpr, MAX_FINGERPRINT_LEN); if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY || gpg_err_code (rc) == GPG_ERR_UNUSABLE_PUBKEY) { char *tempkeystr=xstrdup(keystr_from_pk(pk)); /* No, so try and get it */ if ((opt.keyserver_options.options & KEYSERVER_AUTO_KEY_RETRIEVE) && keyserver_any_configured (ctrl)) { log_info(_("WARNING: key %s may be revoked:" " fetching revocation key %s\n"), tempkeystr,keystr(keyid)); keyserver_import_fprint (ctrl, sig->revkey[idx].fpr, MAX_FINGERPRINT_LEN, opt.keyserver, 0); /* Do we have it now? */ rc=get_pubkey_byfprint_fast (NULL, sig->revkey[idx].fpr, MAX_FINGERPRINT_LEN); } if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY || gpg_err_code (rc) == GPG_ERR_UNUSABLE_PUBKEY) log_info(_("WARNING: key %s may be revoked:" " revocation key %s not present.\n"), tempkeystr,keystr(keyid)); xfree(tempkeystr); } } } } } } } /* * compare and merge the blocks * * o compare the signatures: If we already have this signature, check * that they compare okay; if not, issue a warning and ask the user. * o Simply add the signature. Can't verify here because we may not have * the signature's public key yet; verification is done when putting it * into the trustdb, which is done automagically as soon as this pubkey * is used. * Note: We indicate newly inserted packets with NODE_FLAG_A. */ static int merge_blocks (ctrl_t ctrl, kbnode_t keyblock_orig, kbnode_t keyblock, u32 *keyid, int *n_uids, int *n_sigs, int *n_subk ) { kbnode_t onode, node; int rc, found; /* 1st: handle revocation certificates */ for (node=keyblock->next; node; node=node->next ) { if (node->pkt->pkttype == PKT_USER_ID ) break; else if (node->pkt->pkttype == PKT_SIGNATURE && node->pkt->pkt.signature->sig_class == 0x20) { /* check whether we already have this */ found = 0; for (onode=keyblock_orig->next; onode; onode=onode->next) { if (onode->pkt->pkttype == PKT_USER_ID ) break; else if (onode->pkt->pkttype == PKT_SIGNATURE && onode->pkt->pkt.signature->sig_class == 0x20 && !cmp_signatures(onode->pkt->pkt.signature, node->pkt->pkt.signature)) { found = 1; break; } } if (!found) { kbnode_t n2 = clone_kbnode(node); insert_kbnode( keyblock_orig, n2, 0 ); n2->flag |= NODE_FLAG_A; ++*n_sigs; if(!opt.quiet) { char *p = get_user_id_native (ctrl, keyid); log_info(_("key %s: \"%s\" revocation" " certificate added\n"), keystr(keyid),p); xfree(p); } } } } /* 2nd: merge in any direct key (0x1F) sigs */ for(node=keyblock->next; node; node=node->next) { if (node->pkt->pkttype == PKT_USER_ID ) break; else if (node->pkt->pkttype == PKT_SIGNATURE && node->pkt->pkt.signature->sig_class == 0x1F) { /* check whether we already have this */ found = 0; for (onode=keyblock_orig->next; onode; onode=onode->next) { if (onode->pkt->pkttype == PKT_USER_ID) break; else if (onode->pkt->pkttype == PKT_SIGNATURE && onode->pkt->pkt.signature->sig_class == 0x1F && !cmp_signatures(onode->pkt->pkt.signature, node->pkt->pkt.signature)) { found = 1; break; } } if (!found ) { kbnode_t n2 = clone_kbnode(node); insert_kbnode( keyblock_orig, n2, 0 ); n2->flag |= NODE_FLAG_A; ++*n_sigs; if(!opt.quiet) log_info( _("key %s: direct key signature added\n"), keystr(keyid)); } } } /* 3rd: try to merge new certificates in */ for (onode=keyblock_orig->next; onode; onode=onode->next) { if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID) { /* find the user id in the imported keyblock */ for (node=keyblock->next; node; node=node->next) if (node->pkt->pkttype == PKT_USER_ID && !cmp_user_ids( onode->pkt->pkt.user_id, node->pkt->pkt.user_id ) ) break; if (node ) /* found: merge */ { rc = merge_sigs (onode, node, n_sigs); if (rc ) return rc; } } } /* 4th: add new user-ids */ for (node=keyblock->next; node; node=node->next) { if (node->pkt->pkttype == PKT_USER_ID) { /* do we have this in the original keyblock */ for (onode=keyblock_orig->next; onode; onode=onode->next ) if (onode->pkt->pkttype == PKT_USER_ID && !cmp_user_ids( onode->pkt->pkt.user_id, node->pkt->pkt.user_id ) ) break; if (!onode ) /* this is a new user id: append */ { rc = append_uid (keyblock_orig, node, n_sigs); if (rc ) return rc; ++*n_uids; } } } /* 5th: add new subkeys */ for (node=keyblock->next; node; node=node->next) { onode = NULL; if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { /* do we have this in the original keyblock? */ for(onode=keyblock_orig->next; onode; onode=onode->next) if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY && !cmp_public_keys( onode->pkt->pkt.public_key, node->pkt->pkt.public_key)) break; if (!onode ) /* This is a new subkey: append. */ { rc = append_key (keyblock_orig, node, n_sigs); if (rc) return rc; ++*n_subk; } } else if (node->pkt->pkttype == PKT_SECRET_SUBKEY) { /* do we have this in the original keyblock? */ for (onode=keyblock_orig->next; onode; onode=onode->next ) if (onode->pkt->pkttype == PKT_SECRET_SUBKEY && !cmp_public_keys (onode->pkt->pkt.public_key, node->pkt->pkt.public_key) ) break; if (!onode ) /* This is a new subkey: append. */ { rc = append_key (keyblock_orig, node, n_sigs); if (rc ) return rc; ++*n_subk; } } } /* 6th: merge subkey certificates */ for (onode=keyblock_orig->next; onode; onode=onode->next) { if (!(onode->flag & NODE_FLAG_A) && (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY || onode->pkt->pkttype == PKT_SECRET_SUBKEY)) { /* find the subkey in the imported keyblock */ for(node=keyblock->next; node; node=node->next) { if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) && !cmp_public_keys( onode->pkt->pkt.public_key, node->pkt->pkt.public_key ) ) break; } if (node) /* Found: merge. */ { rc = merge_keysigs( onode, node, n_sigs); if (rc ) return rc; } } } return 0; } /* Helper function for merge_blocks. * Append the userid starting with NODE and all signatures to KEYBLOCK. */ static int append_uid (kbnode_t keyblock, kbnode_t node, int *n_sigs) { kbnode_t n; kbnode_t n_where = NULL; log_assert (node->pkt->pkttype == PKT_USER_ID ); /* find the position */ for (n = keyblock; n; n_where = n, n = n->next) { if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY || n->pkt->pkttype == PKT_SECRET_SUBKEY ) break; } if (!n) n_where = NULL; /* and append/insert */ while (node) { /* we add a clone to the original keyblock, because this * one is released first */ n = clone_kbnode(node); if (n_where) { insert_kbnode( n_where, n, 0 ); n_where = n; } else add_kbnode( keyblock, n ); n->flag |= NODE_FLAG_A; node->flag |= NODE_FLAG_A; if (n->pkt->pkttype == PKT_SIGNATURE ) ++*n_sigs; node = node->next; if (node && node->pkt->pkttype != PKT_SIGNATURE ) break; } return 0; } /* Helper function for merge_blocks * Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID. * (how should we handle comment packets here?) */ static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs) { kbnode_t n, n2; int found = 0; log_assert (dst->pkt->pkttype == PKT_USER_ID); log_assert (src->pkt->pkttype == PKT_USER_ID); for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next) { if (n->pkt->pkttype != PKT_SIGNATURE ) continue; if (n->pkt->pkt.signature->sig_class == 0x18 || n->pkt->pkt.signature->sig_class == 0x28 ) continue; /* skip signatures which are only valid on subkeys */ found = 0; for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next) if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature)) { found++; break; } if (!found ) { /* This signature is new or newer, append N to DST. * We add a clone to the original keyblock, because this * one is released first */ n2 = clone_kbnode(n); insert_kbnode( dst, n2, PKT_SIGNATURE ); n2->flag |= NODE_FLAG_A; n->flag |= NODE_FLAG_A; ++*n_sigs; } } return 0; } /* Helper function for merge_blocks * Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY. */ static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs) { kbnode_t n, n2; int found = 0; log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY || dst->pkt->pkttype == PKT_SECRET_SUBKEY); for (n=src->next; n ; n = n->next) { if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY || n->pkt->pkttype == PKT_PUBLIC_KEY ) break; if (n->pkt->pkttype != PKT_SIGNATURE ) continue; found = 0; for (n2=dst->next; n2; n2 = n2->next) { if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY || n2->pkt->pkttype == PKT_PUBLIC_KEY ) break; if (n2->pkt->pkttype == PKT_SIGNATURE && (n->pkt->pkt.signature->keyid[0] == n2->pkt->pkt.signature->keyid[0]) && (n->pkt->pkt.signature->keyid[1] == n2->pkt->pkt.signature->keyid[1]) && (n->pkt->pkt.signature->timestamp <= n2->pkt->pkt.signature->timestamp) && (n->pkt->pkt.signature->sig_class == n2->pkt->pkt.signature->sig_class)) { found++; break; } } if (!found ) { /* This signature is new or newer, append N to DST. * We add a clone to the original keyblock, because this * one is released first */ n2 = clone_kbnode(n); insert_kbnode( dst, n2, PKT_SIGNATURE ); n2->flag |= NODE_FLAG_A; n->flag |= NODE_FLAG_A; ++*n_sigs; } } return 0; } /* Helper function for merge_blocks. * Append the subkey starting with NODE and all signatures to KEYBLOCK. * Mark all new and copied packets by setting flag bit 0. */ static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs) { kbnode_t n; log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY); while (node) { /* we add a clone to the original keyblock, because this * one is released first */ n = clone_kbnode(node); add_kbnode( keyblock, n ); n->flag |= NODE_FLAG_A; node->flag |= NODE_FLAG_A; if (n->pkt->pkttype == PKT_SIGNATURE ) ++*n_sigs; node = node->next; if (node && node->pkt->pkttype != PKT_SIGNATURE ) break; } return 0; } diff --git a/g10/keydb.h b/g10/keydb.h index 7f427382d..1da93a777 100644 --- a/g10/keydb.h +++ b/g10/keydb.h @@ -1,492 +1,492 @@ /* keydb.h - Key database * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, * 2006, 2010 Free Software Foundation, Inc. * Copyright (C) 2015, 2016 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 <https://www.gnu.org/licenses/>. */ #ifndef G10_KEYDB_H #define G10_KEYDB_H #include "../common/types.h" #include "../common/util.h" #include "packet.h" /* What qualifies as a certification (rather than a signature?) */ #define IS_CERT(s) (IS_KEY_SIG(s) || IS_UID_SIG(s) || IS_SUBKEY_SIG(s) \ || IS_KEY_REV(s) || IS_UID_REV(s) || IS_SUBKEY_REV(s)) #define IS_SIG(s) (!IS_CERT(s)) #define IS_KEY_SIG(s) ((s)->sig_class == 0x1f) #define IS_UID_SIG(s) (((s)->sig_class & ~3) == 0x10) #define IS_SUBKEY_SIG(s) ((s)->sig_class == 0x18) #define IS_KEY_REV(s) ((s)->sig_class == 0x20) #define IS_UID_REV(s) ((s)->sig_class == 0x30) #define IS_SUBKEY_REV(s) ((s)->sig_class == 0x28) struct getkey_ctx_s; typedef struct getkey_ctx_s *GETKEY_CTX; typedef struct getkey_ctx_s *getkey_ctx_t; /**************** * A Keyblock is all packets which form an entire certificate; * i.e. the public key, certificate, trust packets, user ids, * signatures, and subkey. * * This structure is also used to bind arbitrary packets together. */ struct kbnode_struct { KBNODE next; PACKET *pkt; int flag; int private_flag; ulong recno; /* used while updating the trustdb */ }; #define is_deleted_kbnode(a) ((a)->private_flag & 1) #define is_cloned_kbnode(a) ((a)->private_flag & 2) /* Bit flags used with build_pk_list. */ enum { PK_LIST_ENCRYPT_TO = 1, /* This is an encrypt-to recipient. */ PK_LIST_HIDDEN = 2, /* This is a hidden recipient. */ PK_LIST_CONFIG = 4, /* Specified via config file. */ PK_LIST_FROM_FILE = 8 /* Take key from file with that name. */ }; /* To store private data in the flags the private data must be left * shifted by this value. */ enum { PK_LIST_SHIFT = 4 }; /* Structure to hold a couple of public key certificates. */ typedef struct pk_list *PK_LIST; /* Deprecated. */ typedef struct pk_list *pk_list_t; struct pk_list { PK_LIST next; PKT_public_key *pk; int flags; /* See PK_LIST_ constants. */ }; /* Structure to hold a list of secret key certificates. */ typedef struct sk_list *SK_LIST; struct sk_list { SK_LIST next; PKT_public_key *pk; int mark; /* not used */ }; /* structure to collect all information which can be used to * identify a public key */ typedef struct pubkey_find_info *PUBKEY_FIND_INFO; struct pubkey_find_info { u32 keyid[2]; unsigned nbits; byte pubkey_algo; byte fingerprint[MAX_FINGERPRINT_LEN]; char userid[1]; }; -/* Helper type for preference fucntions. */ +/* Helper type for preference functions. */ union pref_hint { int digest_length; }; /* Constants to describe from where a key was fetched or updated. */ enum { KEYSRC_UNKNOWN = 0, KEYSRC_FILE = 1, /* Direct import from a file. */ KEYSRC_KS = 2, /* Public keyserver. */ KEYSRC_PREF_KS = 3, /* Preferred keysrver. */ KEYSRC_WKD = 4, /* Web Key Directory. */ KEYSRC_WKD_SD = 5, /* Web Key Directory but from a sub domain. */ KEYSRC_DANE = 6 /* OpenPGP DANE. */ }; /*-- keydb.c --*/ #define KEYDB_RESOURCE_FLAG_PRIMARY 2 /* The primary resource. */ #define KEYDB_RESOURCE_FLAG_DEFAULT 4 /* The default one. */ #define KEYDB_RESOURCE_FLAG_READONLY 8 /* Open in read only mode. */ #define KEYDB_RESOURCE_FLAG_GPGVDEF 16 /* Default file for gpgv. */ /* Format a search term for debugging output. The caller must free the result. */ char *keydb_search_desc_dump (struct keydb_search_desc *desc); /* Register a resource (keyring or keybox). */ gpg_error_t keydb_add_resource (const char *url, unsigned int flags); /* Dump some statistics to the log. */ void keydb_dump_stats (void); /* Create a new database handle. Returns NULL on error, sets ERRNO, and prints an error diagnostic. */ KEYDB_HANDLE keydb_new (void); /* Free all resources owned by the database handle. */ void keydb_release (KEYDB_HANDLE hd); /* Set a flag on the handle to suppress use of cached results. This is required for updating a keyring and for key listings. Fixme: Using a new parameter for keydb_new might be a better solution. */ void keydb_disable_caching (KEYDB_HANDLE hd); /* Save the last found state and invalidate the current selection. */ void keydb_push_found_state (KEYDB_HANDLE hd); /* Restore the previous save state. */ void keydb_pop_found_state (KEYDB_HANDLE hd); /* Return the file name of the resource. */ const char *keydb_get_resource_name (KEYDB_HANDLE hd); /* Return the keyblock last found by keydb_search. */ gpg_error_t keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb); /* Update the keyblock KB. */ gpg_error_t keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb); /* Insert a keyblock into one of the underlying keyrings or keyboxes. */ gpg_error_t keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb); /* Delete the currently selected keyblock. */ gpg_error_t keydb_delete_keyblock (KEYDB_HANDLE hd); /* Find the first writable resource. */ gpg_error_t keydb_locate_writable (KEYDB_HANDLE hd); /* Rebuild the on-disk caches of all key resources. */ void keydb_rebuild_caches (ctrl_t ctrl, int noisy); /* Return the number of skipped blocks (because they were to large to read from a keybox) since the last search reset. */ unsigned long keydb_get_skipped_counter (KEYDB_HANDLE hd); /* Clears the current search result and resets the handle's position. */ gpg_error_t keydb_search_reset (KEYDB_HANDLE hd); /* Search the database for keys matching the search description. */ gpg_error_t keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t *descindex); /* Return the first non-legacy key in the database. */ gpg_error_t keydb_search_first (KEYDB_HANDLE hd); /* Return the next key (not the next matching key!). */ gpg_error_t keydb_search_next (KEYDB_HANDLE hd); /* This is a convenience function for searching for keys with a long key id. */ gpg_error_t keydb_search_kid (KEYDB_HANDLE hd, u32 *kid); /* This is a convenience function for searching for keys with a long (20 byte) fingerprint. */ gpg_error_t keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr); /*-- pkclist.c --*/ void show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode ); int check_signatures_trust (ctrl_t ctrl, PKT_signature *sig); void release_pk_list (PK_LIST pk_list); int build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list); gpg_error_t find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use, int mark_hidden, int from_file, pk_list_t *pk_list_addr); int algo_available( preftype_t preftype, int algo, const union pref_hint *hint ); int select_algo_from_prefs( PK_LIST pk_list, int preftype, int request, const union pref_hint *hint); int select_mdc_from_pklist (PK_LIST pk_list); void warn_missing_mdc_from_pklist (PK_LIST pk_list); void warn_missing_aes_from_pklist (PK_LIST pk_list); /*-- skclist.c --*/ int random_is_faked (void); void release_sk_list( SK_LIST sk_list ); gpg_error_t build_sk_list (ctrl_t ctrl, strlist_t locusr, SK_LIST *ret_sk_list, unsigned use); /*-- passphrase.h --*/ unsigned char encode_s2k_iterations (int iterations); int have_static_passphrase(void); const char *get_static_passphrase (void); void set_passphrase_from_string(const char *pass); void read_passphrase_from_fd( int fd ); void passphrase_clear_cache (const char *cacheid); DEK *passphrase_to_dek_ext(u32 *keyid, int pubkey_algo, int cipher_algo, STRING2KEY *s2k, int mode, const char *tryagain_text, const char *custdesc, const char *custprompt, int *canceled); DEK *passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache, const char *tryagain_text, int *canceled); void set_next_passphrase( const char *s ); char *get_last_passphrase(void); void next_to_last_passphrase(void); void emit_status_need_passphrase (ctrl_t ctrl, u32 *keyid, u32 *mainkeyid, int pubkey_algo); #define FORMAT_KEYDESC_NORMAL 0 #define FORMAT_KEYDESC_IMPORT 1 #define FORMAT_KEYDESC_EXPORT 2 #define FORMAT_KEYDESC_DELKEY 3 char *gpg_format_keydesc (ctrl_t ctrl, PKT_public_key *pk, int mode, int escaped); /*-- getkey.c --*/ /* Cache a copy of a public key in the public key cache. */ void cache_public_key( PKT_public_key *pk ); /* Disable and drop the public key cache. */ void getkey_disable_caches(void); /* Return the public key with the key id KEYID and store it at PK. */ int get_pubkey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); /* Similar to get_pubkey, but it does not take PK->REQ_USAGE into account nor does it merge in the self-signed data. This function also only considers primary keys. */ int get_pubkey_fast (PKT_public_key *pk, u32 *keyid); /* Return the key block for the key with KEYID. */ kbnode_t get_pubkeyblock (ctrl_t ctrl, u32 *keyid); /* A list used by get_pubkeys to gather all of the matches. */ struct pubkey_s { struct pubkey_s *next; /* The key to use (either the public key or the subkey). */ PKT_public_key *pk; kbnode_t keyblock; }; typedef struct pubkey_s *pubkey_t; /* Free a single key. This does not remove key from any list! */ void pubkey_free (pubkey_t key); /* Free a list of public keys. */ void pubkeys_free (pubkey_t keys); -/* Returns all keys that match the search specfication SEARCH_TERMS. +/* Returns all keys that match the search specification SEARCH_TERMS. The returned keys should be freed using pubkeys_free. */ gpg_error_t get_pubkeys (ctrl_t ctrl, char *search_terms, int use, int include_unusable, char *source, int warn_possibly_ambiguous, pubkey_t *r_keys); /* Find a public key identified by NAME. */ int get_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk, const char *name, KBNODE *ret_keyblock, KEYDB_HANDLE *ret_kdbhd, int include_unusable, int no_akl ); /* Likewise, but only return the best match if NAME resembles a mail * address. */ int get_best_pubkey_byname (ctrl_t ctrl, GETKEY_CTX *retctx, PKT_public_key *pk, const char *name, KBNODE *ret_keyblock, int include_unusable, int no_akl); /* Get a public key directly from file FNAME. */ gpg_error_t get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname); /* Return the public key with the key id KEYID iff the secret key is * available and store it at PK. */ gpg_error_t get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid); /* Lookup a key with the specified fingerprint. */ int get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock, const byte *fprint, size_t fprint_len); /* This function is similar to get_pubkey_byfprint, but it doesn't merge the self-signed data into the public key and subkeys or into the user ids. */ int get_pubkey_byfprint_fast (PKT_public_key *pk, const byte *fprint, size_t fprint_len); /* Returns true if a secret key is available for the public key with key id KEYID. */ int have_secret_key_with_kid (u32 *keyid); /* Parse the --default-key parameter. Returns the last key (in terms of when the option is given) that is available. */ const char *parse_def_secret_key (ctrl_t ctrl); /* Look up a secret key. */ gpg_error_t get_seckey_default (ctrl_t ctrl, PKT_public_key *pk); /* Search for keys matching some criteria. */ gpg_error_t getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, strlist_t names, int want_secret, kbnode_t *ret_keyblock); /* Search for one key matching some criteria. */ gpg_error_t getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk, const char *name, int want_secret, kbnode_t *ret_keyblock); /* Return the next search result. */ gpg_error_t getkey_next (ctrl_t ctrl, getkey_ctx_t ctx, PKT_public_key *pk, kbnode_t *ret_keyblock); /* Release any resources used by a key listing context. */ void getkey_end (ctrl_t ctrl, getkey_ctx_t ctx); /* Return the database handle used by this context. The context still owns the handle. */ KEYDB_HANDLE get_ctx_handle(GETKEY_CTX ctx); /* Enumerate some secret keys. */ gpg_error_t enum_secret_keys (ctrl_t ctrl, void **context, PKT_public_key *pk); /* Set the mainkey_id fields for all keys in KEYBLOCK. */ void setup_main_keyids (kbnode_t keyblock); /* This function merges information from the self-signed data into the data structures. */ void merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock); char*get_user_id_string_native (ctrl_t ctrl, u32 *keyid); char*get_long_user_id_string (ctrl_t ctrl, u32 *keyid); char*get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn); char*get_user_id_native (ctrl_t ctrl, u32 *keyid); char *get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t *rn); char *get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr); void release_akl(void); int parse_auto_key_locate(char *options); /*-- keyid.c --*/ int pubkey_letter( int algo ); char *pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize); #define PUBKEY_STRING_SIZE 32 u32 v3_keyid (gcry_mpi_t a, u32 *ki); void hash_public_key( gcry_md_hd_t md, PKT_public_key *pk ); char *format_keyid (u32 *keyid, int format, char *buffer, int len); /* Return PK's keyid. The memory is owned by PK. */ u32 *pk_keyid (PKT_public_key *pk); /* Return the keyid of the primary key associated with PK. The memory is owned by PK. */ u32 *pk_main_keyid (PKT_public_key *pk); /* Order A and B. If A < B then return -1, if A == B then return 0, and if A > B then return 1. */ static int GPGRT_ATTR_UNUSED keyid_cmp (const u32 *a, const u32 *b) { if (a[0] < b[0]) return -1; if (a[0] > b[0]) return 1; if (a[1] < b[1]) return -1; if (a[1] > b[1]) return 1; return 0; } /* Return whether PK is a primary key. */ static int GPGRT_ATTR_UNUSED pk_is_primary (PKT_public_key *pk) { return keyid_cmp (pk_keyid (pk), pk_main_keyid (pk)) == 0; } /* Copy the keyid in SRC to DEST and return DEST. */ u32 *keyid_copy (u32 *dest, const u32 *src); size_t keystrlen(void); const char *keystr(u32 *keyid); const char *keystr_with_sub (u32 *main_kid, u32 *sub_kid); const char *keystr_from_pk(PKT_public_key *pk); const char *keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk); /* Return PK's key id as a string using the default format. PK owns the storage. */ const char *pk_keyid_str (PKT_public_key *pk); const char *keystr_from_desc(KEYDB_SEARCH_DESC *desc); u32 keyid_from_pk( PKT_public_key *pk, u32 *keyid ); u32 keyid_from_sig (PKT_signature *sig, u32 *keyid ); u32 keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint, size_t fprint_len, u32 *keyid); byte *namehash_from_uid(PKT_user_id *uid); unsigned nbits_from_pk( PKT_public_key *pk ); const char *datestr_from_pk( PKT_public_key *pk ); const char *datestr_from_sig( PKT_signature *sig ); const char *expirestr_from_pk( PKT_public_key *pk ); const char *expirestr_from_sig( PKT_signature *sig ); const char *revokestr_from_pk( PKT_public_key *pk ); const char *usagestr_from_pk (PKT_public_key *pk, int fill); const char *colon_strtime (u32 t); const char *colon_datestr_from_pk (PKT_public_key *pk); const char *colon_datestr_from_sig (PKT_signature *sig); const char *colon_expirestr_from_sig (PKT_signature *sig); byte *fingerprint_from_pk( PKT_public_key *pk, byte *buf, size_t *ret_len ); char *hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen); char *format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen); gpg_error_t keygrip_from_pk (PKT_public_key *pk, unsigned char *array); gpg_error_t hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip); /*-- kbnode.c --*/ KBNODE new_kbnode( PACKET *pkt ); KBNODE clone_kbnode( KBNODE node ); void release_kbnode( KBNODE n ); void delete_kbnode( KBNODE node ); void add_kbnode( KBNODE root, KBNODE node ); void insert_kbnode( KBNODE root, KBNODE node, int pkttype ); void move_kbnode( KBNODE *root, KBNODE node, KBNODE where ); void remove_kbnode( KBNODE *root, KBNODE node ); KBNODE find_prev_kbnode( KBNODE root, KBNODE node, int pkttype ); KBNODE find_next_kbnode( KBNODE node, int pkttype ); KBNODE find_kbnode( KBNODE node, int pkttype ); KBNODE walk_kbnode( KBNODE root, KBNODE *context, int all ); void clear_kbnode_flags( KBNODE n ); int commit_kbnode( KBNODE *root ); void dump_kbnode( KBNODE node ); #endif /*G10_KEYDB_H*/ diff --git a/g10/keyedit.c b/g10/keyedit.c index a7a5f7209..ba08d88c3 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1,6765 +1,6765 @@ /* keyedit.c - Edit properties of a key * Copyright (C) 1998-2010 Free Software Foundation, Inc. * Copyright (C) 1998-2017 Werner Koch * Copyright (C) 2015, 2016 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include <readline/readline.h> #endif #include "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "photoid.h" #include "../common/util.h" #include "main.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/status.h" #include "../common/i18n.h" #include "keyserver-internal.h" #include "call-agent.h" #include "../common/host2net.h" #include "tofu.h" static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose); static void show_names (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, PKT_public_key * pk, unsigned int flag, int with_prefs); static void show_key_with_all_names (ctrl_t ctrl, estream_t fp, KBNODE keyblock, int only_marked, int with_revoker, int with_fpr, int with_subkeys, int with_prefs, int nowarn); static void show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys); static void show_key_and_grip (kbnode_t keyblock); static void subkey_expire_warning (kbnode_t keyblock); static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock, int photo, const char *photo_name, const char *uidstr); static void menu_deluid (KBNODE pub_keyblock); static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only); static void menu_delkey (KBNODE pub_keyblock); static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive); static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int force_mainkey, u32 newexpiration); static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock); static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock); static int menu_set_notation (ctrl_t ctrl, const char *string, kbnode_t pub_keyblock); static int menu_select_uid (KBNODE keyblock, int idx); static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash); static int menu_select_key (KBNODE keyblock, int idx, char *p); static int count_uids (KBNODE keyblock); static int count_uids_with_flag (KBNODE keyblock, unsigned flag); static int count_keys_with_flag (KBNODE keyblock, unsigned flag); static int count_selected_uids (KBNODE keyblock); static int real_uids_left (KBNODE keyblock); static int count_selected_keys (KBNODE keyblock); static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock); static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock); static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, const struct revocation_reason_info *reason, int *modified); static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock); #ifndef NO_TRUST_MODELS static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable); #endif /*!NO_TRUST_MODELS*/ static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock); static int update_trust = 0; #define CONTROL_D ('D' - 'A' + 1) #define NODFLG_BADSIG (1<<0) /* Bad signature. */ #define NODFLG_NOKEY (1<<1) /* No public key. */ #define NODFLG_SIGERR (1<<2) /* Other sig error. */ #define NODFLG_MARK_A (1<<4) /* Temporary mark. */ #define NODFLG_DELSIG (1<<5) /* To be deleted. */ #define NODFLG_SELUID (1<<8) /* Indicate the selected userid. */ #define NODFLG_SELKEY (1<<9) /* Indicate the selected key. */ #define NODFLG_SELSIG (1<<10) /* Indicate a selected signature. */ struct sign_attrib { int non_exportable, non_revocable; struct revocation_reason_info *reason; byte trust_depth, trust_value; char *trust_regexp; }; /* TODO: Fix duplicated code between here and the check-sigs/list-sigs code in keylist.c. */ static int print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int *is_selfsig, int print_without_key) { PKT_signature *sig = node->pkt->pkt.signature; int rc, sigrc; /* TODO: Make sure a cached sig record here still has the pk that issued it. See also keylist.c:list_keyblock_print */ rc = check_key_signature (ctrl, keyblock, node, is_selfsig); switch (gpg_err_code (rc)) { case 0: node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: node->flag = NODFLG_BADSIG; sigrc = '-'; if (inv_sigs) ++ * inv_sigs; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: node->flag = NODFLG_NOKEY; sigrc = '?'; if (no_key) ++ * no_key; break; default: node->flag = NODFLG_SIGERR; sigrc = '%'; if (oth_err) ++ * oth_err; break; } if (sigrc != '?' || print_without_key) { es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:", sigrc, sig->pubkey_algo, (ulong) sig->keyid[0], (ulong) sig->keyid[1], (ulong) sig->timestamp, (ulong) sig->expiredate); if (sig->trust_depth || sig->trust_value) es_printf ("%d %d", sig->trust_depth, sig->trust_value); es_printf (":"); if (sig->trust_regexp) es_write_sanitized (es_stdout, sig->trust_regexp, strlen (sig->trust_regexp), ":", NULL); es_printf ("::%02x%c\n", sig->sig_class, sig->flags.exportable ? 'x' : 'l'); if (opt.show_subpackets) print_subpackets_colon (sig); } return (sigrc == '!'); } /* * Print information about a signature (rc is its status), check it * and return true if the signature is okay. NODE must be a signature * packet. With EXTENDED set all possible signature list options will * always be printed. */ static int print_one_sig (ctrl_t ctrl, int rc, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int is_selfsig, int print_without_key, int extended) { PKT_signature *sig = node->pkt->pkt.signature; int sigrc; int is_rev = sig->sig_class == 0x30; /* TODO: Make sure a cached sig record here still has the pk that issued it. See also keylist.c:list_keyblock_print */ switch (gpg_err_code (rc)) { case 0: node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: node->flag = NODFLG_BADSIG; sigrc = '-'; if (inv_sigs) ++ * inv_sigs; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: node->flag = NODFLG_NOKEY; sigrc = '?'; if (no_key) ++ * no_key; break; default: node->flag = NODFLG_SIGERR; sigrc = '%'; if (oth_err) ++ * oth_err; break; } if (sigrc != '?' || print_without_key) { tty_printf ("%s%c%c %c%c%c%c%c%c %s %s", is_rev ? "rev" : "sig", sigrc, (sig->sig_class - 0x10 > 0 && sig->sig_class - 0x10 < 4) ? '0' + sig->sig_class - 0x10 : ' ', sig->flags.exportable ? ' ' : 'L', sig->flags.revocable ? ' ' : 'R', sig->flags.policy_url ? 'P' : ' ', sig->flags.notation ? 'N' : ' ', sig->flags.expired ? 'X' : ' ', (sig->trust_depth > 9) ? 'T' : (sig->trust_depth > 0) ? '0' + sig->trust_depth : ' ', keystr (sig->keyid), datestr_from_sig (sig)); if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended ) tty_printf (" %s", expirestr_from_sig (sig)); tty_printf (" "); if (sigrc == '%') tty_printf ("[%s] ", gpg_strerror (rc)); else if (sigrc == '?') ; else if (is_selfsig) { tty_printf (is_rev ? _("[revocation]") : _("[self-signature]")); if (extended && sig->flags.chosen_selfsig) tty_printf ("*"); } else { size_t n; char *p = get_user_id (ctrl, sig->keyid, &n); tty_print_utf8_string2 (NULL, p, n, opt.screen_columns - keystrlen () - 26 - ((opt. list_options & LIST_SHOW_SIG_EXPIRE) ? 11 : 0)); xfree (p); } tty_printf ("\n"); if (sig->flags.policy_url && ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended)) show_policy_url (sig, 3, -1); if (sig->flags.notation && ((opt.list_options & LIST_SHOW_NOTATIONS) || extended)) show_notation (sig, 3, -1, ((opt. list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) + ((opt. list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0)); if (sig->flags.pref_ks && ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended)) show_keyserver_url (sig, 3, -1); if (extended) { PKT_public_key *pk = keyblock->pkt->pkt.public_key; const unsigned char *s; s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL); if (s && *s) tty_printf (" [primary]\n"); s = parse_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE, NULL); if (s && buf32_to_u32 (s)) tty_printf (" [expires: %s]\n", isotimestamp (pk->timestamp + buf32_to_u32 (s))); } } return (sigrc == '!'); } static int print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int *is_selfsig, int print_without_key, int extended) { int rc; rc = check_key_signature (ctrl, keyblock, node, is_selfsig); return print_one_sig (ctrl, rc, keyblock, node, inv_sigs, no_key, oth_err, *is_selfsig, print_without_key, extended); } /* Order two signatures. The actual ordering isn't important. Our goal is to ensure that identical signatures occur together. */ static int sig_comparison (const void *av, const void *bv) { const KBNODE an = *(const KBNODE *) av; const KBNODE bn = *(const KBNODE *) bv; const PKT_signature *a; const PKT_signature *b; int ndataa; int ndatab; int i; log_assert (an->pkt->pkttype == PKT_SIGNATURE); log_assert (bn->pkt->pkttype == PKT_SIGNATURE); a = an->pkt->pkt.signature; b = bn->pkt->pkt.signature; if (a->digest_algo < b->digest_algo) return -1; if (a->digest_algo > b->digest_algo) return 1; ndataa = pubkey_get_nsig (a->pubkey_algo); ndatab = pubkey_get_nsig (b->pubkey_algo); if (ndataa != ndatab) return (ndataa < ndatab)? -1 : 1; for (i = 0; i < ndataa; i ++) { int c = gcry_mpi_cmp (a->data[i], b->data[i]); if (c != 0) return c; } /* Okay, they are equal. */ return 0; } /* Perform a few sanity checks on a keyblock is okay and possibly repair some damage. Concretely: - Detect duplicate signatures and remove them. - Detect out of order signatures and relocate them (e.g., a sig over user id X located under subkey Y). Note: this function does not remove signatures that don't belong or components that are not signed! (Although it would be trivial to do so.) If ONLY_SELFSIGS is true, then this function only reorders self signatures (it still checks all signatures for duplicates, however). Returns 1 if the keyblock was modified, 0 otherwise. */ static int check_all_keysigs (ctrl_t ctrl, kbnode_t kb, int only_selected, int only_selfsigs) { gpg_error_t err; PKT_public_key *pk; KBNODE n, n_next, *n_prevp, n2; char *pending_desc = NULL; PKT_public_key *issuer; KBNODE last_printed_component; KBNODE current_component = NULL; int dups = 0; int missing_issuer = 0; int reordered = 0; int bad_signature = 0; int missing_selfsig = 0; int modified = 0; log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); pk = kb->pkt->pkt.public_key; /* First we look for duplicates. */ { int nsigs; kbnode_t *sigs; int i; int last_i; /* Count the sigs. */ for (nsigs = 0, n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; else if (n->pkt->pkttype == PKT_SIGNATURE) nsigs ++; } if (!nsigs) return 0; /* No signatures at all. */ /* Add them all to the SIGS array. */ sigs = xtrycalloc (nsigs, sizeof *sigs); if (!sigs) { log_error (_("error allocating memory: %s\n"), gpg_strerror (gpg_error_from_syserror ())); return 0; } i = 0; for (n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; if (n->pkt->pkttype != PKT_SIGNATURE) continue; sigs[i] = n; i ++; } log_assert (i == nsigs); qsort (sigs, nsigs, sizeof (sigs[0]), sig_comparison); last_i = 0; for (i = 1; i < nsigs; i ++) { log_assert (sigs[last_i]); log_assert (sigs[last_i]->pkt->pkttype == PKT_SIGNATURE); log_assert (sigs[i]); log_assert (sigs[i]->pkt->pkttype == PKT_SIGNATURE); if (sig_comparison (&sigs[last_i], &sigs[i]) == 0) /* They are the same. Kill the latter. */ { if (DBG_PACKET) { PKT_signature *sig = sigs[i]->pkt->pkt.signature; log_debug ("Signature appears multiple times, " "deleting duplicate:\n"); log_debug (" sig: class 0x%x, issuer: %s," " timestamp: %s (%lld), digest: %02x %02x\n", sig->sig_class, keystr (sig->keyid), isotimestamp (sig->timestamp), (long long) sig->timestamp, sig->digest_start[0], sig->digest_start[1]); } /* Remove sigs[i] from the keyblock. */ { KBNODE z, *prevp; int to_kill = last_i; last_i = i; for (prevp = &kb, z = kb; z; prevp = &z->next, z = z->next) if (z == sigs[to_kill]) break; *prevp = sigs[to_kill]->next; sigs[to_kill]->next = NULL; release_kbnode (sigs[to_kill]); sigs[to_kill] = NULL; dups ++; modified = 1; } } else last_i = i; } xfree (sigs); } /* Make sure the sigs occur after the component (public key, subkey, user id) that they sign. */ issuer = NULL; last_printed_component = NULL; for (n_prevp = &kb, n = kb; n; /* If we moved n, then n_prevp is need valid. */ n_prevp = (n->next == n_next ? &n->next : n_prevp), n = n_next) { PACKET *p; int processed_current_component; PKT_signature *sig; int rc; int dump_sig_params = 0; n_next = n->next; if (is_deleted_kbnode (n)) continue; p = n->pkt; if (issuer && issuer != pk) { free_public_key (issuer); issuer = NULL; } xfree (pending_desc); pending_desc = NULL; switch (p->pkttype) { case PKT_PUBLIC_KEY: log_assert (p->pkt.public_key == pk); if (only_selected && ! (n->flag & NODFLG_SELKEY)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("public key %s: timestamp: %s (%lld)\n", pk_keyid_str (pk), isotimestamp (pk->timestamp), (long long) pk->timestamp); current_component = n; break; case PKT_PUBLIC_SUBKEY: if (only_selected && ! (n->flag & NODFLG_SELKEY)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("subkey %s: timestamp: %s (%lld)\n", pk_keyid_str (p->pkt.public_key), isotimestamp (p->pkt.public_key->timestamp), (long long) p->pkt.public_key->timestamp); current_component = n; break; case PKT_USER_ID: if (only_selected && ! (n->flag & NODFLG_SELUID)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("user id: %s\n", p->pkt.user_id->attrib_data ? "[ photo id ]" : p->pkt.user_id->name); current_component = n; break; case PKT_SIGNATURE: if (! current_component) /* The current component is not selected, don't check the sigs under it. */ break; sig = n->pkt->pkt.signature; pending_desc = xasprintf (" sig: class: 0x%x, issuer: %s," " timestamp: %s (%lld), digest: %02x %02x", sig->sig_class, keystr (sig->keyid), isotimestamp (sig->timestamp), (long long) sig->timestamp, sig->digest_start[0], sig->digest_start[1]); if (keyid_cmp (pk_keyid (pk), sig->keyid) == 0) issuer = pk; else /* Issuer is a different key. */ { if (only_selfsigs) continue; issuer = xmalloc (sizeof (*issuer)); err = get_pubkey (ctrl, issuer, sig->keyid); if (err) { xfree (issuer); issuer = NULL; if (DBG_PACKET) { if (pending_desc) log_debug ("%s", pending_desc); log_debug (" Can't check signature allegedly" " issued by %s: %s\n", keystr (sig->keyid), gpg_strerror (err)); } missing_issuer ++; break; } } if ((err = openpgp_pk_test_algo (sig->pubkey_algo))) { if (DBG_PACKET && pending_desc) log_debug ("%s", pending_desc); tty_printf (_("can't check signature with unsupported" " public-key algorithm (%d): %s.\n"), sig->pubkey_algo, gpg_strerror (err)); break; } if ((err = openpgp_md_test_algo (sig->digest_algo))) { if (DBG_PACKET && pending_desc) log_debug ("%s", pending_desc); tty_printf (_("can't check signature with unsupported" " message-digest algorithm %d: %s.\n"), sig->digest_algo, gpg_strerror (err)); break; } /* We iterate over the keyblock. Most likely, the matching component is the current component so always try that first. */ processed_current_component = 0; for (n2 = current_component; n2; n2 = (processed_current_component ? n2->next : kb), processed_current_component = 1) if (is_deleted_kbnode (n2)) continue; else if (processed_current_component && n2 == current_component) /* Don't process it twice. */ continue; else { err = check_signature_over_key_or_uid (ctrl, issuer, sig, kb, n2->pkt, NULL, NULL); if (! err) break; } /* n/sig is a signature and n2 is the component (public key, subkey or user id) that it signs, if any. current_component is that component that it appears to apply to (according to the ordering). */ if (current_component == n2) { if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Good signature over last key or uid!\n"); } rc = 0; } else if (n2) { log_assert (n2->pkt->pkttype == PKT_USER_ID || n2->pkt->pkttype == PKT_PUBLIC_KEY || n2->pkt->pkttype == PKT_PUBLIC_SUBKEY); if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Good signature out of order!" " (Over %s (%d) '%s')\n", n2->pkt->pkttype == PKT_USER_ID ? "user id" : n2->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "subkey" : "primary key", n2->pkt->pkttype, n2->pkt->pkttype == PKT_USER_ID ? n2->pkt->pkt.user_id->name : pk_keyid_str (n2->pkt->pkt.public_key)); } /* Reorder the packets: move the signature n to be just after n2. */ /* Unlink the signature. */ log_assert (n_prevp); *n_prevp = n->next; /* Insert the sig immediately after the component. */ n->next = n2->next; n2->next = n; reordered ++; modified = 1; rc = 0; } else { if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Bad signature.\n"); } if (DBG_PACKET) dump_sig_params = 1; bad_signature ++; rc = GPG_ERR_BAD_SIGNATURE; } /* We don't cache the result here, because we haven't completely checked that the signature is legitimate. For instance, if we have a revocation certificate on Alice's key signed by Bob, the signature may be good, but we haven't checked that Bob is a designated revoker. */ /* cache_sig_result (sig, rc); */ { int has_selfsig = 0; if (! rc && issuer == pk) { if (n2->pkt->pkttype == PKT_PUBLIC_KEY && (/* Direct key signature. */ sig->sig_class == 0x1f /* Key revocation signature. */ || sig->sig_class == 0x20)) has_selfsig = 1; if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY && (/* Subkey binding sig. */ sig->sig_class == 0x18 /* Subkey revocation sig. */ || sig->sig_class == 0x28)) has_selfsig = 1; if (n2->pkt->pkttype == PKT_USER_ID && (/* Certification sigs. */ sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13 /* Certification revocation sig. */ || sig->sig_class == 0x30)) has_selfsig = 1; } if ((n2 && n2 != last_printed_component) || (! n2 && last_printed_component != current_component)) { int is_reordered = n2 && n2 != current_component; if (n2) last_printed_component = n2; else last_printed_component = current_component; if (!modified) ; else if (last_printed_component->pkt->pkttype == PKT_USER_ID) { tty_printf ("uid "); tty_print_utf8_string (last_printed_component ->pkt->pkt.user_id->name, last_printed_component ->pkt->pkt.user_id->len); } else if (last_printed_component->pkt->pkttype == PKT_PUBLIC_KEY) tty_printf ("pub %s", pk_keyid_str (last_printed_component ->pkt->pkt.public_key)); else tty_printf ("sub %s", pk_keyid_str (last_printed_component ->pkt->pkt.public_key)); if (modified) { if (is_reordered) tty_printf (_(" (reordered signatures follow)")); tty_printf ("\n"); } } if (modified) print_one_sig (ctrl, rc, kb, n, NULL, NULL, NULL, has_selfsig, 0, only_selfsigs); } if (dump_sig_params) { int i; for (i = 0; i < pubkey_get_nsig (sig->pubkey_algo); i ++) { char buffer[1024]; size_t len; char *printable; gcry_mpi_print (GCRYMPI_FMT_USG, buffer, sizeof (buffer), &len, sig->data[i]); printable = bin2hex (buffer, len, NULL); log_info (" %d: %s\n", i, printable); xfree (printable); } } break; default: if (DBG_PACKET) log_debug ("unhandled packet: %d\n", p->pkttype); break; } } xfree (pending_desc); pending_desc = NULL; if (issuer != pk) free_public_key (issuer); issuer = NULL; /* Identify keys / uids that don't have a self-sig. */ { int has_selfsig = 0; PACKET *p; PKT_signature *sig; current_component = NULL; for (n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; p = n->pkt; switch (p->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_USER_ID: if (current_component && ! has_selfsig) missing_selfsig ++; current_component = n; has_selfsig = 0; break; case PKT_SIGNATURE: if (! current_component || has_selfsig) break; sig = n->pkt->pkt.signature; if (! (sig->flags.checked && sig->flags.valid)) break; if (keyid_cmp (pk_keyid (pk), sig->keyid) != 0) /* Different issuer, couldn't be a self-sig. */ break; if (current_component->pkt->pkttype == PKT_PUBLIC_KEY && (/* Direct key signature. */ sig->sig_class == 0x1f /* Key revocation signature. */ || sig->sig_class == 0x20)) has_selfsig = 1; if (current_component->pkt->pkttype == PKT_PUBLIC_SUBKEY && (/* Subkey binding sig. */ sig->sig_class == 0x18 /* Subkey revocation sig. */ || sig->sig_class == 0x28)) has_selfsig = 1; if (current_component->pkt->pkttype == PKT_USER_ID && (/* Certification sigs. */ sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13 /* Certification revocation sig. */ || sig->sig_class == 0x30)) has_selfsig = 1; break; default: if (current_component && ! has_selfsig) missing_selfsig ++; current_component = NULL; } } } if (dups || missing_issuer || bad_signature || reordered) tty_printf (_("key %s:\n"), pk_keyid_str (pk)); if (dups) tty_printf (ngettext ("%d duplicate signature removed\n", "%d duplicate signatures removed\n", dups), dups); if (missing_issuer) tty_printf (ngettext ("%d signature not checked due to a missing key\n", "%d signatures not checked due to missing keys\n", missing_issuer), missing_issuer); if (bad_signature) tty_printf (ngettext ("%d bad signature\n", "%d bad signatures\n", bad_signature), bad_signature); if (reordered) tty_printf (ngettext ("%d signature reordered\n", "%d signatures reordered\n", reordered), reordered); if (only_selfsigs && (bad_signature || reordered)) tty_printf (_("Warning: errors found and only checked self-signatures," " run '%s' to check all signatures.\n"), "check"); return modified; } static int sign_mk_attrib (PKT_signature * sig, void *opaque) { struct sign_attrib *attrib = opaque; byte buf[8]; if (attrib->non_exportable) { buf[0] = 0; /* not exportable */ build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1); } if (attrib->non_revocable) { buf[0] = 0; /* not revocable */ build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1); } if (attrib->reason) revocation_reason_build_cb (sig, attrib->reason); if (attrib->trust_depth) { /* Not critical. If someone doesn't understand trust sigs, this can still be a valid regular signature. */ buf[0] = attrib->trust_depth; buf[1] = attrib->trust_value; build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2); /* Critical. If someone doesn't understands regexps, this whole sig should be invalid. Note the +1 for the length - regexps are null terminated. */ if (attrib->trust_regexp) build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP, attrib->trust_regexp, strlen (attrib->trust_regexp) + 1); } return 0; } static void trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp) { char *p; *trust_value = 0; *trust_depth = 0; *regexp = NULL; /* Same string as pkclist.c:do_edit_ownertrust */ tty_printf (_ ("Please decide how far you trust this user to correctly verify" " other users' keys\n(by looking at passports, checking" " fingerprints from different sources, etc.)\n")); tty_printf ("\n"); tty_printf (_(" %d = I trust marginally\n"), 1); tty_printf (_(" %d = I trust fully\n"), 2); tty_printf ("\n"); while (*trust_value == 0) { p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); /* 60 and 120 are as per RFC2440 */ if (p[0] == '1' && !p[1]) *trust_value = 60; else if (p[0] == '2' && !p[1]) *trust_value = 120; xfree (p); } tty_printf ("\n"); tty_printf (_("Please enter the depth of this trust signature.\n" "A depth greater than 1 allows the key you are" " signing to make\n" "trust signatures on your behalf.\n")); tty_printf ("\n"); while (*trust_depth == 0) { p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); *trust_depth = atoi (p); xfree (p); } tty_printf ("\n"); tty_printf (_("Please enter a domain to restrict this signature, " "or enter for none.\n")); tty_printf ("\n"); p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); if (strlen (p) > 0) { char *q = p; int regexplen = 100, ind; *regexp = xmalloc (regexplen); /* Now mangle the domain the user entered into a regexp. To do this, \-escape everything that isn't alphanumeric, and attach "<[^>]+[@.]" to the front, and ">$" to the end. */ strcpy (*regexp, "<[^>]+[@.]"); ind = strlen (*regexp); while (*q) { if (!((*q >= 'A' && *q <= 'Z') || (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9'))) (*regexp)[ind++] = '\\'; (*regexp)[ind++] = *q; if ((regexplen - ind) < 3) { regexplen += 100; *regexp = xrealloc (*regexp, regexplen); } q++; } (*regexp)[ind] = '\0'; strcat (*regexp, ">$"); } xfree (p); tty_printf ("\n"); } /* * Loop over all LOCUSR and sign the uids after asking. If no * user id is marked, all user ids will be signed; if some user_ids * are marked only those will be signed. If QUICK is true the * function won't ask the user and use sensible defaults. */ static int sign_uids (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, strlist_t locusr, int *ret_modified, int local, int nonrevocable, int trust, int interactive, int quick) { int rc = 0; SK_LIST sk_list = NULL; SK_LIST sk_rover = NULL; PKT_public_key *pk = NULL; KBNODE node, uidnode; PKT_public_key *primary_pk = NULL; int select_all = !count_selected_uids (keyblock) || interactive; /* Build a list of all signators. * * We use the CERT flag to request the primary which must always * be one which is capable of signing keys. I can't see a reason * why to sign keys using a subkey. Implementation of USAGE_CERT * is just a hack in getkey.c and does not mean that a subkey * marked as certification capable will be used. */ rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT); if (rc) goto leave; /* Loop over all signators. */ for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { u32 sk_keyid[2], pk_keyid[2]; char *p, *trust_regexp = NULL; int class = 0, selfsig = 0; u32 duration = 0, timestamp = 0; byte trust_depth = 0, trust_value = 0; pk = sk_rover->pk; keyid_from_pk (pk, sk_keyid); /* Set mark A for all selected user ids. */ for (node = keyblock; node; node = node->next) { if (select_all || (node->flag & NODFLG_SELUID)) node->flag |= NODFLG_MARK_A; else node->flag &= ~NODFLG_MARK_A; } /* Reset mark for uids which are already signed. */ uidnode = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { primary_pk = node->pkt->pkt.public_key; keyid_from_pk (primary_pk, pk_keyid); /* Is this a self-sig? */ if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1]) selfsig = 1; } else if (node->pkt->pkttype == PKT_USER_ID) { uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL; if (uidnode) { int yesreally = 0; char *user; user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); if (opt.only_sign_text_ids && uidnode->pkt->pkt.user_id->attribs) { tty_fprintf (fp, _("Skipping user ID \"%s\"," " which is not a text ID.\n"), user); uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (uidnode->pkt->pkt.user_id->flags.revoked) { tty_fprintf (fp, _("User ID \"%s\" is revoked."), user); if (selfsig) tty_fprintf (fp, "\n"); else if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } else if (uidnode->pkt->pkt.user_id->flags.expired) { tty_fprintf (fp, _("User ID \"%s\" is expired."), user); if (selfsig) tty_fprintf (fp, "\n"); else if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.expire_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } else if (!uidnode->pkt->pkt.user_id->created && !selfsig) { tty_fprintf (fp, _("User ID \"%s\" is not self-signed."), user); if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } if (uidnode && interactive && !yesreally && !quick) { tty_fprintf (fp, _("User ID \"%s\" is signable. "), user); if (!cpr_get_answer_is_yes ("sign_uid.sign_okay", _("Sign it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } } xfree (user); } } else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE && (node->pkt->pkt.signature->sig_class & ~3) == 0x10) { if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0] && sk_keyid[1] == node->pkt->pkt.signature->keyid[1]) { char buf[50]; char *user; user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); /* It's a v3 self-sig. Make it into a v4 self-sig? */ if (node->pkt->pkt.signature->version < 4 && selfsig && !quick) { tty_fprintf (fp, _("The self-signature on \"%s\"\n" "is a PGP 2.x-style signature.\n"), user); /* Note that the regular PGP2 warning below still applies if there are no v4 sigs on this key at all. */ if (opt.expert) if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay", _("Do you want to promote " "it to an OpenPGP self-" "signature? (y/N) "))) { node->flag |= NODFLG_DELSIG; xfree (user); continue; } } /* Is the current signature expired? */ if (node->pkt->pkt.signature->flags.expired) { tty_fprintf (fp, _("Your current signature on \"%s\"\n" "has expired.\n"), user); if (quick || cpr_get_answer_is_yes ("sign_uid.replace_expired_okay", _("Do you want to issue a " "new signature to replace " "the expired one? (y/N) "))) { /* Mark these for later deletion. We don't want to delete them here, just in case the replacement signature doesn't happen for some reason. We only delete these after the replacement is already in place. */ node->flag |= NODFLG_DELSIG; xfree (user); continue; } } if (!node->pkt->pkt.signature->flags.exportable && !local) { /* It's a local sig, and we want to make a exportable sig. */ tty_fprintf (fp, _("Your current signature on \"%s\"\n" "is a local signature.\n"), user); if (quick || cpr_get_answer_is_yes ("sign_uid.local_promote_okay", _("Do you want to promote " "it to a full exportable " "signature? (y/N) "))) { /* Mark these for later deletion. We don't want to delete them here, just in case the replacement signature doesn't happen for some reason. We only delete these after the replacement is already in place. */ node->flag |= NODFLG_DELSIG; xfree (user); continue; } } /* Fixme: see whether there is a revocation in which * case we should allow signing it again. */ if (!node->pkt->pkt.signature->flags.exportable && local) tty_fprintf ( fp, _("\"%s\" was already locally signed by key %s\n"), user, keystr_from_pk (pk)); else tty_fprintf (fp, _("\"%s\" was already signed by key %s\n"), user, keystr_from_pk (pk)); if (opt.expert && !quick && cpr_get_answer_is_yes ("sign_uid.dupe_okay", _("Do you want to sign it " "again anyway? (y/N) "))) { /* Don't delete the old sig here since this is an --expert thing. */ xfree (user); continue; } snprintf (buf, sizeof buf, "%08lX%08lX", (ulong) pk->keyid[0], (ulong) pk->keyid[1]); write_status_text (STATUS_ALREADY_SIGNED, buf); uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */ xfree (user); } } } /* Check whether any uids are left for signing. */ if (!count_uids_with_flag (keyblock, NODFLG_MARK_A)) { tty_fprintf (fp, _("Nothing to sign with key %s\n"), keystr_from_pk (pk)); continue; } /* Ask whether we really should sign these user id(s). */ tty_fprintf (fp, "\n"); show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0); tty_fprintf (fp, "\n"); if (primary_pk->expiredate && !selfsig) { /* Static analyzer note: A claim that PRIMARY_PK might be NULL is not correct because it set from the public key packet which is always the first packet in a keyblock and parsed in the above loop over the keyblock. In case the keyblock has no packets at all and thus the loop was not entered the above count_uids_with_flag would have detected this case. */ u32 now = make_timestamp (); if (primary_pk->expiredate <= now) { tty_fprintf (fp, _("This key has expired!")); if (opt.expert && !quick) { tty_fprintf (fp, " "); if (!cpr_get_answer_is_yes ("sign_uid.expired_okay", _("Are you sure you still " "want to sign it? (y/N) "))) continue; } else { tty_fprintf (fp, _(" Unable to sign.\n")); continue; } } else { tty_fprintf (fp, _("This key is due to expire on %s.\n"), expirestr_from_pk (primary_pk)); if (opt.ask_cert_expire && !quick) { char *answer = cpr_get ("sign_uid.expire", _("Do you want your signature to " "expire at the same time? (Y/n) ")); if (answer_is_yes_no_default (answer, 1)) { /* This fixes the signature timestamp we're going to make as now. This is so the expiration date is exactly correct, and not a few seconds off (due to the time it takes to answer the questions, enter the passphrase, etc). */ timestamp = now; duration = primary_pk->expiredate - now; } cpr_kill_prompt (); xfree (answer); } } } /* Only ask for duration if we haven't already set it to match the expiration of the pk */ if (!duration && !selfsig) { if (opt.ask_cert_expire && !quick) duration = ask_expire_interval (1, opt.def_cert_expire); else duration = parse_expire_string (opt.def_cert_expire); } if (selfsig) ; else { if (opt.batch || !opt.ask_cert_level || quick) class = 0x10 + opt.def_cert_level; else { char *answer; tty_fprintf (fp, _("How carefully have you verified the key you are " "about to sign actually belongs\nto the person " "named above? If you don't know what to " "answer, enter \"0\".\n")); tty_fprintf (fp, "\n"); tty_fprintf (fp, _(" (0) I will not answer.%s\n"), opt.def_cert_level == 0 ? " (default)" : ""); tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"), opt.def_cert_level == 1 ? " (default)" : ""); tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"), opt.def_cert_level == 2 ? " (default)" : ""); tty_fprintf (fp, _(" (3) I have done very careful checking.%s\n"), opt.def_cert_level == 3 ? " (default)" : ""); tty_fprintf (fp, "\n"); while (class == 0) { answer = cpr_get ("sign_uid.class", _("Your selection? " "(enter '?' for more information): ")); if (answer[0] == '\0') class = 0x10 + opt.def_cert_level; /* Default */ else if (ascii_strcasecmp (answer, "0") == 0) class = 0x10; /* Generic */ else if (ascii_strcasecmp (answer, "1") == 0) class = 0x11; /* Persona */ else if (ascii_strcasecmp (answer, "2") == 0) class = 0x12; /* Casual */ else if (ascii_strcasecmp (answer, "3") == 0) class = 0x13; /* Positive */ else tty_fprintf (fp, _("Invalid selection.\n")); xfree (answer); } } if (trust && !quick) trustsig_prompt (&trust_value, &trust_depth, &trust_regexp); } if (!quick) { p = get_user_id_native (ctrl, sk_keyid); tty_fprintf (fp, _("Are you sure that you want to sign this key with your\n" "key \"%s\" (%s)\n"), p, keystr_from_pk (pk)); xfree (p); } if (selfsig) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("This will be a self-signature.\n")); if (local) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("WARNING: the signature will not be marked " "as non-exportable.\n")); } if (nonrevocable) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("WARNING: the signature will not be marked " "as non-revocable.\n")); } } else { if (local) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("The signature will be marked as non-exportable.\n")); } if (nonrevocable) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("The signature will be marked as non-revocable.\n")); } switch (class) { case 0x11: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have not checked this key at all.\n")); break; case 0x12: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have checked this key casually.\n")); break; case 0x13: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have checked this key very carefully.\n")); break; } } tty_fprintf (fp, "\n"); if (opt.batch && opt.answer_yes) ; else if (quick) ; else if (!cpr_get_answer_is_yes ("sign_uid.okay", _("Really sign? (y/N) "))) continue; /* Now we can sign the user ids. */ - reloop: /* (Must use this, because we are modifing the list.) */ + reloop: /* (Must use this, because we are modifying the list.) */ primary_pk = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) primary_pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_MARK_A)) { PACKET *pkt; PKT_signature *sig; struct sign_attrib attrib; log_assert (primary_pk); memset (&attrib, 0, sizeof attrib); attrib.non_exportable = local; attrib.non_revocable = nonrevocable; attrib.trust_depth = trust_depth; attrib.trust_value = trust_value; attrib.trust_regexp = trust_regexp; node->flag &= ~NODFLG_MARK_A; /* We force creation of a v4 signature for local * signatures, otherwise we would not generate the * subpacket with v3 keys and the signature becomes * exportable. */ if (selfsig) rc = make_keysig_packet (ctrl, &sig, primary_pk, node->pkt->pkt.user_id, NULL, pk, 0x13, 0, 0, 0, keygen_add_std_prefs, primary_pk, NULL); else rc = make_keysig_packet (ctrl, &sig, primary_pk, node->pkt->pkt.user_id, NULL, pk, class, 0, timestamp, duration, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); goto leave; } *ret_modified = 1; /* We changed the keyblock. */ update_trust = 1; pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE); goto reloop; } } /* Delete any sigs that got promoted */ for (node = keyblock; node; node = node->next) if (node->flag & NODFLG_DELSIG) delete_kbnode (node); } /* End loop over signators. */ leave: release_sk_list (sk_list); return rc; } /* * Change the passphrase of the primary and all secondary keys. Note * that it is common to use only one passphrase for the primary and * all subkeys. However, this is now (since GnuPG 2.1) all up to the * gpg-agent. Returns 0 on success or an error code. */ static gpg_error_t change_passphrase (ctrl_t ctrl, kbnode_t keyblock) { gpg_error_t err; kbnode_t node; PKT_public_key *pk; int any; u32 keyid[2], subid[2]; char *hexgrip = NULL; char *cache_nonce = NULL; char *passwd_nonce = NULL; node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; public key missing!\n"); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } pk = node->pkt->pkt.public_key; keyid_from_pk (pk, keyid); /* Check whether it is likely that we will be able to change the passphrase for any subkey. */ for (any = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { char *serialno; pk = node->pkt->pkt.public_key; keyid_from_pk (pk, subid); xfree (hexgrip); err = hexkeygrip_from_pk (pk, &hexgrip); if (err) goto leave; err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); if (!err && serialno) ; /* Key on card. */ else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* Maybe stub key. */ else if (!err) any = 1; /* Key is known. */ else log_error ("key %s: error getting keyinfo from agent: %s\n", keystr_with_sub (keyid, subid), gpg_strerror (err)); xfree (serialno); } } err = 0; if (!any) { tty_printf (_("Key has only stub or on-card key items - " "no passphrase to change.\n")); goto leave; } /* Change the passphrase for all keys. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { char *desc; pk = node->pkt->pkt.public_key; keyid_from_pk (pk, subid); xfree (hexgrip); err = hexkeygrip_from_pk (pk, &hexgrip); if (err) goto leave; desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1); err = agent_passwd (ctrl, hexgrip, desc, 0, &cache_nonce, &passwd_nonce); xfree (desc); if (err) log_log ((gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) ? GPGRT_LOG_INFO : GPGRT_LOG_ERROR, _("key %s: error changing passphrase: %s\n"), keystr_with_sub (keyid, subid), gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) break; } } leave: xfree (hexgrip); xfree (cache_nonce); xfree (passwd_nonce); return err; } /* Fix various problems in the keyblock. Returns true if the keyblock was changed. Note that a pointer to the keyblock must be given and the function may change it (i.e. replacing the first node). */ static int fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp) { int changed = 0; if (collapse_uids (keyblockp)) changed++; if (check_all_keysigs (ctrl, *keyblockp, 0, 1)) changed++; reorder_keyblock (*keyblockp); /* If we modified the keyblock, make sure the flags are right. */ if (changed) merge_keys_and_selfsig (ctrl, *keyblockp); return changed; } static int parse_sign_type (const char *str, int *localsig, int *nonrevokesig, int *trustsig) { const char *p = str; while (*p) { if (ascii_strncasecmp (p, "l", 1) == 0) { *localsig = 1; p++; } else if (ascii_strncasecmp (p, "nr", 2) == 0) { *nonrevokesig = 1; p += 2; } else if (ascii_strncasecmp (p, "t", 1) == 0) { *trustsig = 1; p++; } else return 0; } return 1; } /* * Menu driven key editor. If seckey_check is true, then a secret key * that matches username will be looked for. If it is false, not all * commands will be available. * * Note: to keep track of certain selections we use node->mark MARKBIT_xxxx. */ /* Need an SK for this command */ #define KEYEDIT_NEED_SK 1 /* Cannot be viewing the SK for this command */ #define KEYEDIT_NOT_SK 2 /* Must be viewing the SK for this command */ #define KEYEDIT_ONLY_SK 4 /* Match the tail of the string */ #define KEYEDIT_TAIL_MATCH 8 enum cmdids { cmdNONE = 0, cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN, cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG, cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY, cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF, cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN, #ifndef NO_TRUST_MODELS cmdENABLEKEY, cmdDISABLEKEY, #endif /*!NO_TRUST_MODELS*/ cmdSHOWPREF, cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD, cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP }; static struct { const char *name; enum cmdids id; int flags; const char *desc; } cmds[] = { { "quit", cmdQUIT, 0, N_("quit this menu")}, { "q", cmdQUIT, 0, NULL}, { "save", cmdSAVE, 0, N_("save and quit")}, { "help", cmdHELP, 0, N_("show this help")}, { "?", cmdHELP, 0, NULL}, { "fpr", cmdFPR, 0, N_("show key fingerprint")}, { "grip", cmdGRIP, 0, N_("show the keygrip")}, { "list", cmdLIST, 0, N_("list key and user IDs")}, { "l", cmdLIST, 0, NULL}, { "uid", cmdSELUID, 0, N_("select user ID N")}, { "key", cmdSELKEY, 0, N_("select subkey N")}, { "check", cmdCHECK, 0, N_("check signatures")}, { "c", cmdCHECK, 0, NULL}, { "change-usage", cmdCHANGEUSAGE, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, { "cross-certify", cmdBACKSIGN, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, { "backsign", cmdBACKSIGN, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, { "sign", cmdSIGN, KEYEDIT_NOT_SK | KEYEDIT_TAIL_MATCH, N_("sign selected user IDs [* see below for related commands]")}, { "s", cmdSIGN, KEYEDIT_NOT_SK, NULL}, /* "lsign" and friends will never match since "sign" comes first and it is a tail match. They are just here so they show up in the help menu. */ { "lsign", cmdNOP, 0, N_("sign selected user IDs locally")}, { "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")}, { "nrsign", cmdNOP, 0, N_("sign selected user IDs with a non-revocable signature")}, { "debug", cmdDEBUG, 0, NULL}, { "adduid", cmdADDUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a user ID")}, { "addphoto", cmdADDPHOTO, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a photo ID")}, { "deluid", cmdDELUID, KEYEDIT_NOT_SK, N_("delete selected user IDs")}, /* delphoto is really deluid in disguise */ { "delphoto", cmdDELUID, KEYEDIT_NOT_SK, NULL}, { "addkey", cmdADDKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a subkey")}, #ifdef ENABLE_CARD_SUPPORT { "addcardkey", cmdADDCARDKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a key to a smartcard")}, { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_ONLY_SK, N_("move a key to a smartcard")}, { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_ONLY_SK, N_("move a backup key to a smartcard")}, #endif /*ENABLE_CARD_SUPPORT */ { "delkey", cmdDELKEY, KEYEDIT_NOT_SK, N_("delete selected subkeys")}, { "addrevoker", cmdADDREVOKER, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("add a revocation key")}, { "delsig", cmdDELSIG, KEYEDIT_NOT_SK, N_("delete signatures from the selected user IDs")}, { "expire", cmdEXPIRE, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("change the expiration date for the key or selected subkeys")}, { "primary", cmdPRIMARY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("flag the selected user ID as primary")}, { "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */ { "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, { "pref", cmdPREF, KEYEDIT_NOT_SK, N_("list preferences (expert)")}, { "showpref", cmdSHOWPREF, KEYEDIT_NOT_SK, N_("list preferences (verbose)")}, { "setpref", cmdSETPREF, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("set preference list for the selected user IDs")}, { "updpref", cmdSETPREF, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, { "keyserver", cmdPREFKS, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("set the preferred keyserver URL for the selected user IDs")}, { "notation", cmdNOTATION, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("set a notation for the selected user IDs")}, { "passwd", cmdPASSWD, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("change the passphrase")}, { "password", cmdPASSWD, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, #ifndef NO_TRUST_MODELS { "trust", cmdTRUST, KEYEDIT_NOT_SK, N_("change the ownertrust")}, #endif /*!NO_TRUST_MODELS*/ { "revsig", cmdREVSIG, KEYEDIT_NOT_SK, N_("revoke signatures on the selected user IDs")}, { "revuid", cmdREVUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("revoke selected user IDs")}, { "revphoto", cmdREVUID, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, NULL}, { "revkey", cmdREVKEY, KEYEDIT_NOT_SK | KEYEDIT_NEED_SK, N_("revoke key or selected subkeys")}, #ifndef NO_TRUST_MODELS { "enable", cmdENABLEKEY, KEYEDIT_NOT_SK, N_("enable key")}, { "disable", cmdDISABLEKEY, KEYEDIT_NOT_SK, N_("disable key")}, #endif /*!NO_TRUST_MODELS*/ { "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")}, { "clean", cmdCLEAN, KEYEDIT_NOT_SK, N_("compact unusable user IDs and remove unusable signatures from key")}, { "minimize", cmdMINIMIZE, KEYEDIT_NOT_SK, N_("compact unusable user IDs and remove all signatures from key")}, { NULL, cmdNONE, 0, NULL} }; #ifdef HAVE_LIBREADLINE /* These two functions are used by readline for command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen (text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text */ if (cmds[list_index++].desc && strncmp (name, text, len) == 0) return strdup (name); } return NULL; } static char ** keyedit_completion (const char *text, int start, int end) { /* If we are at the start of a line, we try and command-complete. If not, just do nothing for now. */ (void) end; if (start == 0) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /* HAVE_LIBREADLINE */ /* Main function of the menu driven key editor. */ void keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, strlist_t commands, int quiet, int seckey_check) { enum cmdids cmd = 0; gpg_error_t err = 0; KBNODE keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; int have_seckey = 0; char *answer = NULL; int redisplay = 1; int modified = 0; int sec_shadowing = 0; int run_subkey_warnings = 0; int have_commands = !!commands; if (opt.command_fd != -1) ; else if (opt.batch && !have_commands) { log_error (_("can't do this in batch mode\n")); goto leave; } #ifdef HAVE_W32_SYSTEM /* Due to Windows peculiarities we need to make sure that the trustdb stale check is done before we open another file (i.e. by searching for a key). In theory we could make sure that the files are closed after use but the open/close caches inhibits that and flushing the cache right before the stale check is not easy to implement. Thus we take the easy way out and run the stale check as early as possible. Note, that for non- W32 platforms it is run indirectly trough a call to get_validity (). */ check_trustdb_stale (ctrl); #endif /* Get the public key */ err = get_pubkey_byname (ctrl, NULL, NULL, username, &keyblock, &kdbhd, 1, 1); if (err) { log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); goto leave; } if (fix_keyblock (ctrl, &keyblock)) modified++; /* See whether we have a matching secret key. */ if (seckey_check) { have_seckey = !agent_probe_any_secret_key (ctrl, keyblock); if (have_seckey && !quiet) tty_printf (_("Secret key is available.\n")); } /* Main command loop. */ for (;;) { int i, arg_number, photo; const char *arg_string = ""; char *p; PKT_public_key *pk = keyblock->pkt->pkt.public_key; tty_printf ("\n"); if (redisplay && !quiet) { /* Show using flags: with_revoker, with_subkeys. */ show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0); tty_printf ("\n"); redisplay = 0; } if (run_subkey_warnings) { run_subkey_warnings = 0; if (!count_selected_keys (keyblock)) subkey_expire_warning (keyblock); } do { xfree (answer); if (have_commands) { if (commands) { answer = xstrdup (commands->d); commands = commands->next; } else if (opt.batch) { answer = xstrdup ("quit"); } else have_commands = 0; } if (!have_commands) { #ifdef HAVE_LIBREADLINE tty_enable_completion (keyedit_completion); #endif answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> "); cpr_kill_prompt (); tty_disable_completion (); } trim_spaces (answer); } while (*answer == '#'); arg_number = 0; /* Here is the init which egcc complains about. */ photo = 0; /* Same here. */ if (!*answer) cmd = cmdLIST; else if (*answer == CONTROL_D) cmd = cmdQUIT; else if (digitp (answer)) { cmd = cmdSELUID; arg_number = atoi (answer); } else { if ((p = strchr (answer, ' '))) { *p++ = 0; trim_spaces (answer); trim_spaces (p); arg_number = atoi (p); arg_string = p; } for (i = 0; cmds[i].name; i++) { if (cmds[i].flags & KEYEDIT_TAIL_MATCH) { size_t l = strlen (cmds[i].name); size_t a = strlen (answer); if (a >= l) { if (!ascii_strcasecmp (&answer[a - l], cmds[i].name)) { answer[a - l] = '\0'; break; } } } else if (!ascii_strcasecmp (answer, cmds[i].name)) break; } if ((cmds[i].flags & KEYEDIT_NEED_SK) && !have_seckey) { tty_printf (_("Need the secret key to do this.\n")); cmd = cmdNOP; } else cmd = cmds[i].id; } /* Dispatch the command. */ switch (cmd) { case cmdHELP: for (i = 0; cmds[i].name; i++) { if ((cmds[i].flags & KEYEDIT_NEED_SK) && !have_seckey) ; /* Skip those item if we do not have the secret key. */ else if (cmds[i].desc) tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc)); } tty_printf ("\n"); tty_printf (_("* The 'sign' command may be prefixed with an 'l' for local " "signatures (lsign),\n" " a 't' for trust signatures (tsign), an 'nr' for " "non-revocable signatures\n" " (nrsign), or any combination thereof (ltsign, " "tnrsign, etc.).\n")); break; case cmdLIST: redisplay = 1; break; case cmdFPR: show_key_and_fingerprint (ctrl, keyblock, (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1)))); break; case cmdGRIP: show_key_and_grip (keyblock); break; case cmdSELUID: if (strlen (arg_string) == NAMEHASH_LEN * 2) redisplay = menu_select_uid_namehash (keyblock, arg_string); else { if (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1))) arg_number = -1; /* Select all. */ redisplay = menu_select_uid (keyblock, arg_number); } break; case cmdSELKEY: { if (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1))) arg_number = -1; /* Select all. */ if (menu_select_key (keyblock, arg_number, p)) redisplay = 1; } break; case cmdCHECK: if (check_all_keysigs (ctrl, keyblock, count_selected_uids (keyblock), !strcmp (arg_string, "selfsig"))) modified = 1; break; case cmdSIGN: { int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0; if (pk->flags.revoked) { tty_printf (_("Key is revoked.")); if (opt.expert) { tty_printf (" "); if (!cpr_get_answer_is_yes ("keyedit.sign_revoked.okay", _("Are you sure you still want to sign it? (y/N) "))) break; } else { tty_printf (_(" Unable to sign.\n")); break; } } if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock)) { int result; if (opt.only_sign_text_ids) result = cpr_get_answer_is_yes ("keyedit.sign_all.okay", _("Really sign all user IDs? (y/N) ")); else result = cpr_get_answer_is_yes ("keyedit.sign_all.okay", _("Really sign all text user IDs? (y/N) ")); if (! result) { if (opt.interactive) interactive = 1; else { tty_printf (_("Hint: Select the user IDs to sign\n")); have_commands = 0; break; } } } /* What sort of signing are we doing? */ if (!parse_sign_type (answer, &localsig, &nonrevokesig, &trustsig)) { tty_printf (_("Unknown signature type '%s'\n"), answer); break; } sign_uids (ctrl, NULL, keyblock, locusr, &modified, localsig, nonrevokesig, trustsig, interactive, 0); } break; case cmdDEBUG: dump_kbnode (keyblock); break; case cmdTOGGLE: /* The toggle command is a leftover from old gpg versions where we worked with a secret and a public keyring. It is not necessary anymore but we keep this command for the sake of scripts using it. */ redisplay = 1; break; case cmdADDPHOTO: if (RFC2440) { tty_printf (_("This command is not allowed while in %s mode.\n"), compliance_option_string ()); break; } photo = 1; /* fall through */ case cmdADDUID: if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL)) { update_trust = 1; redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdDELUID: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (real_uids_left (keyblock) < 1) tty_printf (_("You can't delete the last user ID!\n")); else if (cpr_get_answer_is_yes ("keyedit.remove.uid.okay", n1 > 1 ? _("Really remove all selected user IDs? (y/N) ") : _("Really remove this user ID? (y/N) "))) { menu_deluid (keyblock); redisplay = 1; modified = 1; } } break; case cmdDELSIG: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (menu_delsig (ctrl, keyblock)) { /* No redisplay here, because it may scroll away some * of the status output of this command. */ modified = 1; } } break; case cmdADDKEY: if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; #ifdef ENABLE_CARD_SUPPORT case cmdADDCARDKEY: if (!card_generate_subkey (ctrl, keyblock)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdKEYTOCARD: { KBNODE node = NULL; switch (count_selected_keys (keyblock)) { case 0: if (cpr_get_answer_is_yes ("keyedit.keytocard.use_primary", /* TRANSLATORS: Please take care: This is about moving the key and not about removing it. */ _("Really move the primary key? (y/N) "))) node = keyblock; break; case 1: for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && node->flag & NODFLG_SELKEY) break; } break; default: tty_printf (_("You must select exactly one key.\n")); break; } if (node) { PKT_public_key *xxpk = node->pkt->pkt.public_key; if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0)) { redisplay = 1; sec_shadowing = 1; } } } break; case cmdBKUPTOCARD: { /* Ask for a filename, check whether this is really a backup key as generated by the card generation, parse that key and store it on card. */ KBNODE node; char *fname; PACKET *pkt; IOBUF a; struct parse_packet_ctx_s parsectx; if (!*arg_string) { tty_printf (_("Command expects a filename argument\n")); break; } if (*arg_string == DIRSEP_C) fname = xstrdup (arg_string); else if (*arg_string == '~') fname = make_filename (arg_string, NULL); else fname = make_filename (gnupg_homedir (), arg_string, NULL); /* Open that file. */ a = iobuf_open (fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if (!a) { tty_printf (_("Can't open '%s': %s\n"), fname, strerror (errno)); xfree (fname); break; } /* Parse and check that file. */ pkt = xmalloc (sizeof *pkt); init_packet (pkt); init_parse_packet (&parsectx, a); err = parse_packet (&parsectx, pkt); deinit_parse_packet (&parsectx); iobuf_close (a); iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname); if (!err && pkt->pkttype != PKT_SECRET_KEY && pkt->pkttype != PKT_SECRET_SUBKEY) err = GPG_ERR_NO_SECKEY; if (err) { tty_printf (_("Error reading backup key from '%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); free_packet (pkt, NULL); xfree (pkt); break; } xfree (fname); node = new_kbnode (pkt); /* Transfer it to gpg-agent which handles secret keys. */ err = transfer_secret_keys (ctrl, NULL, node, 1, 1); /* Treat the pkt as a public key. */ pkt->pkttype = PKT_PUBLIC_KEY; /* Ask gpg-agent to store the secret key to card. */ if (card_store_subkey (node, 0)) { redisplay = 1; sec_shadowing = 1; } release_kbnode (node); } break; #endif /* ENABLE_CARD_SUPPORT */ case cmdDELKEY: { int n1; if (!(n1 = count_selected_keys (keyblock))) { tty_printf (_("You must select at least one key.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "key"); } else if (!cpr_get_answer_is_yes ("keyedit.remove.subkey.okay", n1 > 1 ? _("Do you really want to delete the " "selected keys? (y/N) ") : _("Do you really want to delete this key? (y/N) "))) ; else { menu_delkey (keyblock); redisplay = 1; modified = 1; } } break; case cmdADDREVOKER: { int sensitive = 0; if (ascii_strcasecmp (arg_string, "sensitive") == 0) sensitive = 1; if (menu_addrevoker (ctrl, keyblock, sensitive)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } } break; case cmdREVUID: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (cpr_get_answer_is_yes ("keyedit.revoke.uid.okay", n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ") : _("Really revoke this user ID? (y/N) "))) { if (menu_revuid (ctrl, keyblock)) { modified = 1; redisplay = 1; } } } break; case cmdREVKEY: { int n1; if (!(n1 = count_selected_keys (keyblock))) { if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay", _("Do you really want to revoke" " the entire key? (y/N) "))) { if (menu_revkey (ctrl, keyblock)) modified = 1; redisplay = 1; } } else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay", n1 > 1 ? _("Do you really want to revoke" " the selected subkeys? (y/N) ") : _("Do you really want to revoke" " this subkey? (y/N) "))) { if (menu_revsubkey (ctrl, keyblock)) modified = 1; redisplay = 1; } if (modified) merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdEXPIRE: if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE) { merge_keys_and_selfsig (ctrl, keyblock); run_subkey_warnings = 1; modified = 1; redisplay = 1; } break; case cmdCHANGEUSAGE: if (menu_changeusage (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdBACKSIGN: if (menu_backsign (ctrl, keyblock)) { modified = 1; redisplay = 1; } break; case cmdPRIMARY: if (menu_set_primary_uid (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdPASSWD: change_passphrase (ctrl, keyblock); break; #ifndef NO_TRUST_MODELS case cmdTRUST: if (opt.trust_model == TM_EXTERNAL) { tty_printf (_("Owner trust may not be set while " "using a user provided trust database\n")); break; } show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0); tty_printf ("\n"); if (edit_ownertrust (ctrl, find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt. public_key, 1)) { redisplay = 1; /* No real need to set update_trust here as edit_ownertrust() calls revalidation_mark() anyway. */ update_trust = 1; } break; #endif /*!NO_TRUST_MODELS*/ case cmdPREF: { int count = count_selected_uids (keyblock); log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key, count ? NODFLG_SELUID : 0, 1); } break; case cmdSHOWPREF: { int count = count_selected_uids (keyblock); log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key, count ? NODFLG_SELUID : 0, 2); } break; case cmdSETPREF: { PKT_user_id *tempuid; keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0); tempuid = keygen_get_std_prefs (); tty_printf (_("Set preference list to:\n")); show_prefs (tempuid, NULL, 1); free_user_id (tempuid); if (cpr_get_answer_is_yes ("keyedit.setpref.okay", count_selected_uids (keyblock) ? _("Really update the preferences" " for the selected user IDs? (y/N) ") : _("Really update the preferences? (y/N) "))) { if (menu_set_preferences (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } } } break; case cmdPREFKS: if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdNOTATION: if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdNOP: break; case cmdREVSIG: if (menu_revsig (ctrl, keyblock)) { redisplay = 1; modified = 1; } break; #ifndef NO_TRUST_MODELS case cmdENABLEKEY: case cmdDISABLEKEY: if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY)) { redisplay = 1; modified = 1; } break; #endif /*!NO_TRUST_MODELS*/ case cmdSHOWPHOTO: menu_showphoto (ctrl, keyblock); break; case cmdCLEAN: if (menu_clean (ctrl, keyblock, 0)) redisplay = modified = 1; break; case cmdMINIMIZE: if (menu_clean (ctrl, keyblock, 1)) redisplay = modified = 1; break; case cmdQUIT: if (have_commands) goto leave; if (!modified && !sec_shadowing) goto leave; if (!cpr_get_answer_is_yes ("keyedit.save.okay", _("Save changes? (y/N) "))) { if (cpr_enabled () || cpr_get_answer_is_yes ("keyedit.cancel.okay", _("Quit without saving? (y/N) "))) goto leave; break; } /* fall through */ case cmdSAVE: if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); break; } } if (sec_shadowing) { err = agent_scd_learn (NULL, 1); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); break; } } if (!modified && !sec_shadowing) tty_printf (_("Key not changed so no update needed.\n")); if (update_trust) { revalidation_mark (ctrl); update_trust = 0; } goto leave; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } } /* End of the main command loop. */ leave: release_kbnode (keyblock); keydb_release (kdbhd); xfree (answer); } /* Change the passphrase of the secret key identified by USERNAME. */ void keyedit_passwd (ctrl_t ctrl, const char *username) { gpg_error_t err; PKT_public_key *pk; kbnode_t keyblock = NULL; pk = xtrycalloc (1, sizeof *pk); if (!pk) { err = gpg_error_from_syserror (); goto leave; } err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock); if (err) goto leave; err = change_passphrase (ctrl, keyblock); leave: release_kbnode (keyblock); free_public_key (pk); if (err) { log_info ("error changing the passphrase for '%s': %s\n", username, gpg_strerror (err)); write_status_error ("keyedit.passwd", err); } else write_status_text (STATUS_SUCCESS, "keyedit.passwd"); } /* Helper for quick commands to find the keyblock for USERNAME. * Returns on success the key database handle at R_KDBHD and the * keyblock at R_KEYBLOCK. */ static gpg_error_t quick_find_keyblock (ctrl_t ctrl, const char *username, KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; KEYDB_SEARCH_DESC desc; kbnode_t node; *r_kdbhd = NULL; *r_keyblock = NULL; /* Search the key; we don't want the whole getkey stuff here. */ kdbhd = keydb_new (); if (!kdbhd) { /* Note that keydb_new has already used log_error. */ err = gpg_error_from_syserror (); goto leave; } err = classify_user_id (username, &desc, 1); if (!err) err = keydb_search (kdbhd, &desc, 1, NULL); if (!err) { err = keydb_get_keyblock (kdbhd, &keyblock); if (err) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); goto leave; } /* Now with the keyblock retrieved, search again to detect an ambiguous specification. We need to save the found state so that we can do an update later. */ keydb_push_found_state (kdbhd); err = keydb_search (kdbhd, &desc, 1, NULL); if (!err) err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; keydb_pop_found_state (kdbhd); if (!err) { /* We require the secret primary key to set the primary UID. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); log_assert (node); err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = gpg_error (GPG_ERR_NO_PUBKEY); if (err) { log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); goto leave; } fix_keyblock (ctrl, &keyblock); merge_keys_and_selfsig (ctrl, keyblock); *r_keyblock = keyblock; keyblock = NULL; *r_kdbhd = kdbhd; kdbhd = NULL; leave: release_kbnode (keyblock); keydb_release (kdbhd); return err; } /* Unattended adding of a new keyid. USERNAME specifies the key. NEWUID is the new user id to add to the key. */ void keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; char *uidstring = NULL; uidstring = xstrdup (newuid); trim_spaces (uidstring); if (!*uidstring) { log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID)); goto leave; } #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* Search the key; we don't want the whole getkey stuff here. */ err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); if (err) goto leave; if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring)) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } if (update_trust) revalidation_mark (ctrl); } leave: xfree (uidstring); release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended revocation of a keyid. USERNAME specifies the key. UIDTOREV is the user id revoke from the key. */ void keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; kbnode_t node; int modified = 0; size_t revlen; size_t valid_uids; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* Search the key; we don't want the whole getkey stuff here. */ err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); if (err) goto leave; /* Too make sure that we do not revoke the last valid UID, we first count how many valid UIDs there are. */ valid_uids = 0; for (node = keyblock; node; node = node->next) valid_uids += (node->pkt->pkttype == PKT_USER_ID && !node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired); /* Find the right UID. */ revlen = strlen (uidtorev); for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && revlen == node->pkt->pkt.user_id->len && !memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen)) { struct revocation_reason_info *reason; /* Make sure that we do not revoke the last valid UID. */ if (valid_uids == 1 && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired) { log_error (_("cannot revoke the last valid user ID.\n")); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } reason = get_default_uid_revocation_reason (); err = core_revuid (ctrl, keyblock, node, reason, &modified); release_revocation_reason_info (reason); if (err) goto leave; err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } revalidation_mark (ctrl); goto leave; } } err = gpg_error (GPG_ERR_NO_USER_ID); leave: if (err) log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err)); release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended setting of the primary uid. USERNAME specifies the key. PRIMARYUID is the user id which shall be primary. */ void keyedit_quick_set_primary (ctrl_t ctrl, const char *username, const char *primaryuid) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; kbnode_t node; size_t primaryuidlen; int any; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock); if (err) goto leave; /* Find and mark the UID - we mark only the first valid one. */ primaryuidlen = strlen (primaryuid); any = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && !any && !node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired && primaryuidlen == node->pkt->pkt.user_id->len && !memcmp (node->pkt->pkt.user_id->name, primaryuid, primaryuidlen)) { node->flag |= NODFLG_SELUID; any = 1; } else node->flag &= ~NODFLG_SELUID; } if (!any) err = gpg_error (GPG_ERR_NO_USER_ID); else if (menu_set_primary_uid (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } revalidation_mark (ctrl); } else err = gpg_error (GPG_ERR_GENERAL); if (err) log_error (_("setting the primary user ID failed: %s\n"), gpg_strerror (err)); leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Find a keyblock by fingerprint because only this uniquely * identifies a key and may thus be used to select a key for * unattended subkey creation os key signing. */ static gpg_error_t find_by_primary_fpr (ctrl_t ctrl, const char *fpr, kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd) { gpg_error_t err; kbnode_t keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; KEYDB_SEARCH_DESC desc; byte fprbin[MAX_FINGERPRINT_LEN]; size_t fprlen; *r_keyblock = NULL; *r_kdbhd = NULL; if (classify_user_id (fpr, &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_FPR16 || desc.mode == KEYDB_SEARCH_MODE_FPR20)) { log_error (_("\"%s\" is not a fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } err = get_pubkey_byname (ctrl, NULL, NULL, fpr, &keyblock, &kdbhd, 1, 1); if (err) { log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err)); goto leave; } /* Check that the primary fingerprint has been given. */ fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen); if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR16 && !memcmp (fprbin, desc.u.fpr, 16)) ; else if (fprlen == 16 && desc.mode == KEYDB_SEARCH_MODE_FPR && !memcmp (fprbin, desc.u.fpr, 16) && !desc.u.fpr[16] && !desc.u.fpr[17] && !desc.u.fpr[18] && !desc.u.fpr[19]) ; else if (fprlen == 20 && (desc.mode == KEYDB_SEARCH_MODE_FPR20 || desc.mode == KEYDB_SEARCH_MODE_FPR) && !memcmp (fprbin, desc.u.fpr, 20)) ; else { log_error (_("\"%s\" is not the primary fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } *r_keyblock = keyblock; keyblock = NULL; *r_kdbhd = kdbhd; kdbhd = NULL; err = 0; leave: release_kbnode (keyblock); keydb_release (kdbhd); return err; } /* Unattended key signing function. If the key specifified by FPR is available and FPR is the primary fingerprint all user ids of the key are signed using the default signing key. If UIDS is an empty list all usable UIDs are signed, if it is not empty, only those user ids matching one of the entries of the list are signed. With LOCAL being true the signatures are marked as non-exportable. */ void keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids, strlist_t locusr, int local) { gpg_error_t err; kbnode_t keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; int modified = 0; PKT_public_key *pk; kbnode_t node; strlist_t sl; int any; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a key and may thus be used to select a key for unattended key signing. */ if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; /* Give some info in verbose. */ if (opt.verbose) { show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 1/*with_revoker*/, 1/*with_fingerprint*/, 0, 0, 1); es_fflush (es_stdout); } pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n")); goto leave; } /* Set the flags according to the UIDS list. Fixme: We may want to use classify_user_id along with dedicated compare functions so that we match the same way as in the key lookup. */ any = 0; menu_select_uid (keyblock, 0); /* Better clear the flags first. */ for (sl=uids; sl; sl = sl->next) { const char *name = sl->d; int count = 0; sl->flags &= ~(1|2); /* Clear flags used for error reporting. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; if (uid->attrib_data) ; else if (*name == '=' && strlen (name+1) == uid->len && !memcmp (uid->name, name + 1, uid->len)) { /* Exact match - we don't do a check for ambiguity * in this case. */ node->flag |= NODFLG_SELUID; if (any != -1) { sl->flags |= 1; /* Report as found. */ any = 1; } } else if (ascii_memistr (uid->name, uid->len, *name == '*'? name+1:name)) { node->flag |= NODFLG_SELUID; if (any != -1) { sl->flags |= 1; /* Report as found. */ any = 1; } count++; } } } if (count > 1) { any = -1; /* Force failure at end. */ sl->flags |= 2; /* Report as ambiguous. */ } } /* Check whether all given user ids were found. */ for (sl=uids; sl; sl = sl->next) if (!(sl->flags & 1)) any = -1; /* That user id was not found. */ /* Print an error if there was a problem with the user ids. */ if (uids && any < 1) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); es_fflush (es_stdout); for (sl=uids; sl; sl = sl->next) { if ((sl->flags & 2)) log_info (_("Invalid user ID '%s': %s\n"), sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME)); else if (!(sl->flags & 1)) log_info (_("Invalid user ID '%s': %s\n"), sl->d, gpg_strerror (GPG_ERR_NOT_FOUND)); } log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n")); goto leave; } /* Sign. */ sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1); es_fflush (es_stdout); if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } } else log_info (_("Key not changed so no update needed.\n")); if (update_trust) revalidation_mark (ctrl); leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended subkey creation function. * */ void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, const char *usagestr, const char *expirestr) { gpg_error_t err; kbnode_t keyblock; KEYDB_HANDLE kdbhd; int modified = 0; PKT_public_key *pk; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a * key and may thus be used to select a key for unattended subkey * creation. */ if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), "\n"); goto leave; } /* Create the subkey. Note that the called function already prints * an error message. */ if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr)) modified = 1; es_fflush (es_stdout); /* Store. */ if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } } else log_info (_("Key not changed so no update needed.\n")); leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended expiration setting function for the main key. * */ void keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr) { gpg_error_t err; kbnode_t keyblock; KEYDB_HANDLE kdbhd; int modified = 0; PKT_public_key *pk; u32 expire; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a * key and may thus be used to select a key for unattended * expiration setting. */ err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd); if (err) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), "\n"); err = gpg_error (GPG_ERR_CERT_REVOKED); goto leave; } expire = parse_expire_string (expirestr); if (expire == (u32)-1 ) { log_error (_("'%s' is not a valid expiration time\n"), expirestr); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (expire) expire += make_timestamp (); /* Set the new expiration date. */ err = menu_expire (ctrl, keyblock, 1, expire); if (gpg_err_code (err) == GPG_ERR_TRUE) modified = 1; else if (err) goto leave; es_fflush (es_stdout); /* Store. */ if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } if (update_trust) revalidation_mark (ctrl); } else log_info (_("Key not changed so no update needed.\n")); leave: release_kbnode (keyblock); keydb_release (kdbhd); if (err) write_status_error ("set_expire", err); } static void tty_print_notations (int indent, PKT_signature * sig) { int first = 1; struct notation *notation, *nd; if (indent < 0) { first = 0; indent = -indent; } notation = sig_to_notation (sig); for (nd = notation; nd; nd = nd->next) { if (!first) tty_printf ("%*s", indent, ""); else first = 0; tty_print_utf8_string (nd->name, strlen (nd->name)); tty_printf ("="); tty_print_utf8_string (nd->value, strlen (nd->value)); tty_printf ("\n"); } free_notation (notation); } /* * Show preferences of a public keyblock. */ static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose) { const prefitem_t fake = { 0, 0 }; const prefitem_t *prefs; int i; if (!uid) return; if (uid->prefs) prefs = uid->prefs; else if (verbose) prefs = &fake; else return; if (verbose) { int any, des_seen = 0, sha1_seen = 0, uncomp_seen = 0; tty_printf (" "); tty_printf (_("Cipher: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_SYM) { if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (!openpgp_cipher_test_algo (prefs[i].value) && prefs[i].value < 100) tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value)); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == CIPHER_ALGO_3DES) des_seen = 1; } } if (!des_seen) { if (any) tty_printf (", "); tty_printf ("%s", openpgp_cipher_algo_name (CIPHER_ALGO_3DES)); } tty_printf ("\n "); tty_printf (_("Digest: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_HASH) { if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100) tty_printf ("%s", gcry_md_algo_name (prefs[i].value)); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == DIGEST_ALGO_SHA1) sha1_seen = 1; } } if (!sha1_seen) { if (any) tty_printf (", "); tty_printf ("%s", gcry_md_algo_name (DIGEST_ALGO_SHA1)); } tty_printf ("\n "); tty_printf (_("Compression: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_ZIP) { const char *s = compress_algo_to_string (prefs[i].value); if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (s && prefs[i].value < 100) tty_printf ("%s", s); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == COMPRESS_ALGO_NONE) uncomp_seen = 1; } } if (!uncomp_seen) { if (any) tty_printf (", "); else { tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_ZIP)); tty_printf (", "); } tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_NONE)); } if (uid->flags.mdc || !uid->flags.ks_modify) { tty_printf ("\n "); tty_printf (_("Features: ")); any = 0; if (uid->flags.mdc) { tty_printf ("MDC"); any = 1; } if (!uid->flags.ks_modify) { if (any) tty_printf (", "); tty_printf (_("Keyserver no-modify")); } } tty_printf ("\n"); if (selfsig) { const byte *pref_ks; size_t pref_ks_len; pref_ks = parse_sig_subpkt (selfsig->hashed, SIGSUBPKT_PREF_KS, &pref_ks_len); if (pref_ks && pref_ks_len) { tty_printf (" "); tty_printf (_("Preferred keyserver: ")); tty_print_utf8_string (pref_ks, pref_ks_len); tty_printf ("\n"); } if (selfsig->flags.notation) { tty_printf (" "); tty_printf (_("Notations: ")); tty_print_notations (5 + strlen (_("Notations: ")), selfsig); } } } else { tty_printf (" "); for (i = 0; prefs[i].type; i++) { tty_printf (" %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' : prefs[i].type == PREFTYPE_HASH ? 'H' : prefs[i].type == PREFTYPE_ZIP ? 'Z' : '?', prefs[i].value); } if (uid->flags.mdc) tty_printf (" [mdc]"); if (!uid->flags.ks_modify) tty_printf (" [no-ks-modify]"); tty_printf ("\n"); } } /* This is the version of show_key_with_all_names used when opt.with_colons is used. It prints all available data in a easy to parse format and does not translate utf8 */ static void show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock) { KBNODE node; int i, j, ulti_hack = 0; byte pk_version = 0; PKT_public_key *primary = NULL; int have_seckey; if (!fp) fp = es_stdout; /* the keys */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) { PKT_public_key *pk = node->pkt->pkt.public_key; u32 keyid[2]; if (node->pkt->pkttype == PKT_PUBLIC_KEY) { pk_version = pk->version; primary = pk; } keyid_from_pk (pk, keyid); have_seckey = !agent_probe_secret_key (ctrl, pk); if (node->pkt->pkttype == PKT_PUBLIC_KEY) es_fputs (have_seckey? "sec:" : "pub:", fp); else es_fputs (have_seckey? "ssb:" : "sub:", fp); if (!pk->flags.valid) es_putc ('i', fp); else if (pk->flags.revoked) es_putc ('r', fp); else if (pk->has_expired) es_putc ('e', fp); else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks)) { int trust = get_validity_info (ctrl, keyblock, pk, NULL); if (trust == 'u') ulti_hack = 1; es_putc (trust, fp); } es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::", nbits_from_pk (pk), pk->pubkey_algo, (ulong) keyid[0], (ulong) keyid[1], (ulong) pk->timestamp, (ulong) pk->expiredate); if (node->pkt->pkttype == PKT_PUBLIC_KEY && !(opt.fast_list_mode || opt.no_expensive_trust_checks)) es_putc (get_ownertrust_info (ctrl, pk, 0), fp); es_putc (':', fp); es_putc (':', fp); es_putc (':', fp); /* Print capabilities. */ if ((pk->pubkey_usage & PUBKEY_USAGE_ENC)) es_putc ('e', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_SIG)) es_putc ('s', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_CERT)) es_putc ('c', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH)) es_putc ('a', fp); es_putc ('\n', fp); print_fingerprint (ctrl, fp, pk, 0); print_revokers (fp, pk); } } /* the user ids */ i = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; if (uid->attrib_data) es_fputs ("uat:", fp); else es_fputs ("uid:", fp); if (uid->flags.revoked) es_fputs ("r::::::::", fp); else if (uid->flags.expired) es_fputs ("e::::::::", fp); else if (opt.fast_list_mode || opt.no_expensive_trust_checks) es_fputs ("::::::::", fp); else { int uid_validity; if (primary && !ulti_hack) uid_validity = get_validity_info (ctrl, keyblock, primary, uid); else uid_validity = 'u'; es_fprintf (fp, "%c::::::::", uid_validity); } if (uid->attrib_data) es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len); else es_write_sanitized (fp, uid->name, uid->len, ":", NULL); es_putc (':', fp); /* signature class */ es_putc (':', fp); /* capabilities */ es_putc (':', fp); /* preferences */ if (pk_version > 3 || uid->selfsigversion > 3) { const prefitem_t *prefs = uid->prefs; for (j = 0; prefs && prefs[j].type; j++) { if (j) es_putc (' ', fp); es_fprintf (fp, "%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' : prefs[j].type == PREFTYPE_HASH ? 'H' : prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?', prefs[j].value); } if (uid->flags.mdc) es_fputs (",mdc", fp); if (!uid->flags.ks_modify) es_fputs (",no-ks-modify", fp); } es_putc (':', fp); /* flags */ es_fprintf (fp, "%d,", i); if (uid->flags.primary) es_putc ('p', fp); if (uid->flags.revoked) es_putc ('r', fp); if (uid->flags.expired) es_putc ('e', fp); if ((node->flag & NODFLG_SELUID)) es_putc ('s', fp); if ((node->flag & NODFLG_MARK_A)) es_putc ('m', fp); es_putc (':', fp); if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { #ifdef USE_TOFU enum tofu_policy policy; if (! tofu_get_policy (ctrl, primary, uid, &policy) && policy != TOFU_POLICY_NONE) es_fprintf (fp, "%s", tofu_policy_str (policy)); #endif /*USE_TOFU*/ } es_putc (':', fp); es_putc ('\n', fp); } } } static void show_names (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, PKT_public_key * pk, unsigned int flag, int with_prefs) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node)) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; if (!flag || (flag && (node->flag & flag))) { if (!(flag & NODFLG_MARK_A) && pk) tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid)); if (flag & NODFLG_MARK_A) tty_fprintf (fp, " "); else if (node->flag & NODFLG_SELUID) tty_fprintf (fp, "(%d)* ", i); else if (uid->flags.primary) tty_fprintf (fp, "(%d). ", i); else tty_fprintf (fp, "(%d) ", i); tty_print_utf8_string2 (fp, uid->name, uid->len, 0); tty_fprintf (fp, "\n"); if (with_prefs && pk) { if (pk->version > 3 || uid->selfsigversion > 3) { PKT_signature *selfsig = NULL; KBNODE signode; for (signode = node->next; signode && signode->pkt->pkttype == PKT_SIGNATURE; signode = signode->next) { if (signode->pkt->pkt.signature-> flags.chosen_selfsig) { selfsig = signode->pkt->pkt.signature; break; } } show_prefs (uid, selfsig, with_prefs == 2); } else tty_fprintf (fp, _("There are no preferences on a" " PGP 2.x-style user ID.\n")); } } } } } /* * Display the key a the user ids, if only_marked is true, do only so * for user ids with mark A flag set and do not display the index * number. If FP is not NULL print to the given stream and not to the * tty (ignored in with-colons mode). */ static void show_key_with_all_names (ctrl_t ctrl, estream_t fp, KBNODE keyblock, int only_marked, int with_revoker, int with_fpr, int with_subkeys, int with_prefs, int nowarn) { gpg_error_t err; kbnode_t node; int i; int do_warn = 0; int have_seckey = 0; char *serialno = NULL; PKT_public_key *primary = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; if (opt.with_colons) { show_key_with_all_names_colon (ctrl, fp, keyblock); return; } /* the keys */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY && !is_deleted_kbnode (node))) { PKT_public_key *pk = node->pkt->pkt.public_key; const char *otrust = "err"; const char *trust = "err"; if (node->pkt->pkttype == PKT_PUBLIC_KEY) { /* do it here, so that debug messages don't clutter the * output */ static int did_warn = 0; trust = get_validity_string (ctrl, pk, NULL); otrust = get_ownertrust_string (ctrl, pk, 0); /* Show a warning once */ if (!did_warn && (get_validity (ctrl, keyblock, pk, NULL, NULL, 0) & TRUST_FLAG_PENDING_CHECK)) { did_warn = 1; do_warn = 1; } primary = pk; } if (pk->flags.revoked) { char *user = get_user_id_string_native (ctrl, pk->revoked.keyid); tty_fprintf (fp, _("The following key was revoked on" " %s by %s key %s\n"), revokestr_from_pk (pk), gcry_pk_algo_name (pk->revoked.algo), user); xfree (user); } if (with_revoker) { if (!pk->revkey && pk->numrevkeys) BUG (); else for (i = 0; i < pk->numrevkeys; i++) { u32 r_keyid[2]; char *user; const char *algo; algo = gcry_pk_algo_name (pk->revkey[i].algid); keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, MAX_FINGERPRINT_LEN, r_keyid); user = get_user_id_string_native (ctrl, r_keyid); tty_fprintf (fp, _("This key may be revoked by %s key %s"), algo ? algo : "?", user); if (pk->revkey[i].class & 0x40) { tty_fprintf (fp, " "); tty_fprintf (fp, _("(sensitive)")); } tty_fprintf (fp, "\n"); xfree (user); } } keyid_from_pk (pk, NULL); xfree (serialno); serialno = NULL; { char *hexgrip; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) { log_error ("error computing a keygrip: %s\n", gpg_strerror (err)); have_seckey = 0; } else have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); xfree (hexgrip); } tty_fprintf (fp, "%s%c %s/%s", node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" : node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : have_seckey ? "ssb" : "sub", (node->flag & NODFLG_SELKEY) ? '*' : ' ', pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr (pk->keyid)); if (opt.legacy_list_mode) tty_fprintf (fp, " "); else tty_fprintf (fp, "\n "); tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk)); tty_fprintf (fp, " "); if (pk->flags.revoked) tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk)); else if (pk->has_expired) tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk)); else tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); tty_fprintf (fp, " "); tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1)); tty_fprintf (fp, "\n"); if (serialno) { /* The agent told us that a secret key is available and that it has been stored on a card. */ tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", _("card-no: ")); if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12)) { /* This is an OpenPGP card. Print the relevant part. */ /* Example: D2760001240101010001000003470000 */ /* xxxxyyyyyyyy */ tty_fprintf (fp, "%.*s %.*s\n", 4, serialno+16, 8, serialno+20); } else tty_fprintf (fp, "%s\n", serialno); } else if (pk->seckey_info && pk->seckey_info->is_protected && pk->seckey_info->s2k.mode == 1002) { - /* FIXME: Check wether this code path is still used. */ + /* FIXME: Check whether this code path is still used. */ tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", _("card-no: ")); if (pk->seckey_info->ivlen == 16 && !memcmp (pk->seckey_info->iv, "\xD2\x76\x00\x01\x24\x01", 6)) { /* This is an OpenPGP card. */ for (i = 8; i < 14; i++) { if (i == 10) tty_fprintf (fp, " "); tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]); } } else { /* Unknown card: Print all. */ for (i = 0; i < pk->seckey_info->ivlen; i++) tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]); } tty_fprintf (fp, "\n"); } if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) { if (opt.trust_model != TM_ALWAYS) { tty_fprintf (fp, "%*s", opt.legacy_list_mode? ((int) keystrlen () + 13):5, ""); /* Ownertrust is only meaningful for the PGP or classic trust models, or PGP combined with TOFU */ if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP) { int width = 14 - strlen (otrust); if (width <= 0) width = 1; tty_fprintf (fp, _("trust: %s"), otrust); tty_fprintf (fp, "%*s", width, ""); } tty_fprintf (fp, _("validity: %s"), trust); tty_fprintf (fp, "\n"); } if (node->pkt->pkttype == PKT_PUBLIC_KEY && (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED)) { tty_fprintf (fp, "*** "); tty_fprintf (fp, _("This key has been disabled")); tty_fprintf (fp, "\n"); } } if ((node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr) { print_fingerprint (ctrl, fp, pk, 2); tty_fprintf (fp, "\n"); } } } show_names (ctrl, fp, keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs); if (do_warn && !nowarn) tty_fprintf (fp, _("Please note that the shown key validity" " is not necessarily correct\n" "unless you restart the program.\n")); xfree (serialno); } /* Display basic key information. This function is suitable to show information on the key without any dependencies on the trustdb or any other internal GnuPG stuff. KEYBLOCK may either be a public or a secret key. This function may be called with KEYBLOCK containing secret keys and thus the printing of "pub" vs. "sec" does only depend on the packet type and not by checking with gpg-agent. */ void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock) { KBNODE node; int i; char pkstrbuf[PUBKEY_STRING_SIZE]; /* The primary key */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) { PKT_public_key *pk = node->pkt->pkt.public_key; /* Note, we use the same format string as in other show functions to make the translation job easier. */ tty_printf ("%s %s/%s ", node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : node->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "sub" : node->pkt->pkttype == PKT_SECRET_KEY ? "sec" :"ssb", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk)); tty_printf (_("created: %s"), datestr_from_pk (pk)); tty_printf (" "); tty_printf (_("expires: %s"), expirestr_from_pk (pk)); tty_printf ("\n"); print_fingerprint (ctrl, NULL, pk, 3); tty_printf ("\n"); } } /* The user IDs. */ for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; tty_printf (" "); if (uid->flags.revoked) tty_printf ("[%s] ", _("revoked")); else if (uid->flags.expired) tty_printf ("[%s] ", _("expired")); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); } } } static void show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys) { kbnode_t node; PKT_public_key *pk = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { pk = node->pkt->pkt.public_key; tty_printf ("pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk)); } else if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; tty_print_utf8_string (uid->name, uid->len); break; } } tty_printf ("\n"); if (pk) print_fingerprint (ctrl, NULL, pk, 2); if (with_subkeys) { for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { pk = node->pkt->pkt.public_key; tty_printf ("sub %s/%s %s [%s]\n", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk), usagestr_from_pk (pk, 0)); print_fingerprint (ctrl, NULL, pk, 4); } } } } /* Show a listing of the primary and its subkeys along with their keygrips. */ static void show_key_and_grip (kbnode_t keyblock) { kbnode_t node; PKT_public_key *pk = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; char *hexgrip; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { pk = node->pkt->pkt.public_key; tty_printf ("%s %s/%s %s [%s]\n", node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk), usagestr_from_pk (pk, 0)); if (!hexkeygrip_from_pk (pk, &hexgrip)) { tty_printf (" Keygrip: %s\n", hexgrip); xfree (hexgrip); } } } } /* Show a warning if no uids on the key have the primary uid flag set. */ static void no_primary_warning (KBNODE keyblock) { KBNODE node; int have_primary = 0, uid_count = 0; /* TODO: if we ever start behaving differently with a primary or non-primary attribute ID, we will need to check for attributes here as well. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->attrib_data == NULL) { uid_count++; if (node->pkt->pkt.user_id->flags.primary == 2) { have_primary = 1; break; } } } if (uid_count > 1 && !have_primary) log_info (_ ("WARNING: no user ID has been marked as primary. This command" " may\n cause a different user ID to become" " the assumed primary.\n")); } /* Print a warning if the latest encryption subkey expires soon. This function is called after the expire data of the primary key has been changed. */ static void subkey_expire_warning (kbnode_t keyblock) { u32 curtime = make_timestamp (); kbnode_t node; PKT_public_key *pk; /* u32 mainexpire = 0; */ u32 subexpire = 0; u32 latest_date = 0; for (node = keyblock; node; node = node->next) { /* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */ /* { */ /* pk = node->pkt->pkt.public_key; */ /* mainexpire = pk->expiredate; */ /* } */ if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY) continue; pk = node->pkt->pkt.public_key; if (!pk->flags.valid) continue; if (pk->flags.revoked) continue; if (pk->timestamp > curtime) continue; /* Ignore future keys. */ if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC)) continue; /* Not an encryption key. */ if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) { latest_date = pk->timestamp; subexpire = pk->expiredate; } } if (!subexpire) return; /* No valid subkey with an expiration time. */ if (curtime + (10*86400) > subexpire) { log_info (_("WARNING: Your encryption subkey expires soon.\n")); log_info (_("You may want to change its expiration date too.\n")); } } /* * Ask for a new user id, add the self-signature, and update the * keyblock. If UIDSTRING is not NULL the user ID is generated * unattended using that string. UIDSTRING is expected to be utf-8 * encoded and white space trimmed. Returns true if there is a new * user id. */ static int menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock, int photo, const char *photo_name, const char *uidstring) { PKT_user_id *uid; PKT_public_key *pk = NULL; PKT_signature *sig = NULL; PACKET *pkt; KBNODE node; KBNODE pub_where = NULL; gpg_error_t err; if (photo && uidstring) return 0; /* Not allowed. */ for (node = pub_keyblock; node; pub_where = node, node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; } if (!node) /* No subkey. */ pub_where = NULL; log_assert (pk); if (photo) { int hasattrib = 0; for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->attrib_data != NULL) { hasattrib = 1; break; } /* It is legal but bad for compatibility to add a photo ID to a v3 key as it means that PGP2 will not be able to use that key anymore. Also, PGP may not expect a photo on a v3 key. Don't bother to ask this if the key already has a photo - any damage has already been done at that point. -dms */ if (pk->version == 3 && !hasattrib) { if (opt.expert) { tty_printf (_("WARNING: This is a PGP2-style key. " "Adding a photo ID may cause some versions\n" " of PGP to reject this key.\n")); if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay", _("Are you sure you still want " "to add it? (y/N) "))) return 0; } else { tty_printf (_("You may not add a photo ID to " "a PGP2-style key.\n")); return 0; } } uid = generate_photo_id (ctrl, pk, photo_name); } else uid = generate_user_id (pub_keyblock, uidstring); if (!uid) { if (uidstring) { write_status_error ("adduid", gpg_error (304)); log_error ("%s", _("Such a user ID already exists on this key!\n")); } return 0; } err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0, 0, keygen_add_std_prefs, pk, NULL); if (err) { write_status_error ("keysig", err); log_error ("signing failed: %s\n", gpg_strerror (err)); free_user_id (uid); return 0; } /* Insert/append to public keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_USER_ID; pkt->pkt.user_id = uid; node = new_kbnode (pkt); if (pub_where) insert_kbnode (pub_where, node, 0); else add_kbnode (pub_keyblock, node); pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; if (pub_where) insert_kbnode (node, new_kbnode (pkt), 0); else add_kbnode (pub_keyblock, new_kbnode (pkt)); return 1; } /* * Remove all selected userids from the keyring */ static void menu_deluid (KBNODE pub_keyblock) { KBNODE node; int selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { selected = node->flag & NODFLG_SELUID; if (selected) { /* Only cause a trust update if we delete a non-revoked user id */ if (!node->pkt->pkt.user_id->flags.revoked) update_trust = 1; delete_kbnode (node); } } else if (selected && node->pkt->pkttype == PKT_SIGNATURE) delete_kbnode (node); else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) selected = 0; } commit_kbnode (&pub_keyblock); } static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock) { KBNODE node; PKT_user_id *uid = NULL; int changed = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL; } else if (uid && node->pkt->pkttype == PKT_SIGNATURE) { int okay, valid, selfsig, inv_sig, no_key, other_err; tty_printf ("uid "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); okay = inv_sig = no_key = other_err = 0; if (opt.with_colons) valid = print_and_check_one_sig_colon (ctrl, pub_keyblock, node, &inv_sig, &no_key, &other_err, &selfsig, 1); else valid = print_and_check_one_sig (ctrl, pub_keyblock, node, &inv_sig, &no_key, &other_err, &selfsig, 1, 0); if (valid) { okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.valid", _("Delete this good signature? (y/N/q)")); /* Only update trust if we delete a good signature. The other two cases do not affect trust. */ if (okay) update_trust = 1; } else if (inv_sig || other_err) okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.invalid", _("Delete this invalid signature? (y/N/q)")); else if (no_key) okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.unknown", _("Delete this unknown signature? (y/N/q)")); if (okay == -1) break; if (okay && selfsig && !cpr_get_answer_is_yes ("keyedit.delsig.selfsig", _("Really delete this self-signature? (y/N)"))) okay = 0; if (okay) { delete_kbnode (node); changed++; } } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) uid = NULL; } if (changed) { commit_kbnode (&pub_keyblock); tty_printf (ngettext("Deleted %d signature.\n", "Deleted %d signatures.\n", changed), changed); } else tty_printf (_("Nothing deleted.\n")); return changed; } static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only) { KBNODE uidnode; int modified = 0, select_all = !count_selected_uids (keyblock); for (uidnode = keyblock->next; uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY; uidnode = uidnode->next) { if (uidnode->pkt->pkttype == PKT_USER_ID && (uidnode->flag & NODFLG_SELUID || select_all)) { int uids = 0, sigs = 0; char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); clean_one_uid (ctrl, keyblock, uidnode, opt.verbose, self_only, &uids, &sigs); if (uids) { const char *reason; if (uidnode->pkt->pkt.user_id->flags.revoked) reason = _("revoked"); else if (uidnode->pkt->pkt.user_id->flags.expired) reason = _("expired"); else reason = _("invalid"); tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason); modified = 1; } else if (sigs) { tty_printf (ngettext("User ID \"%s\": %d signature removed\n", "User ID \"%s\": %d signatures removed\n", sigs), user, sigs); modified = 1; } else { tty_printf (self_only == 1 ? _("User ID \"%s\": already minimized\n") : _("User ID \"%s\": already clean\n"), user); } xfree (user); } } return modified; } /* * Remove some of the secondary keys */ static void menu_delkey (KBNODE pub_keyblock) { KBNODE node; int selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { selected = node->flag & NODFLG_SELKEY; if (selected) delete_kbnode (node); } else if (selected && node->pkt->pkttype == PKT_SIGNATURE) delete_kbnode (node); else selected = 0; } commit_kbnode (&pub_keyblock); /* No need to set update_trust here since signing keys are no longer used to certify other keys, so there is no change in trust when revoking/removing them. */ } /* * Ask for a new revoker, create the self-signature and put it into * the keyblock. Returns true if there is a new revoker. */ static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive) { PKT_public_key *pk = NULL; PKT_public_key *revoker_pk = NULL; PKT_signature *sig = NULL; PACKET *pkt; struct revocation_key revkey; size_t fprlen; int rc; log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); pk = pub_keyblock->pkt->pkt.public_key; if (pk->numrevkeys == 0 && pk->version == 3) { /* It is legal but bad for compatibility to add a revoker to a v3 key as it means that PGP2 will not be able to use that key anymore. Also, PGP may not expect a revoker on a v3 key. Don't bother to ask this if the key already has a revoker - any damage has already been done at that point. -dms */ if (opt.expert) { tty_printf (_("WARNING: This is a PGP 2.x-style key. " "Adding a designated revoker may cause\n" " some versions of PGP to reject this key.\n")); if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay", _("Are you sure you still want " "to add it? (y/N) "))) return 0; } else { tty_printf (_("You may not add a designated revoker to " "a PGP 2.x-style key.\n")); return 0; } } for (;;) { char *answer; free_public_key (revoker_pk); revoker_pk = xmalloc_clear (sizeof (*revoker_pk)); tty_printf ("\n"); answer = cpr_get_utf8 ("keyedit.add_revoker", _("Enter the user ID of the designated revoker: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); goto fail; } /* Note that I'm requesting CERT here, which usually implies primary keys only, but some casual testing shows that PGP and GnuPG both can handle a designated revocation from a subkey. */ revoker_pk->req_usage = PUBKEY_USAGE_CERT; rc = get_pubkey_byname (ctrl, NULL, revoker_pk, answer, NULL, NULL, 1, 1); if (rc) { log_error (_("key \"%s\" not found: %s\n"), answer, gpg_strerror (rc)); xfree (answer); continue; } xfree (answer); fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); if (fprlen != 20) { log_error (_("cannot appoint a PGP 2.x style key as a " "designated revoker\n")); continue; } revkey.class = 0x80; if (sensitive) revkey.class |= 0x40; revkey.algid = revoker_pk->pubkey_algo; if (cmp_public_keys (revoker_pk, pk) == 0) { /* This actually causes no harm (after all, a key that designates itself as a revoker is the same as a regular key), but it's easy enough to check. */ log_error (_("you cannot appoint a key as its own " "designated revoker\n")); continue; } keyid_from_pk (pk, NULL); /* Does this revkey already exist? */ if (!pk->revkey && pk->numrevkeys) BUG (); else { int i; for (i = 0; i < pk->numrevkeys; i++) { if (memcmp (&pk->revkey[i], &revkey, sizeof (struct revocation_key)) == 0) { char buf[50]; log_error (_("this key has already been designated " "as a revoker\n")); format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf)); write_status_text (STATUS_ALREADY_SIGNED, buf); break; } } if (i < pk->numrevkeys) continue; } print_pubkey_info (ctrl, NULL, revoker_pk); print_fingerprint (ctrl, NULL, revoker_pk, 2); tty_printf ("\n"); tty_printf (_("WARNING: appointing a key as a designated revoker " "cannot be undone!\n")); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay", _("Are you sure you want to appoint this " "key as a designated revoker? (y/N) "))) continue; free_public_key (revoker_pk); revoker_pk = NULL; break; } rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0, 0, keygen_add_revkey, &revkey, NULL); if (rc) { write_status_error ("keysig", rc); log_error ("signing failed: %s\n", gpg_strerror (rc)); goto fail; } /* Insert into public keyblock. */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE); return 1; fail: if (sig) free_seckey_enc (sig); free_public_key (revoker_pk); return 0; } /* With FORCE_MAINKEY cleared this function handles the interactive * menu option "expire". With FORCE_MAINKEY set this functions only * sets the expiration date of the primary key to NEWEXPIRATION and * avoid all interactivity. Retirns 0 if nothing was done, * GPG_ERR_TRUE if the key was modified, or any other error code. */ static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int force_mainkey, u32 newexpiration) { int signumber, rc; u32 expiredate; int mainkey = 0; PKT_public_key *main_pk, *sub_pk; PKT_user_id *uid; kbnode_t node; u32 keyid[2]; if (force_mainkey) { mainkey = 1; expiredate = newexpiration; } else { int n1 = count_selected_keys (pub_keyblock); if (n1 > 1) { if (!cpr_get_answer_is_yes ("keyedit.expire_multiple_subkeys.okay", _("Are you sure you want to change the" " expiration time for multiple subkeys? (y/N) "))) return gpg_error (GPG_ERR_CANCELED);; } else if (n1) tty_printf (_("Changing expiration time for a subkey.\n")); else { tty_printf (_("Changing expiration time for the primary key.\n")); mainkey = 1; no_primary_warning (pub_keyblock); } expiredate = ask_expiredate (); } /* Now we can actually change the self-signature(s) */ main_pk = sub_pk = NULL; uid = NULL; signumber = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); main_pk->expiredate = expiredate; } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if ((node->flag & NODFLG_SELKEY) && !force_mainkey) { sub_pk = node->pkt->pkt.public_key; sub_pk->expiredate = expiredate; } else sub_pk = NULL; } else if (node->pkt->pkttype == PKT_USER_ID) uid = node->pkt->pkt.user_id; else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE && (mainkey || sub_pk)) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && ((mainkey && uid && uid->created && (sig->sig_class & ~3) == 0x10) || (!mainkey && sig->sig_class == 0x18)) && sig->flags.chosen_selfsig) { /* This is a self-signature which is to be replaced. */ PKT_signature *newsig; PACKET *newpkt; signumber++; if ((mainkey && main_pk->version < 4) || (!mainkey && sub_pk->version < 4)) { log_info (_("You can't change the expiration date of a v3 key\n")); return gpg_error (GPG_ERR_LEGACY_KEY); } if (mainkey) rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_key_expire, main_pk); else rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, NULL, sub_pk, main_pk, keygen_add_key_expire, sub_pk); if (rc) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_TRUE) rc = GPG_ERR_GENERAL; return rc; } /* Replace the packet. */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; sub_pk = NULL; } } } update_trust = 1; return gpg_error (GPG_ERR_TRUE); } /* Change the capability of a selected key. This command should only * be used to rectify badly created keys and as such is not suggested * for general use. */ static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock) { int n1, rc; int mainkey = 0; PKT_public_key *main_pk, *sub_pk; PKT_user_id *uid; kbnode_t node; u32 keyid[2]; n1 = count_selected_keys (keyblock); if (n1 > 1) { tty_printf (_("You must select exactly one key.\n")); return 0; } else if (n1) tty_printf ("Changing usage of a subkey.\n"); else { tty_printf ("Changing usage of the primary key.\n"); mainkey = 1; } /* Now we can actually change the self-signature(s) */ main_pk = sub_pk = NULL; uid = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if (node->flag & NODFLG_SELKEY) sub_pk = node->pkt->pkt.public_key; else sub_pk = NULL; } else if (node->pkt->pkttype == PKT_USER_ID) uid = node->pkt->pkt.user_id; else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE && (mainkey || sub_pk)) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && ((mainkey && uid && uid->created && (sig->sig_class & ~3) == 0x10) || (!mainkey && sig->sig_class == 0x18)) && sig->flags.chosen_selfsig) { /* This is the self-signature which is to be replaced. */ PKT_signature *newsig; PACKET *newpkt; if ((mainkey && main_pk->version < 4) || (!mainkey && sub_pk->version < 4)) { log_info ("You can't change the capabilities of a v3 key\n"); return 0; } if (mainkey) main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0, main_pk->pubkey_usage); else sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1, sub_pk->pubkey_usage); if (mainkey) rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_key_flags, main_pk); else rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, NULL, sub_pk, main_pk, keygen_add_key_flags, sub_pk); if (rc) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* Replace the packet. */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; sub_pk = NULL; break; } } } return 1; } static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock) { int rc, modified = 0; PKT_public_key *main_pk; KBNODE node; u32 timestamp; log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); merge_keys_and_selfsig (ctrl, pub_keyblock); main_pk = pub_keyblock->pkt->pkt.public_key; keyid_from_pk (main_pk, NULL); /* We use the same timestamp for all backsigs so that we don't reveal information about the used machine. */ timestamp = make_timestamp (); for (node = pub_keyblock; node; node = node->next) { PKT_public_key *sub_pk = NULL; KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/; /* char *passphrase; */ /* Find a signing subkey with no backsig */ if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG) { if (node->pkt->pkt.public_key->flags.backsig) tty_printf (_ ("signing subkey %s is already cross-certified\n"), keystr_from_pk (node->pkt->pkt.public_key)); else sub_pk = node->pkt->pkt.public_key; } else tty_printf (_("subkey %s does not sign and so does" " not need to be cross-certified\n"), keystr_from_pk (node->pkt->pkt.public_key)); } if (!sub_pk) continue; /* Find the selected selfsig on this subkey */ for (node2 = node->next; node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next) if (node2->pkt->pkt.signature->version >= 4 && node2->pkt->pkt.signature->flags.chosen_selfsig) { sig_pk = node2; break; } if (!sig_pk) continue; /* Find the secret subkey that matches the public subkey */ log_debug ("FIXME: Check whether a secret subkey is available.\n"); /* if (!sub_sk) */ /* { */ /* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */ /* keystr_from_pk (sub_pk)); */ /* continue; */ /* } */ /* Now we can get to work. */ rc = make_backsig (ctrl, sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk, timestamp, NULL); if (!rc) { PKT_signature *newsig; PACKET *newpkt; rc = update_keysig_packet (ctrl, &newsig, sig_pk->pkt->pkt.signature, main_pk, NULL, sub_pk, main_pk, NULL, NULL); if (!rc) { /* Put the new sig into place on the pubkey */ newpkt = xmalloc_clear (sizeof (*newpkt)); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (sig_pk->pkt, NULL); xfree (sig_pk->pkt); sig_pk->pkt = newpkt; modified = 1; } else { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); break; } } else { log_error ("make_backsig failed: %s\n", gpg_strerror (rc)); break; } } return modified; } static int change_primary_uid_cb (PKT_signature * sig, void *opaque) { byte buf[1]; /* first clear all primary uid flags so that we are sure none are * lingering around */ delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID); /* if opaque is set,we want to set the primary id */ if (opaque) { buf[0] = 1; build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1); } return 0; } /* * Set the primary uid flag for the selected UID. We will also reset * all other primary uid flags. For this to work we have to update * all the signature timestamps. If we would do this with the current * time, we lose quite a lot of information, so we use a kludge to * do this: Just increment the timestamp by one second which is * sufficient to updated a signature during import. */ static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected; int attribute = 0; int modified = 0; if (count_selected_uids (pub_keyblock) != 1) { tty_printf (_("Please select exactly one user ID.\n")); return 0; } main_pk = NULL; uid = NULL; selected = 0; /* Is our selected uid an attribute packet? */ for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID) attribute = (node->pkt->pkt.user_id->attrib_data != NULL); for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* No more user ids expected - ready. */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = node->flag & NODFLG_SELUID; } else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && attribute == (uid->attrib_data != NULL) && sig->flags.chosen_selfsig) { if (sig->version < 4) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); xfree (user); } else { /* This is a selfsignature which is to be replaced. We can just ignore v3 signatures because they are not able to carry the primary ID flag. We also ignore self-sigs on user IDs that are not of the same type that we are making primary. That is, if we are making a user ID primary, we alter user IDs. If we are making an attribute packet primary, we alter attribute packets. */ /* FIXME: We must make sure that we only have one self-signature per user ID here (not counting revocations) */ PKT_signature *newsig; PACKET *newpkt; const byte *p; int action; /* See whether this signature has the primary UID flag. */ p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID, NULL); if (!p) p = parse_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID, NULL); if (p && *p) /* yes */ action = selected ? 0 : -1; else /* no */ action = selected ? 1 : 0; if (action) { int rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, change_primary_uid_cb, action > 0 ? "x" : NULL); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } } } } } return modified; } /* * Set preferences to new values for the selected user IDs */ static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; no_primary_warning (pub_keyblock); select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* No more user-ids expected - ready. */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { if (sig->version < 4) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); xfree (user); } else { /* This is a selfsignature which is to be replaced * We have to ignore v3 signatures because they are * not able to carry the preferences. */ PKT_signature *newsig; PACKET *newpkt; int rc; rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_upd_std_prefs, NULL); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } } } } return modified; } static int menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; char *answer, *uri; no_primary_warning (pub_keyblock); if (url) answer = xstrdup (url); else { answer = cpr_get_utf8 ("keyedit.add_keyserver", _("Enter your preferred keyserver URL: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); return 0; } } if (ascii_strcasecmp (answer, "none") == 0) uri = NULL; else { struct keyserver_spec *keyserver = NULL; /* Sanity check the format */ keyserver = parse_keyserver_uri (answer, 1); xfree (answer); if (!keyserver) { log_info (_("could not parse keyserver URL\n")); return 0; } uri = xstrdup (keyserver->uri); free_keyserver_spec (keyserver); } select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* ready */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); if (sig->version < 4) log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); else { /* This is a selfsignature which is to be replaced * We have to ignore v3 signatures because they are * not able to carry the subpacket. */ PKT_signature *newsig; PACKET *newpkt; int rc; const byte *p; size_t plen; p = parse_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &plen); if (p && plen) { tty_printf ("Current preferred keyserver for user" " ID \"%s\": ", user); tty_print_utf8_string (p, plen); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("keyedit.confirm_keyserver", uri ? _("Are you sure you want to replace it? (y/N) ") : _("Are you sure you want to delete it? (y/N) "))) continue; } else if (uri == NULL) { /* There is no current keyserver URL, so there is no point in trying to un-set it. */ continue; } rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_keyserver_url, uri); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); xfree (uri); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } xfree (user); } } } xfree (uri); return modified; } static int menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; char *answer; struct notation *notation; no_primary_warning (pub_keyblock); if (string) answer = xstrdup (string); else { answer = cpr_get_utf8 ("keyedit.add_notation", _("Enter the notation: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); return 0; } } if (!ascii_strcasecmp (answer, "none") || !ascii_strcasecmp (answer, "-")) notation = NULL; /* Delete them all. */ else { notation = string_to_notation (answer, 0); if (!notation) { xfree (answer); return 0; } } xfree (answer); select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* ready */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); if (sig->version < 4) log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); else { PKT_signature *newsig; PACKET *newpkt; int rc, skip = 0, addonly = 1; if (sig->flags.notation) { tty_printf ("Current notations for user ID \"%s\":\n", user); tty_print_notations (-9, sig); } else { tty_printf ("No notations on user ID \"%s\"\n", user); if (notation == NULL) { /* There are no current notations, so there is no point in trying to un-set them. */ continue; } } if (notation) { struct notation *n; int deleting = 0; notation->next = sig_to_notation (sig); for (n = notation->next; n; n = n->next) if (strcmp (n->name, notation->name) == 0) { if (notation->value) { if (strcmp (n->value, notation->value) == 0) { if (notation->flags.ignore) { /* Value match with a delete flag. */ n->flags.ignore = 1; deleting = 1; } else { /* Adding the same notation twice, so don't add it at all. */ skip = 1; tty_printf ("Skipping notation:" " %s=%s\n", notation->name, notation->value); break; } } } else { /* No value, so it means delete. */ n->flags.ignore = 1; deleting = 1; } if (n->flags.ignore) { tty_printf ("Removing notation: %s=%s\n", n->name, n->value); addonly = 0; } } if (!notation->flags.ignore && !skip) tty_printf ("Adding notation: %s=%s\n", notation->name, notation->value); /* We tried to delete, but had no matches. */ if (notation->flags.ignore && !deleting) continue; } else { tty_printf ("Removing all notations\n"); addonly = 0; } if (skip || (!addonly && !cpr_get_answer_is_yes ("keyedit.confirm_notation", _("Proceed? (y/N) ")))) continue; rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_notations, notation); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); free_notation (notation); xfree (user); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; if (notation) { /* Snip off the notation list from the sig */ free_notation (notation->next); notation->next = NULL; } xfree (user); } } } } free_notation (notation); return modified; } /* * Select one user id or remove all selection if IDX is 0 or select * all if IDX is -1. Returns: True if the selection changed. */ static int menu_select_uid (KBNODE keyblock, int idx) { KBNODE node; int i; if (idx == -1) /* Select all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) node->flag |= NODFLG_SELUID; return 1; } else if (idx) /* Toggle. */ { for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) if (++i == idx) break; } if (!node) { tty_printf (_("No user ID with index %d\n"), idx); return 0; } for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { if (++i == idx) { if ((node->flag & NODFLG_SELUID)) node->flag &= ~NODFLG_SELUID; else node->flag |= NODFLG_SELUID; } } } } else /* Unselect all */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) node->flag &= ~NODFLG_SELUID; } return 1; } /* Search in the keyblock for a uid that matches namehash */ static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash) { byte hash[NAMEHASH_LEN]; KBNODE node; int i; log_assert (strlen (namehash) == NAMEHASH_LEN * 2); for (i = 0; i < NAMEHASH_LEN; i++) hash[i] = hextobyte (&namehash[i * 2]); for (node = keyblock->next; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { namehash_from_uid (node->pkt->pkt.user_id); if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) == 0) { if (node->flag & NODFLG_SELUID) node->flag &= ~NODFLG_SELUID; else node->flag |= NODFLG_SELUID; break; } } } if (!node) { tty_printf (_("No user ID with hash %s\n"), namehash); return 0; } return 1; } /* * Select secondary keys * Returns: True if the selection changed. */ static int menu_select_key (KBNODE keyblock, int idx, char *p) { KBNODE node; int i, j; int is_hex_digits; is_hex_digits = p && strlen (p) >= 8; if (is_hex_digits) { /* Skip initial spaces. */ while (spacep (p)) p ++; /* If the id starts with 0x accept and ignore it. */ if (p[0] == '0' && p[1] == 'x') p += 2; for (i = 0, j = 0; p[i]; i ++) if (hexdigitp (&p[i])) { p[j] = toupper (p[i]); j ++; } else if (spacep (&p[i])) /* Skip spaces. */ { } else { is_hex_digits = 0; break; } if (is_hex_digits) /* In case we skipped some spaces, add a new NUL terminator. */ { p[j] = 0; /* If we skipped some spaces, make sure that we still have at least 8 characters. */ is_hex_digits = (/* Short keyid. */ strlen (p) == 8 /* Long keyid. */ || strlen (p) == 16 /* Fingerprints are (currently) 32 or 40 characters. */ || strlen (p) >= 32); } } if (is_hex_digits) { int found_one = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { int match = 0; if (strlen (p) == 8 || strlen (p) == 16) { u32 kid[2]; char kid_str[17]; keyid_from_pk (node->pkt->pkt.public_key, kid); format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG, kid_str, sizeof (kid_str)); if (strcmp (p, kid_str) == 0) match = 1; } else { char fp[2*MAX_FINGERPRINT_LEN + 1]; hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp)); if (strcmp (fp, p) == 0) match = 1; } if (match) { if ((node->flag & NODFLG_SELKEY)) node->flag &= ~NODFLG_SELKEY; else node->flag |= NODFLG_SELKEY; found_one = 1; } } if (found_one) return 1; tty_printf (_("No subkey with key ID '%s'.\n"), p); return 0; } if (idx == -1) /* Select all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) node->flag |= NODFLG_SELKEY; } else if (idx) /* Toggle selection. */ { for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) if (++i == idx) break; } if (!node) { tty_printf (_("No subkey with index %d\n"), idx); return 0; } for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) if (++i == idx) { if ((node->flag & NODFLG_SELKEY)) node->flag &= ~NODFLG_SELKEY; else node->flag |= NODFLG_SELKEY; } } } else /* Unselect all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) node->flag &= ~NODFLG_SELKEY; } return 1; } static int count_uids_with_flag (KBNODE keyblock, unsigned flag) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag)) i++; return i; } static int count_keys_with_flag (KBNODE keyblock, unsigned flag) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag)) i++; return i; } static int count_uids (KBNODE keyblock) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) i++; return i; } /* * Returns true if there is at least one selected user id */ static int count_selected_uids (KBNODE keyblock) { return count_uids_with_flag (keyblock, NODFLG_SELUID); } static int count_selected_keys (KBNODE keyblock) { return count_keys_with_flag (keyblock, NODFLG_SELKEY); } /* Returns how many real (i.e. not attribute) uids are unmarked. */ static int real_uids_left (KBNODE keyblock) { KBNODE node; int real = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) && !node->pkt->pkt.user_id->attrib_data) real++; return real; } /* * Ask whether the signature should be revoked. If the user commits this, * flag bit MARK_A is set on the signature and the user ID. */ static void ask_revoke_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node) { int doit = 0; PKT_user_id *uid; PKT_signature *sig = node->pkt->pkt.signature; KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); if (!unode) { log_error ("Oops: no user ID for signature\n"); return; } uid = unode->pkt->pkt.user_id; if (opt.with_colons) { if (uid->attrib_data) printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len); else { es_printf ("uid:::::::::"); es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL); } es_printf ("\n"); print_and_check_one_sig_colon (ctrl, keyblock, node, NULL, NULL, NULL, NULL, 1); } else { char *p = utf8_to_native (unode->pkt->pkt.user_id->name, unode->pkt->pkt.user_id->len, 0); tty_printf (_("user ID: \"%s\"\n"), p); xfree (p); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), sig->flags.exportable ? "" : _(" (non-exportable)"), ""); } if (sig->flags.expired) { tty_printf (_("This signature expired on %s.\n"), expirestr_from_sig (sig)); /* Use a different question so we can have different help text */ doit = cpr_get_answer_is_yes ("ask_revoke_sig.expired", _("Are you sure you still want to revoke it? (y/N) ")); } else doit = cpr_get_answer_is_yes ("ask_revoke_sig.one", _("Create a revocation certificate for this signature? (y/N) ")); if (doit) { node->flag |= NODFLG_MARK_A; unode->flag |= NODFLG_MARK_A; } } /* * Display all user ids of the current public key together with signatures * done by one of our keys. Then walk over all this sigs and ask the user * whether he wants to revoke this signature. * Return: True when the keyblock has changed. */ static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock) { PKT_signature *sig; PKT_public_key *primary_pk; KBNODE node; int changed = 0; int rc, any, skip = 1, all = !count_selected_uids (keyblock); struct revocation_reason_info *reason = NULL; log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); /* First check whether we have any signatures at all. */ any = 0; for (node = keyblock; node; node = node->next) { node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A); if (node->pkt->pkttype == PKT_USER_ID) { if (node->flag & NODFLG_SELUID || all) skip = 0; else skip = 1; } else if (!skip && node->pkt->pkttype == PKT_SIGNATURE && ((sig = node->pkt->pkt.signature), have_secret_key_with_kid (sig->keyid))) { if ((sig->sig_class & ~3) == 0x10) { any = 1; break; } } } if (!any) { tty_printf (_("Not signed by you.\n")); return 0; } /* FIXME: detect duplicates here */ tty_printf (_("You have signed these user IDs on key %s:\n"), keystr_from_pk (keyblock->pkt->pkt.public_key)); for (node = keyblock; node; node = node->next) { node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A); if (node->pkt->pkttype == PKT_USER_ID) { if (node->flag & NODFLG_SELUID || all) { PKT_user_id *uid = node->pkt->pkt.user_id; /* Hmmm: Should we show only UIDs with a signature? */ tty_printf (" "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); skip = 0; } else skip = 1; } else if (!skip && node->pkt->pkttype == PKT_SIGNATURE && ((sig = node->pkt->pkt.signature), have_secret_key_with_kid (sig->keyid))) { if ((sig->sig_class & ~3) == 0x10) { tty_printf (" "); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), sig->flags.exportable ? "" : _(" (non-exportable)"), sig->flags.revocable ? "" : _(" (non-revocable)")); if (sig->flags.revocable) node->flag |= NODFLG_SELSIG; } else if (sig->sig_class == 0x30) { tty_printf (" "); tty_printf (_("revoked by your key %s on %s\n"), keystr (sig->keyid), datestr_from_sig (sig)); } } } tty_printf ("\n"); /* ask */ for (node = keyblock; node; node = node->next) { if (!(node->flag & NODFLG_SELSIG)) continue; ask_revoke_sig (ctrl, keyblock, node); } /* present selected */ any = 0; for (node = keyblock; node; node = node->next) { if (!(node->flag & NODFLG_MARK_A)) continue; if (!any) { any = 1; tty_printf (_("You are about to revoke these signatures:\n")); } if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; tty_printf (" "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); } else if (node->pkt->pkttype == PKT_SIGNATURE) { sig = node->pkt->pkt.signature; tty_printf (" "); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), "", sig->flags.exportable ? "" : _(" (non-exportable)")); } } if (!any) return 0; /* none selected */ if (!cpr_get_answer_is_yes ("ask_revoke_sig.okay", _("Really create the revocation certificates? (y/N) "))) return 0; /* forget it */ reason = ask_revocation_reason (0, 1, 0); if (!reason) { /* user decided to cancel */ return 0; } /* now we can sign the user ids */ -reloop: /* (must use this, because we are modifing the list) */ +reloop: /* (must use this, because we are modifying the list) */ primary_pk = keyblock->pkt->pkt.public_key; for (node = keyblock; node; node = node->next) { KBNODE unode; PACKET *pkt; struct sign_attrib attrib; PKT_public_key *signerkey; if (!(node->flag & NODFLG_MARK_A) || node->pkt->pkttype != PKT_SIGNATURE) continue; unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); log_assert (unode); /* we already checked this */ memset (&attrib, 0, sizeof attrib); attrib.reason = reason; attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable; node->flag &= ~NODFLG_MARK_A; signerkey = xmalloc_secure_clear (sizeof *signerkey); if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid)) { log_info (_("no secret key\n")); free_public_key (signerkey); continue; } rc = make_keysig_packet (ctrl, &sig, primary_pk, unode->pkt->pkt.user_id, NULL, signerkey, 0x30, 0, 0, 0, sign_mk_attrib, &attrib, NULL); free_public_key (signerkey); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); release_revocation_reason_info (reason); return changed; } changed = 1; /* we changed the keyblock */ update_trust = 1; /* Are we revoking our own uid? */ if (primary_pk->keyid[0] == sig->keyid[0] && primary_pk->keyid[1] == sig->keyid[1]) unode->pkt->pkt.user_id->flags.revoked = 1; pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (unode, new_kbnode (pkt), 0); goto reloop; } release_revocation_reason_info (reason); return changed; } /* return 0 if revocation of NODE (which must be a User ID) was successful, non-zero if there was an error. *modified will be set to 1 if a change was made. */ static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, const struct revocation_reason_info *reason, int *modified) { PKT_public_key *pk = keyblock->pkt->pkt.public_key; gpg_error_t rc; if (node->pkt->pkttype != PKT_USER_ID) { rc = gpg_error (GPG_ERR_NO_USER_ID); write_status_error ("keysig", rc); log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc)); return 1; } else { PKT_user_id *uid = node->pkt->pkt.user_id; if (uid->flags.revoked) { char *user = utf8_to_native (uid->name, uid->len, 0); log_info (_("user ID \"%s\" is already revoked\n"), user); xfree (user); } else { PACKET *pkt; PKT_signature *sig; struct sign_attrib attrib; u32 timestamp = make_timestamp (); if (uid->created >= timestamp) { /* Okay, this is a problem. The user ID selfsig was created in the future, so we need to warn the user and set our revocation timestamp one second after that so everything comes out clean. */ log_info (_("WARNING: a user ID signature is dated %d" " seconds in the future\n"), uid->created - timestamp); timestamp = uid->created + 1; } memset (&attrib, 0, sizeof attrib); /* should not need to cast away const here; but revocation_reason_build_cb needs to take a non-const void* in order to meet the function signtuare for the mksubpkt argument to make_keysig_packet */ attrib.reason = (struct revocation_reason_info *)reason; rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30, 0, timestamp, 0, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); return 1; } else { pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), 0); #ifndef NO_TRUST_MODELS /* If the trustdb has an entry for this key+uid then the trustdb needs an update. */ if (!update_trust && ((get_validity (ctrl, keyblock, pk, uid, NULL, 0) & TRUST_MASK) >= TRUST_UNDEFINED)) update_trust = 1; #endif /*!NO_TRUST_MODELS*/ node->pkt->pkt.user_id->flags.revoked = 1; if (modified) *modified = 1; } } return 0; } } /* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if keyblock changed. */ static int menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; KBNODE node; int changed = 0; int rc; struct revocation_reason_info *reason = NULL; size_t valid_uids; /* Note that this is correct as per the RFCs, but nevertheless somewhat meaningless in the real world. 1991 did define the 0x30 sig class, but PGP 2.x did not actually implement it, so it would probably be safe to use v4 revocations everywhere. -ds */ for (node = pub_keyblock; node; node = node->next) if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->selfsigversion > 3)) { if ((reason = ask_revocation_reason (0, 1, 4))) break; else goto leave; } /* Too make sure that we do not revoke the last valid UID, we first count how many valid UIDs there are. */ valid_uids = 0; for (node = pub_keyblock; node; node = node->next) valid_uids += node->pkt->pkttype == PKT_USER_ID && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired; reloop: /* (better this way because we are modifying the keyring) */ for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID)) { int modified = 0; /* Make sure that we do not revoke the last valid UID. */ if (valid_uids == 1 && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired) { log_error (_("Cannot revoke the last valid user ID.\n")); goto leave; } rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified); if (rc) goto leave; if (modified) { node->flag &= ~NODFLG_SELUID; changed = 1; goto reloop; } } if (changed) commit_kbnode (&pub_keyblock); leave: release_revocation_reason_info (reason); return changed; } /* * Revoke the whole key. */ static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; int rc, changed = 0; struct revocation_reason_info *reason; PACKET *pkt; PKT_signature *sig; if (pk->flags.revoked) { tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk)); return 0; } reason = ask_revocation_reason (1, 0, 0); /* user decided to cancel */ if (!reason) return 0; rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x20, 0, 0, 0, revocation_reason_build_cb, reason, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); goto scram; } changed = 1; /* we changed the keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (pub_keyblock, new_kbnode (pkt), 0); commit_kbnode (&pub_keyblock); update_trust = 1; scram: release_revocation_reason_info (reason); return changed; } static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *mainpk; KBNODE node; int changed = 0; int rc; struct revocation_reason_info *reason = NULL; reason = ask_revocation_reason (1, 0, 0); if (!reason) return 0; /* User decided to cancel. */ - reloop: /* (better this way because we are modifing the keyring) */ + reloop: /* (better this way because we are modifying the keyring) */ mainpk = pub_keyblock->pkt->pkt.public_key; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && (node->flag & NODFLG_SELKEY)) { PACKET *pkt; PKT_signature *sig; PKT_public_key *subpk = node->pkt->pkt.public_key; struct sign_attrib attrib; if (subpk->flags.revoked) { tty_printf (_("Subkey %s is already revoked.\n"), keystr_from_pk (subpk)); continue; } memset (&attrib, 0, sizeof attrib); attrib.reason = reason; node->flag &= ~NODFLG_SELKEY; rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk, 0x28, 0, 0, 0, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); release_revocation_reason_info (reason); return changed; } changed = 1; /* we changed the keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), 0); goto reloop; } } commit_kbnode (&pub_keyblock); /* No need to set update_trust here since signing keys no longer are used to certify other keys, so there is no change in trust when revoking/removing them */ release_revocation_reason_info (reason); return changed; } /* Note that update_ownertrust is going to mark the trustdb dirty when enabling or disabling a key. This is arguably sub-optimal as disabled keys are still counted in the web of trust, but perhaps not worth adding extra complexity to change. -ds */ #ifndef NO_TRUST_MODELS static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable) { PKT_public_key *pk = find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key; unsigned int trust, newtrust; trust = newtrust = get_ownertrust (ctrl, pk); newtrust &= ~TRUST_FLAG_DISABLED; if (disable) newtrust |= TRUST_FLAG_DISABLED; if (trust == newtrust) return 0; /* already in that state */ update_ownertrust (ctrl, pk, newtrust); return 0; } #endif /*!NO_TRUST_MODELS*/ static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock) { KBNODE node; int select_all = !count_selected_uids (keyblock); int count = 0; PKT_public_key *pk = NULL; /* Look for the public key first. We have to be really, really, explicit as to which photo this is, and what key it is a UID on since people may want to sign it. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; count++; if ((select_all || (node->flag & NODFLG_SELUID)) && uid->attribs != NULL) { int i; for (i = 0; i < uid->numattribs; i++) { byte type; u32 size; if (uid->attribs[i].type == ATTRIB_IMAGE && parse_image_header (&uid->attribs[i], &type, &size)) { tty_printf (_("Displaying %s photo ID of size %ld for " "key %s (uid %d)\n"), image_type_to_string (type, 1), (ulong) size, keystr_from_pk (pk), count); show_photos (ctrl, &uid->attribs[i], 1, pk, uid); } } } } } } diff --git a/g10/keylist.c b/g10/keylist.c index c75856f5f..e2b8fef05 100644 --- a/g10/keylist.c +++ b/g10/keylist.c @@ -1,1961 +1,1961 @@ /* keylist.c - Print information about OpenPGP keys * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2008, 2010, 2012 Free Software Foundation, Inc. * Copyright (C) 2013, 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #ifdef HAVE_DOSISH_SYSTEM # include <fcntl.h> /* for setmode() */ #endif #include "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "keydb.h" #include "photoid.h" #include "../common/util.h" #include "../common/ttyio.h" #include "trustdb.h" #include "main.h" #include "../common/i18n.h" #include "../common/status.h" #include "call-agent.h" #include "../common/mbox-util.h" #include "../common/zb32.h" #include "tofu.h" static void list_all (ctrl_t, int, int); static void list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret); static void locate_one (ctrl_t ctrl, strlist_t names); static void print_card_serialno (const char *serialno); struct keylist_context { int check_sigs; /* If set signatures shall be verified. */ int good_sigs; /* Counter used if CHECK_SIGS is set. */ int inv_sigs; /* Counter used if CHECK_SIGS is set. */ int no_key; /* Counter used if CHECK_SIGS is set. */ int oth_err; /* Counter used if CHECK_SIGS is set. */ int no_validity; /* Do not show validity. */ }; static void list_keyblock (ctrl_t ctrl, kbnode_t keyblock, int secret, int has_secret, int fpr, struct keylist_context *listctx); /* The stream used to write attribute packets to. */ static estream_t attrib_fp; /* Release resources from a keylist context. */ static void keylist_context_release (struct keylist_context *listctx) { (void)listctx; /* Nothing to release. */ } /* List the keys. If list is NULL, all available keys are listed. With LOCATE_MODE set the locate algorithm is used to find a key. */ void public_key_list (ctrl_t ctrl, strlist_t list, int locate_mode) { #ifndef NO_TRUST_MODELS if (opt.with_colons) { byte trust_model, marginals, completes, cert_depth, min_cert_level; ulong created, nextcheck; read_trust_options (ctrl, &trust_model, &created, &nextcheck, &marginals, &completes, &cert_depth, &min_cert_level); es_fprintf (es_stdout, "tru:"); if (nextcheck && nextcheck <= make_timestamp ()) es_fprintf (es_stdout, "o"); if (trust_model != opt.trust_model) es_fprintf (es_stdout, "t"); if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP) { if (marginals != opt.marginals_needed) es_fprintf (es_stdout, "m"); if (completes != opt.completes_needed) es_fprintf (es_stdout, "c"); if (cert_depth != opt.max_cert_depth) es_fprintf (es_stdout, "d"); if (min_cert_level != opt.min_cert_level) es_fprintf (es_stdout, "l"); } es_fprintf (es_stdout, ":%d:%lu:%lu", trust_model, created, nextcheck); /* Only show marginals, completes, and cert_depth in the classic or PGP trust models since they are not meaningful otherwise. */ if (trust_model == TM_PGP || trust_model == TM_CLASSIC) es_fprintf (es_stdout, ":%d:%d:%d", marginals, completes, cert_depth); es_fprintf (es_stdout, "\n"); } #endif /*!NO_TRUST_MODELS*/ /* We need to do the stale check right here because it might need to update the keyring while we already have the keyring open. This is very bad for W32 because of a sharing violation. For real OSes it might lead to false results if we are later listing a keyring which is associated with the inode of a deleted file. */ check_trustdb_stale (ctrl); #ifdef USE_TOFU tofu_begin_batch_update (ctrl); #endif if (locate_mode) locate_one (ctrl, list); else if (!list) list_all (ctrl, 0, opt.with_secret); else list_one (ctrl, list, 0, opt.with_secret); #ifdef USE_TOFU tofu_end_batch_update (ctrl); #endif } void secret_key_list (ctrl_t ctrl, strlist_t list) { (void)ctrl; check_trustdb_stale (ctrl); if (!list) list_all (ctrl, 1, 0); else /* List by user id */ list_one (ctrl, list, 1, 0); } char * format_seckey_info (ctrl_t ctrl, PKT_public_key *pk) { u32 keyid[2]; char *p; char pkstrbuf[PUBKEY_STRING_SIZE]; char *info; keyid_from_pk (pk, keyid); p = get_user_id_native (ctrl, keyid); info = xtryasprintf ("sec %s/%s %s %s", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr (keyid), datestr_from_pk (pk), p); xfree (p); return info; } void print_seckey_info (ctrl_t ctrl, PKT_public_key *pk) { char *p = format_seckey_info (ctrl, pk); tty_printf ("\n%s\n", p); xfree (p); } /* Print information about the public key. With FP passed as NULL, the tty output interface is used, otherwise output is directted to the given stream. */ void print_pubkey_info (ctrl_t ctrl, estream_t fp, PKT_public_key *pk) { u32 keyid[2]; char *p; char pkstrbuf[PUBKEY_STRING_SIZE]; keyid_from_pk (pk, keyid); /* If the pk was chosen by a particular user ID, that is the one to print. */ if (pk->user_id) p = utf8_to_native (pk->user_id->name, pk->user_id->len, 0); else p = get_user_id_native (ctrl, keyid); if (fp) tty_printf ("\n"); tty_fprintf (fp, "%s %s/%s %s %s\n", pk->flags.primary? "pub":"sub", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr (keyid), datestr_from_pk (pk), p); xfree (p); } /* Print basic information of a secret key including the card serial number information. */ #ifdef ENABLE_CARD_SUPPORT void print_card_key_info (estream_t fp, kbnode_t keyblock) { kbnode_t node; char *hexgrip; char *serialno; int s2k_char; char pkstrbuf[PUBKEY_STRING_SIZE]; int indent; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { int rc; PKT_public_key *pk = node->pkt->pkt.public_key; serialno = NULL; rc = hexkeygrip_from_pk (pk, &hexgrip); if (rc) { log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); s2k_char = '?'; } else if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) s2k_char = serialno? '>':' '; else s2k_char = '#'; /* Key not found. */ tty_fprintf (fp, "%s%c %s/%s %n", node->pkt->pkttype == PKT_PUBLIC_KEY ? "sec" : "ssb", s2k_char, pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk), &indent); tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk)); tty_fprintf (fp, " "); tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); if (serialno) { tty_fprintf (fp, "\n%*s%s", indent, "", _("card-no: ")); if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12)) { /* This is an OpenPGP card. Print the relevant part. */ /* Example: D2760001240101010001000003470000 */ /* xxxxyyyyyyyy */ tty_fprintf (fp, "%.*s %.*s", 4, serialno+16, 8, serialno+20); } else tty_fprintf (fp, "%s", serialno); } tty_fprintf (fp, "\n"); xfree (hexgrip); xfree (serialno); } } } #endif /*ENABLE_CARD_SUPPORT*/ /* Flags = 0x01 hashed 0x02 critical. */ static void status_one_subpacket (sigsubpkttype_t type, size_t len, int flags, const byte * buf) { char status[40]; /* Don't print these. */ if (len > 256) return; snprintf (status, sizeof status, "%d %u %u ", type, flags, (unsigned int) len); write_status_text_and_buffer (STATUS_SIG_SUBPACKET, status, buf, len, 0); } /* Print a policy URL. Allowed values for MODE are: * -1 - print to the TTY * 0 - print to stdout. * 1 - use log_info and emit status messages. * 2 - emit only status messages. */ void show_policy_url (PKT_signature * sig, int indent, int mode) { const byte *p; size_t len; int seq = 0, crit; estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; while ((p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_POLICY, &len, &seq, &crit))) { if (mode != 2) { const char *str; tty_fprintf (fp, "%*s", indent, ""); if (crit) str = _("Critical signature policy: "); else str = _("Signature policy: "); if (mode > 0) log_info ("%s", str); else tty_fprintf (fp, "%s", str); tty_print_utf8_string2 (fp, p, len, 0); tty_fprintf (fp, "\n"); } if (mode > 0) write_status_buffer (STATUS_POLICY_URL, p, len, 0); } } /* Print a keyserver URL. Allowed values for MODE are: * -1 - print to the TTY * 0 - print to stdout. * 1 - use log_info and emit status messages. * 2 - emit only status messages. */ void show_keyserver_url (PKT_signature * sig, int indent, int mode) { const byte *p; size_t len; int seq = 0, crit; estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; while ((p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_KS, &len, &seq, &crit))) { if (mode != 2) { const char *str; tty_fprintf (fp, "%*s", indent, ""); if (crit) str = _("Critical preferred keyserver: "); else str = _("Preferred keyserver: "); if (mode > 0) log_info ("%s", str); else tty_fprintf (es_stdout, "%s", str); tty_print_utf8_string2 (fp, p, len, 0); tty_fprintf (fp, "\n"); } if (mode > 0) status_one_subpacket (SIGSUBPKT_PREF_KS, len, (crit ? 0x02 : 0) | 0x01, p); } } /* Print notation data. Allowed values for MODE are: * -1 - print to the TTY * 0 - print to stdout. * 1 - use log_info and emit status messages. * 2 - emit only status messages. * * Defined bits in WHICH: * 1 - standard notations * 2 - user notations */ void show_notation (PKT_signature * sig, int indent, int mode, int which) { estream_t fp = mode < 0? NULL : mode ? log_get_stream () : es_stdout; notation_t nd, notations; if (which == 0) which = 3; notations = sig_to_notation (sig); /* There may be multiple notations in the same sig. */ for (nd = notations; nd; nd = nd->next) { if (mode != 2) { int has_at = !!strchr (nd->name, '@'); if ((which & 1 && !has_at) || (which & 2 && has_at)) { const char *str; tty_fprintf (fp, "%*s", indent, ""); if (nd->flags.critical) str = _("Critical signature notation: "); else str = _("Signature notation: "); if (mode > 0) log_info ("%s", str); else tty_fprintf (es_stdout, "%s", str); /* This is all UTF8 */ tty_print_utf8_string2 (fp, nd->name, strlen (nd->name), 0); tty_fprintf (fp, "="); tty_print_utf8_string2 (fp, nd->value, strlen (nd->value), 0); /* (We need to use log_printf so that the next call to a log function does not insert an extra LF.) */ if (mode > 0) log_printf ("\n"); else tty_fprintf (fp, "\n"); } } if (mode > 0) { write_status_buffer (STATUS_NOTATION_NAME, nd->name, strlen (nd->name), 0); if (nd->flags.critical || nd->flags.human) write_status_text (STATUS_NOTATION_FLAGS, nd->flags.critical && nd->flags.human? "1 1" : nd->flags.critical? "1 0" : "0 1"); write_status_buffer (STATUS_NOTATION_DATA, nd->value, strlen (nd->value), 50); } } free_notation (notations); } static void print_signature_stats (struct keylist_context *s) { if (!s->check_sigs) return; /* Signature checking was not requested. */ /* Better flush stdout so that the stats are always printed after * the output. */ es_fflush (es_stdout); if (s->good_sigs) log_info (ngettext("%d good signature\n", "%d good signatures\n", s->good_sigs), s->good_sigs); if (s->inv_sigs) log_info (ngettext("%d bad signature\n", "%d bad signatures\n", s->inv_sigs), s->inv_sigs); if (s->no_key) log_info (ngettext("%d signature not checked due to a missing key\n", "%d signatures not checked due to missing keys\n", s->no_key), s->no_key); if (s->oth_err) log_info (ngettext("%d signature not checked due to an error\n", "%d signatures not checked due to errors\n", s->oth_err), s->oth_err); } /* List all keys. If SECRET is true only secret keys are listed. If MARK_SECRET is true secret keys are indicated in a public key listing. */ static void list_all (ctrl_t ctrl, int secret, int mark_secret) { KEYDB_HANDLE hd; KBNODE keyblock = NULL; int rc = 0; int any_secret; const char *lastresname, *resname; struct keylist_context listctx; memset (&listctx, 0, sizeof (listctx)); if (opt.check_sigs) listctx.check_sigs = 1; hd = keydb_new (); if (!hd) rc = gpg_error_from_syserror (); else rc = keydb_search_first (hd); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("keydb_search_first failed: %s\n", gpg_strerror (rc)); goto leave; } lastresname = NULL; do { rc = keydb_get_keyblock (hd, &keyblock); if (rc) { if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY) continue; /* Skip legacy keys. */ log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); goto leave; } if (secret || mark_secret) any_secret = !agent_probe_any_secret_key (NULL, keyblock); else any_secret = 0; if (secret && !any_secret) ; /* Secret key listing requested but this isn't one. */ else { if (!opt.with_colons) { resname = keydb_get_resource_name (hd); if (lastresname != resname) { int i; es_fprintf (es_stdout, "%s\n", resname); for (i = strlen (resname); i; i--) es_putc ('-', es_stdout); es_putc ('\n', es_stdout); lastresname = resname; } } merge_keys_and_selfsig (ctrl, keyblock); list_keyblock (ctrl, keyblock, secret, any_secret, opt.fingerprint, &listctx); } release_kbnode (keyblock); keyblock = NULL; } while (!(rc = keydb_search_next (hd))); es_fflush (es_stdout); if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); if (keydb_get_skipped_counter (hd)) log_info (ngettext("Warning: %lu key skipped due to its large size\n", "Warning: %lu keys skipped due to their large sizes\n", keydb_get_skipped_counter (hd)), keydb_get_skipped_counter (hd)); if (opt.check_sigs && !opt.with_colons) print_signature_stats (&listctx); leave: keylist_context_release (&listctx); release_kbnode (keyblock); keydb_release (hd); } static void list_one (ctrl_t ctrl, strlist_t names, int secret, int mark_secret) { int rc = 0; KBNODE keyblock = NULL; GETKEY_CTX ctx; const char *resname; const char *keyring_str = _("Keyring"); int i; struct keylist_context listctx; memset (&listctx, 0, sizeof (listctx)); if (!secret && opt.check_sigs) listctx.check_sigs = 1; /* fixme: using the bynames function has the disadvantage that we - * don't know wether one of the names given was not found. OTOH, + * don't know whether one of the names given was not found. OTOH, * this function has the advantage to list the names in the * sequence as defined by the keyDB and does not duplicate * outputs. A solution could be do test whether all given have * been listed (this needs a way to use the keyDB search * functions) or to have the search function return indicators for * found names. Yet another way is to use the keydb search * facilities directly. */ rc = getkey_bynames (ctrl, &ctx, NULL, names, secret, &keyblock); if (rc) { log_error ("error reading key: %s\n", gpg_strerror (rc)); getkey_end (ctrl, ctx); return; } do { if ((opt.list_options & LIST_SHOW_KEYRING) && !opt.with_colons) { resname = keydb_get_resource_name (get_ctx_handle (ctx)); es_fprintf (es_stdout, "%s: %s\n", keyring_str, resname); for (i = strlen (resname) + strlen (keyring_str) + 2; i; i--) es_putc ('-', es_stdout); es_putc ('\n', es_stdout); } list_keyblock (ctrl, keyblock, secret, mark_secret, opt.fingerprint, &listctx); release_kbnode (keyblock); } while (!getkey_next (ctrl, ctx, NULL, &keyblock)); getkey_end (ctrl, ctx); if (opt.check_sigs && !opt.with_colons) print_signature_stats (&listctx); keylist_context_release (&listctx); } static void locate_one (ctrl_t ctrl, strlist_t names) { int rc = 0; strlist_t sl; GETKEY_CTX ctx = NULL; KBNODE keyblock = NULL; struct keylist_context listctx; memset (&listctx, 0, sizeof (listctx)); if (opt.check_sigs) listctx.check_sigs = 1; for (sl = names; sl; sl = sl->next) { rc = get_best_pubkey_byname (ctrl, &ctx, NULL, sl->d, &keyblock, 1, 0); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NO_PUBKEY) log_error ("error reading key: %s\n", gpg_strerror (rc)); else if (opt.verbose) log_info (_("key \"%s\" not found: %s\n"), sl->d, gpg_strerror (rc)); } else { do { list_keyblock (ctrl, keyblock, 0, 0, opt.fingerprint, &listctx); release_kbnode (keyblock); } while (ctx && !getkey_next (ctrl, ctx, NULL, &keyblock)); getkey_end (ctrl, ctx); ctx = NULL; } } if (opt.check_sigs && !opt.with_colons) print_signature_stats (&listctx); keylist_context_release (&listctx); } static void print_key_data (PKT_public_key * pk) { int n = pk ? pubkey_get_npkey (pk->pubkey_algo) : 0; int i; for (i = 0; i < n; i++) { es_fprintf (es_stdout, "pkd:%d:%u:", i, mpi_get_nbits (pk->pkey[i])); mpi_print (es_stdout, pk->pkey[i], 1); es_putc (':', es_stdout); es_putc ('\n', es_stdout); } } static void print_capabilities (ctrl_t ctrl, PKT_public_key *pk, KBNODE keyblock) { unsigned int use = pk->pubkey_usage; int c_printed = 0; if (use & PUBKEY_USAGE_ENC) es_putc ('e', es_stdout); if (use & PUBKEY_USAGE_SIG) { es_putc ('s', es_stdout); if (pk->flags.primary) { es_putc ('c', es_stdout); /* The PUBKEY_USAGE_CERT flag was introduced later and we used to always print 'c' for a primary key. To avoid any regression here we better track whether we printed 'c' already. */ c_printed = 1; } } if ((use & PUBKEY_USAGE_CERT) && !c_printed) es_putc ('c', es_stdout); if ((use & PUBKEY_USAGE_AUTH)) es_putc ('a', es_stdout); if ((use & PUBKEY_USAGE_UNKNOWN)) es_putc ('?', es_stdout); if (keyblock) { /* Figure out the usable capabilities. */ KBNODE k; int enc = 0, sign = 0, cert = 0, auth = 0, disabled = 0; for (k = keyblock; k; k = k->next) { if (k->pkt->pkttype == PKT_PUBLIC_KEY || k->pkt->pkttype == PKT_PUBLIC_SUBKEY) { pk = k->pkt->pkt.public_key; if (pk->flags.primary) disabled = pk_is_disabled (pk); if (pk->flags.valid && !pk->flags.revoked && !pk->has_expired) { if (pk->pubkey_usage & PUBKEY_USAGE_ENC) enc = 1; if (pk->pubkey_usage & PUBKEY_USAGE_SIG) { sign = 1; if (pk->flags.primary) cert = 1; } if (pk->pubkey_usage & PUBKEY_USAGE_CERT) cert = 1; if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH)) auth = 1; } } } if (enc) es_putc ('E', es_stdout); if (sign) es_putc ('S', es_stdout); if (cert) es_putc ('C', es_stdout); if (auth) es_putc ('A', es_stdout); if (disabled) es_putc ('D', es_stdout); } es_putc (':', es_stdout); } /* FLAGS: 0x01 hashed 0x02 critical */ static void print_one_subpacket (sigsubpkttype_t type, size_t len, int flags, const byte * buf) { size_t i; es_fprintf (es_stdout, "spk:%d:%u:%u:", type, flags, (unsigned int) len); for (i = 0; i < len; i++) { /* printable ascii other than : and % */ if (buf[i] >= 32 && buf[i] <= 126 && buf[i] != ':' && buf[i] != '%') es_fprintf (es_stdout, "%c", buf[i]); else es_fprintf (es_stdout, "%%%02X", buf[i]); } es_fprintf (es_stdout, "\n"); } void print_subpackets_colon (PKT_signature * sig) { byte *i; log_assert (opt.show_subpackets); for (i = opt.show_subpackets; *i; i++) { const byte *p; size_t len; int seq, crit; seq = 0; while ((p = enum_sig_subpkt (sig->hashed, *i, &len, &seq, &crit))) print_one_subpacket (*i, len, 0x01 | (crit ? 0x02 : 0), p); seq = 0; while ((p = enum_sig_subpkt (sig->unhashed, *i, &len, &seq, &crit))) print_one_subpacket (*i, len, 0x00 | (crit ? 0x02 : 0), p); } } void dump_attribs (const PKT_user_id *uid, PKT_public_key *pk) { int i; if (!attrib_fp) return; for (i = 0; i < uid->numattribs; i++) { if (is_status_enabled ()) { byte array[MAX_FINGERPRINT_LEN], *p; char buf[(MAX_FINGERPRINT_LEN * 2) + 90]; size_t j, n; if (!pk) BUG (); fingerprint_from_pk (pk, array, &n); p = array; for (j = 0; j < n; j++, p++) sprintf (buf + 2 * j, "%02X", *p); sprintf (buf + strlen (buf), " %lu %u %u %u %lu %lu %u", (ulong) uid->attribs[i].len, uid->attribs[i].type, i + 1, uid->numattribs, (ulong) uid->created, (ulong) uid->expiredate, ((uid->flags.primary ? 0x01 : 0) | (uid->flags.revoked ? 0x02 : 0) | (uid->flags.expired ? 0x04 : 0))); write_status_text (STATUS_ATTRIBUTE, buf); } es_fwrite (uid->attribs[i].data, uid->attribs[i].len, 1, attrib_fp); es_fflush (attrib_fp); } } static void list_keyblock_print (ctrl_t ctrl, kbnode_t keyblock, int secret, int fpr, struct keylist_context *listctx) { int rc; KBNODE kbctx; KBNODE node; PKT_public_key *pk; int skip_sigs = 0; char *hexgrip = NULL; char *serialno = NULL; /* Get the keyid from the keyblock. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; key lost!\n"); dump_kbnode (keyblock); return; } pk = node->pkt->pkt.public_key; if (secret || opt.with_keygrip) { rc = hexkeygrip_from_pk (pk, &hexgrip); if (rc) log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); } if (secret) { /* Encode some info about the secret key in SECRET. */ if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) secret = serialno? 3 : 1; else secret = 2; /* Key not found. */ } if (!listctx->no_validity) check_trustdb_stale (ctrl); /* Print the "pub" line and in KF_NONE mode the fingerprint. */ print_key_line (ctrl, es_stdout, pk, secret); if (fpr) print_fingerprint (ctrl, NULL, pk, 0); if (opt.with_keygrip && hexgrip) es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip); if (serialno) print_card_serialno (serialno); if (opt.with_key_data) print_key_data (pk); for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; int indent; int kl = opt.keyid_format == KF_NONE? 10 : keystrlen (); if ((uid->flags.expired || uid->flags.revoked) && !(opt.list_options & LIST_SHOW_UNUSABLE_UIDS)) { skip_sigs = 1; continue; } else skip_sigs = 0; if (attrib_fp && uid->attrib_data != NULL) dump_attribs (uid, pk); if ((uid->flags.revoked || uid->flags.expired) || ((opt.list_options & LIST_SHOW_UID_VALIDITY) && !listctx->no_validity)) { const char *validity; validity = uid_trust_string_fixed (ctrl, pk, uid); indent = ((kl + (opt.legacy_list_mode? 9:11)) - atoi (uid_trust_string_fixed (ctrl, NULL, NULL))); if (indent < 0 || indent > 40) indent = 0; es_fprintf (es_stdout, "uid%*s%s ", indent, "", validity); } else { indent = kl + (opt.legacy_list_mode? 10:12); es_fprintf (es_stdout, "uid%*s", indent, ""); } print_utf8_buffer (es_stdout, uid->name, uid->len); es_putc ('\n', es_stdout); if (opt.with_wkd_hash) { char *mbox, *hash, *p; char hashbuf[32]; mbox = mailbox_from_userid (uid->name); if (mbox && (p = strchr (mbox, '@'))) { *p++ = 0; gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); hash = zb32_encode (hashbuf, 8*20); if (hash) { es_fprintf (es_stdout, " %*s%s@%s\n", indent, "", hash, p); xfree (hash); } } xfree (mbox); } if ((opt.list_options & LIST_SHOW_PHOTOS) && uid->attribs != NULL) show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid); } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { PKT_public_key *pk2 = node->pkt->pkt.public_key; if ((pk2->flags.revoked || pk2->has_expired) && !(opt.list_options & LIST_SHOW_UNUSABLE_SUBKEYS)) { skip_sigs = 1; continue; } else skip_sigs = 0; xfree (serialno); serialno = NULL; xfree (hexgrip); hexgrip = NULL; if (secret || opt.with_keygrip) { rc = hexkeygrip_from_pk (pk2, &hexgrip); if (rc) log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); } if (secret) { if (!agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) secret = serialno? 3 : 1; else secret = 2; /* Key not found. */ } /* Print the "sub" line. */ print_key_line (ctrl, es_stdout, pk2, secret); if (fpr > 1 || opt.with_subkey_fingerprint) { print_fingerprint (ctrl, NULL, pk2, 0); if (serialno) print_card_serialno (serialno); } if (opt.with_keygrip && hexgrip) es_fprintf (es_stdout, " Keygrip = %s\n", hexgrip); if (opt.with_key_data) print_key_data (pk2); } else if (opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE && !skip_sigs) { PKT_signature *sig = node->pkt->pkt.signature; int sigrc; char *sigstr; if (listctx->check_sigs) { rc = check_key_signature (ctrl, keyblock, node, NULL); switch (gpg_err_code (rc)) { case 0: listctx->good_sigs++; sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: listctx->inv_sigs++; sigrc = '-'; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: listctx->no_key++; continue; default: listctx->oth_err++; sigrc = '%'; break; } /* TODO: Make sure a cached sig record here still has the pk that issued it. See also keyedit.c:print_and_check_one_sig */ } else { rc = 0; sigrc = ' '; } if (sig->sig_class == 0x20 || sig->sig_class == 0x28 || sig->sig_class == 0x30) sigstr = "rev"; else if ((sig->sig_class & ~3) == 0x10) sigstr = "sig"; else if (sig->sig_class == 0x18) sigstr = "sig"; else if (sig->sig_class == 0x1F) sigstr = "sig"; else { es_fprintf (es_stdout, "sig " "[unexpected signature class 0x%02x]\n", sig->sig_class); continue; } es_fputs (sigstr, es_stdout); es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s", sigrc, (sig->sig_class - 0x10 > 0 && sig->sig_class - 0x10 < 4) ? '0' + sig->sig_class - 0x10 : ' ', sig->flags.exportable ? ' ' : 'L', sig->flags.revocable ? ' ' : 'R', sig->flags.policy_url ? 'P' : ' ', sig->flags.notation ? 'N' : ' ', sig->flags.expired ? 'X' : ' ', (sig->trust_depth > 9) ? 'T' : (sig->trust_depth > 0) ? '0' + sig->trust_depth : ' ', keystr (sig->keyid), datestr_from_sig (sig)); if (opt.list_options & LIST_SHOW_SIG_EXPIRE) es_fprintf (es_stdout, " %s", expirestr_from_sig (sig)); es_fprintf (es_stdout, " "); if (sigrc == '%') es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc)); else if (sigrc == '?') ; else if (!opt.fast_list_mode) { size_t n; char *p = get_user_id (ctrl, sig->keyid, &n); print_utf8_buffer (es_stdout, p, n); xfree (p); } es_putc ('\n', es_stdout); if (sig->flags.policy_url && (opt.list_options & LIST_SHOW_POLICY_URLS)) show_policy_url (sig, 3, 0); if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS)) show_notation (sig, 3, 0, ((opt. list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) + ((opt. list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0)); if (sig->flags.pref_ks && (opt.list_options & LIST_SHOW_KEYSERVER_URLS)) show_keyserver_url (sig, 3, 0); /* fixme: check or list other sigs here */ } } es_putc ('\n', es_stdout); xfree (serialno); xfree (hexgrip); } void print_revokers (estream_t fp, PKT_public_key * pk) { /* print the revoker record */ if (!pk->revkey && pk->numrevkeys) BUG (); else { int i, j; for (i = 0; i < pk->numrevkeys; i++) { byte *p; es_fprintf (fp, "rvk:::%d::::::", pk->revkey[i].algid); p = pk->revkey[i].fpr; for (j = 0; j < 20; j++, p++) es_fprintf (fp, "%02X", *p); es_fprintf (fp, ":%02x%s:\n", pk->revkey[i].class, (pk->revkey[i].class & 0x40) ? "s" : ""); } } } /* Print the compliance flags to field 18. PK is the public key. * KEYLENGTH is the length of the key in bits and CURVENAME is either * NULL or the name of the curve. The latter two args are here * merely because the caller has already computed them. */ static void print_compliance_flags (PKT_public_key *pk, unsigned int keylength, const char *curvename) { int any = 0; if (pk->version == 5) { es_fputs ("8", es_stdout); any++; } if (gnupg_pk_is_compliant (CO_DE_VS, pk, keylength, curvename)) { es_fputs (any? " 23":"23", es_stdout); any++; } } /* List a key in colon mode. If SECRET is true this is a secret key record (i.e. requested via --list-secret-key). If HAS_SECRET a secret key is available even if SECRET is not set. */ static void list_keyblock_colon (ctrl_t ctrl, kbnode_t keyblock, int secret, int has_secret) { int rc; KBNODE kbctx; KBNODE node; PKT_public_key *pk; u32 keyid[2]; int trustletter = 0; int trustletter_print; int ownertrust_print; int ulti_hack = 0; int i; char *hexgrip_buffer = NULL; const char *hexgrip = NULL; char *serialno = NULL; int stubkey; unsigned int keylength; char *curve = NULL; const char *curvename = NULL; /* Get the keyid from the keyblock. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; key lost!\n"); dump_kbnode (keyblock); return; } pk = node->pkt->pkt.public_key; if (secret || has_secret || opt.with_keygrip || opt.with_key_data) { rc = hexkeygrip_from_pk (pk, &hexgrip_buffer); if (rc) log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); /* In the error case we print an empty string so that we have a * "grp" record for each and subkey - even if it is empty. This * may help to prevent sync problems. */ hexgrip = hexgrip_buffer? hexgrip_buffer : ""; } stubkey = 0; if ((secret || has_secret) && agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) stubkey = 1; /* Key not found. */ keyid_from_pk (pk, keyid); if (!pk->flags.valid) trustletter_print = 'i'; else if (pk->flags.revoked) trustletter_print = 'r'; else if (pk->has_expired) trustletter_print = 'e'; else if (opt.fast_list_mode || opt.no_expensive_trust_checks) trustletter_print = 0; else { trustletter = get_validity_info (ctrl, keyblock, pk, NULL); if (trustletter == 'u') ulti_hack = 1; trustletter_print = trustletter; } if (!opt.fast_list_mode && !opt.no_expensive_trust_checks) ownertrust_print = get_ownertrust_info (ctrl, pk, 0); else ownertrust_print = 0; keylength = nbits_from_pk (pk); es_fputs (secret? "sec:":"pub:", es_stdout); if (trustletter_print) es_putc (trustletter_print, es_stdout); es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s::", keylength, pk->pubkey_algo, (ulong) keyid[0], (ulong) keyid[1], colon_datestr_from_pk (pk), colon_strtime (pk->expiredate)); if (ownertrust_print) es_putc (ownertrust_print, es_stdout); es_putc (':', es_stdout); es_putc (':', es_stdout); es_putc (':', es_stdout); print_capabilities (ctrl, pk, keyblock); es_putc (':', es_stdout); /* End of field 13. */ es_putc (':', es_stdout); /* End of field 14. */ if (secret || has_secret) { if (stubkey) es_putc ('#', es_stdout); else if (serialno) es_fputs (serialno, es_stdout); else if (has_secret) es_putc ('+', es_stdout); } es_putc (':', es_stdout); /* End of field 15. */ es_putc (':', es_stdout); /* End of field 16. */ if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA || pk->pubkey_algo == PUBKEY_ALGO_EDDSA || pk->pubkey_algo == PUBKEY_ALGO_ECDH) { curve = openpgp_oid_to_str (pk->pkey[0]); curvename = openpgp_oid_to_curve (curve, 0); if (!curvename) curvename = curve; es_fputs (curvename, es_stdout); } es_putc (':', es_stdout); /* End of field 17. */ print_compliance_flags (pk, keylength, curvename); es_putc (':', es_stdout); /* End of field 18 (compliance). */ es_putc (':', es_stdout); /* End of field 19 (last_update). */ es_putc (':', es_stdout); /* End of field 20 (origin). */ es_putc ('\n', es_stdout); print_revokers (es_stdout, pk); print_fingerprint (ctrl, NULL, pk, 0); if (hexgrip) es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip); if (opt.with_key_data) print_key_data (pk); for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; int uid_validity; if (attrib_fp && uid->attrib_data != NULL) dump_attribs (uid, pk); if (uid->flags.revoked) uid_validity = 'r'; else if (uid->flags.expired) uid_validity = 'e'; else if (opt.no_expensive_trust_checks) uid_validity = 0; else if (ulti_hack) uid_validity = 'u'; else uid_validity = get_validity_info (ctrl, keyblock, pk, uid); es_fputs (uid->attrib_data? "uat:":"uid:", es_stdout); if (uid_validity) es_putc (uid_validity, es_stdout); es_fputs ("::::", es_stdout); es_fprintf (es_stdout, "%s:", colon_strtime (uid->created)); es_fprintf (es_stdout, "%s:", colon_strtime (uid->expiredate)); namehash_from_uid (uid); for (i = 0; i < 20; i++) es_fprintf (es_stdout, "%02X", uid->namehash[i]); es_fprintf (es_stdout, "::"); if (uid->attrib_data) es_fprintf (es_stdout, "%u %lu", uid->numattribs, uid->attrib_len); else es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL); es_fputs (":::::::::", es_stdout); es_putc (':', es_stdout); /* End of field 19 (last_update). */ es_putc (':', es_stdout); /* End of field 20 (origin). */ es_putc ('\n', es_stdout); #ifdef USE_TOFU if (!uid->attrib_data && opt.with_tofu_info && (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)) { /* Print a "tfs" record. */ tofu_write_tfs_record (ctrl, es_stdout, pk, uid->name); } #endif /*USE_TOFU*/ } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { u32 keyid2[2]; PKT_public_key *pk2; int need_hexgrip = !!hexgrip; pk2 = node->pkt->pkt.public_key; xfree (hexgrip_buffer); hexgrip_buffer = NULL; hexgrip = NULL; xfree (serialno); serialno = NULL; if (need_hexgrip || secret || has_secret || opt.with_keygrip || opt.with_key_data) { rc = hexkeygrip_from_pk (pk2, &hexgrip_buffer); if (rc) log_error ("error computing a keygrip: %s\n", gpg_strerror (rc)); hexgrip = hexgrip_buffer? hexgrip_buffer : ""; } stubkey = 0; if ((secret||has_secret) && agent_get_keyinfo (NULL, hexgrip, &serialno, NULL)) stubkey = 1; /* Key not found. */ keyid_from_pk (pk2, keyid2); es_fputs (secret? "ssb:":"sub:", es_stdout); if (!pk2->flags.valid) es_putc ('i', es_stdout); else if (pk2->flags.revoked) es_putc ('r', es_stdout); else if (pk2->has_expired) es_putc ('e', es_stdout); else if (opt.fast_list_mode || opt.no_expensive_trust_checks) ; else { /* TRUSTLETTER should always be defined here. */ if (trustletter) es_fprintf (es_stdout, "%c", trustletter); } keylength = nbits_from_pk (pk2); es_fprintf (es_stdout, ":%u:%d:%08lX%08lX:%s:%s:::::", keylength, pk2->pubkey_algo, (ulong) keyid2[0], (ulong) keyid2[1], colon_datestr_from_pk (pk2), colon_strtime (pk2->expiredate)); print_capabilities (ctrl, pk2, NULL); es_putc (':', es_stdout); /* End of field 13. */ es_putc (':', es_stdout); /* End of field 14. */ if (secret || has_secret) { if (stubkey) es_putc ('#', es_stdout); else if (serialno) es_fputs (serialno, es_stdout); else if (has_secret) es_putc ('+', es_stdout); } es_putc (':', es_stdout); /* End of field 15. */ es_putc (':', es_stdout); /* End of field 16. */ if (pk2->pubkey_algo == PUBKEY_ALGO_ECDSA || pk2->pubkey_algo == PUBKEY_ALGO_EDDSA || pk2->pubkey_algo == PUBKEY_ALGO_ECDH) { xfree (curve); curve = openpgp_oid_to_str (pk2->pkey[0]); curvename = openpgp_oid_to_curve (curve, 0); if (!curvename) curvename = curve; es_fputs (curvename, es_stdout); } es_putc (':', es_stdout); /* End of field 17. */ print_compliance_flags (pk2, keylength, curvename); es_putc (':', es_stdout); /* End of field 18. */ es_putc ('\n', es_stdout); print_fingerprint (ctrl, NULL, pk2, 0); if (hexgrip) es_fprintf (es_stdout, "grp:::::::::%s:\n", hexgrip); if (opt.with_key_data) print_key_data (pk2); } else if (opt.list_sigs && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; int sigrc, fprokay = 0; char *sigstr; size_t fplen; byte fparray[MAX_FINGERPRINT_LEN]; char *siguid; size_t siguidlen; if (sig->sig_class == 0x20 || sig->sig_class == 0x28 || sig->sig_class == 0x30) sigstr = "rev"; else if ((sig->sig_class & ~3) == 0x10) sigstr = "sig"; else if (sig->sig_class == 0x18) sigstr = "sig"; else if (sig->sig_class == 0x1F) sigstr = "sig"; else { es_fprintf (es_stdout, "sig::::::::::%02x%c:\n", sig->sig_class, sig->flags.exportable ? 'x' : 'l'); continue; } if (opt.check_sigs) { PKT_public_key *signer_pk = NULL; es_fflush (es_stdout); if (opt.no_sig_cache) signer_pk = xmalloc_clear (sizeof (PKT_public_key)); rc = check_key_signature2 (ctrl, keyblock, node, NULL, signer_pk, NULL, NULL, NULL); switch (gpg_err_code (rc)) { case 0: sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break; default: sigrc = '%'; break; } if (opt.no_sig_cache) { if (!rc) { fingerprint_from_pk (signer_pk, fparray, &fplen); fprokay = 1; } free_public_key (signer_pk); } } else { rc = 0; sigrc = ' '; } if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode) siguid = get_user_id (ctrl, sig->keyid, &siguidlen); else { siguid = NULL; siguidlen = 0; } es_fputs (sigstr, es_stdout); es_putc (':', es_stdout); if (sigrc != ' ') es_putc (sigrc, es_stdout); es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:", sig->pubkey_algo, (ulong) sig->keyid[0], (ulong) sig->keyid[1], colon_datestr_from_sig (sig), colon_expirestr_from_sig (sig)); if (sig->trust_depth || sig->trust_value) es_fprintf (es_stdout, "%d %d", sig->trust_depth, sig->trust_value); es_fprintf (es_stdout, ":"); if (sig->trust_regexp) es_write_sanitized (es_stdout, sig->trust_regexp, strlen (sig->trust_regexp), ":", NULL); es_fprintf (es_stdout, ":"); if (sigrc == '%') es_fprintf (es_stdout, "[%s] ", gpg_strerror (rc)); else if (siguid) es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL); es_fprintf (es_stdout, ":%02x%c::", sig->sig_class, sig->flags.exportable ? 'x' : 'l'); if (opt.no_sig_cache && opt.check_sigs && fprokay) { for (i = 0; i < fplen; i++) es_fprintf (es_stdout, "%02X", fparray[i]); } es_fprintf (es_stdout, ":::%d:\n", sig->digest_algo); if (opt.show_subpackets) print_subpackets_colon (sig); /* fixme: check or list other sigs here */ xfree (siguid); } } xfree (curve); xfree (hexgrip_buffer); xfree (serialno); } /* * Reorder the keyblock so that the primary user ID (and not attribute * packet) comes first. Fixme: Replace this by a generic sort * function. */ static void do_reorder_keyblock (KBNODE keyblock, int attr) { KBNODE primary = NULL, primary0 = NULL, primary2 = NULL; KBNODE last, node; for (node = keyblock; node; primary0 = node, node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && ((attr && node->pkt->pkt.user_id->attrib_data) || (!attr && !node->pkt->pkt.user_id->attrib_data)) && node->pkt->pkt.user_id->flags.primary) { primary = primary2 = node; for (node = node->next; node; primary2 = node, node = node->next) { if (node->pkt->pkttype == PKT_USER_ID || node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { break; } } break; } } if (!primary) return; /* No primary key flag found (should not happen). */ for (last = NULL, node = keyblock; node; last = node, node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) break; } log_assert (node); log_assert (last); /* The user ID is never the first packet. */ log_assert (primary0); /* Ditto (this is the node before primary). */ if (node == primary) return; /* Already the first one. */ last->next = primary; primary0->next = primary2->next; primary2->next = node; } void reorder_keyblock (KBNODE keyblock) { do_reorder_keyblock (keyblock, 1); do_reorder_keyblock (keyblock, 0); } static void list_keyblock (ctrl_t ctrl, KBNODE keyblock, int secret, int has_secret, int fpr, struct keylist_context *listctx) { reorder_keyblock (keyblock); if (opt.with_colons) list_keyblock_colon (ctrl, keyblock, secret, has_secret); else list_keyblock_print (ctrl, keyblock, secret, fpr, listctx); if (secret) es_fflush (es_stdout); } /* Public function used by keygen to list a keyblock. If NO_VALIDITY * is set the validity of a key is never shown. */ void list_keyblock_direct (ctrl_t ctrl, kbnode_t keyblock, int secret, int has_secret, int fpr, int no_validity) { struct keylist_context listctx; memset (&listctx, 0, sizeof (listctx)); listctx.no_validity = !!no_validity; list_keyblock (ctrl, keyblock, secret, has_secret, fpr, &listctx); keylist_context_release (&listctx); } /* Print an hex digit in ICAO spelling. */ static void print_icao_hexdigit (estream_t fp, int c) { static const char *list[16] = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Niner", "Alfa", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot" }; tty_fprintf (fp, "%s", list[c&15]); } /* * Function to print the finperprint. * mode 0: as used in key listings, opt.with_colons is honored * 1: print using log_info () * 2: direct use of tty * 3: direct use of tty but only primary key. * 4: direct use of tty but only subkey. * 10: Same as 0 but with_colons etc is ignored. * 20: Same as 0 but using a compact format. * * Modes 1 and 2 will try and print both subkey and primary key * fingerprints. A MODE with bit 7 set is used internally. If * OVERRIDE_FP is not NULL that stream will be used in 0 instead * of es_stdout or instead of the TTY in modes 2 and 3. */ void print_fingerprint (ctrl_t ctrl, estream_t override_fp, PKT_public_key *pk, int mode) { char hexfpr[2*MAX_FINGERPRINT_LEN+1]; char *p; size_t i; estream_t fp; const char *text; int primary = 0; int with_colons = opt.with_colons; int with_icao = opt.with_icao_spelling; int compact = 0; if (mode == 10) { mode = 0; with_colons = 0; with_icao = 0; } else if (mode == 20) { mode = 0; with_colons = 0; compact = 1; } if (!opt.fingerprint && !opt.with_fingerprint && opt.with_subkey_fingerprint) compact = 1; if (pk->main_keyid[0] == pk->keyid[0] && pk->main_keyid[1] == pk->keyid[1]) primary = 1; /* Just to be safe */ if ((mode & 0x80) && !primary) { log_error ("primary key is not really primary!\n"); return; } mode &= ~0x80; if (!primary && (mode == 1 || mode == 2)) { PKT_public_key *primary_pk = xmalloc_clear (sizeof (*primary_pk)); get_pubkey (ctrl, primary_pk, pk->main_keyid); print_fingerprint (ctrl, override_fp, primary_pk, (mode | 0x80)); free_public_key (primary_pk); } if (mode == 1) { fp = log_get_stream (); if (primary) text = _("Primary key fingerprint:"); else text = _(" Subkey fingerprint:"); } else if (mode == 2) { fp = override_fp; /* Use tty or given stream. */ if (primary) /* TRANSLATORS: this should fit into 24 bytes so that the * fingerprint data is properly aligned with the user ID */ text = _(" Primary key fingerprint:"); else text = _(" Subkey fingerprint:"); } else if (mode == 3) { fp = override_fp; /* Use tty or given stream. */ text = _(" Key fingerprint ="); } else if (mode == 4) { fp = override_fp; /* Use tty or given stream. */ text = _(" Subkey fingerprint:"); } else { fp = override_fp? override_fp : es_stdout; if (opt.keyid_format == KF_NONE) { text = " "; /* To indent ICAO spelling. */ compact = 1; } else text = _(" Key fingerprint ="); } hexfingerprint (pk, hexfpr, sizeof hexfpr); if (with_colons && !mode) { es_fprintf (fp, "fpr:::::::::%s:", hexfpr); } else if (compact && !opt.fingerprint && !opt.with_fingerprint) { tty_fprintf (fp, "%*s%s", 6, "", hexfpr); } else { char fmtfpr[MAX_FORMATTED_FINGERPRINT_LEN + 1]; format_hexfingerprint (hexfpr, fmtfpr, sizeof fmtfpr); if (compact) tty_fprintf (fp, "%*s%s", 6, "", fmtfpr); else tty_fprintf (fp, "%s %s", text, fmtfpr); } tty_fprintf (fp, "\n"); if (!with_colons && with_icao) { ; tty_fprintf (fp, "%*s\"", (int)strlen(text)+1, ""); for (i = 0, p = hexfpr; *p; i++, p++) { if (!i) ; else if (!(i%8)) tty_fprintf (fp, "\n%*s ", (int)strlen(text)+1, ""); else if (!(i%4)) tty_fprintf (fp, " "); else tty_fprintf (fp, " "); print_icao_hexdigit (fp, xtoi_1 (p)); } tty_fprintf (fp, "\"\n"); } } /* Print the serial number of an OpenPGP card if available. */ static void print_card_serialno (const char *serialno) { if (!serialno) return; if (opt.with_colons) return; /* Handled elsewhere. */ es_fputs (_(" Card serial no. ="), es_stdout); es_putc (' ', es_stdout); if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12)) { /* This is an OpenPGP card. Print the relevant part. */ /* Example: D2760001240101010001000003470000 */ /* xxxxyyyyyyyy */ es_fprintf (es_stdout, "%.*s %.*s", 4, serialno+16, 8, serialno+20); } else es_fputs (serialno, es_stdout); es_putc ('\n', es_stdout); } /* Print a public or secret (sub)key line. Example: * * pub dsa2048 2007-12-31 [SC] [expires: 2018-12-31] * 80615870F5BAD690333686D0F2AD85AC1E42B367 * * Some global options may result in a different output format. If * SECRET is set, "sec" or "ssb" is used instead of "pub" or "sub" and * depending on the value a flag character is shown: * * 1 := ' ' Regular secret key * 2 := '#' Stub secret key * 3 := '>' Secret key is on a token. */ void print_key_line (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, int secret) { char pkstrbuf[PUBKEY_STRING_SIZE]; tty_fprintf (fp, "%s%c %s", pk->flags.primary? (secret? "sec":"pub") /**/ : (secret? "ssb":"sub"), secret == 2? '#' : secret == 3? '>' : ' ', pubkey_string (pk, pkstrbuf, sizeof pkstrbuf)); if (opt.keyid_format != KF_NONE) tty_fprintf (fp, "/%s", keystr_from_pk (pk)); tty_fprintf (fp, " %s", datestr_from_pk (pk)); if ((opt.list_options & LIST_SHOW_USAGE)) { tty_fprintf (fp, " [%s]", usagestr_from_pk (pk, 0)); } if (pk->flags.revoked) { tty_fprintf (fp, " ["); tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk)); tty_fprintf (fp, "]"); } else if (pk->has_expired) { tty_fprintf (fp, " ["); tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk)); tty_fprintf (fp, "]"); } else if (pk->expiredate) { tty_fprintf (fp, " ["); tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); tty_fprintf (fp, "]"); } #if 0 /* I need to think about this some more. It's easy enough to include, but it looks sort of confusing in the listing... */ if (opt.list_options & LIST_SHOW_VALIDITY) { int validity = get_validity (ctrl, pk, NULL, NULL, 0); tty_fprintf (fp, " [%s]", trust_value_to_string (validity)); } #endif if (pk->pubkey_algo >= 100) tty_fprintf (fp, " [experimental algorithm %d]", pk->pubkey_algo); tty_fprintf (fp, "\n"); /* if the user hasn't explicitly asked for human-readable fingerprints, show compact fpr of primary key: */ if (pk->flags.primary && !opt.fingerprint && !opt.with_fingerprint) print_fingerprint (ctrl, fp, pk, 20); } void set_attrib_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; /* Fixme: Do we need to check for the log stream here? */ if (attrib_fp && attrib_fp != log_get_stream ()) es_fclose (attrib_fp); attrib_fp = NULL; if (fd == -1) return; if (! gnupg_fd_valid (fd)) log_fatal ("attribute-fd is invalid: %s\n", strerror (errno)); #ifdef HAVE_DOSISH_SYSTEM setmode (fd, O_BINARY); #endif if (fd == 1) attrib_fp = es_stdout; else if (fd == 2) attrib_fp = es_stderr; else attrib_fp = es_fdopen (fd, "wb"); if (!attrib_fp) { log_fatal ("can't open fd %d for attribute output: %s\n", fd, strerror (errno)); } last_fd = fd; } diff --git a/g10/openfile.c b/g10/openfile.c index 2257107ad..80d86cf45 100644 --- a/g10/openfile.c +++ b/g10/openfile.c @@ -1,523 +1,523 @@ /* openfile.c * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2009, * 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include "gpg.h" #include "../common/util.h" #include "../common/ttyio.h" #include "options.h" #include "main.h" #include "../common/status.h" #include "../common/i18n.h" #ifdef USE_ONLY_8DOT3 #define SKELEXT ".skl" #else #define SKELEXT EXTSEP_S "skel" #endif #ifdef HAVE_W32_SYSTEM #define NAME_OF_DEV_NULL "nul" #else #define NAME_OF_DEV_NULL "/dev/null" #endif #if defined (HAVE_DRIVE_LETTERS) || defined (__riscos__) #define CMP_FILENAME(a,b) ascii_strcasecmp( (a), (b) ) #else #define CMP_FILENAME(a,b) strcmp( (a), (b) ) #endif /* FIXME: Implement opt.interactive. */ /* * Check whether FNAME exists and ask if it's okay to overwrite an * existing one. * Returns: True: it's okay to overwrite or the file does not exist * False: Do not overwrite */ int overwrite_filep( const char *fname ) { if ( iobuf_is_pipe_filename (fname) ) return 1; /* Writing to stdout is always okay. */ if ( access( fname, F_OK ) ) return 1; /* Does not exist. */ if ( !compare_filenames (fname, NAME_OF_DEV_NULL) ) return 1; /* Does not do any harm. */ if (opt.answer_yes) return 1; if (opt.answer_no || opt.batch) return 0; /* Do not overwrite. */ tty_printf (_("File '%s' exists. "), fname); if (cpr_enabled ()) tty_printf ("\n"); if (cpr_get_answer_is_yes ("openfile.overwrite.okay", _("Overwrite? (y/N) ")) ) return 1; return 0; } /* * Strip known extensions from iname and return a newly allocated * filename. Return NULL if we can't do that. */ char * make_outfile_name (const char *iname) { size_t n; if (iobuf_is_pipe_filename (iname)) return xstrdup ("-"); n = strlen (iname); if (n > 4 && (!CMP_FILENAME(iname+n-4, EXTSEP_S GPGEXT_GPG) || !CMP_FILENAME(iname+n-4, EXTSEP_S "pgp") || !CMP_FILENAME(iname+n-4, EXTSEP_S "sig") || !CMP_FILENAME(iname+n-4, EXTSEP_S "asc"))) { char *buf = xstrdup (iname); buf[n-4] = 0; return buf; } else if (n > 5 && !CMP_FILENAME(iname+n-5, EXTSEP_S "sign")) { char *buf = xstrdup (iname); buf[n-5] = 0; return buf; } log_info (_("%s: unknown suffix\n"), iname); return NULL; } /* Ask for an output filename; use the given one as default. Return NULL if no file has been given or if it is not possible to ask the - user. NAME is the template len which might conatin enbedded Nuls. + user. NAME is the template len which might contain enbedded Nuls. NAMELEN is its actual length. */ char * ask_outfile_name( const char *name, size_t namelen ) { size_t n; const char *s; char *prompt; char *fname; char *defname; if ( opt.batch ) return NULL; defname = name && namelen? make_printable_string (name, namelen, 0) : NULL; s = _("Enter new filename"); n = strlen(s) + (defname?strlen (defname):0) + 10; prompt = xmalloc (n); if (defname) snprintf (prompt, n, "%s [%s]: ", s, defname ); else snprintf (prompt, n, "%s: ", s ); tty_enable_completion(NULL); fname = cpr_get ("openfile.askoutname", prompt ); cpr_kill_prompt (); tty_disable_completion (); xfree (prompt); if ( !*fname ) { xfree (fname); fname = defname; defname = NULL; } xfree (defname); if (fname) trim_spaces (fname); return fname; } /* * Make an output filename for the inputfile INAME. * Returns an IOBUF and an errorcode * Mode 0 = use ".gpg" * 1 = use ".asc" * 2 = use ".sig" * 3 = use ".rev" * * If INP_FD is not -1 the function simply creates an IOBUF for that * file descriptor and ignore INAME and MODE. Note that INP_FD won't * be closed if the returned IOBUF is closed. With RESTRICTEDPERM a * file will be created with mode 700 if possible. */ int open_outfile (int inp_fd, const char *iname, int mode, int restrictedperm, iobuf_t *a) { int rc = 0; *a = NULL; if (inp_fd != -1) { char xname[64]; *a = iobuf_fdopen_nc (inp_fd, "wb"); if (!*a) { rc = gpg_error_from_syserror (); snprintf (xname, sizeof xname, "[fd %d]", inp_fd); log_error (_("can't open '%s': %s\n"), xname, gpg_strerror (rc)); } else if (opt.verbose) { snprintf (xname, sizeof xname, "[fd %d]", inp_fd); log_info (_("writing to '%s'\n"), xname); } } else if (iobuf_is_pipe_filename (iname) && !opt.outfile) { *a = iobuf_create (NULL, 0); if ( !*a ) { rc = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), "[stdout]", strerror(errno) ); } else if ( opt.verbose ) log_info (_("writing to stdout\n")); } else { char *buf = NULL; const char *name; if (opt.dry_run) name = NAME_OF_DEV_NULL; else if (opt.outfile) name = opt.outfile; else { #ifdef USE_ONLY_8DOT3 if (opt.mangle_dos_filenames) { /* It is quite common for DOS systems to have only one dot in a filename. If we have something like this, we simple replace the suffix except in cases where the suffix is larger than 3 characters and not the same as the new one. We don't map the filenames to 8.3 because this is a duty of the file system. */ char *dot; const char *newsfx; newsfx = (mode==1 ? ".asc" : mode==2 ? ".sig" : mode==3 ? ".rev" : ".gpg"); buf = xmalloc (strlen(iname)+4+1); strcpy (buf, iname); dot = strchr (buf, '.' ); if ( dot && dot > buf && dot[1] && strlen(dot) <= 4 && CMP_FILENAME (newsfx, dot) ) strcpy (dot, newsfx); else if (dot && !dot[1]) /* Do not duplicate a dot. */ strcpy (dot, newsfx+1); else strcat (buf, newsfx); } if (!buf) #endif /* USE_ONLY_8DOT3 */ { buf = xstrconcat (iname, (mode==1 ? EXTSEP_S "asc" : mode==2 ? EXTSEP_S "sig" : mode==3 ? EXTSEP_S "rev" : /* */ EXTSEP_S GPGEXT_GPG), NULL); } name = buf; } rc = 0; while ( !overwrite_filep (name) ) { char *tmp = ask_outfile_name (NULL, 0); if ( !tmp || !*tmp ) { xfree (tmp); rc = gpg_error (GPG_ERR_EEXIST); break; } xfree (buf); name = buf = tmp; } if ( !rc ) { if (is_secured_filename (name) ) { *a = NULL; gpg_err_set_errno (EPERM); } else *a = iobuf_create (name, restrictedperm); if (!*a) { rc = gpg_error_from_syserror (); log_error(_("can't create '%s': %s\n"), name, strerror(errno) ); } else if( opt.verbose ) log_info (_("writing to '%s'\n"), name ); } xfree(buf); } if (*a) iobuf_ioctl (*a, IOBUF_IOCTL_NO_CACHE, 1, NULL); return rc; } /* Find a matching data file for the signature file SIGFILENAME and return it as a malloced string. If no matching data file is found, return NULL. */ char * get_matching_datafile (const char *sigfilename) { char *fname = NULL; size_t len; if (iobuf_is_pipe_filename (sigfilename)) return NULL; len = strlen (sigfilename); if (len > 4 && (!strcmp (sigfilename + len - 4, EXTSEP_S "sig") || (len > 5 && !strcmp(sigfilename + len - 5, EXTSEP_S "sign")) || !strcmp(sigfilename + len - 4, EXTSEP_S "asc"))) { fname = xstrdup (sigfilename); fname[len-(fname[len-1]=='n'?5:4)] = 0 ; if (access (fname, R_OK )) { /* Not found or other error. */ xfree (fname); fname = NULL; } } return fname; } /* * Try to open a file without the extension ".sig" or ".asc" * Return NULL if such a file is not available. */ iobuf_t open_sigfile (const char *sigfilename, progress_filter_context_t *pfx) { iobuf_t a = NULL; char *buf; buf = get_matching_datafile (sigfilename); if (buf) { a = iobuf_open (buf); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if (a) log_info (_("assuming signed data in '%s'\n"), buf); if (a && pfx) handle_progress (pfx, a, buf); xfree (buf); } return a; } /**************** * Copy the option file skeleton for NAME to the given directory. * Returns true if the new option file has any option. */ static int copy_options_file (const char *destdir, const char *name) { const char *datadir = gnupg_datadir (); char *fname; FILE *src, *dst; int linefeeds=0; int c; mode_t oldmask; int esc = 0; int any_option = 0; if (opt.dry_run) return 0; fname = xstrconcat (datadir, DIRSEP_S, name, "-conf", SKELEXT, NULL); src = fopen (fname, "r"); if (src && is_secured_file (fileno (src))) { fclose (src); src = NULL; gpg_err_set_errno (EPERM); } if (!src) { log_info (_("can't open '%s': %s\n"), fname, strerror(errno)); xfree(fname); return 0; } xfree (fname); fname = xstrconcat (destdir, DIRSEP_S, name, EXTSEP_S, "conf", NULL); oldmask = umask (077); if (is_secured_filename (fname)) { dst = NULL; gpg_err_set_errno (EPERM); } else dst = fopen( fname, "w" ); umask (oldmask); if (!dst) { log_info (_("can't create '%s': %s\n"), fname, strerror(errno) ); fclose (src); xfree (fname); return 0; } while ((c = getc (src)) != EOF) { if (linefeeds < 3) { if (c == '\n') linefeeds++; } else { putc (c, dst); if (c== '\n') esc = 1; else if (esc == 1) { if (c == ' ' || c == '\t') ; else if (c == '#') esc = 2; else any_option = 1; } } } fclose (dst); fclose (src); log_info (_("new configuration file '%s' created\n"), fname); xfree (fname); return any_option; } void try_make_homedir (const char *fname) { const char *defhome = standard_homedir (); /* 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 do compare only the suffix if we see that the default homedir does start with a tilde. */ if ( opt.dry_run || opt.no_homedir_creation ) return; if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (fname, defhome) ) #else ( *defhome == '~' && (strlen(fname) >= strlen (defhome+1) && !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) )) || (*defhome != '~' && !compare_filenames( fname, defhome ) ) #endif ) { if (gnupg_mkdir (fname, "-rwx")) log_fatal ( _("can't create directory '%s': %s\n"), fname, strerror(errno) ); else if (!opt.quiet ) log_info ( _("directory '%s' created\n"), fname ); /* Note that we also copy a dirmngr.conf file here. This is because gpg is likely the first invoked tool and thus creates the directory. */ copy_options_file (fname, DIRMNGR_NAME); if (copy_options_file (fname, GPG_NAME)) log_info (_("WARNING: options in '%s'" " are not yet active during this run\n"), fname); } } /* Get and if needed create a string with the directory used to store openpgp revocations. */ char * get_openpgp_revocdir (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_OPENPGP_REVOC_DIR, NULL); if (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); } return fname; } diff --git a/g10/packet.h b/g10/packet.h index 3cb1e3b48..a10495c3f 100644 --- a/g10/packet.h +++ b/g10/packet.h @@ -1,910 +1,910 @@ /* packet.h - OpenPGP packet definitions * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, * 2007 Free Software Foundation, Inc. * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. */ #ifndef G10_PACKET_H #define G10_PACKET_H #include "../common/types.h" #include "../common/iobuf.h" #include "../common/strlist.h" #include "dek.h" #include "filter.h" #include "../common/openpgpdefs.h" #include "../common/userids.h" #include "../common/util.h" #define DEBUG_PARSE_PACKET 1 /* Constants to allocate static MPI arrays. */ #define PUBKEY_MAX_NPKEY 5 #define PUBKEY_MAX_NSKEY 7 #define PUBKEY_MAX_NSIG 2 #define PUBKEY_MAX_NENC 2 /* Usage flags */ #define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */ #define PUBKEY_USAGE_ENC GCRY_PK_USAGE_ENCR /* Good for encryption. */ #define PUBKEY_USAGE_CERT GCRY_PK_USAGE_CERT /* Also good to certify keys.*/ #define PUBKEY_USAGE_AUTH GCRY_PK_USAGE_AUTH /* Good for authentication. */ #define PUBKEY_USAGE_UNKNOWN GCRY_PK_USAGE_UNKN /* Unknown usage flag. */ #define PUBKEY_USAGE_NONE 256 /* No usage given. */ #if (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR | GCRY_PK_USAGE_CERT \ | GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256 # error Please choose another value for PUBKEY_USAGE_NONE #endif /* Helper macros. */ #define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \ || (a)==PUBKEY_ALGO_RSA_S ) #define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL_E) #define is_DSA(a) ((a)==PUBKEY_ALGO_DSA) /* A pointer to the packet object. */ typedef struct packet_struct PACKET; /* PKT_GPG_CONTROL types */ typedef enum { CTRLPKT_CLEARSIGN_START = 1, CTRLPKT_PIPEMODE = 2, CTRLPKT_PLAINTEXT_MARK =3 } ctrlpkttype_t; typedef enum { PREFTYPE_NONE = 0, PREFTYPE_SYM = 1, PREFTYPE_HASH = 2, PREFTYPE_ZIP = 3 } preftype_t; typedef struct { byte type; byte value; } prefitem_t; /* A string-to-key specifier as defined in RFC 4880, Section 3.7. */ typedef struct { int mode; /* Must be an integer due to the GNU modes 1001 et al. */ byte hash_algo; byte salt[8]; /* The *coded* (i.e., the serialized version) iteration count. */ u32 count; } STRING2KEY; /* A symmetric-key encrypted session key packet as defined in RFC 4880, Section 5.3. All fields are serialized. */ typedef struct { /* RFC 4880: this must be 4. */ byte version; /* The cipher algorithm used to encrypt the session key. (This may be different from the algorithm that is used to encrypt the SED packet.) */ byte cipher_algo; /* The string-to-key specifier. */ STRING2KEY s2k; /* The length of SESKEY in bytes or 0 if this packet does not encrypt a session key. (In the latter case, the results of the S2K function on the password is the session key. See RFC 4880, Section 5.3.) */ byte seskeylen; /* The session key as encrypted by the S2K specifier. */ byte seskey[1]; } PKT_symkey_enc; /* A public-key encrypted session key packet as defined in RFC 4880, Section 5.1. All fields are serialized. */ typedef struct { /* The 64-bit keyid. */ u32 keyid[2]; /* The packet's version. Currently, only version 3 is defined. */ byte version; /* The algorithm used for the public key encryption scheme. */ byte pubkey_algo; /* Whether to hide the key id. This value is not directly serialized. */ byte throw_keyid; /* The session key. */ gcry_mpi_t data[PUBKEY_MAX_NENC]; } PKT_pubkey_enc; /* A one-pass signature packet as defined in RFC 4880, Section 5.4. All fields are serialized. */ typedef struct { u32 keyid[2]; /* The 64-bit keyid */ /* The signature's classification (RFC 4880, Section 5.2.1). */ byte sig_class; byte digest_algo; /* algorithm used for digest */ byte pubkey_algo; /* algorithm used for public key scheme */ /* A message can be signed by multiple keys. In this case, there are n one-pass signature packets before the message to sign and n signatures packets after the message. It is conceivable that someone wants to not only sign the message, but all of the signatures. Now we need to distinguish between signing the message and signing the message plus the surrounding signatures. This is the point of this flag. If set, it means: I sign all of the data starting at the next packet. */ byte last; } PKT_onepass_sig; /* A v4 OpenPGP signature has a hashed and unhashed area containing co-called signature subpackets (RFC 4880, Section 5.2.3). These areas are described by this data structure. Use enum_sig_subpkt to parse this area. */ typedef struct { size_t size; /* allocated */ size_t len; /* used (serialized) */ byte data[1]; /* the serialized subpackes (serialized) */ } subpktarea_t; /* The in-memory representation of a designated revoker signature subpacket (RFC 4880, Section 5.2.3.15). */ struct revocation_key { /* A bit field. 0x80 must be set. 0x40 means this information is sensitive (and should not be uploaded to a keyserver by default). */ byte class; /* The public-key algorithm ID. */ byte algid; /* The fingerprint of the authorized key. */ byte fpr[MAX_FINGERPRINT_LEN]; }; /* Object to keep information about a PKA DNS record. */ typedef struct { int valid; /* An actual PKA record exists for EMAIL. */ int checked; /* Set to true if the FPR has been checked against the actual key. */ char *uri; /* Malloced string with the URI. NULL if the URI is not available.*/ unsigned char fpr[20]; /* The fingerprint as stored in the PKA RR. */ char email[1];/* The email address from the notation data. */ } pka_info_t; /* A signature packet (RFC 4880, Section 5.2). Only a subset of these fields are directly serialized (these are marked as such); the rest are read from the subpackets, which are not synthesized when serializing this data structure (i.e., when using build_packet()). Instead, the subpackets must be created by hand. */ typedef struct { struct { unsigned checked:1; /* Signature has been checked. */ unsigned valid:1; /* Signature is good (if checked is set). */ unsigned chosen_selfsig:1; /* A selfsig that is the chosen one. */ unsigned unknown_critical:1; unsigned exportable:1; unsigned revocable:1; unsigned policy_url:1; /* At least one policy URL is present */ unsigned notation:1; /* At least one notation is present */ unsigned pref_ks:1; /* At least one preferred keyserver is present */ unsigned expired:1; unsigned pka_tried:1; /* Set if we tried to retrieve the PKA record. */ } flags; /* The key that allegedly generated this signature. (Directly serialized in v3 sigs; for v4 sigs, this must be explicitly added as an issuer subpacket (5.2.3.5.) */ u32 keyid[2]; /* When the signature was made (seconds since the Epoch). (Directly serialized in v3 sigs; for v4 sigs, this must be explicitly added as a signature creation time subpacket (5.2.3.4).) */ u32 timestamp; u32 expiredate; /* Expires at this date or 0 if not at all. */ /* The serialization format used / to use. If 0, then defaults to version 3. (Serialized.) */ byte version; /* The signature type. (See RFC 4880, Section 5.2.1.) */ byte sig_class; /* Algorithm used for public key scheme (e.g., PUBKEY_ALGO_RSA). (Serialized.) */ byte pubkey_algo; /* Algorithm used for digest (e.g., DIGEST_ALGO_SHA1). (Serialized.) */ byte digest_algo; byte trust_depth; byte trust_value; const byte *trust_regexp; struct revocation_key *revkey; int numrevkeys; pka_info_t *pka_info; /* Malloced PKA data or NULL if not available. See also flags.pka_tried. */ char *signers_uid; /* Malloced value of the SIGNERS_UID * subpacket or NULL. This string has * already been sanitized. */ subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */ subpktarea_t *unhashed; /* Ditto for unhashed data. */ /* First 2 bytes of the digest. (Serialized. Note: this is not automatically filled in when serializing a signature!) */ byte digest_start[2]; /* The signature. (Serialized.) */ gcry_mpi_t data[PUBKEY_MAX_NSIG]; /* The message digest and its length (in bytes). Note the maximum digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then the digest's value has not been saved here. */ byte digest[512 / 8]; int digest_len; } PKT_signature; #define ATTRIB_IMAGE 1 /* This is the cooked form of attributes. */ struct user_attribute { byte type; const byte *data; u32 len; }; /* A user id (RFC 4880, Section 5.11) or a user attribute packet (RFC 4880, Section 5.12). Only a subset of these fields are directly serialized (these are marked as such); the rest are read from the self-signatures in merge_keys_and_selfsig()). */ typedef struct { int ref; /* reference counter */ /* The length of NAME. */ int len; struct user_attribute *attribs; int numattribs; /* If this is not NULL, the packet is a user attribute rather than a user id (See RFC 4880 5.12). (Serialized.) */ byte *attrib_data; /* The length of ATTRIB_DATA. */ unsigned long attrib_len; byte *namehash; int help_key_usage; u32 help_key_expire; int help_full_count; int help_marginal_count; u32 expiredate; /* expires at this date or 0 if not at all */ prefitem_t *prefs; /* list of preferences (may be NULL)*/ u32 created; /* according to the self-signature */ u32 keyupdate; /* From the ring trust packet. */ char *updateurl; /* NULL or the URL of the last update origin. */ byte keysrc; /* From the ring trust packet. */ byte selfsigversion; struct { unsigned int mdc:1; unsigned int ks_modify:1; unsigned int compacted:1; unsigned int primary:2; /* 2 if set via the primary flag, 1 if calculated */ unsigned int revoked:1; unsigned int expired:1; } flags; char *mbox; /* NULL or the result of mailbox_from_userid. */ /* The text contained in the user id packet, which is normally the * name and email address of the key holder (See RFC 4880 5.11). * (Serialized.). For convenience an extra Nul is always appended. */ char name[1]; } PKT_user_id; struct revoke_info { /* revoked at this date */ u32 date; /* the keyid of the revoking key (selfsig or designated revoker) */ u32 keyid[2]; /* the algo of the revoking key */ byte algo; }; /* Information pertaining to secret keys. */ struct seckey_info { int is_protected:1; /* The secret info is protected and must */ /* be decrypted before use, the protected */ /* MPIs are simply (void*) pointers to memory */ /* and should never be passed to a mpi_xxx() */ int sha1chk:1; /* SHA1 is used instead of a 16 bit checksum */ u16 csum; /* Checksum for old protection modes. */ byte algo; /* Cipher used to protect the secret information. */ STRING2KEY s2k; /* S2K parameter. */ byte ivlen; /* Used length of the IV. */ byte iv[16]; /* Initialization vector for CFB mode. */ }; /**************** * The in-memory representation of a public key (RFC 4880, Section * 5.5). Note: this structure contains significantly more information * than is contained in an OpenPGP public key packet. This * information is derived from the self-signed signatures (by * merge_keys_and_selfsig()) and is ignored when serializing the * packet. The fields that are actually written out when serializing * this packet are marked as accordingly. * * We assume that secret keys have the same number of parameters as * the public key and that the public parameters are the first items * in the PKEY array. Thus NPKEY is always less than NSKEY and it is * possible to compare the secret and public keys by comparing the * first NPKEY elements of the PKEY array. Note that since GnuPG 2.1 * we don't use secret keys anymore directly because they are managed * by gpg-agent. However for parsing OpenPGP key files we need a way * to temporary store those secret keys. We do this by putting them * into the public key structure and extending the PKEY field to NSKEY * elements; the extra secret key information are stored in the * SECKEY_INFO field. */ typedef struct { /* When the key was created. (Serialized.) */ u32 timestamp; u32 expiredate; /* expires at this date or 0 if not at all */ u32 max_expiredate; /* must not expire past this date */ struct revoke_info revoked; /* An OpenPGP packet consists of a header and a body. This is the size of the header. If this is 0, an appropriate size is automatically chosen based on the size of the body. (Serialized.) */ byte hdrbytes; /* The serialization format. If 0, the default version (4) is used when serializing. (Serialized.) */ byte version; byte selfsigversion; /* highest version of all of the self-sigs */ /* The public key algorithm. (Serialized.) */ byte pubkey_algo; byte pubkey_usage; /* for now only used to pass it to getkey() */ byte req_usage; /* hack to pass a request to getkey() */ u32 has_expired; /* set to the expiration date if expired */ /* keyid of the primary key. Never access this value directly. Instead, use pk_main_keyid(). */ u32 main_keyid[2]; /* keyid of this key. Never access this value directly! Instead, use pk_keyid(). */ u32 keyid[2]; prefitem_t *prefs; /* list of preferences (may be NULL) */ struct { unsigned int mdc:1; /* MDC feature set. */ unsigned int disabled_valid:1;/* The next flag is valid. */ unsigned int disabled:1; /* The key has been disabled. */ unsigned int primary:1; /* This is a primary key. */ unsigned int revoked:2; /* Key has been revoked. 1 = revoked by the owner 2 = revoked by designated revoker. */ unsigned int maybe_revoked:1; /* A designated revocation is present, but without the key to check it. */ unsigned int valid:1; /* Key (especially subkey) is valid. */ unsigned int dont_cache:1; /* Do not cache this key. */ unsigned int backsig:2; /* 0=none, 1=bad, 2=good. */ unsigned int serialno_valid:1;/* SERIALNO below is valid. */ unsigned int exact:1; /* Found via exact (!) search. */ } flags; PKT_user_id *user_id; /* If != NULL: found by that uid. */ struct revocation_key *revkey; int numrevkeys; u32 trust_timestamp; byte trust_depth; byte trust_value; byte keysrc; /* From the ring trust packet. */ u32 keyupdate; /* From the ring trust packet. */ char *updateurl; /* NULL or the URL of the last update origin. */ const byte *trust_regexp; char *serialno; /* Malloced hex string or NULL if it is likely not on a card. See also flags.serialno_valid. */ /* If not NULL this malloced structure describes a secret key. (Serialized.) */ struct seckey_info *seckey_info; /* The public key. Contains pubkey_get_npkey (pubkey_algo) + pubkey_get_nskey (pubkey_algo) MPIs. (If pubkey_get_npkey returns 0, then the algorithm is not understood and the PKEY contains a single opaque MPI.) (Serialized.) */ gcry_mpi_t pkey[PUBKEY_MAX_NSKEY]; /* Right, NSKEY elements. */ } PKT_public_key; /* Evaluates as true if the pk is disabled, and false if it isn't. If there is no disable value cached, fill one in. */ #define pk_is_disabled(a) \ (((a)->flags.disabled_valid)? \ ((a)->flags.disabled):(cache_disabled_value(ctrl,(a)))) typedef struct { int len; /* length of data */ char data[1]; } PKT_comment; /* A compression packet (RFC 4880, Section 5.6). */ typedef struct { /* Not used. */ u32 len; /* Whether the serialized version of the packet used / should use the new format. */ byte new_ctb; /* The compression algorithm. */ byte algorithm; /* An iobuf holding the data to be decompressed. (This is not used for compression!) */ iobuf_t buf; } PKT_compressed; /* A symmetrically encrypted data packet (RFC 4880, Section 5.7) or a symmetrically encrypted integrity protected data packet (Section 5.13) */ typedef struct { /* Remaining length of encrypted data. */ u32 len; /* When encrypting, the first block size bytes of data are random data and the following 2 bytes are copies of the last two bytes of the random data (RFC 4880, Section 5.7). This provides a simple check that the key is correct. extralen is the size of this extra data. This is used by build_packet when writing out the packet's header. */ int extralen; /* Whether the serialized version of the packet used / should use the new format. */ byte new_ctb; /* Whether the packet has an indeterminate length (old format) or was encoded using partial body length headers (new format). Note: this is ignored when encrypting. */ byte is_partial; /* If 0, MDC is disabled. Otherwise, the MDC method that was used (currently, only DIGEST_ALGO_SHA1 is supported). */ byte mdc_method; /* An iobuf holding the data to be decrypted. (This is not used for encryption!) */ iobuf_t buf; } PKT_encrypted; typedef struct { byte hash[20]; } PKT_mdc; /* Subtypes for the ring trust packet. */ #define RING_TRUST_SIG 0 /* The classical signature cache. */ #define RING_TRUST_KEY 1 /* A KEYSRC on a primary key. */ #define RING_TRUST_UID 2 /* A KEYSRC on a user id. */ /* The local only ring trust packet which OpenPGP declares as * implementation defined. GnuPG uses this to cache signature * verification status and since 2.1.18 also to convey information * about the origin of a key. Note that this packet is not part - * struct packet_struct becuase we use it only local in the packet + * struct packet_struct because we use it only local in the packet * parser and builder. */ typedef struct { unsigned int trustval; unsigned int sigcache; unsigned char subtype; /* The subtype of this ring trust packet. */ unsigned char keysrc; /* The origin of the key (KEYSRC_*). */ u32 keyupdate; /* The wall time the key was last updated. */ char *url; /* NULL or the URL of the source. */ } PKT_ring_trust; /* A plaintext packet (see RFC 4880, 5.9). */ typedef struct { /* The length of data in BUF or 0 if unknown. */ u32 len; /* A buffer containing the data stored in the packet's body. */ iobuf_t buf; byte new_ctb; byte is_partial; /* partial length encoded */ /* The data's formatting. This is either 'b', 't', 'u', 'l' or '1' (however, the last two are deprecated). */ int mode; u32 timestamp; /* The name of the file. This can be at most 255 characters long, since namelen is just a byte in the serialized format. */ int namelen; char name[1]; } PKT_plaintext; typedef struct { int control; size_t datalen; char data[1]; } PKT_gpg_control; /* combine all packets into a union */ struct packet_struct { pkttype_t pkttype; union { void *generic; PKT_symkey_enc *symkey_enc; /* PKT_SYMKEY_ENC */ PKT_pubkey_enc *pubkey_enc; /* PKT_PUBKEY_ENC */ PKT_onepass_sig *onepass_sig; /* PKT_ONEPASS_SIG */ PKT_signature *signature; /* PKT_SIGNATURE */ PKT_public_key *public_key; /* PKT_PUBLIC_[SUB]KEY */ PKT_public_key *secret_key; /* PKT_SECRET_[SUB]KEY */ PKT_comment *comment; /* PKT_COMMENT */ PKT_user_id *user_id; /* PKT_USER_ID */ PKT_compressed *compressed; /* PKT_COMPRESSED */ PKT_encrypted *encrypted; /* PKT_ENCRYPTED[_MDC] */ PKT_mdc *mdc; /* PKT_MDC */ PKT_plaintext *plaintext; /* PKT_PLAINTEXT */ PKT_gpg_control *gpg_control; /* PKT_GPG_CONTROL */ } pkt; }; #define init_packet(a) do { (a)->pkttype = 0; \ (a)->pkt.generic = NULL; \ } while(0) /* A notation. See RFC 4880, Section 5.2.3.16. */ struct notation { /* The notation's name. */ char *name; /* If the notation is human readable, then the value is stored here as a NUL-terminated string. If it is not human readable a human readable approximation of the binary value _may_ be stored here. */ char *value; /* Sometimes we want to %-expand the value. In these cases, we save that transformed value here. */ char *altvalue; /* If the notation is not human readable, then the value is stored here. */ unsigned char *bdat; /* The amount of data stored in BDAT. Note: if this is 0 and BDAT is NULL, this does not necessarily mean that the value is human readable. It could be that we have a 0-length value. To determine whether the notation is human readable, always check if VALUE is not NULL. This works, because if a human-readable value has a length of 0, we will still allocate space for the NUL byte. */ size_t blen; struct { /* The notation is critical. */ unsigned int critical:1; /* The notation is human readable. */ unsigned int human:1; /* The notation should be deleted. */ unsigned int ignore:1; } flags; /* A field to facilitate creating a list of notations. */ struct notation *next; }; typedef struct notation *notation_t; /*-- mainproc.c --*/ void reset_literals_seen(void); int proc_packets (ctrl_t ctrl, void *ctx, iobuf_t a ); int proc_signature_packets (ctrl_t ctrl, void *ctx, iobuf_t a, strlist_t signedfiles, const char *sigfile ); int proc_signature_packets_by_fd (ctrl_t ctrl, void *anchor, IOBUF a, int signed_data_fd ); int proc_encryption_packets (ctrl_t ctrl, void *ctx, iobuf_t a); int list_packets( iobuf_t a ); /*-- parse-packet.c --*/ /* Sets the packet list mode to MODE (i.e., whether we are dumping a packet or not). Returns the current mode. This allows for temporarily suspending dumping by doing the following: int saved_mode = set_packet_list_mode (0); ... set_packet_list_mode (saved_mode); */ int set_packet_list_mode( int mode ); /* A context used with parse_packet. */ struct parse_packet_ctx_s { iobuf_t inp; /* The input stream with the packets. */ struct packet_struct last_pkt; /* The last parsed packet. */ int free_last_pkt; /* Indicates that LAST_PKT must be freed. */ int skip_meta; /* Skip right trust packets. */ }; typedef struct parse_packet_ctx_s *parse_packet_ctx_t; #define init_parse_packet(a,i) do { \ (a)->inp = (i); \ (a)->last_pkt.pkttype = 0; \ (a)->last_pkt.pkt.generic= NULL;\ (a)->free_last_pkt = 0; \ (a)->skip_meta = 0; \ } while (0) #define deinit_parse_packet(a) do { \ if ((a)->free_last_pkt) \ free_packet (NULL, (a)); \ } while (0) #if DEBUG_PARSE_PACKET /* There are debug functions and should not be used directly. */ int dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt, off_t *retpos, int with_uid, const char* file, int lineno ); int dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *ret_pkt, const char *file, int lineno); int dbg_copy_all_packets( iobuf_t inp, iobuf_t out, const char* file, int lineno ); int dbg_copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff, const char* file, int lineno ); int dbg_skip_some_packets( iobuf_t inp, unsigned n, const char* file, int lineno ); #define search_packet( a,b,c,d ) \ dbg_search_packet( (a), (b), (c), (d), __FILE__, __LINE__ ) #define parse_packet( a, b ) \ dbg_parse_packet( (a), (b), __FILE__, __LINE__ ) #define copy_all_packets( a,b ) \ dbg_copy_all_packets((a),(b), __FILE__, __LINE__ ) #define copy_some_packets( a,b,c ) \ dbg_copy_some_packets((a),(b),(c), __FILE__, __LINE__ ) #define skip_some_packets( a,b ) \ dbg_skip_some_packets((a),(b), __FILE__, __LINE__ ) #else /* Return the next valid OpenPGP packet in *PKT. (This function will * skip any packets whose type is 0.) CTX must have been setup prior to * calling this function. * * Returns 0 on success, -1 if EOF is reached, and an error code * otherwise. In the case of an error, the packet in *PKT may be * partially constructed. As such, even if there is an error, it is * necessary to free *PKT to avoid a resource leak. To detect what * has been allocated, clear *PKT before calling this function. */ int parse_packet (parse_packet_ctx_t ctx, PACKET *pkt); /* Return the first OpenPGP packet in *PKT that contains a key (either * a public subkey, a public key, a secret subkey or a secret key) or, * if WITH_UID is set, a user id. * * Saves the position in the pipeline of the start of the returned * packet (according to iobuf_tell) in RETPOS, if it is not NULL. * * The return semantics are the same as parse_packet. */ int search_packet (parse_packet_ctx_t ctx, PACKET *pkt, off_t *retpos, int with_uid); /* Copy all packets (except invalid packets, i.e., those with a type * of 0) from INP to OUT until either an error occurs or EOF is * reached. * * Returns -1 when end of file is reached or an error code, if an * error occurred. (Note: this function never returns 0, because it * effectively keeps going until it gets an EOF.) */ int copy_all_packets (iobuf_t inp, iobuf_t out ); /* Like copy_all_packets, but stops at the first packet that starts at * or after STOPOFF (as indicated by iobuf_tell). * * Example: if STOPOFF is 100, the first packet in INP goes from * 0 to 110 and the next packet starts at offset 111, then the packet * starting at offset 0 will be completely processed (even though it * extends beyond STOPOFF) and the packet starting at offset 111 will * not be processed at all. */ int copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff); /* Skips the next N packets from INP. * * If parsing a packet returns an error code, then the function stops * immediately and returns the error code. Note: in the case of an * error, this function does not indicate how many packets were * successfully processed. */ int skip_some_packets (iobuf_t inp, unsigned int n); #endif /* Parse a signature packet and store it in *SIG. The signature packet is read from INP. The OpenPGP header (the tag and the packet's length) have already been read; the next byte read from INP should be the first byte of the packet's contents. The packet's type (as extract from the tag) must be passed as PKTTYPE and the packet's length must be passed as PKTLEN. This is used as the upper bound on the amount of data read from INP. If the packet is shorter than PKTLEN, the data at the end will be silently skipped. If an error occurs, an error code will be returned. -1 means the EOF was encountered. 0 means parsing was successful. */ int parse_signature( iobuf_t inp, int pkttype, unsigned long pktlen, PKT_signature *sig ); /* Given a subpacket area (typically either PKT_signature.hashed or PKT_signature.unhashed), either: - test whether there are any subpackets with the critical bit set that we don't understand, - list the subpackets, or, - find a subpacket with a specific type. REQTYPE indicates the type of operation. If REQTYPE is SIGSUBPKT_TEST_CRITICAL, then this function checks whether there are any subpackets that have the critical bit and which GnuPG cannot handle. If GnuPG understands all subpackets whose critical bit is set, then this function returns simply returns SUBPKTS. If there is a subpacket whose critical bit is set and which GnuPG does not understand, then this function returns NULL and, if START is not NULL, sets *START to the 1-based index of the subpacket that violates the constraint. If REQTYPE is SIGSUBPKT_LIST_HASHED or SIGSUBPKT_LIST_UNHASHED, the packets are dumped. Note: if REQTYPE is SIGSUBPKT_LIST_HASHED, this function does not check whether the hash is correct; this is merely an indication of the section that the subpackets came from. If REQTYPE is anything else, then this function interprets the values as a subpacket type and looks for the first subpacket with that type. If such a packet is found, *CRITICAL (if not NULL) is set if the critical bit was set, *RET_N is set to the offset of the subpacket's content within the SUBPKTS buffer, *START is set to the 1-based index of the subpacket within the buffer, and returns &SUBPKTS[*RET_N]. *START is the number of initial subpackets to not consider. Thus, if *START is 2, then the first 2 subpackets are ignored. */ const byte *enum_sig_subpkt ( const subpktarea_t *subpkts, sigsubpkttype_t reqtype, size_t *ret_n, int *start, int *critical ); /* Shorthand for: enum_sig_subpkt (buffer, reqtype, ret_n, NULL, NULL); */ const byte *parse_sig_subpkt ( const subpktarea_t *buffer, sigsubpkttype_t reqtype, size_t *ret_n ); /* This calls parse_sig_subpkt first on the hashed signature area in SIG and then, if that returns NULL, calls parse_sig_subpkt on the unhashed subpacket area in SIG. */ const byte *parse_sig_subpkt2 ( PKT_signature *sig, sigsubpkttype_t reqtype); /* Returns whether the N byte large buffer BUFFER is sufficient to hold a subpacket of type TYPE. Note: the buffer refers to the contents of the subpacket (not the header) and it must already be initialized: for some subpackets, it checks some internal constraints. Returns 0 if the size is acceptable. Returns -2 if the buffer is definitely too short. To check for an error, check whether the return value is less than 0. */ int parse_one_sig_subpkt( const byte *buffer, size_t n, int type ); /* Looks for revocation key subpackets (see RFC 4880 5.2.3.15) in the hashed area of the signature packet. Any that are found are added to SIG->REVKEY and SIG->NUMREVKEYS is updated appropriately. */ void parse_revkeys(PKT_signature *sig); /* Extract the attributes from the buffer at UID->ATTRIB_DATA and update UID->ATTRIBS and UID->NUMATTRIBS accordingly. */ int parse_attribute_subpkts(PKT_user_id *uid); /* Set the UID->NAME field according to the attributes. MAX_NAMELEN must be at least 71. */ void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen); /* Allocate and initialize a new GPG control packet. DATA is the data to save in the packet. */ PACKET *create_gpg_control ( ctrlpkttype_t type, const byte *data, size_t datalen ); /*-- build-packet.c --*/ int build_packet (iobuf_t out, PACKET *pkt); gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt); gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a); gpg_error_t gpg_mpi_write_nohdr (iobuf_t out, gcry_mpi_t a); u32 calc_packet_length( PACKET *pkt ); void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type, const byte *buffer, size_t buflen ); void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk); int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type ); void build_attribute_subpkt(PKT_user_id *uid,byte type, const void *buf,u32 buflen, const void *header,u32 headerlen); struct notation *string_to_notation(const char *string,int is_utf8); struct notation *blob_to_notation(const char *name, const char *data, size_t len); struct notation *sig_to_notation(PKT_signature *sig); void free_notation(struct notation *notation); /*-- free-packet.c --*/ void free_symkey_enc( PKT_symkey_enc *enc ); void free_pubkey_enc( PKT_pubkey_enc *enc ); void free_seckey_enc( PKT_signature *enc ); void release_public_key_parts( PKT_public_key *pk ); void free_public_key( PKT_public_key *key ); void free_attributes(PKT_user_id *uid); void free_user_id( PKT_user_id *uid ); void free_comment( PKT_comment *rem ); void free_packet (PACKET *pkt, parse_packet_ctx_t parsectx); prefitem_t *copy_prefs (const prefitem_t *prefs); PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s ); PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s ); PKT_user_id *scopy_user_id (PKT_user_id *sd ); int cmp_public_keys( PKT_public_key *a, PKT_public_key *b ); int cmp_signatures( PKT_signature *a, PKT_signature *b ); int cmp_user_ids( PKT_user_id *a, PKT_user_id *b ); /*-- sig-check.c --*/ /* Check a signature. This is shorthand for check_signature2 with the unnamed arguments passed as NULL. */ int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest); /* Check a signature. Looks up the public key from the key db. (If * R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a * valid hash context that already includes the signed data. This * function adds the relevant meta-data to the hash before finalizing * it and verifying the signature. */ gpg_error_t check_signature2 (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest, u32 *r_expiredate, int *r_expired, int *r_revoked, PKT_public_key **r_pk); /*-- pubkey-enc.c --*/ gpg_error_t get_session_key (ctrl_t ctrl, PKT_pubkey_enc *k, DEK *dek); gpg_error_t get_override_session_key (DEK *dek, const char *string); /*-- compress.c --*/ int handle_compressed (ctrl_t ctrl, void *ctx, PKT_compressed *cd, int (*callback)(iobuf_t, void *), void *passthru ); /*-- encr-data.c --*/ int decrypt_data (ctrl_t ctrl, void *ctx, PKT_encrypted *ed, DEK *dek ); /*-- plaintext.c --*/ gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen, iobuf_t data, char **fnamep, estream_t *fpp); int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx, int nooutput, int clearsig ); int ask_for_detached_datafile( gcry_md_hd_t md, gcry_md_hd_t md2, const char *inname, int textmode ); /*-- sign.c --*/ int make_keysig_packet (ctrl_t ctrl, PKT_signature **ret_sig, PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *subpk, PKT_public_key *pksk, int sigclass, int digest_algo, u32 timestamp, u32 duration, int (*mksubpkt)(PKT_signature *, void *), void *opaque, const char *cache_nonce); gpg_error_t update_keysig_packet (ctrl_t ctrl, PKT_signature **ret_sig, PKT_signature *orig_sig, PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *subpk, PKT_public_key *pksk, int (*mksubpkt)(PKT_signature *, void *), void *opaque ); /*-- keygen.c --*/ PKT_user_id *generate_user_id (kbnode_t keyblock, const char *uidstr); #endif /*G10_PACKET_H*/ diff --git a/g10/seskey.c b/g10/seskey.c index 861793858..15490179d 100644 --- a/g10/seskey.c +++ b/g10/seskey.c @@ -1,359 +1,359 @@ -/* seskey.c - make sesssion keys etc. +/* seskey.c - make session keys etc. * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, * 2006, 2009, 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "gpg.h" #include "../common/util.h" #include "options.h" #include "main.h" #include "../common/i18n.h" /* Generate a new session key in *DEK that is appropriate for the * algorithm DEK->ALGO (i.e., ensure that the key is not weak). * * This function overwrites DEK->KEYLEN, DEK->KEY. The rest of the * fields are left as is. */ void make_session_key( DEK *dek ) { gcry_cipher_hd_t chd; int i, rc; dek->keylen = openpgp_cipher_get_algo_keylen (dek->algo); if (openpgp_cipher_open (&chd, dek->algo, GCRY_CIPHER_MODE_CFB, (GCRY_CIPHER_SECURE | (dek->algo >= 100 ? 0 : GCRY_CIPHER_ENABLE_SYNC))) ) BUG(); gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); for (i=0; i < 16; i++ ) { rc = gcry_cipher_setkey (chd, dek->key, dek->keylen); if (!rc) { gcry_cipher_close (chd); return; } if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) BUG(); log_info(_("weak key created - retrying\n") ); /* Renew the session key until we get a non-weak key. */ gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM); } log_fatal (_("cannot avoid weak key for symmetric cipher; " "tried %d times!\n"), i); } /* Encode the session key stored in DEK as an MPI in preparation to * encrypt it with the public key algorithm OPENPGP_PK_ALGO with a key * whose length (the size of the public key) is NBITS. * * On success, returns an MPI, which the caller must free using * gcry_mpi_release(). */ gcry_mpi_t encode_session_key (int openpgp_pk_algo, DEK *dek, unsigned int nbits) { size_t nframe = (nbits+7) / 8; byte *p; byte *frame; int i,n; u16 csum; gcry_mpi_t a; if (DBG_CRYPTO) log_debug ("encode_session_key: encoding %d byte DEK", dek->keylen); csum = 0; for (p = dek->key, i=0; i < dek->keylen; i++) csum += *p++; /* Shortcut for ECDH. It's padding is minimal to simply make the output be a multiple of 8 bytes. */ if (openpgp_pk_algo == PUBKEY_ALGO_ECDH) { /* Pad to 8 byte granulatiry; the padding byte is the number of * padded bytes. * * A DEK(k bytes) CSUM(2 bytes) 0x 0x 0x 0x ... 0x * +---- x times ---+ */ nframe = (( 1 + dek->keylen + 2 /* The value so far is always odd. */ + 7 ) & (~7)); /* alg+key+csum fit and the size is congruent to 8. */ log_assert (!(nframe%8) && nframe > 1 + dek->keylen + 2 ); frame = xmalloc_secure (nframe); n = 0; frame[n++] = dek->algo; memcpy (frame+n, dek->key, dek->keylen); n += dek->keylen; frame[n++] = csum >> 8; frame[n++] = csum; i = nframe - n; /* Number of padded bytes. */ memset (frame+n, i, i); /* Use it as the value of each padded byte. */ log_assert (n+i == nframe); if (DBG_CRYPTO) log_debug ("encode_session_key: " "[%d] %02x %02x %02x ... %02x %02x %02x\n", (int) nframe, frame[0], frame[1], frame[2], frame[nframe-3], frame[nframe-2], frame[nframe-1]); if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, nframe, &nframe)) BUG(); xfree(frame); return a; } /* The current limitation is that we can only use a session key * whose length is a multiple of BITS_PER_MPI_LIMB * I think we can live with that. */ if (dek->keylen + 7 > nframe || !nframe) log_bug ("can't encode a %d bit key in a %d bits frame\n", dek->keylen*8, nbits ); /* We encode the session key according to PKCS#1 v1.5 (see section * 13.1.1 of RFC 4880): * * 0 2 RND(i bytes) 0 A DEK(k bytes) CSUM(2 bytes) * * (But how can we store the leading 0 - the external representaion * of MPIs doesn't allow leading zeroes =:-) * * RND are (at least 1) non-zero random bytes. * A is the cipher algorithm * DEK is the encryption key (session key) length k depends on the * cipher algorithm (20 is used with blowfish160). * CSUM is the 16 bit checksum over the DEK */ frame = xmalloc_secure( nframe ); n = 0; frame[n++] = 0; frame[n++] = 2; /* The number of random bytes are the number of otherwise unused bytes. See diagram above. */ i = nframe - 6 - dek->keylen; log_assert( i > 0 ); p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM); /* Replace zero bytes by new values. */ for (;;) { int j, k; byte *pp; /* Count the zero bytes. */ for (j=k=0; j < i; j++ ) if (!p[j]) k++; if (!k) break; /* Okay: no zero bytes. */ k += k/128 + 3; /* Better get some more. */ pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM); for (j=0; j < i && k ;) { if (!p[j]) p[j] = pp[--k]; if (p[j]) j++; } xfree (pp); } memcpy (frame+n, p, i); xfree (p); n += i; frame[n++] = 0; frame[n++] = dek->algo; memcpy (frame+n, dek->key, dek->keylen ); n += dek->keylen; frame[n++] = csum >>8; frame[n++] = csum; log_assert (n == nframe); if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe)) BUG(); xfree (frame); return a; } static gcry_mpi_t do_encode_md( gcry_md_hd_t md, int algo, size_t len, unsigned nbits, const byte *asn, size_t asnlen ) { size_t nframe = (nbits+7) / 8; byte *frame; int i,n; gcry_mpi_t a; if (len + asnlen + 4 > nframe) { log_error ("can't encode a %d bit MD into a %d bits frame, algo=%d\n", (int)(len*8), (int)nbits, algo); return NULL; } /* We encode the MD in this way: * * 0 1 PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) * * PAD consists of FF bytes. */ frame = gcry_md_is_secure (md)? xmalloc_secure (nframe) : xmalloc (nframe); n = 0; frame[n++] = 0; frame[n++] = 1; /* block type */ i = nframe - len - asnlen -3 ; log_assert( i > 1 ); memset( frame+n, 0xff, i ); n += i; frame[n++] = 0; memcpy( frame+n, asn, asnlen ); n += asnlen; memcpy( frame+n, gcry_md_read (md, algo), len ); n += len; log_assert( n == nframe ); if (gcry_mpi_scan( &a, GCRYMPI_FMT_USG, frame, n, &nframe )) BUG(); xfree(frame); /* Note that PGP before version 2.3 encoded the MD as: * * 0 1 MD(16 bytes) 0 PAD(n bytes) 1 * * The MD is always 16 bytes here because it's always MD5. We do * not support pre-v2.3 signatures, but I'm including this comment * so the information is easily found in the future. */ return a; } /**************** * Encode a message digest into an MPI. * If it's for a DSA signature, make sure that the hash is large * enough to fill up q. If the hash is too big, take the leftmost * bits. */ gcry_mpi_t encode_md_value (PKT_public_key *pk, gcry_md_hd_t md, int hash_algo) { gcry_mpi_t frame; size_t mdlen; log_assert (hash_algo); log_assert (pk); if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA) { /* EdDSA signs data of arbitrary length. Thus no special treatment is required. */ frame = gcry_mpi_set_opaque_copy (NULL, gcry_md_read (md, hash_algo), 8*gcry_md_get_algo_dlen (hash_algo)); } else if (pk->pubkey_algo == PUBKEY_ALGO_DSA || pk->pubkey_algo == PUBKEY_ALGO_ECDSA) { /* It's a DSA signature, so find out the size of q. */ size_t qbits = gcry_mpi_get_nbits (pk->pkey[1]); /* pkey[1] is Q for ECDSA, which is an uncompressed point, i.e. 04 <x> <y> */ if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA) qbits = ecdsa_qbits_from_Q (qbits); /* Make sure it is a multiple of 8 bits. */ if ((qbits%8)) { log_error(_("DSA requires the hash length to be a" " multiple of 8 bits\n")); return NULL; } /* Don't allow any q smaller than 160 bits. This might need a revisit as the DSA2 design firms up, but for now, we don't want someone to issue signatures from a key with a 16-bit q or something like that, which would look correct but allow trivial forgeries. Yes, I know this rules out using MD5 with DSA. ;) */ if (qbits < 160) { log_error (_("%s key %s uses an unsafe (%zu bit) hash\n"), openpgp_pk_algo_name (pk->pubkey_algo), keystr_from_pk (pk), qbits); return NULL; } /* ECDSA 521 is special has it is larger than the largest hash - we have (SHA-512). Thus we chnage the size for further + we have (SHA-512). Thus we change the size for further processing to 512. */ if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA && qbits > 512) qbits = 512; /* Check if we're too short. Too long is safe as we'll automatically left-truncate. */ mdlen = gcry_md_get_algo_dlen (hash_algo); if (mdlen < qbits/8) { log_error (_("%s key %s requires a %zu bit or larger hash " "(hash is %s)\n"), openpgp_pk_algo_name (pk->pubkey_algo), keystr_from_pk (pk), qbits, gcry_md_algo_name (hash_algo)); return NULL; } /* Note that we do the truncation by passing QBITS/8 as length to mpi_scan. */ if (gcry_mpi_scan (&frame, GCRYMPI_FMT_USG, gcry_md_read (md, hash_algo), qbits/8, NULL)) BUG(); } else { gpg_error_t rc; byte *asn; size_t asnlen; rc = gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, NULL, &asnlen); if (rc) log_fatal ("can't get OID of digest algorithm %d: %s\n", hash_algo, gpg_strerror (rc)); asn = xtrymalloc (asnlen); if (!asn) return NULL; if ( gcry_md_algo_info (hash_algo, GCRYCTL_GET_ASNOID, asn, &asnlen) ) BUG(); frame = do_encode_md (md, hash_algo, gcry_md_get_algo_dlen (hash_algo), gcry_mpi_get_nbits (pk->pkey[0]), asn, asnlen); xfree (asn); } return frame; } diff --git a/g10/tdbio.c b/g10/tdbio.c index c780789f7..7572b9aeb 100644 --- a/g10/tdbio.c +++ b/g10/tdbio.c @@ -1,1870 +1,1870 @@ /* tdbio.c - trust database I/O operations * Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc. * Copyright (C) 1998-2015 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "../common/util.h" #include "options.h" #include "main.h" #include "../common/i18n.h" #include "trustdb.h" #include "tdbio.h" #if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate) #define ftruncate chsize #endif #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #else #define MY_O_BINARY 0 #endif /* We use ERRNO despite that the cegcc provided open/read/write functions don't set ERRNO - at least show that ERRNO does not make sense. */ #ifdef HAVE_W32CE_SYSTEM #undef strerror #define strerror(a) ("[errno not available]") #endif /* * Yes, this is a very simple implementation. We should really * use a page aligned buffer and read complete pages. * To implement a simple trannsaction system, this is sufficient. */ typedef struct cache_ctrl_struct *CACHE_CTRL; struct cache_ctrl_struct { CACHE_CTRL next; struct { unsigned used:1; unsigned dirty:1; } flags; ulong recno; char data[TRUST_RECORD_LEN]; }; /* Size of the cache. The SOFT value is the general one. While in a transaction this may not be sufficient and thus we may increase it then up to the HARD limit. */ #define MAX_CACHE_ENTRIES_SOFT 200 #define MAX_CACHE_ENTRIES_HARD 10000 /* The cache is controlled by these variables. */ static CACHE_CTRL cache_list; static int cache_entries; static int cache_is_dirty; /* An object to pass information to cmp_krec_fpr. */ struct cmp_krec_fpr_struct { int pubkey_algo; const char *fpr; int fprlen; }; /* An object used to pass information to cmp_[s]dir. */ struct cmp_xdir_struct { int pubkey_algo; u32 keyid[2]; }; /* The name of the trustdb file. */ static char *db_name; /* The handle for locking the trustdb file and a flag to record whether a lock has been taken. */ static dotlock_t lockhandle; static int is_locked; /* The file descriptor of the trustdb. */ static int db_fd = -1; /* A flag indicating that a transaction is active. */ static int in_transaction; static void open_db (void); static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type); /* * Take a lock on the trustdb file name. I a lock file can't be * created the function terminates the process. Excvept for a * different return code the function does nothing if the lock has * already been taken. * * Returns: True if lock already exists, False if the lock has * actually been taken. */ static int take_write_lock (void) { if (!lockhandle) lockhandle = dotlock_create (db_name, 0); if (!lockhandle) log_fatal ( _("can't create lock for '%s'\n"), db_name ); if (!is_locked) { if (dotlock_take (lockhandle, -1) ) log_fatal ( _("can't lock '%s'\n"), db_name ); else is_locked = 1; return 0; } else return 1; } /* * Release a lock from the trustdb file unless the global option * --lock-once has been used. */ static void release_write_lock (void) { if (!opt.lock_once) if (!dotlock_release (lockhandle)) is_locked = 0; } /************************************* ************* record cache ********** *************************************/ /* * Get the data from the record cache and return a pointer into that * cache. Caller should copy the returned data. NULL is returned on * a cache miss. */ static const char * get_record_from_cache (ulong recno) { CACHE_CTRL r; for (r = cache_list; r; r = r->next) { if (r->flags.used && r->recno == recno) return r->data; } return NULL; } /* * Write a cached item back to the trustdb file. * * Returns: 0 on success or an error code. */ static int write_cache_item (CACHE_CTRL r) { gpg_error_t err; int n; if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), r->recno, strerror (errno)); return err; } n = write (db_fd, r->data, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), r->recno, n, strerror (errno) ); return err; } r->flags.dirty = 0; return 0; } /* * Put data into the cache. This function may flush * some cache entries if the cache is filled up. * * Returns: 0 on success or an error code. */ static int put_record_into_cache (ulong recno, const char *data) { CACHE_CTRL r, unused; int dirty_count = 0; int clean_count = 0; /* See whether we already cached this one. */ for (unused = NULL, r = cache_list; r; r = r->next) { if (!r->flags.used) { if (!unused) unused = r; } else if (r->recno == recno) { if (!r->flags.dirty) { /* Hmmm: should we use a copy and compare? */ if (memcmp (r->data, data, TRUST_RECORD_LEN)) { r->flags.dirty = 1; cache_is_dirty = 1; } } memcpy (r->data, data, TRUST_RECORD_LEN); return 0; } if (r->flags.used) { if (r->flags.dirty) dirty_count++; else clean_count++; } } /* Not in the cache: add a new entry. */ if (unused) { /* Reuse this entry. */ r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* See whether we reached the limit. */ if (cache_entries < MAX_CACHE_ENTRIES_SOFT) { /* No: Put into cache. */ r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Cache is full: discard some clean entries. */ if (clean_count) { int n; /* We discard a third of the clean entries. */ n = clean_count / 3; if (!n) n = 1; for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && !r->flags.dirty) { if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* No clean entries: We have to flush some dirty entries. */ if (in_transaction) { /* But we can't do this while in a transaction. Thus we * increase the cache size instead. */ if (cache_entries < MAX_CACHE_ENTRIES_HARD) { if (opt.debug && !(cache_entries % 100)) log_debug ("increasing tdbio cache size\n"); r = xmalloc (sizeof *r); r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; r->next = cache_list; cache_list = r; cache_is_dirty = 1; cache_entries++; return 0; } /* Hard limit for the cache size reached. */ log_info (_("trustdb transaction too large\n")); return GPG_ERR_RESOURCE_LIMIT; } if (dirty_count) { int n; /* Discard some dirty entries. */ n = dirty_count / 5; if (!n) n = 1; take_write_lock (); for (unused = NULL, r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { int rc; rc = write_cache_item (r); if (rc) return rc; if (!unused) unused = r; r->flags.used = 0; cache_entries--; if (!--n) break; } } release_write_lock (); /* Now put into the cache. */ log_assert (unused); r = unused; r->flags.used = 1; r->recno = recno; memcpy (r->data, data, TRUST_RECORD_LEN); r->flags.dirty = 1; cache_is_dirty = 1; cache_entries++; return 0; } /* We should never reach this. */ BUG(); } /* Return true if the cache is dirty. */ int tdbio_is_dirty() { return cache_is_dirty; } /* * Flush the cache. This cannot be used while in a transaction. */ int tdbio_sync() { CACHE_CTRL r; int did_lock = 0; if( db_fd == -1 ) open_db(); if( in_transaction ) log_bug("tdbio: syncing while in transaction\n"); if( !cache_is_dirty ) return 0; if (!take_write_lock ()) did_lock = 1; for( r = cache_list; r; r = r->next ) { if( r->flags.used && r->flags.dirty ) { int rc = write_cache_item( r ); if( rc ) return rc; } } cache_is_dirty = 0; if (did_lock) release_write_lock (); return 0; } #if 0 /* Not yet used. */ /* * Simple transactions system: * Everything between begin_transaction and end/cancel_transaction * is not immediately written but at the time of end_transaction. * * NOTE: The transaction code is disabled in the 1.2 branch, as it is * not yet used. */ int tdbio_begin_transaction () /* Not yet used. */ { int rc; if (in_transaction) log_bug ("tdbio: nested transactions\n"); /* Flush everything out. */ rc = tdbio_sync(); if (rc) return rc; in_transaction = 1; return 0; } int tdbio_end_transaction () /* Not yet used. */ { int rc; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); take_write_lock (); gnupg_block_all_signals (); in_transaction = 0; rc = tdbio_sync(); gnupg_unblock_all_signals(); release_write_lock (); return rc; } int tdbio_cancel_transaction () /* Not yet used. */ { CACHE_CTRL r; if (!in_transaction) log_bug ("tdbio: no active transaction\n"); /* Remove all dirty marked entries, so that the original ones are * read back the next time. */ if (cache_is_dirty) { for (r = cache_list; r; r = r->next) { if (r->flags.used && r->flags.dirty) { r->flags.used = 0; cache_entries--; } } cache_is_dirty = 0; } in_transaction = 0; return 0; } #endif /* Not yet used. */ /******************************************************** **************** cached I/O functions ****************** ********************************************************/ /* The cleanup handler for this module. */ static void cleanup (void) { if (is_locked) { if (!dotlock_release (lockhandle)) is_locked = 0; } } /* * Update an existing trustdb record. The caller must call * tdbio_sync. * * Returns: 0 on success or an error code. */ int tdbio_update_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; memset (&rec, 0, sizeof rec); rc = tdbio_read_record (0, &rec, RECTYPE_VER); if (!rc) { rec.r.ver.created = make_timestamp(); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; rec.r.ver.trust_model = opt.trust_model; rec.r.ver.min_cert_level = opt.min_cert_level; rc = tdbio_write_record (ctrl, &rec); } return rc; } /* * Create and write the trustdb version record. * * Returns: 0 on success or an error code. */ static int create_version_record (ctrl_t ctrl) { TRUSTREC rec; int rc; memset (&rec, 0, sizeof rec); rec.r.ver.version = 3; rec.r.ver.created = make_timestamp (); rec.r.ver.marginals = opt.marginals_needed; rec.r.ver.completes = opt.completes_needed; rec.r.ver.cert_depth = opt.max_cert_depth; if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC) rec.r.ver.trust_model = opt.trust_model; else rec.r.ver.trust_model = TM_PGP; rec.r.ver.min_cert_level = opt.min_cert_level; rec.rectype = RECTYPE_VER; rec.recnum = 0; rc = tdbio_write_record (ctrl, &rec); if (!rc) tdbio_sync (); if (!rc) create_hashtable (ctrl, &rec, 0); return rc; } /* * Set the file name for the trustdb to NEW_DBNAME and if CREATE is * true create that file. If NEW_DBNAME is NULL a default name is * used, if the it does not contain a path component separator ('/') * the global GnuPG home directory is used. * * Returns: 0 on success or an error code. * * On the first call this function registers an atexit handler. * */ int tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname, int create, int *r_nofile) { char *fname, *p; struct stat statbuf; static int initialized = 0; int save_slash; if (!initialized) { atexit (cleanup); initialized = 1; } *r_nofile = 0; if (!new_dbname) { fname = make_filename (gnupg_homedir (), "trustdb" EXTSEP_S GPGEXT_GPG, NULL); } else if (*new_dbname != DIRSEP_C ) { if (strchr (new_dbname, DIRSEP_C)) fname = make_filename (new_dbname, NULL); else fname = make_filename (gnupg_homedir (), new_dbname, NULL); } else { fname = xstrdup (new_dbname); } xfree (db_name); db_name = fname; /* Quick check for (likely) case where there already is a * trustdb.gpg. This check is not required in theory, but it helps * in practice avoiding costly operations of preparing and taking * the lock. */ if (!stat (fname, &statbuf) && statbuf.st_size > 0) { /* OK, we have the valid trustdb.gpg already. */ return 0; } else if (!create) { *r_nofile = 1; return 0; } /* Here comes: No valid trustdb.gpg AND CREATE==1 */ /* * Make sure the directory exists. This should be done before * acquiring the lock, which assumes the existence of the directory. */ p = strrchr (fname, DIRSEP_C); #if HAVE_W32_SYSTEM { /* Windows may either have a slash or a backslash. Take care of it. */ char *pp = strrchr (fname, '/'); if (!p || pp > p) p = pp; } #endif /*HAVE_W32_SYSTEM*/ log_assert (p); save_slash = *p; *p = 0; if (access (fname, F_OK)) { try_make_homedir (fname); if (access (fname, F_OK)) log_fatal (_("%s: directory does not exist!\n"), fname); } *p = save_slash; take_write_lock (); if (access (fname, R_OK) || stat (fname, &statbuf) || statbuf.st_size == 0) { FILE *fp; TRUSTREC rec; int rc; mode_t oldmask; #ifdef HAVE_W32CE_SYSTEM /* We know how the cegcc implementation of access works ;-). */ if (GetLastError () == ERROR_FILE_NOT_FOUND) gpg_err_set_errno (ENOENT); else gpg_err_set_errno (EIO); #endif /*HAVE_W32CE_SYSTEM*/ if (errno && errno != ENOENT) log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno)); oldmask = umask (077); if (is_secured_filename (fname)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = fopen (fname, "wb"); umask(oldmask); if (!fp) log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno)); fclose (fp); db_fd = open (db_name, O_RDWR | MY_O_BINARY); if (db_fd == -1) log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno)); rc = create_version_record (ctrl); if (rc) log_fatal (_("%s: failed to create version record: %s"), fname, gpg_strerror (rc)); /* Read again to check that we are okay. */ if (tdbio_read_record (0, &rec, RECTYPE_VER)) log_fatal (_("%s: invalid trustdb created\n"), db_name); if (!opt.quiet) log_info (_("%s: trustdb created\n"), db_name); } release_write_lock (); return 0; } /* * Return the full name of the trustdb. */ const char * tdbio_get_dbname () { return db_name; } /* * Open the trustdb. This may only be called if it has not yet been * opened and after a successful call to tdbio_set_dbname. On return * the trustdb handle (DB_FD) is guaranteed to be open. */ static void open_db () { TRUSTREC rec; log_assert( db_fd == -1 ); #ifdef HAVE_W32CE_SYSTEM { DWORD prevrc = 0; wchar_t *wname = utf8_to_wchar (db_name); if (wname) { db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); xfree (wname); } if (db_fd == -1) log_fatal ("can't open '%s': %d, %d\n", db_name, (int)prevrc, (int)GetLastError ()); } #else /*!HAVE_W32CE_SYSTEM*/ db_fd = open (db_name, O_RDWR | MY_O_BINARY ); if (db_fd == -1 && (errno == EACCES #ifdef EROFS || errno == EROFS #endif ) ) { /* Take care of read-only trustdbs. */ db_fd = open (db_name, O_RDONLY | MY_O_BINARY ); if (db_fd != -1 && !opt.quiet) log_info (_("Note: trustdb not writable\n")); } if ( db_fd == -1 ) log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) ); #endif /*!HAVE_W32CE_SYSTEM*/ register_secured_file (db_name); /* Read the version record. */ if (tdbio_read_record (0, &rec, RECTYPE_VER ) ) log_fatal( _("%s: invalid trustdb\n"), db_name ); } /* * Append a new empty hashtable to the trustdb. TYPE gives the type * of the hash table. The only defined type is 0 for a trust hash. * On return the hashtable has been created, written, the version * record update, and the data flushed to the disk. On a fatal error * the function terminates the process. */ static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type) { TRUSTREC rec; off_t offset; ulong recnum; int i, n, rc; offset = lseek (db_fd, 0, SEEK_END); if (offset == -1) log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* This is will never be the first record. */ if (!type) vr->r.ver.trusthashtbl = recnum; /* Now write the records making up the hash table. */ n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD; for (i=0; i < n; i++, recnum++) { memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HTBL; rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to create hashtable: %s\n"), db_name, gpg_strerror (rc)); } /* Update the version record and flush. */ rc = tdbio_write_record (ctrl, vr); if (!rc) rc = tdbio_sync (); if (rc) log_fatal (_("%s: error updating version record: %s\n"), db_name, gpg_strerror (rc)); } /* * Check whether open trustdb matches the global trust options given * for this process. On a read problem the process is terminated. * * Return: 1 for yes, 0 for no. */ int tdbio_db_matches_options() { static int yes_no = -1; if (yes_no == -1) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if( rc ) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); yes_no = vr.r.ver.marginals == opt.marginals_needed && vr.r.ver.completes == opt.completes_needed && vr.r.ver.cert_depth == opt.max_cert_depth && vr.r.ver.trust_model == opt.trust_model && vr.r.ver.min_cert_level == opt.min_cert_level; } return yes_no; } /* * Read and return the trust model identifier from the trustdb. On a * read problem the process is terminated. */ byte tdbio_read_model (void) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); return vr.r.ver.trust_model; } /* * Read and return the nextstamp value from the trustdb. On a read * problem the process is terminated. */ ulong tdbio_read_nextcheck () { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); return vr.r.ver.nextcheck; } /* * Write the STAMP nextstamp timestamp to the trustdb. On a read or * write problem the process is terminated. * * Return: True if the stamp actually changed. */ int tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.nextcheck == stamp) return 0; vr.r.ver.nextcheck = stamp; rc = tdbio_write_record (ctrl, &vr); if (rc) log_fatal (_("%s: error writing version record: %s\n"), db_name, gpg_strerror (rc)); return 1; } /* * Return the record number of the trusthash table or create one if it * does not yet exist. On a read or write problem the process is * terminated. * * Return: record number */ static ulong get_trusthashrec(void) { static ulong trusthashtbl; /* Record number of the trust hashtable. */ if (!trusthashtbl) { TRUSTREC vr; int rc; rc = tdbio_read_record (0, &vr, RECTYPE_VER ); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc) ); trusthashtbl = vr.r.ver.trusthashtbl; } return trusthashtbl; } /* * Update a hashtable in the trustdb. TABLE gives the start of the * table, KEY and KEYLEN are the key, NEWRECNUM is the record number * to insert into the table. * * Return: 0 on success or an error code. */ static int upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum) { TRUSTREC lastrec, rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL); if (rc) { log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) /* Insert a new item into the hash table. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } else if (item != newrecnum) /* Must do an update. */ { lastrec = rec; rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("upd_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */ { /* Check whether the key is already in this list. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == newrecnum) { return 0; /* Okay, already in the list. */ } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else break; /* key is not in the list */ } /* Find the next free entry and put it in. */ for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (!rec.r.hlst.rnum[i]) { /* Empty slot found. */ rec.r.hlst.rnum[i] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } if (rec.r.hlst.next) { /* read the next reord of the list. */ rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("upd_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else { /* Append a new record to the list. */ rec.r.hlst.next = item = tdbio_new_recnum (ctrl); rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error ("upd_hashtable: write hlst failed: %s\n", gpg_strerror (rc)); return rc; } memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = item; rec.r.hlst.rnum[0] = newrecnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("upd_hashtable: write ext hlst failed: %s\n", gpg_strerror (rc)); return rc; /* Done. */ } } /* end loop over list slots */ } else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record. */ { if (rec.recnum == newrecnum) { return 0; } item = rec.recnum; /* Save number of key record. */ memset (&rec, 0, sizeof rec); rec.rectype = RECTYPE_HLST; rec.recnum = tdbio_new_recnum (ctrl); rec.r.hlst.rnum[0] = item; /* Old key record */ rec.r.hlst.rnum[1] = newrecnum; /* and new key record */ rc = tdbio_write_record (ctrl, &rec); if (rc) { log_error( "upd_hashtable: write new hlst failed: %s\n", gpg_strerror (rc) ); return rc; } /* Update the hashtable record. */ lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum; rc = tdbio_write_record (ctrl, &lastrec); if (rc) log_error ("upd_hashtable: update htbl failed: %s\n", gpg_strerror (rc)); return rc; /* Ready. */ } else { log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); if (opt.verbose > 1) list_trustdb (ctrl, es_stderr, NULL); return GPG_ERR_TRUSTDB; } } return 0; } /* * Drop an entry from a hashtable. TABLE gives the start of the * table, KEY and KEYLEN are the key. * * Return: 0 on success or an error code. */ static int drop_from_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong recnum) { TRUSTREC rec; ulong hashrec, item; int msb; int level = 0; int rc, i; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL ); if (rc) { log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc)); return rc; } item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return 0; /* Not found - forget about it. */ if (item == recnum) /* Table points direct to the record. */ { rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0; rc = tdbio_write_record (ctrl, &rec); if (rc) log_error ("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } rc = tdbio_read_record (item, &rec, 0); if (rc) { log_error ("drop_from_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if (rec.rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections.\n"); return GPG_ERR_TRUSTDB; } goto next_level; } if (rec.rectype == RECTYPE_HLST) { for (;;) { for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec.r.hlst.rnum[i] == recnum) { rec.r.hlst.rnum[i] = 0; /* Mark as free. */ rc = tdbio_write_record (ctrl, &rec); if (rc) log_error("drop_from_hashtable: write htbl failed: %s\n", gpg_strerror (rc)); return rc; } } if (rec.r.hlst.next) { rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST); if (rc) { log_error ("drop_from_hashtable: read hlst failed: %s\n", gpg_strerror (rc)); return rc; } } else return 0; /* Key not in table. */ } } log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n", table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item); return GPG_ERR_TRUSTDB; } /* * Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return * the result in REC. The return value of CMP() should be True if the * record is the desired one. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ static gpg_error_t lookup_hashtable (ulong table, const byte *key, size_t keylen, int (*cmpfnc)(const void*, const TRUSTREC *), const void *cmpdata, TRUSTREC *rec ) { int rc; ulong hashrec, item; int msb; int level = 0; hashrec = table; next_level: msb = key[level]; hashrec += msb / ITEMS_PER_HTBL_RECORD; rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL); if (rc) { log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) ); return rc; } item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD]; if (!item) return gpg_error (GPG_ERR_NOT_FOUND); rc = tdbio_read_record (item, rec, 0); if (rc) { log_error( "hashtable read failed: %s\n", gpg_strerror (rc) ); return rc; } if (rec->rectype == RECTYPE_HTBL) { hashrec = item; level++; if (level >= keylen) { log_error ("hashtable has invalid indirections\n"); return GPG_ERR_TRUSTDB; } goto next_level; } else if (rec->rectype == RECTYPE_HLST) { for (;;) { int i; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { if (rec->r.hlst.rnum[i]) { TRUSTREC tmp; rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0); if (rc) { log_error ("lookup_hashtable: read item failed: %s\n", gpg_strerror (rc)); return rc; } if ((*cmpfnc)(cmpdata, &tmp)) { *rec = tmp; return 0; } } } if (rec->r.hlst.next) { rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST); if (rc) { log_error ("lookup_hashtable: read hlst failed: %s\n", gpg_strerror (rc) ); return rc; } } else return gpg_error (GPG_ERR_NOT_FOUND); } } if ((*cmpfnc)(cmpdata, rec)) return 0; /* really found */ return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */ } /* * Update the trust hash table TR or create the table if it does not * exist. * * Return: 0 on success or an error code. */ static int update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr) { return upd_hashtable (ctrl, get_trusthashrec (), tr->r.trust.fingerprint, 20, tr->recnum); } /* * Dump the trustdb record REC to stream FP. */ void tdbio_dump_record (TRUSTREC *rec, estream_t fp) { int i; ulong rnum = rec->recnum; es_fprintf (fp, "rec %5lu, ", rnum); switch (rec->rectype) { case 0: es_fprintf (fp, "blank\n"); break; case RECTYPE_VER: es_fprintf (fp, "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n", rec->r.ver.trusthashtbl, rec->r.ver.firstfree, rec->r.ver.marginals, rec->r.ver.completes, rec->r.ver.cert_depth, rec->r.ver.trust_model, rec->r.ver.min_cert_level, rec->r.ver.nextcheck, strtimestamp(rec->r.ver.nextcheck) ); break; case RECTYPE_FREE: es_fprintf (fp, "free, next=%lu\n", rec->r.free.next); break; case RECTYPE_HTBL: es_fprintf (fp, "htbl,"); for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) es_fprintf (fp, " %lu", rec->r.htbl.item[i]); es_putc ('\n', fp); break; case RECTYPE_HLST: es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next); for (i=0; i < ITEMS_PER_HLST_RECORD; i++) es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]); es_putc ('\n', fp); break; case RECTYPE_TRUST: es_fprintf (fp, "trust "); for (i=0; i < 20; i++) es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]); es_fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust, rec->r.trust.depth, rec->r.trust.validlist); break; case RECTYPE_VALID: es_fprintf (fp, "valid "); for (i=0; i < 20; i++) es_fprintf(fp, "%02X", rec->r.valid.namehash[i]); es_fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity, rec->r.valid.next); break; default: es_fprintf (fp, "unknown type %d\n", rec->rectype ); break; } } /* * Read the record with number RECNUM into the structure REC. If * EXPECTED is not 0 reading any other record type will return an * error. * * Return: 0 on success, -1 on EOF, or an error code. */ int tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected) { byte readbuf[TRUST_RECORD_LEN]; const byte *buf, *p; gpg_error_t err = 0; int n, i; if (db_fd == -1) open_db (); buf = get_record_from_cache( recnum ); if (!buf) { if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { err = gpg_error_from_syserror (); log_error (_("trustdb: lseek failed: %s\n"), strerror (errno)); return err; } n = read (db_fd, readbuf, TRUST_RECORD_LEN); if (!n) { return -1; /* eof */ } else if (n != TRUST_RECORD_LEN) { err = gpg_error_from_syserror (); log_error (_("trustdb: read failed (n=%d): %s\n"), n, strerror(errno)); return err; } buf = readbuf; } rec->recnum = recnum; rec->dirty = 0; p = buf; rec->rectype = *p++; if (expected && rec->rectype != expected) { log_error ("%lu: read expected rec type %d, got %d\n", recnum, expected, rec->rectype); return gpg_error (GPG_ERR_TRUSTDB); } p++; /* Skip reserved byte. */ switch (rec->rectype) { case 0: /* unused (free) record */ break; case RECTYPE_VER: /* version record */ if (memcmp(buf+1, GPGEXT_GPG, 3)) { log_error (_("%s: not a trustdb file\n"), db_name ); err = gpg_error (GPG_ERR_TRUSTDB); } else { p += 2; /* skip "gpg" */ rec->r.ver.version = *p++; rec->r.ver.marginals = *p++; rec->r.ver.completes = *p++; rec->r.ver.cert_depth = *p++; rec->r.ver.trust_model = *p++; rec->r.ver.min_cert_level = *p++; p += 2; rec->r.ver.created = buf32_to_ulong(p); p += 4; rec->r.ver.nextcheck = buf32_to_ulong(p); p += 4; p += 4; p += 4; rec->r.ver.firstfree = buf32_to_ulong(p); p += 4; p += 4; rec->r.ver.trusthashtbl = buf32_to_ulong(p); if (recnum) { log_error( _("%s: version record with recnum %lu\n"), db_name, (ulong)recnum ); err = gpg_error (GPG_ERR_TRUSTDB); } else if (rec->r.ver.version != 3) { log_error( _("%s: invalid file version %d\n"), db_name, rec->r.ver.version ); err = gpg_error (GPG_ERR_TRUSTDB); } } break; case RECTYPE_FREE: rec->r.free.next = buf32_to_ulong(p); break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { rec->r.htbl.item[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_HLST: rec->r.hlst.next = buf32_to_ulong(p); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++) { rec->r.hlst.rnum[i] = buf32_to_ulong(p); p += 4; } break; case RECTYPE_TRUST: memcpy (rec->r.trust.fingerprint, p, 20); p+=20; rec->r.trust.ownertrust = *p++; rec->r.trust.depth = *p++; rec->r.trust.min_ownertrust = *p++; p++; rec->r.trust.validlist = buf32_to_ulong(p); break; case RECTYPE_VALID: memcpy (rec->r.valid.namehash, p, 20); p+=20; rec->r.valid.validity = *p++; rec->r.valid.next = buf32_to_ulong(p); p += 4; rec->r.valid.full_count = *p++; rec->r.valid.marginal_count = *p++; break; default: log_error ("%s: invalid record type %d at recnum %lu\n", db_name, rec->rectype, (ulong)recnum); err = gpg_error (GPG_ERR_TRUSTDB); break; } return err; } /* * Write the record from the struct REC. * * Return: 0 on success or an error code. */ int tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec) { byte buf[TRUST_RECORD_LEN]; byte *p; int rc = 0; int i; ulong recnum = rec->recnum; if (db_fd == -1) open_db (); memset (buf, 0, TRUST_RECORD_LEN); p = buf; *p++ = rec->rectype; p++; switch (rec->rectype) { case 0: /* unused record */ break; case RECTYPE_VER: /* version record */ if (recnum) BUG (); memcpy(p-1, GPGEXT_GPG, 3 ); p += 2; *p++ = rec->r.ver.version; *p++ = rec->r.ver.marginals; *p++ = rec->r.ver.completes; *p++ = rec->r.ver.cert_depth; *p++ = rec->r.ver.trust_model; *p++ = rec->r.ver.min_cert_level; p += 2; ulongtobuf(p, rec->r.ver.created); p += 4; ulongtobuf(p, rec->r.ver.nextcheck); p += 4; p += 4; p += 4; ulongtobuf(p, rec->r.ver.firstfree ); p += 4; p += 4; ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4; break; case RECTYPE_FREE: ulongtobuf(p, rec->r.free.next); p += 4; break; case RECTYPE_HTBL: for (i=0; i < ITEMS_PER_HTBL_RECORD; i++) { ulongtobuf( p, rec->r.htbl.item[i]); p += 4; } break; case RECTYPE_HLST: ulongtobuf( p, rec->r.hlst.next); p += 4; for (i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4; } break; case RECTYPE_TRUST: memcpy (p, rec->r.trust.fingerprint, 20); p += 20; *p++ = rec->r.trust.ownertrust; *p++ = rec->r.trust.depth; *p++ = rec->r.trust.min_ownertrust; p++; ulongtobuf( p, rec->r.trust.validlist); p += 4; break; case RECTYPE_VALID: memcpy (p, rec->r.valid.namehash, 20); p += 20; *p++ = rec->r.valid.validity; ulongtobuf( p, rec->r.valid.next); p += 4; *p++ = rec->r.valid.full_count; *p++ = rec->r.valid.marginal_count; break; default: BUG(); } rc = put_record_into_cache (recnum, buf); if (rc) ; else if (rec->rectype == RECTYPE_TRUST) rc = update_trusthashtbl (ctrl, rec); return rc; } /* * Delete the record at record number RECNUm from the trustdb. * * Return: 0 on success or an error code. */ int tdbio_delete_record (ctrl_t ctrl, ulong recnum) { TRUSTREC vr, rec; int rc; /* Must read the record fist, so we can drop it from the hash tables */ rc = tdbio_read_record (recnum, &rec, 0); if (rc) ; else if (rec.rectype == RECTYPE_TRUST) { rc = drop_from_hashtable (ctrl, get_trusthashrec(), rec.r.trust.fingerprint, 20, rec.recnum); } if (rc) return rc; - /* Now we can chnage it to a free record. */ + /* Now we can change it to a free record. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal (_("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); rec.recnum = recnum; rec.rectype = RECTYPE_FREE; rec.r.free.next = vr.r.ver.firstfree; vr.r.ver.firstfree = recnum; rc = tdbio_write_record (ctrl, &rec); if (!rc) rc = tdbio_write_record (ctrl, &vr); return rc; } /* * Create a new record and return its record number. */ ulong tdbio_new_recnum (ctrl_t ctrl) { off_t offset; ulong recnum; TRUSTREC vr, rec; int rc; /* Look for unused records. */ rc = tdbio_read_record (0, &vr, RECTYPE_VER); if (rc) log_fatal( _("%s: error reading version record: %s\n"), db_name, gpg_strerror (rc)); if (vr.r.ver.firstfree) { recnum = vr.r.ver.firstfree; rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE); if (rc) { log_error (_("%s: error reading free record: %s\n"), db_name, gpg_strerror (rc)); return rc; } /* Update dir record. */ vr.r.ver.firstfree = rec.r.free.next; rc = tdbio_write_record (ctrl, &vr); if (rc) { log_error (_("%s: error writing dir record: %s\n"), db_name, gpg_strerror (rc)); return rc; } /* Zero out the new record. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* Mark as unused record (actually already done my the memset). */ rec.recnum = recnum; rc = tdbio_write_record (ctrl, &rec); if (rc) log_fatal (_("%s: failed to zero a record: %s\n"), db_name, gpg_strerror (rc)); } else /* Not found - append a new record. */ { offset = lseek (db_fd, 0, SEEK_END); if (offset == (off_t)(-1)) log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno)); recnum = offset / TRUST_RECORD_LEN; log_assert (recnum); /* this is will never be the first record */ /* We must write a record, so that the next call to this * function returns another recnum. */ memset (&rec, 0, sizeof rec); rec.rectype = 0; /* unused record */ rec.recnum = recnum; rc = 0; if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: lseek failed: %s\n"), recnum, strerror (errno)); } else { int n; n = write (db_fd, &rec, TRUST_RECORD_LEN); if (n != TRUST_RECORD_LEN) { rc = gpg_error_from_syserror (); log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"), recnum, n, strerror (errno)); } } if (rc) log_fatal (_("%s: failed to append a record: %s\n"), db_name, gpg_strerror (rc)); } return recnum ; } /* Helper function for tdbio_search_trust_byfpr. */ static int cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec ) { return (rec->rectype == RECTYPE_TRUST && !memcmp (rec->r.trust.fingerprint, fpr, 20)); } /* * Given a 20 byte FINGERPRINT search its trust record and return * that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t tdbio_search_trust_byfpr (const byte *fingerprint, TRUSTREC *rec) { int rc; /* Locate the trust record using the hash table */ rc = lookup_hashtable (get_trusthashrec(), fingerprint, 20, cmp_trec_fpr, fingerprint, rec ); return rc; } /* * Given a primary public key object PK search its trust record and * return that at REC. * * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code. */ gpg_error_t tdbio_search_trust_bypk (PKT_public_key *pk, TRUSTREC *rec) { byte fingerprint[MAX_FINGERPRINT_LEN]; size_t fingerlen; fingerprint_from_pk( pk, fingerprint, &fingerlen ); for (; fingerlen < 20; fingerlen++) fingerprint[fingerlen] = 0; return tdbio_search_trust_byfpr (fingerprint, rec); } /* * Terminate the process with a message about a corrupted trustdb. */ void tdbio_invalid (void) { log_error (_("Error: The trustdb is corrupted.\n")); how_to_fix_the_trustdb (); g10_exit (2); } diff --git a/g10/tofu.c b/g10/tofu.c index 7bef27b19..1437a50b9 100644 --- a/g10/tofu.c +++ b/g10/tofu.c @@ -1,4001 +1,4001 @@ /* tofu.c - TOFU trust model. * Copyright (C) 2015, 2016 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 <https://www.gnu.org/licenses/>. */ /* TODO: - Format the fingerprints nicely when printing (similar to gpg --list-keys) */ #include <config.h> #include <stdio.h> #include <sys/stat.h> #include <stdarg.h> #include <sqlite3.h> #include <time.h> #include "gpg.h" #include "../common/types.h" #include "../common/logging.h" #include "../common/stringhelp.h" #include "options.h" #include "../common/mbox-util.h" #include "../common/i18n.h" #include "../common/ttyio.h" #include "trustdb.h" #include "../common/mkdir_p.h" #include "gpgsql.h" #include "../common/status.h" #include "sqrtu32.h" #include "tofu.h" #define CONTROL_L ('L' - 'A' + 1) /* Number of days with signed / ecnrypted messages required to * indicate that enough history is available for basic trust. */ #define BASIC_TRUST_THRESHOLD 4 /* Number of days with signed / encrypted messages required to * indicate that a lot of history is available. */ #define FULL_TRUST_THRESHOLD 21 /* A struct with data pertaining to the tofu DB. There is one such struct per session and it is cached in session's ctrl structure. To initialize this or get the current singleton, call opendbs(). There is no need to explicitly release it; cleanup is done when the CTRL object is released. */ struct tofu_dbs_s { sqlite3 *db; char *want_lock_file; time_t want_lock_file_ctime; struct { sqlite3_stmt *savepoint_batch; sqlite3_stmt *savepoint_batch_commit; sqlite3_stmt *record_binding_get_old_policy; sqlite3_stmt *record_binding_update; sqlite3_stmt *get_policy_select_policy_and_conflict; sqlite3_stmt *get_trust_bindings_with_this_email; sqlite3_stmt *get_trust_gather_other_user_ids; sqlite3_stmt *get_trust_gather_signature_stats; sqlite3_stmt *get_trust_gather_encryption_stats; sqlite3_stmt *register_already_seen; sqlite3_stmt *register_signature; sqlite3_stmt *register_encryption; } s; int in_batch_transaction; int in_transaction; time_t batch_update_started; }; #define STRINGIFY(s) STRINGIFY2(s) #define STRINGIFY2(s) #s /* The grouping parameters when collecting signature statistics. */ /* If a message is signed a couple of hours in the future, just assume some clock skew. */ #define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60) /* Days. */ #define TIME_AGO_UNIT_SMALL (24 * 60 * 60) #define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL) /* Months. */ #define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60) #define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM) /* Years. */ #define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60) #define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE) /* Local prototypes. */ static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch); static char *email_from_user_id (const char *user_id); static int show_statistics (tofu_dbs_t dbs, const char *fingerprint, const char *email, enum tofu_policy policy, estream_t outfp, int only_status_fd, time_t now); const char * tofu_policy_str (enum tofu_policy policy) { switch (policy) { case TOFU_POLICY_NONE: return "none"; case TOFU_POLICY_AUTO: return "auto"; case TOFU_POLICY_GOOD: return "good"; case TOFU_POLICY_UNKNOWN: return "unknown"; case TOFU_POLICY_BAD: return "bad"; case TOFU_POLICY_ASK: return "ask"; default: return "???"; } } /* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level (e.g., TRUST_BAD) in light of the current configuration. */ int tofu_policy_to_trust_level (enum tofu_policy policy) { if (policy == TOFU_POLICY_AUTO) /* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */ policy = opt.tofu_default_policy; switch (policy) { case TOFU_POLICY_AUTO: /* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default to marginal trust. */ return TRUST_MARGINAL; case TOFU_POLICY_GOOD: return TRUST_FULLY; case TOFU_POLICY_UNKNOWN: return TRUST_UNKNOWN; case TOFU_POLICY_BAD: return TRUST_NEVER; case TOFU_POLICY_ASK: return TRUST_UNKNOWN; default: log_bug ("Bad value for trust policy: %d\n", opt.tofu_default_policy); return 0; } } /* Start a transaction on DB. If ONLY_BATCH is set, then this will start a batch transaction if we haven't started a batch transaction and one has been requested. */ static gpg_error_t begin_transaction (ctrl_t ctrl, int only_batch) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; log_assert (dbs); /* If we've been in batch update mode for a while (on average, more * than 500 ms), to prevent starving other gpg processes, we drop * and retake the batch lock. * * Note: gnupg_get_time has a one second resolution, if we wanted a * higher resolution, we could use npth_clock_gettime. */ if (/* No real transactions. */ dbs->in_transaction == 0 /* There is an open batch transaction. */ && dbs->in_batch_transaction /* And some time has gone by since it was started. */ && dbs->batch_update_started != gnupg_get_time ()) { struct stat statbuf; /* If we are in a batch update, then batch updates better have been enabled. */ log_assert (ctrl->tofu.batch_updated_wanted); /* Check if another process wants to run. (We just ignore any * stat failure. A waiter might have to wait a bit longer, but * otherwise there should be no impact.) */ if (stat (dbs->want_lock_file, &statbuf) == 0 && statbuf.st_ctime != dbs->want_lock_file_ctime) { end_transaction (ctrl, 2); /* Yield to allow another process a chance to run. Note: * testing suggests that anything less than a 100ms tends to * not result in the other process getting the lock. */ gnupg_usleep (100000); } else dbs->batch_update_started = gnupg_get_time (); } if (/* We don't have an open batch transaction. */ !dbs->in_batch_transaction && (/* Batch mode is enabled or we are starting a new transaction. */ ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0)) { struct stat statbuf; /* We are in batch mode, but we don't have an open batch * transaction. Since the batch save point must be the outer * save point, it must be taken before the inner save point. */ log_assert (dbs->in_transaction == 0); rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch, NULL, NULL, &err, "begin immediate transaction;", GPGSQL_ARG_END); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } dbs->in_batch_transaction = 1; dbs->batch_update_started = gnupg_get_time (); if (stat (dbs->want_lock_file, &statbuf) == 0) dbs->want_lock_file_ctime = statbuf.st_ctime; } if (only_batch) return 0; log_assert (dbs->in_transaction >= 0); dbs->in_transaction ++; rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "savepoint inner%d;", dbs->in_transaction); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } /* Commit a transaction. If ONLY_BATCH is 1, then this only ends the * batch transaction if we have left batch mode. If ONLY_BATCH is 2, * this commits any open batch transaction even if we are still in * batch mode. */ static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; if (only_batch || (! only_batch && dbs->in_transaction == 1)) { if (!dbs) return 0; /* Shortcut to allow for easier cleanup code. */ /* If we are releasing the batch transaction, then we better not be in a normal transaction. */ if (only_batch) log_assert (dbs->in_transaction == 0); if (/* Batch mode disabled? */ (!ctrl->tofu.batch_updated_wanted || only_batch == 2) /* But, we still have an open batch transaction? */ && dbs->in_batch_transaction) { /* The batch transaction is still in open, but we've left * batch mode. */ dbs->in_batch_transaction = 0; dbs->in_transaction = 0; rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit, NULL, NULL, &err, "commit transaction;", GPGSQL_ARG_END); if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } if (only_batch) return 0; } log_assert (dbs); log_assert (dbs->in_transaction > 0); rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "release inner%d;", dbs->in_transaction); dbs->in_transaction --; if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } static gpg_error_t rollback_transaction (ctrl_t ctrl) { tofu_dbs_t dbs = ctrl->tofu.dbs; int rc; char *err = NULL; log_assert (dbs); log_assert (dbs->in_transaction > 0); /* Be careful to not undo any progress made by closed transactions in batch mode. */ rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err, "rollback to inner%d;", dbs->in_transaction); dbs->in_transaction --; if (rc) { log_error (_("error rolling back transaction on TOFU database: %s\n"), err); sqlite3_free (err); return gpg_error (GPG_ERR_GENERAL); } return 0; } void tofu_begin_batch_update (ctrl_t ctrl) { ctrl->tofu.batch_updated_wanted ++; } void tofu_end_batch_update (ctrl_t ctrl) { log_assert (ctrl->tofu.batch_updated_wanted > 0); ctrl->tofu.batch_updated_wanted --; end_transaction (ctrl, 1); } /* Suspend any extant batch transaction (it is safe to call this even no batch transaction has been started). Note: you cannot suspend a batch transaction if you are in a normal transaction. The batch transaction can be resumed explicitly by calling tofu_resume_batch_transaction or implicitly by starting a normal transaction. */ static void tofu_suspend_batch_transaction (ctrl_t ctrl) { end_transaction (ctrl, 2); } /* Resume a batch transaction if there is no extant batch transaction and one has been requested using tofu_begin_batch_transaction. */ static void tofu_resume_batch_transaction (ctrl_t ctrl) { begin_transaction (ctrl, 1); } /* Wrapper around strtol which prints a warning in case of a * conversion error. On success the converted value is stored at * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE * and an error code is returned. */ static gpg_error_t string_to_long (long *r_value, const char *string, long fallback, int line) { gpg_error_t err; char *tail = NULL; gpg_err_set_errno (0); *r_value = strtol (string, &tail, 0); if (errno || !(!strcmp (tail, ".0") || !*tail)) { err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); log_debug ("%s:%d: strtol failed for TOFU DB data; returned string" " (string='%.10s%s'; tail='%.10s%s'): %s\n", __FILE__, line, string, string && strlen(string) > 10 ? "..." : "", tail, tail && strlen(tail) > 10 ? "..." : "", gpg_strerror (err)); *r_value = fallback; } else err = 0; return err; } /* Wrapper around strtoul which prints a warning in case of a * conversion error. On success the converted value is stored at * R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE * and an error code is returned. */ static gpg_error_t string_to_ulong (unsigned long *r_value, const char *string, unsigned long fallback, int line) { gpg_error_t err; char *tail = NULL; gpg_err_set_errno (0); *r_value = strtoul (string, &tail, 0); if (errno || !(!strcmp (tail, ".0") || !*tail)) { err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA); log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string" " (string='%.10s%s'; tail='%.10s%s'): %s\n", __FILE__, line, string, string && strlen(string) > 10 ? "..." : "", tail, tail && strlen(tail) > 10 ? "..." : "", gpg_strerror (err)); *r_value = fallback; } else err = 0; return err; } /* Collect results of a select count (*) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_unsigned_long_cb (void *cookie, int argc, char **argv, char **azColName) { unsigned long int *count = cookie; (void) azColName; log_assert (argc == 1); if (string_to_ulong (count, argv[0], 0, __LINE__)) return 1; /* Abort. */ return 0; } static int get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return get_single_unsigned_long_cb (cookie, argc, argv, azColName); } /* We expect a single integer column whose name is "version". COOKIE must point to an int. This function always aborts. On error or a if the version is bad, sets *VERSION to -1. */ static int version_check_cb (void *cookie, int argc, char **argv, char **azColName) { int *version = cookie; if (argc != 1 || strcmp (azColName[0], "version") != 0) { *version = -1; return 1; } if (strcmp (argv[0], "1") == 0) *version = 1; else { log_error (_("unsupported TOFU database version: %s\n"), argv[0]); *version = -1; } /* Don't run again. */ return 1; } static int check_utks (sqlite3 *db) { int rc; char *err = NULL; struct key_item *utks; struct key_item *ki; int utk_count; char *utks_string = NULL; char keyid_str[16+1]; long utks_unchanged = 0; /* An early version of the v1 format did not include the list of * known ultimately trusted keys. * * This list is used to detect when the set of ultimately trusted * keys changes. We need to detect this to invalidate the effective * policy, which can change if an ultimately trusted key is added or * removed. */ rc = sqlite3_exec (db, "create table if not exists ultimately_trusted_keys" " (keyid);\n", NULL, NULL, &err); if (rc) { log_error (_("error creating 'ultimately_trusted_keys' TOFU table: %s\n"), err); sqlite3_free (err); goto out; } utks = tdb_utks (); for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) ; if (utk_count) { /* Build a list of keyids of the form "XXX","YYY","ZZZ". */ int len = (1 + 16 + 1 + 1) * utk_count; int o = 0; utks_string = xmalloc (len); *utks_string = 0; for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++) { utks_string[o ++] = '\''; format_keyid (ki->kid, KF_LONG, keyid_str, sizeof (keyid_str)); memcpy (&utks_string[o], keyid_str, 16); o += 16; utks_string[o ++] = '\''; utks_string[o ++] = ','; } utks_string[o - 1] = 0; log_assert (o == len); } rc = gpgsql_exec_printf (db, get_single_unsigned_long_cb, &utks_unchanged, &err, "select" /* Removed UTKs? (Known UTKs in current UTKs.) */ " ((select count(*) from ultimately_trusted_keys" " where (keyid in (%s))) == %d)" " and" /* New UTKs? */ " ((select count(*) from ultimately_trusted_keys" " where keyid not in (%s)) == 0);", utks_string ? utks_string : "", utk_count, utks_string ? utks_string : ""); xfree (utks_string); if (rc) { log_error (_("TOFU DB error")); print_further_info ("checking if ultimately trusted keys changed: %s", err); sqlite3_free (err); goto out; } if (utks_unchanged) goto out; if (DBG_TRUST) log_debug ("TOFU: ultimately trusted keys changed.\n"); /* Given that the set of ultimately trusted keys * changed, clear any cached policies. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "update bindings set effective_policy = %d;", TOFU_POLICY_NONE); if (rc) { log_error (_("TOFU DB error")); print_further_info ("clearing cached policies: %s", err); sqlite3_free (err); goto out; } /* Now, update the UTK table. */ rc = sqlite3_exec (db, "drop table ultimately_trusted_keys;", NULL, NULL, &err); if (rc) { log_error (_("TOFU DB error")); print_further_info ("dropping ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } rc = sqlite3_exec (db, "create table if not exists" " ultimately_trusted_keys (keyid);\n", NULL, NULL, &err); if (rc) { log_error (_("TOFU DB error")); print_further_info ("creating ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } for (ki = utks; ki; ki = ki->next) { format_keyid (ki->kid, KF_LONG, keyid_str, sizeof (keyid_str)); rc = gpgsql_exec_printf (db, NULL, NULL, &err, "insert into ultimately_trusted_keys values ('%s');", keyid_str); if (rc) { log_error (_("TOFU DB error")); print_further_info ("updating ultimately_trusted_keys: %s", err); sqlite3_free (err); goto out; } } out: return rc; } /* If the DB is new, initialize it. Otherwise, check the DB's version. Return 0 if the database is okay and 1 otherwise. */ static int initdb (sqlite3 *db) { char *err = NULL; int rc; unsigned long int count; int version = -1; rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err); if (rc) { log_error (_("error beginning transaction on TOFU database: %s\n"), err); sqlite3_free (err); return 1; } /* If the DB has no tables, then assume this is a new DB that needs to be initialized. */ rc = sqlite3_exec (db, "select count(*) from sqlite_master where type='table';", get_single_unsigned_long_cb, &count, &err); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("query available tables"); sqlite3_free (err); goto out; } else if (count != 0) /* Assume that the DB is already initialized. Make sure the version is okay. */ { rc = sqlite3_exec (db, "select version from version;", version_check_cb, &version, &err); if (rc == SQLITE_ABORT && version == 1) /* Happy, happy, joy, joy. */ { sqlite3_free (err); rc = 0; goto out; } else if (rc == SQLITE_ABORT && version == -1) /* Unsupported version. */ { /* An error message was already displayed. */ sqlite3_free (err); goto out; } else if (rc) /* Some error. */ { log_error (_("error determining TOFU database's version: %s\n"), err); sqlite3_free (err); goto out; } else { /* Unexpected success. This can only happen if there are no rows. (select returned 0, but expected ABORT.) */ log_error (_("error determining TOFU database's version: %s\n"), gpg_strerror (GPG_ERR_NO_DATA)); rc = 1; goto out; } } /* Create the version table. */ rc = sqlite3_exec (db, "create table version (version INTEGER);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create version"); sqlite3_free (err); goto out; } /* Initialize the version table, which contains a single integer value. */ rc = sqlite3_exec (db, "insert into version values (1);", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("insert version"); sqlite3_free (err); goto out; } /* The list of <fingerprint, email> bindings and auxiliary data. * * OID is a unique ID identifying this binding (and used by the * signatures table, see below). Note: OIDs will never be * reused. * * FINGERPRINT: The key's fingerprint. * * EMAIL: The normalized email address. * * USER_ID: The unmodified user id from which EMAIL was extracted. * * TIME: The time this binding was first observed. * * POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer). * * CONFLICT is either NULL or a fingerprint. Assume that we have * a binding <0xdeadbeef, foo@example.com> and then we observe * <0xbaddecaf, foo@example.com>. There two bindings conflict * (they have the same email address). When we observe the * latter binding, we warn the user about the conflict and ask * for a policy decision about the new binding. We also change * the old binding's policy to ask if it was auto. So that we * know why this occurred, we also set conflict to 0xbaddecaf. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "create table bindings\n" " (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n" " fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n" " policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n" " conflict STRING,\n" " unique (fingerprint, email));\n" "create index bindings_fingerprint_email\n" " on bindings (fingerprint, email);\n" "create index bindings_email on bindings (email);\n", TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create bindings"); sqlite3_free (err); goto out; } /* The signatures that we have observed. * * BINDING refers to a record in the bindings table, which * describes the binding (i.e., this is a foreign key that * references bindings.oid). * * SIG_DIGEST is the digest stored in the signature. * * SIG_TIME is the timestamp stored in the signature. * * ORIGIN is a free-form string that describes who fed this * signature to GnuPG (e.g., email:claws). * * TIME is the time this signature was registered. */ rc = sqlite3_exec (db, "create table signatures " " (binding INTEGER NOT NULL, sig_digest TEXT," " origin TEXT, sig_time INTEGER, time INTEGER," " primary key (binding, sig_digest, origin));", NULL, NULL, &err); if (rc) { log_error (_("error initializing TOFU database: %s\n"), err); print_further_info ("create signatures"); sqlite3_free (err); goto out; } out: if (! rc) { /* Early version of the v1 format did not include the encryption table. Add it. */ rc = sqlite3_exec (db, "create table if not exists encryptions" " (binding INTEGER NOT NULL," " time INTEGER);" "create index if not exists encryptions_binding" " on encryptions (binding);\n", NULL, NULL, &err); if (rc) { log_error (_("error creating 'encryptions' TOFU table: %s\n"), err); sqlite3_free (err); } } if (! rc) { /* The effective policy for a binding. If a key is ultimately * trusted, then the effective policy of all of its bindings is * good. Likewise if a key is signed by an ultimately trusted * key, etc. If the effective policy is NONE, then we need to * recompute the effective policy. Otherwise, the effective * policy is considered to be up to date, i.e., effective_policy * is a cache of the computed policy. */ rc = gpgsql_exec_printf (db, NULL, NULL, &err, "alter table bindings" " add column effective_policy INTEGER" " DEFAULT %d" " CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));", TOFU_POLICY_NONE, TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK); if (rc) { if (rc == SQLITE_ERROR) /* Almost certainly "duplicate column name", which we can * safely ignore. */ rc = 0; else log_error (_("adding column effective_policy to bindings DB: %s\n"), err); sqlite3_free (err); } } if (! rc) rc = check_utks (db); if (rc) { rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err); if (rc) { log_error (_("error rolling back transaction on TOFU database: %s\n"), err); sqlite3_free (err); } return 1; } else { rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err); if (rc) { log_error (_("error committing transaction on TOFU database: %s\n"), err); sqlite3_free (err); return 1; } return 0; } } static int busy_handler (void *cookie, int call_count) { ctrl_t ctrl = cookie; tofu_dbs_t dbs = ctrl->tofu.dbs; (void) call_count; /* Update the want-lock-file time stamp (specifically, the ctime) so * that the current owner knows that we (well, someone) want the * lock. */ if (dbs) { /* Note: we don't fail if we can't create the lock file: this * process will have to wait a bit longer, but otherwise nothing * horrible should happen. */ estream_t fp; fp = es_fopen (dbs->want_lock_file, "w"); if (! fp) log_debug ("TOFU: Error opening '%s': %s\n", dbs->want_lock_file, strerror (errno)); else es_fclose (fp); } /* Call again. */ return 1; } /* Create a new DB handle. Returns NULL on error. */ /* FIXME: Change to return an error code for better reporting by the caller. */ static tofu_dbs_t opendbs (ctrl_t ctrl) { char *filename; sqlite3 *db; int rc; if (!ctrl->tofu.dbs) { filename = make_filename (gnupg_homedir (), "tofu.db", NULL); rc = sqlite3_open (filename, &db); if (rc) { log_error (_("error opening TOFU database '%s': %s\n"), filename, sqlite3_errmsg (db)); /* Even if an error occurs, DB is guaranteed to be valid. */ sqlite3_close (db); db = NULL; } /* If a DB is locked wait up to 5 seconds for the lock to be cleared before failing. */ if (db) { sqlite3_busy_timeout (db, 5 * 1000); sqlite3_busy_handler (db, busy_handler, ctrl); } if (db && initdb (db)) { sqlite3_close (db); db = NULL; } if (db) { ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs); ctrl->tofu.dbs->db = db; ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename); } xfree (filename); } else log_assert (ctrl->tofu.dbs->db); return ctrl->tofu.dbs; } /* Release all of the resources associated with the DB handle. */ void tofu_closedbs (ctrl_t ctrl) { tofu_dbs_t dbs; sqlite3_stmt **statements; dbs = ctrl->tofu.dbs; if (!dbs) return; /* Not initialized. */ log_assert (dbs->in_transaction == 0); end_transaction (ctrl, 2); /* Arghh, that is a surprising use of the struct. */ for (statements = (void *) &dbs->s; (void *) statements < (void *) &(&dbs->s)[1]; statements ++) sqlite3_finalize (*statements); sqlite3_close (dbs->db); xfree (dbs->want_lock_file); xfree (dbs); ctrl->tofu.dbs = NULL; } /* Collect results of a select min (foo) ...; style query. Aborts if the argument is not a valid integer (or real of the form X.0). */ static int get_single_long_cb (void *cookie, int argc, char **argv, char **azColName) { long *count = cookie; (void) azColName; log_assert (argc == 1); if (string_to_long (count, argv[0], 0, __LINE__)) return 1; /* Abort. */ return 0; } static int get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return get_single_long_cb (cookie, argc, argv, azColName); } /* Record (or update) a trust policy about a (possibly new) binding. If SHOW_OLD is set, the binding's old policy is displayed. */ static gpg_error_t record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email, const char *user_id, enum tofu_policy policy, enum tofu_policy effective_policy, const char *conflict, int set_conflict, int show_old, time_t now) { char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); gpg_error_t rc; char *err = NULL; if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy); if (DBG_TRUST || show_old) { /* Get the old policy. Since this is just for informational * purposes, there is no need to start a transaction or to die * if there is a failure. */ /* policy_old needs to be a long and not an enum tofu_policy, because we pass it by reference to get_single_long_cb2, which expects a long. */ long policy_old = TOFU_POLICY_NONE; rc = gpgsql_stepx (dbs->db, &dbs->s.record_binding_get_old_policy, get_single_long_cb2, &policy_old, &err, "select policy from bindings where fingerprint = ? and email = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_END); if (rc) { log_debug ("TOFU: Error reading from binding database" " (reading policy for <key: %s, user id: %s>): %s\n", fingerprint, email, err); sqlite3_free (err); } if (policy_old != TOFU_POLICY_NONE) (show_old ? log_info : log_debug) ("Changing TOFU trust policy for binding" " <key: %s, user id: %s> from %s to %s.\n", fingerprint, show_old ? user_id : email, tofu_policy_str (policy_old), tofu_policy_str (policy)); else (show_old ? log_info : log_debug) ("Setting TOFU trust policy for new binding" " <key: %s, user id: %s> to %s.\n", fingerprint, show_old ? user_id : email, tofu_policy_str (policy)); } if (opt.dry_run) { log_info ("TOFU database update skipped due to --dry-run\n"); rc = 0; goto leave; } rc = gpgsql_stepx (dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err, "insert or replace into bindings\n" " (oid, fingerprint, email, user_id, time," " policy, conflict, effective_policy)\n" " values (\n" /* If we don't explicitly reuse the OID, then SQLite will * reallocate a new one. We just need to search for the OID * based on the fingerprint and email since they are unique. */ " (select oid from bindings where fingerprint = ? and email = ?),\n" " ?, ?, ?, ?, ?," /* If SET_CONFLICT is 0, then preserve conflict's current value. */ " case ?" " when 0 then" " (select conflict from bindings where fingerprint = ? and email = ?)" " else ?" " end," " ?);", /* oid subquery. */ GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, /* values 2 through 6. */ GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, user_id, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_INT, (int) policy, /* conflict subquery. */ GPGSQL_ARG_INT, set_conflict ? 1 : 0, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, conflict ? conflict : "", GPGSQL_ARG_INT, (int) effective_policy, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), err); print_further_info (" insert bindings <key: %s, user id: %s> = %s", fingerprint, email, tofu_policy_str (policy)); sqlite3_free (err); goto leave; } leave: xfree (fingerprint_pp); return rc; } /* Collect the strings returned by a query in a simple string list. Any NULL values are converted to the empty string. If a result has 3 rows and each row contains two columns, then the results are added to the list as follows (the value is parentheses is the 1-based index in the final list): row 1, col 2 (6) row 1, col 1 (5) row 2, col 2 (4) row 2, col 1 (3) row 3, col 2 (2) row 3, col 1 (1) This is because add_to_strlist pushes the results onto the front of the list. The end result is that the rows are backwards, but the columns are in the expected order. */ static int strings_collect_cb (void *cookie, int argc, char **argv, char **azColName) { int i; strlist_t *strlist = cookie; (void) azColName; for (i = argc - 1; i >= 0; i --) add_to_strlist (strlist, argv[i] ? argv[i] : ""); return 0; } static int strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { (void) stmt; return strings_collect_cb (cookie, argc, argv, azColName); } /* Auxiliary data structure to collect statistics about signatures. */ struct signature_stats { struct signature_stats *next; /* The user-assigned policy for this binding. */ enum tofu_policy policy; /* How long ago the signature was created (rounded to a multiple of TIME_AGO_UNIT_SMALL, etc.). */ long time_ago; /* Number of signatures during this time. */ unsigned long count; /* If the corresponding key/user id has been expired / revoked. */ int is_expired; int is_revoked; /* The key that generated this signature. */ char fingerprint[1]; }; static void signature_stats_free (struct signature_stats *stats) { while (stats) { struct signature_stats *next = stats->next; xfree (stats); stats = next; } } static void signature_stats_prepend (struct signature_stats **statsp, const char *fingerprint, enum tofu_policy policy, long time_ago, unsigned long count) { struct signature_stats *stats = xmalloc_clear (sizeof (*stats) + strlen (fingerprint)); stats->next = *statsp; *statsp = stats; strcpy (stats->fingerprint, fingerprint); stats->policy = policy; stats->time_ago = time_ago; stats->count = count; } /* Process rows that contain the four columns: <fingerprint, policy, time ago, count>. */ static int signature_stats_collect_cb (void *cookie, int argc, char **argv, char **azColName, sqlite3_stmt *stmt) { struct signature_stats **statsp = cookie; int i = 0; enum tofu_policy policy; long time_ago; unsigned long count; long along; (void) azColName; (void) stmt; i ++; if (string_to_long (&along, argv[i], 0, __LINE__)) return 1; /* Abort */ policy = along; i ++; if (! argv[i]) time_ago = 0; else { if (string_to_long (&time_ago, argv[i], 0, __LINE__)) return 1; /* Abort. */ } i ++; /* If time_ago is NULL, then we had no messages, but we still have a single row, which count(*) turns into 1. */ if (! argv[i - 1]) count = 0; else { if (string_to_ulong (&count, argv[i], 0, __LINE__)) return 1; /* Abort */ } i ++; log_assert (argc == i); signature_stats_prepend (statsp, argv[0], policy, time_ago, count); return 0; } /* Format the first part of a conflict message and return that as a * malloced string. */ static char * format_conflict_msg_part1 (int policy, strlist_t conflict_set, const char *email) { estream_t fp; char *fingerprint; char *tmpstr, *text; log_assert (conflict_set); fingerprint = conflict_set->d; fp = es_fopenmem (0, "rw,samethread"); if (!fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); if (policy == TOFU_POLICY_NONE) { es_fprintf (fp, _("This is the first time the email address \"%s\" is " "being used with key %s."), email, fingerprint); es_fputs (" ", fp); } else if (policy == TOFU_POLICY_ASK && conflict_set->next) { int conflicts = strlist_length (conflict_set); es_fprintf (fp, ngettext("The email address \"%s\" is associated with %d key!", "The email address \"%s\" is associated with %d keys!", conflicts), email, conflicts); if (opt.verbose) es_fprintf (fp, _(" Since this binding's policy was 'auto', it has been " "changed to 'ask'.")); es_fputs (" ", fp); } es_fprintf (fp, _("Please indicate whether this email address should" " be associated with key %s or whether you think someone" " is impersonating \"%s\"."), fingerprint, email); es_fputc ('\n', fp); es_fputc (0, fp); if (es_fclose_snatch (fp, (void **)&tmpstr, NULL)) log_fatal ("error snatching memory stream\n"); text = format_text (tmpstr, 72, 80); es_free (tmpstr); return text; } /* Return 1 if A signed B and B signed A. */ static int cross_sigs (const char *email, kbnode_t a, kbnode_t b) { int i; PKT_public_key *a_pk = a->pkt->pkt.public_key; PKT_public_key *b_pk = b->pkt->pkt.public_key; char a_keyid[33]; char b_keyid[33]; if (DBG_TRUST) { format_keyid (pk_main_keyid (a_pk), KF_LONG, a_keyid, sizeof (a_keyid)); format_keyid (pk_main_keyid (b_pk), KF_LONG, b_keyid, sizeof (b_keyid)); } for (i = 0; i < 2; i ++) { /* See if SIGNER signed SIGNEE. */ kbnode_t signer = i == 0 ? a : b; kbnode_t signee = i == 0 ? b : a; PKT_public_key *signer_pk = signer->pkt->pkt.public_key; u32 *signer_kid = pk_main_keyid (signer_pk); kbnode_t n; int saw_email = 0; /* Iterate over SIGNEE's keyblock and see if there is a valid signature from SIGNER. */ for (n = signee; n; n = n->next) { PKT_signature *sig; if (n->pkt->pkttype == PKT_USER_ID) { if (saw_email) /* We're done: we've processed all signatures on the user id. */ break; else { /* See if this is the matching user id. */ PKT_user_id *user_id = n->pkt->pkt.user_id; char *email2 = email_from_user_id (user_id->name); if (strcmp (email, email2) == 0) saw_email = 1; xfree (email2); } } if (! saw_email) continue; if (n->pkt->pkttype != PKT_SIGNATURE) continue; sig = n->pkt->pkt.signature; if (! (sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13)) /* Not a signature over a user id. */ continue; /* SIG is on SIGNEE's keyblock. If SIG was generated by the signer, then it's a match. */ if (keyid_cmp (sig->keyid, signer_kid) == 0) /* Match! */ break; } if (! n) /* We didn't find a signature from signer over signee. */ { if (DBG_TRUST) log_debug ("No cross sig between %s and %s\n", a_keyid, b_keyid); return 0; } } /* A signed B and B signed A. */ if (DBG_TRUST) log_debug ("Cross sig between %s and %s\n", a_keyid, b_keyid); return 1; } /* Return whether the key was signed by an ultimately trusted key. */ static int signed_by_utk (const char *email, kbnode_t a) { kbnode_t n; int saw_email = 0; for (n = a; n; n = n->next) { PKT_signature *sig; if (n->pkt->pkttype == PKT_USER_ID) { if (saw_email) /* We're done: we've processed all signatures on the user id. */ break; else { /* See if this is the matching user id. */ PKT_user_id *user_id = n->pkt->pkt.user_id; char *email2 = email_from_user_id (user_id->name); if (strcmp (email, email2) == 0) saw_email = 1; xfree (email2); } } if (! saw_email) continue; if (n->pkt->pkttype != PKT_SIGNATURE) continue; sig = n->pkt->pkt.signature; if (! (sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13)) /* Not a signature over a user id. */ continue; /* SIG is on SIGNEE's keyblock. If SIG was generated by the signer, then it's a match. */ if (tdb_keyid_is_utk (sig->keyid)) { /* Match! */ if (DBG_TRUST) log_debug ("TOFU: %s is signed by an ultimately trusted key.\n", pk_keyid_str (a->pkt->pkt.public_key)); return 1; } } if (DBG_TRUST) log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n", pk_keyid_str (a->pkt->pkt.public_key)); return 0; } enum { BINDING_NEW = 1 << 0, BINDING_CONFLICT = 1 << 1, BINDING_EXPIRED = 1 << 2, BINDING_REVOKED = 1 << 3 }; /* Ask the user about the binding. There are three ways we could end * up here: * * - This is a new binding and there is a conflict * (policy == TOFU_POLICY_NONE && conflict_set_count > 1), * * - This is a new binding and opt.tofu_default_policy is set to * ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy == * TOFU_POLICY_ASK), or, * * - The policy is ask (the user deferred last time) (policy == * TOFU_POLICY_ASK). * * Note: this function must not be called while in a transaction! * * CONFLICT_SET includes all of the conflicting bindings * with FINGERPRINT first. FLAGS is a bit-wise or of * BINDING_NEW, etc. */ static void ask_about_binding (ctrl_t ctrl, enum tofu_policy *policy, int *trust_level, strlist_t conflict_set, const char *fingerprint, const char *email, const char *user_id, time_t now) { tofu_dbs_t dbs; strlist_t iter; int conflict_set_count = strlist_length (conflict_set); char *sqerr = NULL; int rc; estream_t fp; strlist_t other_user_ids = NULL; struct signature_stats *stats = NULL; struct signature_stats *stats_iter = NULL; char *prompt = NULL; const char *choices; dbs = ctrl->tofu.dbs; log_assert (dbs); log_assert (dbs->in_transaction == 0); fp = es_fopenmem (0, "rw,samethread"); if (!fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); { char *text = format_conflict_msg_part1 (*policy, conflict_set, email); es_fputs (text, fp); es_fputc ('\n', fp); xfree (text); } begin_transaction (ctrl, 0); /* Find other user ids associated with this key and whether the * bindings are marked as good or bad. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_other_user_ids, strings_collect_cb2, &other_user_ids, &sqerr, "select user_id, policy from bindings where fingerprint = ?;", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); if (rc) { log_error (_("error gathering other user IDs: %s\n"), sqerr); sqlite3_free (sqerr); sqerr = NULL; rc = gpg_error (GPG_ERR_GENERAL); } if (other_user_ids) { strlist_t strlist_iter; es_fprintf (fp, _("This key's user IDs:\n")); for (strlist_iter = other_user_ids; strlist_iter; strlist_iter = strlist_iter->next) { char *other_user_id = strlist_iter->d; char *other_thing; enum tofu_policy other_policy; log_assert (strlist_iter->next); strlist_iter = strlist_iter->next; other_thing = strlist_iter->d; other_policy = atoi (other_thing); es_fprintf (fp, " %s (", other_user_id); es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy)); es_fprintf (fp, ")\n"); } es_fprintf (fp, "\n"); free_strlist (other_user_ids); } /* Get the stats for all the keys in CONFLICT_SET. */ strlist_rev (&conflict_set); for (iter = conflict_set; iter && ! rc; iter = iter->next) { #define STATS_SQL(table, time, sign) \ "select fingerprint, policy, time_ago, count(*)\n" \ " from\n" \ " (select bindings.*,\n" \ " "sign" case\n" \ " when delta ISNULL then 1\n" \ /* From the future (but if its just a couple of hours in the \ * future don't turn it into a warning)? Or should we use \ * small, medium or large units? (Note: whatever we do, we \ * keep the value in seconds. Then when we group, everything \ * that rounds to the same number of seconds is grouped.) */ \ " when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \ " when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \ " then 3\n" \ " when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \ " then 4\n" \ " when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \ " then 5\n" \ " else 6\n" \ " end time_ago,\n" \ " delta time_ago_raw\n" \ " from bindings\n" \ " left join\n" \ " (select *,\n" \ " cast(? - " time " as real) delta\n" \ " from " table ") ss\n" \ " on ss.binding = bindings.oid)\n" \ " where email = ? and fingerprint = ?\n" \ " group by time_ago\n" \ /* Make sure the current key is first. */ \ " order by time_ago desc;\n" /* Use the time when we saw the signature, not when the signature was created as that can be forged. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_signature_stats, signature_stats_collect_cb, &stats, &sqerr, STATS_SQL ("signatures", "time", ""), GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, iter->d, GPGSQL_ARG_END); if (rc) { rc = gpg_error (GPG_ERR_GENERAL); break; } if (!stats || strcmp (iter->d, stats->fingerprint) != 0) /* No stats for this binding. Add a dummy entry. */ signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1); rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_gather_encryption_stats, signature_stats_collect_cb, &stats, &sqerr, STATS_SQL ("encryptions", "time", "-"), GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, iter->d, GPGSQL_ARG_END); if (rc) { rc = gpg_error (GPG_ERR_GENERAL); break; } #undef STATS_SQL if (!stats || strcmp (iter->d, stats->fingerprint) != 0 || stats->time_ago > 0) /* No stats for this binding. Add a dummy entry. */ signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1); } end_transaction (ctrl, 0); strlist_rev (&conflict_set); if (rc) { strlist_t strlist_iter; log_error (_("error gathering signature stats: %s\n"), sqerr); sqlite3_free (sqerr); sqerr = NULL; es_fprintf (fp, ngettext("The email address \"%s\" is" " associated with %d key:\n", "The email address \"%s\" is" " associated with %d keys:\n", conflict_set_count), email, conflict_set_count); for (strlist_iter = conflict_set; strlist_iter; strlist_iter = strlist_iter->next) es_fprintf (fp, " %s\n", strlist_iter->d); } else { char *key = NULL; strlist_t binding; int seen_in_past = 0; int encrypted = 1; es_fprintf (fp, _("Statistics for keys" " with the email address \"%s\":\n"), email); for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next) { #if 0 log_debug ("%s: time_ago: %ld; count: %ld\n", stats_iter->fingerprint, stats_iter->time_ago, stats_iter->count); #endif if (stats_iter->time_ago > 0 && encrypted) { /* We've change from the encrypted stats to the verified * stats. Reset SEEN_IN_PAST. */ encrypted = 0; seen_in_past = 0; } if (! key || strcmp (key, stats_iter->fingerprint)) { int this_key; char *key_pp; key = stats_iter->fingerprint; this_key = strcmp (key, fingerprint) == 0; key_pp = format_hexfingerprint (key, NULL, 0); es_fprintf (fp, " %s (", key_pp); /* Find the associated binding. */ for (binding = conflict_set; binding; binding = binding->next) if (strcmp (key, binding->d) == 0) break; log_assert (binding); if ((binding->flags & BINDING_REVOKED)) { es_fprintf (fp, _("revoked")); es_fprintf (fp, _(", ")); } else if ((binding->flags & BINDING_EXPIRED)) { es_fprintf (fp, _("expired")); es_fprintf (fp, _(", ")); } if (this_key) es_fprintf (fp, _("this key")); else es_fprintf (fp, _("policy: %s"), tofu_policy_str (stats_iter->policy)); es_fputs ("):\n", fp); xfree (key_pp); seen_in_past = 0; show_statistics (dbs, stats_iter->fingerprint, email, TOFU_POLICY_ASK, NULL, 1, now); } if (labs(stats_iter->time_ago) == 1) { /* The 1 in this case is the NULL entry. */ log_assert (stats_iter->count == 1); stats_iter->count = 0; } seen_in_past += stats_iter->count; es_fputs (" ", fp); if (!stats_iter->count) { if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Verified %d message.", "Verified %d messages.", seen_in_past), seen_in_past); else es_fprintf (fp, ngettext("Encrypted %d message.", "Encrypted %d messages.", seen_in_past), seen_in_past); } else if (labs(stats_iter->time_ago) == 2) { if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Verified %d message in the future.", "Verified %d messages in the future.", seen_in_past), seen_in_past); else es_fprintf (fp, ngettext("Encrypted %d message in the future.", "Encrypted %d messages in the future.", seen_in_past), seen_in_past); /* Reset it. */ seen_in_past = 0; } else { if (labs(stats_iter->time_ago) == 3) { int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d day: %d.", "Messages verified over the past %d days: %d.", days), days, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d day: %d.", "Messages encrypted over the past %d days: %d.", days), days, seen_in_past); } else if (labs(stats_iter->time_ago) == 4) { int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d month: %d.", "Messages verified over the past %d months: %d.", months), months, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d month: %d.", "Messages encrypted over the past %d months: %d.", months), months, seen_in_past); } else if (labs(stats_iter->time_ago) == 5) { int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE; if (stats_iter->time_ago > 0) es_fprintf (fp, ngettext("Messages verified over the past %d year: %d.", "Messages verified over the past %d years: %d.", years), years, seen_in_past); else es_fprintf (fp, ngettext("Messages encrypted over the past %d year: %d.", "Messages encrypted over the past %d years: %d.", years), years, seen_in_past); } else if (labs(stats_iter->time_ago) == 6) { if (stats_iter->time_ago > 0) es_fprintf (fp, _("Messages verified in the past: %d."), seen_in_past); else es_fprintf (fp, _("Messages encrypted in the past: %d."), seen_in_past); } else log_assert (! "Broken SQL.\n"); } es_fputs ("\n", fp); } } if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT)) { /* This is a conflict. */ /* TRANSLATORS: Please translate the text found in the source * file below. We don't directly internationalize that text so * that we can tweak it without breaking translations. */ const char *text = _("TOFU detected a binding conflict"); char *textbuf; if (!strcmp (text, "TOFU detected a binding conflict")) { /* No translation. Use the English text. */ text = "Normally, an email address is associated with a single key. " "However, people sometimes generate a new key if " "their key is too old or they think it might be compromised. " "Alternatively, a new key may indicate a man-in-the-middle " "attack! Before accepting this association, you should talk to or " "call the person to make sure this new key is legitimate."; } textbuf = format_text (text, 72, 80); es_fprintf (fp, "\n%s\n", textbuf); xfree (textbuf); } es_fputc ('\n', fp); /* Add a NUL terminator. */ es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &prompt, NULL)) log_fatal ("error snatching memory stream\n"); /* I think showing the large message once is sufficient. If we * would move it right before the cpr_get many lines will scroll * away and the user might not realize that he merely entered a - * wrong choise (because he does not see that either). As a small + * wrong choice (because he does not see that either). As a small * benefit we allow C-L to redisplay everything. */ tty_printf ("%s", prompt); /* Suspend any transaction: it could take a while until the user responds. */ tofu_suspend_batch_transaction (ctrl); while (1) { char *response; /* TRANSLATORS: Two letters (normally the lower and upper case * version of the hotkey) for each of the five choices. If * there is only one choice in your language, repeat it. */ choices = _("gG" "aA" "uU" "rR" "bB"); if (strlen (choices) != 10) log_bug ("Bad TOFU conflict translation! Please report."); response = cpr_get ("tofu.conflict", _("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? ")); trim_spaces (response); cpr_kill_prompt (); if (*response == CONTROL_L) tty_printf ("%s", prompt); else if (!response[0]) /* Default to unknown. Don't save it. */ { tty_printf (_("Defaulting to unknown.\n")); *policy = TOFU_POLICY_UNKNOWN; break; } else if (!response[1]) { char *choice = strchr (choices, *response); if (choice) { int c = ((size_t) choice - (size_t) choices) / 2; switch (c) { case 0: /* Good. */ *policy = TOFU_POLICY_GOOD; *trust_level = tofu_policy_to_trust_level (*policy); break; case 1: /* Accept once. */ *policy = TOFU_POLICY_ASK; *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD); break; case 2: /* Unknown. */ *policy = TOFU_POLICY_UNKNOWN; *trust_level = tofu_policy_to_trust_level (*policy); break; case 3: /* Reject once. */ *policy = TOFU_POLICY_ASK; *trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD); break; case 4: /* Bad. */ *policy = TOFU_POLICY_BAD; *trust_level = tofu_policy_to_trust_level (*policy); break; default: log_bug ("c should be between 0 and 4 but it is %d!", c); } if (record_binding (dbs, fingerprint, email, user_id, *policy, TOFU_POLICY_NONE, NULL, 0, 0, now)) { /* If there's an error registering the * binding, don't save the signature. */ *trust_level = _tofu_GET_TRUST_ERROR; } break; } } xfree (response); } tofu_resume_batch_transaction (ctrl); xfree (prompt); signature_stats_free (stats); } /* Return the set of keys that conflict with the binding <fingerprint, email> (including the binding itself, which will be first in the list). For each returned key also sets BINDING_NEW, etc. */ static strlist_t build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint, const char *email) { gpg_error_t rc; char *sqerr; strlist_t conflict_set = NULL; int conflict_set_count; strlist_t iter; kbnode_t *kb_all; KEYDB_HANDLE hd; int i; /* Get the fingerprints of any bindings that share the email address * and whether the bindings have a known conflict. * * Note: if the binding in question is in the DB, it will also be * returned. Thus, if the result set is empty, then <email, * fingerprint> is a new binding. */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_trust_bindings_with_this_email, strings_collect_cb2, &conflict_set, &sqerr, "select" /* A binding should only appear once, but try not to break in the * case of corruption. */ " fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end" " from bindings where email = ?" " group by fingerprint" /* Make sure the current key comes first in the result list (if it is present). */ " order by fingerprint = ? asc, fingerprint desc;", GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), sqerr); print_further_info ("listing fingerprints"); sqlite3_free (sqerr); rc = gpg_error (GPG_ERR_GENERAL); return NULL; } /* Set BINDING_CONFLICT if the binding has a known conflict. This * allows us to distinguish between bindings where the user * explicitly set the policy to ask and bindings where we set the * policy to ask due to a conflict. */ for (iter = conflict_set; iter; iter = iter->next) { int l = strlen (iter->d); if (!(l == 2 * MAX_FINGERPRINT_LEN || l == 2 * MAX_FINGERPRINT_LEN + 1)) { log_error (_("TOFU db corruption detected.\n")); print_further_info ("fingerprint '%s' is not %d characters long", iter->d, 2 * MAX_FINGERPRINT_LEN); } if (l >= 1 && iter->d[l - 1] == '!') { iter->flags |= BINDING_CONFLICT; /* Remove the !. */ iter->d[l - 1] = 0; } } /* If the current binding has not yet been recorded, add it to the * list. (The order by above ensures that if it is present, it will * be first.) */ if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0)) { add_to_strlist (&conflict_set, fingerprint); conflict_set->flags |= BINDING_NEW; } conflict_set_count = strlist_length (conflict_set); /* Eliminate false conflicts. */ if (conflict_set_count == 1) /* We only have a single key. There are no false conflicts to eliminate. But, we do need to set the flags. */ { if (pk->has_expired) conflict_set->flags |= BINDING_EXPIRED; if (pk->flags.revoked) conflict_set->flags |= BINDING_REVOKED; return conflict_set; } /* If two keys have cross signatures, then they are controlled by * the same person and thus are not in conflict. */ kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count); hd = keydb_new (); for (i = 0, iter = conflict_set; i < conflict_set_count; i ++, iter = iter->next) { char *fp = iter->d; KEYDB_SEARCH_DESC desc; kbnode_t kb; PKT_public_key *binding_pk; kbnode_t n; int found_user_id; rc = keydb_search_reset (hd); if (rc) { log_error (_("resetting keydb: %s\n"), gpg_strerror (rc)); continue; } rc = classify_user_id (fp, &desc, 0); if (rc) { log_error (_("error parsing key specification '%s': %s\n"), fp, gpg_strerror (rc)); continue; } rc = keydb_search (hd, &desc, 1, NULL); if (rc) { /* Note: it is entirely possible that we don't have the key corresponding to an entry in the TOFU DB. This can happen if we merge two TOFU DBs, but not the key rings. */ log_info (_("key \"%s\" not found: %s\n"), fp, gpg_strerror (rc)); continue; } rc = keydb_get_keyblock (hd, &kb); if (rc) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc)); print_further_info ("fingerprint: %s", fp); continue; } merge_keys_and_selfsig (ctrl, kb); log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); kb_all[i] = kb; /* Since we have the key block, use this opportunity to figure * out if the binding is expired or revoked. */ binding_pk = kb->pkt->pkt.public_key; /* The binding is always expired/revoked if the key is * expired/revoked. */ if (binding_pk->has_expired) iter->flags |= BINDING_EXPIRED; if (binding_pk->flags.revoked) iter->flags |= BINDING_REVOKED; /* The binding is also expired/revoked if the user id is * expired/revoked. */ n = kb; found_user_id = 0; while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id) { PKT_user_id *user_id2 = n->pkt->pkt.user_id; char *email2; if (user_id2->attrib_data) continue; email2 = email_from_user_id (user_id2->name); if (strcmp (email, email2) == 0) { found_user_id = 1; if (user_id2->flags.revoked) iter->flags |= BINDING_REVOKED; if (user_id2->flags.expired) iter->flags |= BINDING_EXPIRED; } xfree (email2); } if (! found_user_id) { log_info (_("TOFU db corruption detected.\n")); print_further_info ("user id '%s' not on key block '%s'", email, fingerprint); } } keydb_release (hd); /* Now that we have the key blocks, check for cross sigs. */ { int j; strlist_t *prevp; strlist_t iter_next; int *die; log_assert (conflict_set_count > 0); die = xtrycalloc (conflict_set_count, sizeof *die); if (!die) { /*err = gpg_error_from_syserror ();*/ - xoutofcore (); /* Fixme: Let the fucntion return an error. */ + xoutofcore (); /* Fixme: Let the function return an error. */ } for (i = 0; i < conflict_set_count; i ++) { /* Look for cross sigs between this key (i == 0) or a key * that has cross sigs with i == 0 (i.e., transitively) */ if (! (i == 0 || die[i])) continue; for (j = i + 1; j < conflict_set_count; j ++) /* Be careful: we might not have a key block for a key. */ if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j])) die[j] = 1; } /* Free unconflicting bindings (and all of the key blocks). */ for (iter = conflict_set, prevp = &conflict_set, i = 0; iter; iter = iter_next, i ++) { iter_next = iter->next; release_kbnode (kb_all[i]); if (die[i]) { *prevp = iter_next; iter->next = NULL; free_strlist (iter); conflict_set_count --; } else { prevp = &iter->next; } } /* We shouldn't have removed the head. */ log_assert (conflict_set); log_assert (conflict_set_count >= 1); xfree (die); } xfree (kb_all); if (DBG_TRUST) { log_debug ("binding <key: %s, email: %s> conflicts:\n", fingerprint, email); for (iter = conflict_set; iter; iter = iter->next) { log_debug (" %s:%s%s%s%s\n", iter->d, (iter->flags & BINDING_NEW) ? " new" : "", (iter->flags & BINDING_CONFLICT) ? " known_conflict" : "", (iter->flags & BINDING_EXPIRED) ? " expired" : "", (iter->flags & BINDING_REVOKED) ? " revoked" : ""); } } return conflict_set; } /* Return the effective policy for the binding <FINGERPRINT, EMAIL> * (email has already been normalized). Returns * _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict * information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the * returned policy is TOFU_POLICY_ASK (consequently, if there is a * conflict, but the user set the policy to good *CONFLICT_SETP will * empty). Note: as per build_conflict_set, which is used to build * the conflict information, the conflict information includes the * current user id as the first element of the linked list. * * This function registers the binding in the bindings table if it has * not yet been registered. */ static enum tofu_policy get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk, const char *fingerprint, const char *user_id, const char *email, strlist_t *conflict_setp, time_t now) { int rc; char *err = NULL; strlist_t results = NULL; enum tofu_policy policy = _tofu_GET_POLICY_ERROR; enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE; enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR; long along; char *conflict_orig = NULL; char *conflict = NULL; strlist_t conflict_set = NULL; int conflict_set_count; /* Check if the <FINGERPRINT, EMAIL> binding is known (TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is still TOFU_POLICY_NONE after executing the query, then the result set was empty.) */ rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict, strings_collect_cb2, &results, &err, "select policy, conflict, effective_policy from bindings\n" " where fingerprint = ? and email = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("reading the policy"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist_length (results) == 0) { /* No results. Use the defaults. */ policy = TOFU_POLICY_NONE; effective_policy = TOFU_POLICY_NONE; } else if (strlist_length (results) == 3) { /* Parse and sanity check the results. */ if (string_to_long (&along, results->d, 0, __LINE__)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("bad value for policy: %s", results->d); goto out; } policy = along; if (! (policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_DB_CORRUPTED)); print_further_info ("invalid value for policy (%d)", policy); effective_policy = _tofu_GET_POLICY_ERROR; goto out; } if (*results->next->d) conflict = xstrdup (results->next->d); if (string_to_long (&along, results->next->next->d, 0, __LINE__)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("bad value for effective policy: %s", results->next->next->d); goto out; } effective_policy = along; if (! (effective_policy == TOFU_POLICY_NONE || effective_policy == TOFU_POLICY_AUTO || effective_policy == TOFU_POLICY_GOOD || effective_policy == TOFU_POLICY_UNKNOWN || effective_policy == TOFU_POLICY_BAD || effective_policy == TOFU_POLICY_ASK)) { log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_DB_CORRUPTED)); print_further_info ("invalid value for effective_policy (%d)", effective_policy); effective_policy = _tofu_GET_POLICY_ERROR; goto out; } } else { /* The result has the wrong form. */ log_error (_("error reading TOFU database: %s\n"), gpg_strerror (GPG_ERR_BAD_DATA)); print_further_info ("reading policy: expected 3 columns, got %d\n", strlist_length (results)); goto out; } /* Save the effective policy and conflict so we know if we changed * them. */ effective_policy_orig = effective_policy; conflict_orig = conflict; /* Unless there is a conflict, if the effective policy is cached, * just return it. The reason we don't do this when there is a * conflict is because of the following scenario: assume A and B * conflict and B has signed A's key. Now, later we import A's * signature on B. We need to recheck A, but the signature was on * B, i.e., when B changes, we invalidate B's effective policy, but * we also need to invalidate A's effective policy. Instead, we * assume that conflicts are rare and don't optimize for them, which * would complicate the code. */ if (effective_policy != TOFU_POLICY_NONE && !conflict) goto out; /* If the user explicitly set the policy, then respect that. */ if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE) { effective_policy = policy; goto out; } /* Unless proven wrong, assume the effective policy is 'auto'. */ effective_policy = TOFU_POLICY_AUTO; /* See if the key is ultimately trusted. */ { u32 kid[2]; keyid_from_pk (pk, kid); if (tdb_keyid_is_utk (kid)) { effective_policy = TOFU_POLICY_GOOD; goto out; } } /* See if the key is signed by an ultimately trusted key. */ { int fingerprint_raw_len = strlen (fingerprint) / 2; char fingerprint_raw[20]; int len = 0; if (fingerprint_raw_len != sizeof fingerprint_raw || ((len = hex2bin (fingerprint, fingerprint_raw, fingerprint_raw_len)) != strlen (fingerprint))) { if (DBG_TRUST) log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n", fingerprint, strlen (fingerprint), len); } else { int lookup_err; kbnode_t kb; lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb, fingerprint_raw, fingerprint_raw_len); if (lookup_err) { if (DBG_TRUST) log_debug ("TOFU: Looking up %s: %s\n", fingerprint, gpg_strerror (lookup_err)); } else { int is_signed_by_utk = signed_by_utk (email, kb); release_kbnode (kb); if (is_signed_by_utk) { effective_policy = TOFU_POLICY_GOOD; goto out; } } } } /* Check for any conflicts / see if a previously discovered conflict * disappeared. The latter can happen if the conflicting bindings * are now cross signed, for instance. */ conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); conflict_set_count = strlist_length (conflict_set); if (conflict_set_count == 0) { /* build_conflict_set should always at least return the current binding. Something went wrong. */ effective_policy = _tofu_GET_POLICY_ERROR; goto out; } if (conflict_set_count == 1 && (conflict_set->flags & BINDING_NEW)) { /* We've never observed a binding with this email address and we * have a default policy, which is not to ask the user. */ /* If we've seen this binding, then we've seen this email and * policy couldn't possibly be TOFU_POLICY_NONE. */ log_assert (policy == TOFU_POLICY_NONE); if (DBG_TRUST) log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n", fingerprint, email); effective_policy = TOFU_POLICY_AUTO; goto out; } if (conflict_set_count == 1 && (conflict_set->flags & BINDING_CONFLICT)) { /* No known conflicts now, but there was a conflict. This means * at some point, there was a conflict and we changed this * binding's policy to ask and set the conflicting key. The * conflict can go away if there is not a cross sig between the * two keys. In this case, just silently clear the conflict and * reset the policy to auto. */ if (DBG_TRUST) log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).\n", fingerprint, email); effective_policy = TOFU_POLICY_AUTO; conflict = NULL; goto out; } if (conflict_set_count == 1) { /* No conflicts and never marked as conflicting. */ log_assert (!conflict); effective_policy = TOFU_POLICY_AUTO; goto out; } /* There is a conflicting key. */ log_assert (conflict_set_count > 1); effective_policy = TOFU_POLICY_ASK; conflict = xstrdup (conflict_set->next->d); out: log_assert (policy == _tofu_GET_POLICY_ERROR || policy == TOFU_POLICY_NONE || policy == TOFU_POLICY_AUTO || policy == TOFU_POLICY_GOOD || policy == TOFU_POLICY_UNKNOWN || policy == TOFU_POLICY_BAD || policy == TOFU_POLICY_ASK); /* Everything but NONE. */ log_assert (effective_policy == _tofu_GET_POLICY_ERROR || effective_policy == TOFU_POLICY_AUTO || effective_policy == TOFU_POLICY_GOOD || effective_policy == TOFU_POLICY_UNKNOWN || effective_policy == TOFU_POLICY_BAD || effective_policy == TOFU_POLICY_ASK); if (effective_policy != TOFU_POLICY_ASK && conflict) conflict = NULL; /* If we don't have a record of this binding, its effective policy * changed, or conflict changed, update the DB. */ if (effective_policy != _tofu_GET_POLICY_ERROR && (/* New binding. */ policy == TOFU_POLICY_NONE /* effective_policy changed. */ || effective_policy != effective_policy_orig /* conflict changed. */ || (conflict != conflict_orig && (!conflict || !conflict_orig || strcmp (conflict, conflict_orig) != 0)))) { if (record_binding (dbs, fingerprint, email, user_id, policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy, effective_policy, conflict, 1, 0, now) != 0) log_error (_("error setting TOFU binding's policy" " to %s\n"), tofu_policy_str (policy)); } /* If the caller wants the set of conflicts, return it. */ if (effective_policy == TOFU_POLICY_ASK && conflict_setp) { if (! conflict_set) conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email); *conflict_setp = conflict_set; } else { free_strlist (conflict_set); if (conflict_setp) *conflict_setp = NULL; } xfree (conflict_orig); if (conflict != conflict_orig) xfree (conflict); free_strlist (results); return effective_policy; } /* Return the trust level (TRUST_NEVER, etc.) for the binding * <FINGERPRINT, EMAIL> (email is already normalized). If no policy * is registered, returns TOFU_POLICY_NONE. If an error occurs, * returns _tofu_GET_TRUST_ERROR. * * PK is the public key object for FINGERPRINT. * * USER_ID is the unadulterated user id. * * If MAY_ASK is set, then we may interact with the user. This is * necessary if there is a conflict or the binding's policy is * TOFU_POLICY_ASK. In the case of a conflict, we set the new * conflicting binding's policy to TOFU_POLICY_ASK. In either case, * we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this * function must not be called while in a transaction! */ static enum tofu_policy get_trust (ctrl_t ctrl, PKT_public_key *pk, const char *fingerprint, const char *email, const char *user_id, int may_ask, enum tofu_policy *policyp, strlist_t *conflict_setp, time_t now) { tofu_dbs_t dbs = ctrl->tofu.dbs; int in_transaction = 0; enum tofu_policy policy; int rc; char *sqerr = NULL; strlist_t conflict_set = NULL; int trust_level = TRUST_UNKNOWN; strlist_t iter; log_assert (dbs); if (may_ask) log_assert (dbs->in_transaction == 0); if (opt.batch) may_ask = 0; log_assert (pk_is_primary (pk)); /* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust levels. */ log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN && _tofu_GET_TRUST_ERROR != TRUST_EXPIRED && _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED && _tofu_GET_TRUST_ERROR != TRUST_NEVER && _tofu_GET_TRUST_ERROR != TRUST_MARGINAL && _tofu_GET_TRUST_ERROR != TRUST_FULLY && _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE); begin_transaction (ctrl, 0); in_transaction = 1; /* We need to call get_policy even if the key is ultimately trusted * to make sure the binding has been registered. */ policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, &conflict_set, now); if (policy == TOFU_POLICY_ASK) /* The conflict set should always contain at least one element: * the current key. */ log_assert (conflict_set); else /* If the policy is not TOFU_POLICY_ASK, then conflict_set will be * NULL. */ log_assert (! conflict_set); /* If the key is ultimately trusted, there is nothing to do. */ { u32 kid[2]; keyid_from_pk (pk, kid); if (tdb_keyid_is_utk (kid)) { trust_level = TRUST_ULTIMATE; policy = TOFU_POLICY_GOOD; goto out; } } if (policy == TOFU_POLICY_AUTO) { policy = opt.tofu_default_policy; if (DBG_TRUST) log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is" " auto (default: %s).\n", fingerprint, email, tofu_policy_str (opt.tofu_default_policy)); if (policy == TOFU_POLICY_ASK) /* The default policy is ASK, but there is no conflict (policy * was 'auto'). In this case, we need to make sure the * conflict set includes at least the current user id. */ { add_to_strlist (&conflict_set, fingerprint); } } switch (policy) { case TOFU_POLICY_AUTO: case TOFU_POLICY_GOOD: case TOFU_POLICY_UNKNOWN: case TOFU_POLICY_BAD: /* The saved judgement is auto -> auto, good, unknown or bad. * We don't need to ask the user anything. */ if (DBG_TRUST) log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n", fingerprint, email, tofu_policy_str (policy)); trust_level = tofu_policy_to_trust_level (policy); goto out; case TOFU_POLICY_ASK: /* We need to ask the user what to do. */ break; case _tofu_GET_POLICY_ERROR: trust_level = _tofu_GET_TRUST_ERROR; goto out; default: log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy); } /* We get here if: * * 1. The saved policy is auto and the default policy is ask * (get_policy() == TOFU_POLICY_AUTO * && opt.tofu_default_policy == TOFU_POLICY_ASK) * * 2. The saved policy is ask (either last time the user selected * accept once or reject once or there was a conflict and this * binding's policy was changed from auto to ask) * (policy == TOFU_POLICY_ASK). */ log_assert (policy == TOFU_POLICY_ASK); if (may_ask) { /* We can't be in a normal transaction in ask_about_binding. */ end_transaction (ctrl, 0); in_transaction = 0; /* If we get here, we need to ask the user about the binding. */ ask_about_binding (ctrl, &policy, &trust_level, conflict_set, fingerprint, email, user_id, now); } else { trust_level = TRUST_UNDEFINED; } /* Mark any conflicting bindings that have an automatic policy as * now requiring confirmation. Note: we do this after we ask for * confirmation so that when the current policy is printed, it is * correct. */ if (! in_transaction) { begin_transaction (ctrl, 0); in_transaction = 1; } /* The conflict set should always contain at least one element: * the current key. */ log_assert (conflict_set); for (iter = conflict_set->next; iter; iter = iter->next) { /* We don't immediately set the effective policy to 'ask, because */ rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &sqerr, "update bindings set effective_policy = %d, conflict = %Q" " where email = %Q and fingerprint = %Q and effective_policy != %d;", TOFU_POLICY_NONE, fingerprint, email, iter->d, TOFU_POLICY_ASK); if (rc) { log_error (_("error changing TOFU policy: %s\n"), sqerr); print_further_info ("binding: <key: %s, user id: %s>", fingerprint, user_id); sqlite3_free (sqerr); sqerr = NULL; rc = gpg_error (GPG_ERR_GENERAL); } else if (DBG_TRUST) log_debug ("Set %s to conflict with %s\n", iter->d, fingerprint); } out: if (in_transaction) end_transaction (ctrl, 0); if (policyp) *policyp = policy; if (conflict_setp) *conflict_setp = conflict_set; else free_strlist (conflict_set); return trust_level; } /* Return a malloced string of the form * "7~months" * The caller should replace all '~' in the returned string by a space * and also free the returned string. * * This is actually a bad hack which may not work correctly with all * languages. */ static char * time_ago_str (long long int t) { /* It would be nice to use a macro to do this, but gettext works on the unpreprocessed code. */ #define MIN_SECS (60) #define HOUR_SECS (60 * MIN_SECS) #define DAY_SECS (24 * HOUR_SECS) #define WEEK_SECS (7 * DAY_SECS) #define MONTH_SECS (30 * DAY_SECS) #define YEAR_SECS (365 * DAY_SECS) if (t > 2 * YEAR_SECS) { long long int c = t / YEAR_SECS; return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c); } if (t > 2 * MONTH_SECS) { long long int c = t / MONTH_SECS; return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c); } if (t > 2 * WEEK_SECS) { long long int c = t / WEEK_SECS; return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c); } if (t > 2 * DAY_SECS) { long long int c = t / DAY_SECS; return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c); } if (t > 2 * HOUR_SECS) { long long int c = t / HOUR_SECS; return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c); } if (t > 2 * MIN_SECS) { long long int c = t / MIN_SECS; return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c); } return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t); } /* If FP is NULL, write TOFU_STATS status line. If FP is not NULL * write a "tfs" record to that stream. */ static void write_stats_status (estream_t fp, enum tofu_policy policy, unsigned long signature_count, unsigned long signature_first_seen, unsigned long signature_most_recent, unsigned long signature_days, unsigned long encryption_count, unsigned long encryption_first_done, unsigned long encryption_most_recent, unsigned long encryption_days) { int summary; int validity; unsigned long days; /* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the sum of the magnitudes (m = a + b) to ensure a balance between verified signatures and encrypted messages. */ days = sqrtu32 (signature_days * signature_days + encryption_days * encryption_days); if (days < 1) validity = 1; /* Key without history. */ else if (days < 2 * BASIC_TRUST_THRESHOLD) validity = 2; /* Key with too little history. */ else if (days < 2 * FULL_TRUST_THRESHOLD) validity = 3; /* Key with enough history for basic trust. */ else validity = 4; /* Key with a lot of history. */ if (policy == TOFU_POLICY_ASK) summary = 0; /* Key requires attention. */ else summary = validity; if (fp) { es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n", summary, signature_count, encryption_count, tofu_policy_str (policy), signature_first_seen, signature_most_recent, encryption_first_done, encryption_most_recent, validity, signature_days, encryption_days); } else { write_status_printf (STATUS_TOFU_STATS, "%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu", summary, signature_count, encryption_count, tofu_policy_str (policy), signature_first_seen, signature_most_recent, encryption_first_done, encryption_most_recent, validity, signature_days, encryption_days); } } /* Note: If OUTFP is not NULL, this function merely prints a "tfs" record * to OUTFP. * * POLICY is the key's policy (as returned by get_policy). * * Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether * the caller should call show_warning after iterating over all user * ids. */ static int show_statistics (tofu_dbs_t dbs, const char *fingerprint, const char *email, enum tofu_policy policy, estream_t outfp, int only_status_fd, time_t now) { char *fingerprint_pp; int rc; strlist_t strlist = NULL; char *err = NULL; unsigned long signature_first_seen = 0; unsigned long signature_most_recent = 0; unsigned long signature_count = 0; unsigned long signature_days = 0; unsigned long encryption_first_done = 0; unsigned long encryption_most_recent = 0; unsigned long encryption_count = 0; unsigned long encryption_days = 0; int show_warning = 0; if (only_status_fd && ! is_status_enabled ()) return 0; fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0); /* Get the signature stats. */ rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*), coalesce (min (signatures.time), 0),\n" " coalesce (max (signatures.time), 0)\n" " from signatures\n" " left join bindings on signatures.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q;", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting signature statistics"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*) from\n" " (select round(signatures.time / (24 * 60 * 60)) day\n" " from signatures\n" " left join bindings on signatures.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q\n" " group by day);", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting signature statistics (by day)"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist) { /* We expect exactly 4 elements. */ log_assert (strlist->next); log_assert (strlist->next->next); log_assert (strlist->next->next->next); log_assert (! strlist->next->next->next->next); string_to_ulong (&signature_days, strlist->d, -1, __LINE__); string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__); string_to_ulong (&signature_first_seen, strlist->next->next->d, -1, __LINE__); string_to_ulong (&signature_most_recent, strlist->next->next->next->d, -1, __LINE__); free_strlist (strlist); strlist = NULL; } /* Get the encryption stats. */ rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*), coalesce (min (encryptions.time), 0),\n" " coalesce (max (encryptions.time), 0)\n" " from encryptions\n" " left join bindings on encryptions.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q;", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting encryption statistics"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } rc = gpgsql_exec_printf (dbs->db, strings_collect_cb, &strlist, &err, "select count (*) from\n" " (select round(encryptions.time / (24 * 60 * 60)) day\n" " from encryptions\n" " left join bindings on encryptions.binding = bindings.oid\n" " where fingerprint = %Q and email = %Q\n" " group by day);", fingerprint, email); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("getting encryption statistics (by day)"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); goto out; } if (strlist) { /* We expect exactly 4 elements. */ log_assert (strlist->next); log_assert (strlist->next->next); log_assert (strlist->next->next->next); log_assert (! strlist->next->next->next->next); string_to_ulong (&encryption_days, strlist->d, -1, __LINE__); string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__); string_to_ulong (&encryption_first_done, strlist->next->next->d, -1, __LINE__); string_to_ulong (&encryption_most_recent, strlist->next->next->next->d, -1, __LINE__); free_strlist (strlist); strlist = NULL; } if (!outfp) write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint, email, strlen (email), 0); write_stats_status (outfp, policy, signature_count, signature_first_seen, signature_most_recent, signature_days, encryption_count, encryption_first_done, encryption_most_recent, encryption_days); if (!outfp && !only_status_fd) { estream_t fp; char *msg; fp = es_fopenmem (0, "rw,samethread"); if (! fp) log_fatal ("error creating memory stream: %s\n", gpg_strerror (gpg_error_from_syserror())); if (signature_count == 0 && encryption_count == 0) { es_fprintf (fp, _("%s: Verified 0~signatures and encrypted 0~messages."), email); } else { if (signature_count == 0) es_fprintf (fp, _("%s: Verified 0 signatures."), email); else { /* TRANSLATORS: The final %s is replaced by a string like "7~months". */ char *ago_str = time_ago_str (now - signature_first_seen); es_fprintf (fp, ngettext("%s: Verified %ld~signature in the past %s.", "%s: Verified %ld~signatures in the past %s.", signature_count), email, signature_count, ago_str); xfree (ago_str); } es_fputs (" ", fp); if (encryption_count == 0) es_fprintf (fp, _("Encrypted 0 messages.")); else { char *ago_str = time_ago_str (now - encryption_first_done); /* TRANSLATORS: The final %s is replaced by a string like "7~months". */ es_fprintf (fp, ngettext("Encrypted %ld~message in the past %s.", "Encrypted %ld~messages in the past %s.", encryption_count), encryption_count, ago_str); xfree (ago_str); } } if (opt.verbose) { es_fputs (" ", fp); es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy)); } es_fputs ("\n", fp); { char *tmpmsg, *p; es_fputc (0, fp); if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL)) log_fatal ("error snatching memory stream\n"); msg = format_text (tmpmsg, 72, 80); es_free (tmpmsg); /* Print a status line but suppress the trailing LF. * Spaces are not percent escaped. */ if (*msg) write_status_buffer (STATUS_TOFU_STATS_LONG, msg, strlen (msg)-1, -1); /* Remove the non-breaking space markers. */ for (p=msg; *p; p++) if (*p == '~') *p = ' '; } log_string (GPGRT_LOG_INFO, msg); xfree (msg); if (policy == TOFU_POLICY_AUTO) { if (signature_count == 0) log_info (_("Warning: we have yet to see" " a message signed using this key and user id!\n")); else if (signature_count == 1) log_info (_("Warning: we've only seen one message" " signed using this key and user id!\n")); if (encryption_count == 0) log_info (_("Warning: you have yet to encrypt" " a message to this key!\n")); else if (encryption_count == 1) log_info (_("Warning: you have only encrypted" " one message to this key!\n")); /* Cf. write_stats_status */ if (sqrtu32 (encryption_count * encryption_count + signature_count * signature_count) < 2 * BASIC_TRUST_THRESHOLD) show_warning = 1; } } out: xfree (fingerprint_pp); return show_warning; } static void show_warning (const char *fingerprint, strlist_t user_id_list) { char *set_policy_command; char *text; char *tmpmsg; set_policy_command = xasprintf ("gpg --tofu-policy bad %s", fingerprint); tmpmsg = xasprintf (ngettext ("Warning: if you think you've seen more signatures " "by this key and user id, then this key might be a " "forgery! Carefully examine the email address for small " "variations. If the key is suspect, then use\n" " %s\n" "to mark it as being bad.\n", "Warning: if you think you've seen more signatures " "by this key and these user ids, then this key might be a " "forgery! Carefully examine the email addresses for small " "variations. If the key is suspect, then use\n" " %s\n" "to mark it as being bad.\n", strlist_length (user_id_list)), set_policy_command); text = format_text (tmpmsg, 72, 80); xfree (tmpmsg); log_string (GPGRT_LOG_INFO, text); xfree (text); es_free (set_policy_command); } /* Extract the email address from a user id and normalize it. If the user id doesn't contain an email address, then we use the whole user_id and normalize that. The returned string must be freed. */ static char * email_from_user_id (const char *user_id) { char *email = mailbox_from_userid (user_id); if (! email) { /* Hmm, no email address was provided or we are out of core. Just take the lower-case version of the whole user id. It could be a hostname, for instance. */ email = ascii_strlwr (xstrdup (user_id)); } return email; } /* Register the signature with the bindings <fingerprint, USER_ID>, for each USER_ID in USER_ID_LIST. The fingerprint is taken from the primary key packet PK. SIG_DIGEST_BIN is the binary representation of the message's digest. SIG_DIGEST_BIN_LEN is its length. SIG_TIME is the time that the signature was generated. ORIGIN is a free-formed string describing the origin of the signature. If this was from an email and the Claws MUA was used, then this should be something like: "email:claws". If this is NULL, the default is simply "unknown". If MAY_ASK is 1, then this function may interact with the user. This is necessary if there is a conflict or the binding's policy is TOFU_POLICY_ASK. This function returns 0 on success and an error code if an error occurred. */ gpg_error_t tofu_register_signature (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, const byte *sig_digest_bin, int sig_digest_bin_len, time_t sig_time, const char *origin) { time_t now = gnupg_get_time (); gpg_error_t rc; tofu_dbs_t dbs; char *fingerprint = NULL; strlist_t user_id; char *email = NULL; char *err = NULL; char *sig_digest; unsigned long c; dbs = opendbs (ctrl); if (! dbs) { rc = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (rc)); return rc; } /* We do a query and then an insert. Make sure they are atomic by wrapping them in a transaction. */ rc = begin_transaction (ctrl, 0); if (rc) return rc; log_assert (pk_is_primary (pk)); sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len); fingerprint = hexfingerprint (pk, NULL, 0); if (! origin) /* The default origin is simply "unknown". */ origin = "unknown"; for (user_id = user_id_list; user_id; user_id = user_id->next) { email = email_from_user_id (user_id->d); if (DBG_TRUST) log_debug ("TOFU: Registering signature %s with binding" " <key: %s, user id: %s>\n", sig_digest, fingerprint, email); /* Make sure the binding exists and record any TOFU conflicts. */ if (get_trust (ctrl, pk, fingerprint, email, user_id->d, 0, NULL, NULL, now) == _tofu_GET_TRUST_ERROR) { rc = gpg_error (GPG_ERR_GENERAL); xfree (email); break; } /* If we've already seen this signature before, then don't add it again. */ rc = gpgsql_stepx (dbs->db, &dbs->s.register_already_seen, get_single_unsigned_long_cb2, &c, &err, "select count (*)\n" " from signatures left join bindings\n" " on signatures.binding = bindings.oid\n" " where fingerprint = ? and email = ? and sig_time = ?\n" " and sig_digest = ?", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_LONG_LONG, (long long) sig_time, GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_END); if (rc) { log_error (_("error reading TOFU database: %s\n"), err); print_further_info ("checking existence"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); } else if (c > 1) /* Duplicates! This should not happen. In particular, because <fingerprint, email, sig_time, sig_digest> is the primary key! */ log_debug ("SIGNATURES DB contains duplicate records" " <key: %s, email: %s, time: 0x%lx, sig: %s," " origin: %s>." " Please report.\n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); else if (c == 1) { if (DBG_TRUST) log_debug ("Already observed the signature and binding" " <key: %s, email: %s, time: 0x%lx, sig: %s," " origin: %s>\n", fingerprint, email, (unsigned long) sig_time, sig_digest, origin); } else if (opt.dry_run) { log_info ("TOFU database update skipped due to --dry-run\n"); } else /* This is the first time that we've seen this signature and binding. Record it. */ { if (DBG_TRUST) log_debug ("TOFU: Saving signature" " <key: %s, user id: %s, sig: %s>\n", fingerprint, email, sig_digest); log_assert (c == 0); rc = gpgsql_stepx (dbs->db, &dbs->s.register_signature, NULL, NULL, &err, "insert into signatures\n" " (binding, sig_digest, origin, sig_time, time)\n" " values\n" " ((select oid from bindings\n" " where fingerprint = ? and email = ?),\n" " ?, ?, ?, ?);", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin, GPGSQL_ARG_LONG_LONG, (long long) sig_time, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), err); print_further_info ("insert signatures"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); } } xfree (email); if (rc) break; } if (rc) rollback_transaction (ctrl); else rc = end_transaction (ctrl, 0); xfree (fingerprint); xfree (sig_digest); return rc; } gpg_error_t tofu_register_encryption (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, int may_ask) { time_t now = gnupg_get_time (); gpg_error_t rc = 0; tofu_dbs_t dbs; kbnode_t kb = NULL; int free_user_id_list = 0; char *fingerprint = NULL; strlist_t user_id; char *err = NULL; dbs = opendbs (ctrl); if (! dbs) { rc = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (rc)); return rc; } if (/* We need the key block to find the primary key. */ ! pk_is_primary (pk) /* We need the key block to find all user ids. */ || ! user_id_list) kb = get_pubkeyblock (ctrl, pk->keyid); /* Make sure PK is a primary key. */ if (! pk_is_primary (pk)) pk = kb->pkt->pkt.public_key; if (! user_id_list) { /* Use all non-revoked user ids. Do use expired user ids. */ kbnode_t n = kb; while ((n = find_next_kbnode (n, PKT_USER_ID))) { PKT_user_id *uid = n->pkt->pkt.user_id; if (uid->flags.revoked) continue; add_to_strlist (&user_id_list, uid->name); } free_user_id_list = 1; if (! user_id_list) log_info (_("WARNING: Encrypting to %s, which has no " "non-revoked user ids\n"), keystr (pk->keyid)); } fingerprint = hexfingerprint (pk, NULL, 0); tofu_begin_batch_update (ctrl); tofu_resume_batch_transaction (ctrl); for (user_id = user_id_list; user_id; user_id = user_id->next) { char *email = email_from_user_id (user_id->d); strlist_t conflict_set = NULL; enum tofu_policy policy; /* Make sure the binding exists and that we recognize any conflicts. */ int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, may_ask, &policy, &conflict_set, now); if (tl == _tofu_GET_TRUST_ERROR) { /* An error. */ rc = gpg_error (GPG_ERR_GENERAL); xfree (email); goto die; } /* If there is a conflict and MAY_ASK is true, we need to show * the TOFU statistics for the current binding and the * conflicting bindings. But, if we are not in batch mode, then * they have already been printed (this is required to make sure * the information is available to the caller before cpr_get is * called). */ if (policy == TOFU_POLICY_ASK && may_ask && opt.batch) { strlist_t iter; /* The conflict set should contain at least the current * key. */ log_assert (conflict_set); for (iter = conflict_set; iter; iter = iter->next) show_statistics (dbs, iter->d, email, TOFU_POLICY_ASK, NULL, 1, now); } free_strlist (conflict_set); rc = gpgsql_stepx (dbs->db, &dbs->s.register_encryption, NULL, NULL, &err, "insert into encryptions\n" " (binding, time)\n" " values\n" " ((select oid from bindings\n" " where fingerprint = ? and email = ?),\n" " ?);", GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email, GPGSQL_ARG_LONG_LONG, (long long) now, GPGSQL_ARG_END); if (rc) { log_error (_("error updating TOFU database: %s\n"), err); print_further_info ("insert encryption"); sqlite3_free (err); rc = gpg_error (GPG_ERR_GENERAL); } xfree (email); } die: tofu_end_batch_update (ctrl); if (kb) release_kbnode (kb); if (free_user_id_list) free_strlist (user_id_list); xfree (fingerprint); return rc; } /* Combine a trust level returned from the TOFU trust model with a trust level returned by the PGP trust model. This is primarily of interest when the trust model is tofu+pgp (TM_TOFU_PGP). This function ors together the upper bits (the values not covered by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */ int tofu_wot_trust_combine (int tofu_base, int wot_base) { int tofu = tofu_base & TRUST_MASK; int wot = wot_base & TRUST_MASK; int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK); log_assert (tofu == TRUST_UNKNOWN || tofu == TRUST_EXPIRED || tofu == TRUST_UNDEFINED || tofu == TRUST_NEVER || tofu == TRUST_MARGINAL || tofu == TRUST_FULLY || tofu == TRUST_ULTIMATE); log_assert (wot == TRUST_UNKNOWN || wot == TRUST_EXPIRED || wot == TRUST_UNDEFINED || wot == TRUST_NEVER || wot == TRUST_MARGINAL || wot == TRUST_FULLY || wot == TRUST_ULTIMATE); /* We first consider negative trust policys. These trump positive trust policies. */ if (tofu == TRUST_NEVER || wot == TRUST_NEVER) /* TRUST_NEVER trumps everything else. */ return upper | TRUST_NEVER; if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED) /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ return upper | TRUST_EXPIRED; /* Now we only have positive or neutral trust policies. We take the max. */ if (tofu == TRUST_ULTIMATE) return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_ULTIMATE) return upper | TRUST_ULTIMATE; if (tofu == TRUST_FULLY) return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_FULLY) return upper | TRUST_FULLY; if (tofu == TRUST_MARGINAL) return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_MARGINAL) return upper | TRUST_MARGINAL; if (tofu == TRUST_UNDEFINED) return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED; if (wot == TRUST_UNDEFINED) return upper | TRUST_UNDEFINED; return upper | TRUST_UNKNOWN; } /* Write a "tfs" record for a --with-colons listing. */ gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, const char *user_id) { time_t now = gnupg_get_time (); gpg_error_t err; tofu_dbs_t dbs; char *fingerprint; char *email; enum tofu_policy policy; if (!*user_id) return 0; /* No TOFU stats possible for an empty ID. */ dbs = opendbs (ctrl); if (!dbs) { err = gpg_error (GPG_ERR_GENERAL); log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err)); return err; } fingerprint = hexfingerprint (pk, NULL, 0); email = email_from_user_id (user_id); policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now); show_statistics (dbs, fingerprint, email, policy, fp, 0, now); xfree (email); xfree (fingerprint); return 0; } /* Return the validity (TRUST_NEVER, etc.) of the bindings <FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST. If USER_ID_LIST->FLAG is set, then the id is considered to be expired. PK is the primary key packet. If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user will be prompted to choose a policy. If MAY_ASK is 0 and the policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned. Returns TRUST_UNDEFINED if an error occurs. */ int tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list, int may_ask) { time_t now = gnupg_get_time (); tofu_dbs_t dbs; char *fingerprint = NULL; strlist_t user_id; int trust_level = TRUST_UNKNOWN; int bindings = 0; int bindings_valid = 0; int need_warning = 0; int had_conflict = 0; dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return TRUST_UNDEFINED; } fingerprint = hexfingerprint (pk, NULL, 0); tofu_begin_batch_update (ctrl); /* Start the batch transaction now. */ tofu_resume_batch_transaction (ctrl); for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++) { char *email = email_from_user_id (user_id->d); strlist_t conflict_set = NULL; enum tofu_policy policy; /* Always call get_trust to make sure the binding is registered. */ int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d, may_ask, &policy, &conflict_set, now); if (tl == _tofu_GET_TRUST_ERROR) { /* An error. */ trust_level = TRUST_UNDEFINED; xfree (email); goto die; } if (DBG_TRUST) log_debug ("TOFU: validity for <key: %s, user id: %s>: %s%s.\n", fingerprint, email, trust_value_to_string (tl), user_id->flags ? " (but expired)" : ""); if (user_id->flags) tl = TRUST_EXPIRED; if (tl != TRUST_EXPIRED) bindings_valid ++; if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED) { /* If policy is ask, then we already printed out the * conflict information in ask_about_binding or will do so * in a moment. */ if (policy != TOFU_POLICY_ASK) need_warning |= show_statistics (dbs, fingerprint, email, policy, NULL, 0, now); /* If there is a conflict and MAY_ASK is true, we need to * show the TOFU statistics for the current binding and the * conflicting bindings. But, if we are not in batch mode, * then they have already been printed (this is required to * make sure the information is available to the caller * before cpr_get is called). */ if (policy == TOFU_POLICY_ASK && opt.batch) { strlist_t iter; /* The conflict set should contain at least the current * key. */ log_assert (conflict_set); had_conflict = 1; for (iter = conflict_set; iter; iter = iter->next) show_statistics (dbs, iter->d, email, TOFU_POLICY_ASK, NULL, 1, now); } } free_strlist (conflict_set); if (tl == TRUST_NEVER) trust_level = TRUST_NEVER; else if (tl == TRUST_EXPIRED) /* Ignore expired bindings in the trust calculation. */ ; else if (tl > trust_level) { /* The expected values: */ log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED || tl == TRUST_MARGINAL || tl == TRUST_FULLY || tl == TRUST_ULTIMATE); /* We assume the following ordering: */ log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED); log_assert (TRUST_UNDEFINED < TRUST_MARGINAL); log_assert (TRUST_MARGINAL < TRUST_FULLY); log_assert (TRUST_FULLY < TRUST_ULTIMATE); trust_level = tl; } xfree (email); } if (need_warning && ! had_conflict) show_warning (fingerprint, user_id_list); die: tofu_end_batch_update (ctrl); xfree (fingerprint); if (bindings_valid == 0) { if (DBG_TRUST) log_debug ("no (of %d) valid bindings." " Can't get TOFU validity for this set of user ids.\n", bindings); return TRUST_NEVER; } return trust_level; } /* Set the policy for all non-revoked user ids in the keyblock KB to POLICY. If no key is available with the specified key id, then this function returns GPG_ERR_NO_PUBKEY. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy) { gpg_error_t err = 0; time_t now = gnupg_get_time (); tofu_dbs_t dbs; PKT_public_key *pk; char *fingerprint = NULL; log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); pk = kb->pkt->pkt.public_key; dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } if (DBG_TRUST) log_debug ("Setting TOFU policy for %s to %s\n", keystr (pk->keyid), tofu_policy_str (policy)); if (! pk_is_primary (pk)) log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__); fingerprint = hexfingerprint (pk, NULL, 0); begin_transaction (ctrl, 0); for (; kb; kb = kb->next) { PKT_user_id *user_id; char *email; if (kb->pkt->pkttype != PKT_USER_ID) continue; user_id = kb->pkt->pkt.user_id; if (user_id->flags.revoked) /* Skip revoked user ids. (Don't skip expired user ids, the expiry can be changed.) */ continue; email = email_from_user_id (user_id->name); err = record_binding (dbs, fingerprint, email, user_id->name, policy, TOFU_POLICY_NONE, NULL, 0, 1, now); if (err) { log_error (_("error setting policy for key %s, user id \"%s\": %s"), fingerprint, email, gpg_strerror (err)); xfree (email); break; } xfree (email); } if (err) rollback_transaction (ctrl); else end_transaction (ctrl, 0); xfree (fingerprint); return err; } /* Return the TOFU policy for the specified binding in *POLICY. If no policy has been set for the binding, sets *POLICY to TOFU_POLICY_NONE. PK is a primary public key and USER_ID is a user id. Returns 0 on success and an error code otherwise. */ gpg_error_t tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, enum tofu_policy *policy) { time_t now = gnupg_get_time (); tofu_dbs_t dbs; char *fingerprint; char *email; /* Make sure PK is a primary key. */ log_assert (pk_is_primary (pk)); dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } fingerprint = hexfingerprint (pk, NULL, 0); email = email_from_user_id (user_id->name); *policy = get_policy (ctrl, dbs, pk, fingerprint, user_id->name, email, NULL, now); xfree (email); xfree (fingerprint); if (*policy == _tofu_GET_POLICY_ERROR) return gpg_error (GPG_ERR_GENERAL); return 0; } gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) { tofu_dbs_t dbs; PKT_public_key *pk; char *fingerprint; char *sqlerr = NULL; int rc; /* Make sure PK is a primary key. */ setup_main_keyids (kb); pk = kb->pkt->pkt.public_key; log_assert (pk_is_primary (pk)); dbs = opendbs (ctrl); if (! dbs) { log_error (_("error opening TOFU database: %s\n"), gpg_strerror (GPG_ERR_GENERAL)); return gpg_error (GPG_ERR_GENERAL); } fingerprint = hexfingerprint (pk, NULL, 0); rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr, "update bindings set effective_policy = ?" " where fingerprint = ?;", GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE, GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END); xfree (fingerprint); if (rc == _tofu_GET_POLICY_ERROR) return gpg_error (GPG_ERR_GENERAL); return 0; } diff --git a/g10/trustdb.c b/g10/trustdb.c index f8a0bc9b3..e2c3bdaa9 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -1,2250 +1,2250 @@ /* trustdb.c * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2008, 2012 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #ifndef DISABLE_REGEX #include <sys/types.h> #include <regex.h> #endif /* !DISABLE_REGEX */ #include "gpg.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "../common/util.h" #include "options.h" #include "packet.h" #include "main.h" #include "../common/mbox-util.h" #include "../common/i18n.h" #include "tdbio.h" #include "trustdb.h" #include "tofu.h" typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */ /* * Structure to keep track of keys, this is used as an array wherre * the item right after the last one has a keyblock set to NULL. * Maybe we can drop this thing and replace it by key_item */ struct key_array { KBNODE keyblock; }; /* Control information for the trust DB. */ static struct { int init; int level; char *dbname; int no_trustdb; } trustdb_args; /* Some globals. */ static struct key_item *user_utk_list; /* temp. used to store --trusted-keys */ static struct key_item *utk_list; /* all ultimately trusted keys */ static int pending_check_trustdb; static int validate_keys (ctrl_t ctrl, int interactive); /********************************************** ************* some helpers ******************* **********************************************/ static struct key_item * new_key_item (void) { struct key_item *k; k = xmalloc_clear (sizeof *k); return k; } static void release_key_items (struct key_item *k) { struct key_item *k2; for (; k; k = k2) { k2 = k->next; xfree (k->trust_regexp); xfree (k); } } #define KEY_HASH_TABLE_SIZE 1024 /* * For fast keylook up we need a hash table. Each byte of a KeyID * should be distributed equally over the 256 possible values (except * for v3 keyIDs but we consider them as not important here). So we * can just use 10 bits to index a table of KEY_HASH_TABLE_SIZE key items. * Possible optimization: Do not use key_items but other hash_table when the * duplicates lists get too large. */ static KeyHashTable new_key_hash_table (void) { struct key_item **tbl; tbl = xmalloc_clear (KEY_HASH_TABLE_SIZE * sizeof *tbl); return tbl; } static void release_key_hash_table (KeyHashTable tbl) { int i; if (!tbl) return; for (i=0; i < KEY_HASH_TABLE_SIZE; i++) release_key_items (tbl[i]); xfree (tbl); } /* * Returns: True if the keyID is in the given hash table */ static int test_key_hash_table (KeyHashTable tbl, u32 *kid) { struct key_item *k; for (k = tbl[(kid[1] % KEY_HASH_TABLE_SIZE)]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return 1; return 0; } /* * Add a new key to the hash table. The key is identified by its key ID. */ static void add_key_hash_table (KeyHashTable tbl, u32 *kid) { int i = kid[1] % KEY_HASH_TABLE_SIZE; struct key_item *k, *kk; for (k = tbl[i]; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return; /* already in table */ kk = new_key_item (); kk->kid[0] = kid[0]; kk->kid[1] = kid[1]; kk->next = tbl[i]; tbl[i] = kk; } /* * Release a key_array */ static void release_key_array ( struct key_array *keys ) { struct key_array *k; if (keys) { for (k=keys; k->keyblock; k++) release_kbnode (k->keyblock); xfree (keys); } } /********************************************* ********** Initialization ***************** *********************************************/ /* * Used to register extra ultimately trusted keys - this has to be done * before initializing the validation module. * FIXME: Should be replaced by a function to add those keys to the trustdb. */ void tdb_register_trusted_keyid (u32 *keyid) { struct key_item *k; k = new_key_item (); k->kid[0] = keyid[0]; k->kid[1] = keyid[1]; k->next = user_utk_list; user_utk_list = k; } void tdb_register_trusted_key( const char *string ) { gpg_error_t err; KEYDB_SEARCH_DESC desc; err = classify_user_id (string, &desc, 1); if (err || desc.mode != KEYDB_SEARCH_MODE_LONG_KID ) { log_error(_("'%s' is not a valid long keyID\n"), string ); return; } register_trusted_keyid(desc.u.kid); } /* * Helper to add a key to the global list of ultimately trusted keys. * Returns: true = inserted, false = already in list. */ static int add_utk (u32 *kid) { struct key_item *k; if (tdb_keyid_is_utk (kid)) return 0; k = new_key_item (); k->kid[0] = kid[0]; k->kid[1] = kid[1]; k->ownertrust = TRUST_ULTIMATE; k->next = utk_list; utk_list = k; if( opt.verbose > 1 ) log_info(_("key %s: accepted as trusted key\n"), keystr(kid)); return 1; } /**************** * Verify that all our secret keys are usable and put them into the utk_list. */ static void verify_own_keys (ctrl_t ctrl) { TRUSTREC rec; ulong recnum; int rc; struct key_item *k; if (utk_list) return; /* scan the trustdb to find all ultimately trusted keys */ for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) { if ( rec.rectype == RECTYPE_TRUST && (rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE) { byte *fpr = rec.r.trust.fingerprint; int fprlen; u32 kid[2]; /* Problem: We do only use fingerprints in the trustdb but * we need the keyID here to indetify the key; we can only * use that ugly hack to distinguish between 16 and 20 * butes fpr - it does not work always so we better change * the whole validation code to only work with * fingerprints */ fprlen = (!fpr[16] && !fpr[17] && !fpr[18] && !fpr[19])? 16:20; keyid_from_fingerprint (ctrl, fpr, fprlen, kid); if (!add_utk (kid)) log_info(_("key %s occurs more than once in the trustdb\n"), keystr(kid)); } } /* Put any --trusted-key keys into the trustdb */ for (k = user_utk_list; k; k = k->next) { if ( add_utk (k->kid) ) { /* not yet in trustDB as ultimately trusted */ PKT_public_key pk; memset (&pk, 0, sizeof pk); rc = get_pubkey (ctrl, &pk, k->kid); if (rc) log_info(_("key %s: no public key for trusted key - skipped\n"), keystr(k->kid)); else { tdb_update_ownertrust (ctrl, &pk, ((tdb_get_ownertrust (ctrl, &pk, 0) & ~TRUST_MASK) | TRUST_ULTIMATE )); release_public_key_parts (&pk); } log_info (_("key %s marked as ultimately trusted\n"),keystr(k->kid)); } } /* release the helper table table */ release_key_items (user_utk_list); user_utk_list = NULL; return; } /* Returns whether KID is on the list of ultimately trusted keys. */ int tdb_keyid_is_utk (u32 *kid) { struct key_item *k; for (k = utk_list; k; k = k->next) if (k->kid[0] == kid[0] && k->kid[1] == kid[1]) return 1; return 0; } /* Return the list of ultimately trusted keys. */ struct key_item * tdb_utks (void) { return utk_list; } /********************************************* *********** TrustDB stuff ******************* *********************************************/ /* * Read a record but die if it does not exist */ static void read_record (ulong recno, TRUSTREC *rec, int rectype ) { int rc = tdbio_read_record (recno, rec, rectype); if (rc) { log_error(_("trust record %lu, req type %d: read failed: %s\n"), recno, rec->rectype, gpg_strerror (rc) ); tdbio_invalid(); } if (rectype != rec->rectype) { log_error(_("trust record %lu is not of requested type %d\n"), rec->recnum, rectype); tdbio_invalid(); } } /* * Write a record and die on error */ static void write_record (ctrl_t ctrl, TRUSTREC *rec) { int rc = tdbio_write_record (ctrl, rec); if (rc) { log_error(_("trust record %lu, type %d: write failed: %s\n"), rec->recnum, rec->rectype, gpg_strerror (rc) ); tdbio_invalid(); } } /* * sync the TrustDb and die on error */ static void do_sync(void) { int rc = tdbio_sync (); if(rc) { log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) ); g10_exit(2); } } const char * trust_model_string (int model) { switch (model) { case TM_CLASSIC: return "classic"; case TM_PGP: return "pgp"; case TM_EXTERNAL: return "external"; case TM_TOFU: return "tofu"; case TM_TOFU_PGP: return "tofu+pgp"; case TM_ALWAYS: return "always"; case TM_DIRECT: return "direct"; default: return "unknown"; } } /**************** * Perform some checks over the trustdb * level 0: only open the db * 1: used for initial program startup */ int setup_trustdb( int level, const char *dbname ) { /* just store the args */ if( trustdb_args.init ) return 0; trustdb_args.level = level; trustdb_args.dbname = dbname? xstrdup(dbname): NULL; return 0; } void how_to_fix_the_trustdb () { const char *name = trustdb_args.dbname; if (!name) name = "trustdb.gpg"; log_info (_("You may try to re-create the trustdb using the commands:\n")); log_info (" cd %s\n", default_homedir ()); log_info (" %s --export-ownertrust > otrust.tmp\n", GPG_NAME); #ifdef HAVE_W32_SYSTEM log_info (" del %s\n", name); #else log_info (" rm %s\n", name); #endif log_info (" %s --import-ownertrust < otrust.tmp\n", GPG_NAME); log_info (_("If that does not work, please consult the manual\n")); } /* Initialize the trustdb. With NO_CREATE set a missing trustdb is * not an error and the function won't terminate the process on error; * in that case 0 is returned if there is a trustdb or an error code * if no trustdb is available. */ gpg_error_t init_trustdb (ctrl_t ctrl, int no_create) { int level = trustdb_args.level; const char* dbname = trustdb_args.dbname; if( trustdb_args.init ) return 0; trustdb_args.init = 1; if(level==0 || level==1) { int rc = tdbio_set_dbname (ctrl, dbname, (!no_create && level), &trustdb_args.no_trustdb); if (no_create && trustdb_args.no_trustdb) { /* No trustdb found and the caller asked us not to create * it. Return an error and set the initialization state * back so that we always test for an existing trustdb. */ trustdb_args.init = 0; return gpg_error (GPG_ERR_ENOENT); } if (rc) log_fatal("can't init trustdb: %s\n", gpg_strerror (rc) ); } else BUG(); if(opt.trust_model==TM_AUTO) { /* Try and set the trust model off of whatever the trustdb says it is. */ opt.trust_model=tdbio_read_model(); /* Sanity check this ;) */ if(opt.trust_model != TM_CLASSIC && opt.trust_model != TM_PGP && opt.trust_model != TM_TOFU_PGP && opt.trust_model != TM_TOFU && opt.trust_model != TM_EXTERNAL) { log_info(_("unable to use unknown trust model (%d) - " "assuming %s trust model\n"),opt.trust_model,"pgp"); opt.trust_model = TM_PGP; } if(opt.verbose) log_info(_("using %s trust model\n"), trust_model_string (opt.trust_model)); } if (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC || opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { /* Verify the list of ultimately trusted keys and move the --trusted-keys list there as well. */ if(level==1) verify_own_keys (ctrl); if(!tdbio_db_matches_options()) pending_check_trustdb=1; } return 0; } /* Check whether we have a trust database, initializing it if necessary if the trust model is not 'always trust'. Returns true if we do have a usable trust database. */ int have_trustdb (ctrl_t ctrl) { return !init_trustdb (ctrl, opt.trust_model == TM_ALWAYS); } /**************** * Recreate the WoT but do not ask for new ownertrusts. Special * feature: In batch mode and without a forced yes, this is only done * when a check is due. This can be used to run the check from a crontab */ void check_trustdb (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) { if (opt.batch && !opt.answer_yes) { ulong scheduled; scheduled = tdbio_read_nextcheck (); if (!scheduled) { log_info (_("no need for a trustdb check\n")); return; } if (scheduled > make_timestamp ()) { log_info (_("next trustdb check due at %s\n"), strtimestamp (scheduled)); return; } } validate_keys (ctrl, 0); } else log_info (_("no need for a trustdb check with '%s' trust model\n"), trust_model_string(opt.trust_model)); } /* * Recreate the WoT. */ void update_trustdb (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU) validate_keys (ctrl, 1); else log_info (_("no need for a trustdb update with '%s' trust model\n"), trust_model_string(opt.trust_model)); } void tdb_revalidation_mark (ctrl_t ctrl) { init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; /* We simply set the time for the next check to 1 (far back in 1970) so that a --update-trustdb will be scheduled. */ if (tdbio_write_nextcheck (ctrl, 1)) do_sync (); pending_check_trustdb = 1; } int trustdb_pending_check(void) { return pending_check_trustdb; } /* If the trustdb is dirty, and we're interactive, update it. Otherwise, check it unless no-auto-check-trustdb is set. */ void tdb_check_or_update (ctrl_t ctrl) { if (trustdb_pending_check ()) { if (opt.interactive) update_trustdb (ctrl); else if (!opt.no_auto_check_trustdb) check_trustdb (ctrl); } } void read_trust_options (ctrl_t ctrl, byte *trust_model, ulong *created, ulong *nextcheck, byte *marginals, byte *completes, byte *cert_depth, byte *min_cert_level) { TRUSTREC opts; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) memset (&opts, 0, sizeof opts); else read_record (0, &opts, RECTYPE_VER); if(trust_model) *trust_model=opts.r.ver.trust_model; if(created) *created=opts.r.ver.created; if(nextcheck) *nextcheck=opts.r.ver.nextcheck; if(marginals) *marginals=opts.r.ver.marginals; if(completes) *completes=opts.r.ver.completes; if(cert_depth) *cert_depth=opts.r.ver.cert_depth; if(min_cert_level) *min_cert_level=opts.r.ver.min_cert_level; } /*********************************************** *********** Ownertrust et al. **************** ***********************************************/ static int read_trust_record (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec) { int rc; init_trustdb (ctrl, 0); rc = tdbio_search_trust_bypk (pk, rec); if (rc) { if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND) log_error ("trustdb: searching trust record failed: %s\n", gpg_strerror (rc)); return rc; } if (rec->rectype != RECTYPE_TRUST) { log_error ("trustdb: record %lu is not a trust record\n", rec->recnum); return GPG_ERR_TRUSTDB; } return 0; } /* * Return the assigned ownertrust value for the given public key. The * key should be the primary key. If NO_CREATE is set a missing * trustdb will not be created. This comes for example handy when we * want to print status lines (DECRYPTION_KEY) which carry ownertrust * values but we usually use --always-trust. */ unsigned int tdb_get_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; /* If the caller asked not to create a trustdb we call init_trustdb * directly and allow it to fail with an error code for a * non-existing trustdb. */ if (no_create && init_trustdb (ctrl, 1)) return TRUST_UNKNOWN; err = read_trust_record (ctrl, pk, &rec); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) return TRUST_UNKNOWN; /* no record yet */ if (err) { tdbio_invalid (); return TRUST_UNKNOWN; /* actually never reached */ } return rec.r.trust.ownertrust; } unsigned int tdb_get_min_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; /* If the caller asked not to create a trustdb we call init_trustdb * directly and allow it to fail with an error code for a * non-existing trustdb. */ if (no_create && init_trustdb (ctrl, 1)) return TRUST_UNKNOWN; err = read_trust_record (ctrl, pk, &rec); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) return TRUST_UNKNOWN; /* no record yet */ if (err) { tdbio_invalid (); return TRUST_UNKNOWN; /* actually never reached */ } return rec.r.trust.min_ownertrust; } /* * Set the trust value of the given public key to the new value. * The key should be a primary one. */ void tdb_update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, unsigned int new_trust ) { TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) log_debug ("update ownertrust from %u to %u\n", (unsigned int)rec.r.trust.ownertrust, new_trust ); if (rec.r.trust.ownertrust != new_trust) { rec.r.trust.ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* no record yet - create a new one */ size_t dummy; if (DBG_TRUST) log_debug ("insert ownertrust %u\n", new_trust ); memset (&rec, 0, sizeof rec); rec.recnum = tdbio_new_recnum (ctrl); rec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); rec.r.trust.ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } else { tdbio_invalid (); } } static void update_min_ownertrust (ctrl_t ctrl, u32 *kid, unsigned int new_trust) { PKT_public_key *pk; TRUSTREC rec; gpg_error_t err; if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return; pk = xmalloc_clear (sizeof *pk); err = get_pubkey (ctrl, pk, kid); if (err) { log_error (_("public key %s not found: %s\n"), keystr (kid), gpg_strerror (err)); return; } err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) log_debug ("key %08lX%08lX: update min_ownertrust from %u to %u\n", (ulong)kid[0],(ulong)kid[1], (unsigned int)rec.r.trust.min_ownertrust, new_trust ); if (rec.r.trust.min_ownertrust != new_trust) { rec.r.trust.min_ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* no record yet - create a new one */ size_t dummy; if (DBG_TRUST) log_debug ("insert min_ownertrust %u\n", new_trust ); memset (&rec, 0, sizeof rec); rec.recnum = tdbio_new_recnum (ctrl); rec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, rec.r.trust.fingerprint, &dummy); rec.r.trust.min_ownertrust = new_trust; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); } else { tdbio_invalid (); } } /* * Clear the ownertrust and min_ownertrust values. * * Return: True if a change actually happened. */ int tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk) { TRUSTREC rec; gpg_error_t err; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return 0; err = read_trust_record (ctrl, pk, &rec); if (!err) { if (DBG_TRUST) { log_debug ("clearing ownertrust (old value %u)\n", (unsigned int)rec.r.trust.ownertrust); log_debug ("clearing min_ownertrust (old value %u)\n", (unsigned int)rec.r.trust.min_ownertrust); } if (rec.r.trust.ownertrust || rec.r.trust.min_ownertrust) { rec.r.trust.ownertrust = 0; rec.r.trust.min_ownertrust = 0; write_record (ctrl, &rec); tdb_revalidation_mark (ctrl); do_sync (); return 1; } } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); } return 0; } /* * Note: Caller has to do a sync */ static void update_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid, int depth, int validity) { TRUSTREC trec, vrec; gpg_error_t err; ulong recno; namehash_from_uid(uid); err = read_trust_record (ctrl, pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); return; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record yet - create a new one. */ size_t dummy; memset (&trec, 0, sizeof trec); trec.recnum = tdbio_new_recnum (ctrl); trec.rectype = RECTYPE_TRUST; fingerprint_from_pk (pk, trec.r.trust.fingerprint, &dummy); trec.r.trust.ownertrust = 0; } /* locate an existing one */ recno = trec.r.trust.validlist; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if ( !memcmp (vrec.r.valid.namehash, uid->namehash, 20) ) break; recno = vrec.r.valid.next; } if (!recno) /* insert a new validity record */ { memset (&vrec, 0, sizeof vrec); vrec.recnum = tdbio_new_recnum (ctrl); vrec.rectype = RECTYPE_VALID; memcpy (vrec.r.valid.namehash, uid->namehash, 20); vrec.r.valid.next = trec.r.trust.validlist; trec.r.trust.validlist = vrec.recnum; } vrec.r.valid.validity = validity; vrec.r.valid.full_count = uid->help_full_count; vrec.r.valid.marginal_count = uid->help_marginal_count; write_record (ctrl, &vrec); trec.r.trust.depth = depth; write_record (ctrl, &trec); } /*********************************************** ********* Query trustdb values ************** ***********************************************/ /* Return true if key is disabled. Note that this is usually used via the pk_is_disabled macro. */ int tdb_cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; TRUSTREC trec; int disabled = 0; if (pk->flags.disabled_valid) return pk->flags.disabled; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb) return 0; /* No trustdb => not disabled. */ err = read_trust_record (ctrl, pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); goto leave; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record found, so assume not disabled. */ goto leave; } if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED)) disabled = 1; /* Cache it for later so we don't need to look at the trustdb every time */ pk->flags.disabled = disabled; pk->flags.disabled_valid = 1; leave: return disabled; } void tdb_check_trustdb_stale (ctrl_t ctrl) { static int did_nextcheck=0; init_trustdb (ctrl, 0); if (trustdb_args.no_trustdb) return; /* No trustdb => can't be stale. */ if (!did_nextcheck && (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)) { ulong scheduled; did_nextcheck = 1; scheduled = tdbio_read_nextcheck (); if ((scheduled && scheduled <= make_timestamp ()) || pending_check_trustdb) { if (opt.no_auto_check_trustdb) { pending_check_trustdb = 1; if (!opt.quiet) log_info (_("please do a --check-trustdb\n")); } else { if (!opt.quiet) log_info (_("checking the trustdb\n")); validate_keys (ctrl, 0); } } } } /* * Return the validity information for KB/PK (at least one of them * must be non-NULL). This is the core of get_validity. If SIG is * not NULL, then the trust is being evaluated in the context of the * provided signature. This is used by the TOFU code to record * statistics. */ unsigned int tdb_get_validity_core (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, PKT_public_key *main_pk, PKT_signature *sig, int may_ask) { TRUSTREC trec, vrec; gpg_error_t err = 0; ulong recno; #ifdef USE_TOFU unsigned int tofu_validity = TRUST_UNKNOWN; int free_kb = 0; #endif unsigned int validity = TRUST_UNKNOWN; if (kb && pk) log_assert (keyid_cmp (pk_main_keyid (pk), pk_main_keyid (kb->pkt->pkt.public_key)) == 0); if (! pk) { log_assert (kb); pk = kb->pkt->pkt.public_key; } #ifndef USE_TOFU (void)sig; (void)may_ask; #endif init_trustdb (ctrl, 0); /* If we have no trustdb (which also means it has not been created) and the trust-model is always, we don't know the validity - return immediately. If we won't do that the tdbio code would try to open the trustdb and run into a fatal error. */ if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS) return TRUST_UNKNOWN; check_trustdb_stale (ctrl); if(opt.trust_model==TM_DIRECT) { /* Note that this happens BEFORE any user ID stuff is checked. The direct trust model applies to keys as a whole. */ validity = tdb_get_ownertrust (ctrl, main_pk, 0); goto leave; } #ifdef USE_TOFU if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { kbnode_t n = NULL; strlist_t user_id_list = NULL; int done = 0; /* If the caller didn't supply a user id then use all uids. */ if (! uid) { if (! kb) { kb = get_pubkeyblock (ctrl, main_pk->keyid); free_kb = 1; } n = kb; } if (DBG_TRUST && sig && sig->signers_uid) log_debug ("TOFU: only considering user id: '%s'\n", sig->signers_uid); while (!done && (uid || (n = find_next_kbnode (n, PKT_USER_ID)))) { PKT_user_id *user_id; int expired = 0; if (uid) { user_id = uid; /* If the caller specified a user id, then we only process the specified user id and are done after the first iteration. */ done = 1; } else user_id = n->pkt->pkt.user_id; if (user_id->attrib_data) /* Skip user attributes. */ continue; if (sig && sig->signers_uid) /* Make sure the UID matches. */ { char *email = mailbox_from_userid (user_id->name); if (!email || !*email || strcmp (sig->signers_uid, email) != 0) { if (DBG_TRUST) log_debug ("TOFU: skipping user id '%s', which does" " not match the signer's email ('%s')\n", email, sig->signers_uid); xfree (email); continue; } xfree (email); } /* If the user id is revoked or expired, then skip it. */ if (user_id->flags.revoked || user_id->flags.expired) { if (DBG_TRUST) { char *s; if (user_id->flags.revoked && user_id->flags.expired) s = "revoked and expired"; else if (user_id->flags.revoked) s = "revoked"; else s = "expire"; log_debug ("TOFU: Ignoring %s user id (%s)\n", s, user_id->name); } if (user_id->flags.revoked) continue; expired = 1; } add_to_strlist (&user_id_list, user_id->name); user_id_list->flags = expired; } /* Process the user ids in the order they appear in the key block. */ strlist_rev (&user_id_list); /* It only makes sense to observe any signature before getting the validity. This is because if the current signature results in a conflict, then we damn well want to take that into account. */ if (sig) { err = tofu_register_signature (ctrl, main_pk, user_id_list, sig->digest, sig->digest_len, sig->timestamp, "unknown"); if (err) { log_error ("TOFU: error registering signature: %s\n", gpg_strerror (err)); tofu_validity = TRUST_UNKNOWN; } } if (! err) tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list, may_ask); free_strlist (user_id_list); if (free_kb) release_kbnode (kb); } #endif /*USE_TOFU*/ if (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_PGP) { err = read_trust_record (ctrl, main_pk, &trec); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) { tdbio_invalid (); return 0; } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* No record found. */ validity = TRUST_UNKNOWN; goto leave; } /* Loop over all user IDs */ recno = trec.r.trust.validlist; validity = 0; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if(uid) { /* If a user ID is given we return the validity for that user ID ONLY. If the namehash is not found, then there is no validity at all (i.e. the user ID wasn't signed). */ if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) { validity=(vrec.r.valid.validity & TRUST_MASK); break; } } else { /* If no user ID is given, we take the maximum validity over all user IDs */ if (validity < (vrec.r.valid.validity & TRUST_MASK)) validity = (vrec.r.valid.validity & TRUST_MASK); } recno = vrec.r.valid.next; } if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED)) { validity |= TRUST_FLAG_DISABLED; pk->flags.disabled = 1; } else pk->flags.disabled = 0; pk->flags.disabled_valid = 1; } leave: #ifdef USE_TOFU validity = tofu_wot_trust_combine (tofu_validity, validity); #else /*!USE_TOFU*/ validity &= TRUST_MASK; if (validity == TRUST_NEVER) /* TRUST_NEVER trumps everything else. */ validity |= TRUST_NEVER; if (validity == TRUST_EXPIRED) /* TRUST_EXPIRED trumps everything but TRUST_NEVER. */ validity |= TRUST_EXPIRED; #endif /*!USE_TOFU*/ if (opt.trust_model != TM_TOFU && pending_check_trustdb) validity |= TRUST_FLAG_PENDING_CHECK; return validity; } static void get_validity_counts (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid) { TRUSTREC trec, vrec; ulong recno; if(pk==NULL || uid==NULL) BUG(); namehash_from_uid(uid); uid->help_marginal_count=uid->help_full_count=0; init_trustdb (ctrl, 0); if(read_trust_record (ctrl, pk, &trec)) return; /* loop over all user IDs */ recno = trec.r.trust.validlist; while (recno) { read_record (recno, &vrec, RECTYPE_VALID); if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0) { uid->help_marginal_count=vrec.r.valid.marginal_count; uid->help_full_count=vrec.r.valid.full_count; /* es_printf("Fetched marginal %d, full %d\n",uid->help_marginal_count,uid->help_full_count); */ break; } recno = vrec.r.valid.next; } } void list_trust_path( const char *username ) { (void)username; } /**************** * Enumerate all keys, which are needed to build all trust paths for * the given key. This function does not return the key itself or * the ultimate key (the last point in cerificate chain). Only * certificate chains which ends up at an ultimately trusted key * are listed. If ownertrust or validity is not NULL, the corresponding * value for the returned LID is also returned in these variable(s). * * 1) create a void pointer and initialize it to NULL * 2) pass this void pointer by reference to this function. * Set lid to the key you want to enumerate and pass it by reference. * 3) call this function as long as it does not return -1 * to indicate EOF. LID does contain the next key used to build the web * 4) Always call this function a last time with LID set to NULL, * so that it can free its context. * * Returns: -1 on EOF or the level of the returned LID */ int enum_cert_paths( void **context, ulong *lid, unsigned *ownertrust, unsigned *validity ) { (void)context; (void)lid; (void)ownertrust; (void)validity; return -1; } /**************** * Print the current path */ void enum_cert_paths_print (void **context, FILE *fp, int refresh, ulong selected_lid) { (void)context; (void)fp; (void)refresh; (void)selected_lid; } /**************************************** *********** NEW NEW NEW **************** ****************************************/ static int ask_ownertrust (ctrl_t ctrl, u32 *kid, int minimum) { PKT_public_key *pk; int rc; int ot; pk = xmalloc_clear (sizeof *pk); rc = get_pubkey (ctrl, pk, kid); if (rc) { log_error (_("public key %s not found: %s\n"), keystr(kid), gpg_strerror (rc) ); return TRUST_UNKNOWN; } if(opt.force_ownertrust) { log_info("force trust for key %s to %s\n", keystr(kid),trust_value_to_string(opt.force_ownertrust)); tdb_update_ownertrust (ctrl, pk, opt.force_ownertrust); ot=opt.force_ownertrust; } else { ot=edit_ownertrust (ctrl, pk, 0); if(ot>0) ot = tdb_get_ownertrust (ctrl, pk, 0); else if(ot==0) ot = minimum?minimum:TRUST_UNDEFINED; else ot = -1; /* quit */ } free_public_key( pk ); return ot; } static void mark_keyblock_seen (KeyHashTable tbl, KBNODE node) { for ( ;node; node = node->next ) if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { u32 aki[2]; keyid_from_pk (node->pkt->pkt.public_key, aki); add_key_hash_table (tbl, aki); } } static void dump_key_array (int depth, struct key_array *keys) { struct key_array *kar; for (kar=keys; kar->keyblock; kar++) { KBNODE node = kar->keyblock; u32 kid[2]; keyid_from_pk(node->pkt->pkt.public_key, kid); es_printf ("%d:%08lX%08lX:K::%c::::\n", depth, (ulong)kid[0], (ulong)kid[1], '?'); for (; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { int len = node->pkt->pkt.user_id->len; if (len > 30) len = 30; es_printf ("%d:%08lX%08lX:U:::%c:::", depth, (ulong)kid[0], (ulong)kid[1], (node->flag & 4)? 'f': (node->flag & 2)? 'm': (node->flag & 1)? 'q':'-'); es_write_sanitized (es_stdout, node->pkt->pkt.user_id->name, len, ":", NULL); es_putc (':', es_stdout); es_putc ('\n', es_stdout); } } } } static void store_validation_status (ctrl_t ctrl, int depth, kbnode_t keyblock, KeyHashTable stored) { KBNODE node; int status; int any = 0; for (node=keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; if (node->flag & 4) status = TRUST_FULLY; else if (node->flag & 2) status = TRUST_MARGINAL; else if (node->flag & 1) status = TRUST_UNDEFINED; else status = 0; if (status) { update_validity (ctrl, keyblock->pkt->pkt.public_key, uid, depth, status); mark_keyblock_seen(stored,keyblock); any = 1; } } } if (any) do_sync (); } /* Returns a sanitized copy of the regexp (which might be "", but not NULL). */ #ifndef DISABLE_REGEX static char * sanitize_regexp(const char *old) { size_t start=0,len=strlen(old),idx=0; int escaped=0,standard_bracket=0; char *new=xmalloc((len*2)+1); /* enough to \-escape everything if we have to */ /* There are basically two commonly-used regexps here. GPG and most versions of PGP use "<[^>]+[@.]example\.com>$" and PGP (9) - command line uses "example.com" (i.e. whatever the user specfies, + command line uses "example.com" (i.e. whatever the user specifies, and we can't expect users know to use "\." instead of "."). So here are the rules: we're allowed to start with "<[^>]+[@.]" and end with ">$" or start and end with nothing. In between, the only legal regex character is ".", and everything else gets escaped. Part of the gotcha here is that some regex packages allow more than RFC-4880 requires. For example, 4880 has no "{}" operator, but GNU regex does. Commenting removes these operators from consideration. A possible future enhancement is to use commenting to effectively back off a given regex to the Henry Spencer syntax in 4880. -dshaw */ /* Are we bracketed between "<[^>]+[@.]" and ">$" ? */ if(len>=12 && strncmp(old,"<[^>]+[@.]",10)==0 && old[len-2]=='>' && old[len-1]=='$') { strcpy(new,"<[^>]+[@.]"); idx=strlen(new); standard_bracket=1; start+=10; len-=2; } /* Walk the remaining characters and ensure that everything that is left is not an operational regex character. */ for(;start<len;start++) { if(!escaped && old[start]=='\\') escaped=1; else if(!escaped && old[start]!='.') new[idx++]='\\'; else escaped=0; new[idx++]=old[start]; } new[idx]='\0'; /* Note that the (sub)string we look at might end with a bare "\". If it does, leave it that way. If the regexp actually ended with ">$", then it was escaping the ">" and is fine. If the regexp actually ended with the bare "\", then it's an illegal regexp and regcomp should kick it out. */ if(standard_bracket) strcat(new,">$"); return new; } #endif /*!DISABLE_REGEX*/ /* Used by validate_one_keyblock to confirm a regexp within a trust signature. Returns 1 for match, and 0 for no match or regex error. */ static int check_regexp(const char *expr,const char *string) { #ifdef DISABLE_REGEX (void)expr; (void)string; /* When DISABLE_REGEX is defined, assume all regexps do not match. */ return 0; #else int ret; char *regexp; regexp=sanitize_regexp(expr); #ifdef __riscos__ ret=riscos_check_regexp(expr, string, DBG_TRUST); #else { regex_t pat; ret=regcomp(&pat,regexp,REG_ICASE|REG_NOSUB|REG_EXTENDED); if(ret==0) { ret=regexec(&pat,string,0,NULL,0); regfree(&pat); } ret=(ret==0); } #endif if(DBG_TRUST) log_debug("regexp '%s' ('%s') on '%s': %s\n", regexp,expr,string,ret?"YES":"NO"); xfree(regexp); return ret; #endif } /* * Return true if the key is signed by one of the keys in the given * key ID list. User IDs with a valid signature are marked by node * flags as follows: * flag bit 0: There is at least one signature * 1: There is marginal confidence that this is a legitimate uid * 2: There is full confidence that this is a legitimate uid. * 8: Used for internal purposes. * 9: Ditto (in mark_usable_uid_certs()) * 10: Ditto (ditto) * This function assumes that all kbnode flags are cleared on entry. */ static int validate_one_keyblock (ctrl_t ctrl, kbnode_t kb, struct key_item *klist, u32 curtime, u32 *next_expire) { struct key_item *kr; KBNODE node, uidnode=NULL; PKT_user_id *uid=NULL; PKT_public_key *pk = kb->pkt->pkt.public_key; u32 main_kid[2]; int issigned=0, any_signed = 0; keyid_from_pk(pk, main_kid); for (node=kb; node; node = node->next) { /* A bit of discussion here: is it better for the web of trust to be built among only self-signed uids? On the one hand, a self-signed uid is a statement that the key owner definitely intended that uid to be there, but on the other hand, a signed (but not self-signed) uid does carry trust, of a sort, even if it is a statement being made by people other than the key owner "through" the uids on the key owner's key. I'm going with the latter. However, if the user ID was explicitly revoked, or passively allowed to expire, that should stop validity through the user ID until it is resigned. -dshaw */ if (node->pkt->pkttype == PKT_USER_ID && !node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired) { if (uidnode && issigned) { if (uid->help_full_count >= opt.completes_needed || uid->help_marginal_count >= opt.marginals_needed ) uidnode->flag |= 4; else if (uid->help_full_count || uid->help_marginal_count) uidnode->flag |= 2; uidnode->flag |= 1; any_signed = 1; } uidnode = node; uid=uidnode->pkt->pkt.user_id; /* If the selfsig is going to expire... */ if(uid->expiredate && uid->expiredate<*next_expire) *next_expire = uid->expiredate; issigned = 0; get_validity_counts (ctrl, pk, uid); mark_usable_uid_certs (ctrl, kb, uidnode, main_kid, klist, curtime, next_expire); } else if (node->pkt->pkttype == PKT_SIGNATURE && (node->flag & (1<<8)) && uid) { /* Note that we are only seeing unrevoked sigs here */ PKT_signature *sig = node->pkt->pkt.signature; kr = is_in_klist (klist, sig); /* If the trust_regexp does not match, it's as if the sig did not exist. This is safe for non-trust sigs as well since we don't accept a regexp on the sig unless it's a trust sig. */ if (kr && (!kr->trust_regexp || !(opt.trust_model == TM_PGP || opt.trust_model == TM_TOFU_PGP) || (uidnode && check_regexp(kr->trust_regexp, uidnode->pkt->pkt.user_id->name)))) { /* Are we part of a trust sig chain? We always favor the latest trust sig, rather than the greater or lesser trust sig or value. I could make a decent argument for any of these cases, but this seems to be what PGP does, and I'd like to be compatible. -dms */ if ((opt.trust_model == TM_PGP || opt.trust_model == TM_TOFU_PGP) && sig->trust_depth && pk->trust_timestamp <= sig->timestamp) { unsigned char depth; /* If the depth on the signature is less than the chain currently has, then use the signature depth so we don't increase the depth beyond what the signer wanted. If the depth on the signature is more than the chain currently has, then use the chain depth so we use as much of the signature depth as the chain will permit. An ultimately trusted signature can restart the depth to whatever level it likes. */ if (sig->trust_depth < kr->trust_depth || kr->ownertrust == TRUST_ULTIMATE) depth = sig->trust_depth; else depth = kr->trust_depth; if (depth) { if(DBG_TRUST) log_debug ("trust sig on %s, sig depth is %d," " kr depth is %d\n", uidnode->pkt->pkt.user_id->name, sig->trust_depth, kr->trust_depth); /* If we got here, we know that: this is a trust sig. it's a newer trust sig than any previous trust sig on this key (not uid). it is legal in that it was either generated by an ultimate key, or a key that was part of a trust chain, and the depth does not violate the original trust sig. if there is a regexp attached, it matched successfully. */ if (DBG_TRUST) log_debug ("replacing trust value %d with %d and " "depth %d with %d\n", pk->trust_value,sig->trust_value, pk->trust_depth,depth); pk->trust_value = sig->trust_value; pk->trust_depth = depth-1; /* If the trust sig contains a regexp, record it on the pk for the next round. */ if (sig->trust_regexp) pk->trust_regexp = sig->trust_regexp; } } if (kr->ownertrust == TRUST_ULTIMATE) uid->help_full_count = opt.completes_needed; else if (kr->ownertrust == TRUST_FULLY) uid->help_full_count++; else if (kr->ownertrust == TRUST_MARGINAL) uid->help_marginal_count++; issigned = 1; } } } if (uidnode && issigned) { if (uid->help_full_count >= opt.completes_needed || uid->help_marginal_count >= opt.marginals_needed ) uidnode->flag |= 4; else if (uid->help_full_count || uid->help_marginal_count) uidnode->flag |= 2; uidnode->flag |= 1; any_signed = 1; } return any_signed; } static int search_skipfnc (void *opaque, u32 *kid, int dummy_uid_no) { (void)dummy_uid_no; return test_key_hash_table ((KeyHashTable)opaque, kid); } /* * Scan all keys and return a key_array of all suitable keys from * kllist. The caller has to pass keydb handle so that we don't use * to create our own. Returns either a key_array or NULL in case of * an error. No results found are indicated by an empty array. * Caller hast to release the returned array. */ static struct key_array * validate_key_list (ctrl_t ctrl, KEYDB_HANDLE hd, KeyHashTable full_trust, struct key_item *klist, u32 curtime, u32 *next_expire) { KBNODE keyblock = NULL; struct key_array *keys = NULL; size_t nkeys, maxkeys; int rc; KEYDB_SEARCH_DESC desc; maxkeys = 1000; keys = xmalloc ((maxkeys+1) * sizeof *keys); nkeys = 0; rc = keydb_search_reset (hd); if (rc) { log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc)); xfree (keys); return NULL; } memset (&desc, 0, sizeof desc); desc.mode = KEYDB_SEARCH_MODE_FIRST; desc.skipfnc = search_skipfnc; desc.skipfncvalue = full_trust; rc = keydb_search (hd, &desc, 1, NULL); if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { keys[nkeys].keyblock = NULL; return keys; } if (rc) { log_error ("keydb_search(first) failed: %s\n", gpg_strerror (rc)); goto die; } desc.mode = KEYDB_SEARCH_MODE_NEXT; /* change mode */ do { PKT_public_key *pk; rc = keydb_get_keyblock (hd, &keyblock); if (rc) { log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc)); goto die; } if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY) { log_debug ("ooops: invalid pkttype %d encountered\n", keyblock->pkt->pkttype); dump_kbnode (keyblock); release_kbnode(keyblock); continue; } /* prepare the keyblock for further processing */ merge_keys_and_selfsig (ctrl, keyblock); clear_kbnode_flags (keyblock); pk = keyblock->pkt->pkt.public_key; if (pk->has_expired || pk->flags.revoked) { /* it does not make sense to look further at those keys */ mark_keyblock_seen (full_trust, keyblock); } else if (validate_one_keyblock (ctrl, keyblock, klist, curtime, next_expire)) { KBNODE node; if (pk->expiredate && pk->expiredate >= curtime && pk->expiredate < *next_expire) *next_expire = pk->expiredate; if (nkeys == maxkeys) { maxkeys += 1000; keys = xrealloc (keys, (maxkeys+1) * sizeof *keys); } keys[nkeys++].keyblock = keyblock; /* Optimization - if all uids are fully trusted, then we never need to consider this key as a candidate again. */ for (node=keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & 4)) break; if(node==NULL) mark_keyblock_seen (full_trust, keyblock); keyblock = NULL; } release_kbnode (keyblock); keyblock = NULL; } while (!(rc = keydb_search (hd, &desc, 1, NULL))); if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND) { log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); goto die; } keys[nkeys].keyblock = NULL; return keys; die: keys[nkeys].keyblock = NULL; release_key_array (keys); return NULL; } /* Caller must sync */ static void reset_trust_records (ctrl_t ctrl) { TRUSTREC rec; ulong recnum; int count = 0, nreset = 0; for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ ) { if(rec.rectype==RECTYPE_TRUST) { count++; if(rec.r.trust.min_ownertrust) { rec.r.trust.min_ownertrust=0; write_record (ctrl, &rec); } } else if(rec.rectype==RECTYPE_VALID && ((rec.r.valid.validity&TRUST_MASK) || rec.r.valid.marginal_count || rec.r.valid.full_count)) { rec.r.valid.validity &= ~TRUST_MASK; rec.r.valid.marginal_count=rec.r.valid.full_count=0; nreset++; write_record (ctrl, &rec); } } if (opt.verbose) { log_info (ngettext("%d key processed", "%d keys processed", count), count); log_printf (ngettext(" (%d validity count cleared)\n", " (%d validity counts cleared)\n", nreset), nreset); } } /* * Run the key validation procedure. * * This works this way: * Step 1: Find all ultimately trusted keys (UTK). * mark them all as seen and put them into klist. * Step 2: loop max_cert_times * Step 3: if OWNERTRUST of any key in klist is undefined * ask user to assign ownertrust * Step 4: Loop over all keys in the keyDB which are not marked seen * Step 5: if key is revoked or expired * mark key as seen * continue loop at Step 4 * Step 6: For each user ID of that key signed by a key in klist * Calculate validity by counting trusted signatures. * Set validity of user ID * Step 7: If any signed user ID was found * mark key as seen * End Loop * Step 8: Build a new klist from all fully trusted keys from step 6 * End Loop * Ready * */ static int validate_keys (ctrl_t ctrl, int interactive) { int rc = 0; int quit=0; struct key_item *klist = NULL; struct key_item *k; struct key_array *keys = NULL; struct key_array *kar; KEYDB_HANDLE kdb = NULL; KBNODE node; int depth; int ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate; KeyHashTable stored,used,full_trust; u32 start_time, next_expire; /* Make sure we have all sigs cached. TODO: This is going to require some architectural re-thinking, as it is agonizingly slow. Perhaps combine this with reset_trust_records(), or only check the caches on keys that are actually involved in the web of trust. */ keydb_rebuild_caches (ctrl, 0); kdb = keydb_new (); if (!kdb) return gpg_error_from_syserror (); start_time = make_timestamp (); next_expire = 0xffffffff; /* set next expire to the year 2106 */ stored = new_key_hash_table (); used = new_key_hash_table (); full_trust = new_key_hash_table (); reset_trust_records (ctrl); /* Fixme: Instead of always building a UTK list, we could just build it * here when needed */ if (!utk_list) { if (!opt.quiet) log_info (_("no ultimately trusted keys found\n")); goto leave; } /* mark all UTKs as used and fully_trusted and set validity to ultimate */ for (k=utk_list; k; k = k->next) { KBNODE keyblock; PKT_public_key *pk; keyblock = get_pubkeyblock (ctrl, k->kid); if (!keyblock) { log_error (_("public key of ultimately" " trusted key %s not found\n"), keystr(k->kid)); continue; } mark_keyblock_seen (used, keyblock); mark_keyblock_seen (stored, keyblock); mark_keyblock_seen (full_trust, keyblock); pk = keyblock->pkt->pkt.public_key; for (node=keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) update_validity (ctrl, pk, node->pkt->pkt.user_id, 0, TRUST_ULTIMATE); } if ( pk->expiredate && pk->expiredate >= start_time && pk->expiredate < next_expire) next_expire = pk->expiredate; release_kbnode (keyblock); do_sync (); } if (opt.trust_model == TM_TOFU) /* In the TOFU trust model, we only need to save the ultimately trusted keys. */ goto leave; klist = utk_list; if (!opt.quiet) log_info ("marginals needed: %d completes needed: %d trust model: %s\n", opt.marginals_needed, opt.completes_needed, trust_model_string (opt.trust_model)); for (depth=0; depth < opt.max_cert_depth; depth++) { int valids=0,key_count; /* See whether we should assign ownertrust values to the keys in klist. */ ot_unknown = ot_undefined = ot_never = 0; ot_marginal = ot_full = ot_ultimate = 0; for (k=klist; k; k = k->next) { int min=0; /* 120 and 60 are as per RFC2440 */ if(k->trust_value>=120) min=TRUST_FULLY; else if(k->trust_value>=60) min=TRUST_MARGINAL; if(min!=k->min_ownertrust) update_min_ownertrust (ctrl, k->kid,min); if (interactive && k->ownertrust == TRUST_UNKNOWN) { k->ownertrust = ask_ownertrust (ctrl, k->kid,min); if (k->ownertrust == (unsigned int)(-1)) { quit=1; goto leave; } } /* This can happen during transition from an old trustdb before trust sigs. It can also happen if a user uses two different versions of GnuPG or changes the --trust-model setting. */ if(k->ownertrust<min) { if(DBG_TRUST) log_debug("key %08lX%08lX:" " overriding ownertrust '%s' with '%s'\n", (ulong)k->kid[0],(ulong)k->kid[1], trust_value_to_string(k->ownertrust), trust_value_to_string(min)); k->ownertrust=min; } if (k->ownertrust == TRUST_UNKNOWN) ot_unknown++; else if (k->ownertrust == TRUST_UNDEFINED) ot_undefined++; else if (k->ownertrust == TRUST_NEVER) ot_never++; else if (k->ownertrust == TRUST_MARGINAL) ot_marginal++; else if (k->ownertrust == TRUST_FULLY) ot_full++; else if (k->ownertrust == TRUST_ULTIMATE) ot_ultimate++; valids++; } /* Find all keys which are signed by a key in kdlist */ keys = validate_key_list (ctrl, kdb, full_trust, klist, start_time, &next_expire); if (!keys) { log_error ("validate_key_list failed\n"); rc = GPG_ERR_GENERAL; goto leave; } for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++) ; /* Store the calculated valididation status somewhere */ if (opt.verbose > 1 && DBG_TRUST) dump_key_array (depth, keys); for (kar=keys; kar->keyblock; kar++) store_validation_status (ctrl, depth, kar->keyblock, stored); if (!opt.quiet) log_info (_("depth: %d valid: %3d signed: %3d" " trust: %d-, %dq, %dn, %dm, %df, %du\n"), depth, valids, key_count, ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate ); /* Build a new kdlist from all fully valid keys in KEYS */ if (klist != utk_list) release_key_items (klist); klist = NULL; for (kar=keys; kar->keyblock; kar++) { for (node=kar->keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && (node->flag & 4)) { u32 kid[2]; /* have we used this key already? */ keyid_from_pk (kar->keyblock->pkt->pkt.public_key, kid); if(test_key_hash_table(used,kid)==0) { /* Normally we add both the primary and subkey ids to the hash via mark_keyblock_seen, but since we aren't using this hash as a skipfnc, that doesn't matter here. */ add_key_hash_table (used,kid); k = new_key_item (); k->kid[0]=kid[0]; k->kid[1]=kid[1]; k->ownertrust = (tdb_get_ownertrust (ctrl, kar->keyblock->pkt->pkt.public_key, 0) & TRUST_MASK); k->min_ownertrust = tdb_get_min_ownertrust (ctrl, kar->keyblock->pkt->pkt.public_key, 0); k->trust_depth= kar->keyblock->pkt->pkt.public_key->trust_depth; k->trust_value= kar->keyblock->pkt->pkt.public_key->trust_value; if(kar->keyblock->pkt->pkt.public_key->trust_regexp) k->trust_regexp= xstrdup(kar->keyblock->pkt-> pkt.public_key->trust_regexp); k->next = klist; klist = k; break; } } } } release_key_array (keys); keys = NULL; if (!klist) break; /* no need to dive in deeper */ } leave: keydb_release (kdb); release_key_array (keys); if (klist != utk_list) release_key_items (klist); release_key_hash_table (full_trust); release_key_hash_table (used); release_key_hash_table (stored); if (!rc && !quit) /* mark trustDB as checked */ { int rc2; if (next_expire == 0xffffffff || next_expire < start_time ) tdbio_write_nextcheck (ctrl, 0); else { tdbio_write_nextcheck (ctrl, next_expire); if (!opt.quiet) log_info (_("next trustdb check due at %s\n"), strtimestamp (next_expire)); } rc2 = tdbio_update_version_record (ctrl); if (rc2) { log_error (_("unable to update trustdb version record: " "write failed: %s\n"), gpg_strerror (rc2)); tdbio_invalid (); } do_sync (); pending_check_trustdb = 0; } return rc; } diff --git a/g10/verify.c b/g10/verify.c index 4399f7111..caeb1a244 100644 --- a/g10/verify.c +++ b/g10/verify.c @@ -1,275 +1,275 @@ /* verify.c - Verify signed data * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006, * 2007, 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "../common/util.h" #include "main.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" /**************** * Assume that the input is a signature and verify it without * generating any output. With no arguments, the signature packet * is read from stdin (it may be a detached signature when not * used in batch mode). If only a sigfile is given, it may be a complete * signature or a detached signature in which case the signed stuff * is expected from stdin. With more than 1 argument, the first should * be a detached signature and the remaining files are the signed stuff. */ int verify_signatures (ctrl_t ctrl, int nfiles, char **files ) { IOBUF fp; armor_filter_context_t *afx = NULL; progress_filter_context_t *pfx = new_progress_context (); const char *sigfile; int i, rc; strlist_t sl; /* Decide whether we should handle a detached or a normal signature, * which is needed so that the code later can hash the correct data and * not have a normal signature act as detached signature and ignoring the - * indended signed material from the 2nd file or stdin. + * intended signed material from the 2nd file or stdin. * 1. gpg <file - normal * 2. gpg file - normal (or detached) * 3. gpg file <file2 - detached * 4. gpg file file2 - detached * The question is how decide between case 2 and 3? The only way * we can do it is by reading one byte from stdin and then unget * it; the problem here is that we may be reading from the * terminal (which could be detected using isatty() but won't work * when under contol of a pty using program (e.g. expect)) and * might get us in trouble when stdin is used for another purpose * (--passphrase-fd 0). So we have to break with the behaviour * prior to gpg 1.0.4 by assuming that case 3 is a normal * signature (where file2 is ignored and require for a detached * signature to indicate signed material comes from stdin by using * case 4 with a file2 of "-". * * Actually we don't have to change anything here but can handle * that all quite easily in mainproc.c */ sigfile = nfiles? *files : NULL; /* open the signature file */ fp = iobuf_open(sigfile); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if( !fp ) { rc = gpg_error_from_syserror (); log_error(_("can't open '%s': %s\n"), print_fname_stdin(sigfile), gpg_strerror (rc)); goto leave; } handle_progress (pfx, fp, sigfile); if ( !opt.no_armor && use_armor_filter( fp ) ) { afx = new_armor_context (); push_armor_filter (afx, fp); } sl = NULL; for(i=nfiles-1 ; i > 0 ; i-- ) add_to_strlist( &sl, files[i] ); rc = proc_signature_packets (ctrl, NULL, fp, sl, sigfile ); free_strlist(sl); iobuf_close(fp); if( (afx && afx->no_openpgp_data && rc == -1) || gpg_err_code (rc) == GPG_ERR_NO_DATA ) { log_error(_("the signature could not be verified.\n" "Please remember that the signature file (.sig or .asc)\n" "should be the first file given on the command line.\n") ); rc = 0; } leave: release_armor_context (afx); release_progress_context (pfx); return rc; } void print_file_status( int status, const char *name, int what ) { char *p = xmalloc(strlen(name)+10); sprintf(p, "%d %s", what, name ); write_status_text( status, p ); xfree(p); } static int verify_one_file (ctrl_t ctrl, const char *name ) { IOBUF fp; armor_filter_context_t *afx = NULL; progress_filter_context_t *pfx = new_progress_context (); int rc; print_file_status( STATUS_FILE_START, name, 1 ); fp = iobuf_open(name); if (fp) iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL); if (fp && is_secured_file (iobuf_get_fd (fp))) { iobuf_close (fp); fp = NULL; gpg_err_set_errno (EPERM); } if( !fp ) { rc = gpg_error_from_syserror (); log_error(_("can't open '%s': %s\n"), print_fname_stdin(name), strerror (errno)); print_file_status( STATUS_FILE_ERROR, name, 1 ); goto leave; } handle_progress (pfx, fp, name); if( !opt.no_armor ) { if( use_armor_filter( fp ) ) { afx = new_armor_context (); push_armor_filter (afx, fp); } } rc = proc_signature_packets (ctrl, NULL, fp, NULL, name ); iobuf_close(fp); write_status( STATUS_FILE_DONE ); reset_literals_seen(); leave: release_armor_context (afx); release_progress_context (pfx); return rc; } /**************** * Verify each file given in the files array or read the names of the * files from stdin. * Note: This function can not handle detached signatures. */ int verify_files (ctrl_t ctrl, int nfiles, char **files ) { int i; if( !nfiles ) { /* read the filenames from stdin */ char line[2048]; unsigned int lno = 0; while( fgets(line, DIM(line), stdin) ) { lno++; if( !*line || line[strlen(line)-1] != '\n' ) { log_error(_("input line %u too long or missing LF\n"), lno ); return GPG_ERR_GENERAL; } /* This code does not work on MSDOS but how cares there are * also no script languages available. We don't strip any * spaces, so that we can process nearly all filenames */ line[strlen(line)-1] = 0; verify_one_file (ctrl, line ); } } else { /* take filenames from the array */ for(i=0; i < nfiles; i++ ) verify_one_file (ctrl, files[i] ); } return 0; } /* Perform a verify operation. To verify detached signatures, DATA_FD shall be the descriptor of the signed data; for regular signatures it needs to be -1. If OUT_FP is not NULL and DATA_FD is not -1 the the signed material gets written that stream. FIXME: OUTFP is not yet implemented. */ int gpg_verify (ctrl_t ctrl, int sig_fd, int data_fd, estream_t out_fp) { int rc; iobuf_t fp; armor_filter_context_t *afx = NULL; progress_filter_context_t *pfx = new_progress_context (); (void)ctrl; (void)out_fp; if (is_secured_file (sig_fd)) { fp = NULL; gpg_err_set_errno (EPERM); } else fp = iobuf_fdopen_nc (sig_fd, "rb"); if (!fp) { rc = gpg_error_from_syserror (); log_error (_("can't open fd %d: %s\n"), sig_fd, strerror (errno)); goto leave; } handle_progress (pfx, fp, NULL); if ( !opt.no_armor && use_armor_filter (fp) ) { afx = new_armor_context (); push_armor_filter (afx, fp); } rc = proc_signature_packets_by_fd (ctrl, NULL, fp, data_fd); if ( afx && afx->no_openpgp_data && (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) ) rc = gpg_error (GPG_ERR_NO_DATA); leave: iobuf_close (fp); release_progress_context (pfx); release_armor_context (afx); return rc; } diff --git a/g13/backend.c b/g13/backend.c index 0123b458c..835c66be4 100644 --- a/g13/backend.c +++ b/g13/backend.c @@ -1,295 +1,295 @@ /* backend.c - Dispatcher to the various backends. * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include "g13.h" #include "../common/i18n.h" #include "keyblob.h" #include "backend.h" #include "be-encfs.h" #include "be-truecrypt.h" #include "be-dmcrypt.h" #include "call-syshelp.h" #define no_such_backend(a) _no_such_backend ((a), __func__) static gpg_error_t _no_such_backend (int conttype, const char *func) { log_error ("invalid backend %d given in %s - this is most likely a bug\n", conttype, func); return gpg_error (GPG_ERR_INTERNAL); } /* Parse NAME and return the corresponding content type. If the name is not known, a error message is printed and zero returned. If NAME is NULL the supported backend types are listed and 0 is returned. */ int be_parse_conttype_name (const char *name) { static struct { const char *name; int conttype; } names[] = { { "encfs", CONTTYPE_ENCFS }, { "dm-crypt", CONTTYPE_DM_CRYPT } }; int i; if (!name) { log_info ("Known backend types:\n"); for (i=0; i < DIM (names); i++) log_info (" %s\n", names[i].name); return 0; } for (i=0; i < DIM (names); i++) { if (!strcmp (names[i].name, name)) return names[i].conttype; } log_error ("invalid backend type '%s' given\n", name); return 0; } /* Return true if CONTTYPE is supported by us. */ int be_is_supported_conttype (int conttype) { switch (conttype) { case CONTTYPE_ENCFS: case CONTTYPE_DM_CRYPT: return 1; default: return 0; } } /* Create a lock file for the container FNAME and store the lock at * R_LOCK and return 0. On error return an error code and store NULL * at R_LOCK. */ gpg_error_t be_take_lock_for_create (ctrl_t ctrl, const char *fname, dotlock_t *r_lock) { gpg_error_t err; dotlock_t lock = NULL; struct stat sb; *r_lock = NULL; /* A DM-crypt container requires special treatment by using the - syshelper fucntions. */ + syshelper functions. */ if (ctrl->conttype == CONTTYPE_DM_CRYPT) { /* */ err = call_syshelp_set_device (ctrl, fname); goto leave; } /* A quick check to see that no container with that name already exists. */ if (!access (fname, F_OK)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } /* Take a lock and proceed with the creation. If there is a lock we immediately return an error because for creation it does not make sense to wait. */ lock = dotlock_create (fname, 0); if (!lock) { err = gpg_error_from_syserror (); goto leave; } if (dotlock_take (lock, 0)) { err = gpg_error_from_syserror (); goto leave; } /* Check again that the file does not exist. */ err = stat (fname, &sb)? 0 : gpg_error (GPG_ERR_EEXIST); leave: if (!err) { *r_lock = lock; lock = NULL; } dotlock_destroy (lock); return err; } /* If the backend requires a separate file or directory for the container, return its name by computing it from FNAME which gives the g13 filename. The new file name is allocated and stored at R_NAME, if this is expected to be a directory true is stored at R_ISDIR. If no detached name is expected or an error occurs NULL is stored at R_NAME. The function returns 0 on success or an error code. */ gpg_error_t be_get_detached_name (int conttype, const char *fname, char **r_name, int *r_isdir) { *r_name = NULL; *r_isdir = 0; switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_get_detached_name (fname, r_name, r_isdir); case CONTTYPE_DM_CRYPT: return 0; default: return no_such_backend (conttype); } } gpg_error_t be_create_new_keys (int conttype, membuf_t *mb) { switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_create_new_keys (mb); case CONTTYPE_TRUECRYPT: return be_truecrypt_create_new_keys (mb); case CONTTYPE_DM_CRYPT: return 0; default: return no_such_backend (conttype); } } /* Dispatcher to the backend's create function. */ gpg_error_t be_create_container (ctrl_t ctrl, int conttype, const char *fname, int fd, tupledesc_t tuples, unsigned int *r_id) { (void)fd; /* Not yet used. */ switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_create_container (ctrl, fname, tuples, r_id); case CONTTYPE_DM_CRYPT: return be_dmcrypt_create_container (ctrl); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's mount function. */ gpg_error_t be_mount_container (ctrl_t ctrl, int conttype, const char *fname, const char *mountpoint, tupledesc_t tuples, unsigned int *r_id) { switch (conttype) { case CONTTYPE_ENCFS: return be_encfs_mount_container (ctrl, fname, mountpoint, tuples, r_id); case CONTTYPE_DM_CRYPT: return be_dmcrypt_mount_container (ctrl, fname, mountpoint, tuples); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's umount function. */ gpg_error_t be_umount_container (ctrl_t ctrl, int conttype, const char *fname) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_umount_container (ctrl, fname); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's suspend function. */ gpg_error_t be_suspend_container (ctrl_t ctrl, int conttype, const char *fname) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_suspend_container (ctrl, fname); default: return no_such_backend (conttype); } } /* Dispatcher to the backend's resume function. */ gpg_error_t be_resume_container (ctrl_t ctrl, int conttype, const char *fname, tupledesc_t tuples) { switch (conttype) { case CONTTYPE_ENCFS: return gpg_error (GPG_ERR_NOT_SUPPORTED); case CONTTYPE_DM_CRYPT: return be_dmcrypt_resume_container (ctrl, fname, tuples); default: return no_such_backend (conttype); } } diff --git a/g13/create.c b/g13/create.c index d55b85931..d773dfa81 100644 --- a/g13/create.c +++ b/g13/create.c @@ -1,302 +1,302 @@ /* create.c - Create a new crypto container * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <assert.h> #include "g13.h" #include "../common/i18n.h" #include "create.h" #include "keyblob.h" #include "backend.h" #include "g13tuple.h" #include "../common/call-gpg.h" /* Create a new blob with all the session keys and other meta information which are to be stored encrypted in the crypto container header. On success the malloced blob is stored at R_BLOB and its length at R_BLOBLEN. On error an error code is returned and (R_BLOB,R_BLOBLEN) are set to (NULL,0). The format of this blob is a sequence of tag-length-value tuples. All tuples have this format: 2 byte TAG Big endian unsigned integer (0..65535) described by the KEYBLOB_TAG_ constants. 2 byte LENGTH Big endian unsigned integer (0..65535) giving the length of the value. length bytes VALUE The value described by the tag. The first tag in a keyblob must be a BLOBVERSION. The other tags depend on the type of the container as described by the CONTTYPE tag. See keyblob.h for details. */ static gpg_error_t create_new_keyblob (ctrl_t ctrl, int is_detached, void **r_blob, size_t *r_bloblen) { gpg_error_t err; unsigned char twobyte[2]; membuf_t mb; *r_blob = NULL; *r_bloblen = 0; init_membuf_secure (&mb, 512); append_tuple (&mb, KEYBLOB_TAG_BLOBVERSION, "\x01", 1); twobyte[0] = (ctrl->conttype >> 8); twobyte[1] = (ctrl->conttype); append_tuple (&mb, KEYBLOB_TAG_CONTTYPE, twobyte, 2); if (is_detached) append_tuple (&mb, KEYBLOB_TAG_DETACHED, NULL, 0); err = be_create_new_keys (ctrl->conttype, &mb); if (err) goto leave; /* Just for testing. */ append_tuple (&mb, KEYBLOB_TAG_FILLER, "filler", 6); *r_blob = get_membuf (&mb, r_bloblen); if (!*r_blob) { err = gpg_error_from_syserror (); *r_bloblen = 0; } else log_debug ("used keyblob size is %zu\n", *r_bloblen); leave: xfree (get_membuf (&mb, NULL)); return err; } /* Encrypt the keyblob (KEYBLOB,KEYBLOBLEN) and store the result at (R_ENCBLOB, R_ENCBLOBLEN). Returns 0 on success or an error code. On error R_EKYBLOB is set to NULL. Depending on the keys set in CTRL the result is a single OpenPGP binary message, a single special OpenPGP packet encapsulating a CMS message or a concatenation of both with the CMS packet being the last. */ gpg_error_t g13_encrypt_keyblob (ctrl_t ctrl, void *keyblob, size_t keybloblen, void **r_encblob, size_t *r_encbloblen) { gpg_error_t err; /* FIXME: For now we only implement OpenPGP. */ err = gpg_encrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments, keyblob, keybloblen, ctrl->recipients, r_encblob, r_encbloblen); return err; } /* Write a new file under the name FILENAME with the keyblob and an appropriate header. This function is called with a lock file in place and after checking that the filename does not exists. */ static gpg_error_t write_keyblob (const char *filename, const void *keyblob, size_t keybloblen) { gpg_error_t err; estream_t fp; unsigned char packet[32]; size_t headerlen, paddinglen; fp = es_fopen (filename, "wbx"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error creating new container '%s': %s\n", filename, gpg_strerror (err)); return err; } /* Allow for an least 8 times larger keyblob to accommodate for future key changes. Round it up to 4096 byte. */ headerlen = ((32 + 8 * keybloblen + 16) + 4095) / 4096 * 4096; paddinglen = headerlen - 32 - keybloblen; assert (paddinglen >= 16); packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = 0; packet[3] = 0; packet[4] = 0; packet[5] = 26; memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */ packet[16] = 1; /* G13 packet format version. */ packet[17] = 0; /* Reserved. */ packet[18] = 0; /* Reserved. */ packet[19] = 0; /* OS Flag. */ packet[20] = (headerlen >> 24); /* Total length of header. */ packet[21] = (headerlen >> 16); packet[22] = (headerlen >> 8); packet[23] = (headerlen); packet[24] = 1; /* Number of header copies. */ packet[25] = 0; /* Number of header copies at the end. */ packet[26] = 0; /* Reserved. */ packet[27] = 0; /* Reserved. */ packet[28] = 0; /* Reserved. */ packet[29] = 0; /* Reserved. */ packet[30] = 0; /* Reserved. */ packet[31] = 0; /* Reserved. */ if (es_fwrite (packet, 32, 1, fp) != 1) goto writeerr; if (es_fwrite (keyblob, keybloblen, 1, fp) != 1) goto writeerr; /* Write the padding. */ packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */ packet[1] = 0xff; /* 5 byte length packet, value 20. */ packet[2] = (paddinglen-6) >> 24; packet[3] = (paddinglen-6) >> 16; packet[4] = (paddinglen-6) >> 8; packet[5] = (paddinglen-6); memcpy (packet+6, "GnuPG/PAD", 10); /* Packet subtype. */ if (es_fwrite (packet, 16, 1, fp) != 1) goto writeerr; memset (packet, 0, 32); for (paddinglen-=16; paddinglen >= 32; paddinglen -= 32) if (es_fwrite (packet, 32, 1, fp) != 1) goto writeerr; if (paddinglen) if (es_fwrite (packet, paddinglen, 1, fp) != 1) goto writeerr; if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", filename, gpg_strerror (err)); remove (filename); return err; } return 0; writeerr: err = gpg_error_from_syserror (); log_error ("error writing header to '%s': %s\n", filename, gpg_strerror (err)); es_fclose (fp); remove (filename); return err; } -/* Create a new container under the name FILENAME and intialize it +/* Create a new container under the name FILENAME and initialize it using the current settings. If the file already exists an error is returned. */ gpg_error_t g13_create_container (ctrl_t ctrl, const char *filename) { gpg_error_t err; dotlock_t lock; void *keyblob = NULL; size_t keybloblen; void *enckeyblob = NULL; size_t enckeybloblen; char *detachedname = NULL; int detachedisdir; tupledesc_t tuples = NULL; unsigned int dummy_rid; if (!ctrl->recipients) return gpg_error (GPG_ERR_NO_PUBKEY); err = be_take_lock_for_create (ctrl, filename, &lock); if (err) goto leave; /* And a possible detached file or directory may not exist either. */ err = be_get_detached_name (ctrl->conttype, filename, &detachedname, &detachedisdir); if (err) goto leave; if (detachedname) { struct stat sb; if (!stat (detachedname, &sb)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; } } if (ctrl->conttype != CONTTYPE_DM_CRYPT) { /* Create a new keyblob. */ err = create_new_keyblob (ctrl, !!detachedname, &keyblob, &keybloblen); if (err) goto leave; /* Encrypt that keyblob. */ err = g13_encrypt_keyblob (ctrl, keyblob, keybloblen, &enckeyblob, &enckeybloblen); if (err) goto leave; /* Put a copy of the keyblob into a tuple structure. */ err = create_tupledesc (&tuples, keyblob, keybloblen); if (err) goto leave; keyblob = NULL; /* if (opt.verbose) */ /* dump_keyblob (tuples); */ /* Write out the header, the encrypted keyblob and some padding. */ err = write_keyblob (filename, enckeyblob, enckeybloblen); if (err) goto leave; } /* Create and append the container. FIXME: We should pass the estream object in addition to the filename, so that the backend can append the container to the g13 file. */ err = be_create_container (ctrl, ctrl->conttype, filename, -1, tuples, &dummy_rid); leave: destroy_tupledesc (tuples); xfree (detachedname); xfree (enckeyblob); xfree (keyblob); dotlock_destroy (lock); return err; } diff --git a/g13/g13-common.h b/g13/g13-common.h index 1fe80d3c7..acf25b843 100644 --- a/g13/g13-common.h +++ b/g13/g13-common.h @@ -1,96 +1,96 @@ /* g13.h - Global definitions for G13. * Copyright (C) 2009 Free Software Foundation, Inc. * Copyright (C) 2009, 2015 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 <https://www.gnu.org/licenses/>. */ #ifndef G13_COMMON_H #define G13_COMMON_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_G13 #include <gpg-error.h> #include "../common/util.h" #include "../common/status.h" #include "../common/session-env.h" #include "../common/strlist.h" /* Debug values and macros. */ #define DBG_MOUNT_VALUE 1 /* Debug mount or device stuff. */ #define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ #define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ #define DBG_MEMSTAT_VALUE 128 /* Show memory statistics. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_MOUNT (opt.debug & DBG_MOUNT_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) /* A large struct named "opt" to keep global flags. Note that this struct is used by g13 and g13-syshelp and thus some fields may only make sense for one of them. */ struct { unsigned int debug; /* Debug flags (DBG_foo_VALUE). */ int verbose; /* Verbosity level. */ int quiet; /* Be as quiet as possible. */ int dry_run; /* Don't change any persistent data. */ const char *config_filename; /* Name of the used config file. */ /* Filename of the AGENT program. */ const char *agent_program; /* Filename of the GPG program. Unless set via an program option it - is initialzed at the first engine startup to the standard gpg + is initialized at the first engine startup to the standard gpg filename. */ const char *gpg_program; /* GPG arguments. XXX: Currently it is not possible to set them. */ strlist_t gpg_arguments; /* Environment variables passed along to the engine. */ char *display; char *ttyname; char *ttytype; char *lc_ctype; char *lc_messages; char *xauthority; char *pinentry_user_data; session_env_t session_env; /* Name of the output file - FIXME: what is this? */ const char *outfile; } opt; /*-- g13-common.c --*/ void g13_init_signals (void); void g13_install_emergency_cleanup (void); void g13_exit (int rc); /*-- server.c and g13-sh-cmd.c --*/ gpg_error_t g13_status (ctrl_t ctrl, int no, ...) GPGRT_ATTR_SENTINEL(0); #endif /*G13_COMMON_H*/ diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c index 82f1cfec3..687421219 100644 --- a/kbx/keybox-blob.c +++ b/kbx/keybox-blob.c @@ -1,1057 +1,1057 @@ /* keybox-blob.c - KBX Blob handling * Copyright (C) 2000, 2001, 2002, 2003, 2008 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ /* * The keybox data format The KeyBox uses an augmented OpenPGP/X.509 key format. This makes random access to a keyblock/certificate easier and also gives the opportunity to store additional information (e.g. the fingerprint) along with the key. All integers are stored in network byte order, offsets are counted from the beginning of the Blob. ** Overview of blob types | Byte 4 | Blob type | |--------+--------------| | 0 | Empty blob | | 1 | First blob | | 2 | OpenPGP blob | | 3 | X.509 blob | ** The First blob The first blob of a plain KBX file has a special format: - u32 Length of this blob - byte Blob type (1) - byte Version number (1) - u16 Header flags bit 0 - RFU bit 1 - Is being or has been used for OpenPGP blobs - b4 Magic 'KBXf' - u32 RFU - u32 file_created_at - u32 last_maintenance_run - u32 RFU - u32 RFU ** The OpenPGP and X.509 blobs The OpenPGP and X.509 blobs are very similar, things which are X.509 specific are noted like [X.509: xxx] - u32 Length of this blob (including these 4 bytes) - byte Blob type 2 = OpenPGP 3 = X509 - byte Version number of this blob type 1 = The only defined value - u16 Blob flags bit 0 = contains secret key material (not used) - bit 1 = ephemeral blob (e.g. used while quering external resources) + bit 1 = ephemeral blob (e.g. used while querying external resources) - u32 Offset to the OpenPGP keyblock or the X.509 DER encoded certificate - u32 The length of the keyblock or certificate - u16 [NKEYS] Number of keys (at least 1!) [X509: always 1] - u16 Size of the key information structure (at least 28). - NKEYS times: - b20 The fingerprint of the key. Fingerprints are always 20 bytes, MD5 left padded with zeroes. - u32 Offset to the n-th key's keyID (a keyID is always 8 byte) or 0 if not known which is the case only for X.509. - u16 Key flags bit 0 = qualified signature (not yet implemented} - u16 RFU - bN Optional filler up to the specified length of this structure. - u16 Size of the serial number (may be zero) - bN The serial number. N as giiven above. - u16 Number of user IDs - u16 [NUIDS] Size of user ID information structure - NUIDS times: For X509, the first user ID is the Issuer, the second the Subject and the others are subjectAltNames. For OpenPGP we only store the information from UserID packets here. - u32 Blob offset to the n-th user ID - u32 Length of this user ID. - u16 User ID flags. (not yet used) - byte Validity - byte RFU - u16 [NSIGS] Number of signatures - u16 Size of signature information (4) - NSIGS times: - u32 Expiration time of signature with some special values. Since version 2.1.20 these special valuesare not anymore used for OpenPGP: - 0x00000000 = not checked - 0x00000001 = missing key - 0x00000002 = bad signature - 0x10000000 = valid and expires at some date in 1978. - 0xffffffff = valid and does not expire - u8 Assigned ownertrust [X509: not used] - u8 All_Validity OpenPGP: See ../g10/trustdb/TRUST_* [not yet used] X509: Bit 4 set := key has been revoked. Note that this value matches TRUST_FLAG_REVOKED - u16 RFU - u32 Recheck_after - u32 Latest timestamp in the keyblock (useful for KS syncronsiation?) - u32 Blob created at - u32 [NRES] Size of reserved space (not including this field) - bN Reserved space of size NRES for future use. - bN Arbitrary space for example used to store data which is not part of the keyblock or certificate. For example the v3 key IDs go here. - bN Space for the keyblock or certificate. - bN RFU. This is the remaining space after keyblock and before the checksum. It is not covered by the checksum. - b20 SHA-1 checksum (useful for KS syncronisation?) Note, that KBX versions before GnuPG 2.1 used an MD5 checksum. However it was only created but never checked. Thus we do not expect problems if we switch to SHA-1. If the checksum fails and the first 4 bytes are zero, we can try again with MD5. SHA-1 has the advantage that it is faster on CPUs with dedicated SHA-1 support. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <assert.h> #include <time.h> #include "keybox-defs.h" #include <gcrypt.h> #ifdef KEYBOX_WITH_X509 #include <ksba.h> #endif #include "../common/gettime.h" /* special values of the signature status */ #define SF_NONE(a) ( !(a) ) #define SF_NOKEY(a) ((a) & (1<<0)) #define SF_BAD(a) ((a) & (1<<1)) #define SF_VALID(a) ((a) & (1<<29)) struct membuf { size_t len; size_t size; char *buf; int out_of_core; }; /* #if MAX_FINGERPRINT_LEN < 20 */ /* #error fingerprints are 20 bytes */ /* #endif */ struct keyboxblob_key { char fpr[20]; u32 off_kid; ulong off_kid_addr; u16 flags; }; struct keyboxblob_uid { u32 off; ulong off_addr; char *name; /* used only with x509 */ u32 len; u16 flags; byte validity; }; struct keyid_list { struct keyid_list *next; int seqno; byte kid[8]; }; struct fixup_list { struct fixup_list *next; u32 off; u32 val; }; struct keyboxblob { byte *blob; size_t bloblen; off_t fileoffset; /* stuff used only by keybox_create_blob */ unsigned char *serialbuf; const unsigned char *serial; size_t seriallen; int nkeys; struct keyboxblob_key *keys; int nuids; struct keyboxblob_uid *uids; int nsigs; u32 *sigs; struct fixup_list *fixups; int fixup_out_of_core; struct keyid_list *temp_kids; struct membuf bufbuf; /* temporary store for the blob */ struct membuf *buf; }; -/* A simple implemention of a dynamic buffer. Use init_membuf() to +/* A simple implementation of a dynamic buffer. Use init_membuf() to create a buffer, put_membuf to append bytes and get_membuf to release and return the buffer. Allocation errors are detected but only returned at the final get_membuf(), this helps not to clutter the code with out of core checks. */ static void init_membuf (struct membuf *mb, int initiallen) { mb->len = 0; mb->size = initiallen; mb->out_of_core = 0; mb->buf = xtrymalloc (initiallen); if (!mb->buf) mb->out_of_core = 1; } static void put_membuf (struct membuf *mb, const void *buf, size_t len) { if (mb->out_of_core) return; if (mb->len + len >= mb->size) { char *p; mb->size += len + 1024; p = xtryrealloc (mb->buf, mb->size); if (!p) { mb->out_of_core = 1; return; } mb->buf = p; } if (buf) memcpy (mb->buf + mb->len, buf, len); else memset (mb->buf + mb->len, 0, len); mb->len += len; } static void * get_membuf (struct membuf *mb, size_t *len) { char *p; if (mb->out_of_core) { xfree (mb->buf); mb->buf = NULL; return NULL; } p = mb->buf; *len = mb->len; mb->buf = NULL; mb->out_of_core = 1; /* don't allow a reuse */ return p; } static void put8 (struct membuf *mb, byte a ) { put_membuf (mb, &a, 1); } static void put16 (struct membuf *mb, u16 a ) { unsigned char tmp[2]; tmp[0] = a>>8; tmp[1] = a; put_membuf (mb, tmp, 2); } static void put32 (struct membuf *mb, u32 a ) { unsigned char tmp[4]; tmp[0] = a>>24; tmp[1] = a>>16; tmp[2] = a>>8; tmp[3] = a; put_membuf (mb, tmp, 4); } /* Store a value in the fixup list */ static void add_fixup (KEYBOXBLOB blob, u32 off, u32 val) { struct fixup_list *fl; if (blob->fixup_out_of_core) return; fl = xtrycalloc(1, sizeof *fl); if (!fl) blob->fixup_out_of_core = 1; else { fl->off = off; fl->val = val; fl->next = blob->fixups; blob->fixups = fl; } } /* OpenPGP specific stuff */ /* We must store the keyid at some place because we can't calculate the offset yet. This is only used for v3 keyIDs. Function returns an index value for later fixup or -1 for out of core. The value must be a non-zero value. */ static int pgp_temp_store_kid (KEYBOXBLOB blob, struct _keybox_openpgp_key_info *kinfo) { struct keyid_list *k, *r; k = xtrymalloc (sizeof *k); if (!k) return -1; memcpy (k->kid, kinfo->keyid, 8); k->seqno = 0; k->next = blob->temp_kids; blob->temp_kids = k; for (r=k; r; r = r->next) k->seqno++; return k->seqno; } /* Helper for pgp_create_key_part. */ static gpg_error_t pgp_create_key_part_single (KEYBOXBLOB blob, int n, struct _keybox_openpgp_key_info *kinfo) { size_t fprlen; int off; fprlen = kinfo->fprlen; if (fprlen > 20) fprlen = 20; memcpy (blob->keys[n].fpr, kinfo->fpr, fprlen); if (fprlen != 20) /* v3 fpr - shift right and fill with zeroes. */ { memmove (blob->keys[n].fpr + 20 - fprlen, blob->keys[n].fpr, fprlen); memset (blob->keys[n].fpr, 0, 20 - fprlen); off = pgp_temp_store_kid (blob, kinfo); if (off == -1) return gpg_error_from_syserror (); blob->keys[n].off_kid = off; } else blob->keys[n].off_kid = 0; /* Will be fixed up later */ blob->keys[n].flags = 0; return 0; } static gpg_error_t pgp_create_key_part (KEYBOXBLOB blob, keybox_openpgp_info_t info) { gpg_error_t err; int n = 0; struct _keybox_openpgp_key_info *kinfo; err = pgp_create_key_part_single (blob, n++, &info->primary); if (err) return err; if (info->nsubkeys) for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next) if ((err=pgp_create_key_part_single (blob, n++, kinfo))) return err; assert (n == blob->nkeys); return 0; } static void pgp_create_uid_part (KEYBOXBLOB blob, keybox_openpgp_info_t info) { int n = 0; struct _keybox_openpgp_uid_info *u; if (info->nuids) { for (u = &info->uids; u; u = u->next) { blob->uids[n].off = u->off; blob->uids[n].len = u->len; blob->uids[n].flags = 0; blob->uids[n].validity = 0; n++; } } assert (n == blob->nuids); } static void pgp_create_sig_part (KEYBOXBLOB blob, u32 *sigstatus) { int n; for (n=0; n < blob->nsigs; n++) { blob->sigs[n] = sigstatus? sigstatus[n+1] : 0; } } static int pgp_create_blob_keyblock (KEYBOXBLOB blob, const unsigned char *image, size_t imagelen) { struct membuf *a = blob->buf; int n; u32 kbstart = a->len; add_fixup (blob, 8, kbstart); for (n = 0; n < blob->nuids; n++) add_fixup (blob, blob->uids[n].off_addr, kbstart + blob->uids[n].off); put_membuf (a, image, imagelen); add_fixup (blob, 12, a->len - kbstart); return 0; } #ifdef KEYBOX_WITH_X509 /* X.509 specific stuff */ /* Write the raw certificate out */ static int x509_create_blob_cert (KEYBOXBLOB blob, ksba_cert_t cert) { struct membuf *a = blob->buf; const unsigned char *image; size_t length; u32 kbstart = a->len; /* Store our offset for later fixup */ add_fixup (blob, 8, kbstart); image = ksba_cert_get_image (cert, &length); if (!image) return gpg_error (GPG_ERR_GENERAL); put_membuf (a, image, length); add_fixup (blob, 12, a->len - kbstart); return 0; } #endif /*KEYBOX_WITH_X509*/ /* Write a stored keyID out to the buffer */ static void write_stored_kid (KEYBOXBLOB blob, int seqno) { struct keyid_list *r; for ( r = blob->temp_kids; r; r = r->next ) { if (r->seqno == seqno ) { put_membuf (blob->buf, r->kid, 8); return; } } never_reached (); } /* Release a list of key IDs */ static void release_kid_list (struct keyid_list *kl) { struct keyid_list *r, *r2; for ( r = kl; r; r = r2 ) { r2 = r->next; xfree (r); } } static int create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral) { struct membuf *a = blob->buf; int i; put32 ( a, 0 ); /* blob length, needs fixup */ put8 ( a, blobtype); put8 ( a, 1 ); /* blob type version */ put16 ( a, as_ephemeral? 2:0 ); /* blob flags */ put32 ( a, 0 ); /* offset to the raw data, needs fixup */ put32 ( a, 0 ); /* length of the raw data, needs fixup */ put16 ( a, blob->nkeys ); put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */ for ( i=0; i < blob->nkeys; i++ ) { put_membuf (a, blob->keys[i].fpr, 20); blob->keys[i].off_kid_addr = a->len; put32 ( a, 0 ); /* offset to keyid, fixed up later */ put16 ( a, blob->keys[i].flags ); put16 ( a, 0 ); /* reserved */ } put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/ if (blob->serial) put_membuf (a, blob->serial, blob->seriallen); put16 ( a, blob->nuids ); put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */ for (i=0; i < blob->nuids; i++) { blob->uids[i].off_addr = a->len; put32 ( a, 0 ); /* offset to userid, fixed up later */ put32 ( a, blob->uids[i].len ); put16 ( a, blob->uids[i].flags ); put8 ( a, 0 ); /* validity */ put8 ( a, 0 ); /* reserved */ } put16 ( a, blob->nsigs ); put16 ( a, 4 ); /* size of sig info */ for (i=0; i < blob->nsigs; i++) { put32 ( a, blob->sigs[i]); } put8 ( a, 0 ); /* assigned ownertrust */ put8 ( a, 0 ); /* validity of all user IDs */ put16 ( a, 0 ); /* reserved */ put32 ( a, 0 ); /* time of next recheck */ put32 ( a, 0 ); /* newest timestamp (none) */ put32 ( a, make_timestamp() ); /* creation time */ put32 ( a, 0 ); /* size of reserved space */ /* reserved space (which is currently of size 0) */ /* space where we write keyIDs and other stuff so that the pointers can actually point to somewhere */ if (blobtype == KEYBOX_BLOBTYPE_PGP) { /* We need to store the keyids for all pgp v3 keys because those key IDs are not part of the fingerprint. While we are doing that, we fixup all the keyID offsets */ for (i=0; i < blob->nkeys; i++ ) { if (blob->keys[i].off_kid) { /* this is a v3 one */ add_fixup (blob, blob->keys[i].off_kid_addr, a->len); write_stored_kid (blob, blob->keys[i].off_kid); } else { /* the better v4 key IDs - just store an offset 8 bytes back */ add_fixup (blob, blob->keys[i].off_kid_addr, blob->keys[i].off_kid_addr - 8); } } } if (blobtype == KEYBOX_BLOBTYPE_X509) { /* We don't want to point to ASN.1 encoded UserIDs (DNs) but to the utf-8 string represenation of them */ for (i=0; i < blob->nuids; i++ ) { if (blob->uids[i].name) { /* this is a v3 one */ add_fixup (blob, blob->uids[i].off_addr, a->len); put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len); } } } return 0; } static int create_blob_trailer (KEYBOXBLOB blob) { (void)blob; return 0; } static int create_blob_finish (KEYBOXBLOB blob) { struct membuf *a = blob->buf; unsigned char *p; unsigned char *pp; size_t n; /* Write a placeholder for the checksum */ put_membuf (a, NULL, 20); /* get the memory area */ n = 0; /* (Just to avoid compiler warning.) */ p = get_membuf (a, &n); if (!p) return gpg_error (GPG_ERR_ENOMEM); assert (n >= 20); /* fixup the length */ add_fixup (blob, 0, n); /* do the fixups */ if (blob->fixup_out_of_core) { xfree (p); return gpg_error (GPG_ERR_ENOMEM); } { struct fixup_list *fl, *next; for (fl = blob->fixups; fl; fl = next) { assert (fl->off+4 <= n); p[fl->off+0] = fl->val >> 24; p[fl->off+1] = fl->val >> 16; p[fl->off+2] = fl->val >> 8; p[fl->off+3] = fl->val; next = fl->next; xfree (fl); } blob->fixups = NULL; } /* Compute and store the SHA-1 checksum. */ gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 20); pp = xtrymalloc (n); if ( !pp ) { xfree (p); return gpg_error_from_syserror (); } memcpy (pp , p, n); xfree (p); blob->blob = pp; blob->bloblen = n; return 0; } gpg_error_t _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, keybox_openpgp_info_t info, const unsigned char *image, size_t imagelen, int as_ephemeral) { gpg_error_t err; KEYBOXBLOB blob; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error_from_syserror (); blob->nkeys = 1 + info->nsubkeys; blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); if (!blob->keys) { err = gpg_error_from_syserror (); goto leave; } blob->nuids = info->nuids; if (blob->nuids) { blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); if (!blob->uids) { err = gpg_error_from_syserror (); goto leave; } } blob->nsigs = info->nsigs; if (blob->nsigs) { blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); if (!blob->sigs) { err = gpg_error_from_syserror (); goto leave; } } err = pgp_create_key_part (blob, info); if (err) goto leave; pgp_create_uid_part (blob, info); pgp_create_sig_part (blob, NULL); init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP, as_ephemeral); if (err) goto leave; err = pgp_create_blob_keyblock (blob, image, imagelen); if (err) goto leave; err = create_blob_trailer (blob); if (err) goto leave; err = create_blob_finish (blob); if (err) goto leave; leave: release_kid_list (blob->temp_kids); blob->temp_kids = NULL; if (err) _keybox_release_blob (blob); else *r_blob = blob; return err; } #ifdef KEYBOX_WITH_X509 /* Return an allocated string with the email address extracted from a DN. Note hat we use this code also in ../sm/keylist.c. */ static char * x509_email_kludge (const char *name) { const char *p, *string; unsigned char *buf; int n; string = name; for (;;) { p = strstr (string, "1.2.840.113549.1.9.1=#"); if (!p) return NULL; if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\')) { name = p + 22; break; } string = p + 22; } /* This looks pretty much like an email address in the subject's DN we use this to add an additional user ID entry. This way, OpenSSL generated keys get a nicer and usable listing. */ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) ; if (!n) return NULL; buf = xtrymalloc (n+3); if (!buf) return NULL; /* oops, out of core */ *buf = '<'; for (n=1, p=name; hexdigitp (p); p +=2, n++) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; return (char*)buf; } /* Note: We should move calculation of the digest into libksba and remove that parameter */ int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, unsigned char *sha1_digest, int as_ephemeral) { int i, rc = 0; KEYBOXBLOB blob; unsigned char *sn; char *p; char **names = NULL; size_t max_names; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if( !blob ) return gpg_error_from_syserror (); sn = ksba_cert_get_serial (cert); if (sn) { size_t n, len; n = gcry_sexp_canon_len (sn, 0, NULL, NULL); if (n < 2) { xfree (sn); return gpg_error (GPG_ERR_GENERAL); } blob->serialbuf = sn; sn++; n--; /* skip '(' */ for (len=0; n && *sn && *sn != ':' && digitp (sn); n--, sn++) len = len*10 + atoi_1 (sn); if (*sn != ':') { xfree (blob->serialbuf); blob->serialbuf = NULL; return gpg_error (GPG_ERR_GENERAL); } sn++; blob->serial = sn; blob->seriallen = len; } blob->nkeys = 1; /* create list of names */ blob->nuids = 0; max_names = 100; names = xtrymalloc (max_names * sizeof *names); if (!names) { rc = gpg_error_from_syserror (); goto leave; } p = ksba_cert_get_issuer (cert, 0); if (!p) { rc = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } names[blob->nuids++] = p; for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) { if (blob->nuids >= max_names) { char **tmp; max_names += 100; tmp = xtryrealloc (names, max_names * sizeof *names); if (!tmp) { rc = gpg_error_from_syserror (); goto leave; } names = tmp; } names[blob->nuids++] = p; if (!i && (p=x509_email_kludge (p))) names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/ } /* space for signature information */ blob->nsigs = 1; blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys ); blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids ); blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs ); if (!blob->keys || !blob->uids || !blob->sigs) { rc = gpg_error (GPG_ERR_ENOMEM); goto leave; } memcpy (blob->keys[0].fpr, sha1_digest, 20); blob->keys[0].off_kid = 0; /* We don't have keyids */ blob->keys[0].flags = 0; /* issuer and subject names */ for (i=0; i < blob->nuids; i++) { blob->uids[i].name = names[i]; blob->uids[i].len = strlen(names[i]); names[i] = NULL; blob->uids[i].flags = 0; blob->uids[i].validity = 0; } xfree (names); names = NULL; /* signatures */ blob->sigs[0] = 0; /* not yet checked */ /* Create a temporary buffer for further processing */ init_membuf (&blob->bufbuf, 1024); blob->buf = &blob->bufbuf; /* write out what we already have */ rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral); if (rc) goto leave; rc = x509_create_blob_cert (blob, cert); if (rc) goto leave; rc = create_blob_trailer (blob); if (rc) goto leave; rc = create_blob_finish ( blob ); if (rc) goto leave; leave: release_kid_list (blob->temp_kids); blob->temp_kids = NULL; if (names) { for (i=0; i < blob->nuids; i++) xfree (names[i]); xfree (names); } if (rc) { _keybox_release_blob (blob); *r_blob = NULL; } else { *r_blob = blob; } return rc; } #endif /*KEYBOX_WITH_X509*/ int _keybox_new_blob (KEYBOXBLOB *r_blob, unsigned char *image, size_t imagelen, off_t off) { KEYBOXBLOB blob; *r_blob = NULL; blob = xtrycalloc (1, sizeof *blob); if (!blob) return gpg_error_from_syserror (); blob->blob = image; blob->bloblen = imagelen; blob->fileoffset = off; *r_blob = blob; return 0; } void _keybox_release_blob (KEYBOXBLOB blob) { int i; if (!blob) return; if (blob->buf) { size_t len; xfree (get_membuf (blob->buf, &len)); } xfree (blob->keys ); xfree (blob->serialbuf); for (i=0; i < blob->nuids; i++) xfree (blob->uids[i].name); xfree (blob->uids ); xfree (blob->sigs ); xfree (blob->blob ); xfree (blob ); } const unsigned char * _keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n ) { *n = blob->bloblen; return blob->blob; } off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob) { return blob->fileoffset; } void _keybox_update_header_blob (KEYBOXBLOB blob, int for_openpgp) { if (blob->bloblen >= 32 && blob->blob[4] == KEYBOX_BLOBTYPE_HEADER) { u32 val = make_timestamp (); /* Update the last maintenance run times tamp. */ blob->blob[20] = (val >> 24); blob->blob[20+1] = (val >> 16); blob->blob[20+2] = (val >> 8); blob->blob[20+3] = (val ); if (for_openpgp) blob->blob[7] |= 0x02; /* OpenPGP data may be available. */ } } diff --git a/kbx/keybox-defs.h b/kbx/keybox-defs.h index 154d4fca6..fd331f12b 100644 --- a/kbx/keybox-defs.h +++ b/kbx/keybox-defs.h @@ -1,272 +1,272 @@ /* keybox-defs.h - internal Keybox definitions * Copyright (C) 2001, 2004 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #ifndef KEYBOX_DEFS_H #define KEYBOX_DEFS_H 1 #ifdef GPG_ERR_SOURCE_DEFAULT # if GPG_ERR_SOURCE_DEFAULT != GPG_ERR_SOURCE_KEYBOX # error GPG_ERR_SOURCE_DEFAULT already defined # endif #else # define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX #endif #include <gpg-error.h> #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include <sys/types.h> /* off_t */ -/* We include the type defintions from jnlib instead of defining our +/* We include the type definitions from jnlib instead of defining our owns here. This will not allow us build KBX in a standalone way but there is currently no need for it anyway. Same goes for stringhelp.h which for example provides a replacement for stpcpy - fixme: Better use the LIBOBJ mechnism. */ #include "../common/types.h" #include "../common/stringhelp.h" #include "../common/dotlock.h" #include "../common/logging.h" #include "keybox.h" typedef struct keyboxblob *KEYBOXBLOB; typedef struct keybox_name *KB_NAME; struct keybox_name { /* Link to the next resources, so that we can walk all resources. */ KB_NAME next; /* True if this is a keybox with secret keys. */ int secret; /* A table with all the handles accessing this resources. HANDLE_TABLE_SIZE gives the allocated length of this table unused entrues are set to NULL. HANDLE_TABLE may be NULL. */ KEYBOX_HANDLE *handle_table; size_t handle_table_size; /* The lock handle or NULL it not yet initialized. */ dotlock_t lockhd; /* Not yet used. */ int is_locked; /* Not yet used. */ int did_full_scan; /* The name of the resource file. */ char fname[1]; }; struct keybox_found_s { KEYBOXBLOB blob; size_t pk_no; size_t uid_no; }; struct keybox_handle { KB_NAME kb; int secret; /* this is for a secret keybox */ FILE *fp; int eof; int error; int ephemeral; int for_openpgp; /* Used by gpg. */ struct keybox_found_s found; struct keybox_found_s saved_found; struct { char *name; char *pattern; } word_match; }; /* Openpgp helper structures. */ struct _keybox_openpgp_key_info { struct _keybox_openpgp_key_info *next; int algo; unsigned char keyid[8]; int fprlen; /* Either 16 or 20 */ unsigned char fpr[20]; }; struct _keybox_openpgp_uid_info { struct _keybox_openpgp_uid_info *next; size_t off; size_t len; }; struct _keybox_openpgp_info { int is_secret; /* True if this is a secret key. */ unsigned int nsubkeys;/* Total number of subkeys. */ unsigned int nuids; /* Total number of user IDs in the keyblock. */ unsigned int nsigs; /* Total number of signatures in the keyblock. */ /* Note, we use 2 structs here to better cope with the most common use of having one primary and one subkey - this allows us to statically allocate this structure and only malloc stuff for more than one subkey. */ struct _keybox_openpgp_key_info primary; struct _keybox_openpgp_key_info subkeys; struct _keybox_openpgp_uid_info uids; }; typedef struct _keybox_openpgp_info *keybox_openpgp_info_t; /* Don't know whether this is needed: */ /* static struct { */ /* int dry_run; */ /* int quiet; */ /* int verbose; */ /* int preserve_permissions; */ /* } keybox_opt; */ /*-- keybox-init.c --*/ void _keybox_close_file (KEYBOX_HANDLE hd); /*-- keybox-blob.c --*/ gpg_error_t _keybox_create_openpgp_blob (KEYBOXBLOB *r_blob, keybox_openpgp_info_t info, const unsigned char *image, size_t imagelen, int as_ephemeral); #ifdef KEYBOX_WITH_X509 int _keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert, unsigned char *sha1_digest, int as_ephemeral); #endif /*KEYBOX_WITH_X509*/ int _keybox_new_blob (KEYBOXBLOB *r_blob, unsigned char *image, size_t imagelen, off_t off); void _keybox_release_blob (KEYBOXBLOB blob); const unsigned char *_keybox_get_blob_image (KEYBOXBLOB blob, size_t *n); off_t _keybox_get_blob_fileoffset (KEYBOXBLOB blob); void _keybox_update_header_blob (KEYBOXBLOB blob, int for_openpgp); /*-- keybox-openpgp.c --*/ gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen, size_t *nparsed, keybox_openpgp_info_t info); void _keybox_destroy_openpgp_info (keybox_openpgp_info_t info); /*-- keybox-file.c --*/ int _keybox_read_blob (KEYBOXBLOB *r_blob, FILE *fp, int *skipped_deleted); int _keybox_write_blob (KEYBOXBLOB blob, FILE *fp); /*-- keybox-search.c --*/ gpg_err_code_t _keybox_get_flag_location (const unsigned char *buffer, size_t length, int what, size_t *flag_off, size_t *flag_size); static inline int blob_get_type (KEYBOXBLOB blob) { const unsigned char *buffer; size_t length; buffer = _keybox_get_blob_image (blob, &length); if (length < 32) return -1; /* blob too short */ return buffer[4]; } /*-- keybox-dump.c --*/ int _keybox_dump_blob (KEYBOXBLOB blob, FILE *fp); int _keybox_dump_file (const char *filename, int stats_only, FILE *outfp); int _keybox_dump_find_dups (const char *filename, int print_them, FILE *outfp); int _keybox_dump_cut_records (const char *filename, unsigned long from, unsigned long to, FILE *outfp); /*-- keybox-util.c --*/ void *_keybox_malloc (size_t n); void *_keybox_calloc (size_t n, size_t m); void *_keybox_realloc (void *p, size_t n); void _keybox_free (void *p); #define xtrymalloc(a) _keybox_malloc ((a)) #define xtrycalloc(a,b) _keybox_calloc ((a),(b)) #define xtryrealloc(a,b) _keybox_realloc((a),(b)) #define xfree(a) _keybox_free ((a)) #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) #ifndef STR # define STR(v) #v #endif #define STR2(v) STR(v) /* a couple of handy macros */ #define return_if_fail(expr) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return; \ } } while (0) #define return_null_if_fail(expr) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return NULL; \ } } while (0) #define return_val_if_fail(expr,val) do { \ if (!(expr)) { \ fprintf (stderr, "%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return (val); \ } } while (0) #define never_reached() do { \ fprintf (stderr, "%s:%d: oops; should never get here\n", \ __FILE__, __LINE__ ); \ } while (0) /* some macros to replace ctype ones and avoid locale problems */ #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* the atoi macros assume that the buffer has only valid digits */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #endif /*KEYBOX_DEFS_H*/ diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index d82c2cb75..0ba0b9ae8 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -1,522 +1,522 @@ /* keybox-openpgp.c - OpenPGP key parsing * Copyright (C) 2001, 2003, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ /* This is a simple OpenPGP parser suitable for all OpenPGP key material. It just provides the functionality required to build and parse an KBX OpenPGP key blob. Thus it is not a complete parser. However it is self-contained and optimized for fast in-memory parsing. Note that we don't support old ElGamal v3 keys anymore. */ #include <config.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <assert.h> #include "keybox-defs.h" #include <gcrypt.h> #include "../common/openpgpdefs.h" #include "../common/host2net.h" /* Assume a valid OpenPGP packet at the address pointed to by BUFBTR which has a maximum length as stored at BUFLEN. Return the header information of that packet and advance the pointer stored at BUFPTR to the next packet; also adjust the length stored at BUFLEN to match the remaining bytes. If there are no more packets, store NULL at BUFPTR. Return an non-zero error code on failure or the following data on success: R_DATAPKT = Pointer to the begin of the packet data. R_DATALEN = Length of this data. This has already been checked to fit into the buffer. R_PKTTYPE = The packet type. R_NTOTAL = The total number of bytes of this packet Note that these values are only updated on success. */ static gpg_error_t next_packet (unsigned char const **bufptr, size_t *buflen, unsigned char const **r_data, size_t *r_datalen, int *r_pkttype, size_t *r_ntotal) { const unsigned char *buf = *bufptr; size_t len = *buflen; int c, ctb, pkttype; unsigned long pktlen; if (!len) return gpg_error (GPG_ERR_NO_DATA); ctb = *buf++; len--; if ( !(ctb & 0x80) ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid CTB. */ if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */ { pkttype = (ctb & 0x3f); if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 1st length byte. */ c = *buf++; len--; if (pkttype == PKT_COMPRESSED) return gpg_error (GPG_ERR_UNEXPECTED); /* ... packet in a keyblock. */ if ( c < 192 ) pktlen = c; else if ( c < 224 ) { pktlen = (c - 192) * 256; if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 2nd length byte. */ c = *buf++; len--; pktlen += c + 192; } else if (c == 255) { if (len <4 ) return gpg_error (GPG_ERR_INV_PACKET); /* No length bytes. */ pktlen = buf32_to_ulong (buf); buf += 4; len -= 4; } else /* Partial length encoding is not allowed for key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); } else /* Old style CTB. */ { int lenbytes; pktlen = 0; pkttype = (ctb>>2)&0xf; lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); if (!lenbytes) /* Not allowed in key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); if (len < lenbytes) return gpg_error (GPG_ERR_INV_PACKET); /* Not enough length bytes. */ for (; lenbytes; lenbytes--) { pktlen <<= 8; pktlen |= *buf++; len--; } } /* Do some basic sanity check. */ switch (pkttype) { case PKT_SIGNATURE: case PKT_SECRET_KEY: case PKT_PUBLIC_KEY: case PKT_SECRET_SUBKEY: case PKT_MARKER: case PKT_RING_TRUST: case PKT_USER_ID: case PKT_PUBLIC_SUBKEY: case PKT_OLD_COMMENT: case PKT_ATTRIBUTE: case PKT_COMMENT: case PKT_GPG_CONTROL: break; /* Okay these are allowed packets. */ default: return gpg_error (GPG_ERR_UNEXPECTED); } if (pkttype == 63 && pktlen == 0xFFFFFFFF) /* Sometimes the decompressing layer enters an error state in which it simply outputs 0xff for every byte read. If we have a stream of 0xff bytes, then it will be detected as a new format packet with type 63 and a 4-byte encoded length that is 4G-1. Since packets with type 63 are private and we use them as a control packet, which won't be 4 GB, we reject such packets as invalid. */ return gpg_error (GPG_ERR_INV_PACKET); if (pktlen > len) return gpg_error (GPG_ERR_INV_PACKET); /* Packet length header too long. */ *r_data = buf; *r_datalen = pktlen; *r_pkttype = pkttype; *r_ntotal = (buf - *bufptr) + pktlen; *bufptr = buf + pktlen; *buflen = len - pktlen; if (!*buflen) *bufptr = NULL; return 0; } /* Parse a key packet and store the information in KI. */ static gpg_error_t parse_key (const unsigned char *data, size_t datalen, struct _keybox_openpgp_key_info *ki) { gpg_error_t err; const unsigned char *data_start = data; int i, version, algorithm; size_t n; int npkey; unsigned char hashbuffer[768]; const unsigned char *mpi_n = NULL; size_t mpi_n_len = 0, mpi_e_len = 0; gcry_md_hd_t md; int is_ecc = 0; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); version = *data++; datalen--; if (version < 2 || version > 4 ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */ /*timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));*/ data +=4; datalen -=4; if (version < 4) { if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); data +=2; datalen -= 2; } if (!datalen) return gpg_error (GPG_ERR_INV_PACKET); algorithm = *data++; datalen--; switch (algorithm) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: npkey = 2; break; case PUBKEY_ALGO_ELGAMAL_E: case PUBKEY_ALGO_ELGAMAL: npkey = 3; break; case PUBKEY_ALGO_DSA: npkey = 4; break; case PUBKEY_ALGO_ECDH: npkey = 3; is_ecc = 1; break; case PUBKEY_ALGO_ECDSA: case PUBKEY_ALGO_EDDSA: npkey = 2; is_ecc = 1; break; default: /* Unknown algorithm. */ return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); } ki->algo = algorithm; for (i=0; i < npkey; i++ ) { unsigned int nbits, nbytes; if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); if (is_ecc && (i == 0 || i == 2)) { nbytes = data[0]; if (nbytes < 2 || nbytes > 254) return gpg_error (GPG_ERR_INV_PACKET); nbytes++; /* The size byte itself. */ if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); } else { nbits = ((data[0]<<8)|(data[1])); data += 2; datalen -= 2; nbytes = (nbits+7) / 8; if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); /* For use by v3 fingerprint calculation we need to know the RSA modulus and exponent. */ if (i==0) { mpi_n = data; mpi_n_len = nbytes; } else if (i==1) mpi_e_len = nbytes; } data += nbytes; datalen -= nbytes; } n = data - data_start; if (version < 4) { /* We do not support any other algorithm than RSA in v3 packets. */ if (algorithm < 1 || algorithm > 3) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); err = gcry_md_open (&md, GCRY_MD_MD5, 0); if (err) return err; /* Oops */ gcry_md_write (md, mpi_n, mpi_n_len); gcry_md_write (md, mpi_n+mpi_n_len+2, mpi_e_len); memcpy (ki->fpr, gcry_md_read (md, 0), 16); gcry_md_close (md); ki->fprlen = 16; if (mpi_n_len < 8) { /* Moduli less than 64 bit are out of the specs scope. Zero them out because this is what gpg does too. */ memset (ki->keyid, 0, 8); } else memcpy (ki->keyid, mpi_n + mpi_n_len - 8, 8); } else { - /* Its a pitty that we need to prefix the buffer with the tag + /* Its a pity that we need to prefix the buffer with the tag and a length header: We can't simply pass it to the fast hashing function for that reason. It might be a good idea to have a scatter-gather enabled hash function. What we do here is to use a static buffer if this one is large enough and only use the regular hash functions if this buffer is not large enough. */ if ( 3 + n < sizeof hashbuffer ) { hashbuffer[0] = 0x99; /* CTB */ hashbuffer[1] = (n >> 8); /* 2 byte length header. */ hashbuffer[2] = n; memcpy (hashbuffer + 3, data_start, n); gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); } else { err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) return err; /* Oops */ gcry_md_putc (md, 0x99 ); /* CTB */ gcry_md_putc (md, (n >> 8) ); /* 2 byte length header. */ gcry_md_putc (md, n ); gcry_md_write (md, data_start, n); memcpy (ki->fpr, gcry_md_read (md, 0), 20); gcry_md_close (md); } ki->fprlen = 20; memcpy (ki->keyid, ki->fpr+12, 8); } return 0; } /* The caller must pass the address of an INFO structure which will get filled on success with information pertaining to the OpenPGP keyblock IMAGE of length IMAGELEN. Note that a caller does only need to release this INFO structure if the function returns success. If NPARSED is not NULL the actual number of bytes parsed will be stored at this address. */ gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen, size_t *nparsed, keybox_openpgp_info_t info) { gpg_error_t err = 0; const unsigned char *image_start, *data; size_t n, datalen; int pkttype; int first = 1; int read_error = 0; struct _keybox_openpgp_key_info *k, **ktail = NULL; struct _keybox_openpgp_uid_info *u, **utail = NULL; memset (info, 0, sizeof *info); if (nparsed) *nparsed = 0; image_start = image; while (image) { err = next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n); if (err) { read_error = 1; break; } if (first) { if (pkttype == PKT_PUBLIC_KEY) ; else if (pkttype == PKT_SECRET_KEY) info->is_secret = 1; else { err = gpg_error (GPG_ERR_UNEXPECTED); if (nparsed) *nparsed += n; break; } first = 0; } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; if (pkttype == PKT_SIGNATURE) { /* For now we only count the total number of signatures. */ info->nsigs++; } else if (pkttype == PKT_USER_ID) { info->nuids++; if (info->nuids == 1) { info->uids.off = data - image_start; info->uids.len = datalen; utail = &info->uids.next; } else { u = xtrycalloc (1, sizeof *u); if (!u) { err = gpg_error_from_syserror (); break; } u->off = data - image_start; u->len = datalen; *utail = u; utail = &u->next; } } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) { err = parse_key (data, datalen, &info->primary); if (err) break; } else if( pkttype == PKT_PUBLIC_SUBKEY && datalen && *data == '#' ) { /* Early versions of GnuPG used old PGP comment packets; * luckily all those comments are prefixed by a hash * sign - ignore these packets. */ } else if (pkttype == PKT_PUBLIC_SUBKEY || pkttype == PKT_SECRET_SUBKEY) { info->nsubkeys++; if (info->nsubkeys == 1) { err = parse_key (data, datalen, &info->subkeys); if (err) { info->nsubkeys--; /* We ignore subkeys with unknown algorithms. */ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM) err = 0; if (err) break; } else ktail = &info->subkeys.next; } else { k = xtrycalloc (1, sizeof *k); if (!k) { err = gpg_error_from_syserror (); break; } err = parse_key (data, datalen, k); if (err) { xfree (k); info->nsubkeys--; /* We ignore subkeys with unknown algorithms. */ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM) err = 0; if (err) break; } else { *ktail = k; ktail = &k->next; } } } } if (err) { _keybox_destroy_openpgp_info (info); if (!read_error) { /* Packet parsing worked, thus we should be able to skip the rest of the keyblock. */ while (image) { if (next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n) ) break; /* Another error - stop here. */ if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; } } } return err; } /* Release any malloced data in INFO but not INFO itself! */ void _keybox_destroy_openpgp_info (keybox_openpgp_info_t info) { struct _keybox_openpgp_key_info *k, *k2; struct _keybox_openpgp_uid_info *u, *u2; assert (!info->primary.next); for (k=info->subkeys.next; k; k = k2) { k2 = k->next; xfree (k); } for (u=info->uids.next; u; u = u2) { u2 = u->next; xfree (u); } } diff --git a/m4/lib-link.m4 b/m4/lib-link.m4 index e3d26fc42..9bdaffa9f 100644 --- a/m4/lib-link.m4 +++ b/m4/lib-link.m4 @@ -1,709 +1,709 @@ # lib-link.m4 serial 13 (gettext-0.17) dnl Copyright (C) 2001-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. AC_PREREQ(2.54) dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and dnl augments the CPPFLAGS variable. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) define([Name],[translit([$1],[./-], [___])]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [ AC_LIB_LINKFLAGS_BODY([$1], [$2]) ac_cv_lib[]Name[]_libs="$LIB[]NAME" ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME" ac_cv_lib[]Name[]_cppflags="$INC[]NAME" ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX" ]) LIB[]NAME="$ac_cv_lib[]Name[]_libs" LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs" INC[]NAME="$ac_cv_lib[]Name[]_cppflags" LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the dnl results of this search when this library appears as a dependency. HAVE_LIB[]NAME=yes undefine([Name]) undefine([NAME]) ]) dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode) dnl searches for libname and the libraries corresponding to explicit and dnl implicit dependencies, together with the specified include files and dnl the ability to compile and link the specified testcode. If found, it dnl sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} and dnl LTLIB${NAME} variables and augments the CPPFLAGS variable, and dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty. dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_HAVE_LINKFLAGS], [ AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) AC_REQUIRE([AC_LIB_RPATH]) define([Name],[translit([$1],[./-], [___])]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME dnl accordingly. AC_LIB_LINKFLAGS_BODY([$1], [$2]) dnl Add $INC[]NAME to CPPFLAGS before performing the following checks, dnl because if the user has installed lib[]Name and not disabled its use dnl via --without-lib[]Name-prefix, he wants to use it. ac_save_CPPFLAGS="$CPPFLAGS" AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [ ac_save_LIBS="$LIBS" LIBS="$LIBS $LIB[]NAME" AC_TRY_LINK([$3], [$4], [ac_cv_lib[]Name=yes], [ac_cv_lib[]Name=no]) LIBS="$ac_save_LIBS" ]) if test "$ac_cv_lib[]Name" = yes; then HAVE_LIB[]NAME=yes AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the $1 library.]) AC_MSG_CHECKING([how to link with lib[]$1]) AC_MSG_RESULT([$LIB[]NAME]) else HAVE_LIB[]NAME=no dnl If $LIB[]NAME didn't lead to a usable library, we don't need dnl $INC[]NAME either. CPPFLAGS="$ac_save_CPPFLAGS" LIB[]NAME= LTLIB[]NAME= LIB[]NAME[]_PREFIX= fi AC_SUBST([HAVE_LIB]NAME) AC_SUBST([LIB]NAME) AC_SUBST([LTLIB]NAME) AC_SUBST([LIB]NAME[_PREFIX]) undefine([Name]) undefine([NAME]) ]) dnl Determine the platform dependent parameters needed to use rpath: dnl acl_libext, dnl acl_shlibext, dnl acl_hardcode_libdir_flag_spec, dnl acl_hardcode_libdir_separator, dnl acl_hardcode_direct, dnl acl_hardcode_minus_L. AC_DEFUN([AC_LIB_RPATH], [ dnl Tell automake >= 1.10 to complain if config.rpath is missing. m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir AC_CACHE_CHECK([for shared library run path origin], acl_cv_rpath, [ CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh . ./conftest.sh rm -f ./conftest.sh acl_cv_rpath=done ]) wl="$acl_cv_wl" acl_libext="$acl_cv_libext" acl_shlibext="$acl_cv_shlibext" acl_libname_spec="$acl_cv_libname_spec" acl_library_names_spec="$acl_cv_library_names_spec" acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" acl_hardcode_direct="$acl_cv_hardcode_direct" acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" dnl Determine whether the user wants rpath handling at all. AC_ARG_ENABLE(rpath, [ --disable-rpath do not hardcode runtime library paths], :, enable_rpath=yes) ]) dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS_BODY], [ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) define([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) dnl Autoconf >= 2.61 supports dots in --with options. define([N_A_M_E],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[translit([$1],[.],[_])],[$1])]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_LIB_ARG_WITH([lib]N_A_M_E[-prefix], [ --with-lib]N_A_M_E[-prefix[=DIR] search for lib$1 in DIR/include and DIR/lib --without-lib]N_A_M_E[-prefix don't search for lib$1 in includedir and libdir], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" fi fi ]) dnl Search the library and its dependencies in $additional_libdir and - dnl $LDFLAGS. Using breadth-first-seach. + dnl $LDFLAGS. Using breadth-first-search. LIB[]NAME= LTLIB[]NAME= INC[]NAME= LIB[]NAME[]_PREFIX= rpathdirs= ltrpathdirs= names_already_handled= names_next_round='$1 $2' while test -n "$names_next_round"; do names_this_round="$names_next_round" names_next_round= for name in $names_this_round; do already_handled= for n in $names_already_handled; do if test "$n" = "$name"; then already_handled=yes break fi done if test -z "$already_handled"; then names_already_handled="$names_already_handled $name" dnl See if it was already located by an earlier AC_LIB_LINKFLAGS dnl or AC_LIB_HAVE_LINKFLAGS call. uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'` eval value=\"\$HAVE_LIB$uppername\" if test -n "$value"; then if test "$value" = yes; then eval value=\"\$LIB$uppername\" test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" eval value=\"\$LTLIB$uppername\" test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" else dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined dnl that this library doesn't exist. So just drop it. : fi else dnl Search the library lib$name in $additional_libdir and $LDFLAGS dnl and the already constructed $LIBNAME/$LTLIBNAME. found_dir= found_la= found_so= found_a= eval libname=\"$acl_libname_spec\" # typically: libname=lib$name if test -n "$acl_shlibext"; then shrext=".$acl_shlibext" # typically: shrext=.so else shrext= fi if test $use_additional = yes; then dir="$additional_libdir" dnl The same code as in the loop below: dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi fi if test "X$found_dir" = "X"; then for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) case "$x" in -L*) dir=`echo "X$x" | sed -e 's/^X-L//'` dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext"; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi ;; esac if test "X$found_dir" != "X"; then break fi done fi if test "X$found_dir" != "X"; then dnl Found the library. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" if test "X$found_so" != "X"; then dnl Linking with a shared library. We attempt to hardcode its dnl directory into the executable's runpath, unless it's the dnl standard /usr/lib. if test "$enable_rpath" = no || test "X$found_dir" = "X/usr/$acl_libdirstem"; then dnl No hardcoding is needed. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl Use an explicit option to hardcode DIR into the resulting dnl binary. dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $found_dir" fi dnl The hardcoding into $LIBNAME is system dependent. if test "$acl_hardcode_direct" = yes; then dnl Using DIR/libNAME.so during linking hardcodes DIR into the dnl resulting binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode DIR into the resulting dnl binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $found_dir" fi else dnl Rely on "-L$found_dir". dnl But don't add it if it's already contained in the LDFLAGS dnl or the already constructed $LIBNAME haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" fi if test "$acl_hardcode_minus_L" != no; then dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH dnl here, because this doesn't fit in flags passed to the dnl compiler. So give up. No hardcoding. This affects only dnl very old systems. dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" fi fi fi fi else if test "X$found_a" != "X"; then dnl Linking with a static library. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" else dnl We shouldn't come here, but anyway it's good to have a dnl fallback. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" fi fi dnl Assume the include files are nearby. additional_includedir= case "$found_dir" in */$acl_libdirstem | */$acl_libdirstem/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` LIB[]NAME[]_PREFIX="$basedir" additional_includedir="$basedir/include" ;; esac if test "X$additional_includedir" != "X"; then dnl Potentially add $additional_includedir to $INCNAME. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's /usr/local/include and we are using GCC on Linux, dnl 3. if it's already present in $CPPFLAGS or the already dnl constructed $INCNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then for x in $CPPFLAGS $INC[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $INCNAME. INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" fi fi fi fi fi dnl Look for dependencies. if test -n "$found_la"; then dnl Read the .la file. It defines the variables dnl dlname, library_names, old_library, dependency_libs, current, dnl age, revision, installed, dlopen, dlpreopen, libdir. save_libdir="$libdir" case "$found_la" in */* | *\\*) . "$found_la" ;; *) . "./$found_la" ;; esac libdir="$save_libdir" dnl We use only dependency_libs. for dep in $dependency_libs; do case "$dep" in -L*) additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's /usr/local/lib and we are using GCC on Linux, dnl 3. if it's already present in $LDFLAGS or the already dnl constructed $LIBNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then haveit= if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LIBNAME. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" fi fi haveit= for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LTLIBNAME. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" fi fi fi fi ;; -R*) dir=`echo "X$dep" | sed -e 's/^X-R//'` if test "$enable_rpath" != no; then dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $dir" fi dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $dir" fi fi ;; -l*) dnl Handle this in the next round. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` ;; *.la) dnl Handle this in the next round. Throw away the .la's dnl directory; it is already contained in a preceding -L dnl option. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` ;; *) dnl Most likely an immediate library name. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" ;; esac done fi else dnl Didn't find the library; assume it is in the system directories dnl known to the linker and runtime loader. (All the system dnl directories known to the linker should also be known to the dnl runtime loader, otherwise the system is severely misconfigured.) LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" fi fi fi done done if test "X$rpathdirs" != "X"; then if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user must dnl pass all path elements in one option. We can arrange that for a dnl single library, but not when more than one $LIBNAMEs are used. alldirs= for found_dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" done dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" else dnl The -rpath options are cumulative. for found_dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$found_dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" done fi fi if test "X$ltrpathdirs" != "X"; then dnl When using libtool, the option that works for both libraries and dnl executables is -R. The -R options are cumulative. for found_dir in $ltrpathdirs; do LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" done fi ]) dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, dnl unless already present in VAR. dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes dnl contains two or three consecutive elements that belong together. AC_DEFUN([AC_LIB_APPENDTOVAR], [ for element in [$2]; do haveit= for x in $[$1]; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X$element"; then haveit=yes break fi done if test -z "$haveit"; then [$1]="${[$1]}${[$1]:+ }$element" fi done ]) dnl For those cases where a variable contains several -L and -l options dnl referring to unknown libraries and directories, this macro determines the dnl necessary additional linker options for the runtime path. dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) dnl sets LDADDVAR to linker options needed together with LIBSVALUE. dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, dnl otherwise linking without libtool is assumed. AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], [ AC_REQUIRE([AC_LIB_RPATH]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) $1= if test "$enable_rpath" != no; then if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode directories into the resulting dnl binary. rpathdirs= next= for opt in $2; do if test -n "$next"; then dir="$next" dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem"; then rpathdirs="$rpathdirs $dir" fi next= else case $opt in -L) next=yes ;; -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem"; then rpathdirs="$rpathdirs $dir" fi next= ;; *) next= ;; esac fi done if test "X$rpathdirs" != "X"; then if test -n ""$3""; then dnl libtool is used for linking. Use -R options. for dir in $rpathdirs; do $1="${$1}${$1:+ }-R$dir" done else dnl The linker is used for linking directly. if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user dnl must pass all path elements in one option. alldirs= for dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" done acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="$flag" else dnl The -rpath options are cumulative. for dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="${$1}${$1:+ }$flag" done fi fi fi fi fi AC_SUBST([$1]) ]) diff --git a/m4/po.m4 b/m4/po.m4 index 201c7cab3..354981d39 100644 --- a/m4/po.m4 +++ b/m4/po.m4 @@ -1,449 +1,449 @@ # po.m4 serial 15 (gettext-0.17) dnl Copyright (C) 1995-2007 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl dnl This file can be used in projects which are not available under dnl the GNU General Public License or the GNU Library General Public dnl License but which still want to provide support for the GNU gettext dnl functionality. dnl Please note that the actual code of the GNU gettext library is covered dnl by the GNU Library General Public License, and the rest of the GNU dnl gettext package is covered by the GNU General Public License. dnl They are *not* in the public domain. dnl Authors: dnl Ulrich Drepper <drepper@cygnus.com>, 1995-2000. dnl Bruno Haible <haible@clisp.cons.org>, 2000-2003. AC_PREREQ(2.50) dnl Checks for all prerequisites of the po subdirectory. AC_DEFUN([AM_PO_SUBDIRS], [ AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl AC_REQUIRE([AM_NLS])dnl dnl Release version of the gettext macros. This is used to ensure that dnl the gettext macros and po/Makefile.in.in are in sync. AC_SUBST([GETTEXT_MACRO_VERSION], [0.17]) dnl Perform the following tests also if --disable-nls has been given, dnl because they are needed for "make dist" to work. dnl Search for GNU msgfmt in the PATH. dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions. dnl The second test excludes FreeBSD msgfmt. AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt, [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], :) AC_PATH_PROG(GMSGFMT, gmsgfmt, $MSGFMT) dnl Test whether it is GNU msgfmt >= 0.15. changequote(,)dnl case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;; *) MSGFMT_015=$MSGFMT ;; esac changequote([,])dnl AC_SUBST([MSGFMT_015]) changequote(,)dnl case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;; *) GMSGFMT_015=$GMSGFMT ;; esac changequote([,])dnl AC_SUBST([GMSGFMT_015]) dnl Search for GNU xgettext 0.12 or newer in the PATH. dnl The first test excludes Solaris xgettext and early GNU xgettext versions. dnl The second test excludes FreeBSD xgettext. AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext, [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && (if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], :) dnl Remove leftover from FreeBSD xgettext call. rm -f messages.po dnl Test whether it is GNU xgettext >= 0.15. changequote(,)dnl case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;; *) XGETTEXT_015=$XGETTEXT ;; esac changequote([,])dnl AC_SUBST([XGETTEXT_015]) dnl Search for GNU msgmerge 0.11 or newer in the PATH. AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge, [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :) dnl Installation directories. dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we dnl have to define it here, so that it can be used in po/Makefile. test -n "$localedir" || localedir='${datadir}/locale' AC_SUBST([localedir]) dnl Support for AM_XGETTEXT_OPTION. test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS= AC_SUBST([XGETTEXT_EXTRA_OPTIONS]) AC_CONFIG_COMMANDS([po-directories], [[ for ac_file in $CONFIG_FILES; do # Support "outfile[:infile[:infile...]]" case "$ac_file" in *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; esac # PO directories have a Makefile.in generated from Makefile.in.in. case "$ac_file" in */Makefile.in) # Adjust a relative srcdir. ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` # In autoconf-2.13 it is called $ac_given_srcdir. # In autoconf-2.50 it is called $srcdir. test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" case "$ac_given_srcdir" in .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; /*) top_srcdir="$ac_given_srcdir" ;; *) top_srcdir="$ac_dots$ac_given_srcdir" ;; esac # Treat a directory as a PO directory if and only if it has a # POTFILES.in file. This allows packages to have multiple PO # directories under different names or in different locations. if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then rm -f "$ac_dir/POTFILES" test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES" cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ ]*\$/d" -e "s,.*, $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES" POMAKEFILEDEPS="POTFILES.in" # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend # on $ac_dir but don't depend on user-specified configuration # parameters. if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then # The LINGUAS file contains the set of available languages. if test -n "$OBSOLETE_ALL_LINGUAS"; then test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete" fi ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"` - # Hide the ALL_LINGUAS assigment from automake < 1.5. + # Hide the ALL_LINGUAS assignment from automake < 1.5. eval 'ALL_LINGUAS''=$ALL_LINGUAS_' POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS" else # The set of available languages was given in configure.in. - # Hide the ALL_LINGUAS assigment from automake < 1.5. + # Hide the ALL_LINGUAS assignment from automake < 1.5. eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS' fi # Compute POFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po) # Compute UPDATEPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update) # Compute DUMMYPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop) # Compute GMOFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo) case "$ac_given_srcdir" in .) srcdirpre= ;; *) srcdirpre='$(srcdir)/' ;; esac POFILES= UPDATEPOFILES= DUMMYPOFILES= GMOFILES= for lang in $ALL_LINGUAS; do POFILES="$POFILES $srcdirpre$lang.po" UPDATEPOFILES="$UPDATEPOFILES $lang.po-update" DUMMYPOFILES="$DUMMYPOFILES $lang.nop" GMOFILES="$GMOFILES $srcdirpre$lang.gmo" done # CATALOGS depends on both $ac_dir and the user's LINGUAS # environment variable. INST_LINGUAS= if test -n "$ALL_LINGUAS"; then for presentlang in $ALL_LINGUAS; do useit=no if test "%UNSET%" != "$LINGUAS"; then desiredlanguages="$LINGUAS" else desiredlanguages="$ALL_LINGUAS" fi for desiredlang in $desiredlanguages; do # Use the presentlang catalog if desiredlang is # a. equal to presentlang, or # b. a variant of presentlang (because in this case, # presentlang can be used as a fallback for messages # which are not translated in the desiredlang catalog). case "$desiredlang" in "$presentlang"*) useit=yes;; esac done if test $useit = yes; then INST_LINGUAS="$INST_LINGUAS $presentlang" fi done fi CATALOGS= if test -n "$INST_LINGUAS"; then for lang in $INST_LINGUAS; do CATALOGS="$CATALOGS $lang.gmo" done fi test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile" sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile" for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do if test -f "$f"; then case "$f" in *.orig | *.bak | *~) ;; *) cat "$f" >> "$ac_dir/Makefile" ;; esac fi done fi ;; esac done]], [# Capture the value of obsolete ALL_LINGUAS because we need it to compute # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it # from automake < 1.5. eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"' # Capture the value of LINGUAS because we need it to compute CATALOGS. LINGUAS="${LINGUAS-%UNSET%}" ]) ]) dnl Postprocesses a Makefile in a directory containing PO files. AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE], [ # When this code is run, in config.status, two variables have already been # set: # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in, # - LINGUAS is the value of the environment variable LINGUAS at configure # time. changequote(,)dnl # Adjust a relative srcdir. ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` # In autoconf-2.13 it is called $ac_given_srcdir. # In autoconf-2.50 it is called $srcdir. test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" case "$ac_given_srcdir" in .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; /*) top_srcdir="$ac_given_srcdir" ;; *) top_srcdir="$ac_dots$ac_given_srcdir" ;; esac # Find a way to echo strings without interpreting backslash. if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then gt_echo='echo' else if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then gt_echo='printf %s\n' else echo_func () { cat <<EOT $* EOT } gt_echo='echo_func' fi fi # A sed script that extracts the value of VARIABLE from a Makefile. sed_x_variable=' # Test if the hold space is empty. x s/P/P/ x ta # Yes it was empty. Look if we have the expected variable definition. /^[ ]*VARIABLE[ ]*=/{ # Seen the first line of the variable definition. s/^[ ]*VARIABLE[ ]*=// ba } bd :a # Here we are processing a line from the variable definition. # Remove comment, more precisely replace it with a space. s/#.*$/ / # See if the line ends in a backslash. tb :b s/\\$// # Print the line, without the trailing backslash. p tc # There was no trailing backslash. The end of the variable definition is # reached. Clear the hold space. s/^.*$// x bd :c # A trailing backslash means that the variable definition continues in the # next line. Put a nonempty string into the hold space to indicate this. s/^.*$/P/ x :d ' changequote([,])dnl # Set POTFILES to the value of the Makefile variable POTFILES. sed_x_POTFILES=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/POTFILES/g'` POTFILES=`sed -n -e "$sed_x_POTFILES" < "$ac_file"` # Compute POTFILES_DEPS as # $(foreach file, $(POTFILES), $(top_srcdir)/$(file)) POTFILES_DEPS= for file in $POTFILES; do POTFILES_DEPS="$POTFILES_DEPS "'$(top_srcdir)/'"$file" done POMAKEFILEDEPS="" if test -n "$OBSOLETE_ALL_LINGUAS"; then test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete" fi if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then # The LINGUAS file contains the set of available languages. ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"` POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS" else # Set ALL_LINGUAS to the value of the Makefile variable LINGUAS. sed_x_LINGUAS=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 's/VARIABLE/LINGUAS/g'` ALL_LINGUAS_=`sed -n -e "$sed_x_LINGUAS" < "$ac_file"` fi - # Hide the ALL_LINGUAS assigment from automake < 1.5. + # Hide the ALL_LINGUAS assignment from automake < 1.5. eval 'ALL_LINGUAS''=$ALL_LINGUAS_' # Compute POFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po) # Compute UPDATEPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update) # Compute DUMMYPOFILES # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop) # Compute GMOFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo) # Compute PROPERTIESFILES # as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).properties) # Compute CLASSFILES # as $(foreach lang, $(ALL_LINGUAS), $(top_srcdir)/$(DOMAIN)_$(lang).class) # Compute QMFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).qm) # Compute MSGFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang)).msg) # Compute RESOURCESDLLFILES # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang))/$(DOMAIN).resources.dll) case "$ac_given_srcdir" in .) srcdirpre= ;; *) srcdirpre='$(srcdir)/' ;; esac POFILES= UPDATEPOFILES= DUMMYPOFILES= GMOFILES= PROPERTIESFILES= CLASSFILES= QMFILES= MSGFILES= RESOURCESDLLFILES= for lang in $ALL_LINGUAS; do POFILES="$POFILES $srcdirpre$lang.po" UPDATEPOFILES="$UPDATEPOFILES $lang.po-update" DUMMYPOFILES="$DUMMYPOFILES $lang.nop" GMOFILES="$GMOFILES $srcdirpre$lang.gmo" PROPERTIESFILES="$PROPERTIESFILES \$(top_srcdir)/\$(DOMAIN)_$lang.properties" CLASSFILES="$CLASSFILES \$(top_srcdir)/\$(DOMAIN)_$lang.class" QMFILES="$QMFILES $srcdirpre$lang.qm" frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` MSGFILES="$MSGFILES $srcdirpre$frobbedlang.msg" frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'` RESOURCESDLLFILES="$RESOURCESDLLFILES $srcdirpre$frobbedlang/\$(DOMAIN).resources.dll" done # CATALOGS depends on both $ac_dir and the user's LINGUAS # environment variable. INST_LINGUAS= if test -n "$ALL_LINGUAS"; then for presentlang in $ALL_LINGUAS; do useit=no if test "%UNSET%" != "$LINGUAS"; then desiredlanguages="$LINGUAS" else desiredlanguages="$ALL_LINGUAS" fi for desiredlang in $desiredlanguages; do # Use the presentlang catalog if desiredlang is # a. equal to presentlang, or # b. a variant of presentlang (because in this case, # presentlang can be used as a fallback for messages # which are not translated in the desiredlang catalog). case "$desiredlang" in "$presentlang"*) useit=yes;; esac done if test $useit = yes; then INST_LINGUAS="$INST_LINGUAS $presentlang" fi done fi CATALOGS= JAVACATALOGS= QTCATALOGS= TCLCATALOGS= CSHARPCATALOGS= if test -n "$INST_LINGUAS"; then for lang in $INST_LINGUAS; do CATALOGS="$CATALOGS $lang.gmo" JAVACATALOGS="$JAVACATALOGS \$(DOMAIN)_$lang.properties" QTCATALOGS="$QTCATALOGS $lang.qm" frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` TCLCATALOGS="$TCLCATALOGS $frobbedlang.msg" frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'` CSHARPCATALOGS="$CSHARPCATALOGS $frobbedlang/\$(DOMAIN).resources.dll" done fi sed -e "s|@POTFILES_DEPS@|$POTFILES_DEPS|g" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@PROPERTIESFILES@|$PROPERTIESFILES|g" -e "s|@CLASSFILES@|$CLASSFILES|g" -e "s|@QMFILES@|$QMFILES|g" -e "s|@MSGFILES@|$MSGFILES|g" -e "s|@RESOURCESDLLFILES@|$RESOURCESDLLFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@JAVACATALOGS@|$JAVACATALOGS|g" -e "s|@QTCATALOGS@|$QTCATALOGS|g" -e "s|@TCLCATALOGS@|$TCLCATALOGS|g" -e "s|@CSHARPCATALOGS@|$CSHARPCATALOGS|g" -e 's,^#distdir:,distdir:,' < "$ac_file" > "$ac_file.tmp" if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then # Add dependencies that cannot be formulated as a simple suffix rule. for lang in $ALL_LINGUAS; do frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` cat >> "$ac_file.tmp" <<EOF $frobbedlang.msg: $lang.po @echo "\$(MSGFMT) -c --tcl -d \$(srcdir) -l $lang $srcdirpre$lang.po"; \ \$(MSGFMT) -c --tcl -d "\$(srcdir)" -l $lang $srcdirpre$lang.po || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; } EOF done fi if grep -l '@CSHARPCATALOGS@' "$ac_file" > /dev/null; then # Add dependencies that cannot be formulated as a simple suffix rule. for lang in $ALL_LINGUAS; do frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'` cat >> "$ac_file.tmp" <<EOF $frobbedlang/\$(DOMAIN).resources.dll: $lang.po @echo "\$(MSGFMT) -c --csharp -d \$(srcdir) -l $lang $srcdirpre$lang.po -r \$(DOMAIN)"; \ \$(MSGFMT) -c --csharp -d "\$(srcdir)" -l $lang $srcdirpre$lang.po -r "\$(DOMAIN)" || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; } EOF done fi if test -n "$POMAKEFILEDEPS"; then cat >> "$ac_file.tmp" <<EOF Makefile: $POMAKEFILEDEPS EOF fi mv "$ac_file.tmp" "$ac_file" ]) dnl Initializes the accumulator used by AM_XGETTEXT_OPTION. AC_DEFUN([AM_XGETTEXT_OPTION_INIT], [ XGETTEXT_EXTRA_OPTIONS= ]) dnl Registers an option to be passed to xgettext in the po subdirectory. AC_DEFUN([AM_XGETTEXT_OPTION], [ AC_REQUIRE([AM_XGETTEXT_OPTION_INIT]) XGETTEXT_EXTRA_OPTIONS="$XGETTEXT_EXTRA_OPTIONS $1" ]) diff --git a/scd/apdu.c b/scd/apdu.c index 65f770dfd..97624ebad 100644 --- a/scd/apdu.c +++ b/scd/apdu.c @@ -1,3259 +1,3259 @@ /* apdu.c - ISO 7816 APDU functions and low level I/O * Copyright (C) 2003, 2004, 2008, 2009, 2010, * 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ /* NOTE: This module is also used by other software, thus the use of the macro USE_NPTH is mandatory. For GnuPG this macro is guaranteed to be defined true. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <signal.h> #ifdef USE_NPTH # include <unistd.h> # include <fcntl.h> # include <npth.h> #endif /* If requested include the definitions for the remote APDU protocol code. */ #ifdef USE_G10CODE_RAPDU #include "rapdu.h" #endif /*USE_G10CODE_RAPDU*/ #if defined(GNUPG_SCD_MAIN_HEADER) #include GNUPG_SCD_MAIN_HEADER #elif GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ #include "../common/options.h" #include "errors.h" #include "memory.h" #include "../common/util.h" #include "../common/i18n.h" #include "dynload.h" #include "cardglue.h" #else /* GNUPG_MAJOR_VERSION != 1 */ #include "scdaemon.h" #include "../common/exechelp.h" #endif /* GNUPG_MAJOR_VERSION != 1 */ #include "../common/host2net.h" #include "iso7816.h" #include "apdu.h" #define CCID_DRIVER_INCLUDE_USB_IDS 1 #include "ccid-driver.h" struct dev_list { struct ccid_dev_table *ccid_table; const char *portstr; int idx; int idx_max; }; #define MAX_READER 4 /* Number of readers we support concurrently. */ #if defined(_WIN32) || defined(__CYGWIN__) #define DLSTDCALL __stdcall #else #define DLSTDCALL #endif #if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__) typedef unsigned int pcsc_dword_t; #else typedef unsigned long pcsc_dword_t; #endif /* A structure to collect information pertaining to one reader slot. */ struct reader_table_s { int used; /* True if slot is used. */ unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */ - /* Function pointers intialized to the various backends. */ + /* Function pointers initialized to the various backends. */ int (*connect_card)(int); int (*disconnect_card)(int); int (*close_reader)(int); int (*reset_reader)(int); int (*get_status_reader)(int, unsigned int *, int); int (*send_apdu_reader)(int,unsigned char *,size_t, unsigned char *, size_t *, pininfo_t *); int (*check_pinpad)(int, int, pininfo_t *); void (*dump_status_reader)(int); int (*set_progress_cb)(int, gcry_handler_progress_t, void*); int (*pinpad_verify)(int, int, int, int, int, pininfo_t *); int (*pinpad_modify)(int, int, int, int, int, pininfo_t *); struct { ccid_driver_t handle; } ccid; struct { long context; long card; pcsc_dword_t protocol; pcsc_dword_t verify_ioctl; pcsc_dword_t modify_ioctl; int pinmin; int pinmax; } pcsc; #ifdef USE_G10CODE_RAPDU struct { rapdu_t handle; } rapdu; #endif /*USE_G10CODE_RAPDU*/ char *rdrname; /* Name of the connected reader or NULL if unknown. */ unsigned int is_t0:1; /* True if we know that we are running T=0. */ unsigned int is_spr532:1; /* True if we know that the reader is a SPR532. */ unsigned int pinpad_varlen_supported:1; /* True if we know that the reader supports variable length pinpad input. */ unsigned int require_get_status:1; unsigned char atr[33]; size_t atrlen; /* A zero length indicates that the ATR has not yet been read; i.e. the card is not ready for use. */ #ifdef USE_NPTH npth_mutex_t lock; #endif }; typedef struct reader_table_s *reader_table_t; /* A global table to keep track of active readers. */ static struct reader_table_s reader_table[MAX_READER]; #ifdef USE_NPTH static npth_mutex_t reader_table_lock; #endif /* PC/SC constants and function pointer. */ #define PCSC_SCOPE_USER 0 #define PCSC_SCOPE_TERMINAL 1 #define PCSC_SCOPE_SYSTEM 2 #define PCSC_SCOPE_GLOBAL 3 #define PCSC_PROTOCOL_T0 1 #define PCSC_PROTOCOL_T1 2 #ifdef HAVE_W32_SYSTEM # define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */ #else # define PCSC_PROTOCOL_RAW 4 #endif #define PCSC_SHARE_EXCLUSIVE 1 #define PCSC_SHARE_SHARED 2 #define PCSC_SHARE_DIRECT 3 #define PCSC_LEAVE_CARD 0 #define PCSC_RESET_CARD 1 #define PCSC_UNPOWER_CARD 2 #define PCSC_EJECT_CARD 3 #ifdef HAVE_W32_SYSTEM # define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */ # define PCSC_ABSENT 0x0001 /* Card is absent. */ # define PCSC_PRESENT 0x0002 /* Card is present. */ # define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */ # define PCSC_POWERED 0x0004 /* Card is powered. */ # define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */ # define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */ #else # define PCSC_UNKNOWN 0x0001 # define PCSC_ABSENT 0x0002 /* Card is absent. */ # define PCSC_PRESENT 0x0004 /* Card is present. */ # define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */ # define PCSC_POWERED 0x0010 /* Card is powered. */ # define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */ # define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */ #endif #define PCSC_STATE_UNAWARE 0x0000 /* Want status. */ #define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */ #define PCSC_STATE_CHANGED 0x0002 /* State has changed. */ #define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */ #define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */ #define PCSC_STATE_EMPTY 0x0010 /* Card removed. */ #define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */ #define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */ #define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */ #define PCSC_STATE_INUSE 0x0100 /* Shared mode. */ #define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */ #ifdef HAVE_W32_SYSTEM # define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */ #endif /* Some PC/SC error codes. */ #define PCSC_E_CANCELLED 0x80100002 #define PCSC_E_CANT_DISPOSE 0x8010000E #define PCSC_E_INSUFFICIENT_BUFFER 0x80100008 #define PCSC_E_INVALID_ATR 0x80100015 #define PCSC_E_INVALID_HANDLE 0x80100003 #define PCSC_E_INVALID_PARAMETER 0x80100004 #define PCSC_E_INVALID_TARGET 0x80100005 #define PCSC_E_INVALID_VALUE 0x80100011 #define PCSC_E_NO_MEMORY 0x80100006 #define PCSC_E_UNKNOWN_READER 0x80100009 #define PCSC_E_TIMEOUT 0x8010000A #define PCSC_E_SHARING_VIOLATION 0x8010000B #define PCSC_E_NO_SMARTCARD 0x8010000C #define PCSC_E_UNKNOWN_CARD 0x8010000D #define PCSC_E_PROTO_MISMATCH 0x8010000F #define PCSC_E_NOT_READY 0x80100010 #define PCSC_E_SYSTEM_CANCELLED 0x80100012 #define PCSC_E_NOT_TRANSACTED 0x80100016 #define PCSC_E_READER_UNAVAILABLE 0x80100017 #define PCSC_E_NO_SERVICE 0x8010001D #define PCSC_E_SERVICE_STOPPED 0x8010001E #define PCSC_W_REMOVED_CARD 0x80100069 -/* Fix pcsc-lite ABI incompatibilty. */ +/* Fix pcsc-lite ABI incompatibility. */ #ifndef SCARD_CTL_CODE #ifdef _WIN32 #include <winioctl.h> #define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \ METHOD_BUFFERED, FILE_ANY_ACCESS) #else #define SCARD_CTL_CODE(code) (0x42000000 + (code)) #endif #endif #define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400) #define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1) #define FEATURE_VERIFY_PIN_DIRECT 0x06 #define FEATURE_MODIFY_PIN_DIRECT 0x07 #define FEATURE_GET_TLV_PROPERTIES 0x12 #define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2 #define PCSCv2_PART10_PROPERTY_bTimeOut2 3 #define PCSCv2_PART10_PROPERTY_bMinPINSize 6 #define PCSCv2_PART10_PROPERTY_bMaxPINSize 7 #define PCSCv2_PART10_PROPERTY_wIdVendor 11 #define PCSCv2_PART10_PROPERTY_wIdProduct 12 /* The PC/SC error is defined as a long as per specs. Due to left shifts bit 31 will get sign extended. We use this mask to fix it. */ #define PCSC_ERR_MASK(a) ((a) & 0xffffffff) struct pcsc_io_request_s { unsigned long protocol; unsigned long pci_len; }; typedef struct pcsc_io_request_s *pcsc_io_request_t; #ifdef __APPLE__ #pragma pack(1) #endif struct pcsc_readerstate_s { const char *reader; void *user_data; pcsc_dword_t current_state; pcsc_dword_t event_state; pcsc_dword_t atrlen; unsigned char atr[33]; }; #ifdef __APPLE__ #pragma pack() #endif typedef struct pcsc_readerstate_s *pcsc_readerstate_t; long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope, const void *reserved1, const void *reserved2, long *r_context); long (* DLSTDCALL pcsc_release_context) (long context); long (* DLSTDCALL pcsc_list_readers) (long context, const char *groups, char *readers, pcsc_dword_t*readerslen); long (* DLSTDCALL pcsc_get_status_change) (long context, pcsc_dword_t timeout, pcsc_readerstate_t readerstates, pcsc_dword_t nreaderstates); long (* DLSTDCALL pcsc_connect) (long context, const char *reader, pcsc_dword_t share_mode, pcsc_dword_t preferred_protocols, long *r_card, pcsc_dword_t *r_active_protocol); long (* DLSTDCALL pcsc_reconnect) (long card, pcsc_dword_t share_mode, pcsc_dword_t preferred_protocols, pcsc_dword_t initialization, pcsc_dword_t *r_active_protocol); long (* DLSTDCALL pcsc_disconnect) (long card, pcsc_dword_t disposition); long (* DLSTDCALL pcsc_status) (long card, char *reader, pcsc_dword_t *readerlen, pcsc_dword_t *r_state, pcsc_dword_t *r_protocol, unsigned char *atr, pcsc_dword_t *atrlen); long (* DLSTDCALL pcsc_begin_transaction) (long card); long (* DLSTDCALL pcsc_end_transaction) (long card, pcsc_dword_t disposition); long (* DLSTDCALL pcsc_transmit) (long card, const pcsc_io_request_t send_pci, const unsigned char *send_buffer, pcsc_dword_t send_len, pcsc_io_request_t recv_pci, unsigned char *recv_buffer, pcsc_dword_t *recv_len); long (* DLSTDCALL pcsc_set_timeout) (long context, pcsc_dword_t timeout); long (* DLSTDCALL pcsc_control) (long card, pcsc_dword_t control_code, const void *send_buffer, pcsc_dword_t send_len, void *recv_buffer, pcsc_dword_t recv_len, pcsc_dword_t *bytes_returned); /* Prototypes. */ static int pcsc_vendor_specific_init (int slot); static int pcsc_get_status (int slot, unsigned int *status, int on_wire); static int reset_pcsc_reader (int slot); static int apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire); static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo); static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo); static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo); /* Helper */ static int lock_slot (int slot) { #ifdef USE_NPTH int err; err = npth_mutex_lock (&reader_table[slot].lock); if (err) { log_error ("failed to acquire apdu lock: %s\n", strerror (err)); return SW_HOST_LOCKING_FAILED; } #endif /*USE_NPTH*/ return 0; } static int trylock_slot (int slot) { #ifdef USE_NPTH int err; err = npth_mutex_trylock (&reader_table[slot].lock); if (err == EBUSY) return SW_HOST_BUSY; else if (err) { log_error ("failed to acquire apdu lock: %s\n", strerror (err)); return SW_HOST_LOCKING_FAILED; } #endif /*USE_NPTH*/ return 0; } static void unlock_slot (int slot) { #ifdef USE_NPTH int err; err = npth_mutex_unlock (&reader_table[slot].lock); if (err) log_error ("failed to release apdu lock: %s\n", strerror (errno)); #endif /*USE_NPTH*/ } /* Find an unused reader slot for PORTSTR and put it into the reader table. Return -1 on error or the index into the reader table. Acquire slot's lock on successful return. Caller needs to unlock it. */ static int new_reader_slot (void) { int i, reader = -1; for (i=0; i < MAX_READER; i++) if (!reader_table[i].used) { reader = i; reader_table[reader].used = 1; break; } if (reader == -1) { log_error ("new_reader_slot: out of slots\n"); return -1; } if (lock_slot (reader)) { reader_table[reader].used = 0; return -1; } reader_table[reader].connect_card = NULL; reader_table[reader].disconnect_card = NULL; reader_table[reader].close_reader = NULL; reader_table[reader].reset_reader = NULL; reader_table[reader].get_status_reader = NULL; reader_table[reader].send_apdu_reader = NULL; reader_table[reader].check_pinpad = check_pcsc_pinpad; reader_table[reader].dump_status_reader = NULL; reader_table[reader].set_progress_cb = NULL; reader_table[reader].pinpad_verify = pcsc_pinpad_verify; reader_table[reader].pinpad_modify = pcsc_pinpad_modify; reader_table[reader].is_t0 = 1; reader_table[reader].is_spr532 = 0; reader_table[reader].pinpad_varlen_supported = 0; reader_table[reader].require_get_status = 1; reader_table[reader].pcsc.verify_ioctl = 0; reader_table[reader].pcsc.modify_ioctl = 0; reader_table[reader].pcsc.pinmin = -1; reader_table[reader].pcsc.pinmax = -1; return reader; } static void dump_reader_status (int slot) { if (!opt.verbose) return; if (reader_table[slot].dump_status_reader) reader_table[slot].dump_status_reader (slot); if (reader_table[slot].atrlen) { log_info ("slot %d: ATR=", slot); log_printhex ("", reader_table[slot].atr, reader_table[slot].atrlen); } } static const char * host_sw_string (long err) { switch (err) { case 0: return "okay"; case SW_HOST_OUT_OF_CORE: return "out of core"; case SW_HOST_INV_VALUE: return "invalid value"; case SW_HOST_NO_DRIVER: return "no driver"; case SW_HOST_NOT_SUPPORTED: return "not supported"; case SW_HOST_LOCKING_FAILED: return "locking failed"; case SW_HOST_BUSY: return "busy"; case SW_HOST_NO_CARD: return "no card"; case SW_HOST_CARD_INACTIVE: return "card inactive"; case SW_HOST_CARD_IO_ERROR: return "card I/O error"; case SW_HOST_GENERAL_ERROR: return "general error"; case SW_HOST_NO_READER: return "no reader"; case SW_HOST_ABORTED: return "aborted"; case SW_HOST_NO_PINPAD: return "no pinpad"; case SW_HOST_ALREADY_CONNECTED: return "already connected"; default: return "unknown host status error"; } } const char * apdu_strerror (int rc) { switch (rc) { case SW_EOF_REACHED : return "eof reached"; case SW_EEPROM_FAILURE : return "eeprom failure"; case SW_WRONG_LENGTH : return "wrong length"; case SW_CHV_WRONG : return "CHV wrong"; case SW_CHV_BLOCKED : return "CHV blocked"; case SW_REF_DATA_INV : return "referenced data invalidated"; case SW_USE_CONDITIONS : return "use conditions not satisfied"; case SW_BAD_PARAMETER : return "bad parameter"; case SW_NOT_SUPPORTED : return "not supported"; case SW_FILE_NOT_FOUND : return "file not found"; case SW_RECORD_NOT_FOUND:return "record not found"; case SW_REF_NOT_FOUND : return "reference not found"; case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file"; case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure."; case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1"; case SW_BAD_LC : return "Lc inconsistent with P0,P1"; case SW_BAD_P0_P1 : return "bad P0,P1"; case SW_INS_NOT_SUP : return "instruction not supported"; case SW_CLA_NOT_SUP : return "class not supported"; case SW_SUCCESS : return "success"; default: if ((rc & ~0x00ff) == SW_MORE_DATA) return "more data available"; if ( (rc & 0x10000) ) return host_sw_string (rc); return "unknown status error"; } } /* PC/SC Interface */ static const char * pcsc_error_string (long err) { const char *s; if (!err) return "okay"; if ((err & 0x80100000) != 0x80100000) return "invalid PC/SC error code"; err &= 0xffff; switch (err) { case 0x0002: s = "cancelled"; break; case 0x000e: s = "can't dispose"; break; case 0x0008: s = "insufficient buffer"; break; case 0x0015: s = "invalid ATR"; break; case 0x0003: s = "invalid handle"; break; case 0x0004: s = "invalid parameter"; break; case 0x0005: s = "invalid target"; break; case 0x0011: s = "invalid value"; break; case 0x0006: s = "no memory"; break; case 0x0013: s = "comm error"; break; case 0x0001: s = "internal error"; break; case 0x0014: s = "unknown error"; break; case 0x0007: s = "waited too long"; break; case 0x0009: s = "unknown reader"; break; case 0x000a: s = "timeout"; break; case 0x000b: s = "sharing violation"; break; case 0x000c: s = "no smartcard"; break; case 0x000d: s = "unknown card"; break; case 0x000f: s = "proto mismatch"; break; case 0x0010: s = "not ready"; break; case 0x0012: s = "system cancelled"; break; case 0x0016: s = "not transacted"; break; case 0x0017: s = "reader unavailable"; break; case 0x0065: s = "unsupported card"; break; case 0x0066: s = "unresponsive card"; break; case 0x0067: s = "unpowered card"; break; case 0x0068: s = "reset card"; break; case 0x0069: s = "removed card"; break; case 0x006a: s = "inserted card"; break; case 0x001f: s = "unsupported feature"; break; case 0x0019: s = "PCI too small"; break; case 0x001a: s = "reader unsupported"; break; case 0x001b: s = "duplicate reader"; break; case 0x001c: s = "card unsupported"; break; case 0x001d: s = "no service"; break; case 0x001e: s = "service stopped"; break; default: s = "unknown PC/SC error code"; break; } return s; } /* Map PC/SC error codes to our special host status words. */ static int pcsc_error_to_sw (long ec) { int rc; switch ( PCSC_ERR_MASK (ec) ) { case 0: rc = 0; break; case PCSC_E_CANCELLED: rc = SW_HOST_ABORTED; break; case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break; case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break; case PCSC_E_NO_SERVICE: case PCSC_E_SERVICE_STOPPED: case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break; case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break; case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break; case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break; case PCSC_E_INVALID_TARGET: case PCSC_E_INVALID_VALUE: case PCSC_E_INVALID_HANDLE: case PCSC_E_INVALID_PARAMETER: case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break; default: rc = SW_HOST_GENERAL_ERROR; break; } return rc; } static void dump_pcsc_reader_status (int slot) { if (reader_table[slot].pcsc.card) { log_info ("reader slot %d: active protocol:", slot); if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0)) log_printf (" T0"); else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) log_printf (" T1"); else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW)) log_printf (" raw"); log_printf ("\n"); } else log_info ("reader slot %d: not connected\n", slot); } static int pcsc_get_status (int slot, unsigned int *status, int on_wire) { long err; struct pcsc_readerstate_s rdrstates[1]; (void)on_wire; memset (rdrstates, 0, sizeof *rdrstates); rdrstates[0].reader = reader_table[slot].rdrname; rdrstates[0].current_state = PCSC_STATE_UNAWARE; err = pcsc_get_status_change (reader_table[slot].pcsc.context, 0, rdrstates, 1); if (err == PCSC_E_TIMEOUT) err = 0; /* Timeout is no error error here. */ if (err) { log_error ("pcsc_get_status_change failed: %s (0x%lx)\n", pcsc_error_string (err), err); return pcsc_error_to_sw (err); } /* log_debug */ /* ("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n", */ /* (rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"", */ /* (rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"", */ /* (rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"", */ /* (rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"", */ /* (rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"", */ /* (rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"", */ /* (rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"", */ /* (rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"", */ /* (rdrstates[0].event_state & PCSC_STATE_INUSE)? " unuse":"", */ /* (rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" ); */ *status = 0; if ( (rdrstates[0].event_state & PCSC_STATE_PRESENT) ) { *status |= APDU_CARD_PRESENT; if ( !(rdrstates[0].event_state & PCSC_STATE_MUTE) ) *status |= APDU_CARD_ACTIVE; } #ifndef HAVE_W32_SYSTEM /* We indicate a useful card if it is not in use by another application. This is because we only use exclusive access mode. */ if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE) && !(rdrstates[0].event_state & PCSC_STATE_INUSE) ) *status |= APDU_CARD_USABLE; #else /* Some winscard drivers may set EXCLUSIVE and INUSE at the same time when we are the only user (SCM SCR335) under Windows. */ if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) == (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)) *status |= APDU_CARD_USABLE; #endif return 0; } /* Send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be stored at BUFLEN. Returns: A status word. */ static int pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen, pininfo_t *pininfo) { long err; struct pcsc_io_request_s send_pci; pcsc_dword_t recv_len; (void)pininfo; if (!reader_table[slot].atrlen && (err = reset_pcsc_reader (slot))) return err; if (DBG_CARD_IO) log_printhex (" PCSC_data:", apdu, apdulen); if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1)) send_pci.protocol = PCSC_PROTOCOL_T1; else send_pci.protocol = PCSC_PROTOCOL_T0; send_pci.pci_len = sizeof send_pci; recv_len = *buflen; err = pcsc_transmit (reader_table[slot].pcsc.card, &send_pci, apdu, apdulen, NULL, buffer, &recv_len); *buflen = recv_len; if (err) log_error ("pcsc_transmit failed: %s (0x%lx)\n", pcsc_error_string (err), err); return pcsc_error_to_sw (err); } /* Do some control with the value of IOCTL_CODE to the card inserted to SLOT. Input buffer is specified by CNTLBUF of length LEN. Output buffer is specified by BUFFER of length *BUFLEN, and the actual output size will be stored at BUFLEN. Returns: A status word. This routine is used for PIN pad input support. */ static int control_pcsc (int slot, pcsc_dword_t ioctl_code, const unsigned char *cntlbuf, size_t len, unsigned char *buffer, pcsc_dword_t *buflen) { long err; err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code, cntlbuf, len, buffer, buflen? *buflen:0, buflen); if (err) { log_error ("pcsc_control failed: %s (0x%lx)\n", pcsc_error_string (err), err); return pcsc_error_to_sw (err); } return 0; } static int close_pcsc_reader (int slot) { pcsc_release_context (reader_table[slot].pcsc.context); return 0; } /* Connect a PC/SC card. */ static int connect_pcsc_card (int slot) { long err; assert (slot >= 0 && slot < MAX_READER); if (reader_table[slot].pcsc.card) return SW_HOST_ALREADY_CONNECTED; reader_table[slot].atrlen = 0; reader_table[slot].is_t0 = 0; err = pcsc_connect (reader_table[slot].pcsc.context, reader_table[slot].rdrname, PCSC_SHARE_EXCLUSIVE, PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1, &reader_table[slot].pcsc.card, &reader_table[slot].pcsc.protocol); if (err) { reader_table[slot].pcsc.card = 0; if (err != PCSC_E_NO_SMARTCARD) log_error ("pcsc_connect failed: %s (0x%lx)\n", pcsc_error_string (err), err); } else { char reader[250]; pcsc_dword_t readerlen, atrlen; pcsc_dword_t card_state, card_protocol; pcsc_vendor_specific_init (slot); atrlen = DIM (reader_table[0].atr); readerlen = sizeof reader -1 ; err = pcsc_status (reader_table[slot].pcsc.card, reader, &readerlen, &card_state, &card_protocol, reader_table[slot].atr, &atrlen); if (err) log_error ("pcsc_status failed: %s (0x%lx) %lu\n", pcsc_error_string (err), err, (long unsigned int)readerlen); else { if (atrlen > DIM (reader_table[0].atr)) log_bug ("ATR returned by pcsc_status is too large\n"); reader_table[slot].atrlen = atrlen; reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0); } } dump_reader_status (slot); return pcsc_error_to_sw (err); } static int disconnect_pcsc_card (int slot) { long err; assert (slot >= 0 && slot < MAX_READER); if (!reader_table[slot].pcsc.card) return 0; err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD); if (err) { log_error ("pcsc_disconnect failed: %s (0x%lx)\n", pcsc_error_string (err), err); return SW_HOST_CARD_IO_ERROR; } reader_table[slot].pcsc.card = 0; return 0; } /* Send an PC/SC reset command and return a status word on error or 0 on success. */ static int reset_pcsc_reader (int slot) { int sw; sw = disconnect_pcsc_card (slot); if (!sw) sw = connect_pcsc_card (slot); return sw; } /* Examine reader specific parameters and initialize. This is mostly for pinpad input. Called at opening the connection to the reader. */ static int pcsc_vendor_specific_init (int slot) { unsigned char buf[256]; pcsc_dword_t len; int sw; int vendor = 0; int product = 0; pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1; unsigned char *p; len = sizeof (buf); sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len); if (sw) { log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n", sw); return SW_NOT_SUPPORTED; } else { p = buf; while (p < buf + len) { unsigned char code = *p++; int l = *p++; unsigned int v = 0; if (l == 1) v = p[0]; else if (l == 2) v = buf16_to_uint (p); else if (l == 4) v = buf32_to_uint (p); if (code == FEATURE_VERIFY_PIN_DIRECT) reader_table[slot].pcsc.verify_ioctl = v; else if (code == FEATURE_MODIFY_PIN_DIRECT) reader_table[slot].pcsc.modify_ioctl = v; else if (code == FEATURE_GET_TLV_PROPERTIES) get_tlv_ioctl = v; if (DBG_CARD_IO) log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v); p += l; } } if (get_tlv_ioctl == (pcsc_dword_t)-1) { /* * For system which doesn't support GET_TLV_PROPERTIES, * we put some heuristics here. */ if (reader_table[slot].rdrname) { if (strstr (reader_table[slot].rdrname, "SPRx32")) { reader_table[slot].is_spr532 = 1; reader_table[slot].pinpad_varlen_supported = 1; } else if (strstr (reader_table[slot].rdrname, "ST-2xxx")) { reader_table[slot].pcsc.pinmax = 15; reader_table[slot].pinpad_varlen_supported = 1; } else if (strstr (reader_table[slot].rdrname, "cyberJack") || strstr (reader_table[slot].rdrname, "DIGIPASS") || strstr (reader_table[slot].rdrname, "Gnuk") || strstr (reader_table[slot].rdrname, "KAAN")) reader_table[slot].pinpad_varlen_supported = 1; } return 0; } len = sizeof (buf); sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len); if (sw) { log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw); return SW_NOT_SUPPORTED; } p = buf; while (p < buf + len) { unsigned char tag = *p++; int l = *p++; unsigned int v = 0; /* Umm... here is little endian, while the encoding above is big. */ if (l == 1) v = p[0]; else if (l == 2) v = (((unsigned int)p[1] << 8) | p[0]); else if (l == 4) v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]); if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize) reader_table[slot].pcsc.pinmin = v; else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize) reader_table[slot].pcsc.pinmax = v; else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor) vendor = v; else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct) product = v; if (DBG_CARD_IO) log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v); p += l; } if (vendor == VENDOR_VEGA && product == VEGA_ALPHA) { /* * Please read the comment of ccid_vendor_specific_init in * ccid-driver.c. */ const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' }; sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE, cmd, sizeof (cmd), NULL, 0); if (sw) return SW_NOT_SUPPORTED; } else if (vendor == VENDOR_SCM && product == SCM_SPR532) /* SCM SPR532 */ { reader_table[slot].is_spr532 = 1; reader_table[slot].pinpad_varlen_supported = 1; } else if (vendor == 0x046a) { /* Cherry ST-2xxx (product == 0x003e) supports TPDU level * exchange. Other products which only support short APDU level * exchange only work with shorter keys like RSA 1024. */ reader_table[slot].pcsc.pinmax = 15; reader_table[slot].pinpad_varlen_supported = 1; } else if (vendor == 0x0c4b /* Tested with Reiner cyberJack GO */ || vendor == 0x1a44 /* Tested with Vasco DIGIPASS 920 */ || vendor == 0x234b /* Tested with FSIJ Gnuk Token */ || vendor == 0x0d46 /* Tested with KAAN Advanced??? */) reader_table[slot].pinpad_varlen_supported = 1; return 0; } /* Open the PC/SC reader without using the wrapper. Returns -1 on error or a slot number for the reader. */ static int open_pcsc_reader (const char *portstr) { long err; int slot; char *list = NULL; char *rdrname = NULL; pcsc_dword_t nreader; char *p; slot = new_reader_slot (); if (slot == -1) return -1; /* Fixme: Allocating a context for each slot is not required. One global context should be sufficient. */ err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL, &reader_table[slot].pcsc.context); if (err) { log_error ("pcsc_establish_context failed: %s (0x%lx)\n", pcsc_error_string (err), err); reader_table[slot].used = 0; unlock_slot (slot); return -1; } err = pcsc_list_readers (reader_table[slot].pcsc.context, NULL, NULL, &nreader); if (!err) { list = xtrymalloc (nreader+1); /* Better add 1 for safety reasons. */ if (!list) { log_error ("error allocating memory for reader list\n"); pcsc_release_context (reader_table[slot].pcsc.context); reader_table[slot].used = 0; unlock_slot (slot); return -1 /*SW_HOST_OUT_OF_CORE*/; } err = pcsc_list_readers (reader_table[slot].pcsc.context, NULL, list, &nreader); } if (err) { log_error ("pcsc_list_readers failed: %s (0x%lx)\n", pcsc_error_string (err), err); pcsc_release_context (reader_table[slot].pcsc.context); reader_table[slot].used = 0; xfree (list); unlock_slot (slot); return -1; } p = list; while (nreader) { if (!*p && !p[1]) break; log_info ("detected reader '%s'\n", p); if (nreader < (strlen (p)+1)) { log_error ("invalid response from pcsc_list_readers\n"); break; } if (!rdrname && portstr && !strncmp (p, portstr, strlen (portstr))) rdrname = p; nreader -= strlen (p)+1; p += strlen (p) + 1; } if (!rdrname) rdrname = list; reader_table[slot].rdrname = xtrystrdup (rdrname); if (!reader_table[slot].rdrname) { log_error ("error allocating memory for reader name\n"); pcsc_release_context (reader_table[slot].pcsc.context); reader_table[slot].used = 0; unlock_slot (slot); return -1; } xfree (list); list = NULL; reader_table[slot].pcsc.card = 0; reader_table[slot].atrlen = 0; reader_table[slot].connect_card = connect_pcsc_card; reader_table[slot].disconnect_card = disconnect_pcsc_card; reader_table[slot].close_reader = close_pcsc_reader; reader_table[slot].reset_reader = reset_pcsc_reader; reader_table[slot].get_status_reader = pcsc_get_status; reader_table[slot].send_apdu_reader = pcsc_send_apdu; reader_table[slot].dump_status_reader = dump_pcsc_reader_status; dump_reader_status (slot); unlock_slot (slot); return slot; } /* Check whether the reader supports the ISO command code COMMAND on the pinpad. Return 0 on success. */ static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo) { int r; if (reader_table[slot].pcsc.pinmin >= 0) pininfo->minlen = reader_table[slot].pcsc.pinmin; if (reader_table[slot].pcsc.pinmax >= 0) pininfo->maxlen = reader_table[slot].pcsc.pinmax; if (!pininfo->minlen) pininfo->minlen = 1; if (!pininfo->maxlen) pininfo->maxlen = 15; if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0) || (command == ISO7816_CHANGE_REFERENCE_DATA && reader_table[slot].pcsc.modify_ioctl != 0)) r = 0; /* Success */ else r = SW_NOT_SUPPORTED; if (DBG_CARD_IO) log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n", (unsigned int)command, r); if (reader_table[slot].pinpad_varlen_supported) pininfo->fixedlen = 0; return r; } #define PIN_VERIFY_STRUCTURE_SIZE 24 static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo) { int sw; unsigned char *pin_verify; int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen; /* * The result buffer is only expected to have two-byte result on * return. However, some implementation uses this buffer for lower * layer too and it assumes that there is enough space for lower * layer communication. Such an implementation fails for TPDU * readers with "insufficient buffer", as it needs header and * trailer. Six is the number for header + result + trailer (TPDU). */ unsigned char result[6]; pcsc_dword_t resultlen = 6; int no_lc; if (!reader_table[slot].atrlen && (sw = reset_pcsc_reader (slot))) return sw; if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16) return SW_NOT_SUPPORTED; pin_verify = xtrymalloc (len); if (!pin_verify) return SW_HOST_OUT_OF_CORE; no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532); pin_verify[0] = 0x00; /* bTimeOut */ pin_verify[1] = 0x00; /* bTimeOut2 */ pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */ pin_verify[4] = 0x00; /* bmPINLengthFormat */ pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */ pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */ pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */ if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen) pin_verify[7] |= 0x01; /* Max size reached. */ pin_verify[8] = 0x01; /* bNumberMessage: One message */ pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */ pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */ pin_verify[11] = 0x00; /* bMsgIndex */ pin_verify[12] = 0x00; /* bTeoPrologue[0] */ pin_verify[13] = 0x00; /* bTeoPrologue[1] */ pin_verify[14] = pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */ pin_verify[15] = pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */ pin_verify[16] = 0x00; /* ulDataLength */ pin_verify[17] = 0x00; /* ulDataLength */ pin_verify[18] = 0x00; /* ulDataLength */ pin_verify[19] = class; /* abData[0] */ pin_verify[20] = ins; /* abData[1] */ pin_verify[21] = p0; /* abData[2] */ pin_verify[22] = p1; /* abData[3] */ pin_verify[23] = pininfo->fixedlen; /* abData[4] */ if (pininfo->fixedlen) memset (&pin_verify[24], 0xff, pininfo->fixedlen); else if (no_lc) len--; if (DBG_CARD_IO) log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n", class, ins, p0, p1, len, pininfo->maxlen); sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl, pin_verify, len, result, &resultlen); xfree (pin_verify); if (sw || resultlen < 2) { log_error ("control_pcsc failed: %d\n", sw); return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; if (DBG_CARD_IO) log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); return sw; } #define PIN_MODIFY_STRUCTURE_SIZE 29 static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo) { int sw; unsigned char *pin_modify; int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen; unsigned char result[6]; /* See the comment at pinpad_verify. */ pcsc_dword_t resultlen = 6; int no_lc; if (!reader_table[slot].atrlen && (sw = reset_pcsc_reader (slot))) return sw; if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16) return SW_NOT_SUPPORTED; pin_modify = xtrymalloc (len); if (!pin_modify) return SW_HOST_OUT_OF_CORE; no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532); pin_modify[0] = 0x00; /* bTimeOut */ pin_modify[1] = 0x00; /* bTimeOut2 */ pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */ pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */ pin_modify[4] = 0x00; /* bmPINLengthFormat */ pin_modify[5] = 0x00; /* bInsertionOffsetOld */ pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */ pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */ pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */ pin_modify[9] = (p0 == 0 ? 0x03 : 0x01); /* bConfirmPIN * 0x00: new PIN once * 0x01: new PIN twice (confirmation) * 0x02: old PIN and new PIN once * 0x03: old PIN and new PIN twice (confirmation) */ pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */ if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen) pin_modify[10] |= 0x01; /* Max size reached. */ pin_modify[11] = 0x03; /* bNumberMessage: Three messages */ pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */ pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */ pin_modify[14] = 0x00; /* bMsgIndex1 */ pin_modify[15] = 0x01; /* bMsgIndex2 */ pin_modify[16] = 0x02; /* bMsgIndex3 */ pin_modify[17] = 0x00; /* bTeoPrologue[0] */ pin_modify[18] = 0x00; /* bTeoPrologue[1] */ pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */ pin_modify[20] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* ulDataLength */ pin_modify[21] = 0x00; /* ulDataLength */ pin_modify[22] = 0x00; /* ulDataLength */ pin_modify[23] = 0x00; /* ulDataLength */ pin_modify[24] = class; /* abData[0] */ pin_modify[25] = ins; /* abData[1] */ pin_modify[26] = p0; /* abData[2] */ pin_modify[27] = p1; /* abData[3] */ pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */ if (pininfo->fixedlen) memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen); else if (no_lc) len--; if (DBG_CARD_IO) log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n", class, ins, p0, p1, len, (int)pininfo->maxlen); sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl, pin_modify, len, result, &resultlen); xfree (pin_modify); if (sw || resultlen < 2) { log_error ("control_pcsc failed: %d\n", sw); return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; if (DBG_CARD_IO) log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); return sw; } #ifdef HAVE_LIBUSB /* Internal CCID driver interface. */ static void dump_ccid_reader_status (int slot) { log_info ("reader slot %d: using ccid driver\n", slot); } static int close_ccid_reader (int slot) { ccid_close_reader (reader_table[slot].ccid.handle); return 0; } static int reset_ccid_reader (int slot) { int err; reader_table_t slotp = reader_table + slot; unsigned char atr[33]; size_t atrlen; err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen); if (err) return err; /* If the reset was successful, update the ATR. */ assert (sizeof slotp->atr >= sizeof atr); slotp->atrlen = atrlen; memcpy (slotp->atr, atr, atrlen); dump_reader_status (slot); return 0; } static int set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg) { reader_table_t slotp = reader_table + slot; return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg); } static int get_status_ccid (int slot, unsigned int *status, int on_wire) { int rc; int bits; rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire); if (rc) return rc; if (bits == 0) *status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE); else if (bits == 1) *status = APDU_CARD_PRESENT; else *status = 0; return 0; } /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be set to BUFLEN. Returns: Internal CCID driver error code. */ static int send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen, pininfo_t *pininfo) { long err; size_t maxbuflen; /* If we don't have an ATR, we need to reset the reader first. */ if (!reader_table[slot].atrlen && (err = reset_ccid_reader (slot))) return err; if (DBG_CARD_IO) log_printhex (" raw apdu:", apdu, apdulen); maxbuflen = *buflen; if (pininfo) err = ccid_transceive_secure (reader_table[slot].ccid.handle, apdu, apdulen, pininfo, buffer, maxbuflen, buflen); else err = ccid_transceive (reader_table[slot].ccid.handle, apdu, apdulen, buffer, maxbuflen, buflen); if (err) log_error ("ccid_transceive failed: (0x%lx)\n", err); return err; } /* Check whether the CCID reader supports the ISO command code COMMAND on the pinpad. Return 0 on success. For a description of the pin parameters, see ccid-driver.c */ static int check_ccid_pinpad (int slot, int command, pininfo_t *pininfo) { unsigned char apdu[] = { 0, 0, 0, 0x81 }; apdu[1] = command; return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu, sizeof apdu, pininfo, NULL, 0, NULL); } static int ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo) { unsigned char apdu[4]; int err, sw; unsigned char result[2]; size_t resultlen = 2; apdu[0] = class; apdu[1] = ins; apdu[2] = p0; apdu[3] = p1; err = ccid_transceive_secure (reader_table[slot].ccid.handle, apdu, sizeof apdu, pininfo, result, 2, &resultlen); if (err) return err; if (resultlen < 2) return SW_HOST_INCOMPLETE_CARD_RESPONSE; sw = (result[resultlen-2] << 8) | result[resultlen-1]; return sw; } /* Open the reader and try to read an ATR. */ static int open_ccid_reader (struct dev_list *dl) { int err; int slot; int require_get_status; reader_table_t slotp; slot = new_reader_slot (); if (slot == -1) return -1; slotp = reader_table + slot; err = ccid_open_reader (dl->portstr, dl->idx, dl->ccid_table, &slotp->ccid.handle, &slotp->rdrname); if (!err) err = ccid_get_atr (slotp->ccid.handle, slotp->atr, sizeof slotp->atr, &slotp->atrlen); if (err) { slotp->used = 0; unlock_slot (slot); return -1; } require_get_status = ccid_require_get_status (slotp->ccid.handle); reader_table[slot].close_reader = close_ccid_reader; reader_table[slot].reset_reader = reset_ccid_reader; reader_table[slot].get_status_reader = get_status_ccid; reader_table[slot].send_apdu_reader = send_apdu_ccid; reader_table[slot].check_pinpad = check_ccid_pinpad; reader_table[slot].dump_status_reader = dump_ccid_reader_status; reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader; reader_table[slot].pinpad_verify = ccid_pinpad_operation; reader_table[slot].pinpad_modify = ccid_pinpad_operation; /* Our CCID reader code does not support T=0 at all, thus reset the flag. */ reader_table[slot].is_t0 = 0; reader_table[slot].require_get_status = require_get_status; dump_reader_status (slot); unlock_slot (slot); return slot; } #endif /* HAVE_LIBUSB */ #ifdef USE_G10CODE_RAPDU /* The Remote APDU Interface. This uses the Remote APDU protocol to contact a reader. The port number is actually an index into the list of ports as returned via the protocol. */ static int rapdu_status_to_sw (int status) { int rc; switch (status) { case RAPDU_STATUS_SUCCESS: rc = 0; break; case RAPDU_STATUS_INVCMD: case RAPDU_STATUS_INVPROT: case RAPDU_STATUS_INVSEQ: case RAPDU_STATUS_INVCOOKIE: case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break; case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break; case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break; case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break; case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break; case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break; case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break; default: rc = SW_HOST_GENERAL_ERROR; break; } return rc; } static int close_rapdu_reader (int slot) { rapdu_release (reader_table[slot].rapdu.handle); return 0; } static int reset_rapdu_reader (int slot) { int err; reader_table_t slotp; rapdu_msg_t msg = NULL; slotp = reader_table + slot; err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET); if (err) { log_error ("sending rapdu command RESET failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); rapdu_msg_release (msg); return rapdu_status_to_sw (err); } err = rapdu_read_msg (slotp->rapdu.handle, &msg); if (err) { log_error ("receiving rapdu message failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); rapdu_msg_release (msg); return rapdu_status_to_sw (err); } if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) { int sw = rapdu_status_to_sw (msg->cmd); log_error ("rapdu command RESET failed: %s\n", rapdu_strerror (msg->cmd)); rapdu_msg_release (msg); return sw; } if (msg->datalen > DIM (slotp->atr)) { log_error ("ATR returned by the RAPDU layer is too large\n"); rapdu_msg_release (msg); return SW_HOST_INV_VALUE; } slotp->atrlen = msg->datalen; memcpy (slotp->atr, msg->data, msg->datalen); rapdu_msg_release (msg); return 0; } static int my_rapdu_get_status (int slot, unsigned int *status, int on_wire) { int err; reader_table_t slotp; rapdu_msg_t msg = NULL; int oldslot; (void)on_wire; slotp = reader_table + slot; oldslot = rapdu_set_reader (slotp->rapdu.handle, slot); err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS); rapdu_set_reader (slotp->rapdu.handle, oldslot); if (err) { log_error ("sending rapdu command GET_STATUS failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); return rapdu_status_to_sw (err); } err = rapdu_read_msg (slotp->rapdu.handle, &msg); if (err) { log_error ("receiving rapdu message failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); rapdu_msg_release (msg); return rapdu_status_to_sw (err); } if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) { int sw = rapdu_status_to_sw (msg->cmd); log_error ("rapdu command GET_STATUS failed: %s\n", rapdu_strerror (msg->cmd)); rapdu_msg_release (msg); return sw; } *status = msg->data[0]; rapdu_msg_release (msg); return 0; } /* Actually send the APDU of length APDULEN to SLOT and return a maximum of *BUFLEN data in BUFFER, the actual returned size will be set to BUFLEN. Returns: APDU error code. */ static int my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen, pininfo_t *pininfo) { int err; reader_table_t slotp; rapdu_msg_t msg = NULL; size_t maxlen = *buflen; slotp = reader_table + slot; *buflen = 0; if (DBG_CARD_IO) log_printhex (" APDU_data:", apdu, apdulen); if (apdulen < 4) { log_error ("rapdu_send_apdu: APDU is too short\n"); return SW_HOST_INV_VALUE; } err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen); if (err) { log_error ("sending rapdu command APDU failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); rapdu_msg_release (msg); return rapdu_status_to_sw (err); } err = rapdu_read_msg (slotp->rapdu.handle, &msg); if (err) { log_error ("receiving rapdu message failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); rapdu_msg_release (msg); return rapdu_status_to_sw (err); } if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) { int sw = rapdu_status_to_sw (msg->cmd); log_error ("rapdu command APDU failed: %s\n", rapdu_strerror (msg->cmd)); rapdu_msg_release (msg); return sw; } if (msg->datalen > maxlen) { log_error ("rapdu response apdu too large\n"); rapdu_msg_release (msg); return SW_HOST_INV_VALUE; } *buflen = msg->datalen; memcpy (buffer, msg->data, msg->datalen); rapdu_msg_release (msg); return 0; } static int open_rapdu_reader (int portno, const unsigned char *cookie, size_t length, int (*readfnc) (void *opaque, void *buffer, size_t size), void *readfnc_value, int (*writefnc) (void *opaque, const void *buffer, size_t size), void *writefnc_value, void (*closefnc) (void *opaque), void *closefnc_value) { int err; int slot; reader_table_t slotp; rapdu_msg_t msg = NULL; slot = new_reader_slot (); if (slot == -1) return -1; slotp = reader_table + slot; slotp->rapdu.handle = rapdu_new (); if (!slotp->rapdu.handle) { slotp->used = 0; unlock_slot (slot); return -1; } rapdu_set_reader (slotp->rapdu.handle, portno); rapdu_set_iofunc (slotp->rapdu.handle, readfnc, readfnc_value, writefnc, writefnc_value, closefnc, closefnc_value); rapdu_set_cookie (slotp->rapdu.handle, cookie, length); /* First try to get the current ATR, but if the card is inactive issue a reset instead. */ err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR); if (err == RAPDU_STATUS_NEEDRESET) err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET); if (err) { log_info ("sending rapdu command GET_ATR/RESET failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); goto failure; } err = rapdu_read_msg (slotp->rapdu.handle, &msg); if (err) { log_info ("receiving rapdu message failed: %s\n", err < 0 ? strerror (errno): rapdu_strerror (err)); goto failure; } if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen) { log_info ("rapdu command GET ATR failed: %s\n", rapdu_strerror (msg->cmd)); goto failure; } if (msg->datalen > DIM (slotp->atr)) { log_error ("ATR returned by the RAPDU layer is too large\n"); goto failure; } slotp->atrlen = msg->datalen; memcpy (slotp->atr, msg->data, msg->datalen); reader_table[slot].close_reader = close_rapdu_reader; reader_table[slot].reset_reader = reset_rapdu_reader; reader_table[slot].get_status_reader = my_rapdu_get_status; reader_table[slot].send_apdu_reader = my_rapdu_send_apdu; reader_table[slot].check_pinpad = NULL; reader_table[slot].dump_status_reader = NULL; reader_table[slot].pinpad_verify = NULL; reader_table[slot].pinpad_modify = NULL; dump_reader_status (slot); rapdu_msg_release (msg); unlock_slot (slot); return slot; failure: rapdu_msg_release (msg); rapdu_release (slotp->rapdu.handle); slotp->used = 0; unlock_slot (slot); return -1; } #endif /*USE_G10CODE_RAPDU*/ /* Driver Access */ gpg_error_t apdu_dev_list_start (const char *portstr, struct dev_list **l_p) { struct dev_list *dl = xtrymalloc (sizeof (struct dev_list)); *l_p = NULL; if (!dl) return gpg_error_from_syserror (); dl->portstr = portstr; dl->idx = 0; npth_mutex_lock (&reader_table_lock); #ifdef HAVE_LIBUSB if (opt.disable_ccid) { dl->ccid_table = NULL; dl->idx_max = 1; } else { gpg_error_t err; err = ccid_dev_scan (&dl->idx_max, &dl->ccid_table); if (err) return err; if (dl->idx_max == 0) { /* If a CCID reader specification has been given, the user does not want a fallback to other drivers. */ if (portstr && strlen (portstr) > 5 && portstr[4] == ':') { if (DBG_READER) log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n"); xfree (dl); npth_mutex_unlock (&reader_table_lock); return gpg_error (GPG_ERR_ENODEV); } else dl->idx_max = 1; } } #else dl->ccid_table = NULL; dl->idx_max = 1; #endif /* HAVE_LIBUSB */ *l_p = dl; return 0; } void apdu_dev_list_finish (struct dev_list *dl) { #ifdef HAVE_LIBUSB if (dl->ccid_table) ccid_dev_scan_finish (dl->ccid_table, dl->idx_max); #endif xfree (dl); npth_mutex_unlock (&reader_table_lock); } /* Open the reader and return an internal slot number or -1 on error. If PORTSTR is NULL we default to a suitable port (for ctAPI: the first USB reader. For PC/SC the first listed reader). */ static int apdu_open_one_reader (const char *portstr) { static int pcsc_api_loaded; int slot; if (DBG_READER) log_debug ("enter: apdu_open_reader: portstr=%s\n", portstr); /* Lets try the PC/SC API */ if (!pcsc_api_loaded) { void *handle; handle = dlopen (opt.pcsc_driver, RTLD_LAZY); if (!handle) { log_error ("apdu_open_reader: failed to open driver '%s': %s\n", opt.pcsc_driver, dlerror ()); return -1; } pcsc_establish_context = dlsym (handle, "SCardEstablishContext"); pcsc_release_context = dlsym (handle, "SCardReleaseContext"); pcsc_list_readers = dlsym (handle, "SCardListReaders"); #if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_list_readers) pcsc_list_readers = dlsym (handle, "SCardListReadersA"); #endif pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange"); #if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_get_status_change) pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA"); #endif pcsc_connect = dlsym (handle, "SCardConnect"); #if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_connect) pcsc_connect = dlsym (handle, "SCardConnectA"); #endif pcsc_reconnect = dlsym (handle, "SCardReconnect"); #if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_reconnect) pcsc_reconnect = dlsym (handle, "SCardReconnectA"); #endif pcsc_disconnect = dlsym (handle, "SCardDisconnect"); pcsc_status = dlsym (handle, "SCardStatus"); #if defined(_WIN32) || defined(__CYGWIN__) if (!pcsc_status) pcsc_status = dlsym (handle, "SCardStatusA"); #endif pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction"); pcsc_end_transaction = dlsym (handle, "SCardEndTransaction"); pcsc_transmit = dlsym (handle, "SCardTransmit"); pcsc_set_timeout = dlsym (handle, "SCardSetTimeout"); pcsc_control = dlsym (handle, "SCardControl"); if (!pcsc_establish_context || !pcsc_release_context || !pcsc_list_readers || !pcsc_get_status_change || !pcsc_connect || !pcsc_reconnect || !pcsc_disconnect || !pcsc_status || !pcsc_begin_transaction || !pcsc_end_transaction || !pcsc_transmit || !pcsc_control /* || !pcsc_set_timeout */) { /* Note that set_timeout is currently not used and also not available under Windows. */ log_error ("apdu_open_reader: invalid PC/SC driver " "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n", !!pcsc_establish_context, !!pcsc_release_context, !!pcsc_list_readers, !!pcsc_get_status_change, !!pcsc_connect, !!pcsc_reconnect, !!pcsc_disconnect, !!pcsc_status, !!pcsc_begin_transaction, !!pcsc_end_transaction, !!pcsc_transmit, !!pcsc_set_timeout, !!pcsc_control ); dlclose (handle); return -1; } pcsc_api_loaded = 1; } slot = open_pcsc_reader (portstr); if (DBG_READER) log_debug ("leave: apdu_open_reader => slot=%d [pc/sc]\n", slot); return slot; } int apdu_open_reader (struct dev_list *dl, int app_empty) { int slot; #ifdef HAVE_LIBUSB if (dl->ccid_table) { /* CCID readers. */ int readerno; /* See whether we want to use the reader ID string or a reader number. A readerno of -1 indicates that the reader ID string is to be used. */ if (dl->portstr && strchr (dl->portstr, ':')) readerno = -1; /* We want to use the readerid. */ else if (dl->portstr) { readerno = atoi (dl->portstr); if (readerno < 0) { return -1; } } else readerno = 0; /* Default. */ if (readerno > 0) { /* Use single, the specific reader. */ if (readerno >= dl->idx_max) return -1; dl->idx = readerno; dl->portstr = NULL; slot = open_ccid_reader (dl); dl->idx = dl->idx_max; if (slot >= 0) return slot; else return -1; } while (dl->idx < dl->idx_max) { unsigned int bai = ccid_get_BAI (dl->idx, dl->ccid_table); if (DBG_READER) log_debug ("apdu_open_reader: BAI=%x\n", bai); /* Check identity by BAI against already opened HANDLEs. */ for (slot = 0; slot < MAX_READER; slot++) if (reader_table[slot].used && reader_table[slot].ccid.handle && ccid_compare_BAI (reader_table[slot].ccid.handle, bai)) break; if (slot == MAX_READER) { /* Found a new device. */ if (DBG_READER) log_debug ("apdu_open_reader: new device=%x\n", bai); slot = open_ccid_reader (dl); dl->idx++; if (slot >= 0) return slot; else { /* Skip this reader. */ log_error ("ccid open error: skip\n"); continue; } } else dl->idx++; } /* Not found. Try one for PC/SC, only when it's the initial scan. */ if (app_empty && dl->idx == dl->idx_max) { dl->idx++; slot = apdu_open_one_reader (dl->portstr); } else slot = -1; } else #endif { /* PC/SC readers. */ if (app_empty && dl->idx == 0) { dl->idx++; slot = apdu_open_one_reader (dl->portstr); } else slot = -1; } return slot; } /* Open an remote reader and return an internal slot number or -1 on error. This function is an alternative to apdu_open_reader and used with remote readers only. Note that the supplied CLOSEFNC will only be called once and the slot will not be valid afther this. If PORTSTR is NULL we default to the first available port. */ int apdu_open_remote_reader (const char *portstr, const unsigned char *cookie, size_t length, int (*readfnc) (void *opaque, void *buffer, size_t size), void *readfnc_value, int (*writefnc) (void *opaque, const void *buffer, size_t size), void *writefnc_value, void (*closefnc) (void *opaque), void *closefnc_value) { #ifdef USE_G10CODE_RAPDU return open_rapdu_reader (portstr? atoi (portstr) : 0, cookie, length, readfnc, readfnc_value, writefnc, writefnc_value, closefnc, closefnc_value); #else (void)portstr; (void)cookie; (void)length; (void)readfnc; (void)readfnc_value; (void)writefnc; (void)writefnc_value; (void)closefnc; (void)closefnc_value; #ifdef _WIN32 errno = ENOENT; #else errno = ENOSYS; #endif return -1; #endif } int apdu_close_reader (int slot) { int sw; if (DBG_READER) log_debug ("enter: apdu_close_reader: slot=%d\n", slot); if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) { if (DBG_READER) log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n"); return SW_HOST_NO_DRIVER; } sw = apdu_disconnect (slot); if (sw) { /* * When the reader/token was removed it might come here. * It should go through to call CLOSE_READER even if we got an error. */ if (DBG_READER) log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw); } if (reader_table[slot].close_reader) { sw = reader_table[slot].close_reader (slot); reader_table[slot].used = 0; if (DBG_READER) log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw); return sw; } xfree (reader_table[slot].rdrname); reader_table[slot].rdrname = NULL; reader_table[slot].used = 0; if (DBG_READER) log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n"); return SW_HOST_NOT_SUPPORTED; } /* Function suitable for a cleanup function to close all reader. It should not be used if the reader will be opened again. The reason for implementing this to properly close USB devices so that they will startup the next time without error. */ void apdu_prepare_exit (void) { static int sentinel; int slot; if (!sentinel) { sentinel = 1; npth_mutex_lock (&reader_table_lock); for (slot = 0; slot < MAX_READER; slot++) if (reader_table[slot].used) { apdu_disconnect (slot); if (reader_table[slot].close_reader) reader_table[slot].close_reader (slot); xfree (reader_table[slot].rdrname); reader_table[slot].rdrname = NULL; reader_table[slot].used = 0; } npth_mutex_unlock (&reader_table_lock); sentinel = 0; } } /* Enumerate all readers and return information on whether this reader is in use. The caller should start with SLOT set to 0 and increment it with each call until an error is returned. */ int apdu_enum_reader (int slot, int *used) { if (slot < 0 || slot >= MAX_READER) return SW_HOST_NO_DRIVER; *used = reader_table[slot].used; return 0; } /* Connect a card. This is used to power up the card and make sure that an ATR is available. Depending on the reader backend it may return an error for an inactive card or if no card is available. Return -1 on error. Return 1 if reader requires get_status to watch card removal. Return 0 if it's a token (always with a card), or it supports INTERRUPT endpoint to watch card removal. */ int apdu_connect (int slot) { int sw = 0; unsigned int status = 0; if (DBG_READER) log_debug ("enter: apdu_connect: slot=%d\n", slot); if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) { if (DBG_READER) log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n"); return -1; } /* Only if the access method provides a connect function we use it. If not, we expect that the card has been implicitly connected by apdu_open_reader. */ if (reader_table[slot].connect_card) { sw = lock_slot (slot); if (!sw) { sw = reader_table[slot].connect_card (slot); unlock_slot (slot); } } /* We need to call apdu_get_status_internal, so that the last-status machinery gets setup properly even if a card is inserted while scdaemon is fired up and apdu_get_status has not yet been called. Without that we would force a reset of the card with the next call to apdu_get_status. */ if (!sw) sw = apdu_get_status_internal (slot, 1, &status, 1); if (sw) ; else if (!(status & APDU_CARD_PRESENT)) sw = SW_HOST_NO_CARD; else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE)) sw = SW_HOST_CARD_INACTIVE; if (sw == SW_HOST_CARD_INACTIVE) { /* Try power it up again. */ sw = apdu_reset (slot); } if (DBG_READER) log_debug ("leave: apdu_connect => sw=0x%x\n", sw); if (sw) return -1; return reader_table[slot].require_get_status; } int apdu_disconnect (int slot) { int sw; if (DBG_READER) log_debug ("enter: apdu_disconnect: slot=%d\n", slot); if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) { if (DBG_READER) log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n"); return SW_HOST_NO_DRIVER; } if (reader_table[slot].disconnect_card) { sw = lock_slot (slot); if (!sw) { sw = reader_table[slot].disconnect_card (slot); unlock_slot (slot); } } else sw = 0; if (DBG_READER) log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw); return sw; } /* Set the progress callback of SLOT to CB and its args to CB_ARG. If CB is NULL the progress callback is removed. */ int apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg) { int sw; if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (reader_table[slot].set_progress_cb) { sw = lock_slot (slot); if (!sw) { sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg); unlock_slot (slot); } } else sw = 0; return sw; } /* Do a reset for the card in reader at SLOT. */ int apdu_reset (int slot) { int sw; if (DBG_READER) log_debug ("enter: apdu_reset: slot=%d\n", slot); if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) { if (DBG_READER) log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n"); return SW_HOST_NO_DRIVER; } if ((sw = lock_slot (slot))) { if (DBG_READER) log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw); return sw; } if (reader_table[slot].reset_reader) sw = reader_table[slot].reset_reader (slot); unlock_slot (slot); if (DBG_READER) log_debug ("leave: apdu_reset => sw=0x%x\n", sw); return sw; } /* Return the ATR or NULL if none is available. On success the length of the ATR is stored at ATRLEN. The caller must free the returned value. */ unsigned char * apdu_get_atr (int slot, size_t *atrlen) { unsigned char *buf; if (DBG_READER) log_debug ("enter: apdu_get_atr: slot=%d\n", slot); if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) { if (DBG_READER) log_debug ("leave: apdu_get_atr => NULL (bad slot)\n"); return NULL; } if (!reader_table[slot].atrlen) { if (DBG_READER) log_debug ("leave: apdu_get_atr => NULL (no ATR)\n"); return NULL; } buf = xtrymalloc (reader_table[slot].atrlen); if (!buf) { if (DBG_READER) log_debug ("leave: apdu_get_atr => NULL (out of core)\n"); return NULL; } memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen); *atrlen = reader_table[slot].atrlen; if (DBG_READER) log_debug ("leave: apdu_get_atr => atrlen=%zu\n", *atrlen); return buf; } /* Retrieve the status for SLOT. The function does only wait for the card to become available if HANG is set to true. On success the bits in STATUS will be set to APDU_CARD_USABLE (bit 0) = card present and usable APDU_CARD_PRESENT (bit 1) = card present APDU_CARD_ACTIVE (bit 2) = card active (bit 3) = card access locked [not yet implemented] For most applications, testing bit 0 is sufficient. */ static int apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire) { int sw; unsigned int s = 0; if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if ((sw = hang? lock_slot (slot) : trylock_slot (slot))) return sw; if (reader_table[slot].get_status_reader) sw = reader_table[slot].get_status_reader (slot, &s, on_wire); unlock_slot (slot); if (sw) { if (on_wire) reader_table[slot].atrlen = 0; s = 0; } if (status) *status = s; return sw; } /* See above for a description. */ int apdu_get_status (int slot, int hang, unsigned int *status) { int sw; if (DBG_READER) log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang); sw = apdu_get_status_internal (slot, hang, status, 0); if (DBG_READER) { if (status) log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n", sw, *status); else log_debug ("leave: apdu_get_status => sw=0x%x\n", sw); } return sw; } /* Check whether the reader supports the ISO command code COMMAND on the pinpad. Return 0 on success. For a description of the pin parameters, see ccid-driver.c */ int apdu_check_pinpad (int slot, int command, pininfo_t *pininfo) { if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (opt.enable_pinpad_varlen) pininfo->fixedlen = 0; if (reader_table[slot].check_pinpad) { int sw; if ((sw = lock_slot (slot))) return sw; sw = reader_table[slot].check_pinpad (slot, command, pininfo); unlock_slot (slot); return sw; } else return SW_HOST_NOT_SUPPORTED; } int apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo) { if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (reader_table[slot].pinpad_verify) { int sw; if ((sw = lock_slot (slot))) return sw; sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1, pininfo); unlock_slot (slot); return sw; } else return SW_HOST_NOT_SUPPORTED; } int apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1, pininfo_t *pininfo) { if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (reader_table[slot].pinpad_modify) { int sw; if ((sw = lock_slot (slot))) return sw; sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1, pininfo); unlock_slot (slot); return sw; } else return SW_HOST_NOT_SUPPORTED; } /* Dispatcher for the actual send_apdu function. Note, that this function should be called in locked state. */ static int send_apdu (int slot, unsigned char *apdu, size_t apdulen, unsigned char *buffer, size_t *buflen, pininfo_t *pininfo) { if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (reader_table[slot].send_apdu_reader) return reader_table[slot].send_apdu_reader (slot, apdu, apdulen, buffer, buflen, pininfo); else return SW_HOST_NOT_SUPPORTED; } /* Core APDU tranceiver function. Parameters are described at apdu_send_le with the exception of PININFO which indicates pinpad related operations if not NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used according to these values: n < 0 := Use command chaining with the data part limited to -n in each chunk. If -1 is used a default value is used. n == 0 := No extended mode or command chaining. n == 1 := Use extended length for input and output without a length limit. n > 1 := Use extended length with up to N bytes. */ static int send_le (int slot, int class, int ins, int p0, int p1, int lc, const char *data, int le, unsigned char **retbuf, size_t *retbuflen, pininfo_t *pininfo, int extended_mode) { #define SHORT_RESULT_BUFFER_SIZE 258 /* We allocate 8 extra bytes as a safety margin towards a driver bug. */ unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; unsigned char *result_buffer = NULL; size_t result_buffer_size; unsigned char *result; size_t resultlen; unsigned char short_apdu_buffer[5+256+1]; unsigned char *apdu_buffer = NULL; size_t apdu_buffer_size; unsigned char *apdu; size_t apdulen; int sw; long rc; /* We need a long here due to PC/SC. */ int did_exact_length_hack = 0; int use_chaining = 0; int use_extended_length = 0; int lc_chunk; if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (DBG_CARD_IO) log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n", class, ins, p0, p1, lc, le, extended_mode); if (lc != -1 && (lc > 255 || lc < 0)) { /* Data does not fit into an APDU. What we do now depends on the EXTENDED_MODE parameter. */ if (!extended_mode) return SW_WRONG_LENGTH; /* No way to send such an APDU. */ else if (extended_mode > 0) use_extended_length = 1; else if (extended_mode < 0) { /* Send APDU using chaining mode. */ if (lc > 16384) return SW_WRONG_LENGTH; /* Sanity check. */ if ((class&0xf0) != 0) return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */ use_chaining = extended_mode == -1? 255 : -extended_mode; use_chaining &= 0xff; } else return SW_HOST_INV_VALUE; } else if (lc == -1 && extended_mode > 0) use_extended_length = 1; if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0)) { /* Expected Data does not fit into an APDU. What we do now depends on the EXTENDED_MODE parameter. Note that a check for command chaining does not make sense because we are looking at Le. */ if (!extended_mode) return SW_WRONG_LENGTH; /* No way to send such an APDU. */ else if (use_extended_length) ; /* We are already using extended length. */ else if (extended_mode > 0) use_extended_length = 1; else return SW_HOST_INV_VALUE; } if ((!data && lc != -1) || (data && lc == -1)) return SW_HOST_INV_VALUE; if (use_extended_length) { if (reader_table[slot].is_t0) return SW_HOST_NOT_SUPPORTED; /* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */ apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2; apdu_buffer = xtrymalloc (apdu_buffer_size + 10); if (!apdu_buffer) return SW_HOST_OUT_OF_CORE; apdu = apdu_buffer; } else { apdu_buffer_size = sizeof short_apdu_buffer; apdu = short_apdu_buffer; } if (use_extended_length && (le > 256 || le < 0)) { /* Two more bytes are needed for status bytes. */ result_buffer_size = le < 0? 4096 : (le + 2); result_buffer = xtrymalloc (result_buffer_size); if (!result_buffer) { xfree (apdu_buffer); return SW_HOST_OUT_OF_CORE; } result = result_buffer; } else { result_buffer_size = SHORT_RESULT_BUFFER_SIZE; result = short_result_buffer; } #undef SHORT_RESULT_BUFFER_SIZE if ((sw = lock_slot (slot))) { xfree (apdu_buffer); xfree (result_buffer); return sw; } do { if (use_extended_length) { use_chaining = 0; apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = ins; apdu[apdulen++] = p0; apdu[apdulen++] = p1; if (lc > 0) { apdu[apdulen++] = 0; /* Z byte: Extended length marker. */ apdu[apdulen++] = ((lc >> 8) & 0xff); apdu[apdulen++] = (lc & 0xff); memcpy (apdu+apdulen, data, lc); data += lc; apdulen += lc; } if (le != -1) { if (lc <= 0) apdu[apdulen++] = 0; /* Z byte: Extended length marker. */ apdu[apdulen++] = ((le >> 8) & 0xff); apdu[apdulen++] = (le & 0xff); } } else { apdulen = 0; apdu[apdulen] = class; if (use_chaining && lc > 255) { apdu[apdulen] |= 0x10; assert (use_chaining < 256); lc_chunk = use_chaining; lc -= use_chaining; } else { use_chaining = 0; lc_chunk = lc; } apdulen++; apdu[apdulen++] = ins; apdu[apdulen++] = p0; apdu[apdulen++] = p1; if (lc_chunk != -1) { apdu[apdulen++] = lc_chunk; memcpy (apdu+apdulen, data, lc_chunk); data += lc_chunk; apdulen += lc_chunk; /* T=0 does not allow the use of Lc together with Le; thus disable Le in this case. */ if (reader_table[slot].is_t0) le = -1; } if (le != -1 && !use_chaining) apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */ } exact_length_hack: /* As a safeguard don't pass any garbage to the driver. */ assert (apdulen <= apdu_buffer_size); memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo); if (rc || resultlen < 2) { log_info ("apdu_send_simple(%d) failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); xfree (apdu_buffer); xfree (result_buffer); return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; if (!use_extended_length && !did_exact_length_hack && SW_EXACT_LENGTH_P (sw)) { apdu[apdulen-1] = (sw & 0x00ff); did_exact_length_hack = 1; goto exact_length_hack; } } while (use_chaining && sw == SW_SUCCESS); if (apdu_buffer) { xfree (apdu_buffer); apdu_buffer = NULL; } /* Store away the returned data but strip the statusword. */ resultlen -= 2; if (DBG_CARD_IO) { log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) log_printhex (" dump: ", result, resultlen); } if (sw == SW_SUCCESS || sw == SW_EOF_REACHED) { if (retbuf) { *retbuf = xtrymalloc (resultlen? resultlen : 1); if (!*retbuf) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } *retbuflen = resultlen; memcpy (*retbuf, result, resultlen); } } else if ((sw & 0xff00) == SW_MORE_DATA) { unsigned char *p = NULL, *tmp; size_t bufsize = 4096; /* It is likely that we need to return much more data, so we start off with a large buffer. */ if (retbuf) { *retbuf = p = xtrymalloc (bufsize); if (!*retbuf) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } assert (resultlen < bufsize); memcpy (p, result, resultlen); p += resultlen; } do { int len = (sw & 0x00ff); if (DBG_CARD_IO) log_debug ("apdu_send_simple(%d): %d more bytes available\n", slot, len); apdu_buffer_size = sizeof short_apdu_buffer; apdu = short_apdu_buffer; apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = 0xC0; apdu[apdulen++] = 0; apdu[apdulen++] = 0; apdu[apdulen++] = len; assert (apdulen <= apdu_buffer_size); memset (apdu+apdulen, 0, apdu_buffer_size - apdulen); resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_simple(%d) for get response failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); xfree (result_buffer); return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; resultlen -= 2; if (DBG_CARD_IO) { log_debug (" more: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) log_printhex (" dump: ", result, resultlen); } if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS || sw == SW_EOF_REACHED ) { if (retbuf && resultlen) { if (p - *retbuf + resultlen > bufsize) { bufsize += resultlen > 4096? resultlen: 4096; tmp = xtryrealloc (*retbuf, bufsize); if (!tmp) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } p = tmp + (p - *retbuf); *retbuf = tmp; } memcpy (p, result, resultlen); p += resultlen; } } else log_info ("apdu_send_simple(%d) " "got unexpected status %04X from get response\n", slot, sw); } while ((sw & 0xff00) == SW_MORE_DATA); if (retbuf) { *retbuflen = p - *retbuf; tmp = xtryrealloc (*retbuf, *retbuflen); if (tmp) *retbuf = tmp; } } unlock_slot (slot); xfree (result_buffer); if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) log_printhex (" dump: ", *retbuf, *retbuflen); return sw; } /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 for LC won't sent this field and the data field; in this case DATA must also be passed as NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used; see send_le for details. The return value is the status word or -1 for an invalid SLOT or other non card related error. If RETBUF is not NULL, it will receive an allocated buffer with the returned data. The length of that data will be put into *RETBUFLEN. The caller is responsible for releasing the buffer even in case of errors. */ int apdu_send_le(int slot, int extended_mode, int class, int ins, int p0, int p1, int lc, const char *data, int le, unsigned char **retbuf, size_t *retbuflen) { return send_le (slot, class, ins, p0, p1, lc, data, le, retbuf, retbuflen, NULL, extended_mode); } /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for LC won't sent this field and the data field; in this case DATA must also be passed as NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used; see send_le for details. The return value is the status word or -1 for an invalid SLOT or other non card related error. If RETBUF is not NULL, it will receive an allocated buffer with the returned data. The length of that data will be put into *RETBUFLEN. The caller is responsible for releasing the buffer even in case of errors. */ int apdu_send (int slot, int extended_mode, int class, int ins, int p0, int p1, int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) { return send_le (slot, class, ins, p0, p1, lc, data, 256, retbuf, retbuflen, NULL, extended_mode); } /* Send an APDU to the card in SLOT. The APDU is created from all given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for LC won't sent this field and the data field; in this case DATA must also be passed as NULL. If EXTENDED_MODE is not 0 command chaining or extended length will be used; see send_le for details. The return value is the status word or -1 for an invalid SLOT or other non card related error. No data will be returned. */ int apdu_send_simple (int slot, int extended_mode, int class, int ins, int p0, int p1, int lc, const char *data) { return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL, extended_mode); } /* This is a more generic version of the apdu sending routine. It takes an already formatted APDU in APDUDATA or length APDUDATALEN and returns with an APDU including the status word. With HANDLE_MORE set to true this function will handle the MORE DATA status and return all APDUs concatenated with one status word at the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed with a max. result data length of EXTENDED_LENGTH bytes. The function does not return a regular status word but 0 on success. If the slot is locked, the function returns immediately with an error. */ int apdu_send_direct (int slot, size_t extended_length, const unsigned char *apdudata, size_t apdudatalen, int handle_more, unsigned char **retbuf, size_t *retbuflen) { #define SHORT_RESULT_BUFFER_SIZE 258 unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10]; unsigned char *result_buffer = NULL; size_t result_buffer_size; unsigned char *result; size_t resultlen; unsigned char short_apdu_buffer[5+256+10]; unsigned char *apdu_buffer = NULL; unsigned char *apdu; size_t apdulen; int sw; long rc; /* we need a long here due to PC/SC. */ int class; if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) return SW_HOST_NO_DRIVER; if (apdudatalen > 65535) return SW_HOST_INV_VALUE; if (apdudatalen > sizeof short_apdu_buffer - 5) { apdu_buffer = xtrymalloc (apdudatalen + 5); if (!apdu_buffer) return SW_HOST_OUT_OF_CORE; apdu = apdu_buffer; } else { apdu = short_apdu_buffer; } apdulen = apdudatalen; memcpy (apdu, apdudata, apdudatalen); class = apdulen? *apdu : 0; if (extended_length >= 256 && extended_length <= 65536) { result_buffer_size = extended_length; result_buffer = xtrymalloc (result_buffer_size + 10); if (!result_buffer) { xfree (apdu_buffer); return SW_HOST_OUT_OF_CORE; } result = result_buffer; } else { result_buffer_size = SHORT_RESULT_BUFFER_SIZE; result = short_result_buffer; } #undef SHORT_RESULT_BUFFER_SIZE if ((sw = trylock_slot (slot))) { xfree (apdu_buffer); xfree (result_buffer); return sw; } resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); xfree (apdu_buffer); apdu_buffer = NULL; if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); xfree (result_buffer); return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; /* Store away the returned data but strip the statusword. */ resultlen -= 2; if (DBG_CARD_IO) { log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) log_printhex (" dump: ", result, resultlen); } if (handle_more && (sw & 0xff00) == SW_MORE_DATA) { unsigned char *p = NULL, *tmp; size_t bufsize = 4096; /* It is likely that we need to return much more data, so we start off with a large buffer. */ if (retbuf) { *retbuf = p = xtrymalloc (bufsize + 2); if (!*retbuf) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } assert (resultlen < bufsize); memcpy (p, result, resultlen); p += resultlen; } do { int len = (sw & 0x00ff); if (DBG_CARD_IO) log_debug ("apdu_send_direct(%d): %d more bytes available\n", slot, len); apdu = short_apdu_buffer; apdulen = 0; apdu[apdulen++] = class; apdu[apdulen++] = 0xC0; apdu[apdulen++] = 0; apdu[apdulen++] = 0; apdu[apdulen++] = len; memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen); resultlen = result_buffer_size; rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL); if (rc || resultlen < 2) { log_error ("apdu_send_direct(%d) for get response failed: %s\n", slot, apdu_strerror (rc)); unlock_slot (slot); xfree (result_buffer); return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE; } sw = (result[resultlen-2] << 8) | result[resultlen-1]; resultlen -= 2; if (DBG_CARD_IO) { log_debug (" more: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen); if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) log_printhex (" dump: ", result, resultlen); } if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS || sw == SW_EOF_REACHED ) { if (retbuf && resultlen) { if (p - *retbuf + resultlen > bufsize) { bufsize += resultlen > 4096? resultlen: 4096; tmp = xtryrealloc (*retbuf, bufsize + 2); if (!tmp) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } p = tmp + (p - *retbuf); *retbuf = tmp; } memcpy (p, result, resultlen); p += resultlen; } } else log_info ("apdu_send_direct(%d) " "got unexpected status %04X from get response\n", slot, sw); } while ((sw & 0xff00) == SW_MORE_DATA); if (retbuf) { *retbuflen = p - *retbuf; tmp = xtryrealloc (*retbuf, *retbuflen + 2); if (tmp) *retbuf = tmp; } } else { if (retbuf) { *retbuf = xtrymalloc ((resultlen? resultlen : 1)+2); if (!*retbuf) { unlock_slot (slot); xfree (result_buffer); return SW_HOST_OUT_OF_CORE; } *retbuflen = resultlen; memcpy (*retbuf, result, resultlen); } } unlock_slot (slot); xfree (result_buffer); /* Append the status word. Note that we reserved the two extra bytes while allocating the buffer. */ if (retbuf) { (*retbuf)[(*retbuflen)++] = (sw >> 8); (*retbuf)[(*retbuflen)++] = sw; } if (DBG_CARD_IO && retbuf) log_printhex (" dump: ", *retbuf, *retbuflen); return 0; } const char * apdu_get_reader_name (int slot) { return reader_table[slot].rdrname; } gpg_error_t apdu_init (void) { #ifdef USE_NPTH gpg_error_t err; int i; if (npth_mutex_init (&reader_table_lock, NULL)) goto leave; for (i = 0; i < MAX_READER; i++) if (npth_mutex_init (&reader_table[i].lock, NULL)) goto leave; /* All done well. */ return 0; leave: err = gpg_error_from_syserror (); log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err)); return err; #endif /*USE_NPTH*/ return 0; } diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 66b235d81..25f3dbef5 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -1,5105 +1,5105 @@ /* app-openpgp.c - The OpenPGP card application. * Copyright (C) 2003, 2004, 2005, 2007, 2008, * 2009, 2013, 2014, 2015 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ /* Some notes: CHV means Card Holder Verification and is nothing else than a PIN or password. That term seems to have been used originally with GSM cards. Version v2 of the specs changes the term to the clearer term PW for password. We use the terms here interchangeable because we do not want to change existing strings i18n wise. Version 2 of the specs also drops the separate PW2 which was required in v1 due to ISO requirements. It is now possible to have one physical PW but two reference to it so that they can be individually be verified (e.g. to implement a forced verification for one key). Thus you will noticed the use of PW2 with the verify command but not with change_reference_data because the latter operates directly on the physical PW. The Reset Code (RC) as implemented by v2 cards uses the same error counter as the PW2 of v1 cards. By default no RC is set and thus that error counter is set to 0. After setting the RC the error counter will be initialized to 3. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <assert.h> #include <time.h> #if GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ #include "options.h" #include "errors.h" #include "memory.h" #include "cardglue.h" #else /* GNUPG_MAJOR_VERSION != 1 */ #include "scdaemon.h" #endif /* GNUPG_MAJOR_VERSION != 1 */ #include "../common/util.h" #include "../common/i18n.h" #include "iso7816.h" #include "app-common.h" #include "../common/tlv.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" /* A table describing the DOs of the card. */ static struct { int tag; int constructed; int get_from; /* Constructed DO with this DO or 0 for direct access. */ int binary:1; int dont_cache:1; int flush_on_error:1; int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of this data object if it is used in 1.1 and later versions of the card. This does not work with composite DO and is currently only useful for the CHV status bytes. */ int try_extlen:1; /* Large object; try to use an extended length APDU. */ char *desc; } data_objects[] = { { 0x005E, 0, 0, 1, 0, 0, 0, 0, "Login Data" }, { 0x5F50, 0, 0, 0, 0, 0, 0, 0, "URL" }, { 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" }, { 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"}, { 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" }, { 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" }, { 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Sex" }, { 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" }, { 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" }, { 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" }, { 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" }, { 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" }, { 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" }, { 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" }, { 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" }, { 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" }, { 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" }, { 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" }, { 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" }, { 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" }, { 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" }, { 0x0101, 0, 0, 0, 0, 0, 0, 0, "Private DO 1"}, { 0x0102, 0, 0, 0, 0, 0, 0, 0, "Private DO 2"}, { 0x0103, 0, 0, 0, 0, 0, 0, 0, "Private DO 3"}, { 0x0104, 0, 0, 0, 0, 0, 0, 0, "Private DO 4"}, { 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"}, /* V3.0 */ { 0x7F74, 0, 0, 1, 0, 0, 0, 0, "General Feature Management"}, { 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"}, { 0 } }; /* Type of keys. */ typedef enum { KEY_TYPE_ECC, KEY_TYPE_RSA, } key_type_t; /* The format of RSA private keys. */ typedef enum { RSA_UNKNOWN_FMT, RSA_STD, RSA_STD_N, RSA_CRT, RSA_CRT_N } rsa_key_format_t; /* One cache item for DOs. */ struct cache_s { struct cache_s *next; int tag; size_t length; unsigned char data[1]; }; /* Object with application (i.e. OpenPGP card) specific data. */ struct app_local_s { /* A linked list with cached DOs. */ struct cache_s *cache; /* Keep track of the public keys. */ struct { int read_done; /* True if we have at least tried to read them. */ unsigned char *key; /* This is a malloced buffer with a canonical encoded S-expression encoding a public key. Might be NULL if key is not available. */ size_t keylen; /* The length of the above S-expression. This is usually only required for cross checks because the length of an S-expression is implicitly available. */ } pk[3]; unsigned char status_indicator; /* The card status indicator. */ unsigned int manufacturer:16; /* Manufacturer ID from the s/n. */ /* Keep track of the ISO card capabilities. */ struct { unsigned int cmd_chaining:1; /* Command chaining is supported. */ unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */ } cardcap; /* Keep track of extended card capabilities. */ struct { unsigned int is_v2:1; /* This is a v2.0 compatible card. */ unsigned int sm_supported:1; /* Secure Messaging is supported. */ unsigned int get_challenge:1; unsigned int key_import:1; unsigned int change_force_chv:1; unsigned int private_dos:1; unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */ unsigned int has_decrypt:1; /* Support symmetric decryption. */ unsigned int has_button:1; unsigned int sm_algo:2; /* Symmetric crypto algo for SM. */ unsigned int max_certlen_3:16; unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */ } extcap; /* Flags used to control the application. */ struct { unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */ unsigned int def_chv2:1; /* Use 123456 for CHV2. */ } flags; /* Pinpad request specified on card. */ struct { unsigned int specified:1; int fixedlen_user; int fixedlen_admin; } pinpad; struct { key_type_t key_type; union { struct { unsigned int n_bits; /* Size of the modulus in bits. The rest of this strucuire is only valid if this is not 0. */ unsigned int e_bits; /* Size of the public exponent in bits. */ rsa_key_format_t format; } rsa; struct { const char *curve; int flags; } ecc; }; } keyattr[3]; }; #define ECC_FLAG_DJB_TWEAK (1 << 0) #define ECC_FLAG_PUBKEY (1 << 1) /***** Local prototypes *****/ static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen); static unsigned long get_sig_counter (app_t app); static gpg_error_t do_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen); static void parse_algorithm_attribute (app_t app, int keyno); static gpg_error_t change_keyattr_from_string (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *value, size_t valuelen); /* Deconstructor. */ static void do_deinit (app_t app) { if (app && app->app_local) { struct cache_s *c, *c2; int i; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } for (i=0; i < DIM (app->app_local->pk); i++) { xfree (app->app_local->pk[i].key); app->app_local->pk[i].read_done = 0; } xfree (app->app_local); app->app_local = NULL; } } /* Wrapper around iso7816_get_data which first tries to get the data from the cache. With GET_IMMEDIATE passed as true, the cache is bypassed. With TRY_EXTLEN extended lengths APDUs are use if supported by the card. */ static gpg_error_t get_cached_data (app_t app, int tag, unsigned char **result, size_t *resultlen, int get_immediate, int try_extlen) { gpg_error_t err; int i; unsigned char *p; size_t len; struct cache_s *c; int exmode; *result = NULL; *resultlen = 0; if (!get_immediate) { for (c=app->app_local->cache; c; c = c->next) if (c->tag == tag) { if(c->length) { p = xtrymalloc (c->length); if (!p) return gpg_error (gpg_err_code_from_errno (errno)); memcpy (p, c->data, c->length); *result = p; } *resultlen = c->length; return 0; } } if (try_extlen && app->app_local->cardcap.ext_lc_le) exmode = app->app_local->extcap.max_certlen_3; else exmode = 0; err = iso7816_get_data (app->slot, exmode, tag, &p, &len); if (err) return err; *result = p; *resultlen = len; /* Check whether we should cache this object. */ if (get_immediate) return 0; for (i=0; data_objects[i].tag; i++) if (data_objects[i].tag == tag) { if (data_objects[i].dont_cache) return 0; break; } /* Okay, cache it. */ for (c=app->app_local->cache; c; c = c->next) assert (c->tag != tag); c = xtrymalloc (sizeof *c + len); if (c) { memcpy (c->data, p, len); c->length = len; c->tag = tag; c->next = app->app_local->cache; app->app_local->cache = c; } return 0; } /* Remove DO at TAG from the cache. */ static void flush_cache_item (app_t app, int tag) { struct cache_s *c, *cprev; int i; if (!app->app_local) return; for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next) if (c->tag == tag) { if (cprev) cprev->next = c->next; else app->app_local->cache = c->next; xfree (c); for (c=app->app_local->cache; c ; c = c->next) { assert (c->tag != tag); /* Oops: duplicated entry. */ } return; } /* Try again if we have an outer tag. */ for (i=0; data_objects[i].tag; i++) if (data_objects[i].tag == tag && data_objects[i].get_from && data_objects[i].get_from != tag) flush_cache_item (app, data_objects[i].get_from); } /* Flush all entries from the cache which might be out of sync after an error. */ static void flush_cache_after_error (app_t app) { int i; for (i=0; data_objects[i].tag; i++) if (data_objects[i].flush_on_error) flush_cache_item (app, data_objects[i].tag); } /* Flush the entire cache. */ static void flush_cache (app_t app) { if (app && app->app_local) { struct cache_s *c, *c2; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } app->app_local->cache = NULL; } } /* Get the DO identified by TAG from the card in SLOT and return a buffer with its content in RESULT and NBYTES. The return value is NULL if not found or a pointer which must be used to release the buffer holding value. */ static void * get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, int *r_rc) { int rc, i; unsigned char *buffer; size_t buflen; unsigned char *value; size_t valuelen; int dummyrc; int exmode; if (!r_rc) r_rc = &dummyrc; *result = NULL; *nbytes = 0; *r_rc = 0; for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; if (app->card_version > 0x0100 && data_objects[i].get_immediate_in_v11) { exmode = 0; rc = iso7816_get_data (app->slot, exmode, tag, &buffer, &buflen); if (rc) { *r_rc = rc; return NULL; } *result = buffer; *nbytes = buflen; return buffer; } value = NULL; rc = -1; if (data_objects[i].tag && data_objects[i].get_from) { rc = get_cached_data (app, data_objects[i].get_from, &buffer, &buflen, (data_objects[i].dont_cache || data_objects[i].get_immediate_in_v11), data_objects[i].try_extlen); if (!rc) { const unsigned char *s; s = find_tlv_unchecked (buffer, buflen, tag, &valuelen); if (!s) value = NULL; /* not found */ else if (valuelen > buflen - (s - buffer)) { log_error ("warning: constructed DO too short\n"); value = NULL; xfree (buffer); buffer = NULL; } else value = buffer + (s - buffer); } } if (!value) /* Not in a constructed DO, try simple. */ { rc = get_cached_data (app, tag, &buffer, &buflen, (data_objects[i].dont_cache || data_objects[i].get_immediate_in_v11), data_objects[i].try_extlen); if (!rc) { value = buffer; valuelen = buflen; } } if (!rc) { *nbytes = valuelen; *result = value; return buffer; } *r_rc = rc; return NULL; } static void dump_all_do (int slot) { int rc, i, j; unsigned char *buffer; size_t buflen; for (i=0; data_objects[i].tag; i++) { if (data_objects[i].get_from) continue; /* We don't try extended length APDU because such large DO would be pretty useless in a log file. */ rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen); if (gpg_err_code (rc) == GPG_ERR_NO_OBJ) ; else if (rc) log_info ("DO '%s' not available: %s\n", data_objects[i].desc, gpg_strerror (rc)); else { if (data_objects[i].binary) { log_info ("DO '%s': ", data_objects[i].desc); log_printhex ("", buffer, buflen); } else log_info ("DO '%s': '%.*s'\n", data_objects[i].desc, (int)buflen, buffer); /* FIXME: sanitize */ if (data_objects[i].constructed) { for (j=0; data_objects[j].tag; j++) { const unsigned char *value; size_t valuelen; if (j==i || data_objects[i].tag != data_objects[j].get_from) continue; value = find_tlv_unchecked (buffer, buflen, data_objects[j].tag, &valuelen); if (!value) ; /* not found */ else if (valuelen > buflen - (value - buffer)) log_error ("warning: constructed DO too short\n"); else { if (data_objects[j].binary) { log_info ("DO '%s': ", data_objects[j].desc); if (valuelen > 200) log_info ("[%u]\n", (unsigned int)valuelen); else log_printhex ("", value, valuelen); } else log_info ("DO '%s': '%.*s'\n", data_objects[j].desc, (int)valuelen, value); /* FIXME: sanitize */ } } } } xfree (buffer); buffer = NULL; } } /* Count the number of bits, assuming the A represents an unsigned big integer of length LEN bytes. */ static unsigned int count_bits (const unsigned char *a, size_t len) { unsigned int n = len * 8; int i; for (; len && !*a; len--, a++, n -=8) ; if (len) { for (i=7; i && !(*a & (1<<i)); i--) n--; } return n; } /* GnuPG makes special use of the login-data DO, this function parses the login data to store the flags for later use. It may be called at any time and should be called after changing the login-data DO. Everything up to a LF is considered a mailbox or account name. If the first LF is followed by DC4 (0x14) control sequence are expected up to the next LF. Control sequences are separated by FS (0x18) and consist of key=value pairs. There are two keys defined: F=<flags> Where FLAGS is a plain hexadecimal number representing flag values. The lsb is here the rightmost bit. Defined flags bits are: Bit 0 = CHV1 and CHV2 are not syncronized Bit 1 = CHV2 has been set to the default PIN of "123456" (this implies that bit 0 is also set). P=<pinpad-request> Where PINPAD_REQUEST is in the format of: <n> or <n>,<m>. N for user PIN, M for admin PIN. If M is missing it means M=N. 0 means to force not to use pinpad. */ static void parse_login_data (app_t app) { unsigned char *buffer, *p; size_t buflen, len; void *relptr; /* Set defaults. */ app->app_local->flags.no_sync = 0; app->app_local->flags.def_chv2 = 0; app->app_local->pinpad.specified = 0; app->app_local->pinpad.fixedlen_user = -1; app->app_local->pinpad.fixedlen_admin = -1; /* Read the DO. */ relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL); if (!relptr) return; /* Ooops. */ for (; buflen; buflen--, buffer++) if (*buffer == '\n') break; if (buflen < 2 || buffer[1] != '\x14') { xfree (relptr); return; /* No control sequences. */ } buflen--; buffer++; do { buflen--; buffer++; if (buflen > 1 && *buffer == 'F' && buffer[1] == '=') { /* Flags control sequence found. */ int lastdig = 0; /* For now we are only interested in the last digit, so skip any leading digits but bail out on invalid characters. */ for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--) lastdig = xtoi_1 (p); buffer = p; buflen = len; if (len && !(*p == '\n' || *p == '\x18')) goto next; /* Invalid characters in field. */ app->app_local->flags.no_sync = !!(lastdig & 1); app->app_local->flags.def_chv2 = (lastdig & 3) == 3; } else if (buflen > 1 && *buffer == 'P' && buffer[1] == '=') { /* Pinpad request control sequence found. */ buffer += 2; buflen -= 2; if (buflen) { if (digitp (buffer)) { char *q; int n, m; n = strtol (buffer, &q, 10); if (q >= (char *)buffer + buflen || *q == '\x18' || *q == '\n') m = n; else { if (*q++ != ',' || !digitp (q)) goto next; m = strtol (q, &q, 10); } if (buflen < ((unsigned char *)q - buffer)) break; buflen -= ((unsigned char *)q - buffer); buffer = q; if (buflen && !(*buffer == '\n' || *buffer == '\x18')) goto next; app->app_local->pinpad.specified = 1; app->app_local->pinpad.fixedlen_user = n; app->app_local->pinpad.fixedlen_admin = m; } } } next: /* Skip to FS (0x18) or LF (\n). */ for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--) buffer++; } while (buflen && *buffer != '\n'); xfree (relptr); } #define MAX_ARGS_STORE_FPR 3 /* Note, that FPR must be at least 20 bytes. */ static gpg_error_t store_fpr (app_t app, int keynumber, u32 timestamp, unsigned char *fpr, int algo, ...) { unsigned int n, nbits; unsigned char *buffer, *p; int tag, tag2; int rc; const unsigned char *m[MAX_ARGS_STORE_FPR]; size_t mlen[MAX_ARGS_STORE_FPR]; va_list ap; int argc; int i; n = 6; /* key packet version, 4-byte timestamps, and algorithm */ if (algo == PUBKEY_ALGO_ECDH) argc = 3; else argc = 2; va_start (ap, algo); for (i = 0; i < argc; i++) { m[i] = va_arg (ap, const unsigned char *); mlen[i] = va_arg (ap, size_t); if (algo == PUBKEY_ALGO_RSA || i == 1) n += 2; n += mlen[i]; } va_end (ap); p = buffer = xtrymalloc (3 + n); if (!buffer) return gpg_error_from_syserror (); *p++ = 0x99; /* ctb */ *p++ = n >> 8; /* 2 byte length header */ *p++ = n; *p++ = 4; /* key packet version */ *p++ = timestamp >> 24; *p++ = timestamp >> 16; *p++ = timestamp >> 8; *p++ = timestamp; *p++ = algo; for (i = 0; i < argc; i++) { if (algo == PUBKEY_ALGO_RSA || i == 1) { nbits = count_bits (m[i], mlen[i]); *p++ = nbits >> 8; *p++ = nbits; } memcpy (p, m[i], mlen[i]); p += mlen[i]; } gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); xfree (buffer); tag = (app->card_version > 0x0007? 0xC7 : 0xC6) + keynumber; flush_cache_item (app, 0xC5); tag2 = 0xCE + keynumber; flush_cache_item (app, 0xCD); rc = iso7816_put_data (app->slot, 0, tag, fpr, 20); if (rc) log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc)); if (!rc && app->card_version > 0x0100) { unsigned char buf[4]; buf[0] = timestamp >> 24; buf[1] = timestamp >> 16; buf[2] = timestamp >> 8; buf[3] = timestamp; rc = iso7816_put_data (app->slot, 0, tag2, buf, 4); if (rc) log_error (_("failed to store the creation date: %s\n"), gpg_strerror (rc)); } return rc; } static void send_fpr_if_not_null (ctrl_t ctrl, const char *keyword, int number, const unsigned char *fpr) { int i; char buf[41]; char numbuf[25]; for (i=0; i < 20 && !fpr[i]; i++) ; if (i==20) return; /* All zero. */ bin2hex (fpr, 20, buf); if (number == -1) *numbuf = 0; /* Don't print the key number */ else sprintf (numbuf, "%d", number); send_status_info (ctrl, keyword, numbuf, (size_t)strlen(numbuf), buf, (size_t)strlen (buf), NULL, 0); } static void send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword, int number, const unsigned char *stamp) { char numbuf1[50], numbuf2[50]; unsigned long value; value = buf32_to_ulong (stamp); if (!value) return; sprintf (numbuf1, "%d", number); sprintf (numbuf2, "%lu", value); send_status_info (ctrl, keyword, numbuf1, (size_t)strlen(numbuf1), numbuf2, (size_t)strlen(numbuf2), NULL, 0); } static void send_key_data (ctrl_t ctrl, const char *name, const unsigned char *a, size_t alen) { char *buffer, *buf; size_t buflen; buffer = buf = bin2hex (a, alen, NULL); if (!buffer) { log_error ("memory allocation error in send_key_data\n"); return; } buflen = strlen (buffer); /* 768 is the hexified size for the modulus of an 3072 bit key. We use extra chunks to transmit larger data (i.e for 4096 bit). */ for ( ;buflen > 768; buflen -= 768, buf += 768) send_status_info (ctrl, "KEY-DATA", "-", 1, buf, 768, NULL, 0); send_status_info (ctrl, "KEY-DATA", name, (size_t)strlen(name), buf, buflen, NULL, 0); xfree (buffer); } static void send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno) { char buffer[200]; assert (keyno >=0 && keyno < DIM(app->app_local->keyattr)); if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d", keyno+1, app->app_local->keyattr[keyno].rsa.n_bits, app->app_local->keyattr[keyno].rsa.e_bits, app->app_local->keyattr[keyno].rsa.format); else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC) { snprintf (buffer, sizeof buffer, "%d %d %s", keyno+1, keyno==1? PUBKEY_ALGO_ECDH : (app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)? PUBKEY_ALGO_EDDSA : PUBKEY_ALGO_ECDSA, app->app_local->keyattr[keyno].ecc.curve); } else snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1); send_status_direct (ctrl, keyword, buffer); } #define RSA_SMALL_SIZE_KEY 1952 #define RSA_SMALL_SIZE_OP 2048 static int determine_rsa_response (app_t app, int keyno) { int size; size = 2 + 3 /* header */ + 4 /* tag+len */ + app->app_local->keyattr[keyno].rsa.n_bits/8 + 2 /* tag+len */ + app->app_local->keyattr[keyno].rsa.e_bits/8; return size; } /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { static struct { const char *name; int tag; int special; } table[] = { { "DISP-NAME", 0x005B }, { "LOGIN-DATA", 0x005E }, { "DISP-LANG", 0x5F2D }, { "DISP-SEX", 0x5F35 }, { "PUBKEY-URL", 0x5F50 }, { "KEY-FPR", 0x00C5, 3 }, { "KEY-TIME", 0x00CD, 4 }, { "KEY-ATTR", 0x0000, -5 }, { "CA-FPR", 0x00C6, 3 }, { "CHV-STATUS", 0x00C4, 1 }, { "SIG-COUNTER", 0x0093, 2 }, { "SERIALNO", 0x004F, -1 }, { "AID", 0x004F }, { "EXTCAP", 0x0000, -2 }, { "PRIVATE-DO-1", 0x0101 }, { "PRIVATE-DO-2", 0x0102 }, { "PRIVATE-DO-3", 0x0103 }, { "PRIVATE-DO-4", 0x0104 }, { "$AUTHKEYID", 0x0000, -3 }, { "$DISPSERIALNO",0x0000, -4 }, { NULL, 0 } }; int idx, i, rc; void *relptr; unsigned char *value; size_t valuelen; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); if (table[idx].special == -1) { /* The serial number is very special. We could have used the AID DO to retrieve it. The AID DO is available anyway but not hex formatted. */ char *serial = app_get_serialno (app); if (serial) { send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); } return 0; } if (table[idx].special == -2) { char tmp[110]; snprintf (tmp, sizeof tmp, "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d " "sm=%d si=%u dec=%d bt=%d", app->app_local->extcap.get_challenge, app->app_local->extcap.key_import, app->app_local->extcap.change_force_chv, app->app_local->extcap.private_dos, app->app_local->extcap.max_certlen_3, app->app_local->extcap.algo_attr_change, (app->app_local->extcap.sm_supported ? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES : (app->app_local->extcap.sm_algo == 1? CIPHER_ALGO_AES : CIPHER_ALGO_AES256)) : 0), app->app_local->status_indicator, app->app_local->extcap.has_decrypt, app->app_local->extcap.has_button); send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } if (table[idx].special == -3) { char const tmp[] = "OPENPGP.3"; send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); return 0; } if (table[idx].special == -4) { char *serial = app_get_serialno (app); if (serial) { if (strlen (serial) > 16+12) { send_status_info (ctrl, table[idx].name, serial+16, 12, NULL, 0); xfree (serial); return 0; } xfree (serial); } return gpg_error (GPG_ERR_INV_NAME); } if (table[idx].special == -5) { for (i=0; i < 3; i++) send_key_attr (ctrl, app, table[idx].name, i); return 0; } relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc); if (relptr) { if (table[idx].special == 1) { char numbuf[7*23]; for (i=0,*numbuf=0; i < valuelen && i < 7; i++) sprintf (numbuf+strlen (numbuf), " %d", value[i]); send_status_info (ctrl, table[idx].name, numbuf, strlen (numbuf), NULL, 0); } else if (table[idx].special == 2) { char numbuf[50]; sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen)); send_status_info (ctrl, table[idx].name, numbuf, strlen (numbuf), NULL, 0); } else if (table[idx].special == 3) { if (valuelen >= 60) for (i=0; i < 3; i++) send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20); } else if (table[idx].special == 4) { if (valuelen >= 12) for (i=0; i < 3; i++) send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4); } else send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); xfree (relptr); } return rc; } /* Return the DISP-NAME without any padding characters. Caller must * free the result. If not found or empty NULL is returned. */ static char * get_disp_name (app_t app) { int rc; void *relptr; unsigned char *value; size_t valuelen; char *string; char *p, *given; char *result; relptr = get_one_do (app, 0x005B, &value, &valuelen, &rc); if (!relptr) return NULL; string = xtrymalloc (valuelen + 1); if (!string) { xfree (relptr); return NULL; } memcpy (string, value, valuelen); string[valuelen] = 0; xfree (relptr); /* Swap surname and given name. */ given = strstr (string, "<<"); for (p = string; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; result = strconcat (given, " ", string, NULL); } else { result = string; string = NULL; } xfree (string); return result; } /* Return the pretty formatted serialnumber. On error NULL is * returned. */ static char * get_disp_serialno (app_t app) { char *serial = app_get_serialno (app); /* For our OpenPGP cards we do not want to show the entire serial * number but a nicely reformatted actual serial number. */ if (serial && strlen (serial) > 16+12) { memmove (serial, serial+16, 4); serial[4] = ' '; /* memmove (serial+5, serial+20, 4); */ /* serial[9] = ' '; */ /* memmove (serial+10, serial+24, 4); */ /* serial[14] = 0; */ memmove (serial+5, serial+20, 8); serial[13] = 0; } return serial; } /* Return the number of remaining tries for the standard or the admin * pw. Returns -1 on card error. */ static int get_remaining_tries (app_t app, int adminpw) { void *relptr; unsigned char *value; size_t valuelen; int remaining; relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); if (!relptr || valuelen < 7) { log_error (_("error retrieving CHV status from card\n")); xfree (relptr); return -1; } remaining = value[adminpw? 6 : 4]; xfree (relptr); return remaining; } /* Retrieve the fingerprint from the card inserted in SLOT and write the according hex representation to FPR. Caller must have provide a buffer at FPR of least 41 bytes. Returns 0 on success or an error code. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t retrieve_fpr_from_card (app_t app, int keyno, char *fpr) { gpg_error_t err = 0; void *relptr; unsigned char *value; size_t valuelen; assert (keyno >=0 && keyno <= 2); relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL); if (relptr && valuelen >= 60) bin2hex (value+keyno*20, 20, fpr); else err = gpg_error (GPG_ERR_NOT_FOUND); xfree (relptr); return err; } #endif /*GNUPG_MAJOR_VERSION > 1*/ /* Retrieve the public key material for the RSA key, whose fingerprint is FPR, from gpg output, which can be read through the stream FP. The RSA modulus will be stored at the address of M and MLEN, the public exponent at E and ELEN. Returns zero on success, an error code on failure. Caller must release the allocated buffers at M and E if the function returns success. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t retrieve_key_material (FILE *fp, const char *hexkeyid, const unsigned char **m, size_t *mlen, const unsigned char **e, size_t *elen) { gcry_error_t err = 0; char *line = NULL; /* read_line() buffer. */ size_t line_size = 0; /* Helper for for read_line. */ int found_key = 0; /* Helper to find a matching key. */ unsigned char *m_new = NULL; unsigned char *e_new = NULL; size_t m_new_n = 0; size_t e_new_n = 0; /* Loop over all records until we have found the subkey corresponding to the fingerprint. Inm general the first record should be the pub record, but we don't rely on that. Given that we only need to look at one key, it is sufficient to compare the keyid so that we don't need to look at "fpr" records. */ for (;;) { char *p; char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL }; int nfields; size_t max_length; gcry_mpi_t mpi; int i; max_length = 4096; i = read_line (fp, &line, &line_size, &max_length); if (!i) break; /* EOF. */ if (i < 0) { err = gpg_error_from_syserror (); goto leave; /* Error. */ } if (!max_length) { err = gpg_error (GPG_ERR_TRUNCATED); goto leave; /* Line truncated - we better stop processing. */ } /* Parse the line into fields. */ for (nfields=0, p=line; p && nfields < DIM (fields); nfields++) { fields[nfields] = p; p = strchr (p, ':'); if (p) *(p++) = 0; } if (!nfields) continue; /* No fields at all - skip line. */ if (!found_key) { if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) && nfields > 4 && !strcmp (fields[4], hexkeyid)) found_key = 1; continue; } if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") ) break; /* Next key - stop. */ if ( strcmp (fields[0], "pkd") ) continue; /* Not a key data record. */ if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1 || (!i && m_new) || (i && e_new)) { err = gpg_error (GPG_ERR_GENERAL); goto leave; /* Error: Invalid key data record or not an RSA key. */ } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL); if (err) mpi = NULL; else if (!i) err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi); else err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi); gcry_mpi_release (mpi); if (err) goto leave; } if (m_new && e_new) { *m = m_new; *mlen = m_new_n; m_new = NULL; *e = e_new; *elen = e_new_n; e_new = NULL; } else err = gpg_error (GPG_ERR_GENERAL); leave: xfree (m_new); xfree (e_new); xfree (line); return err; } #endif /*GNUPG_MAJOR_VERSION > 1*/ static gpg_error_t rsa_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno, const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp) { gpg_error_t err; const unsigned char *m, *e; size_t mlen, elen; unsigned char *mbuf = NULL, *ebuf = NULL; m = find_tlv (data, datalen, 0x0081, &mlen); if (!m) { log_error (_("response does not contain the RSA modulus\n")); return gpg_error (GPG_ERR_CARD); } e = find_tlv (data, datalen, 0x0082, &elen); if (!e) { log_error (_("response does not contain the RSA public exponent\n")); return gpg_error (GPG_ERR_CARD); } if (ctrl) { send_key_data (ctrl, "n", m, mlen); send_key_data (ctrl, "e", e, elen); } for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ ; for (; elen && !*e; elen--, e++) /* strip leading zeroes */ ; if (ctrl) { unsigned char fprbuf[20]; err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA, m, mlen, e, elen); if (err) return err; send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); } mbuf = xtrymalloc (mlen + 1); if (!mbuf) { err = gpg_error_from_syserror (); goto leave; } /* Prepend numbers with a 0 if needed. */ if (mlen && (*m & 0x80)) { *mbuf = 0; memcpy (mbuf+1, m, mlen); mlen++; } else memcpy (mbuf, m, mlen); ebuf = xtrymalloc (elen + 1); if (!ebuf) { err = gpg_error_from_syserror (); goto leave; } /* Prepend numbers with a 0 if needed. */ if (elen && (*e & 0x80)) { *ebuf = 0; memcpy (ebuf+1, e, elen); elen++; } else memcpy (ebuf, e, elen); err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))", (int)mlen, mbuf, (int)elen, ebuf); leave: xfree (mbuf); xfree (ebuf); return err; } /* Determine KDF hash algorithm and KEK encryption algorithm by CURVE. */ static const unsigned char* ecdh_params (const char *curve) { unsigned int nbits; openpgp_curve_to_oid (curve, &nbits); /* See RFC-6637 for those constants. 0x03: Number of bytes 0x01: Version for this parameter format KDF algo KEK algo */ if (nbits <= 256) return (const unsigned char*)"\x03\x01\x08\x07"; else if (nbits <= 384) return (const unsigned char*)"\x03\x01\x09\x08"; else return (const unsigned char*)"\x03\x01\x0a\x09"; } static gpg_error_t ecc_read_pubkey (app_t app, ctrl_t ctrl, u32 created_at, int keyno, const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp) { gpg_error_t err; unsigned char *qbuf = NULL; const unsigned char *ecc_q; size_t ecc_q_len; gcry_mpi_t oid = NULL; int n; const char *curve; const char *oidstr; const unsigned char *oidbuf; size_t oid_len; int algo; const char *format; ecc_q = find_tlv (data, datalen, 0x0086, &ecc_q_len); if (!ecc_q) { log_error (_("response does not contain the EC public key\n")); return gpg_error (GPG_ERR_CARD); } curve = app->app_local->keyattr[keyno].ecc.curve; oidstr = openpgp_curve_to_oid (curve, NULL); err = openpgp_oid_from_str (oidstr, &oid); if (err) return err; oidbuf = gcry_mpi_get_opaque (oid, &n); if (!oidbuf) { err = gpg_error_from_syserror (); goto leave; } oid_len = (n+7)/8; qbuf = xtrymalloc (ecc_q_len + 1); if (!qbuf) { err = gpg_error_from_syserror (); goto leave; } if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) { /* Prepend 0x40 prefix. */ *qbuf = 0x40; memcpy (qbuf+1, ecc_q, ecc_q_len); ecc_q_len++; } else memcpy (qbuf, ecc_q, ecc_q_len); if (ctrl) { send_key_data (ctrl, "q", qbuf, ecc_q_len); send_key_data (ctrl, "curve", oidbuf, oid_len); } if (keyno == 1) { if (ctrl) send_key_data (ctrl, "kdf/kek", ecdh_params (curve), (size_t)4); algo = PUBKEY_ALGO_ECDH; } else { if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) algo = PUBKEY_ALGO_EDDSA; else algo = PUBKEY_ALGO_ECDSA; } if (ctrl) { unsigned char fprbuf[20]; err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len, qbuf, ecc_q_len, ecdh_params (curve), (size_t)4); if (err) goto leave; send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); } if (!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)) format = "(public-key(ecc(curve%s)(q%b)))"; else if (keyno == 1) format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))"; else format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))"; err = gcry_sexp_build (r_sexp, NULL, format, app->app_local->keyattr[keyno].ecc.curve, (int)ecc_q_len, qbuf); leave: gcry_mpi_release (oid); xfree (qbuf); return err; } /* Parse tag-length-value data for public key in BUFFER of BUFLEN length. Key of KEYNO in APP is updated with an S-expression of public key. When CTRL is not NULL, fingerprint is computed with CREATED_AT, and fingerprint is written to the card, and key data and fingerprint are send back to the client side. */ static gpg_error_t read_public_key (app_t app, ctrl_t ctrl, u32 created_at, int keyno, const unsigned char *buffer, size_t buflen) { gpg_error_t err; const unsigned char *data; size_t datalen; gcry_sexp_t s_pkey = NULL; data = find_tlv (buffer, buflen, 0x7F49, &datalen); if (!data) { log_error (_("response does not contain the public key data\n")); return gpg_error (GPG_ERR_CARD); } if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) err = rsa_read_pubkey (app, ctrl, created_at, keyno, data, datalen, &s_pkey); else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC) err = ecc_read_pubkey (app, ctrl, created_at, keyno, data, datalen, &s_pkey); else err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); if (!err) { unsigned char *keybuf; size_t len; len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); keybuf = xtrymalloc (len); if (!data) { err = gpg_error_from_syserror (); gcry_sexp_release (s_pkey); return err; } gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len); gcry_sexp_release (s_pkey); app->app_local->pk[keyno].key = keybuf; /* Decrement for trailing '\0' */ app->app_local->pk[keyno].keylen = len - 1; } return err; } -/* Get the public key for KEYNO and store it as an S-expresion with +/* Get the public key for KEYNO and store it as an S-expression with the APP handle. On error that field gets cleared. If we already know about the public key we will just return. Note that this does not mean a key is available; this is solely indicated by the presence of the app->app_local->pk[KEYNO].key field. Note that GnuPG 1.x does not need this and it would be too time consuming to send it just for the fun of it. However, given that we - use the same code in gpg 1.4, we can't use the gcry S-expresion + use the same code in gpg 1.4, we can't use the gcry S-expression here but need to open encode it. */ #if GNUPG_MAJOR_VERSION > 1 static gpg_error_t get_public_key (app_t app, int keyno) { gpg_error_t err = 0; unsigned char *buffer; const unsigned char *m, *e; size_t buflen; size_t mlen = 0; size_t elen = 0; char *keybuf = NULL; gcry_sexp_t s_pkey; size_t len; if (keyno < 0 || keyno > 2) return gpg_error (GPG_ERR_INV_ID); /* Already cached? */ if (app->app_local->pk[keyno].read_done) return 0; xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; m = e = NULL; /* (avoid cc warning) */ if (app->card_version > 0x0100) { int exmode, le_value; /* We may simply read the public key out of these cards. */ if (app->app_local->cardcap.ext_lc_le && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA && app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY) { exmode = 1; /* Use extended length. */ le_value = determine_rsa_response (app, keyno); } else { exmode = 0; le_value = 256; /* Use legacy value. */ } err = iso7816_read_public_key (app->slot, exmode, (keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, le_value, &buffer, &buflen); if (err) { log_error (_("reading public key failed: %s\n"), gpg_strerror (err)); goto leave; } err = read_public_key (app, NULL, 0U, keyno, buffer, buflen); } else { /* Due to a design problem in v1.0 cards we can't get the public key out of these cards without doing a verify on CHV3. Clearly that is not an option and thus we try to locate the key using an external helper. The helper we use here is gpg itself, which should know about the key in any case. */ char fpr[41]; char *hexkeyid; char *command = NULL; FILE *fp; int ret; buffer = NULL; /* We don't need buffer. */ err = retrieve_fpr_from_card (app, keyno, fpr); if (err) { log_error ("error while retrieving fpr from card: %s\n", gpg_strerror (err)); goto leave; } hexkeyid = fpr + 24; ret = gpgrt_asprintf (&command, "gpg --list-keys --with-colons --with-key-data '%s'", fpr); if (ret < 0) { err = gpg_error_from_syserror (); goto leave; } fp = popen (command, "r"); xfree (command); if (!fp) { err = gpg_error_from_syserror (); log_error ("running gpg failed: %s\n", gpg_strerror (err)); goto leave; } err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen); pclose (fp); if (err) { log_error ("error while retrieving key material through pipe: %s\n", gpg_strerror (err)); goto leave; } err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))", (int)mlen, m, (int)elen, e); if (err) goto leave; len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); keybuf = xtrymalloc (len); if (!keybuf) { err = gpg_error_from_syserror (); gcry_sexp_release (s_pkey); goto leave; } gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len); gcry_sexp_release (s_pkey); app->app_local->pk[keyno].key = (unsigned char*)keybuf; /* Decrement for trailing '\0' */ app->app_local->pk[keyno].keylen = len - 1; } leave: /* Set a flag to indicate that we tried to read the key. */ app->app_local->pk[keyno].read_done = 1; xfree (buffer); return err; } #endif /* GNUPG_MAJOR_VERSION > 1 */ /* Send the KEYPAIRINFO back. KEY needs to be in the range [1,3]. This is used by the LEARN command. */ static gpg_error_t send_keypair_info (app_t app, ctrl_t ctrl, int key) { int keyno = key - 1; gpg_error_t err = 0; /* Note that GnuPG 1.x does not need this and it would be too time consuming to send it just for the fun of it. */ #if GNUPG_MAJOR_VERSION > 1 unsigned char grip[20]; char gripstr[41]; char idbuf[50]; err = get_public_key (app, keyno); if (err) goto leave; assert (keyno >= 0 && keyno <= 2); if (!app->app_local->pk[keyno].key) goto leave; /* No such key - ignore. */ err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key, app->app_local->pk[keyno].keylen, grip); if (err) goto leave; bin2hex (grip, 20, gripstr); sprintf (idbuf, "OPENPGP.%d", keyno+1); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, idbuf, strlen (idbuf), NULL, (size_t)0); leave: #endif /* GNUPG_MAJOR_VERSION > 1 */ return err; } /* Handle the LEARN command for OpenPGP. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { (void)flags; do_getattr (app, ctrl, "EXTCAP"); do_getattr (app, ctrl, "DISP-NAME"); do_getattr (app, ctrl, "DISP-LANG"); do_getattr (app, ctrl, "DISP-SEX"); do_getattr (app, ctrl, "PUBKEY-URL"); do_getattr (app, ctrl, "LOGIN-DATA"); do_getattr (app, ctrl, "KEY-FPR"); if (app->card_version > 0x0100) do_getattr (app, ctrl, "KEY-TIME"); do_getattr (app, ctrl, "CA-FPR"); do_getattr (app, ctrl, "CHV-STATUS"); do_getattr (app, ctrl, "SIG-COUNTER"); if (app->app_local->extcap.private_dos) { do_getattr (app, ctrl, "PRIVATE-DO-1"); do_getattr (app, ctrl, "PRIVATE-DO-2"); if (app->did_chv2) do_getattr (app, ctrl, "PRIVATE-DO-3"); if (app->did_chv3) do_getattr (app, ctrl, "PRIVATE-DO-4"); } send_keypair_info (app, ctrl, 1); send_keypair_info (app, ctrl, 2); send_keypair_info (app, ctrl, 3); /* Note: We do not send the Cardholder Certificate, because that is relatively long and for OpenPGP applications not really needed. */ return 0; } /* Handle the READKEY command for OpenPGP. On success a canonical encoded S-expression with the public key will get stored at PK and its length (for assertions) at PKLEN; the caller must release that buffer. On error PK and PKLEN are not changed and an error code is returned. */ static gpg_error_t do_readkey (app_t app, int advanced, const char *keyid, unsigned char **pk, size_t *pklen) { #if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; int keyno; unsigned char *buf; if (!strcmp (keyid, "OPENPGP.1")) keyno = 0; else if (!strcmp (keyid, "OPENPGP.2")) keyno = 1; else if (!strcmp (keyid, "OPENPGP.3")) keyno = 2; else return gpg_error (GPG_ERR_INV_ID); err = get_public_key (app, keyno); if (err) return err; buf = app->app_local->pk[keyno].key; if (!buf) return gpg_error (GPG_ERR_NO_PUBKEY); if (advanced) { gcry_sexp_t s_key; err = gcry_sexp_new (&s_key, buf, app->app_local->pk[keyno].keylen, 0); if (err) return err; *pklen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); *pk = xtrymalloc (*pklen); if (!*pk) { err = gpg_error_from_syserror (); *pklen = 0; return err; } gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, *pk, *pklen); gcry_sexp_release (s_key); /* Decrement for trailing '\0' */ *pklen = *pklen - 1; } else { *pklen = app->app_local->pk[keyno].keylen; *pk = xtrymalloc (*pklen); if (!*pk) { err = gpg_error_from_syserror (); *pklen = 0; return err; } memcpy (*pk, buf, *pklen); } return 0; #else return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Read the standard certificate of an OpenPGP v2 card. It is returned in a freshly allocated buffer with that address stored at CERT and the length of the certificate stored at CERTLEN. CERTID needs to be set to "OPENPGP.3". */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { #if GNUPG_MAJOR_VERSION > 1 gpg_error_t err; unsigned char *buffer; size_t buflen; void *relptr; *cert = NULL; *certlen = 0; if (strcmp (certid, "OPENPGP.3")) return gpg_error (GPG_ERR_INV_ID); if (!app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_FOUND); relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL); if (!relptr) return gpg_error (GPG_ERR_NOT_FOUND); if (!buflen) err = gpg_error (GPG_ERR_NOT_FOUND); else if (!(*cert = xtrymalloc (buflen))) err = gpg_error_from_syserror (); else { memcpy (*cert, buffer, buflen); *certlen = buflen; err = 0; } xfree (relptr); return err; #else return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Decide if we use the pinpad of the reader for PIN input according to the user preference on the card, and the capability of the reader. This routine is only called when the reader has pinpad. Returns 0 if we use pinpad, 1 otherwise. */ static int check_pinpad_request (app_t app, pininfo_t *pininfo, int admin_pin) { if (app->app_local->pinpad.specified == 0) /* No preference on card. */ { if (pininfo->fixedlen == 0) /* Reader has varlen capability. */ return 0; /* Then, use pinpad. */ else /* * Reader has limited capability, and it may not match PIN of * the card. */ return 1; } if (admin_pin) pininfo->fixedlen = app->app_local->pinpad.fixedlen_admin; else pininfo->fixedlen = app->app_local->pinpad.fixedlen_user; if (pininfo->fixedlen == 0 /* User requests disable pinpad. */ || pininfo->fixedlen < pininfo->minlen || pininfo->fixedlen > pininfo->maxlen /* Reader doesn't have the capability to input a PIN which * length is FIXEDLEN. */) return 1; return 0; } /* Return a string with information about the card for use in a * prompt. Returns NULL on memory failure. */ static char * get_prompt_info (app_t app, int chvno, unsigned long sigcount, int remaining) { char *serial, *disp_name, *rembuf, *tmpbuf, *result; serial = get_disp_serialno (app); if (!serial) return NULL; disp_name = get_disp_name (app); if (chvno == 1) { /* TRANSLATORS: Put a \x1f right before a colon. This can be * used by pinentry to nicely align the names and values. Keep * the %s at the start and end of the string. */ result = xtryasprintf (_("%s" "Number\x1f: %s%%0A" "Holder\x1f: %s%%0A" "Counter\x1f: %lu" "%s"), "\x1e", serial, disp_name? disp_name:"", sigcount, ""); } else { result = xtryasprintf (_("%s" "Number\x1f: %s%%0A" "Holder\x1f: %s" "%s"), "\x1e", serial, disp_name? disp_name:"", ""); } xfree (disp_name); xfree (serial); if (remaining != -1) { /* TRANSLATORS: This is the number of remaining attempts to * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); if (!rembuf) { xfree (result); return NULL; } tmpbuf = strconcat (result, "%0A%0A", rembuf, NULL); xfree (rembuf); if (!tmpbuf) { xfree (result); return NULL; } xfree (result); result = tmpbuf; } return result; } /* Verify a CHV either using the pinentry or if possible by using a pinpad. PINCB and PINCB_ARG describe the usual callback for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only used with CHV1. PINVALUE is the address of a pointer which will receive a newly allocated block with the actual PIN (this is useful in case that PIN shall be used for another verify operation). The caller needs to free this value. If the function returns with success and NULL is stored at PINVALUE, the caller should take this as an indication that the pinpad has been used. */ static gpg_error_t verify_a_chv (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, int chvno, unsigned long sigcount, char **pinvalue) { int rc = 0; char *prompt_buffer = NULL; const char *prompt; pininfo_t pininfo; int minlen = 6; int remaining; log_assert (chvno == 1 || chvno == 2); *pinvalue = NULL; remaining = get_remaining_tries (app, 0); if (remaining == -1) return gpg_error (GPG_ERR_CARD); if (chvno == 2 && app->app_local->flags.def_chv2) { /* Special case for def_chv2 mechanism. */ if (opt.verbose) log_info (_("using default PIN as %s\n"), "CHV2"); rc = iso7816_verify (app->slot, 0x82, "123456", 6); if (rc) { /* Verification of CHV2 with the default PIN failed, although the card pretends to have the default PIN set as CHV2. We better disable the def_chv2 flag now. */ log_info (_("failed to use default PIN as %s: %s" " - disabling further default use\n"), "CHV2", gpg_strerror (rc)); app->app_local->flags.def_chv2 = 0; } return rc; } memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; { const char *firstline = _("||Please unlock the card"); char *infoblock = get_prompt_info (app, chvno, sigcount, remaining < 3? remaining : -1); prompt_buffer = strconcat (firstline, "%0A%0A", infoblock, NULL); if (prompt_buffer) prompt = prompt_buffer; else prompt = firstline; /* ENOMEM fallback. */ xfree (infoblock); } if (!opt.disable_pinpad && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) && !check_pinpad_request (app, &pininfo, 0)) { /* The reader supports the verify command through the pinpad. Note that the pincb appends a text to the prompt telling the user to use the pinpad. */ rc = pincb (pincb_arg, prompt, NULL); prompt = NULL; xfree (prompt_buffer); prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } rc = iso7816_verify_kp (app->slot, 0x80+chvno, &pininfo); /* Dismiss the prompt. */ pincb (pincb_arg, NULL, NULL); log_assert (!*pinvalue); } else { /* The reader has no pinpad or we don't want to use it. */ rc = pincb (pincb_arg, prompt, pinvalue); prompt = NULL; xfree (prompt_buffer); prompt_buffer = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } if (strlen (*pinvalue) < minlen) { log_error (_("PIN for CHV%d is too short;" " minimum length is %d\n"), chvno, minlen); xfree (*pinvalue); *pinvalue = NULL; return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x80+chvno, *pinvalue, strlen (*pinvalue)); } if (rc) { log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc)); xfree (*pinvalue); *pinvalue = NULL; flush_cache_after_error (app); } return rc; } /* Verify CHV2 if required. Depending on the configuration of the card CHV1 will also be verified. */ static gpg_error_t verify_chv2 (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc; char *pinvalue; if (app->did_chv2) return 0; /* We already verified CHV2. */ rc = verify_a_chv (app, pincb, pincb_arg, 2, 0, &pinvalue); if (rc) return rc; app->did_chv2 = 1; if (!app->did_chv1 && !app->force_chv1 && pinvalue) { /* For convenience we verify CHV1 here too. We do this only if the card is not configured to require a verification before each CHV1 controlled operation (force_chv1) and if we are not using the pinpad (PINVALUE == NULL). */ rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc)); flush_cache_after_error (app); } else app->did_chv1 = 1; } xfree (pinvalue); return rc; } /* Build the prompt to enter the Admin PIN. The prompt depends on the current sdtate of the card. */ static gpg_error_t build_enter_admin_pin_prompt (app_t app, char **r_prompt) { int remaining; char *prompt; char *infoblock; *r_prompt = NULL; remaining = get_remaining_tries (app, 1); if (remaining == -1) return gpg_error (GPG_ERR_CARD); if (!remaining) { log_info (_("card is permanently locked!\n")); return gpg_error (GPG_ERR_BAD_PIN); } log_info (ngettext("%d Admin PIN attempt remaining before card" " is permanently locked\n", "%d Admin PIN attempts remaining before card" " is permanently locked\n", remaining), remaining); infoblock = get_prompt_info (app, 3, 0, remaining < 3? remaining : -1); /* TRANSLATORS: Do not translate the "|A|" prefix but keep it at the start of the string. Use %0A (single percent) for a linefeed. */ prompt = strconcat (_("|A|Please enter the Admin PIN"), "%0A%0A", infoblock, NULL); xfree (infoblock); if (!prompt) return gpg_error_from_syserror (); *r_prompt = prompt; return 0; } /* Verify CHV3 if required. */ static gpg_error_t verify_chv3 (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; #if GNUPG_MAJOR_VERSION != 1 if (!opt.allow_admin) { log_info (_("access to admin commands is not configured\n")); return gpg_error (GPG_ERR_EACCES); } #endif if (!app->did_chv3) { pininfo_t pininfo; int minlen = 8; char *prompt; memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; rc = build_enter_admin_pin_prompt (app, &prompt); if (rc) return rc; if (!opt.disable_pinpad && !iso7816_check_pinpad (app->slot, ISO7816_VERIFY, &pininfo) && !check_pinpad_request (app, &pininfo, 1)) { /* The reader supports the verify command through the pinpad. */ rc = pincb (pincb_arg, prompt, NULL); xfree (prompt); prompt = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } rc = iso7816_verify_kp (app->slot, 0x83, &pininfo); /* Dismiss the prompt. */ pincb (pincb_arg, NULL, NULL); } else { char *pinvalue; rc = pincb (pincb_arg, prompt, &pinvalue); xfree (prompt); prompt = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); return rc; } if (strlen (pinvalue) < minlen) { log_error (_("PIN for CHV%d is too short;" " minimum length is %d\n"), 3, minlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); xfree (pinvalue); } if (rc) { log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc)); flush_cache_after_error (app); return rc; } app->did_chv3 = 1; } return rc; } /* Handle the SETATTR operation. All arguments are already basically checked. */ static gpg_error_t do_setattr (app_t app, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t rc; int idx; static struct { const char *name; int tag; int need_chv; int special; unsigned int need_v2:1; } table[] = { { "DISP-NAME", 0x005B, 3 }, { "LOGIN-DATA", 0x005E, 3, 2 }, { "DISP-LANG", 0x5F2D, 3 }, { "DISP-SEX", 0x5F35, 3 }, { "PUBKEY-URL", 0x5F50, 3 }, { "CHV-STATUS-1", 0x00C4, 3, 1 }, { "CA-FPR-1", 0x00CA, 3 }, { "CA-FPR-2", 0x00CB, 3 }, { "CA-FPR-3", 0x00CC, 3 }, { "PRIVATE-DO-1", 0x0101, 2 }, { "PRIVATE-DO-2", 0x0102, 3 }, { "PRIVATE-DO-3", 0x0103, 2 }, { "PRIVATE-DO-4", 0x0104, 3 }, { "CERT-3", 0x7F21, 3, 0, 1 }, { "SM-KEY-ENC", 0x00D1, 3, 0, 1 }, { "SM-KEY-MAC", 0x00D2, 3, 0, 1 }, { "KEY-ATTR", 0, 0, 3, 1 }, { "AESKEY", 0x00D5, 3, 0, 1 }, { NULL, 0 } }; int exmode; for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) ; if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); if (table[idx].need_v2 && !app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ if (table[idx].special == 3) return change_keyattr_from_string (app, pincb, pincb_arg, value, valuelen); switch (table[idx].need_chv) { case 2: rc = verify_chv2 (app, pincb, pincb_arg); break; case 3: rc = verify_chv3 (app, pincb, pincb_arg); break; default: rc = 0; } if (rc) return rc; /* Flush the cache before writing it, so that the next get operation will reread the data from the card and thus get synced in case of errors (e.g. data truncated by the card). */ flush_cache_item (app, table[idx].tag); if (app->app_local->cardcap.ext_lc_le && valuelen > 254) exmode = 1; /* Use extended length w/o a limit. */ else if (app->app_local->cardcap.cmd_chaining && valuelen > 254) exmode = -254; /* Command chaining with max. 254 bytes. */ else exmode = 0; rc = iso7816_put_data (app->slot, exmode, table[idx].tag, value, valuelen); if (rc) log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc)); if (table[idx].special == 1) app->force_chv1 = (valuelen && *value == 0); else if (table[idx].special == 2) parse_login_data (app); return rc; } /* Handle the WRITECERT command for OpenPGP. This rites the standard certifciate to the card; CERTID needs to be set to "OPENPGP.3". PINCB and PINCB_ARG are the usual arguments for the pinentry callback. */ static gpg_error_t do_writecert (app_t app, ctrl_t ctrl, const char *certidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *certdata, size_t certdatalen) { (void)ctrl; #if GNUPG_MAJOR_VERSION > 1 if (strcmp (certidstr, "OPENPGP.3")) return gpg_error (GPG_ERR_INV_ID); if (!certdata || !certdatalen) return gpg_error (GPG_ERR_INV_ARG); if (!app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); if (certdatalen > app->app_local->extcap.max_certlen_3) return gpg_error (GPG_ERR_TOO_LARGE); return do_setattr (app, "CERT-3", pincb, pincb_arg, certdata, certdatalen); #else return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Handle the PASSWD command. The following combinations are possible: Flags CHVNO Vers. Description RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2 RESET 1 2 Verify PW3 and set a new PW1. RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2. RESET 2 2 Verify PW3 and set a new Reset Code. RESET 3 any Returns GPG_ERR_INV_ID. - 1 1 Verify CHV2 and set a new CHV1 and CHV2. - 1 2 Verify PW1 and set a new PW1. - 2 1 Verify CHV2 and set a new CHV1 and CHV2. - 2 2 Verify Reset Code and set a new PW1. - 3 any Verify CHV3/PW3 and set a new CHV3/PW3. */ static gpg_error_t do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int rc = 0; int chvno = atoi (chvnostr); char *resetcode = NULL; char *oldpinvalue = NULL; char *pinvalue = NULL; int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET); int set_resetcode = 0; pininfo_t pininfo; int use_pinpad = 0; int minlen = 6; (void)ctrl; memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = -1; pininfo.minlen = minlen; if (reset_mode && chvno == 3) { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } if (!app->app_local->extcap.is_v2) { /* Version 1 cards. */ if (reset_mode || chvno == 3) { /* We always require that the PIN is entered. */ app->did_chv3 = 0; rc = verify_chv3 (app, pincb, pincb_arg); if (rc) goto leave; } else if (chvno == 1 || chvno == 2) { /* On a v1.x card CHV1 and CVH2 should always have the same value, thus we enforce it here. */ int save_force = app->force_chv1; app->force_chv1 = 0; app->did_chv1 = 0; app->did_chv2 = 0; rc = verify_chv2 (app, pincb, pincb_arg); app->force_chv1 = save_force; if (rc) goto leave; } else { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } } else { /* Version 2 cards. */ if (!opt.disable_pinpad && !iso7816_check_pinpad (app->slot, ISO7816_CHANGE_REFERENCE_DATA, &pininfo) && !check_pinpad_request (app, &pininfo, chvno == 3)) use_pinpad = 1; if (reset_mode) { /* To reset a PIN the Admin PIN is required. */ use_pinpad = 0; app->did_chv3 = 0; rc = verify_chv3 (app, pincb, pincb_arg); if (rc) goto leave; if (chvno == 2) set_resetcode = 1; } else if (chvno == 1 || chvno == 3) { if (!use_pinpad) { char *promptbuf = NULL; const char *prompt; if (chvno == 3) { minlen = 8; rc = build_enter_admin_pin_prompt (app, &promptbuf); if (rc) goto leave; prompt = promptbuf; } else prompt = _("||Please enter the PIN"); rc = pincb (pincb_arg, prompt, &oldpinvalue); xfree (promptbuf); promptbuf = NULL; if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); goto leave; } if (strlen (oldpinvalue) < minlen) { log_info (_("PIN for CHV%d is too short;" " minimum length is %d\n"), chvno, minlen); rc = gpg_error (GPG_ERR_BAD_PIN); goto leave; } } } else if (chvno == 2) { /* There is no PW2 for v2 cards. We use this condition to allow a PW reset using the Reset Code. */ void *relptr; unsigned char *value; size_t valuelen; int remaining; use_pinpad = 0; minlen = 8; relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); if (!relptr || valuelen < 7) { log_error (_("error retrieving CHV status from card\n")); xfree (relptr); rc = gpg_error (GPG_ERR_CARD); goto leave; } remaining = value[5]; xfree (relptr); if (!remaining) { log_error (_("Reset Code not or not anymore available\n")); rc = gpg_error (GPG_ERR_BAD_PIN); goto leave; } rc = pincb (pincb_arg, _("||Please enter the Reset Code for the card"), &resetcode); if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); goto leave; } if (strlen (resetcode) < minlen) { log_info (_("Reset Code is too short; minimum length is %d\n"), minlen); rc = gpg_error (GPG_ERR_BAD_PIN); goto leave; } } else { rc = gpg_error (GPG_ERR_INV_ID); goto leave; } } if (chvno == 3) app->did_chv3 = 0; else app->did_chv1 = app->did_chv2 = 0; if (!use_pinpad) { /* TRANSLATORS: Do not translate the "|*|" prefixes but keep it at the start of the string. We need this elsewhere to get some infos on the string. */ rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") : chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"), &pinvalue); if (rc || pinvalue == NULL) { log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc)); goto leave; } } if (resetcode) { char *buffer; buffer = xtrymalloc (strlen (resetcode) + strlen (pinvalue) + 1); if (!buffer) rc = gpg_error_from_syserror (); else { strcpy (stpcpy (buffer, resetcode), pinvalue); rc = iso7816_reset_retry_counter_with_rc (app->slot, 0x81, buffer, strlen (buffer)); wipememory (buffer, strlen (buffer)); xfree (buffer); } } else if (set_resetcode) { if (strlen (pinvalue) < 8) { log_error (_("Reset Code is too short; minimum length is %d\n"), 8); rc = gpg_error (GPG_ERR_BAD_PIN); } else rc = iso7816_put_data (app->slot, 0, 0xD3, pinvalue, strlen (pinvalue)); } else if (reset_mode) { rc = iso7816_reset_retry_counter (app->slot, 0x81, pinvalue, strlen (pinvalue)); if (!rc && !app->app_local->extcap.is_v2) rc = iso7816_reset_retry_counter (app->slot, 0x82, pinvalue, strlen (pinvalue)); } else if (!app->app_local->extcap.is_v2) { /* Version 1 cards. */ if (chvno == 1 || chvno == 2) { rc = iso7816_change_reference_data (app->slot, 0x81, NULL, 0, pinvalue, strlen (pinvalue)); if (!rc) rc = iso7816_change_reference_data (app->slot, 0x82, NULL, 0, pinvalue, strlen (pinvalue)); } else /* CHVNO == 3 */ { rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, NULL, 0, pinvalue, strlen (pinvalue)); } } else { /* Version 2 cards. */ assert (chvno == 1 || chvno == 3); if (use_pinpad) { rc = pincb (pincb_arg, chvno == 3 ? _("||Please enter the Admin PIN and New Admin PIN") : _("||Please enter the PIN and New PIN"), NULL); if (rc) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (rc)); goto leave; } rc = iso7816_change_reference_data_kp (app->slot, 0x80 + chvno, 0, &pininfo); pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, oldpinvalue, strlen (oldpinvalue), pinvalue, strlen (pinvalue)); } if (pinvalue) { wipememory (pinvalue, strlen (pinvalue)); xfree (pinvalue); } if (rc) flush_cache_after_error (app); leave: if (resetcode) { wipememory (resetcode, strlen (resetcode)); xfree (resetcode); } if (oldpinvalue) { wipememory (oldpinvalue, strlen (oldpinvalue)); xfree (oldpinvalue); } return rc; } /* Check whether a key already exists. KEYIDX is the index of the key (0..2). If FORCE is TRUE a diagnositic will be printed but no error returned if the key already exists. The flag GENERATING is only used to print correct messages. */ static gpg_error_t does_key_exist (app_t app, int keyidx, int generating, int force) { const unsigned char *fpr; unsigned char *buffer; size_t buflen, n; int i; assert (keyidx >=0 && keyidx <= 2); if (iso7816_get_data (app->slot, 0, 0x006E, &buffer, &buflen)) { log_error (_("error reading application data\n")); return gpg_error (GPG_ERR_GENERAL); } fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n < 60) { log_error (_("error reading fingerprint DO\n")); xfree (buffer); return gpg_error (GPG_ERR_GENERAL); } fpr += 20*keyidx; for (i=0; i < 20 && !fpr[i]; i++) ; xfree (buffer); if (i!=20 && !force) { log_error (_("key already exists\n")); return gpg_error (GPG_ERR_EEXIST); } else if (i!=20) log_info (_("existing key will be replaced\n")); else if (generating) log_info (_("generating new key\n")); else log_info (_("writing new key\n")); return 0; } /* Create a TLV tag and value and store it at BUFFER. Return the length of tag and length. A LENGTH greater than 65535 is truncated. */ static size_t add_tlv (unsigned char *buffer, unsigned int tag, size_t length) { unsigned char *p = buffer; assert (tag <= 0xffff); if ( tag > 0xff ) *p++ = tag >> 8; *p++ = tag; if (length < 128) *p++ = length; else if (length < 256) { *p++ = 0x81; *p++ = length; } else { if (length > 0xffff) length = 0xffff; *p++ = 0x82; *p++ = length >> 8; *p++ = length; } return p - buffer; } static gpg_error_t build_privkey_template (app_t app, int keyno, const unsigned char *rsa_n, size_t rsa_n_len, const unsigned char *rsa_e, size_t rsa_e_len, const unsigned char *rsa_p, size_t rsa_p_len, const unsigned char *rsa_q, size_t rsa_q_len, const unsigned char *rsa_u, size_t rsa_u_len, const unsigned char *rsa_dp, size_t rsa_dp_len, const unsigned char *rsa_dq, size_t rsa_dq_len, unsigned char **result, size_t *resultlen) { size_t rsa_e_reqlen; unsigned char privkey[7*(1+3+3)]; size_t privkey_len; unsigned char exthdr[2+2+3]; size_t exthdr_len; unsigned char suffix[2+3]; size_t suffix_len; unsigned char *tp; size_t datalen; unsigned char *template; size_t template_size; *result = NULL; *resultlen = 0; switch (app->app_local->keyattr[keyno].rsa.format) { case RSA_STD: case RSA_STD_N: case RSA_CRT: case RSA_CRT_N: break; default: return gpg_error (GPG_ERR_INV_VALUE); } /* Get the required length for E. Rounded up to the nearest byte */ rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8; assert (rsa_e_len <= rsa_e_reqlen); /* Build the 7f48 cardholder private key template. */ datalen = 0; tp = privkey; tp += add_tlv (tp, 0x91, rsa_e_reqlen); datalen += rsa_e_reqlen; tp += add_tlv (tp, 0x92, rsa_p_len); datalen += rsa_p_len; tp += add_tlv (tp, 0x93, rsa_q_len); datalen += rsa_q_len; if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) { tp += add_tlv (tp, 0x94, rsa_u_len); datalen += rsa_u_len; tp += add_tlv (tp, 0x95, rsa_dp_len); datalen += rsa_dp_len; tp += add_tlv (tp, 0x96, rsa_dq_len); datalen += rsa_dq_len; } if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) { tp += add_tlv (tp, 0x97, rsa_n_len); datalen += rsa_n_len; } privkey_len = tp - privkey; /* Build the extended header list without the private key template. */ tp = exthdr; *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4; *tp++ = 0; tp += add_tlv (tp, 0x7f48, privkey_len); exthdr_len = tp - exthdr; /* Build the 5f48 suffix of the data. */ tp = suffix; tp += add_tlv (tp, 0x5f48, datalen); suffix_len = tp - suffix; /* Now concatenate everything. */ template_size = (1 + 3 /* 0x4d and len. */ + exthdr_len + privkey_len + suffix_len + datalen); tp = template = xtrymalloc_secure (template_size); if (!template) return gpg_error_from_syserror (); tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen); memcpy (tp, exthdr, exthdr_len); tp += exthdr_len; memcpy (tp, privkey, privkey_len); tp += privkey_len; memcpy (tp, suffix, suffix_len); tp += suffix_len; memcpy (tp, rsa_e, rsa_e_len); if (rsa_e_len < rsa_e_reqlen) { /* Right justify E. */ memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len); memset (tp, 0, rsa_e_reqlen - rsa_e_len); } tp += rsa_e_reqlen; memcpy (tp, rsa_p, rsa_p_len); tp += rsa_p_len; memcpy (tp, rsa_q, rsa_q_len); tp += rsa_q_len; if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) { memcpy (tp, rsa_u, rsa_u_len); tp += rsa_u_len; memcpy (tp, rsa_dp, rsa_dp_len); tp += rsa_dp_len; memcpy (tp, rsa_dq, rsa_dq_len); tp += rsa_dq_len; } if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N || app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N) { memcpy (tp, rsa_n, rsa_n_len); tp += rsa_n_len; } /* Sanity check. We don't know the exact length because we allocated 3 bytes for the first length header. */ assert (tp - template <= template_size); *result = template; *resultlen = tp - template; return 0; } static gpg_error_t build_ecc_privkey_template (app_t app, int keyno, const unsigned char *ecc_d, size_t ecc_d_len, const unsigned char *ecc_q, size_t ecc_q_len, unsigned char **result, size_t *resultlen) { unsigned char privkey[2+2]; size_t privkey_len; unsigned char exthdr[2+2+1]; size_t exthdr_len; unsigned char suffix[2+1]; size_t suffix_len; unsigned char *tp; size_t datalen; unsigned char *template; size_t template_size; int pubkey_required; pubkey_required = !!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_PUBKEY); *result = NULL; *resultlen = 0; /* Build the 7f48 cardholder private key template. */ datalen = 0; tp = privkey; tp += add_tlv (tp, 0x92, ecc_d_len); datalen += ecc_d_len; if (pubkey_required) { tp += add_tlv (tp, 0x99, ecc_q_len); datalen += ecc_q_len; } privkey_len = tp - privkey; /* Build the extended header list without the private key template. */ tp = exthdr; *tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4; *tp++ = 0; tp += add_tlv (tp, 0x7f48, privkey_len); exthdr_len = tp - exthdr; /* Build the 5f48 suffix of the data. */ tp = suffix; tp += add_tlv (tp, 0x5f48, datalen); suffix_len = tp - suffix; /* Now concatenate everything. */ template_size = (1 + 1 /* 0x4d and len. */ + exthdr_len + privkey_len + suffix_len + datalen); if (exthdr_len + privkey_len + suffix_len + datalen >= 128) template_size++; tp = template = xtrymalloc_secure (template_size); if (!template) return gpg_error_from_syserror (); tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen); memcpy (tp, exthdr, exthdr_len); tp += exthdr_len; memcpy (tp, privkey, privkey_len); tp += privkey_len; memcpy (tp, suffix, suffix_len); tp += suffix_len; memcpy (tp, ecc_d, ecc_d_len); tp += ecc_d_len; if (pubkey_required) { memcpy (tp, ecc_q, ecc_q_len); tp += ecc_q_len; } assert (tp - template == template_size); *result = template; *resultlen = tp - template; return 0; } /* Helper for do_writekley to change the size of a key. Not ethat this deletes the entire key without asking. */ static gpg_error_t change_keyattr (app_t app, int keyno, const unsigned char *buf, size_t buflen, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; assert (keyno >=0 && keyno <= 2); /* Prepare for storing the key. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) return err; /* Change the attribute. */ err = iso7816_put_data (app->slot, 0, 0xC1+keyno, buf, buflen); if (err) log_error ("error changing key attribute (key=%d)\n", keyno+1); else log_info ("key attribute changed (key=%d)\n", keyno+1); flush_cache (app); parse_algorithm_attribute (app, keyno); app->did_chv1 = 0; app->did_chv2 = 0; app->did_chv3 = 0; return err; } static gpg_error_t change_rsa_keyattr (app_t app, int keyno, unsigned int nbits, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err = 0; unsigned char *buf; size_t buflen; void *relptr; /* Read the current attributes into a buffer. */ relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL); if (!relptr) err = gpg_error (GPG_ERR_CARD); else if (buflen < 6 || buf[0] != PUBKEY_ALGO_RSA) { /* Attriutes too short or not an RSA key. */ xfree (relptr); err = gpg_error (GPG_ERR_CARD); } else { /* We only change n_bits and don't touch anything else. Before we do so, we round up NBITS to a sensible way in the same way as gpg's key generation does it. This may help to sort out problems with a few bits too short keys. */ nbits = ((nbits + 31) / 32) * 32; buf[1] = (nbits >> 8); buf[2] = nbits; err = change_keyattr (app, keyno, buf, buflen, pincb, pincb_arg); xfree (relptr); } return err; } /* Helper to process an setattr command for name KEY-ATTR. In (VALUE,VALUELEN), it expects following string: RSA: "--force <key> <algo> rsa<nbits>" ECC: "--force <key> <algo> <curvename>" */ static gpg_error_t change_keyattr_from_string (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *value, size_t valuelen) { gpg_error_t err = 0; char *string; int key, keyno, algo; int n = 0; /* VALUE is expected to be a string but not guaranteed to be terminated. Thus copy it to an allocated buffer first. */ string = xtrymalloc (valuelen+1); if (!string) return gpg_error_from_syserror (); memcpy (string, value, valuelen); string[valuelen] = 0; /* Because this function deletes the key we require the string "--force" in the data to make clear that something serious might happen. */ sscanf (string, "--force %d %d %n", &key, &algo, &n); if (n < 12) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } keyno = key - 1; if (keyno < 0 || keyno > 2) err = gpg_error (GPG_ERR_INV_ID); else if (algo == PUBKEY_ALGO_RSA) { unsigned int nbits; errno = 0; nbits = strtoul (string+n+3, NULL, 10); if (errno) err = gpg_error (GPG_ERR_INV_DATA); else if (nbits < 1024) err = gpg_error (GPG_ERR_TOO_SHORT); else if (nbits > 4096) err = gpg_error (GPG_ERR_TOO_LARGE); else err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg); } else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) { const char *oidstr; gcry_mpi_t oid; const unsigned char *oidbuf; size_t oid_len; oidstr = openpgp_curve_to_oid (string+n, NULL); if (!oidstr) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } err = openpgp_oid_from_str (oidstr, &oid); if (err) goto leave; oidbuf = gcry_mpi_get_opaque (oid, &n); oid_len = (n+7)/8; /* We have enough room at STRING. */ string[0] = algo; memcpy (string+1, oidbuf+1, oid_len-1); err = change_keyattr (app, keyno, string, oid_len, pincb, pincb_arg); gcry_mpi_release (oid); } else err = gpg_error (GPG_ERR_PUBKEY_ALGO); leave: xfree (string); return err; } static gpg_error_t rsa_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, int keyno, const unsigned char *buf, size_t buflen, int depth) { gpg_error_t err; const unsigned char *tok; size_t toklen; int last_depth1, last_depth2; const unsigned char *rsa_n = NULL; const unsigned char *rsa_e = NULL; const unsigned char *rsa_p = NULL; const unsigned char *rsa_q = NULL; size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len; unsigned int nbits; unsigned int maxbits; unsigned char *template = NULL; unsigned char *tp; size_t template_len; unsigned char fprbuf[20]; u32 created_at = 0; if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA) { log_error (_("unsupported algorithm: %s"), "RSA"); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 1) { const unsigned char **mpi; size_t *mpi_len; switch (*tok) { case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break; case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break; case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break; case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break; default: mpi = NULL; mpi_len = NULL; break; } if (mpi && *mpi) { err = gpg_error (GPG_ERR_DUP_VALUE); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && mpi) { /* Strip off leading zero bytes and save. */ for (;toklen && !*tok; toklen--, tok++) ; *mpi = tok; *mpi_len = toklen; } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Parse other attributes. */ last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen)) { if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen))) goto leave; if (tok) { for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9'; tok++, toklen--) created_at = created_at*10 + (*tok - '0'); } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Check that we have all parameters and that they match the card description. */ if (!created_at) { log_error (_("creation timestamp missing\n")); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } maxbits = app->app_local->keyattr[keyno].rsa.n_bits; nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0; if (opt.verbose) log_info ("RSA modulus size is %u bits\n", nbits); if (nbits && nbits != maxbits && app->app_local->extcap.algo_attr_change) { /* Try to switch the key to a new length. */ err = change_rsa_keyattr (app, keyno, nbits, pincb, pincb_arg); if (!err) maxbits = app->app_local->keyattr[keyno].rsa.n_bits; } if (nbits != maxbits) { log_error (_("RSA modulus missing or not of size %d bits\n"), (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } maxbits = app->app_local->keyattr[keyno].rsa.e_bits; if (maxbits > 32 && !app->app_local->extcap.is_v2) maxbits = 32; /* Our code for v1 does only support 32 bits. */ nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0; if (nbits < 2 || nbits > maxbits) { log_error (_("RSA public exponent missing or larger than %d bits\n"), (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2; nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0; if (nbits != maxbits) { log_error (_("RSA prime %s missing or not of size %d bits\n"), "P", (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0; if (nbits != maxbits) { log_error (_("RSA prime %s missing or not of size %d bits\n"), "Q", (int)maxbits); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } /* We need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; if (app->app_local->extcap.is_v2) { unsigned char *rsa_u, *rsa_dp, *rsa_dq; size_t rsa_u_len, rsa_dp_len, rsa_dq_len; gcry_mpi_t mpi_e, mpi_p, mpi_q; gcry_mpi_t mpi_u = gcry_mpi_snew (0); gcry_mpi_t mpi_dp = gcry_mpi_snew (0); gcry_mpi_t mpi_dq = gcry_mpi_snew (0); gcry_mpi_t mpi_tmp = gcry_mpi_snew (0); int exmode; /* Calculate the u, dp and dq components needed by RSA_CRT cards */ gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL); gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL); gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL); gcry_mpi_invm (mpi_u, mpi_q, mpi_p); gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1); gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp); gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1); gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp); gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u); gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp); gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq); gcry_mpi_release (mpi_e); gcry_mpi_release (mpi_p); gcry_mpi_release (mpi_q); gcry_mpi_release (mpi_u); gcry_mpi_release (mpi_dp); gcry_mpi_release (mpi_dq); gcry_mpi_release (mpi_tmp); /* Build the private key template as described in section 4.3.3.7 of the OpenPGP card specs version 2.0. */ err = build_privkey_template (app, keyno, rsa_n, rsa_n_len, rsa_e, rsa_e_len, rsa_p, rsa_p_len, rsa_q, rsa_q_len, rsa_u, rsa_u_len, rsa_dp, rsa_dp_len, rsa_dq, rsa_dq_len, &template, &template_len); xfree(rsa_u); xfree(rsa_dp); xfree(rsa_dq); if (err) goto leave; /* Prepare for storing the key. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) goto leave; /* Store the key. */ if (app->app_local->cardcap.ext_lc_le && template_len > 254) exmode = 1; /* Use extended length w/o a limit. */ else if (app->app_local->cardcap.cmd_chaining && template_len > 254) exmode = -254; else exmode = 0; err = iso7816_put_data_odd (app->slot, exmode, 0x3fff, template, template_len); } else { /* Build the private key template as described in section 4.3.3.6 of the OpenPGP card specs version 1.1: 0xC0 <length> public exponent 0xC1 <length> prime p 0xC2 <length> prime q */ assert (rsa_e_len <= 4); template_len = (1 + 1 + 4 + 1 + 1 + rsa_p_len + 1 + 1 + rsa_q_len); template = tp = xtrymalloc_secure (template_len); if (!template) { err = gpg_error_from_syserror (); goto leave; } *tp++ = 0xC0; *tp++ = 4; memcpy (tp, rsa_e, rsa_e_len); if (rsa_e_len < 4) { /* Right justify E. */ memmove (tp+4-rsa_e_len, tp, rsa_e_len); memset (tp, 0, 4-rsa_e_len); } tp += 4; *tp++ = 0xC1; *tp++ = rsa_p_len; memcpy (tp, rsa_p, rsa_p_len); tp += rsa_p_len; *tp++ = 0xC2; *tp++ = rsa_q_len; memcpy (tp, rsa_q, rsa_q_len); tp += rsa_q_len; assert (tp - template == template_len); /* Prepare for storing the key. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) goto leave; /* Store the key. */ err = iso7816_put_data (app->slot, 0, (app->card_version > 0x0007? 0xE0:0xE9)+keyno, template, template_len); } if (err) { log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); goto leave; } err = store_fpr (app, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA, rsa_n, rsa_n_len, rsa_e, rsa_e_len); if (err) goto leave; leave: xfree (template); return err; } static gpg_error_t ecc_writekey (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, int keyno, const unsigned char *buf, size_t buflen, int depth) { gpg_error_t err; const unsigned char *tok; size_t toklen; int last_depth1, last_depth2; const unsigned char *ecc_q = NULL; const unsigned char *ecc_d = NULL; size_t ecc_q_len, ecc_d_len; const char *curve = NULL; u32 created_at = 0; const char *oidstr; int flag_djb_tweak = 0; int algo; gcry_mpi_t oid = NULL; const unsigned char *oidbuf; unsigned int n; size_t oid_len; unsigned char fprbuf[20]; /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)): curve = "NIST P-256" */ /* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)): curve = "secp256k1" */ /* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)): curve = "Ed25519" */ last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 5 && !memcmp (tok, "curve", 5)) { char *curve_name; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; curve_name = xtrymalloc (toklen+1); if (!curve_name) { err = gpg_error_from_syserror (); goto leave; } memcpy (curve_name, tok, toklen); curve_name[toklen] = 0; curve = openpgp_is_curve_supported (curve_name, NULL, NULL); xfree (curve_name); } else if (tok && toklen == 5 && !memcmp (tok, "flags", 5)) { if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok) { if ((toklen == 5 && !memcmp (tok, "eddsa", 5)) || (toklen == 9 && !memcmp (tok, "djb-tweak", 9))) flag_djb_tweak = 1; } } else if (tok && toklen == 1) { const unsigned char **buf2; size_t *buf2len; int native = flag_djb_tweak; switch (*tok) { case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break; case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; native = 0; break; default: buf2 = NULL; buf2len = NULL; break; } if (buf2 && *buf2) { err = gpg_error (GPG_ERR_DUP_VALUE); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && buf2) { if (!native) /* Strip off leading zero bytes and save. */ for (;toklen && !*tok; toklen--, tok++) ; *buf2 = tok; *buf2len = toklen; } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Parse other attributes. */ last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) { err = gpg_error (GPG_ERR_UNKNOWN_SEXP); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen)) { if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen))) goto leave; if (tok) { for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9'; tok++, toklen--) created_at = created_at*10 + (*tok - '0'); } } /* Skip until end of list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) goto leave; } /* Check that we have all parameters and that they match the card description. */ if (!curve) { log_error (_("unsupported curve\n")); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (!created_at) { log_error (_("creation timestamp missing\n")); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (flag_djb_tweak && keyno != 1) algo = PUBKEY_ALGO_EDDSA; else if (keyno == 1) algo = PUBKEY_ALGO_ECDH; else algo = PUBKEY_ALGO_ECDSA; oidstr = openpgp_curve_to_oid (curve, NULL); err = openpgp_oid_from_str (oidstr, &oid); if (err) goto leave; oidbuf = gcry_mpi_get_opaque (oid, &n); if (!oidbuf) { err = gpg_error_from_syserror (); goto leave; } oid_len = (n+7)/8; if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC || app->app_local->keyattr[keyno].ecc.curve != curve || (flag_djb_tweak != (app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))) { if (app->app_local->extcap.algo_attr_change) { unsigned char *keyattr; if (!oid_len) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } keyattr = xtrymalloc (oid_len); if (!keyattr) { err = gpg_error_from_syserror (); goto leave; } keyattr[0] = algo; memcpy (keyattr+1, oidbuf+1, oid_len-1); err = change_keyattr (app, keyno, keyattr, oid_len, pincb, pincb_arg); xfree (keyattr); if (err) goto leave; } else { log_error ("key attribute on card doesn't match\n"); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } } if (opt.verbose) log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len); /* We need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; if (app->app_local->extcap.is_v2) { /* Build the private key template as described in section 4.3.3.7 of the OpenPGP card specs version 2.0. */ unsigned char *template; size_t template_len; int exmode; err = build_ecc_privkey_template (app, keyno, ecc_d, ecc_d_len, ecc_q, ecc_q_len, &template, &template_len); if (err) goto leave; /* Prepare for storing the key. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) { xfree (template); goto leave; } /* Store the key. */ if (app->app_local->cardcap.ext_lc_le && template_len > 254) exmode = 1; /* Use extended length w/o a limit. */ else if (app->app_local->cardcap.cmd_chaining && template_len > 254) exmode = -254; else exmode = 0; err = iso7816_put_data_odd (app->slot, exmode, 0x3fff, template, template_len); xfree (template); } else err = gpg_error (GPG_ERR_NOT_SUPPORTED); if (err) { log_error (_("failed to store the key: %s\n"), gpg_strerror (err)); goto leave; } err = store_fpr (app, keyno, created_at, fprbuf, algo, oidbuf, oid_len, ecc_q, ecc_q_len, ecdh_params (curve), (size_t)4); leave: gcry_mpi_release (oid); return err; } /* Handle the WRITEKEY command for OpenPGP. This function expects a canonical encoded S-expression with the secret key in KEYDATA and its length (for assertions) in KEYDATALEN. KEYID needs to be the usual keyid which for OpenPGP is the string "OPENPGP.n" with n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall get overwritten. PINCB and PINCB_ARG are the usual arguments for the pinentry callback. */ static gpg_error_t do_writekey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; int force = (flags & 1); int keyno; const unsigned char *buf, *tok; size_t buflen, toklen; int depth; (void)ctrl; if (!strcmp (keyid, "OPENPGP.1")) keyno = 0; else if (!strcmp (keyid, "OPENPGP.2")) keyno = 1; else if (!strcmp (keyid, "OPENPGP.3")) keyno = 2; else return gpg_error (GPG_ERR_INV_ID); err = does_key_exist (app, keyno, 0, force); if (err) return err; /* Parse the S-expression */ buf = keydata; buflen = keydatalen; depth = 0; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen)) { if (!tok) ; else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen)) log_info ("protected-private-key passed to writekey\n"); else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) log_info ("shadowed-private-key passed to writekey\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) goto leave; if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0) err = rsa_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth); else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0) err = ecc_writekey (app, pincb, pincb_arg, keyno, buf, buflen, depth); else { err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); goto leave; } leave: return err; } /* Handle the GENKEY command. */ static gpg_error_t do_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; char numbuf[30]; unsigned char *buffer = NULL; const unsigned char *keydata; size_t buflen, keydatalen; u32 created_at; int keyno = atoi (keynostr) - 1; int force = (flags & 1); time_t start_at; int exmode = 0; int le_value = 256; /* Use legacy value. */ if (keyno < 0 || keyno > 2) return gpg_error (GPG_ERR_INV_ID); /* We flush the cache to increase the traffic before a key generation. This _might_ help a card to gather more entropy. */ flush_cache (app); /* Obviously we need to remove the cached public key. */ xfree (app->app_local->pk[keyno].key); app->app_local->pk[keyno].key = NULL; app->app_local->pk[keyno].keylen = 0; app->app_local->pk[keyno].read_done = 0; /* Check whether a key already exists. */ err = does_key_exist (app, keyno, 1, force); if (err) return err; if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) { unsigned int keybits = app->app_local->keyattr[keyno].rsa.n_bits; /* Because we send the key parameter back via status lines we need to put a limit on the max. allowed keysize. 2048 bit will already lead to a 527 byte long status line and thus a 4096 bit key would exceed the Assuan line length limit. */ if (keybits > 4096) return gpg_error (GPG_ERR_TOO_LARGE); if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY && app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA) { exmode = 1; /* Use extended length w/o a limit. */ le_value = determine_rsa_response (app, keyno); /* No need to check le_value because it comes from a 16 bit value and thus can't create an overflow on a 32 bit system. */ } } /* Prepare for key generation by verifying the Admin PIN. */ err = verify_chv3 (app, pincb, pincb_arg); if (err) return err; log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); err = iso7816_generate_keypair (app->slot, exmode, (keyno == 0? "\xB6" : keyno == 1? "\xB8" : "\xA4"), 2, le_value, &buffer, &buflen); if (err) { log_error (_("generating key failed\n")); return gpg_error (GPG_ERR_CARD); } { int nsecs = (int)(time (NULL) - start_at); log_info (ngettext("key generation completed (%d second)\n", "key generation completed (%d seconds)\n", nsecs), nsecs); } keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata) { err = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the public key data\n")); goto leave; } created_at = (u32)(createtime? createtime : gnupg_get_time ()); sprintf (numbuf, "%u", created_at); send_status_info (ctrl, "KEY-CREATED-AT", numbuf, (size_t)strlen(numbuf), NULL, 0); err = read_public_key (app, ctrl, created_at, keyno, buffer, buflen); leave: xfree (buffer); return err; } static unsigned long convert_sig_counter_value (const unsigned char *value, size_t valuelen) { unsigned long ul; if (valuelen == 3 ) ul = (value[0] << 16) | (value[1] << 8) | value[2]; else { log_error (_("invalid structure of OpenPGP card (DO 0x93)\n")); ul = 0; } return ul; } static unsigned long get_sig_counter (app_t app) { void *relptr; unsigned char *value; size_t valuelen; unsigned long ul; relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL); if (!relptr) return 0; ul = convert_sig_counter_value (value, valuelen); xfree (relptr); return ul; } static gpg_error_t compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr) { const unsigned char *fpr; unsigned char *buffer; size_t buflen, n; int rc, i; assert (keyno >= 0 && keyno <= 2); rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0); if (rc) { log_error (_("error reading application data\n")); return gpg_error (GPG_ERR_GENERAL); } fpr = find_tlv (buffer, buflen, 0x00C5, &n); if (!fpr || n != 60) { xfree (buffer); log_error (_("error reading fingerprint DO\n")); return gpg_error (GPG_ERR_GENERAL); } fpr += keyno*20; for (i=0; i < 20; i++) if (sha1fpr[i] != fpr[i]) { xfree (buffer); log_info (_("fingerprint on card does not match requested one\n")); return gpg_error (GPG_ERR_WRONG_SECKEY); } xfree (buffer); return 0; } /* If a fingerprint has been specified check it against the one on the card. This allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg has not been updated. If there is no fingerprint we assume that this is okay. */ static gpg_error_t check_against_given_fingerprint (app_t app, const char *fpr, int key) { unsigned char tmp[20]; const char *s; int n; for (s=fpr, n=0; hexdigitp (s); s++, n++) ; if (n != 40) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* okay */ else return gpg_error (GPG_ERR_INV_ID); for (s=fpr, n=0; n < 20; s += 2, n++) tmp[n] = xtoi_2 (s); return compare_fingerprint (app, key-1, tmp); } /* Compute a digital signature on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). As a special feature a KEYIDSTR of "OPENPGP.3" redirects the operation to the auth command. */ static gpg_error_t do_sign (app_t app, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C }; static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; int rc; unsigned char data[19+64]; size_t datalen; unsigned char tmp_sn[20]; /* Actually 16 bytes but also for the fpr. */ const char *s; int n; const char *fpr = NULL; unsigned long sigcount; int use_auth = 0; int exmode, le_value; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); /* Strip off known prefixes. */ #define X(a,b,c,d) \ if (hashalgo == GCRY_MD_ ## a \ && (d) \ && indatalen == sizeof b ## _prefix + (c) \ && !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \ { \ indata = (const char*)indata + sizeof b ## _prefix; \ indatalen -= sizeof b ## _prefix; \ } if (indatalen == 20) ; /* Assume a plain SHA-1 or RMD160 digest has been given. */ else X(SHA1, sha1, 20, 1) else X(RMD160, rmd160, 20, 1) else X(SHA224, sha224, 28, app->app_local->extcap.is_v2) else X(SHA256, sha256, 32, app->app_local->extcap.is_v2) else X(SHA384, sha384, 48, app->app_local->extcap.is_v2) else X(SHA512, sha512, 64, app->app_local->extcap.is_v2) else if ((indatalen == 28 || indatalen == 32 || indatalen == 48 || indatalen ==64) && app->app_local->extcap.is_v2) ; /* Assume a plain SHA-3 digest has been given. */ else { log_error (_("card does not support digest algorithm %s\n"), gcry_md_algo_name (hashalgo)); /* Or the supplied digest length does not match an algorithm. */ return gpg_error (GPG_ERR_INV_VALUE); } #undef X /* Check whether an OpenPGP card of any version has been requested. */ if (!strcmp (keyidstr, "OPENPGP.1")) ; else if (!strcmp (keyidstr, "OPENPGP.3")) use_auth = 1; else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); else { for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, gpg will detect a bogus signature anyway due to the verify-after-signing feature. */ rc = fpr? check_against_given_fingerprint (app, fpr, 1) : 0; if (rc) return rc; /* Concatenate prefix and digest. */ #define X(a,b,d) \ if (hashalgo == GCRY_MD_ ## a && (d) ) \ { \ datalen = sizeof b ## _prefix + indatalen; \ assert (datalen <= sizeof data); \ memcpy (data, b ## _prefix, sizeof b ## _prefix); \ memcpy (data + sizeof b ## _prefix, indata, indatalen); \ } if (use_auth || app->app_local->keyattr[use_auth? 2: 0].key_type == KEY_TYPE_RSA) { X(SHA1, sha1, 1) else X(RMD160, rmd160, 1) else X(SHA224, sha224, app->app_local->extcap.is_v2) else X(SHA256, sha256, app->app_local->extcap.is_v2) else X(SHA384, sha384, app->app_local->extcap.is_v2) else X(SHA512, sha512, app->app_local->extcap.is_v2) else return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); } else { datalen = indatalen; memcpy (data, indata, indatalen); } #undef X /* Redirect to the AUTH command if asked to. */ if (use_auth) { return do_auth (app, "OPENPGP.3", pincb, pincb_arg, data, datalen, outdata, outdatalen); } /* Show the number of signature done using this key. */ sigcount = get_sig_counter (app); log_info (_("signatures created so far: %lu\n"), sigcount); /* Check CHV if needed. */ if (!app->did_chv1 || app->force_chv1 ) { char *pinvalue; rc = verify_a_chv (app, pincb, pincb_arg, 1, sigcount, &pinvalue); if (rc) return rc; app->did_chv1 = 1; /* For cards with versions < 2 we want to keep CHV1 and CHV2 in sync, thus we verify CHV2 here using the given PIN. Cards with version2 to not have the need for a separate CHV2 and internally use just one. Obviously we can't do that if the pinpad has been used. */ if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2) { rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); if (gpg_err_code (rc) == GPG_ERR_BAD_PIN) rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED); if (rc) { log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc)); xfree (pinvalue); flush_cache_after_error (app); return rc; } app->did_chv2 = 1; } xfree (pinvalue); } if (app->app_local->cardcap.ext_lc_le && app->app_local->keyattr[0].key_type == KEY_TYPE_RSA && app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP) { exmode = 1; /* Use extended length. */ le_value = app->app_local->keyattr[0].rsa.n_bits / 8; } else { exmode = 0; le_value = 0; } rc = iso7816_compute_ds (app->slot, exmode, data, datalen, le_value, outdata, outdatalen); return rc; } /* Compute a digital signature using the INTERNAL AUTHENTICATE command on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. Optionally the id OPENPGP.3 may be given. Note that this function may return the error code GPG_ERR_WRONG_CARD to indicate that the card currently present does not match the one required for the requested action (e.g. the serial number does not match). */ static gpg_error_t do_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { int rc; unsigned char tmp_sn[20]; /* Actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA && indatalen > 101) /* For a 2048 bit key. */ return gpg_error (GPG_ERR_INV_VALUE); if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC) { if (!(app->app_local->keyattr[2].ecc.flags & ECC_FLAG_DJB_TWEAK) && (indatalen == 51 || indatalen == 67 || indatalen == 83)) { const char *p = (const char *)indata + 19; indata = p; indatalen -= 19; } else { const char *p = (const char *)indata + 15; indata = p; indatalen -= 15; } } /* Check whether an OpenPGP card of any version has been requested. */ if (!strcmp (keyidstr, "OPENPGP.3")) ; else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); else { for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, gpg will detect a bogus signature anyway due to the verify-after-signing feature. */ rc = fpr? check_against_given_fingerprint (app, fpr, 3) : 0; if (rc) return rc; rc = verify_chv2 (app, pincb, pincb_arg); if (!rc) { int exmode, le_value; if (app->app_local->cardcap.ext_lc_le && app->app_local->keyattr[2].key_type == KEY_TYPE_RSA && app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP) { exmode = 1; /* Use extended length. */ le_value = app->app_local->keyattr[2].rsa.n_bits / 8; } else { exmode = 0; le_value = 0; } rc = iso7816_internal_authenticate (app->slot, exmode, indata, indatalen, le_value, outdata, outdatalen); } return rc; } static gpg_error_t do_decipher (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { int rc; unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ const char *s; int n; const char *fpr = NULL; int exmode, le_value; unsigned char *fixbuf = NULL; int padind = 0; int fixuplen = 0; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ if (!strcmp (keyidstr, "OPENPGP.2")) ; else if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); else { for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* no fingerprint given: we allow this for now. */ else if (*s == '/') fpr = s + 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); } /* If a fingerprint has been specified check it against the one on the card. This is allows for a meaningful error message in case the key on the card has been replaced but the shadow information known to gpg was not updated. If there is no fingerprint, the decryption won't produce the right plaintext anyway. */ rc = fpr? check_against_given_fingerprint (app, fpr, 2) : 0; if (rc) return rc; rc = verify_chv2 (app, pincb, pincb_arg); if (rc) return rc; if ((indatalen == 16 + 1 || indatalen == 32 + 1) && ((char *)indata)[0] == 0x02) { /* PSO:DECIPHER with symmetric key. */ padind = -1; } else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA) { /* We might encounter a couple of leading zeroes in the cryptogram. Due to internal use of MPIs these leading zeroes are stripped. However the OpenPGP card expects exactly 128 bytes for the cryptogram (for a 1k key). Thus we need to fix it up. We do this for up to 16 leading zero bytes; a cryptogram with more than this is with a very high probability anyway broken. If a signed conversion was used we may also encounter one leading zero followed by the correct length. We fix that as well. */ if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */ fixuplen = 128 - indatalen; else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */ fixuplen = 192 - indatalen; else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */ fixuplen = 256 - indatalen; else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */ fixuplen = 384 - indatalen; else if (indatalen >= (512-16) && indatalen < 512) /* 4096 bit key. */ fixuplen = 512 - indatalen; else if (!*(const char *)indata && (indatalen == 129 || indatalen == 193 || indatalen == 257 || indatalen == 385 || indatalen == 513)) fixuplen = -1; else fixuplen = 0; if (fixuplen > 0) { /* While we have to prepend stuff anyway, we can also include the padding byte here so that iso1816_decipher does not need to do another data mangling. */ fixuplen++; fixbuf = xtrymalloc (fixuplen + indatalen); if (!fixbuf) return gpg_error_from_syserror (); memset (fixbuf, 0, fixuplen); memcpy (fixbuf+fixuplen, indata, indatalen); indata = fixbuf; indatalen = fixuplen + indatalen; padind = -1; /* Already padded. */ } else if (fixuplen < 0) { /* We use the extra leading zero as the padding byte. */ padind = -1; } } else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) { int old_format_len = 0; if ((app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK)) { if (indatalen > 32 && (indatalen % 2)) { /* * Skip the prefix. It may be 0x40 (in new format), or MPI * head of 0x00 (in old format). */ indata = (const char *)indata + 1; indatalen--; } else if (indatalen < 32) { /* * Old format trancated by MPI handling. */ old_format_len = indatalen; indatalen = 32; } } fixuplen = 7; fixbuf = xtrymalloc (fixuplen + indatalen); if (!fixbuf) return gpg_error_from_syserror (); /* Build 'Cipher DO' */ fixbuf[0] = '\xa6'; fixbuf[1] = (char)(indatalen+5); fixbuf[2] = '\x7f'; fixbuf[3] = '\x49'; fixbuf[4] = (char)(indatalen+2); fixbuf[5] = '\x86'; fixbuf[6] = (char)indatalen; if (old_format_len) { memset (fixbuf+fixuplen, 0, 32 - old_format_len); memcpy (fixbuf+fixuplen + 32 - old_format_len, indata, old_format_len); } else { memcpy (fixbuf+fixuplen, indata, indatalen); } indata = fixbuf; indatalen = fixuplen + indatalen; padind = -1; } else return gpg_error (GPG_ERR_INV_VALUE); if (app->app_local->cardcap.ext_lc_le && (indatalen > 254 || (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA && app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP))) { exmode = 1; /* Extended length w/o a limit. */ le_value = app->app_local->keyattr[1].rsa.n_bits / 8; } else if (app->app_local->cardcap.cmd_chaining && indatalen > 254) { exmode = -254; /* Command chaining with max. 254 bytes. */ le_value = 0; } else exmode = le_value = 0; rc = iso7816_decipher (app->slot, exmode, indata, indatalen, le_value, padind, outdata, outdatalen); xfree (fixbuf); if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC) { unsigned char prefix = 0; if (app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK) prefix = 0x40; else if ((*outdatalen % 2) == 0) /* No 0x04 -> x-coordinate only */ prefix = 0x41; if (prefix) { /* Add the prefix */ fixbuf = xtrymalloc (*outdatalen + 1); if (!fixbuf) { xfree (*outdata); return gpg_error_from_syserror (); } fixbuf[0] = prefix; memcpy (fixbuf+1, *outdata, *outdatalen); xfree (*outdata); *outdata = fixbuf; *outdatalen = *outdatalen + 1; } } if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */ && app->app_local->manufacturer == 5 && app->card_version == 0x0200) log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)" " do not work with encryption keys > 2048 bits\n"); *r_info |= APP_DECIPHER_INFO_NOPAD; return rc; } /* Perform a simple verify operation for CHV1 and CHV2, so that further operations won't ask for CHV2 and it is possible to do a cheap check on the PIN: If there is something wrong with the PIN entry system, only the regular CHV will get blocked and not the dangerous CHV3. KEYIDSTR is the usual card's serial number; an optional fingerprint part will be ignored. There is a special mode if the keyidstr is "<serialno>[CHV3]" with the "[CHV3]" being a literal string: The Admin Pin is checked if and only if the retry counter is still at 3. */ static gpg_error_t do_check_pin (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { unsigned char tmp_sn[20]; const char *s; int n; int admin_pin = 0; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); /* Check whether an OpenPGP card of any version has been requested. */ if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; hexdigitp (s); s++, n++) ; if (n != 32) return gpg_error (GPG_ERR_INV_ID); else if (!*s) ; /* No fingerprint given: we allow this for now. */ else if (*s == '/') ; /* We ignore a fingerprint. */ else if (!strcmp (s, "[CHV3]") ) admin_pin = 1; else return gpg_error (GPG_ERR_INV_ID); for (s=keyidstr, n=0; n < 16; s += 2, n++) tmp_sn[n] = xtoi_2 (s); if (app->serialnolen != 16) return gpg_error (GPG_ERR_INV_CARD); if (memcmp (app->serialno, tmp_sn, 16)) return gpg_error (GPG_ERR_WRONG_CARD); /* Yes, there is a race conditions: The user might pull the card right here and we won't notice that. However this is not a problem and the check above is merely for a graceful failure between operations. */ if (admin_pin) { void *relptr; unsigned char *value; size_t valuelen; int count; relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL); if (!relptr || valuelen < 7) { log_error (_("error retrieving CHV status from card\n")); xfree (relptr); return gpg_error (GPG_ERR_CARD); } count = value[6]; xfree (relptr); if (!count) { log_info (_("card is permanently locked!\n")); return gpg_error (GPG_ERR_BAD_PIN); } else if (count < 3) { log_info (_("verification of Admin PIN is currently prohibited " "through this command\n")); return gpg_error (GPG_ERR_GENERAL); } app->did_chv3 = 0; /* Force verification. */ return verify_chv3 (app, pincb, pincb_arg); } else return verify_chv2 (app, pincb, pincb_arg); } /* Show information about card capabilities. */ static void show_caps (struct app_local_s *s) { log_info ("Version-2 ......: %s\n", s->extcap.is_v2? "yes":"no"); log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no"); if (s->extcap.get_challenge) log_printf (" (%u bytes max)", s->extcap.max_get_challenge); log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no"); log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no"); log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no"); log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no"); log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no"); if (s->extcap.sm_supported) log_printf (" (%s)", s->extcap.sm_algo==2? "3DES": (s->extcap.sm_algo==2? "AES-128" : "AES-256")); log_info ("Max-Cert3-Len ..: %u\n", s->extcap.max_certlen_3); log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no"); log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no"); log_info ("Status Indicator: %02X\n", s->status_indicator); log_info ("Symmetric crypto: %s\n", s->extcap.has_decrypt? "yes":"no"); log_info ("Button..........: %s\n", s->extcap.has_button? "yes":"no"); log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no"); log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no"); } /* Parse the historical bytes in BUFFER of BUFLEN and store them in APPLOC. */ static void parse_historical (struct app_local_s *apploc, const unsigned char * buffer, size_t buflen) { /* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */ if (buflen < 4) { log_error ("warning: historical bytes are too short\n"); return; /* Too short. */ } if (*buffer) { log_error ("warning: bad category indicator in historical bytes\n"); return; } /* Skip category indicator. */ buffer++; buflen--; /* Get the status indicator. */ apploc->status_indicator = buffer[buflen-3]; buflen -= 3; /* Parse the compact TLV. */ while (buflen) { unsigned int tag = (*buffer & 0xf0) >> 4; unsigned int len = (*buffer & 0x0f); if (len+1 > buflen) { log_error ("warning: bad Compact-TLV in historical bytes\n"); return; /* Error. */ } buffer++; buflen--; if (tag == 7 && len == 3) { /* Card capabilities. */ apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80); apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40); } buffer += len; buflen -= len; } } /* * Check if the OID in an DER encoding is available by GnuPG/libgcrypt, * and return the curve name. Return NULL if not available. * The constant string is not allocated dynamically, never free it. */ static const char * ecc_curve (unsigned char *buf, size_t buflen) { gcry_mpi_t oid; char *oidstr; const char *result; unsigned char *oidbuf; oidbuf = xtrymalloc (buflen + 1); if (!oidbuf) return NULL; memcpy (oidbuf+1, buf, buflen); oidbuf[0] = buflen; oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8); if (!oid) { xfree (oidbuf); return NULL; } oidstr = openpgp_oid_to_str (oid); gcry_mpi_release (oid); if (!oidstr) return NULL; result = openpgp_oid_to_curve (oidstr, 1); xfree (oidstr); return result; } /* Parse and optionally show the algorithm attributes for KEYNO. KEYNO must be in the range 0..2. */ static void parse_algorithm_attribute (app_t app, int keyno) { unsigned char *buffer; size_t buflen; void *relptr; const char desc[3][5] = {"sign", "encr", "auth"}; assert (keyno >=0 && keyno <= 2); app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA; app->app_local->keyattr[keyno].rsa.n_bits = 0; relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL); if (!relptr) { log_error ("error reading DO 0x%02X\n", 0xc1+keyno); return; } if (buflen < 1) { log_error ("error reading DO 0x%02X\n", 0xc1+keyno); xfree (relptr); return; } if (opt.verbose) log_info ("Key-Attr-%s ..: ", desc[keyno]); if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6)) { app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]); app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]); app->app_local->keyattr[keyno].rsa.format = 0; if (buflen < 6) app->app_local->keyattr[keyno].rsa.format = RSA_STD; else app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD : buffer[5] == 1? RSA_STD_N : buffer[5] == 2? RSA_CRT : buffer[5] == 3? RSA_CRT_N : RSA_UNKNOWN_FMT); if (opt.verbose) log_printf ("RSA, n=%u, e=%u, fmt=%s\n", app->app_local->keyattr[keyno].rsa.n_bits, app->app_local->keyattr[keyno].rsa.e_bits, app->app_local->keyattr[keyno].rsa.format == RSA_STD? "std" : app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n": app->app_local->keyattr[keyno].rsa.format == RSA_CRT? "crt" : app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?"); } else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA || *buffer == PUBKEY_ALGO_EDDSA) { const char *curve; int oidlen = buflen - 1; app->app_local->keyattr[keyno].ecc.flags = 0; if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff) { /* Found "pubkey required"-byte for private key template. */ oidlen--; if (buffer[buflen-1] == 0xff) app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_PUBKEY; } curve = ecc_curve (buffer + 1, oidlen); if (!curve) log_printhex ("Curve with OID not supported: ", buffer+1, buflen-1); else { app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC; app->app_local->keyattr[keyno].ecc.curve = curve; if (*buffer == PUBKEY_ALGO_EDDSA || (*buffer == PUBKEY_ALGO_ECDH && !strcmp (app->app_local->keyattr[keyno].ecc.curve, "Curve25519"))) app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_DJB_TWEAK; if (opt.verbose) log_printf ("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.curve, !(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)? "": keyno==1? " (djb-tweak)": " (eddsa)"); } } else if (opt.verbose) log_printhex ("", buffer, buflen); xfree (relptr); } /* Select the OpenPGP application on the card in SLOT. This function must be used before any other OpenPGP application functions. */ gpg_error_t app_select_openpgp (app_t app) { static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; int slot = app->slot; int rc; unsigned char *buffer; size_t buflen; void *relptr; /* Note that the card can't cope with P2=0xCO, thus we need to pass a special flag value. */ rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001); if (!rc) { unsigned int manufacturer; app->apptype = "OPENPGP"; app->did_chv1 = 0; app->did_chv2 = 0; app->did_chv3 = 0; app->app_local = NULL; /* The OpenPGP card returns the serial number as part of the AID; because we prefer to use OpenPGP serial numbers, we replace a possibly already set one from a EF.GDO with this one. Note, that for current OpenPGP cards, no EF.GDO exists and thus it won't matter at all. */ rc = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen); if (rc) goto leave; if (opt.verbose) { log_info ("AID: "); log_printhex ("", buffer, buflen); } app->card_version = buffer[6] << 8; app->card_version |= buffer[7]; manufacturer = (buffer[8]<<8 | buffer[9]); xfree (app->serialno); app->serialno = buffer; app->serialnolen = buflen; buffer = NULL; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error (gpg_err_code_from_errno (errno)); goto leave; } app->app_local->manufacturer = manufacturer; if (app->card_version >= 0x0200) app->app_local->extcap.is_v2 = 1; /* Read the historical bytes. */ relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL); if (relptr) { if (opt.verbose) { log_info ("Historical Bytes: "); log_printhex ("", buffer, buflen); } parse_historical (app->app_local, buffer, buflen); xfree (relptr); } /* Read the force-chv1 flag. */ relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL); if (!relptr) { log_error (_("can't access %s - invalid OpenPGP card?\n"), "CHV Status Bytes"); goto leave; } app->force_chv1 = (buflen && *buffer == 0); xfree (relptr); /* Read the extended capabilities. */ relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL); if (!relptr) { log_error (_("can't access %s - invalid OpenPGP card?\n"), "Extended Capability Flags" ); goto leave; } if (buflen) { app->app_local->extcap.sm_supported = !!(*buffer & 0x80); app->app_local->extcap.get_challenge = !!(*buffer & 0x40); app->app_local->extcap.key_import = !!(*buffer & 0x20); app->app_local->extcap.change_force_chv = !!(*buffer & 0x10); app->app_local->extcap.private_dos = !!(*buffer & 0x08); app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04); app->app_local->extcap.has_decrypt = !!(*buffer & 0x02); } if (buflen >= 10) { /* Available with v2 cards. */ app->app_local->extcap.sm_algo = buffer[1]; app->app_local->extcap.max_get_challenge = (buffer[2] << 8 | buffer[3]); app->app_local->extcap.max_certlen_3 = (buffer[4] << 8 | buffer[5]); } xfree (relptr); /* Some of the first cards accidentally don't set the CHANGE_FORCE_CHV bit but allow it anyway. */ if (app->card_version <= 0x0100 && manufacturer == 1) app->app_local->extcap.change_force_chv = 1; /* Check optional DO of "General Feature Management" for button. */ relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL); if (relptr) /* It must be: 03 81 01 20 */ app->app_local->extcap.has_button = 1; parse_login_data (app); if (opt.verbose) show_caps (app->app_local); parse_algorithm_attribute (app, 0); parse_algorithm_attribute (app, 1); parse_algorithm_attribute (app, 2); if (opt.verbose > 1) dump_all_do (slot); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; app->fnc.writecert = do_writecert; app->fnc.writekey = do_writekey; app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; app->fnc.auth = do_auth; app->fnc.decipher = do_decipher; app->fnc.change_pin = do_change_pin; app->fnc.check_pin = do_check_pin; } leave: if (rc) do_deinit (app); return rc; } diff --git a/scd/app.c b/scd/app.c index 044bb1db1..ec04b404f 100644 --- a/scd/app.c +++ b/scd/app.c @@ -1,1127 +1,1127 @@ /* app.c - Application selection. * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <npth.h> #include "scdaemon.h" #include "../common/exechelp.h" #include "app-common.h" #include "iso7816.h" #include "apdu.h" #include "../common/tlv.h" static npth_mutex_t app_list_lock; static app_t app_top; static void print_progress_line (void *opaque, const char *what, int pc, int cur, int tot) { ctrl_t ctrl = opaque; char line[100]; if (ctrl) { snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot); send_status_direct (ctrl, "PROGRESS", line); } } /* Lock the reader SLOT. This function shall be used right before calling any of the actual application functions to serialize access to the reader. We do this always even if the reader is not actually used. This allows an actual connection to assume that it never shares a reader (while performing one command). Returns 0 on success; only then the unlock_reader function must be called after returning from the handler. */ static gpg_error_t lock_app (app_t app, ctrl_t ctrl) { if (npth_mutex_lock (&app->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to acquire APP lock for %p: %s\n", app, gpg_strerror (err)); return err; } apdu_set_progress_cb (app->slot, print_progress_line, ctrl); return 0; } /* Release a lock on the reader. See lock_reader(). */ static void unlock_app (app_t app) { apdu_set_progress_cb (app->slot, NULL, NULL); if (npth_mutex_unlock (&app->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to release APP lock for %p: %s\n", app, gpg_strerror (err)); } } /* This function may be called to print information pertaining to the current state of this module to the log. */ void app_dump_state (void) { app_t a; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) log_info ("app_dump_state: app=%p type='%s'\n", a, a->apptype); npth_mutex_unlock (&app_list_lock); } -/* Check wether the application NAME is allowed. This does not mean +/* Check whether the application NAME is allowed. This does not mean we have support for it though. */ static int is_app_allowed (const char *name) { strlist_t l; for (l=opt.disabled_applications; l; l = l->next) if (!strcmp (l->d, name)) return 0; /* no */ return 1; /* yes */ } static gpg_error_t check_conflict (app_t app, const char *name) { if (!app || !name || (app->apptype && !ascii_strcasecmp (app->apptype, name))) return 0; log_info ("application '%s' in use - can't switch\n", app->apptype? app->apptype : "<null>"); return gpg_error (GPG_ERR_CONFLICT); } /* This function is used by the serialno command to check for an application conflict which may appear if the serialno command is used to request a specific application and the connection has already done a select_application. */ gpg_error_t check_application_conflict (const char *name, app_t app) { return check_conflict (app, name); } gpg_error_t app_reset (app_t app, ctrl_t ctrl, int send_reset) { gpg_error_t err = 0; if (send_reset) { int sw; lock_app (app, ctrl); sw = apdu_reset (app->slot); if (sw) err = gpg_error (GPG_ERR_CARD_RESET); app->reset_requested = 1; unlock_app (app); scd_kick_the_loop (); gnupg_sleep (1); } else { ctrl->app_ctx = NULL; release_application (app, 0); } return err; } static gpg_error_t app_new_register (int slot, ctrl_t ctrl, const char *name, int periodical_check_needed) { gpg_error_t err = 0; app_t app = NULL; unsigned char *result = NULL; size_t resultlen; int want_undefined; /* Need to allocate a new one. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_syserror (); log_info ("error allocating context: %s\n", gpg_strerror (err)); return err; } app->slot = slot; app->card_status = (unsigned int)-1; if (npth_mutex_init (&app->lock, NULL)) { err = gpg_error_from_syserror (); log_error ("error initializing mutex: %s\n", gpg_strerror (err)); xfree (app); return err; } err = lock_app (app, ctrl); if (err) { xfree (app); return err; } want_undefined = (name && !strcmp (name, "undefined")); /* Try to read the GDO file first to get a default serial number. We skip this if the undefined application has been requested. */ if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); if (!err) { size_t n; const unsigned char *p; p = find_tlv_unchecked (result, resultlen, 0x5A, &n); if (p) resultlen -= (p-result); if (p && n > resultlen && n == 0x0d && resultlen+1 == n) { /* The object it does not fit into the buffer. This is an invalid encoding (or the buffer is too short. However, I have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ log_info ("enabling BMI testcard workaround\n"); n--; } if (p && n <= resultlen) { /* The GDO file is pretty short, thus we simply reuse it for storing the serial number. */ memmove (result, p, n); app->serialno = result; app->serialnolen = n; err = app_munge_serialno (app); if (err) goto leave; } else xfree (result); result = NULL; } } /* For certain error codes, there is no need to try more. */ if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT || gpg_err_code (err) == GPG_ERR_ENODEV) goto leave; /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; err = 0; } else err = gpg_error (GPG_ERR_NOT_FOUND); if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) err = app_select_nks (app); if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) err = app_select_p15 (app); if (err && is_app_allowed ("geldkarte") && (!name || !strcmp (name, "geldkarte"))) err = app_select_geldkarte (app); if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) err = app_select_dinsig (app); if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm"))) err = app_select_sc_hsm (app); if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: if (err) { if (name) log_info ("can't select application '%s': %s\n", name, gpg_strerror (err)); else log_info ("no supported card application found: %s\n", gpg_strerror (err)); unlock_app (app); xfree (app); return err; } app->periodical_check_needed = periodical_check_needed; npth_mutex_lock (&app_list_lock); app->next = app_top; app_top = app; npth_mutex_unlock (&app_list_lock); unlock_app (app); return 0; } /* If called with NAME as NULL, select the best fitting application and return a context; otherwise select the application with NAME and return a context. Returns an error code and stores NULL at R_APP if no application was found or no card is present. */ gpg_error_t select_application (ctrl_t ctrl, const char *name, app_t *r_app, int scan, const unsigned char *serialno_bin, size_t serialno_bin_len) { gpg_error_t err = 0; app_t a, a_prev = NULL; *r_app = NULL; if (scan || !app_top) { struct dev_list *l; int periodical_check_needed = 0; /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) return err; while (1) { int slot; int periodical_check_needed_this; slot = apdu_open_reader (l, !app_top); if (slot < 0) break; periodical_check_needed_this = apdu_connect (slot); if (periodical_check_needed_this < 0) { /* We close a reader with no card. */ err = gpg_error (GPG_ERR_ENODEV); } else { err = app_new_register (slot, ctrl, name, periodical_check_needed_this); if (periodical_check_needed_this) periodical_check_needed = 1; } if (err) apdu_close_reader (slot); } apdu_dev_list_finish (l); /* If periodical check is needed for new device(s), kick the scdaemon loop. */ if (periodical_check_needed) scd_kick_the_loop (); } npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { lock_app (a, ctrl); if (serialno_bin == NULL) break; if (a->serialnolen == serialno_bin_len && !memcmp (a->serialno, serialno_bin, a->serialnolen)) break; unlock_app (a); a_prev = a; } if (a) { err = check_conflict (a, name); if (!err) { a->ref_count++; *r_app = a; if (a_prev) { a_prev->next = a->next; a->next = app_top; app_top = a; } } unlock_app (a); } else err = gpg_error (GPG_ERR_ENODEV); npth_mutex_unlock (&app_list_lock); return err; } char * get_supported_applications (void) { const char *list[] = { "openpgp", "nks", "p15", "geldkarte", "dinsig", "sc-hsm", /* Note: "undefined" is not listed here because it needs special treatment by the client. */ NULL }; int idx; size_t nbytes; char *buffer, *p; for (nbytes=1, idx=0; list[idx]; idx++) nbytes += strlen (list[idx]) + 1 + 1; buffer = xtrymalloc (nbytes); if (!buffer) return NULL; for (p=buffer, idx=0; list[idx]; idx++) if (is_app_allowed (list[idx])) p = stpcpy (stpcpy (p, list[idx]), ":\n"); *p = 0; return buffer; } /* Deallocate the application. */ static void deallocate_app (app_t app) { app_t a, a_prev = NULL; for (a = app_top; a; a = a->next) if (a == app) { if (a_prev == NULL) app_top = a->next; else a_prev->next = a->next; break; } else a_prev = a; if (app->ref_count) log_error ("trying to release context used yet (%d)\n", app->ref_count); if (app->fnc.deinit) { app->fnc.deinit (app); app->fnc.deinit = NULL; } xfree (app->serialno); unlock_app (app); xfree (app); } /* Free the resources associated with the application APP. APP is allowed to be NULL in which case this is a no-op. Note that we are using reference counting to track the users of the application and actually deferring the deallocation to allow for a later reuse by a new connection. */ void release_application (app_t app, int locked_already) { if (!app) return; /* We don't deallocate app here. Instead, we keep it. This is useful so that a card does not get reset even if only one session is using the card - this way the PIN cache and other cached data are preserved. */ if (!locked_already) lock_app (app, NULL); if (!app->ref_count) log_bug ("trying to release an already released context\n"); --app->ref_count; if (!locked_already) unlock_app (app); } /* The serial number may need some cosmetics. Do it here. This function shall only be called once after a new serial number has been put into APP->serialno. Prefixes we use: FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. FF 7F 00 = No serialno. All other serial number not starting with FF are used as they are. */ gpg_error_t app_munge_serialno (app_t app) { if (app->serialnolen && app->serialno[0] == 0xff) { /* The serial number starts with our special prefix. This requires that we put our default prefix "FF0000" in front. */ unsigned char *p = xtrymalloc (app->serialnolen + 3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\0", 3); memcpy (p+3, app->serialno, app->serialnolen); app->serialnolen += 3; xfree (app->serialno); app->serialno = p; } else if (!app->serialnolen) { unsigned char *p = xtrymalloc (3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\x7f", 3); app->serialnolen = 3; xfree (app->serialno); app->serialno = p; } return 0; } /* Retrieve the serial number of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL. Caller must free SERIAL unless the function returns an error. */ char * app_get_serialno (app_t app) { char *serial; if (!app) return NULL; if (!app->serialnolen) serial = xtrystrdup ("FF7F00"); else serial = bin2hex (app->serialno, app->serialnolen, NULL); return serial; } /* Write out the application specifig status lines for the LEARN command. */ gpg_error_t app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We do not send APPTYPE if only keypairinfo is requested. */ if (app->apptype && !(flags & 1)) send_status_direct (ctrl, "APPTYPE", app->apptype); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.learn_status (app, ctrl, flags); unlock_app (app); return err; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ gpg_error_t app_readcert (app_t app, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen) { gpg_error_t err; if (!app) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readcert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.readcert (app, certid, cert, certlen); unlock_app (app); return err; } /* Read the key with ID KEYID. On success a canonical encoded S-expression with the public key will get stored at PK and its length (for assertions) at PKLEN; the caller must release that buffer. On error NULL will be stored at PK and PKLEN and an error code returned. This function might not be supported by all applications. */ gpg_error_t app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, unsigned char **pk, size_t *pklen) { gpg_error_t err; if (pk) *pk = NULL; if (pklen) *pklen = 0; if (!app || !keyid || !pk || !pklen) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.readkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err= app->fnc.readkey (app, advanced, keyid, pk, pklen); unlock_app (app); return err; } /* Perform a GETATTR operation. */ gpg_error_t app_getattr (app_t app, ctrl_t ctrl, const char *name) { gpg_error_t err; if (!app || !name || !*name) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); return 0; } if (name && !strcmp (name, "SERIALNO")) { char *serial; serial = app_get_serialno (app); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); return 0; } if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_setattr (app_t app, ctrl_t ctrl, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t err; if (!app || !name || !*name || !value) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.setattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); unlock_app (app); return err; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.sign) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.sign (app, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_app (app); if (opt.verbose) log_info ("operation sign result: %s\n", gpg_strerror (err)); return err; } /* Create the signature using the INTERNAL AUTHENTICATE command and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.auth) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.auth (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); unlock_app (app); if (opt.verbose) log_info ("operation auth result: %s\n", gpg_strerror (err)); return err; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { gpg_error_t err; *r_info = 0; if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.decipher) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.decipher (app, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen, r_info); unlock_app (app); if (opt.verbose) log_info ("operation decipher result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITECERT operation. */ gpg_error_t app_writecert (app_t app, ctrl_t ctrl, const char *certidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *data, size_t datalen) { gpg_error_t err; if (!app || !certidstr || !*certidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writecert) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.writecert (app, ctrl, certidstr, pincb, pincb_arg, data, datalen); unlock_app (app); if (opt.verbose) log_info ("operation writecert result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITEKEY operation. */ gpg_error_t app_writekey (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.writekey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.writekey (app, ctrl, keyidstr, flags, pincb, pincb_arg, keydata, keydatalen); unlock_app (app); if (opt.verbose) log_info ("operation writekey result: %s\n", gpg_strerror (err)); return err; } /* Perform a SETATTR operation. */ gpg_error_t app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keynostr || !*keynostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.genkey) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.genkey (app, ctrl, keynostr, flags, createtime, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation genkey result: %s\n", gpg_strerror (err)); return err; } /* Perform a GET CHALLENGE operation. This function is special as it directly accesses the card without any application specific wrapper. */ gpg_error_t app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) { gpg_error_t err; if (!app || !nbytes || !buffer) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); err = lock_app (app, ctrl); if (err) return err; err = iso7816_get_challenge (app->slot, nbytes, buffer); unlock_app (app); return err; } /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, int reset_mode, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !chvnostr || !*chvnostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.change_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); return err; } /* Perform a VERIFY operation without doing anything lese. This may - be used to initialze a the PIN cache for long lasting other + be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!app || !keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (!app->fnc.check_pin) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); err = lock_app (app, ctrl); if (err) return err; err = app->fnc.check_pin (app, keyidstr, pincb, pincb_arg); unlock_app (app); if (opt.verbose) log_info ("operation check_pin result: %s\n", gpg_strerror (err)); return err; } static void report_change (int slot, int old_status, int cur_status) { char *homestr, *envstr; char *fname; char templ[50]; FILE *fp; snprintf (templ, sizeof templ, "reader_%d.status", slot); fname = make_filename (gnupg_homedir (), templ, NULL ); fp = fopen (fname, "w"); if (fp) { fprintf (fp, "%s\n", (cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); fclose (fp); } xfree (fname); homestr = make_filename (gnupg_homedir (), NULL); if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) log_error ("out of core while building environment\n"); else { gpg_error_t err; const char *args[9], *envs[2]; char numbuf1[30], numbuf2[30], numbuf3[30]; envs[0] = envstr; envs[1] = NULL; sprintf (numbuf1, "%d", slot); sprintf (numbuf2, "0x%04X", old_status); sprintf (numbuf3, "0x%04X", cur_status); args[0] = "--reader-port"; args[1] = numbuf1; args[2] = "--old-code"; args[3] = numbuf2; args[4] = "--new-code"; args[5] = numbuf3; args[6] = "--status"; args[7] = ((cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); args[8] = NULL; fname = make_filename (gnupg_homedir (), "scd-event", NULL); err = gnupg_spawn_process_detached (fname, args, envs); if (err && gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("failed to run event handler '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); xfree (envstr); } xfree (homestr); } int scd_update_reader_status_file (void) { app_t a, app_next; int periodical_check_needed = 0; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = app_next) { int sw; unsigned int status; lock_app (a, NULL); app_next = a->next; if (a->reset_requested) status = 0; else { sw = apdu_get_status (a->slot, 0, &status); if (sw == SW_HOST_NO_READER) { /* Most likely the _reader_ has been unplugged. */ status = 0; } else if (sw) { /* Get status failed. Ignore that. */ if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); continue; } } if (a->card_status != status) { report_change (a->slot, a->card_status, status); send_client_notifications (a, status == 0); if (status == 0) { log_debug ("Removal of a card: %d\n", a->slot); apdu_close_reader (a->slot); deallocate_app (a); } else { a->card_status = status; if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); } } else { if (a->periodical_check_needed) periodical_check_needed = 1; unlock_app (a); } } npth_mutex_unlock (&app_list_lock); return periodical_check_needed; } /* 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. */ gpg_error_t initialize_module_command (void) { gpg_error_t err; if (npth_mutex_init (&app_list_lock, NULL)) { err = gpg_error_from_syserror (); log_error ("app: error initializing mutex: %s\n", gpg_strerror (err)); return err; } return apdu_init (); } void app_send_card_list (ctrl_t ctrl) { app_t a; char buf[65]; npth_mutex_lock (&app_list_lock); for (a = app_top; a; a = a->next) { if (DIM (buf) < 2 * a->serialnolen + 1) continue; bin2hex (a->serialno, a->serialnolen, buf); send_status_direct (ctrl, "SERIALNO", buf); } npth_mutex_unlock (&app_list_lock); } diff --git a/scd/atr.c b/scd/atr.c index 9dc79de25..4f5a3b82c 100644 --- a/scd/atr.c +++ b/scd/atr.c @@ -1,290 +1,290 @@ -/* atr.c - ISO 7816 ATR fucntions +/* atr.c - ISO 7816 ATR functions * Copyright (C) 2003, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <gpg-error.h> #include "../common/logging.h" #include "atr.h" static int const fi_table[16] = { 0, 372, 558, 744, 1116,1488, 1860, -1, -1, 512, 768, 1024, 1536, 2048, -1, -1 }; static int const di_table[16] = { -1, 1, 2, 4, 8, 16, -1, -1, 0, -1, -2, -4, -8, -16, -32, -64}; /* Dump the ATR in (BUFFER,BUFLEN) to a human readable format and return that as a malloced buffer. The caller must release this buffer using es_free! On error this function returns NULL and sets ERRNO. */ char * atr_dump (const void *buffer, size_t buflen) { const unsigned char *atr = buffer; size_t atrlen = buflen; estream_t fp; int have_ta, have_tb, have_tc, have_td; int n_historical; int idx, val; unsigned char chksum; char *result; fp = es_fopenmem (0, "rwb,samethread"); if (!fp) return NULL; if (!atrlen) { es_fprintf (fp, "error: empty ATR\n"); goto bailout; } for (idx=0; idx < atrlen ; idx++) es_fprintf (fp, "%s%02X", idx?" ":"", atr[idx]); es_putc ('\n', fp); if (*atr == 0x3b) es_fputs ("Direct convention\n", fp); else if (*atr == 0x3f) es_fputs ("Inverse convention\n", fp); else es_fprintf (fp,"error: invalid TS character 0x%02x\n", *atr); if (!--atrlen) goto bailout; atr++; chksum = *atr; for (idx=1; idx < atrlen-1; idx++) chksum ^= atr[idx]; have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); n_historical = (*atr & 0x0f); es_fprintf (fp, "%d historical characters indicated\n", n_historical); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) es_fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; if (have_ta) { es_fputs ("TA1: F=", fp); val = fi_table[(*atr >> 4) & 0x0f]; if (!val) es_fputs ("internal clock", fp); else if (val == -1) es_fputs ("RFU", fp); else es_fprintf (fp, "%d", val); es_fputs (" D=", fp); val = di_table[*atr & 0x0f]; if (!val) es_fputs ("[impossible value]\n", fp); else if (val == -1) es_fputs ("RFU\n", fp); else if (val < 0 ) es_fprintf (fp, "1/%d\n", val); else es_fprintf (fp, "%d\n", val); if (!--atrlen) goto bailout; atr++; } if (have_tb) { es_fprintf (fp, "TB1: II=%d PI1=%d%s\n", ((*atr >> 5) & 3), (*atr & 0x1f), (*atr & 0x80)? " [high bit not cleared]":""); if (!--atrlen) goto bailout; atr++; } if (have_tc) { if (*atr == 255) es_fputs ("TC1: guard time shortened to 1 etu\n", fp); else es_fprintf (fp, "TC1: (extra guard time) N=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); es_fprintf (fp, "TD1: protocol T%d supported\n", (*atr & 0x0f)); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) es_fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; if (have_ta) { es_fprintf (fp, "TA2: (PTS) %stoggle, %splicit, T=%02X\n", (*atr & 0x80)? "no-":"", (*atr & 0x10)? "im": "ex", (*atr & 0x0f)); if ((*atr & 0x60)) es_fprintf (fp, "note: reserved bits are set (TA2=0x%02X)\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_tb) { es_fprintf (fp, "TB2: PI2=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_tc) { es_fprintf (fp, "TC2: PWI=%d\n", *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); es_fprintf (fp, "TD2: protocol T%d supported\n", *atr & 0x0f); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) es_fputs ("error: ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; for (idx = 3; have_ta || have_tb || have_tc || have_td; idx++) { if (have_ta) { es_fprintf (fp, "TA%d: IFSC=%d\n", idx, *atr); if (!--atrlen) goto bailout; atr++; } if (have_tb) { es_fprintf (fp, "TB%d: BWI=%d CWI=%d\n", idx, (*atr >> 4) & 0x0f, *atr & 0x0f); if (!--atrlen) goto bailout; atr++; } if (have_tc) { es_fprintf (fp, "TC%d: 0x%02X\n", idx, *atr); if (!--atrlen) goto bailout; atr++; } if (have_td) { have_ta = !!(*atr & 0x10); have_tb = !!(*atr & 0x20); have_tc = !!(*atr & 0x40); have_td = !!(*atr & 0x80); es_fprintf (fp, "TD%d: protocol T%d supported\n", idx, *atr & 0x0f); if (have_ta + have_tb + have_tc + have_td + n_historical > atrlen) es_fputs ("error: " "ATR shorter than indicated by format character\n", fp); if (!--atrlen) goto bailout; atr++; } else have_ta = have_tb = have_tc = have_td = 0; } if (n_historical + 1 > atrlen) es_fputs ("error: ATR shorter than required for historical bytes " "and checksum\n", fp); if (n_historical) { es_fputs ("HCH:", fp); for (; n_historical && atrlen ; n_historical--, atrlen--, atr++) es_fprintf (fp, " %02X", *atr); es_putc ('\n', fp); } if (!atrlen) es_fputs ("error: checksum missing\n", fp); else if (*atr == chksum) es_fprintf (fp, "TCK: %02X (good)\n", *atr); else es_fprintf (fp, "TCK: %02X (bad; computed %02X)\n", *atr, chksum); atrlen--; if (atrlen) es_fprintf (fp, "error: %u bytes garbage at end of ATR\n", (unsigned int)atrlen ); bailout: es_putc ('\0', fp); /* We want a string. */ if (es_fclose_snatch (fp, (void**)&result, NULL)) { log_error ("oops: es_fclose_snatch failed: %s\n", strerror (errno)); return NULL; } return result; } diff --git a/scd/command.c b/scd/command.c index 56fdf7489..6bcbce4fc 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1,1983 +1,1983 @@ /* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2007, 2008, 2009, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <unistd.h> #include <signal.h> #ifdef USE_NPTH # include <npth.h> #endif #include "scdaemon.h" #include <assuan.h> #include <ksba.h> #include "app-common.h" #include "iso7816.h" #include "apdu.h" /* Required for apdu_*_reader (). */ #include "atr.h" #ifdef HAVE_LIBUSB #include "ccid-driver.h" #endif #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 /* Maximum allowed size of key data as used in inquiries. */ #define MAXLEN_KEYDATA 4096 /* Maximum allowed total data size for SETDATA. */ #define MAXLEN_SETDATA 4096 /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) #define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local) /* Data used to associate an Assuan context with local server data. This object describes the local properties of one session. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; /* The Assuan context used by this session/server. */ assuan_context_t assuan_ctx; #ifdef HAVE_W32_SYSTEM unsigned long event_signal; /* Or 0 if not used. */ #else int event_signal; /* Or 0 if not used. */ #endif /* True if the card has been removed and a reset is required to continue operation. */ int card_removed; /* If set to true we will be terminate ourself at the end of the this session. */ int stopme; }; /* To keep track of all running sessions, we link all active server contexts and the anchor in this variable. */ static struct server_local_s *session_list; /* If a session has been locked we store a link to its server object in this variable. */ static struct server_local_s *locked_session; /* Convert the STRING into a newly allocated buffer while translating the hex numbers. Stops at the first invalid character. Blanks and colons are allowed to separate the hex digits. Returns NULL on error or a newly malloced buffer and its length in LENGTH. */ static unsigned char * hex_to_buffer (const char *string, size_t *r_length) { unsigned char *buffer; const char *s; size_t n; buffer = xtrymalloc (strlen (string)+1); if (!buffer) return NULL; for (s=string, n=0; *s; s++) { if (spacep (s) || *s == ':') continue; if (hexdigitp (s) && hexdigitp (s+1)) { buffer[n++] = xtoi_2 (s); s++; } else break; } *r_length = n; return buffer; } /* Reset the card and free the application context. With SEND_RESET set to true actually send a RESET to the reader; this is the normal way of calling the function. */ static void do_reset (ctrl_t ctrl, int send_reset) { app_t app = ctrl->app_ctx; if (app) app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset); /* If we hold a lock, unlock now. */ if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESET\n"); } } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; do_reset (ctrl, 1); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "event-signal")) { /* A value of 0 is allowed to reset the event signal. */ #ifdef HAVE_W32_SYSTEM if (!*value) return gpg_error (GPG_ERR_ASS_PARAMETER); ctrl->server_local->event_signal = strtoul (value, NULL, 16); #else int i = *value? atoi (value) : -1; if (i < 0) return gpg_error (GPG_ERR_ASS_PARAMETER); ctrl->server_local->event_signal = i; #endif } return 0; } /* If the card has not yet been opened, do it. */ static gpg_error_t open_card (ctrl_t ctrl) { /* If we ever got a card not present error code, return that. Only the SERIALNO command and a reset are able to clear from that state. */ if (ctrl->server_local->card_removed) return gpg_error (GPG_ERR_CARD_REMOVED); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if (ctrl->app_ctx) return 0; return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0); } /* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */ static gpg_error_t open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno) { gpg_error_t err; unsigned char *serialno_bin = NULL; size_t serialno_bin_len = 0; app_t app = ctrl->app_ctx; /* If we are already initialized for one specific application we need to check that the client didn't requested a specific application different from the one in use before we continue. */ if (apptype && ctrl->app_ctx) return check_application_conflict (apptype, ctrl->app_ctx); /* Re-scan USB devices. Release APP, before the scan. */ ctrl->app_ctx = NULL; release_application (app, 0); if (serialno) serialno_bin = hex_to_buffer (serialno, &serialno_bin_len); err = select_application (ctrl, apptype, &ctrl->app_ctx, 1, serialno_bin, serialno_bin_len); xfree (serialno_bin); return err; } static const char hlp_serialno[] = "SERIALNO [--demand=<serialno>] [<apptype>]\n" "\n" "Return the serial number of the card using a status response. This\n" "function should be used to check for the presence of a card.\n" "\n" "If --demand is given, an application on the card with SERIALNO is\n" "selected and an error is returned if no such card available.\n" "\n" "If APPTYPE is given, an application of that type is selected and an\n" "error is returned if the application is not supported or available.\n" "The default is to auto-select the application using a hardwired\n" "preference system. Note, that a future extension to this function\n" "may enable specifying a list and order of applications to try.\n" "\n" "This function is special in that it can be used to reset the card.\n" "Most other functions will return an error when a card change has\n" "been detected and the use of this function is therefore required.\n" "\n" "Background: We want to keep the client clear of handling card\n" "changes between operations; i.e. the client can assume that all\n" "operations are done on the same card unless he calls this function."; static gpg_error_t cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); struct server_local_s *sl; int rc = 0; char *serial; const char *demand; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((demand = has_option_name (line, "--demand"))) { if (*demand != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); line = (char *)++demand; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; } else demand = NULL; /* Clear the remove flag so that the open_card is able to reread it. */ if (ctrl->server_local->card_removed) ctrl->server_local->card_removed = 0; if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand))) { ctrl->server_local->card_removed = 1; return rc; } /* Success, clear the card_removed flag for all sessions. */ for (sl=session_list; sl; sl = sl->next_session) { ctrl_t c = sl->ctrl_backlink; if (c != ctrl) c->server_local->card_removed = 0; } serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); xfree (serial); return rc; } static const char hlp_learn[] = "LEARN [--force] [--keypairinfo]\n" "\n" "Learn all useful information of the currently inserted card. When\n" "used without the force options, the command might do an INQUIRE\n" "like this:\n" "\n" " INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n" "\n" "The client should just send an \"END\" if the processing should go on\n" "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" "this:\n" "\n" " S APPTYPE <apptype>\n" "\n" "This returns the type of the application, currently the strings:\n" "\n" " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" "\n" " S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id>\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" "about all certificates stored on the card is also returned:\n" "\n" " S CERTINFO <certtype> <hexstring_with_id>\n" "\n" "Where CERTTYPE is a number indicating the type of certificate:\n" " 0 := Unknown\n" " 100 := Regular X.509 cert\n" " 101 := Trusted X.509 cert\n" " 102 := Useful X.509 cert\n" " 110 := Root CA cert in a special format (e.g. DINSIG)\n" " 111 := Root CA cert as standard X509 cert.\n" "\n" "For certain cards, more information will be returned:\n" "\n" " S KEY-FPR <no> <hexstring>\n" "\n" "For OpenPGP cards this returns the stored fingerprints of the\n" "keys. This can be used check whether a key is available on the\n" "card. NO may be 1, 2 or 3.\n" "\n" " S CA-FPR <no> <hexstring>\n" "\n" "Similar to above, these are the fingerprints of keys assumed to be\n" "ultimately trusted.\n" "\n" " S DISP-NAME <name_of_card_holder>\n" "\n" "The name of the card holder as stored on the card; percent\n" "escaping takes place, spaces are encoded as '+'\n" "\n" " S PUBKEY-URL <url>\n" "\n" "The URL to be used for locating the entire public key.\n" " \n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; int only_keypairinfo = has_option (line, "--keypairinfo"); if ((rc = open_card (ctrl))) return rc; /* Unless the force option is used we try a shortcut by identifying the card using a serial number and inquiring the client with that. The client may choose to cancel the operation if he already knows about this card */ if (!only_keypairinfo) { const char *reader; char *serial; app_t app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); reader = apdu_get_reader_name (app->slot); if (!reader) return out_of_core (); send_status_direct (ctrl, "READER", reader); /* No need to free the string of READER. */ serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); if (rc < 0) { xfree (serial); return out_of_core (); } if (!has_option (line, "--force")) { char *command; rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial); if (rc < 0) { xfree (serial); return out_of_core (); } rc = assuan_inquire (ctx, command, NULL, NULL, 0); xfree (command); if (rc) { if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED) log_error ("inquire KNOWNCARDP failed: %s\n", gpg_strerror (rc)); xfree (serial); return rc; } - /* Not canceled, so we have to proceeed. */ + /* Not canceled, so we have to proceed. */ } xfree (serial); } /* Let the application print out its collection of useful status information. */ if (!rc) rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo); return rc; } static const char hlp_readcert[] = "READCERT <hexified_certid>|<keyid>\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_readcert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert; size_t ncert; if ((rc = open_card (ctrl))) return rc; line = xstrdup (line); /* Need a copy of the line. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); xfree (line); line = NULL; if (!rc) { rc = assuan_send_data (ctx, cert, ncert); xfree (cert); if (rc) return rc; } return rc; } static const char hlp_readkey[] = "READKEY [--advanced] <keyid>\n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression.\n" "In --advanced mode it returns the S-expression in advanced format.\n" "\n" "Note that this function may even be used on a locked card."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int advanced = 0; unsigned char *cert = NULL; size_t ncert, n; ksba_cert_t kc = NULL; ksba_sexp_t p; unsigned char *pk; size_t pklen; if ((rc = open_card (ctrl))) return rc; if (has_option (line, "--advanced")) advanced = 1; line = skip_options (line); line = xstrdup (line); /* Need a copy of the line. */ /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); if (!rc) { /* Yeah, got that key - send it back. */ rc = assuan_send_data (ctx, pk, pklen); xfree (pk); xfree (line); line = NULL; goto leave; } if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION) log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); else { rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); } xfree (line); line = NULL; if (rc) goto leave; rc = ksba_cert_new (&kc); if (rc) goto leave; rc = ksba_cert_init_from_mem (kc, cert, ncert); if (rc) { log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); goto leave; } p = ksba_cert_get_public_key (kc); if (!p) { rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } n = gcry_sexp_canon_len (p, 0, NULL, NULL); rc = assuan_send_data (ctx, p, n); xfree (p); leave: ksba_cert_release (kc); xfree (cert); return rc; } static const char hlp_setdata[] = "SETDATA [--append] <hexstring>\n" "\n" "The client should use this command to tell us the data he want to sign.\n" "With the option --append, the data is appended to the data set by a\n" "previous SETDATA command."; static gpg_error_t cmd_setdata (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int append; int n, i, off; char *p; unsigned char *buf; append = (ctrl->in_data.value && has_option (line, "--append")); line = skip_options (line); if (locked_session && locked_session != ctrl->server_local) return gpg_error (GPG_ERR_LOCKED); /* Parse the hexstring. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if (!n) return set_error (GPG_ERR_ASS_PARAMETER, "no data given"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); n /= 2; if (append) { if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA) return set_error (GPG_ERR_TOO_LARGE, "limit on total size of data reached"); buf = xtrymalloc (ctrl->in_data.valuelen + n); } else buf = xtrymalloc (n); if (!buf) return out_of_core (); if (append) { memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen); off = ctrl->in_data.valuelen; } else off = 0; for (p=line, i=0; i < n; p += 2, i++) buf[off+i] = xtoi_2 (p); xfree (ctrl->in_data.value); ctrl->in_data.value = buf; ctrl->in_data.valuelen = off+n; return 0; } static gpg_error_t pin_cb (void *opaque, const char *info, char **retstr) { assuan_context_t ctx = opaque; char *command; int rc; unsigned char *value; size_t valuelen; if (!retstr) { /* We prompt for pinpad entry. To make sure that the popup has been show we use an inquire and not just a status message. We ignore any value returned. */ if (info) { log_debug ("prompting for pinpad entry '%s'\n", info); rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); xfree (command); } else { log_debug ("dismiss pinpad entry prompt\n"); rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT", &value, &valuelen, MAXLEN_PIN); } if (!rc) xfree (value); return rc; } *retstr = NULL; log_debug ("asking for PIN '%s'\n", info); rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); /* Fixme: Write an inquire function which returns the result in secure memory and check all further handling of the PIN. */ rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); xfree (command); if (rc) return rc; if (!valuelen || value[valuelen-1]) { /* We require that the returned value is an UTF-8 string */ xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } *retstr = (char*)value; return 0; } static const char hlp_pksign[] = "PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] <hexified_id>\n" "\n" "The --hash option is optional; the default is SHA1."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; int hash_algo; if (has_option (line, "--hash=rmd160")) hash_algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=sha1")) hash_algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) hash_algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) hash_algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) hash_algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) hash_algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=md5")) hash_algo = GCRY_MD_MD5; else if (!strstr (line, "--")) hash_algo = GCRY_MD_SHA1; else return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); line = skip_options (line); if ((rc = open_card (ctrl))) return rc; /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_sign (ctrl->app_ctx, ctrl, keyidstr, hash_algo, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkauth[] = "PKAUTH <hexified_id>"; static gpg_error_t cmd_pkauth (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_auth failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkdecrypt[] = "PKDECRYPT <hexified_id>"; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; unsigned int infoflags; if ((rc = open_card (ctrl))) return rc; keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen, &infoflags); xfree (keyidstr); if (rc) { log_error ("app_decipher failed: %s\n", gpg_strerror (rc)); } else { /* If the card driver told us that there is no padding, send a status line. If there is a padding it is assumed that the caller knows what padding is used. It would have been better to always send that information but for backward compatibility we can't do that. */ if ((infoflags & APP_DECIPHER_INFO_NOPAD)) send_status_direct (ctrl, "PADDING", "0"); rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_getattr[] = "GETATTR <name>\n" "\n" "This command is used to retrieve data from a smartcard. The\n" "allowed names depend on the currently selected smartcard\n" "application. NAME must be percent and '+' escaped. The value is\n" "returned through status message, see the LEARN command for details.\n" "\n" "However, the current implementation assumes that Name is not escaped;\n" "this works as long as no one uses arbitrary escaping. \n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_getattr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; const char *keyword; if ((rc = open_card (ctrl))) return rc; keyword = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* (We ignore any garbage for now.) */ /* FIXME: Applications should not return sensitive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); return rc; } static const char hlp_setattr[] = "SETATTR <name> <value> \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" "application. NAME and VALUE must be percent and '+' escaped.\n" "\n" "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; if ((rc = open_card (ctrl))) return rc; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ line = linebuf = xtrystrdup (orig_line); if (!line) return out_of_core (); keyword = line; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; if (*line) *line++ = 0; while (spacep (line)) line++; nbytes = percent_plus_unescape_inplace (line, 0); rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, (const unsigned char*)line, nbytes); xfree (linebuf); return rc; } static const char hlp_writecert[] = "WRITECERT <hexified_certid>\n" "\n" "This command is used to store a certifciate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" "\n" "In almost all cases a PIN will be requested. See the related\n" "writecert function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *certid; unsigned char *certdata; size_t certdatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no certid given"); certid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); certid = xtrystrdup (certid); if (!certid) return out_of_core (); /* Now get the actual keydata. */ rc = assuan_inquire (ctx, "CERTDATA", &certdata, &certdatalen, MAXLEN_CERTDATA); if (rc) { xfree (certid); return rc; } /* Write the certificate to the card. */ rc = app_writecert (ctrl->app_ctx, ctrl, certid, pin_cb, ctx, certdata, certdatalen); xfree (certid); xfree (certdata); return rc; } static const char hlp_writekey[] = "WRITEKEY [--force] <keyid> \n" "\n" "This command is used to store a secret key on a smartcard. The\n" "allowed keyids depend on the currently selected smartcard\n" "application. The actual keydata is requested using the inquiry\n" "\"KEYDATA\" and need to be provided without any protection. With\n" "--force set an existing key under this KEYID will get overwritten.\n" "The keydata is expected to be the usual canonical encoded\n" "S-expression.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "writekey function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writekey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyid; int force = has_option (line, "--force"); unsigned char *keydata; size_t keydatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given"); keyid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyid = xtrystrdup (keyid); if (!keyid) return out_of_core (); /* Now get the actual keydata. */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (rc) { xfree (keyid); return rc; } /* Write the key to the card. */ rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0, pin_cb, ctx, keydata, keydatalen); xfree (keyid); xfree (keydata); return rc; } static const char hlp_genkey[] = "GENKEY [--force] [--timestamp=<isodate>] <no>\n" "\n" "Generate a key on-card identified by NO, which is application\n" "specific. Return values are application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR <hexstring>\n" " S KEY-CREATED-AT <seconds_since_epoch>\n" " S KEY-DATA [-|p|n] <hexdata>\n" "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" " by the next KEY-DATA.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" "part of the hashed key material for the fingerprint.\n" "\n" "If --timestamp is given an OpenPGP key will be created using this\n" "value. The value needs to be in ISO Format; e.g.\n" "\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n" "\n" "The public part of the key can also later be retrieved using the\n" "READKEY command."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyno; int force; const char *s; time_t timestamp; force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); timestamp = isotime2epoch (s+1); if (timestamp < 1) return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); } else timestamp = 0; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); keyno = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyno = xtrystrdup (keyno); if (!keyno) return out_of_core (); rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, timestamp, pin_cb, ctx); xfree (keyno); return rc; } static const char hlp_random[] = "RANDOM <nbytes>\n" "\n" "Get NBYTES of random from the card and send them back as data.\n" "This usually involves EEPROM write on the card and thus excessive\n" "use of this command may destroy the card.\n" "\n" "Note, that this function may be even be used on a locked card."; static gpg_error_t cmd_random (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; size_t nbytes; unsigned char *buffer; if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "number of requested bytes missing"); nbytes = strtoul (line, NULL, 0); if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); buffer = xtrymalloc (nbytes); if (!buffer) return out_of_core (); rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer); if (!rc) { rc = assuan_send_data (ctx, buffer, nbytes); xfree (buffer); return rc; /* that is already an assuan error code */ } xfree (buffer); return rc; } static const char hlp_passwd[] = "PASSWD [--reset] [--nullpin] <chvno>\n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" "used for TCOS cards to set the initial PIN. The format of CHVNO\n" "depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *chvnostr; unsigned int flags = 0; if (has_option (line, "--reset")) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given"); chvnostr = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); chvnostr = xtrystrdup (chvnostr); if (!chvnostr) return out_of_core (); rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx); if (rc) log_error ("command passwd failed: %s\n", gpg_strerror (rc)); xfree (chvnostr); return rc; } static const char hlp_checkpin[] = "CHECKPIN <idstr>\n" "\n" "Perform a VERIFY operation without doing anything else. This may\n" "be used to initialize a the PIN cache earlier to long lasting\n" "operations. Its use is highly application dependent.\n" "\n" "For OpenPGP:\n" "\n" " Perform a simple verify operation for CHV1 and CHV2, so that\n" " further operations won't ask for CHV2 and it is possible to do a\n" " cheap check on the PIN: If there is something wrong with the PIN\n" " entry system, only the regular CHV will get blocked and not the\n" " dangerous CHV3. IDSTR is the usual card's serial number in hex\n" " notation; an optional fingerprint part will get ignored. There\n" " is however a special mode if the IDSTR is sffixed with the\n" " literal string \"[CHV3]\": In this case the Admin PIN is checked\n" " if and only if the retry counter is still at 3.\n" "\n" "For Netkey:\n" "\n" " Any of the valid PIN Ids may be used. These are the strings:\n" "\n" " PW1.CH - Global password 1\n" " PW2.CH - Global password 2\n" " PW1.CH.SIG - SigG password 1\n" " PW2.CH.SIG - SigG password 2\n" "\n" " For a definitive list, see the implementation in app-nks.c.\n" " Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n" " 3.0 they are technically alternative PINs used to mutally\n" " unblock each other."; static gpg_error_t cmd_checkpin (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *idstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid. */ idstr = xtrystrdup (line); if (!idstr) return out_of_core (); rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx); xfree (idstr); if (rc) log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_lock[] = "LOCK [--wait]\n" "\n" "Grant exclusive card access to this session. Note that there is\n" "no lock counter used and a second lock from the same session will\n" "be ignored. A single unlock (or RESET) unlocks the session.\n" "Return GPG_ERR_LOCKED if another session has locked the reader.\n" "\n" "If the option --wait is given the command will wait until a\n" "lock has been released."; static gpg_error_t cmd_lock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; retry: if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); } else locked_session = ctrl->server_local; #ifdef USE_NPTH if (rc && has_option (line, "--wait")) { rc = 0; npth_sleep (1); /* Better implement an event mechanism. However, for card operations this should be sufficient. */ /* FIXME: Need to check that the connection is still alive. This can be done by issuing status messages. */ goto retry; } #endif /*USE_NPTH*/ if (rc) log_error ("cmd_lock failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_unlock[] = "UNLOCK\n" "\n" "Release exclusive card access."; static gpg_error_t cmd_unlock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; (void)line; if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); else locked_session = NULL; } else rc = gpg_error (GPG_ERR_NOT_LOCKED); if (rc) log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_getinfo[] = "GETINFO <what>\n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " connections - Return number of active connections.\n" " status - Return the status of the current reader (in the future,\n" " may also return the status of all readers). The status\n" " is a list of one-character flags. The following flags\n" " are currently defined:\n" " 'u' Usable card present.\n" " 'r' Card removed. A reset is necessary.\n" " These flags are exclusive.\n" " reader_list - Return a list of detected card readers. Does\n" " currently only work with the internal CCID driver.\n" " deny_admin - Returns OK if admin commands are not allowed or\n" " GPG_ERR_GENERAL if admin commands are allowed.\n" " app_list - Return a list of supported applications. One\n" " application per line, fields delimited by colons,\n" " first field is the name.\n" " card_list - Return a list of serial numbers of active cards,\n" " using a status response."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = scd_get_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "status")) { ctrl_t ctrl = assuan_get_pointer (ctx); char flag; if (open_card (ctrl)) flag = 'r'; else flag = 'u'; rc = assuan_send_data (ctx, &flag, 1); } else if (!strcmp (line, "reader_list")) { #ifdef HAVE_LIBUSB char *s = ccid_get_reader_list (); #else char *s = NULL; #endif if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); xfree (s); } else if (!strcmp (line, "deny_admin")) rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0; else if (!strcmp (line, "app_list")) { char *s = get_supported_applications (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = 0; xfree (s); } else if (!strcmp (line, "card_list")) { ctrl_t ctrl = assuan_get_pointer (ctx); app_send_card_list (ctrl); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_restart[] = "RESTART\n" "\n" "Restart the current connection; this is a kind of warm reset. It\n" "deletes the context used by this connection but does not send a\n" "RESET to the card. Thus the card itself won't get reset. \n" "\n" "This is used by gpg-agent to reuse a primary pipe connection and\n" "may be used by clients to backup from a conflict in the serial\n" "command; i.e. to select another application."; static gpg_error_t cmd_restart (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app = ctrl->app_ctx; (void)line; if (app) { ctrl->app_ctx = NULL; release_application (app, 0); } if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESTART\n"); } return 0; } static const char hlp_disconnect[] = "DISCONNECT\n" "\n" "Disconnect the card if the backend supports a disconnect operation."; static gpg_error_t cmd_disconnect (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); apdu_disconnect (ctrl->app_ctx->slot); return 0; } static const char hlp_apdu[] = "APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n" "\n" "Send an APDU to the current reader. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU. If HEXSTRING is not given no\n" "commands are set to the card but the command will implictly check\n" "whether the card is ready for use. \n" "\n" "Using the option \"--atr\" returns the ATR of the card as a status\n" "message before any data like this:\n" " S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n" "\n" "Using the option --more handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used\n" "(currently 4096)."; static gpg_error_t cmd_apdu (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app; int rc; unsigned char *apdu; size_t apdulen; int with_atr; int handle_more; const char *s; size_t exlen; if (has_option (line, "--dump-atr")) with_atr = 2; else with_atr = has_option (line, "--atr"); handle_more = has_option (line, "--more"); if ((s=has_option_name (line, "--exlen"))) { if (*s == '=') exlen = strtoul (s+1, NULL, 0); else exlen = 4096; } else exlen = 0; line = skip_options (line); if ((rc = open_card (ctrl))) return rc; app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); if (with_atr) { unsigned char *atr; size_t atrlen; char hexbuf[400]; atr = apdu_get_atr (app->slot, &atrlen); if (!atr || atrlen > sizeof hexbuf - 2 ) { rc = gpg_error (GPG_ERR_INV_CARD); goto leave; } if (with_atr == 2) { char *string, *p, *pend; string = atr_dump (atr, atrlen); if (string) { for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1) { rc = assuan_send_data (ctx, p, pend - p + 1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (!rc && *p) rc = assuan_send_data (ctx, p, strlen (p)); es_free (string); if (rc) goto leave; } } else { bin2hex (atr, atrlen, hexbuf); send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0); } xfree (atr); } apdu = hex_to_buffer (line, &apdulen); if (!apdu) { rc = gpg_error_from_syserror (); goto leave; } if (apdulen) { unsigned char *result = NULL; size_t resultlen; rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else { rc = assuan_send_data (ctx, result, resultlen); xfree (result); } } xfree (apdu); leave: return rc; } static const char hlp_killscd[] = "KILLSCD\n" "\n" "Commit suicide."; static gpg_error_t cmd_killscd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SERIALNO", cmd_serialno, hlp_serialno }, { "LEARN", cmd_learn, hlp_learn }, { "READCERT", cmd_readcert, hlp_readcert }, { "READKEY", cmd_readkey, hlp_readkey }, { "SETDATA", cmd_setdata, hlp_setdata }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKAUTH", cmd_pkauth, hlp_pkauth }, { "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "GETATTR", cmd_getattr, hlp_getattr }, { "SETATTR", cmd_setattr, hlp_setattr }, { "WRITECERT", cmd_writecert,hlp_writecert }, { "WRITEKEY", cmd_writekey, hlp_writekey }, { "GENKEY", cmd_genkey, hlp_genkey }, { "RANDOM", cmd_random, hlp_random }, { "PASSWD", cmd_passwd, hlp_passwd }, { "CHECKPIN", cmd_checkpin, hlp_checkpin }, { "LOCK", cmd_lock, hlp_lock }, { "UNLOCK", cmd_unlock, hlp_unlock }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "RESTART", cmd_restart, hlp_restart }, { "DISCONNECT", cmd_disconnect,hlp_disconnect }, { "APDU", cmd_apdu, hlp_apdu }, { "KILLSCD", cmd_killscd, hlp_killscd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If FD is given as -1 this is simple pipe server, otherwise it is a regular server. Returns true if there are no more active asessions. */ int scd_command_handler (ctrl_t ctrl, int fd) { int rc; assuan_context_t ctx = NULL; int stopme; rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); scd_exit (2); } if (fd == -1) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, INT2FD(fd), ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); scd_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); scd_exit (2); } assuan_set_pointer (ctx, ctrl); /* Allocate and initialize the server object. Put it into the list of active sessions. */ ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->next_session = session_list; session_list = ctrl->server_local; ctrl->server_local->ctrl_backlink = ctrl; ctrl->server_local->assuan_ctx = ctx; /* Command processing loop. */ for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Cleanup. We don't send an explicit reset to the card. */ do_reset (ctrl, 0); /* Release the server object. */ if (session_list == ctrl->server_local) session_list = ctrl->server_local->next_session; else { struct server_local_s *sl; for (sl=session_list; sl->next_session; sl = sl->next_session) if (sl->next_session == ctrl->server_local) break; if (!sl->next_session) BUG (); sl->next_session = ctrl->server_local->next_session; } stopme = ctrl->server_local->stopme; xfree (ctrl->server_local); ctrl->server_local = NULL; /* Release the Assuan context. */ assuan_release (ctx); if (stopme) scd_exit (0); /* If there are no more sessions return true. */ return !session_list; } /* Send a line with status information via assuan and escape all given buffers. The variable elements are pairs of (char *, size_t), terminated with a (NULL, 0). */ void send_status_info (ctrl_t ctrl, const char *keyword, ...) { va_list arg_ptr; const unsigned char *value; size_t valuelen; char buf[950], *p; size_t n; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); p = buf; n = 0; while ( (value = va_arg (arg_ptr, const unsigned char *)) ) { valuelen = va_arg (arg_ptr, size_t); if (!valuelen) continue; /* empty buffer */ if (n) { *p++ = ' '; n++; } for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) { if (*value == '+' || *value == '\"' || *value == '%' || *value < ' ') { sprintf (p, "%%%02X", *value); p += 3; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } } *p = 0; assuan_write_status (ctx, keyword, buf); va_end (arg_ptr); } /* Send a ready formatted status line via assuan. */ void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (strchr (args, '\n')) log_error ("error: LF detected in status line - not sending\n"); else assuan_write_status (ctx, keyword, args); } /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) { struct { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif } killed[50]; int killidx = 0; int kidx; struct server_local_s *sl; for (sl=session_list; sl; sl = sl->next_session) if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app) { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif if (removal) { sl->ctrl_backlink->app_ctx = NULL; sl->card_removed = 1; release_application (app, 1); } if (!sl->event_signal || !sl->assuan_ctx) continue; pid = assuan_get_pid (sl->assuan_ctx); #ifdef HAVE_W32_SYSTEM handle = (void *)sl->event_signal; for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].handle == handle) break; if (kidx < killidx) log_info ("event %lx (%p) already triggered for client %d\n", sl->event_signal, handle, (int)pid); else { log_info ("triggering event %lx (%p) for client %d\n", sl->event_signal, handle, (int)pid); if (!SetEvent (handle)) log_error ("SetEvent(%lx) failed: %s\n", sl->event_signal, w32_strerror (-1)); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].handle = handle; killidx++; } } #else /*!HAVE_W32_SYSTEM*/ signo = sl->event_signal; if (pid != (pid_t)(-1) && pid && signo > 0) { for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].signo == signo) break; if (kidx < killidx) log_info ("signal %d already sent to client %d\n", signo, (int)pid); else { log_info ("sending signal %d to client %d\n", signo, (int)pid); kill (pid, signo); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].signo = signo; killidx++; } } } #endif /*!HAVE_W32_SYSTEM*/ } } diff --git a/scd/iso7816.c b/scd/iso7816.c index d146bd00a..081b0808c 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -1,814 +1,814 @@ /* iso7816.c - ISO 7816 commands * Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #if defined(GNUPG_SCD_MAIN_HEADER) #include GNUPG_SCD_MAIN_HEADER #elif GNUPG_MAJOR_VERSION == 1 /* This is used with GnuPG version < 1.9. The code has been source copied from the current GnuPG >= 1.9 and is maintained over there. */ #include "options.h" #include "errors.h" #include "memory.h" #include "../common/util.h" #include "../common/i18n.h" #else /* GNUPG_MAJOR_VERSION != 1 */ #include "scdaemon.h" #endif /* GNUPG_MAJOR_VERSION != 1 */ #include "iso7816.h" #include "apdu.h" #define CMD_SELECT_FILE 0xA4 #define CMD_VERIFY ISO7816_VERIFY #define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA #define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER #define CMD_GET_DATA 0xCA #define CMD_PUT_DATA 0xDA #define CMD_MSE 0x22 #define CMD_PSO 0x2A #define CMD_INTERNAL_AUTHENTICATE 0x88 #define CMD_GENERATE_KEYPAIR 0x47 #define CMD_GET_CHALLENGE 0x84 #define CMD_READ_BINARY 0xB0 #define CMD_READ_RECORD 0xB2 static gpg_error_t map_sw (int sw) { gpg_err_code_t ec; switch (sw) { case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break; case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break; case SW_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break; case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break; case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break; case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break; case SW_EXACT_LENGTH: ec = GPG_ERR_INV_VALUE; break; case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break; case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break; case SW_SUCCESS: ec = 0; break; case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break; case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break; case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break; case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break; case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break; case SW_HOST_NO_CARD: ec = GPG_ERR_CARD_NOT_PRESENT; break; case SW_HOST_CARD_INACTIVE: ec = GPG_ERR_CARD_RESET; break; case SW_HOST_CARD_IO_ERROR: ec = GPG_ERR_EIO; break; case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break; case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break; case SW_HOST_ABORTED: ec = GPG_ERR_CANCELED; break; case SW_HOST_NO_PINPAD: ec = GPG_ERR_NOT_SUPPORTED; break; default: if ((sw & 0x010000)) ec = GPG_ERR_GENERAL; /* Should not happen. */ else if ((sw & 0xff00) == SW_MORE_DATA) ec = 0; /* This should actually never been seen here. */ else ec = GPG_ERR_CARD; } return gpg_error (ec); } /* Map a status word from the APDU layer to a gpg-error code. */ gpg_error_t iso7816_map_sw (int sw) { /* All APDU functions should return 0x9000 on success but for historical reasons of the implementation some return 0 to indicate success. We allow for that here. */ return sw? map_sw (sw) : 0; } /* This function is specialized version of the SELECT FILE command. SLOT is the card and reader as created for example by apdu_open_reader (), AID is a buffer of size AIDLEN holding the requested application ID. The function can't be used to enumerate AIDs and won't return the AID on success. The return value is 0 for okay or a GPG error code. Note that ISO error codes are internally mapped. Bit 0 of FLAGS should be set if the card does not understand P2=0xC0. */ gpg_error_t iso7816_select_application (int slot, const char *aid, size_t aidlen, unsigned int flags) { int sw; sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4, (flags&1)? 0 :0x0c, aidlen, aid); return map_sw (sw); } gpg_error_t iso7816_select_file (int slot, int tag, int is_dir) { int sw, p0, p1; unsigned char tagbuf[2]; tagbuf[0] = (tag >> 8) & 0xff; tagbuf[1] = tag & 0xff; p0 = (tag == 0x3F00)? 0: is_dir? 1:2; p1 = 0x0c; /* No FC return. */ sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, 2, (char*)tagbuf ); return map_sw (sw); } /* Do a select file command with a direct path. */ gpg_error_t iso7816_select_path (int slot, const unsigned short *path, size_t pathlen) { int sw, p0, p1; unsigned char buffer[100]; int buflen; if (pathlen/2 >= sizeof buffer) return gpg_error (GPG_ERR_TOO_LARGE); for (buflen = 0; pathlen; pathlen--, path++) { buffer[buflen++] = (*path >> 8); buffer[buflen++] = *path; } p0 = 0x08; p1 = 0x0c; /* No FC return. */ sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, p0, p1, buflen, (char*)buffer ); return map_sw (sw); } /* This is a private command currently only working for TCOS cards. */ gpg_error_t iso7816_list_directory (int slot, int list_dirs, unsigned char **result, size_t *resultlen) { int sw; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; sw = apdu_send (slot, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } return map_sw (sw); } -/* This funcion sends an already formatted APDU to the card. With +/* This function sends an already formatted APDU to the card. With HANDLE_MORE set to true a MORE DATA status will be handled internally. The return value is a gpg error code (i.e. a mapped status word). This is basically the same as apdu_send_direct but it maps the status word and does not return it in the result buffer. */ gpg_error_t iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen, int handle_more, unsigned char **result, size_t *resultlen) { int sw; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more, result, resultlen); if (!sw) { if (*resultlen < 2) sw = SW_HOST_GENERAL_ERROR; else { sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1]; (*resultlen)--; (*resultlen)--; } } if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } return map_sw (sw); } /* Check whether the reader supports the ISO command code COMMAND on the pinpad. Returns 0 on success. */ gpg_error_t iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo) { int sw; sw = apdu_check_pinpad (slot, command, pininfo); return iso7816_map_sw (sw); } /* Perform a VERIFY command on SLOT using the card holder verification vector CHVNO. With PININFO non-NULL the pinpad of the reader will be used. Returns 0 on success. */ gpg_error_t iso7816_verify_kp (int slot, int chvno, pininfo_t *pininfo) { int sw; sw = apdu_pinpad_verify (slot, 0x00, CMD_VERIFY, 0, chvno, pininfo); return map_sw (sw); } /* Perform a VERIFY command on SLOT using the card holder verification vector CHVNO with a CHV of length CHVLEN. Returns 0 on success. */ gpg_error_t iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) { int sw; sw = apdu_send_simple (slot, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); return map_sw (sw); } /* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder verification vector CHVNO. With PININFO non-NULL the pinpad of the reader will be used. If IS_EXCHANGE is 0, a "change reference data" is done, otherwise an "exchange reference data". */ gpg_error_t iso7816_change_reference_data_kp (int slot, int chvno, int is_exchange, pininfo_t *pininfo) { int sw; sw = apdu_pinpad_modify (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, is_exchange ? 1 : 0, chvno, pininfo); return map_sw (sw); } /* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN 0), a "change reference data" is done, otherwise an "exchange reference data". The new reference data is expected in NEWCHV of length NEWCHVLEN. */ gpg_error_t iso7816_change_reference_data (int slot, int chvno, const char *oldchv, size_t oldchvlen, const char *newchv, size_t newchvlen) { int sw; char *buf; if ((!oldchv && oldchvlen) || (oldchv && !oldchvlen) || !newchv || !newchvlen ) return gpg_error (GPG_ERR_INV_VALUE); buf = xtrymalloc (oldchvlen + newchvlen); if (!buf) return gpg_error (gpg_err_code_from_errno (errno)); if (oldchvlen) memcpy (buf, oldchv, oldchvlen); memcpy (buf+oldchvlen, newchv, newchvlen); sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA, oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); xfree (buf); return map_sw (sw); } gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, const char *data, size_t datalen) { int sw; if (!data || !datalen ) return gpg_error (GPG_ERR_INV_VALUE); sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, 0, chvno, datalen, data); return map_sw (sw); } gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, const char *newchv, size_t newchvlen) { int sw; sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER, 2, chvno, newchvlen, newchv); return map_sw (sw); } /* Perform a GET DATA command requesting TAG and storing the result in a newly allocated buffer at the address passed by RESULT. Return the length of this data at the address of RESULTLEN. */ gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen) { int sw; int le; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; if (extended_mode > 0 && extended_mode < 256) le = 65534; /* Not 65535 in case it is used as some special flag. */ else if (extended_mode > 0) le = extended_mode; else le = 256; sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA, ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } return 0; } /* Perform a PUT DATA command on card in SLOT. Write DATA of length DATALEN to TAG. EXTENDED_MODE controls whether extended length headers or command chaining is used instead of single length bytes. */ gpg_error_t iso7816_put_data (int slot, int extended_mode, int tag, const void *data, size_t datalen) { int sw; sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA, ((tag >> 8) & 0xff), (tag & 0xff), datalen, (const char*)data); return map_sw (sw); } /* Same as iso7816_put_data but uses an odd instruction byte. */ gpg_error_t iso7816_put_data_odd (int slot, int extended_mode, int tag, const void *data, size_t datalen) { int sw; sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1, ((tag >> 8) & 0xff), (tag & 0xff), datalen, (const char*)data); return map_sw (sw); } /* Manage Security Environment. This is a weird operation and there is no easy abstraction for it. Furthermore, some card seem to have a different interpreation of 7816-8 and thus we resort to let the caller decide what to do. */ gpg_error_t iso7816_manage_security_env (int slot, int p1, int p2, const unsigned char *data, size_t datalen) { int sw; if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 ) return gpg_error (GPG_ERR_INV_VALUE); sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2, data? datalen : -1, (const char*)data); return map_sw (sw); } /* Perform the security operation COMPUTE DIGITAL SIGANTURE. On success 0 is returned and the data is availavle in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t iso7816_compute_ds (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { int sw; if (!data || !datalen || !result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; if (!extended_mode) le = 256; /* Ignore provided Le and use what apdu_send uses. */ else if (le >= 0 && le < 256) le = 256; sw = apdu_send_le (slot, extended_mode, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data, le, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } return 0; } /* Perform the security operation DECIPHER. PADIND is the padding indicator to be used. It should be 0 if no padding is required, a value of -1 suppresses the padding byte. On success 0 is returned and the plaintext is available in a newly allocated buffer stored at RESULT with its length stored at RESULTLEN. For LE see do_generate_keypair. */ gpg_error_t iso7816_decipher (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, int padind, unsigned char **result, size_t *resultlen) { int sw; unsigned char *buf; if (!data || !datalen || !result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; if (!extended_mode) le = 256; /* Ignore provided Le and use what apdu_send uses. */ else if (le >= 0 && le < 256) le = 256; if (padind >= 0) { /* We need to prepend the padding indicator. */ buf = xtrymalloc (datalen + 1); if (!buf) return gpg_error (gpg_err_code_from_errno (errno)); *buf = padind; /* Padding indicator. */ memcpy (buf+1, data, datalen); sw = apdu_send_le (slot, extended_mode, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, (char*)buf, le, result, resultlen); xfree (buf); } else { sw = apdu_send_le (slot, extended_mode, 0x00, CMD_PSO, 0x80, 0x86, datalen, (const char *)data, le, result, resultlen); } if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } return 0; } /* For LE see do_generate_keypair. */ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode, const unsigned char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { int sw; if (!data || !datalen || !result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; if (!extended_mode) le = 256; /* Ignore provided Le and use what apdu_send uses. */ else if (le >= 0 && le < 256) le = 256; sw = apdu_send_le (slot, extended_mode, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, datalen, (const char*)data, le, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } return 0; } /* LE is the expected return length. This is usually 0 except if extended length mode is used and more than 256 byte will be returned. In that case a value of -1 uses a large default (e.g. 4096 bytes), a value larger 256 used that value. */ static gpg_error_t do_generate_keypair (int slot, int extended_mode, int read_only, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { int sw; if (!data || !datalen || !result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GENERATE_KEYPAIR, read_only? 0x81:0x80, 0, datalen, data, le >= 0 && le < 256? 256:le, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } return 0; } gpg_error_t iso7816_generate_keypair (int slot, int extended_mode, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { return do_generate_keypair (slot, extended_mode, 0, data, datalen, le, result, resultlen); } gpg_error_t iso7816_read_public_key (int slot, int extended_mode, const char *data, size_t datalen, int le, unsigned char **result, size_t *resultlen) { return do_generate_keypair (slot, extended_mode, 1, data, datalen, le, result, resultlen); } gpg_error_t iso7816_get_challenge (int slot, int length, unsigned char *buffer) { int sw; unsigned char *result; size_t resultlen, n; if (!buffer || length < 1) return gpg_error (GPG_ERR_INV_VALUE); do { result = NULL; n = length > 254? 254 : length; sw = apdu_send_le (slot, 0, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, n, &result, &resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (result); return map_sw (sw); } if (resultlen > n) resultlen = n; memcpy (buffer, result, resultlen); buffer += resultlen; length -= resultlen; xfree (result); } while (length > 0); return 0; } /* Perform a READ BINARY command requesting a maximum of NMAX bytes from OFFSET. With NMAX = 0 the entire file is read. The result is stored in a newly allocated buffer at the address passed by RESULT. Returns the length of this data at the address of RESULTLEN. */ gpg_error_t iso7816_read_binary (int slot, size_t offset, size_t nmax, unsigned char **result, size_t *resultlen) { int sw; unsigned char *buffer; size_t bufferlen; int read_all = !nmax; size_t n; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus we check for this limit. */ if (offset > 32767) return gpg_error (GPG_ERR_INV_VALUE); do { buffer = NULL; bufferlen = 0; n = read_all? 0 : nmax; sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); if ( SW_EXACT_LENGTH_P(sw) ) { n = (sw & 0x00ff); sw = apdu_send_le (slot, 0, 0x00, CMD_READ_BINARY, ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL, n, &buffer, &bufferlen); } if (*result && sw == SW_BAD_P0_P1) { /* Bad Parameter means that the offset is outside of the EF. When reading all data we take this as an indication for EOF. */ break; } if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) { /* Make sure that pending buffers are released. */ xfree (buffer); xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } if (*result) /* Need to extend the buffer. */ { unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen); if (!p) { gpg_error_t err = gpg_error_from_syserror (); xfree (buffer); xfree (*result); *result = NULL; *resultlen = 0; return err; } *result = p; memcpy (*result + *resultlen, buffer, bufferlen); *resultlen += bufferlen; xfree (buffer); buffer = NULL; } else /* Transfer the buffer into our result. */ { *result = buffer; *resultlen = bufferlen; } offset += bufferlen; if (offset > 32767) break; /* We simply truncate the result for too large files. */ if (nmax > bufferlen) nmax -= bufferlen; else nmax = 0; } while ((read_all && sw != SW_EOF_REACHED) || (!read_all && nmax)); return 0; } /* Perform a READ RECORD command. RECNO gives the record number to read with 0 indicating the current record. RECCOUNT must be 1 (not all cards support reading of more than one record). SHORT_EF should be 0 to read the current EF or contain a short EF. The result is stored in a newly allocated buffer at the address passed by RESULT. Returns the length of this data at the address of RESULTLEN. */ gpg_error_t iso7816_read_record (int slot, int recno, int reccount, int short_ef, unsigned char **result, size_t *resultlen) { int sw; unsigned char *buffer; size_t bufferlen; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; /* We can only encode 15 bits in p0,p1 to indicate an offset. Thus we check for this limit. */ if (recno < 0 || recno > 255 || reccount != 1 || short_ef < 0 || short_ef > 254 ) return gpg_error (GPG_ERR_INV_VALUE); buffer = NULL; bufferlen = 0; sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD, recno, short_ef? short_ef : 0x04, -1, NULL, 0, &buffer, &bufferlen); if (sw != SW_SUCCESS && sw != SW_EOF_REACHED) { /* Make sure that pending buffers are released. */ xfree (buffer); xfree (*result); *result = NULL; *resultlen = 0; return map_sw (sw); } *result = buffer; *resultlen = bufferlen; return 0; } diff --git a/sm/certlist.c b/sm/certlist.c index e493cda97..39ab03c5d 100644 --- a/sm/certlist.c +++ b/sm/certlist.c @@ -1,570 +1,570 @@ /* certlist.c - build list of certificates * Copyright (C) 2001, 2003, 2004, 2005, 2007, * 2008, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <assert.h> #include "gpgsm.h" #include <gcrypt.h> #include <ksba.h> #include "keydb.h" #include "../common/i18n.h" static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; /* Return 0 if the cert is usable for encryption. A MODE of 0 checks for signing a MODE of 1 checks for encryption, a MODE of 2 checks for verification and a MODE of 3 for decryption (just for debugging). MODE 4 is for certificate signing, MODE for COSP response signing. */ static int cert_usage_p (ksba_cert_t cert, int mode) { gpg_error_t err; unsigned int use; char *extkeyusages; int have_ocsp_signing = 0; err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* no policy given */ if (!err) { unsigned int extusemask = ~0; /* Allow all. */ if (extkeyusages) { char *p, *pend; int any_critical = 0; extusemask = 0; p = extkeyusages; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; /* Only care about critical flagged usages. */ if ( *pend == 'C' ) { any_critical = 1; if ( !strcmp (p, oid_kp_serverAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_clientAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_codeSigning)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); else if ( !strcmp (p, oid_kp_emailProtection)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_timeStamping)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION); } /* This is a hack to cope with OCSP. Note that we do not yet fully comply with the requirements and that the entire CRL/OCSP checking thing should undergo a thorough review and probably redesign. */ if ( !strcmp (p, oid_kp_ocspSigning)) have_ocsp_signing = 1; if ((p = strchr (pend, '\n'))) p++; } xfree (extkeyusages); extkeyusages = NULL; if (!any_critical) extusemask = ~0; /* Reset to the don't care mask. */ } err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (opt.verbose && mode < 2) log_info (_("no key usage specified - assuming all usages\n")); use = ~0; } /* Apply extKeyUsage. */ use &= extusemask; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); xfree (extkeyusages); return err; } if (mode == 4) { if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) return 0; log_info (_("certificate should not have " "been used for certification\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (mode == 5) { if (use != ~0 && (have_ocsp_signing || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN |KSBA_KEYUSAGE_CRL_SIGN)))) return 0; log_info (_("certificate should not have " "been used for OCSP response signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if ((use & ((mode&1)? (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) ) return 0; log_info (mode==3? _("certificate should not have been used for encryption\n"): mode==2? _("certificate should not have been used for signing\n"): mode==1? _("certificate is not usable for encryption\n"): _("certificate is not usable for signing\n")); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return 0 if the cert is usable for signing */ int gpgsm_cert_use_sign_p (ksba_cert_t cert) { return cert_usage_p (cert, 0); } /* Return 0 if the cert is usable for encryption */ int gpgsm_cert_use_encrypt_p (ksba_cert_t cert) { return cert_usage_p (cert, 1); } int gpgsm_cert_use_verify_p (ksba_cert_t cert) { return cert_usage_p (cert, 2); } int gpgsm_cert_use_decrypt_p (ksba_cert_t cert) { return cert_usage_p (cert, 3); } int gpgsm_cert_use_cert_p (ksba_cert_t cert) { return cert_usage_p (cert, 4); } int gpgsm_cert_use_ocsp_p (ksba_cert_t cert) { return cert_usage_p (cert, 5); } /* Return true if CERT has the well known private key extension. */ int gpgsm_cert_has_well_known_private_key (ksba_cert_t cert) { int idx; const char *oid; for (idx=0; !ksba_cert_get_extension (cert, idx, &oid, NULL, NULL, NULL);idx++) if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.2") ) return 1; /* Yes. */ return 0; /* No. */ } static int same_subject_issuer (const char *subject, const char *issuer, ksba_cert_t cert) { char *subject2 = ksba_cert_get_subject (cert, 0); char *issuer2 = ksba_cert_get_issuer (cert, 0); int tmp; tmp = (subject && subject2 && !strcmp (subject, subject2) && issuer && issuer2 && !strcmp (issuer, issuer2)); xfree (subject2); xfree (issuer2); return tmp; } /* Return true if CERT_A is the same as CERT_B. */ int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b) { const unsigned char *img_a, *img_b; size_t len_a, len_b; img_a = ksba_cert_get_image (cert_a, &len_a); if (img_a) { img_b = ksba_cert_get_image (cert_b, &len_b); if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a)) return 1; /* Identical. */ } return 0; } /* Return true if CERT is already contained in CERTLIST. */ static int is_cert_in_certlist (ksba_cert_t cert, certlist_t certlist) { const unsigned char *img_a, *img_b; size_t len_a, len_b; img_a = ksba_cert_get_image (cert, &len_a); if (img_a) { for ( ; certlist; certlist = certlist->next) { img_b = ksba_cert_get_image (certlist->cert, &len_b); if (img_b && len_a == len_b && !memcmp (img_a, img_b, len_a)) return 1; /* Already contained. */ } } return 0; } /* Add CERT to the list of certificates at CERTADDR but avoid duplicates. */ int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, certlist_t *listaddr, int is_encrypt_to) { (void)ctrl; if (!is_cert_in_certlist (cert, *listaddr)) { certlist_t cl = xtrycalloc (1, sizeof *cl); if (!cl) return out_of_core (); cl->cert = cert; ksba_cert_ref (cert); cl->next = *listaddr; cl->is_encrypt_to = is_encrypt_to; *listaddr = cl; } return 0; } /* Add a certificate to a list of certificate and make sure that it is a valid certificate. With SECRET set to true a secret key must be available for the certificate. IS_ENCRYPT_TO sets the corresponding flag in the new create LISTADDR item. */ int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, certlist_t *listaddr, int is_encrypt_to) { int rc; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; ksba_cert_t cert = NULL; rc = classify_user_id (name, &desc, 0); if (!rc) { kh = keydb_new (); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); else { int wrong_usage = 0; char *first_subject = NULL; char *first_issuer = NULL; get_next: rc = keydb_search (ctrl, kh, &desc, 1); if (!rc) rc = keydb_get_cert (kh, &cert); if (!rc) { if (!first_subject) { /* Save the subject and the issuer for key usage and ambiguous name tests. */ first_subject = ksba_cert_get_subject (cert, 0); first_issuer = ksba_cert_get_issuer (cert, 0); } rc = secret? gpgsm_cert_use_sign_p (cert) : gpgsm_cert_use_encrypt_p (cert); if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE) { /* There might be another certificate with the correct usage, so we try again */ if (!wrong_usage) { /* save the first match */ wrong_usage = rc; ksba_cert_release (cert); cert = NULL; goto get_next; } else if (same_subject_issuer (first_subject, first_issuer, cert)) { wrong_usage = rc; ksba_cert_release (cert); cert = NULL; goto get_next; } else wrong_usage = rc; } } /* We want the error code from the first match in this case. */ if (rc && wrong_usage) rc = wrong_usage; if (!rc) { certlist_t dup_certs = NULL; next_ambigious: rc = keydb_search (ctrl, kh, &desc, 1); if (rc == -1) rc = 0; else if (!rc) { ksba_cert_t cert2 = NULL; /* If this is the first possible duplicate, add the original certificate to our list of duplicates. */ if (!dup_certs) gpgsm_add_cert_to_certlist (ctrl, cert, &dup_certs, 0); - /* We have to ignore ambigious names as long as + /* We have to ignore ambiguous names as long as there only fault is a bad key usage. This is required to support encryption and signing certificates of the same subject. Further we ignore them if they are due to an identical certificate (which may happen if a certificate is accidential duplicated in the keybox). */ if (!keydb_get_cert (kh, &cert2)) { int tmp = (same_subject_issuer (first_subject, first_issuer, cert2) && ((gpg_err_code ( secret? gpgsm_cert_use_sign_p (cert2) : gpgsm_cert_use_encrypt_p (cert2) ) ) == GPG_ERR_WRONG_KEY_USAGE)); if (tmp) gpgsm_add_cert_to_certlist (ctrl, cert2, &dup_certs, 0); else { if (is_cert_in_certlist (cert2, dup_certs)) tmp = 1; } ksba_cert_release (cert2); if (tmp) goto next_ambigious; } rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); } gpgsm_release_certlist (dup_certs); } xfree (first_subject); xfree (first_issuer); first_subject = NULL; first_issuer = NULL; if (!rc && !is_cert_in_certlist (cert, *listaddr)) { if (!rc && secret) { char *p; rc = gpg_error (GPG_ERR_NO_SECKEY); p = gpgsm_get_keygrip_hexstring (cert); if (p) { if (!gpgsm_agent_havekey (ctrl, p)) rc = 0; xfree (p); } } if (!rc) rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL); if (!rc) { certlist_t cl = xtrycalloc (1, sizeof *cl); if (!cl) rc = out_of_core (); else { cl->cert = cert; cert = NULL; cl->next = *listaddr; cl->is_encrypt_to = is_encrypt_to; *listaddr = cl; } } } } } keydb_release (kh); ksba_cert_release (cert); return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; } void gpgsm_release_certlist (certlist_t list) { while (list) { certlist_t cl = list->next; ksba_cert_release (list->cert); xfree (list); list = cl; } } /* Like gpgsm_add_to_certlist, but look only for one certificate. No chain validation is done. If KEYID is not NULL it is taken as an additional filter value which must match the subjectKeyIdentifier. */ int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert) { int rc; KEYDB_SEARCH_DESC desc; KEYDB_HANDLE kh = NULL; *r_cert = NULL; rc = classify_user_id (name, &desc, 0); if (!rc) { kh = keydb_new (); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); else { nextone: rc = keydb_search (ctrl, kh, &desc, 1); if (!rc) { rc = keydb_get_cert (kh, r_cert); if (!rc && keyid) { ksba_sexp_t subj; rc = ksba_cert_get_subj_key_id (*r_cert, NULL, &subj); if (!rc) { if (cmp_simple_canon_sexp (keyid, subj)) { xfree (subj); goto nextone; } xfree (subj); /* Okay: Here we know that the certificate's subjectKeyIdentifier matches the requested one. */ } else if (gpg_err_code (rc) == GPG_ERR_NO_DATA) goto nextone; } } /* If we don't have the KEYID filter we need to check for - ambigious search results. Note, that it is somehwat + ambiguous search results. Note, that it is somehwat reasonable to assume that a specification of a KEYID won't lead to ambiguous names. */ if (!rc && !keyid) { next_ambiguous: rc = keydb_search (ctrl, kh, &desc, 1); if (rc == -1) rc = 0; else { if (!rc) { ksba_cert_t cert2 = NULL; if (!keydb_get_cert (kh, &cert2)) { if (gpgsm_certs_identical_p (*r_cert, cert2)) { ksba_cert_release (cert2); goto next_ambiguous; } ksba_cert_release (cert2); } rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); } ksba_cert_release (*r_cert); *r_cert = NULL; } } } } keydb_release (kh); return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; } diff --git a/sm/import.c b/sm/import.c index c7b65ad14..8796cd206 100644 --- a/sm/import.c +++ b/sm/import.c @@ -1,939 +1,939 @@ /* import.c - Import certificates * Copyright (C) 2001, 2003, 2004, 2009, 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <time.h> #include <assert.h> #include <unistd.h> #include "gpgsm.h" #include <gcrypt.h> #include <ksba.h> #include "keydb.h" #include "../common/exechelp.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "../common/membuf.h" #include "minip12.h" /* The arbitrary limit of one PKCS#12 object. */ #define MAX_P12OBJ_SIZE 128 /*kb*/ struct stats_s { unsigned long count; unsigned long imported; unsigned long unchanged; unsigned long not_imported; unsigned long secret_read; unsigned long secret_imported; unsigned long secret_dups; }; struct rsa_secret_key_s { gcry_mpi_t n; /* public modulus */ gcry_mpi_t e; /* public exponent */ gcry_mpi_t d; /* exponent */ gcry_mpi_t p; /* prime p. */ gcry_mpi_t q; /* prime q. */ gcry_mpi_t u; /* inverse of p mod q. */ }; static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats); static void print_imported_status (ctrl_t ctrl, ksba_cert_t cert, int new_cert) { char *fpr; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (new_cert) gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL); gpgsm_status2 (ctrl, STATUS_IMPORT_OK, new_cert? "1":"0", fpr, NULL); xfree (fpr); } /* Print an IMPORT_PROBLEM status. REASON is one of: 0 := "No specific reason given". 1 := "Invalid Certificate". 2 := "Issuer Certificate missing". 3 := "Certificate Chain too long". 4 := "Error storing certificate". */ static void print_import_problem (ctrl_t ctrl, ksba_cert_t cert, int reason) { char *fpr = NULL; char buf[25]; int i; sprintf (buf, "%d", reason); if (cert) { fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); /* detetect an error (all high) value */ for (i=0; fpr[i] == 'F'; i++) ; if (!fpr[i]) { xfree (fpr); fpr = NULL; } } gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL); xfree (fpr); } void print_imported_summary (ctrl_t ctrl, struct stats_s *stats) { char buf[14*25]; if (!opt.quiet) { log_info (_("total number processed: %lu\n"), stats->count); if (stats->imported) { log_info (_(" imported: %lu"), stats->imported ); log_printf ("\n"); } if (stats->unchanged) log_info (_(" unchanged: %lu\n"), stats->unchanged); if (stats->secret_read) log_info (_(" secret keys read: %lu\n"), stats->secret_read ); if (stats->secret_imported) log_info (_(" secret keys imported: %lu\n"), stats->secret_imported ); if (stats->secret_dups) log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups ); if (stats->not_imported) log_info (_(" not imported: %lu\n"), stats->not_imported); } sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", stats->count, 0l /*stats->no_user_id*/, stats->imported, 0l /*stats->imported_rsa*/, stats->unchanged, 0l /*stats->n_uids*/, 0l /*stats->n_subk*/, 0l /*stats->n_sigs*/, 0l /*stats->n_revoc*/, stats->secret_read, stats->secret_imported, stats->secret_dups, 0l /*stats->skipped_new_keys*/, stats->not_imported ); gpgsm_status (ctrl, STATUS_IMPORT_RES, buf); } static void check_and_store (ctrl_t ctrl, struct stats_s *stats, ksba_cert_t cert, int depth) { int rc; if (stats) stats->count++; if ( depth >= 50 ) { log_error (_("certificate chain too long\n")); if (stats) stats->not_imported++; print_import_problem (ctrl, cert, 3); return; } /* Some basic checks, but don't care about missing certificates; this is so that we are able to import entire certificate chains w/o requiring a special order (i.e. root-CA first). This used to be different but because gpgsm_verify even imports certificates without any checks, it doesn't matter much and the code gets much cleaner. A housekeeping function to remove certificates w/o an anchor would be nice, though. Optionally we do a full validation in addition to the basic test. */ rc = gpgsm_basic_cert_check (ctrl, cert); if (!rc && ctrl->with_validation) rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL); if (!rc || (!ctrl->with_validation && (gpg_err_code (rc) == GPG_ERR_MISSING_CERT || gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT))) { int existed; if (!keydb_store_cert (ctrl, cert, 0, &existed)) { ksba_cert_t next = NULL; if (!existed) { print_imported_status (ctrl, cert, 1); if (stats) stats->imported++; } else { print_imported_status (ctrl, cert, 0); if (stats) stats->unchanged++; } if (opt.verbose > 1 && existed) { if (depth) log_info ("issuer certificate already in DB\n"); else log_info ("certificate already in DB\n"); } else if (opt.verbose && !existed) { if (depth) log_info ("issuer certificate imported\n"); else log_info ("certificate imported\n"); } /* Now lets walk up the chain and import all certificates up the chain. This is required in case we already stored parent certificates in the ephemeral keybox. Do not update the statistics, though. */ if (!gpgsm_walk_cert_chain (ctrl, cert, &next)) { check_and_store (ctrl, NULL, next, depth+1); ksba_cert_release (next); } } else { log_error (_("error storing certificate\n")); if (stats) stats->not_imported++; print_import_problem (ctrl, cert, 4); } } else { log_error (_("basic certificate checks failed - not imported\n")); if (stats) stats->not_imported++; /* We keep the test for GPG_ERR_MISSING_CERT only in case GPG_ERR_MISSING_CERT has been used instead of the newer GPG_ERR_MISSING_ISSUER_CERT. */ print_import_problem (ctrl, cert, gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT? 2 : gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 : gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0); } } static int import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd) { int rc; gnupg_ksba_io_t b64reader = NULL; ksba_reader_t reader; ksba_cert_t cert = NULL; ksba_cms_t cms = NULL; estream_t fp = NULL; ksba_content_type_t ct; int any = 0; fp = es_fdopen_nc (in_fd, "rb"); if (!fp) { rc = gpg_error_from_syserror (); log_error ("fdopen() failed: %s\n", strerror (errno)); goto leave; } rc = gnupg_ksba_create_reader (&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0) | (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0) | GNUPG_KSBA_IO_MULTIPEM), fp, &reader); if (rc) { log_error ("can't create reader: %s\n", gpg_strerror (rc)); goto leave; } /* We need to loop here to handle multiple PEM objects in one file. */ do { ksba_cms_release (cms); cms = NULL; ksba_cert_release (cert); cert = NULL; ct = ksba_cms_identify (reader); if (ct == KSBA_CT_SIGNED_DATA) { /* This is probably a signed-only message - import the certs */ ksba_stop_reason_t stopreason; int i; rc = ksba_cms_new (&cms); if (rc) goto leave; rc = ksba_cms_set_reader_writer (cms, reader, NULL); if (rc) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (rc)); goto leave; } do { rc = ksba_cms_parse (cms, &stopreason); if (rc) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc)); goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) log_info ("not a certs-only message\n"); } while (stopreason != KSBA_SR_READY); for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) { check_and_store (ctrl, stats, cert, 0); ksba_cert_release (cert); cert = NULL; } if (!i) log_error ("no certificate found\n"); else any = 1; } else if (ct == KSBA_CT_PKCS12) { /* This seems to be a pkcs12 message. */ rc = parse_p12 (ctrl, reader, stats); if (!rc) any = 1; } else if (ct == KSBA_CT_NONE) { /* Failed to identify this message - assume a certificate */ rc = ksba_cert_new (&cert); if (rc) goto leave; rc = ksba_cert_read_der (cert, reader); if (rc) goto leave; check_and_store (ctrl, stats, cert, 0); any = 1; } else { log_error ("can't extract certificates from input\n"); rc = gpg_error (GPG_ERR_NO_DATA); } ksba_reader_clear (reader, NULL, NULL); } while (!gnupg_ksba_reader_eof_seen (b64reader)); leave: if (any && gpg_err_code (rc) == GPG_ERR_EOF) rc = 0; ksba_cms_release (cms); ksba_cert_release (cert); gnupg_ksba_destroy_reader (b64reader); es_fclose (fp); return rc; } /* Re-import certifciates. IN_FD is a list of linefeed delimited fingerprints t re-import. The actual re-import is done by clearing the ephemeral flag. */ static int reimport_one (ctrl_t ctrl, struct stats_s *stats, int in_fd) { gpg_error_t err = 0; estream_t fp = NULL; char line[100]; /* Sufficient for a fingerprint. */ KEYDB_HANDLE kh; KEYDB_SEARCH_DESC desc; ksba_cert_t cert = NULL; unsigned int flags; kh = keydb_new (); if (!kh) { err = gpg_error (GPG_ERR_ENOMEM);; log_error (_("failed to allocate keyDB handle\n")); goto leave; } keydb_set_ephemeral (kh, 1); fp = es_fdopen_nc (in_fd, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("es_fdopen(%d) failed: %s\n", in_fd, gpg_strerror (err)); goto leave; } while (es_fgets (line, DIM(line)-1, fp) ) { if (*line && line[strlen(line)-1] != '\n') { err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } trim_spaces (line); if (!*line) continue; stats->count++; err = classify_user_id (line, &desc, 0); if (err) { print_import_problem (ctrl, NULL, 0); stats->not_imported++; continue; } keydb_search_reset (kh); err = keydb_search (ctrl, kh, &desc, 1); if (err) { print_import_problem (ctrl, NULL, 0); stats->not_imported++; continue; } ksba_cert_release (cert); cert = NULL; err = keydb_get_cert (kh, &cert); if (err) { log_error ("keydb_get_cert() failed: %s\n", gpg_strerror (err)); print_import_problem (ctrl, NULL, 1); stats->not_imported++; continue; } err = keydb_get_flags (kh, KEYBOX_FLAG_BLOB, 0, &flags); if (err) { log_error (_("error getting stored flags: %s\n"), gpg_strerror (err)); print_imported_status (ctrl, cert, 0); stats->not_imported++; continue; } if ( !(flags & KEYBOX_FLAG_BLOB_EPHEMERAL) ) { print_imported_status (ctrl, cert, 0); stats->unchanged++; continue; } err = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0, KEYBOX_FLAG_BLOB_EPHEMERAL, 0); if (err) { log_error ("clearing ephemeral flag failed: %s\n", gpg_strerror (err)); print_import_problem (ctrl, cert, 0); stats->not_imported++; continue; } print_imported_status (ctrl, cert, 1); stats->imported++; } err = 0; if (es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading fd %d: %s\n", in_fd, gpg_strerror (err)); goto leave; } leave: ksba_cert_release (cert); keydb_release (kh); es_fclose (fp); return err; } int gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode) { int rc; struct stats_s stats; memset (&stats, 0, sizeof stats); if (reimport_mode) rc = reimport_one (ctrl, &stats, in_fd); else rc = import_one (ctrl, &stats, in_fd); print_imported_summary (ctrl, &stats); /* If we never printed an error message do it now so that a command line invocation will return with an error (log_error keeps a global errorcount) */ if (rc && !log_get_errorcount (0)) log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); return rc; } int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, int (*of)(const char *fname)) { int rc = 0; struct stats_s stats; memset (&stats, 0, sizeof stats); if (!nfiles) rc = import_one (ctrl, &stats, 0); else { for (; nfiles && !rc ; nfiles--, files++) { int fd = of (*files); rc = import_one (ctrl, &stats, fd); close (fd); if (rc == -1) rc = 0; } } print_imported_summary (ctrl, &stats); /* If we never printed an error message do it now so that a command line invocation will return with an error (log_error keeps a global errorcount) */ if (rc && !log_get_errorcount (0)) log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); return rc; } /* Check that the RSA secret key SKEY is valid. Swap parameters to the libgcrypt standard. */ static gpg_error_t rsa_key_check (struct rsa_secret_key_s *skey) { int err = 0; gcry_mpi_t t = gcry_mpi_snew (0); gcry_mpi_t t1 = gcry_mpi_snew (0); gcry_mpi_t t2 = gcry_mpi_snew (0); gcry_mpi_t phi = gcry_mpi_snew (0); /* Check that n == p * q. */ gcry_mpi_mul (t, skey->p, skey->q); if (gcry_mpi_cmp( t, skey->n) ) { log_error ("RSA oops: n != p * q\n"); err++; } /* Check that p is less than q. */ if (gcry_mpi_cmp (skey->p, skey->q) > 0) { gcry_mpi_t tmp; log_info ("swapping secret primes\n"); tmp = gcry_mpi_copy (skey->p); gcry_mpi_set (skey->p, skey->q); gcry_mpi_set (skey->q, tmp); gcry_mpi_release (tmp); /* Recompute u. */ gcry_mpi_invm (skey->u, skey->p, skey->q); } /* Check that e divides neither p-1 nor q-1. */ gcry_mpi_sub_ui (t, skey->p, 1 ); gcry_mpi_div (NULL, t, t, skey->e, 0); if (!gcry_mpi_cmp_ui( t, 0) ) { log_error ("RSA oops: e divides p-1\n"); err++; } gcry_mpi_sub_ui (t, skey->q, 1); gcry_mpi_div (NULL, t, t, skey->e, 0); if (!gcry_mpi_cmp_ui( t, 0)) { log_info ("RSA oops: e divides q-1\n" ); err++; } /* Check that d is correct. */ gcry_mpi_sub_ui (t1, skey->p, 1); gcry_mpi_sub_ui (t2, skey->q, 1); gcry_mpi_mul (phi, t1, t2); gcry_mpi_invm (t, skey->e, phi); if (gcry_mpi_cmp (t, skey->d)) { /* No: try universal exponent. */ gcry_mpi_gcd (t, t1, t2); gcry_mpi_div (t, NULL, phi, t, 0); gcry_mpi_invm (t, skey->e, t); if (gcry_mpi_cmp (t, skey->d)) { log_error ("RSA oops: bad secret exponent\n"); err++; } } /* Check for correctness of u. */ gcry_mpi_invm (t, skey->p, skey->q); if (gcry_mpi_cmp (t, skey->u)) { log_info ("RSA oops: bad u parameter\n"); err++; } if (err) log_info ("RSA secret key check failed\n"); gcry_mpi_release (t); gcry_mpi_release (t1); gcry_mpi_release (t2); gcry_mpi_release (phi); return err? gpg_error (GPG_ERR_BAD_SECKEY):0; } /* Object passed to store_cert_cb. */ struct store_cert_parm_s { gpg_error_t err; /* First error seen. */ struct stats_s *stats; /* The stats object. */ ctrl_t ctrl; /* The control object. */ }; /* Helper to store the DER encoded certificate CERTDATA of length CERTDATALEN. */ static void store_cert_cb (void *opaque, const unsigned char *certdata, size_t certdatalen) { struct store_cert_parm_s *parm = opaque; gpg_error_t err; ksba_cert_t cert; err = ksba_cert_new (&cert); if (err) { if (!parm->err) parm->err = err; return; } err = ksba_cert_init_from_mem (cert, certdata, certdatalen); if (err) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); if (!parm->err) parm->err = err; } else check_and_store (parm->ctrl, parm->stats, cert, 0); ksba_cert_release (cert); } /* Assume that the reader is at a pkcs#12 message and try to import certificates from that stupid format. We will transfer secret keys to the agent. */ static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats) { gpg_error_t err = 0; char buffer[1024]; size_t ntotal, nread; membuf_t p12mbuf; char *p12buffer = NULL; size_t p12buflen; size_t p12bufoff; gcry_mpi_t *kparms = NULL; struct rsa_secret_key_s sk; char *passphrase = NULL; unsigned char *key = NULL; size_t keylen; void *kek = NULL; size_t keklen; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; gcry_sexp_t s_key = NULL; unsigned char grip[20]; int bad_pass = 0; int i; struct store_cert_parm_s store_cert_parm; memset (&store_cert_parm, 0, sizeof store_cert_parm); store_cert_parm.ctrl = ctrl; store_cert_parm.stats = stats; init_membuf (&p12mbuf, 4096); ntotal = 0; while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread))) { if (ntotal >= MAX_P12OBJ_SIZE*1024) { /* Arbitrary limit to avoid DoS attacks. */ err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE); break; } put_membuf (&p12mbuf, buffer, nread); ntotal += nread; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; if (!err) { p12buffer = get_membuf (&p12mbuf, &p12buflen); if (!p12buffer) err = gpg_error_from_syserror (); } if (err) { log_error (_("error reading input: %s\n"), gpg_strerror (err)); goto leave; } /* GnuPG 2.0.4 accidentally created binary P12 files with the string "The passphrase is %s encoded.\n\n" prepended to the ASN.1 data. We fix that here. */ if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18)) { for (p12bufoff=18; p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n'; p12bufoff++) ; p12bufoff++; if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n') p12bufoff++; } else p12bufoff = 0; err = gpgsm_agent_ask_passphrase (ctrl, i18n_utf8 ("Please enter the passphrase to unprotect the PKCS#12 object."), 0, &passphrase); if (err) goto leave; kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff, passphrase, store_cert_cb, &store_cert_parm, &bad_pass); xfree (passphrase); passphrase = NULL; if (!kparms) { log_error ("error parsing or decrypting the PKCS#12 file\n"); err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } /* print_mpi (" n", kparms[0]); */ /* print_mpi (" e", kparms[1]); */ /* print_mpi (" d", kparms[2]); */ /* print_mpi (" p", kparms[3]); */ /* print_mpi (" q", kparms[4]); */ /* print_mpi ("dmp1", kparms[5]); */ /* print_mpi ("dmq1", kparms[6]); */ /* print_mpi (" u", kparms[7]); */ sk.n = kparms[0]; sk.e = kparms[1]; sk.d = kparms[2]; sk.q = kparms[3]; sk.p = kparms[4]; sk.u = kparms[7]; err = rsa_key_check (&sk); if (err) goto leave; /* print_mpi (" n", sk.n); */ /* print_mpi (" e", sk.e); */ /* print_mpi (" d", sk.d); */ /* print_mpi (" p", sk.p); */ /* print_mpi (" q", sk.q); */ /* print_mpi (" u", sk.u); */ - /* Create an S-expresion from the parameters. */ + /* Create an S-expression from the parameters. */ err = gcry_sexp_build (&s_key, NULL, "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))", sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL); for (i=0; i < 8; i++) gcry_mpi_release (kparms[i]); gcry_free (kparms); kparms = NULL; if (err) { log_error ("failed to create S-expression from key: %s\n", gpg_strerror (err)); goto leave; } /* Compute the keygrip. */ if (!gcry_pk_get_keygrip (s_key, grip)) { err = gpg_error (GPG_ERR_GENERAL); log_error ("can't calculate keygrip\n"); goto leave; } log_printhex ("keygrip=", grip, 20); /* Convert to canonical encoding using a function which pads it to a multiple of 64 bits. We need this padding for AESWRAP. */ err = make_canon_sexp_pad (s_key, 1, &key, &keylen); if (err) { log_error ("error creating canonical S-expression\n"); goto leave; } gcry_sexp_release (s_key); s_key = NULL; /* Get the current KEK. */ err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen); if (err) { log_error ("error getting the KEK: %s\n", gpg_strerror (err)); goto leave; } /* Wrap the key. */ err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, kek, keklen); if (err) goto leave; xfree (kek); kek = NULL; wrappedkeylen = keylen + 8; wrappedkey = xtrymalloc (wrappedkeylen); if (!wrappedkey) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); if (err) goto leave; xfree (key); key = NULL; gcry_cipher_close (cipherhd); cipherhd = NULL; /* Send the wrapped key to the agent. */ err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen); if (!err) { stats->count++; stats->secret_read++; stats->secret_imported++; } else if ( gpg_err_code (err) == GPG_ERR_EEXIST ) { err = 0; stats->count++; stats->secret_read++; stats->secret_dups++; } /* If we did not get an error from storing the secret key we return a possible error from parsing the certificates. We do this after storing the secret keys so that a bad certificate does not inhibit our chance to store the secret key. */ if (!err && store_cert_parm.err) err = store_cert_parm.err; leave: if (kparms) { for (i=0; i < 8; i++) gcry_mpi_release (kparms[i]); gcry_free (kparms); kparms = NULL; } xfree (key); gcry_sexp_release (s_key); xfree (passphrase); gcry_cipher_close (cipherhd); xfree (wrappedkey); xfree (kek); xfree (get_membuf (&p12mbuf, NULL)); xfree (p12buffer); if (bad_pass) { /* We only write a plain error code and not direct BAD_PASSPHRASE because the pkcs12 parser might issue this message multiple times, BAD_PASSPHRASE in general requires a keyID and parts of the import might actually succeed so that IMPORT_PROBLEM is also not appropriate. */ gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "import.parsep12", GPG_ERR_BAD_PASSPHRASE); } return err; } diff --git a/sm/keylist.c b/sm/keylist.c index 1b1a261fd..13de45d9c 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -1,1598 +1,1598 @@ /* keylist.c - Print certificates in various formats. * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009, * 2010, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <assert.h> #include "gpgsm.h" #include <gcrypt.h> #include <ksba.h> #include "keydb.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "../common/i18n.h" #include "../common/tlv.h" struct list_external_parm_s { ctrl_t ctrl; estream_t fp; int print_header; int with_colons; int with_chain; int raw_mode; }; /* This table is to map Extended Key Usage OIDs to human readable names. */ struct { const char *oid; const char *name; } key_purpose_map[] = { { "1.3.6.1.5.5.7.3.1", "serverAuth" }, { "1.3.6.1.5.5.7.3.2", "clientAuth" }, { "1.3.6.1.5.5.7.3.3", "codeSigning" }, { "1.3.6.1.5.5.7.3.4", "emailProtection" }, { "1.3.6.1.5.5.7.3.5", "ipsecEndSystem" }, { "1.3.6.1.5.5.7.3.6", "ipsecTunnel" }, { "1.3.6.1.5.5.7.3.7", "ipsecUser" }, { "1.3.6.1.5.5.7.3.8", "timeStamping" }, { "1.3.6.1.5.5.7.3.9", "ocspSigning" }, { "1.3.6.1.5.5.7.3.10", "dvcs" }, { "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" }, { "1.3.6.1.5.5.7.3.13", "eapOverPPP" }, { "1.3.6.1.5.5.7.3.14", "wlanSSID" }, { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */ { "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */ { "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck" }, { NULL, NULL } }; /* Do not print this extension in the list of extensions. This is set - for oids which are already available via ksba fucntions. */ + for oids which are already available via ksba functions. */ #define OID_FLAG_SKIP 1 /* The extension is a simple UTF8String and should be printed. */ #define OID_FLAG_UTF8 2 /* A table mapping OIDs to a descriptive string. */ static struct { char *oid; char *name; unsigned int flag; /* A flag as described above. */ } oidtranstbl[] = { /* Algorithms. */ { "1.2.840.10040.4.1", "dsa" }, { "1.2.840.10040.4.3", "dsaWithSha1" }, { "1.2.840.113549.1.1.1", "rsaEncryption" }, { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, { "1.2.840.113549.1.1.3", "md4WithRSAEncryption" }, { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, { "1.2.840.113549.1.1.7", "rsaOAEP" }, { "1.2.840.113549.1.1.8", "rsaOAEP-MGF" }, { "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" }, { "1.2.840.113549.1.1.10", "rsaPSS" }, { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, { "1.3.14.3.2.26", "sha1" }, { "1.3.14.3.2.29", "sha-1WithRSAEncryption" }, { "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" }, /* Telesec extensions. */ { "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" }, { "0.2.262.1.10.12.1", "telesecCertIdExt" }, { "0.2.262.1.10.12.2", "telesecPolicyIdentifier" }, { "0.2.262.1.10.12.3", "telesecPolicyQualifierID" }, { "0.2.262.1.10.12.4", "telesecCRLFilteredExt" }, { "0.2.262.1.10.12.5", "telesecCRLFilterExt"}, { "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" }, #define OIDSTR_restriction \ "1.3.36.8.3.8" { OIDSTR_restriction, "restriction", OID_FLAG_UTF8 }, /* PKIX private extensions. */ { "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" }, { "1.3.6.1.5.5.7.1.2", "biometricInfo" }, { "1.3.6.1.5.5.7.1.3", "qcStatements" }, { "1.3.6.1.5.5.7.1.4", "acAuditIdentity" }, { "1.3.6.1.5.5.7.1.5", "acTargeting" }, { "1.3.6.1.5.5.7.1.6", "acAaControls" }, { "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" }, { "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" }, { "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" }, { "1.3.6.1.5.5.7.1.10", "acProxying" }, { "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" }, { "1.3.6.1.5.5.7.48.1", "ocsp" }, { "1.3.6.1.5.5.7.48.2", "caIssuers" }, { "1.3.6.1.5.5.7.48.3", "timeStamping" }, { "1.3.6.1.5.5.7.48.5", "caRepository" }, /* X.509 id-ce */ { "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP}, { "2.5.29.15", "keyUsage", OID_FLAG_SKIP}, { "2.5.29.16", "privateKeyUsagePeriod" }, { "2.5.29.17", "subjectAltName", OID_FLAG_SKIP}, { "2.5.29.18", "issuerAltName", OID_FLAG_SKIP}, { "2.5.29.19", "basicConstraints", OID_FLAG_SKIP}, { "2.5.29.20", "cRLNumber" }, { "2.5.29.21", "cRLReason" }, { "2.5.29.22", "expirationDate" }, { "2.5.29.23", "instructionCode" }, { "2.5.29.24", "invalidityDate" }, { "2.5.29.27", "deltaCRLIndicator" }, { "2.5.29.28", "issuingDistributionPoint" }, { "2.5.29.29", "certificateIssuer" }, { "2.5.29.30", "nameConstraints" }, { "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP}, { "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP}, { "2.5.29.32.0", "anyPolicy" }, { "2.5.29.33", "policyMappings" }, { "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP}, { "2.5.29.36", "policyConstraints" }, { "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP}, { "2.5.29.46", "freshestCRL" }, { "2.5.29.54", "inhibitAnyPolicy" }, /* Netscape certificate extensions. */ { "2.16.840.1.113730.1.1", "netscape-cert-type" }, { "2.16.840.1.113730.1.2", "netscape-base-url" }, { "2.16.840.1.113730.1.3", "netscape-revocation-url" }, { "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" }, { "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" }, { "2.16.840.1.113730.1.8", "netscape-ca-policy-url" }, { "2.16.840.1.113730.1.9", "netscape-homePage-url" }, { "2.16.840.1.113730.1.10", "netscape-entitylogo" }, { "2.16.840.1.113730.1.11", "netscape-userPicture" }, { "2.16.840.1.113730.1.12", "netscape-ssl-server-name" }, { "2.16.840.1.113730.1.13", "netscape-comment" }, /* GnuPG extensions */ { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" }, { "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" }, { "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" }, /* Extensions used by the Bundesnetzagentur. */ { "1.3.6.1.4.1.8301.3.5", "validityModel" }, { NULL } }; /* Return the description for OID; if no description is available NULL is returned. */ static const char * get_oid_desc (const char *oid, unsigned int *flag) { int i; if (oid) for (i=0; oidtranstbl[i].oid; i++) if (!strcmp (oidtranstbl[i].oid, oid)) { if (flag) *flag = oidtranstbl[i].flag; return oidtranstbl[i].name; } if (flag) *flag = 0; return NULL; } static void print_key_data (ksba_cert_t cert, estream_t fp) { #if 0 int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; int i; for(i=0; i < n; i++ ) { es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); mpi_print(stdout, pk->pkey[i], 1 ); putchar(':'); putchar('\n'); } #else (void)cert; (void)fp; #endif } static void print_capabilities (ksba_cert_t cert, estream_t fp) { gpg_error_t err; unsigned int use; size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!err && buflen) { if (*buffer) es_putc ('q', fp); } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { es_putc ('e', fp); es_putc ('s', fp); es_putc ('c', fp); es_putc ('E', fp); es_putc ('S', fp); es_putc ('C', fp); return; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); return; } if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) es_putc ('e', fp); if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) es_putc ('s', fp); if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_putc ('c', fp); if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) es_putc ('E', fp); if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) es_putc ('S', fp); if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_putc ('C', fp); es_putc (':', fp); } static void print_time (gnupg_isotime_t t, estream_t fp) { if (!t || !*t) ; else es_fputs (t, fp); } /* Return an allocated string with the email address extracted from a DN. Note hat we use this code also in ../kbx/keybox-blob.c. */ static char * email_kludge (const char *name) { const char *p, *string; unsigned char *buf; int n; string = name; for (;;) { p = strstr (string, "1.2.840.113549.1.9.1=#"); if (!p) return NULL; if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\')) { name = p + 22; break; } string = p + 22; } /* This looks pretty much like an email address in the subject's DN we use this to add an additional user ID entry. This way, OpenSSL generated keys get a nicer and usable listing. */ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) ; if (!n) return NULL; buf = xtrymalloc (n+3); if (!buf) return NULL; /* oops, out of core */ *buf = '<'; for (n=1, p=name; hexdigitp (p); p +=2, n++) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; return (char*)buf; } /* Print the compliance flags to field 18. ALGO is the gcrypt algo * number. NBITS is the length of the key in bits. */ static void print_compliance_flags (int algo, unsigned int nbits, estream_t fp) { if (algo == GCRY_PK_RSA && nbits >= 2048) es_fputs ("23", fp); } /* List one certificate in colon mode */ static void list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, estream_t fp, int have_secret) { int rc; int idx; char truststring[2]; char *p; ksba_sexp_t sexp; char *fpr; ksba_isotime_t t; gpg_error_t valerr; int algo; unsigned int nbits; const char *chain_id; char *chain_id_buffer = NULL; int is_root = 0; char *kludge_uid; if (ctrl->with_validation) valerr = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, NULL, 0, NULL); else valerr = 0; /* We need to get the fingerprint and the chaining ID in advance. */ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); { ksba_cert_t next; rc = gpgsm_walk_cert_chain (ctrl, cert, &next); if (!rc) /* We known the issuer's certificate. */ { p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); chain_id_buffer = p; chain_id = chain_id_buffer; ksba_cert_release (next); } else if (rc == -1) /* We have reached the root certificate. */ { chain_id = fpr; is_root = 1; } else chain_id = NULL; } es_fputs (have_secret? "crs:":"crt:", fp); /* Note: We can't use multiple flags, like "ei", because the validation check does only return one error. */ truststring[0] = 0; truststring[1] = 0; if ((validity & VALIDITY_REVOKED) || gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED) *truststring = 'r'; else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED) *truststring = 'e'; else { /* Lets also check whether the certificate under question expired. This is merely a hack until we found a proper way to store the expiration flag in the keybox. */ ksba_isotime_t current_time, not_after; gnupg_get_isotime (current_time); if (!opt.ignore_expiration && !ksba_cert_get_validity (cert, 1, not_after) && *not_after && strcmp (current_time, not_after) > 0 ) *truststring = 'e'; else if (valerr) { if (gpgsm_cert_has_well_known_private_key (cert)) *truststring = 'w'; /* Well, this is dummy CA. */ else *truststring = 'i'; } else if (ctrl->with_validation && !is_root) *truststring = 'f'; } /* If we have no truststring yet (i.e. the certificate might be good) and this is a root certificate, we ask the agent whether this is a trusted root certificate. */ if (!*truststring && is_root) { struct rootca_flags_s dummy_flags; if (gpgsm_cert_has_well_known_private_key (cert)) *truststring = 'w'; /* Well, this is dummy CA. */ else { rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags); if (!rc) *truststring = 'u'; /* Yes, we trust this one (ultimately). */ else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) *truststring = 'n'; /* No, we do not trust this one. */ /* (in case of an error we can't tell anything.) */ } } if (*truststring) es_fputs (truststring, fp); algo = gpgsm_get_key_algo_info (cert, &nbits); es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24); ksba_cert_get_validity (cert, 0, t); print_time (t, fp); es_putc (':', fp); ksba_cert_get_validity (cert, 1, t); print_time ( t, fp); es_putc (':', fp); /* Field 8, serial number: */ if ((sexp = ksba_cert_get_serial (cert))) { int len; const unsigned char *s = sexp; if (*s == '(') { s++; for (len=0; *s && *s != ':' && digitp (s); s++) len = len*10 + atoi_1 (s); if (*s == ':') for (s++; len; len--, s++) es_fprintf (fp,"%02X", *s); } xfree (sexp); } es_putc (':', fp); /* Field 9, ownertrust - not used here */ es_putc (':', fp); /* field 10, old user ID - we use it here for the issuer DN */ if ((p = ksba_cert_get_issuer (cert,0))) { es_write_sanitized (fp, p, strlen (p), ":", NULL); xfree (p); } es_putc (':', fp); /* Field 11, signature class - not used */ es_putc (':', fp); /* Field 12, capabilities: */ print_capabilities (cert, fp); /* Field 13, not used: */ es_putc (':', fp); /* Field 14, not used: */ es_putc (':', fp); if (have_secret || ctrl->with_secret) { char *cardsn; p = gpgsm_get_keygrip_hexstring (cert); if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && (cardsn || ctrl->with_secret)) { /* Field 15: Token serial number or secret key indicator. */ if (cardsn) es_fputs (cardsn, fp); else if (ctrl->with_secret) es_putc ('+', fp); } xfree (cardsn); xfree (p); } es_putc (':', fp); /* End of field 15. */ es_putc (':', fp); /* End of field 16. */ es_putc (':', fp); /* End of field 17. */ print_compliance_flags (algo, nbits, fp); es_putc (':', fp); /* End of field 18. */ es_putc ('\n', fp); /* FPR record */ es_fprintf (fp, "fpr:::::::::%s:::", fpr); /* Print chaining ID (field 13)*/ if (chain_id) es_fputs (chain_id, fp); es_putc (':', fp); es_putc ('\n', fp); xfree (fpr); fpr = NULL; chain_id = NULL; xfree (chain_id_buffer); chain_id_buffer = NULL; if (opt.with_key_data) { if ( (p = gpgsm_get_keygrip_hexstring (cert))) { es_fprintf (fp, "grp:::::::::%s:\n", p); xfree (p); } print_key_data (cert, fp); } kludge_uid = NULL; for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) { /* In the case that the same email address is in the subject DN as well as in an alternate subject name we avoid printing it a second time. */ if (kludge_uid && !strcmp (kludge_uid, p)) continue; es_fprintf (fp, "uid:%s::::::::", truststring); es_write_sanitized (fp, p, strlen (p), ":", NULL); es_putc (':', fp); es_putc (':', fp); es_putc ('\n', fp); if (!idx) { /* It would be better to get the faked email address from the keydb. But as long as we don't have a way to pass the meta data back, we just check it the same way as the code used to create the keybox meta data does */ kludge_uid = email_kludge (p); if (kludge_uid) { es_fprintf (fp, "uid:%s::::::::", truststring); es_write_sanitized (fp, kludge_uid, strlen (kludge_uid), ":", NULL); es_putc (':', fp); es_putc (':', fp); es_putc ('\n', fp); } } xfree (p); } xfree (kludge_uid); } static void print_name_raw (estream_t fp, const char *string) { if (!string) es_fputs ("[error]", fp); else es_write_sanitized (fp, string, strlen (string), NULL, NULL); } static void print_names_raw (estream_t fp, int indent, ksba_name_t name) { int idx; const char *s; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; if (!name) { es_fputs ("none\n", fp); return; } for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); es_fprintf (fp, "%*s", idx||indent_all?indent:0, ""); es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL); es_putc ('\n', fp); xfree (p); } } static void print_utf8_extn_raw (estream_t fp, int indent, const unsigned char *der, size_t derlen) { gpg_error_t err; int class, tag, constructed, ndef; size_t objlen, hdrlen; if (indent < 0) indent = - indent; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err)); return; } es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der); } static void print_utf8_extn (estream_t fp, int indent, const unsigned char *der, size_t derlen) { gpg_error_t err; int class, tag, constructed, ndef; size_t objlen, hdrlen; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { es_fprintf (fp, "%*s[%s%s]\n", indent_all? indent:0, "", _("Error - "), gpg_strerror (err)); return; } es_fprintf (fp, "%*s\"", indent_all? indent:0, ""); /* Fixme: we should implement word wrapping */ es_write_sanitized (fp, der, objlen, "\"", NULL); es_fputs ("\"\n", fp); } /* List one certificate in raw mode useful to have a closer look at the certificate. This one does no beautification and only minimal output sanitation. It is mainly useful for debugging. */ static void list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, estream_t fp, int have_secret, int with_validation) { gpg_error_t err; size_t off, len; ksba_sexp_t sexp, keyid; char *dn; ksba_isotime_t t; int idx, i; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; const char *oid, *s; ksba_name_t name, name2; unsigned int reason; const unsigned char *cert_der = NULL; (void)have_secret; es_fprintf (fp, " ID: 0x%08lX\n", gpgsm_get_short_fingerprint (cert, NULL)); sexp = ksba_cert_get_serial (cert); es_fputs (" S/N: ", fp); gpgsm_print_serial (fp, sexp); ksba_free (sexp); es_putc ('\n', fp); dn = ksba_cert_get_issuer (cert, 0); es_fputs (" Issuer: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { es_fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); es_fputs (" Subject: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { es_fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = gpgsm_get_fingerprint_string (cert, 0); es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_certid (cert); es_fprintf (fp, " certid: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_keygrip_hexstring (cert); es_fprintf (fp, " keygrip: %s\n", dn?dn:"error"); xfree (dn); ksba_cert_get_validity (cert, 0, t); es_fputs (" notBefore: ", fp); gpgsm_print_time (fp, t); es_putc ('\n', fp); es_fputs (" notAfter: ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); es_putc ('\n', fp); oid = ksba_cert_get_digest_algo (cert); s = get_oid_desc (oid, NULL); es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":""); { const char *algoname; unsigned int nbits; algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); es_fprintf (fp, " keyType: %u bit %s\n", nbits, algoname? algoname:"?"); } /* subjectKeyIdentifier */ es_fputs (" subjKeyId: ", fp); err = ksba_cert_get_subj_key_id (cert, NULL, &keyid); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) es_fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, keyid); ksba_free (keyid); es_putc ('\n', fp); } } else es_fputs ("[?]\n", fp); /* authorityKeyIdentifier */ es_fputs (" authKeyId: ", fp); err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name) es_fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, sexp); ksba_free (sexp); es_putc ('\n', fp); print_names_raw (fp, -15, name); ksba_name_release (name); } if (keyid) { es_fputs (" authKeyId.ki: ", fp); gpgsm_print_serial (fp, keyid); ksba_free (keyid); es_putc ('\n', fp); } } else es_fputs ("[?]\n", fp); es_fputs (" keyUsage:", fp); err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) es_fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) es_fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) es_fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) es_fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) es_fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) es_fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) es_fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) es_fputs (" decipherOnly", fp); } es_putc ('\n', fp); } else es_fputs (" [none]\n", fp); es_fputs (" extKeyUsage: ", fp); err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); p = pend; if (*p != 'C') es_fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs ("\n ", fp); } } xfree (string); } es_putc ('\n', fp); } else es_fputs ("[none]\n", fp); es_fputs (" policies: ", fp); err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; es_fputs (p, fp); p = pend; if (*p == 'C') es_fputs (" (critical)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs ("\n ", fp); } } xfree (string); } es_putc ('\n', fp); } else es_fputs ("[none]\n", fp); es_fputs (" chainLength: ", fp); err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) es_fprintf (fp, "[none]"); else if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) es_fputs ("unlimited", fp); else es_fprintf (fp, "%d", chainlen); es_putc ('\n', fp); } else es_fputs ("not a CA\n", fp); /* CRL distribution point */ for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2, &reason)) ;idx++) { es_fputs (" crlDP: ", fp); print_names_raw (fp, 15, name); if (reason) { es_fputs (" reason: ", fp); if ( (reason & KSBA_CRLREASON_UNSPECIFIED)) es_fputs (" unused", fp); if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE)) es_fputs (" keyCompromise", fp); if ( (reason & KSBA_CRLREASON_CA_COMPROMISE)) es_fputs (" caCompromise", fp); if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED)) es_fputs (" affiliationChanged", fp); if ( (reason & KSBA_CRLREASON_SUPERSEDED)) es_fputs (" superseded", fp); if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION)) es_fputs (" cessationOfOperation", fp); if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD)) es_fputs (" certificateHold", fp); es_putc ('\n', fp); } es_fputs (" issuer: ", fp); print_names_raw (fp, 23, name2); ksba_name_release (name); ksba_name_release (name2); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" crlDP: [error]\n", fp); else if (!idx) es_fputs (" crlDP: [none]\n", fp); /* authorityInfoAccess. */ for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string, &name)); idx++) { es_fputs (" authInfo: ", fp); s = get_oid_desc (string, NULL); es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" authInfo: [error]\n", fp); else if (!idx) es_fputs (" authInfo: [none]\n", fp); /* subjectInfoAccess. */ for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string, &name)); idx++) { es_fputs (" subjectInfo: ", fp); s = get_oid_desc (string, NULL); es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" subjInfo: [error]\n", fp); else if (!idx) es_fputs (" subjInfo: [none]\n", fp); for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &i, &off, &len));idx++) { unsigned int flag; s = get_oid_desc (oid, &flag); if ((flag & OID_FLAG_SKIP)) continue; es_fprintf (fp, " %s: %s%s%s%s [%d octets]\n", i? "critExtn":" extn", oid, s?" (":"", s?s:"", s?")":"", (int)len); if ((flag & OID_FLAG_UTF8)) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); assert (cert_der); print_utf8_extn_raw (fp, -15, cert_der+off, len); } } if (with_validation) { err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL); if (!err) es_fprintf (fp, " [certificate is good]\n"); else es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } if (hd) { unsigned int blobflags; err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags); if (err) es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err)); else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL)) es_fprintf (fp, " [stored as ephemeral]\n"); } } /* List one certificate in standard mode */ static void list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret, int with_validation) { gpg_error_t err; ksba_sexp_t sexp; char *dn; ksba_isotime_t t; int idx, i; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; size_t off, len; const char *oid; const unsigned char *cert_der = NULL; es_fprintf (fp, " ID: 0x%08lX\n", gpgsm_get_short_fingerprint (cert, NULL)); sexp = ksba_cert_get_serial (cert); es_fputs (" S/N: ", fp); gpgsm_print_serial (fp, sexp); ksba_free (sexp); es_putc ('\n', fp); dn = ksba_cert_get_issuer (cert, 0); es_fputs (" Issuer: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { es_fputs (" aka: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); es_fputs (" Subject: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { es_fputs (" aka: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); } ksba_cert_get_validity (cert, 0, t); es_fputs (" validity: ", fp); gpgsm_print_time (fp, t); es_fputs (" through ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); es_putc ('\n', fp); { const char *algoname; unsigned int nbits; algoname = gcry_pk_algo_name (gpgsm_get_key_algo_info (cert, &nbits)); es_fprintf (fp, " key type: %u bit %s\n", nbits, algoname? algoname:"?"); } err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs (" key usage:", fp); if (err) es_fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) es_fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) es_fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) es_fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) es_fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) es_fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) es_fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) es_fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) es_fputs (" decipherOnly", fp); } es_putc ('\n', fp); } err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs ("ext key usage: ", fp); if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; for (i=0; key_purpose_map[i].oid; i++) if ( !strcmp (key_purpose_map[i].oid, p) ) break; es_fputs (key_purpose_map[i].oid?key_purpose_map[i].name:p, fp); p = pend; if (*p != 'C') es_fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs (", ", fp); } } xfree (string); } es_putc ('\n', fp); } /* Print restrictions. */ for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, NULL, &off, &len));idx++) { if (!strcmp (oid, OIDSTR_restriction) ) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); assert (cert_der); es_fputs (" restriction: ", fp); print_utf8_extn (fp, 15, cert_der+off, len); } } /* Print policies. */ err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs (" policies: ", fp); if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { for (p=string; *p; p++) { if (*p == '\n') *p = ','; } es_write_sanitized (fp, string, strlen (string), NULL, NULL); xfree (string); } es_putc ('\n', fp); } err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { es_fputs (" chain length: ", fp); if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) es_fprintf (fp, "none"); else if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) es_fputs ("unlimited", fp); else es_fprintf (fp, "%d", chainlen); es_putc ('\n', fp); } if (opt.with_md5_fingerprint) { dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error"); xfree (dn); } dn = gpgsm_get_fingerprint_string (cert, 0); es_fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); xfree (dn); if (opt.with_keygrip) { dn = gpgsm_get_keygrip_hexstring (cert); if (dn) { es_fprintf (fp, " keygrip: %s\n", dn); xfree (dn); } } if (have_secret) { char *cardsn; p = gpgsm_get_keygrip_hexstring (cert); if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn) es_fprintf (fp, " card s/n: %s\n", cardsn); xfree (cardsn); xfree (p); } if (with_validation) { gpg_error_t tmperr; size_t buflen; char buffer[1]; err = gpgsm_validate_chain (ctrl, cert, "", NULL, 1, fp, 0, NULL); tmperr = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!tmperr && buflen) { if (*buffer) es_fputs (" [qualified]\n", fp); } else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (tmperr)); if (!err) es_fprintf (fp, " [certificate is good]\n"); else es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } } /* Same as standard mode list all certifying certs too. */ static void list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, int raw_mode, estream_t fp, int with_validation) { ksba_cert_t next = NULL; if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, 0, with_validation); else list_cert_std (ctrl, cert, fp, 0, with_validation); ksba_cert_ref (cert); while (!gpgsm_walk_cert_chain (ctrl, cert, &next)) { ksba_cert_release (cert); es_fputs ("Certified by\n", fp); if (raw_mode) list_cert_raw (ctrl, hd, next, fp, 0, with_validation); else list_cert_std (ctrl, next, fp, 0, with_validation); cert = next; } ksba_cert_release (cert); es_putc ('\n', fp); } /* List all internal keys or just the keys given as NAMES. MODE is a bit vector to specify what keys are to be included; see gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw output mode will be used instead of the standard beautified one. */ static gpg_error_t list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode, int raw_mode) { KEYDB_HANDLE hd; KEYDB_SEARCH_DESC *desc = NULL; strlist_t sl; int ndesc; ksba_cert_t cert = NULL; ksba_cert_t lastcert = NULL; gpg_error_t rc = 0; const char *lastresname, *resname; int have_secret; int want_ephemeral = ctrl->with_ephemeral_keys; hd = keydb_new (); if (!hd) { log_error ("keydb_new failed\n"); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { rc = gpg_error_from_syserror (); log_error ("out of core\n"); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = classify_user_id (sl->d, desc+ndesc, 0); if (rc) { log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc)); rc = 0; } else ndesc++; } } /* If all specifications are done by fingerprint or keygrip, we switch to ephemeral mode so that _all_ currently available and matching certificates are listed. */ if (!want_ephemeral && names && ndesc) { int i; for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 || desc[i].mode == KEYDB_SEARCH_MODE_FPR16 || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) want_ephemeral = 1; } if (want_ephemeral) keydb_set_ephemeral (hd, 1); /* It would be nice to see which of the given users did actually match one in the keyring. To implement this we need to have a found flag for each entry in desc and to set this we must check all those entries after a match to mark all matched one - currently we stop at the first match. To do this we need an extra flag to enable this feature so */ /* Suppress duplicates at least when they follow each other. */ lastresname = NULL; while (!(rc = keydb_search (ctrl, hd, desc, ndesc))) { unsigned int validity; if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity); if (rc) { log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); goto leave; } /* Skip duplicated certificates, at least if they follow each others. This works best if a single key is searched for and expected. FIXME: Non-sequential duplicates remain. */ if (gpgsm_certs_identical_p (cert, lastcert)) { ksba_cert_release (cert); cert = NULL; continue; } resname = keydb_get_resource_name (hd); if (lastresname != resname ) { int i; if (ctrl->no_server) { es_fprintf (fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) es_putc ('-', fp); es_putc ('\n', fp); lastresname = resname; } } have_secret = 0; if (mode) { char *p = gpgsm_get_keygrip_hexstring (cert); if (p) { rc = gpgsm_agent_havekey (ctrl, p); if (!rc) have_secret = 1; else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY) goto leave; rc = 0; xfree (p); } } if (!mode || ((mode & 1) && !have_secret) || ((mode & 2) && have_secret) ) { if (ctrl->with_colons) list_cert_colon (ctrl, cert, validity, fp, have_secret); else if (ctrl->with_chain) list_cert_chain (ctrl, hd, cert, raw_mode, fp, ctrl->with_validation); else { if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, have_secret, ctrl->with_validation); else list_cert_std (ctrl, cert, fp, have_secret, ctrl->with_validation); es_putc ('\n', fp); } } ksba_cert_release (lastcert); lastcert = cert; cert = NULL; } if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 ) rc = 0; if (rc) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); leave: ksba_cert_release (cert); ksba_cert_release (lastcert); xfree (desc); keydb_release (hd); return rc; } static void list_external_cb (void *cb_value, ksba_cert_t cert) { struct list_external_parm_s *parm = cb_value; if (keydb_store_cert (parm->ctrl, cert, 1, NULL)) log_error ("error storing certificate as ephemeral\n"); if (parm->print_header) { const char *resname = "[external keys]"; int i; es_fprintf (parm->fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) es_putc('-', parm->fp); es_putc ('\n', parm->fp); parm->print_header = 0; } if (parm->with_colons) list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0); else if (parm->with_chain) list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0); else { if (parm->raw_mode) list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0); else list_cert_std (parm->ctrl, cert, parm->fp, 0, 0); es_putc ('\n', parm->fp); } } /* List external keys similar to internal one. Note: mode does not make sense here because it would be unwise to list external secret keys */ static gpg_error_t list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode) { int rc; struct list_external_parm_s parm; parm.fp = fp; parm.ctrl = ctrl, parm.print_header = ctrl->no_server; parm.with_colons = ctrl->with_colons; parm.with_chain = ctrl->with_chain; parm.raw_mode = raw_mode; rc = gpgsm_dirmngr_lookup (ctrl, names, 0, list_external_cb, &parm); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = 0; /* "Not found" is not an error here. */ if (rc) log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); return rc; } /* List all keys or just the key given as NAMES. MODE controls the operation mode: Bit 0-2: 0 = list all public keys but don't flag secret ones 1 = list only public keys 2 = list only secret keys 3 = list secret and public keys Bit 6: list internal keys Bit 7: list external keys Bit 8: Do a raw format dump. */ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode) { gpg_error_t err = 0; if ((mode & (1<<6))) err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256)); if (!err && (mode & (1<<7))) err = list_external_keys (ctrl, names, fp, (mode&256)); return err; } diff --git a/sm/misc.c b/sm/misc.c index 1e2465f84..6d047763b 100644 --- a/sm/misc.c +++ b/sm/misc.c @@ -1,218 +1,218 @@ -/* misc.c - Miscellaneous fucntions +/* misc.c - Miscellaneous functions * Copyright (C) 2004, 2009, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <unistd.h> #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include "gpgsm.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/tlv.h" #include "../common/sexp-parse.h" /* Setup the environment so that the pinentry is able to get all required information. This is used prior to an exec of the protect-tool. */ void setup_pinentry_env (void) { #ifndef HAVE_W32_SYSTEM char *lc; const char *name, *value; int iterator; /* Try to make sure that GPG_TTY has been set. This is needed if we call for example the protect-tools with redirected stdin and thus it won't be able to ge a default by itself. Try to do it here but print a warning. */ value = session_env_getenv (opt.session_env, "GPG_TTY"); if (value) gnupg_setenv ("GPG_TTY", value, 1); else if (!(lc=getenv ("GPG_TTY")) || !*lc) { log_error (_("GPG_TTY has not been set - " "using maybe bogus default\n")); lc = gnupg_ttyname (0); if (!lc) lc = "/dev/tty"; gnupg_setenv ("GPG_TTY", lc, 1); } if (opt.lc_ctype) gnupg_setenv ("LC_CTYPE", opt.lc_ctype, 1); #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) else if ( (lc = setlocale (LC_CTYPE, "")) ) gnupg_setenv ("LC_CTYPE", lc, 1); #endif if (opt.lc_messages) gnupg_setenv ("LC_MESSAGES", opt.lc_messages, 1); #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) else if ( (lc = setlocale (LC_MESSAGES, "")) ) gnupg_setenv ("LC_MESSAGES", lc, 1); #endif iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { if (!strcmp (name, "GPG_TTY")) continue; /* Already set. */ value = session_env_getenv (opt.session_env, name); if (value) gnupg_setenv (name, value, 1); } #endif /*!HAVE_W32_SYSTEM*/ } /* Transform a sig-val style s-expression as returned by Libgcrypt to one which includes an algorithm identifier encoding the public key and the hash algorithm. The public key algorithm is taken directly from SIGVAL and the hash algorithm is given by MDALGO. This is required because X.509 merges the public key algorithm and the hash algorithm into one OID but Libgcrypt is not aware of that. The function ignores missing parameters so that it can also be used to create an siginfo value as expected by ksba_certreq_set_siginfo. To create a siginfo s-expression a public-key s-expression may be used instead of a sig-val. We only support RSA for now. */ gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, unsigned char **r_newsigval, size_t *r_newsigvallen) { gpg_error_t err; const unsigned char *buf, *tok; size_t buflen, toklen; int depth, last_depth1, last_depth2; int is_pubkey = 0; const unsigned char *rsa_s = NULL; size_t rsa_s_len = 0; const char *oid; gcry_sexp_t sexp; *r_newsigval = NULL; if (r_newsigvallen) *r_newsigvallen = 0; buf = sigval; buflen = sigvallen; depth = 0; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (tok && toklen == 7 && !memcmp ("sig-val", tok, toklen)) ; else if (tok && toklen == 10 && !memcmp ("public-key", tok, toklen)) is_pubkey = 1; else return gpg_error (GPG_ERR_UNKNOWN_SEXP); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen)) return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO); last_depth1 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth1) { if (tok) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (tok && toklen == 1) { const unsigned char **mpi; size_t *mpi_len; switch (*tok) { case 's': mpi = &rsa_s; mpi_len = &rsa_s_len; break; default: mpi = NULL; mpi_len = NULL; break; } if (mpi && *mpi) return gpg_error (GPG_ERR_DUP_VALUE); if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))) return err; if (tok && mpi) { *mpi = tok; *mpi_len = toklen; } } /* Skip to the end of the list. */ last_depth2 = depth; while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)) && depth && depth >= last_depth2) ; if (err) return err; } if (err) return err; /* Map the hash algorithm to an OID. */ switch (mdalgo) { case GCRY_MD_SHA1: oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */ break; case GCRY_MD_SHA256: oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */ break; case GCRY_MD_SHA384: oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */ break; case GCRY_MD_SHA512: oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */ break; default: return gpg_error (GPG_ERR_DIGEST_ALGO); } if (rsa_s && !is_pubkey) err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", oid, (int)rsa_s_len, rsa_s); else err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid); if (err) return err; err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen); gcry_sexp_release (sexp); return err; } diff --git a/sm/qualified.c b/sm/qualified.c index 718141edc..564e77929 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -1,325 +1,325 @@ /* qualified.c - Routines related to qualified signatures * Copyright (C) 2005, 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <assert.h> #include <errno.h> #include "gpgsm.h" #include "../common/i18n.h" #include <ksba.h> /* We open the file only once and keep the open file pointer as well as the name of the file here. Note that, a listname not equal to - NULL indicates that this module has been intialized and if the + NULL indicates that this module has been initialized and if the LISTFP is also NULL, no list of qualified signatures exists. */ static char *listname; static FILE *listfp; /* Read the trustlist and return entry by entry. KEY must point to a buffer of at least 41 characters. COUNTRY shall be a buffer of at least 3 characters to receive the country code of that qualified signature (i.e. "de" for German and "be" for Belgium). Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF and any other error condition is indicated by the appropriate error code. */ static gpg_error_t read_list (char *key, char *country, int *lnr) { gpg_error_t err; int c, i, j; char *p, line[256]; *key = 0; *country = 0; if (!listname) { listname = make_filename (gnupg_datadir (), "qualified.txt", NULL); listfp = fopen (listname, "r"); if (!listfp && errno != ENOENT) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), listname, gpg_strerror (err)); return err; } } if (!listfp) return gpg_error (GPG_ERR_EOF); do { if (!fgets (line, DIM(line)-1, listfp) ) { if (feof (listfp)) return gpg_error (GPG_ERR_EOF); return gpg_error_from_syserror (); } if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line. */ while ( (c=getc (listfp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } ++*lnr; /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) if ( p[i] != ':' ) key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; key[j] = 0; if (j != 40 || !(spacep (p+i) || p[i] == '\n')) { log_error (_("invalid formatted fingerprint in '%s', line %d\n"), listname, *lnr); return gpg_error (GPG_ERR_BAD_DATA); } assert (p[i]); i++; while (spacep (p+i)) i++; if ( p[i] >= 'a' && p[i] <= 'z' && p[i+1] >= 'a' && p[i+1] <= 'z' && (spacep (p+i+2) || p[i+2] == '\n')) { country[0] = p[i]; country[1] = p[i+1]; country[2] = 0; } else { log_error (_("invalid country code in '%s', line %d\n"), listname, *lnr); return gpg_error (GPG_ERR_BAD_DATA); } return 0; } /* Check whether the certificate CERT is included in the list of qualified certificates. This list is similar to the "trustlist.txt" as maintained by gpg-agent and includes fingerprints of root certificates to be used for qualified (legally binding like handwritten) signatures. We keep this list system wide and not per user because it is not a decision of the user. Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it is not in the list or any other error (e.g. if no list of qualified signatures is available. If COUNTRY has not been passed as NULL a string witha maximum length of 2 will be copied into it; thus the caller needs to provide a buffer of length 3. */ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) { gpg_error_t err; char *fpr; char key[41]; char mycountry[3]; int lnr = 0; (void)ctrl; if (country) *country = 0; fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (!fpr) return gpg_error (GPG_ERR_GENERAL); if (listfp) { /* W32ce has no rewind, thus we use the equivalent code. */ fseek (listfp, 0, SEEK_SET); clearerr (listfp); } while (!(err = read_list (key, mycountry, &lnr))) { if (!strcmp (key, fpr)) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); if (!err && country) strcpy (country, mycountry); xfree (fpr); return err; } /* We know that CERT is a qualified certificate. Ask the user for consent to actually create a signature using this certificate. Returns: 0 for yes, GPG_ERR_CANCEL for no or any other error code. */ gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; char *name, *subject, *buffer, *p; const char *s; char *orig_codeset = NULL; name = ksba_cert_get_subject (cert, 0); if (!name) return gpg_error (GPG_ERR_GENERAL); subject = gpgsm_format_name2 (name, 0); ksba_free (name); name = NULL; orig_codeset = i18n_switchto_utf8 (); if (asprintf (&name, _("You are about to create a signature using your " "certificate:\n" "\"%s\"\n" "This will create a qualified signature by law " "equated to a handwritten signature.\n\n%s%s" "Are you really sure that you want to do this?"), subject? subject:"?", opt.qualsig_approval? "": _("Note, that this software is not officially approved " "to create or verify such signatures.\n"), opt.qualsig_approval? "":"\n" ) < 0 ) err = gpg_error_from_syserror (); else err = 0; i18n_switchback (orig_codeset); xfree (subject); if (err) return err; buffer = p = xtrymalloc (strlen (name) * 3 + 1); if (!buffer) { err = gpg_error_from_syserror (); free (name); return err; } for (s=name; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; free (name); err = gpgsm_agent_get_confirmation (ctrl, buffer); xfree (buffer); return err; } /* Popup a prompt to inform the user that the signature created is not a qualified one. This is of course only done if we know that we have been approved. */ gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert) { gpg_error_t err; char *name, *subject, *buffer, *p; const char *s; char *orig_codeset; if (!opt.qualsig_approval) return 0; name = ksba_cert_get_subject (cert, 0); if (!name) return gpg_error (GPG_ERR_GENERAL); subject = gpgsm_format_name2 (name, 0); ksba_free (name); name = NULL; orig_codeset = i18n_switchto_utf8 (); if (asprintf (&name, _("You are about to create a signature using your " "certificate:\n" "\"%s\"\n" "Note, that this certificate will NOT create a " "qualified signature!"), subject? subject:"?") < 0 ) err = gpg_error_from_syserror (); else err = 0; i18n_switchback (orig_codeset); xfree (subject); if (err) return err; buffer = p = xtrymalloc (strlen (name) * 3 + 1); if (!buffer) { err = gpg_error_from_syserror (); free (name); return err; } for (s=name; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; free (name); err = gpgsm_agent_get_confirmation (ctrl, buffer); xfree (buffer); return err; } diff --git a/sm/server.c b/sm/server.c index 37d66e22c..64a3add9a 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1,1496 +1,1496 @@ /* server.c - Server mode and main entry point * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, * 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <ctype.h> #include <unistd.h> #include "gpgsm.h" #include <assuan.h> #include "../common/sysutils.h" #include "../common/server-help.h" #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* The filepointer for status message used in non-server mode */ static FILE *statusfp; /* Data used to assuciate an Assuan context with local server data */ struct server_local_s { assuan_context_t assuan_ctx; int message_fd; int list_internal; int list_external; int list_to_output; /* Write keylistings to the output fd. */ int enable_audit_log; /* Use an audit log. */ certlist_t recplist; certlist_t signerlist; certlist_t default_recplist; /* As set by main() - don't release. */ int allow_pinentry_notify; /* Set if pinentry notifications should be passed back to the client. */ int no_encrypt_to; /* Local version of option. */ }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; static int command_has_option (const char *cmd, const char *cmdopt); /* Note that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1; */ static void strcpy_escaped_plus (char *d, const char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 (s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, buffer, size)) { gpg_err_set_errno (EIO); return -1; } return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } static void close_message_fd (ctrl_t ctrl) { if (ctrl->server_local->message_fd != -1) { #ifdef HAVE_W32CE_SYSTEM #warning Is this correct for W32/W32CE? #endif close (ctrl->server_local->message_fd); ctrl->server_local->message_fd = -1; } } /* Start a new audit session if this has been enabled. */ static gpg_error_t start_audit_session (ctrl_t ctrl) { audit_release (ctrl->audit); ctrl->audit = NULL; if (ctrl->server_local->enable_audit_log && !(ctrl->audit = audit_new ()) ) return gpg_error_from_syserror (); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: <NAME> Delete envvar NAME <KEY>= Set envvar NAME to the empty string <KEY>=<VALUE> Set envvar NAME to VALUE */ err = session_env_putenv (opt.session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (opt.session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { err = session_env_setenv (opt.session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { err = session_env_setenv (opt.session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { xfree (opt.lc_ctype); opt.lc_ctype = xtrystrdup (value); if (!opt.lc_ctype) err = gpg_error_from_syserror (); } else if (!strcmp (key, "lc-messages")) { xfree (opt.lc_messages); opt.lc_messages = xtrystrdup (value); if (!opt.lc_messages) err = gpg_error_from_syserror (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (opt.session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "include-certs")) { int i = *value? atoi (value) : -1; if (ctrl->include_certs < -2) err = gpg_error (GPG_ERR_ASS_PARAMETER); else ctrl->include_certs = i; } else if (!strcmp (key, "list-mode")) { int i = *value? atoi (value) : 0; if (!i || i == 1) /* default and mode 1 */ { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 0; } else if (i == 2) { ctrl->server_local->list_internal = 0; ctrl->server_local->list_external = 1; } else if (i == 3) { ctrl->server_local->list_internal = 1; ctrl->server_local->list_external = 1; } else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "list-to-output")) { int i = *value? atoi (value) : 0; ctrl->server_local->list_to_output = i; } else if (!strcmp (key, "with-validation")) { int i = *value? atoi (value) : 0; ctrl->with_validation = i; } else if (!strcmp (key, "with-secret")) { int i = *value? atoi (value) : 0; ctrl->with_secret = i; } else if (!strcmp (key, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 2 ) ctrl->validation_model = i; else err = gpg_error (GPG_ERR_ASS_PARAMETER); } else if (!strcmp (key, "with-key-data")) { opt.with_key_data = 1; } else if (!strcmp (key, "enable-audit-log")) { int i = *value? atoi (value) : 0; ctrl->server_local->enable_audit_log = i; } else if (!strcmp (key, "allow-pinentry-notify")) { ctrl->server_local->allow_pinentry_notify = 1; } else if (!strcmp (key, "with-ephemeral-keys")) { int i = *value? atoi (value) : 0; ctrl->with_ephemeral_keys = i; } else if (!strcmp (key, "no-encrypt-to")) { ctrl->server_local->no_encrypt_to = 1; } else if (!strcmp (key, "offline")) { /* We ignore this option if gpgsm has been started with --disable-dirmngr (which also sets offline). */ if (!opt.disable_dirmngr) { int i = *value? !!atoi (value) : 1; ctrl->offline = i; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->signerlist); ctrl->server_local->recplist = NULL; ctrl->server_local->signerlist = NULL; close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static gpg_error_t input_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->autodetect_encoding = 0; ctrl->is_pem = 0; ctrl->is_base64 = 0; if (strstr (line, "--armor")) ctrl->is_pem = 1; else if (strstr (line, "--base64")) ctrl->is_base64 = 1; else if (strstr (line, "--binary")) ; else ctrl->autodetect_encoding = 1; return 0; } static gpg_error_t output_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); ctrl->create_pem = 0; ctrl->create_base64 = 0; if (strstr (line, "--armor")) ctrl->create_pem = 1; else if (strstr (line, "--base64")) ctrl->create_base64 = 1; /* just the raw output */ return 0; } static const char hlp_recipient[] = "RECIPIENT <userID>\n" "\n" "Set the recipient for the encryption. USERID shall be the\n" "internal representation of the key; the server may accept any other\n" "way of specification [we will support this]. If this is a valid and\n" "trusted recipient the server does respond with OK, otherwise the\n" "return is an ERR with the reason why the recipient can't be used,\n" "the encryption will then not be done for this recipient. If the\n" "policy is not to encrypt at all if not all recipients are valid, the\n" "client has to take care of this. All RECIPIENT commands are\n" "cumulative until a RESET or an successful ENCRYPT command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; if (!ctrl->audit) rc = start_audit_session (ctrl); else rc = 0; if (!rc) rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_signer[] = "SIGNER <userID>\n" "\n" "Set the signer's keys for the signature creation. USERID should\n" "be the internal representation of the key; the server may accept any\n" "other way of specification [we will support this]. If this is a\n" "valid and usable signing key the server does respond with OK,\n" "otherwise it returns an ERR with the reason why the key can't be\n" "used, the signing will then not be done for this key. If the policy\n" "is not to sign at all if not all signer keys are valid, the client\n" "has to take care of this. All SIGNER commands are cumulative until\n" "a RESET but they are *not* reset by an SIGN command because it can\n" "be expected that set of signers are used for more than one sign\n" "operation."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist, 0); if (rc) { gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), line, NULL); - /* For compatibiliy reasons we also issue the old code after the + /* For compatibility reasons we also issue the old code after the new one. */ gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), line, NULL); } return rc; } static const char hlp_encrypt[] = "ENCRYPT \n" "\n" "Do the actual encryption process. Takes the plaintext from the INPUT\n" "command, writes to the ciphertext to the file descriptor set with\n" "the OUTPUT command, take the recipients form all the recipients set\n" "so far. If this command fails the clients should try to delete all\n" "output currently done or otherwise mark it as invalid. GPGSM does\n" "ensure that there won't be any security problem with leftover data\n" "on the output in this case.\n" "\n" "This command should in general not fail, as all necessary checks\n" "have been done while setting the recipients. The input and output\n" "pipes are closed."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); certlist_t cl; int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); /* Now add all encrypt-to marked recipients from the default list. */ rc = 0; if (!opt.no_encrypt_to && !ctrl->server_local->no_encrypt_to) { for (cl=ctrl->server_local->default_recplist; !rc && cl; cl = cl->next) if (cl->is_encrypt_to) rc = gpgsm_add_cert_to_certlist (ctrl, cl->cert, &ctrl->server_local->recplist, 1); } if (!rc) rc = ctrl->audit? 0 : start_audit_session (ctrl); if (!rc) rc = gpgsm_encrypt (assuan_get_pointer (ctx), ctrl->server_local->recplist, inp_fd, out_fp); es_fclose (out_fp); gpgsm_release_certlist (ctrl->server_local->recplist); ctrl->server_local->recplist = NULL; /* Close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_decrypt[] = "DECRYPT\n" "\n" "This performs the decrypt operation after doing some check on the\n" "internal state. (e.g. that only needed data has been set). Because\n" "it utilizes the GPG-Agent for the session key decryption, there is\n" "no need to ask the client for a protecting passphrase - GPG-Agent\n" "does take care of this by requesting this from the user."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); es_fclose (out_fp); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_verify[] = "VERIFY\n" "\n" "This does a verify operation on the message send to the input FD.\n" "The result is written out using status lines. If an output FD was\n" "given, the signed text will be written to that.\n" "\n" "If the signature is a detached one, the server will inquire about\n" "the signed material and the client must provide it."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp = NULL; (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); if (out_fd != -1) { out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_verify (assuan_get_pointer (ctx), fd, ctrl->server_local->message_fd, out_fp); es_fclose (out_fp); /* Close and reset the fd. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_sign[] = "SIGN [--detached]\n" "\n" "Sign the data set with the INPUT command and write it to the sink\n" "set by OUTPUT. With \"--detached\", a detached signature is\n" "created (surprise)."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t out_fp; int detached; int rc; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); detached = has_option (line, "--detached"); out_fp = es_fdopen_nc (out_fd, "w"); if (!out_fp) return set_error (GPG_ERR_ASS_GENERAL, "fdopen() failed"); rc = start_audit_session (ctrl); if (!rc) rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, inp_fd, detached, out_fp); es_fclose (out_fp); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_import[] = "IMPORT [--re-import]\n" "\n" "Import the certificates read form the input-fd, return status\n" "message for each imported one. The import checks the validity of\n" "the certificate but not of the entire chain. It is possible to\n" "import expired certificates.\n" "\n" "With the option --re-import the input data is expected to a be a LF\n" "separated list of fingerprints. The command will re-import these\n" "certificates, meaning that they are made permanent by removing\n" "their ephemeral flag."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); int reimport = has_option (line, "--re-import"); (void)line; if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); rc = gpgsm_import (assuan_get_pointer (ctx), fd, reimport); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_export[] = "EXPORT [--data [--armor|--base64]] [--secret [--(raw|pkcs12)] [--] <pattern>\n" "\n" "Export the certificates selected by PATTERN. With --data the output\n" "is returned using Assuan D lines; the default is to use the sink given\n" "by the last \"OUTPUT\" command. The options --armor or --base64 encode \n" "the output using the PEM respective a plain base-64 format; the default\n" "is a binary format which is only suitable for a single certificate.\n" "With --secret the secret key is exported using the PKCS#8 format,\n" "with --raw using PKCS#1, and with --pkcs12 as full PKCS#12 container."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int use_data; int opt_secret; int opt_raw = 0; int opt_pkcs12 = 0; use_data = has_option (line, "--data"); if (use_data) { /* We need to override any possible setting done by an OUTPUT command. */ ctrl->create_pem = has_option (line, "--armor"); ctrl->create_base64 = has_option (line, "--base64"); } opt_secret = has_option (line, "--secret"); if (opt_secret) { opt_raw = has_option (line, "--raw"); opt_pkcs12 = has_option (line, "--pkcs12"); } line = skip_options (line); /* Break the line down into an strlist_t. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (opt_secret) { if (!list || !*list->d) return set_error (GPG_ERR_NO_DATA, "No key given"); if (list->next) return set_error (GPG_ERR_TOO_MANY, "Only one key allowed"); } if (use_data) { estream_t stream; stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!stream) { free_strlist (list); return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, stream, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, stream); es_fclose (stream); } else { int fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); estream_t out_fp; if (fd == -1) { free_strlist (list); return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); } out_fp = es_fdopen_nc (fd, "w"); if (!out_fp) { free_strlist (list); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } if (opt_secret) gpgsm_p12_export (ctrl, list->d, out_fp, opt_raw? 2 : opt_pkcs12 ? 0 : 1); else gpgsm_export (ctrl, list, out_fp); es_fclose (out_fp); } free_strlist (list); /* Close and reset the fds. */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return 0; } static const char hlp_delkeys[] = "DELKEYS <patterns>\n" "\n" "Delete the certificates specified by PATTERNS. Each pattern shall be\n" "a percent-plus escaped certificate specification. Usually a\n" "fingerprint will be used for this."; static gpg_error_t cmd_delkeys (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *p; strlist_t list, sl; int rc; /* break the line down into an strlist_t */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } rc = gpgsm_delete (ctrl, list); free_strlist (list); /* close and reset the fd */ close_message_fd (ctrl); assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_output[] = "OUTPUT FD[=<n>]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"INPUT\" and \"MESSAGE\" commands."; static const char hlp_input[] = "INPUT FD[=<n>]\n" "\n" "Set the file descriptor to read the input data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used. See also the\n" "\"MESSAGE\" and \"OUTPUT\" commands."; static const char hlp_message[] = "MESSAGE FD[=<n>]\n" "\n" "Set the file descriptor to read the message for a detached\n" "signatures to N. If N is not given and the operating system\n" "supports file descriptor passing, the file descriptor currently in\n" "flight will be used. See also the \"INPUT\" and \"OUTPUT\" commands."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { int rc; gnupg_fd_t sysfd; int fd; ctrl_t ctrl = assuan_get_pointer (ctx); rc = assuan_command_parse_fd (ctx, line, &sysfd); if (rc) return rc; #ifdef HAVE_W32CE_SYSTEM sysfd = _assuan_w32ce_finish_pipe ((int)sysfd, 0); if (sysfd == INVALID_HANDLE_VALUE) return set_error (gpg_err_code_from_syserror (), "rvid conversion failed"); #endif fd = translate_sys2libc_fd (sysfd, 0); if (fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); ctrl->server_local->message_fd = fd; return 0; } static const char hlp_listkeys[] = "LISTKEYS [<patterns>]\n" "LISTSECRETKEYS [<patterns>]\n" "DUMPKEYS [<patterns>]\n" "DUMPSECRETKEYS [<patterns>]\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" "pattern shall be a percent-plus escaped certificate specification.\n" "The \"SECRET\" versions of the command filter the output to include\n" "only certificates where the secret key is available or a corresponding\n" "smartcard has been registered. The \"DUMP\" versions of the command\n" "are only useful for debugging. The output format is a percent escaped\n" "colon delimited listing as described in the manual.\n" "\n" "These \"OPTION\" command keys effect the output::\n" "\n" " \"list-mode\" set to 0: List only local certificates (default).\n" " 1: Ditto.\n" " 2: List only external certificates.\n" " 3: List local and external certificates.\n" "\n" " \"with-validation\" set to true: Validate each certificate.\n" "\n" " \"with-ephemeral-key\" set to true: Always include ephemeral\n" " certificates.\n" "\n" " \"list-to-output\" set to true: Write output to the file descriptor\n" " given by the last \"OUTPUT\" command."; static int do_listkeys (assuan_context_t ctx, char *line, int mode) { ctrl_t ctrl = assuan_get_pointer (ctx); estream_t fp; char *p; strlist_t list, sl; unsigned int listmode; gpg_error_t err; /* Break the line down into an strlist. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { free_strlist (list); return out_of_core (); } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if (ctrl->server_local->list_to_output) { int outfd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if ( outfd == -1 ) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); fp = es_fdopen_nc (outfd, "w"); if (!fp) return set_error (gpg_err_code_from_syserror (), "es_fdopen() failed"); } else { fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } ctrl->with_colons = 1; listmode = mode; if (ctrl->server_local->list_internal) listmode |= (1<<6); if (ctrl->server_local->list_external) listmode |= (1<<7); err = gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); free_strlist (list); es_fclose (fp); if (ctrl->server_local->list_to_output) assuan_close_output_fd (ctx); return err; } static gpg_error_t cmd_listkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 3); } static gpg_error_t cmd_dumpkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 259); } static gpg_error_t cmd_listsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 2); } static gpg_error_t cmd_dumpsecretkeys (assuan_context_t ctx, char *line) { return do_listkeys (ctx, line, 258); } static const char hlp_genkey[] = "GENKEY\n" "\n" "Read the parameters in native format from the input fd and write a\n" "certificate request to the output."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int inp_fd, out_fd; estream_t in_stream, out_stream; int rc; (void)line; inp_fd = translate_sys2libc_fd (assuan_get_input_fd (ctx), 0); if (inp_fd == -1) return set_error (GPG_ERR_ASS_NO_INPUT, NULL); out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); in_stream = es_fdopen_nc (inp_fd, "r"); if (!in_stream) return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen failed"); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { es_fclose (in_stream); return set_error (gpg_err_code_from_syserror (), "fdopen() failed"); } rc = gpgsm_genkey (ctrl, in_stream, out_stream); es_fclose (out_stream); es_fclose (in_stream); /* close and reset the fds */ assuan_close_input_fd (ctx); assuan_close_output_fd (ctx); return rc; } static const char hlp_getauditlog[] = "GETAUDITLOG [--data] [--html]\n" "\n" "If --data is used, the output is send using D-lines and not to the\n" "file descriptor given by an OUTPUT command.\n" "\n" "If --html is used the output is formatted as an XHTML block. This is\n" "designed to be incorporated into a HTML document."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int out_fd; estream_t out_stream; int opt_data, opt_html; int rc; opt_data = has_option (line, "--data"); opt_html = has_option (line, "--html"); /* Not needed: line = skip_options (line); */ if (!ctrl->audit) return gpg_error (GPG_ERR_NO_DATA); if (opt_data) { out_stream = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!out_stream) return set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); } else { out_fd = translate_sys2libc_fd (assuan_get_output_fd (ctx), 1); if (out_fd == -1) return set_error (GPG_ERR_ASS_NO_OUTPUT, NULL); out_stream = es_fdopen_nc (out_fd, "w"); if (!out_stream) { return set_error (GPG_ERR_ASS_GENERAL, "es_fdopen() failed"); } } audit_print_result (ctrl->audit, out_stream, opt_html); rc = 0; es_fclose (out_stream); /* Close and reset the fd. */ if (!opt_data) assuan_close_output_fd (ctx); return rc; } static const char hlp_getinfo[] = "GETINFO <what>\n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " agent-check - Return success if the agent is running.\n" " cmd_has_option CMD OPT\n" " - Returns OK if the command CMD implements the option OPT.\n" " offline - Returns OK if the connection is in offline mode."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "agent-check")) { rc = gpgsm_agent_send_nop (ctrl); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_GENERAL); } } } } else if (!strcmp (line, "offline")) { rc = ctrl->offline? 0 : gpg_error (GPG_ERR_GENERAL); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_passwd[] = "PASSWD <userID>\n" "\n" "Change the passphrase of the secret key for USERID."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; char *grip = NULL; line = skip_options (line); err = gpgsm_find_cert (ctrl, line, NULL, &cert); if (err) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) err = gpg_error (GPG_ERR_INTERNAL); else { char *desc = gpgsm_format_keydesc (cert); err = gpgsm_agent_passwd (ctrl, grip, desc); xfree (desc); } xfree (grip); ksba_cert_release (cert); return err; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "IMPORT")) { if (!strcmp (cmdopt, "re-import")) return 1; } return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "RECIPIENT", cmd_recipient, hlp_recipient }, { "SIGNER", cmd_signer, hlp_signer }, { "ENCRYPT", cmd_encrypt, hlp_encrypt }, { "DECRYPT", cmd_decrypt, hlp_decrypt }, { "VERIFY", cmd_verify, hlp_verify }, { "SIGN", cmd_sign, hlp_sign }, { "IMPORT", cmd_import, hlp_import }, { "EXPORT", cmd_export, hlp_export }, { "INPUT", NULL, hlp_input }, { "OUTPUT", NULL, hlp_output }, { "MESSAGE", cmd_message, hlp_message }, { "LISTKEYS", cmd_listkeys, hlp_listkeys }, { "DUMPKEYS", cmd_dumpkeys, hlp_listkeys }, { "LISTSECRETKEYS",cmd_listsecretkeys, hlp_listkeys }, { "DUMPSECRETKEYS",cmd_dumpsecretkeys, hlp_listkeys }, { "GENKEY", cmd_genkey, hlp_genkey }, { "DELKEYS", cmd_delkeys, hlp_delkeys }, { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Startup the server. DEFAULT_RECPLIST is the list of recipients as set from the command line or config file. We only require those marked as encrypt-to. */ void gpgsm_server (certlist_t default_recplist) { int rc; assuan_fd_t filedes[2]; assuan_context_t ctx; struct server_control_s ctrl; static const char hello[] = ("GNU Privacy Guard's S/M server " VERSION " ready"); memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FILEDES in this case. */ #ifdef HAVE_W32CE_SYSTEM #define SERVER_STDIN es_fileno(es_stdin) #define SERVER_STDOUT es_fileno(es_stdout) #else #define SERVER_STDIN 0 #define SERVER_STDOUT 1 #endif filedes[0] = assuan_fdopen (SERVER_STDIN); filedes[1] = assuan_fdopen (SERVER_STDOUT); rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = assuan_init_pipe_server (ctx, filedes); if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror (rc)); gpgsm_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to the register commands with Assuan: %s\n", gpg_strerror(rc)); gpgsm_exit (2); } if (opt.verbose || opt.debug) { char *tmp; /* Fixme: Use the really used socket name. */ if (asprintf (&tmp, "Home: %s\n" "Config: %s\n" "DirmngrInfo: %s\n" "%s", gnupg_homedir (), opt.config_filename, dirmngr_socket_name (), hello) > 0) { assuan_set_hello_line (ctx, tmp); free (tmp); } } else assuan_set_hello_line (ctx, hello); assuan_register_reset_notify (ctx, reset_notify); assuan_register_input_notify (ctx, input_notify); assuan_register_output_notify (ctx, output_notify); assuan_register_option_handler (ctx, option_handler); assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; ctrl.server_local->list_internal = 1; ctrl.server_local->list_external = 0; ctrl.server_local->default_recplist = default_recplist; for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } gpgsm_release_certlist (ctrl.server_local->recplist); ctrl.server_local->recplist = NULL; gpgsm_release_certlist (ctrl.server_local->signerlist); ctrl.server_local->signerlist = NULL; xfree (ctrl.server_local); audit_release (ctrl.audit); ctrl.audit = NULL; assuan_release (ctx); } gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) { gpg_error_t err = 0; va_list arg_ptr; const char *text; va_start (arg_ptr, no); if (ctrl->no_server && ctrl->status_fd == -1) ; /* No status wanted. */ else if (ctrl->no_server) { if (!statusfp) { if (ctrl->status_fd == 1) statusfp = stdout; else if (ctrl->status_fd == 2) statusfp = stderr; else statusfp = fdopen (ctrl->status_fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", ctrl->status_fd, strerror(errno)); } } fputs ("[GNUPG:] ", statusfp); fputs (get_status_string (no), statusfp); while ( (text = va_arg (arg_ptr, const char*) )) { putc ( ' ', statusfp ); for (; *text; text++) { if (*text == '\n') fputs ( "\\n", statusfp ); else if (*text == '\r') fputs ( "\\r", statusfp ); else putc ( *(const byte *)text, statusfp ); } } putc ('\n', statusfp); fflush (statusfp); } else { assuan_context_t ctx = ctrl->server_local->assuan_ctx; char buf[950], *p; size_t n; p = buf; n = 0; while ( (text = va_arg (arg_ptr, const char *)) ) { if (n) { *p++ = ' '; n++; } for ( ; *text && n < DIM (buf)-2; n++) *p++ = *text++; } *p = 0; err = assuan_write_status (ctx, get_status_string (no), buf); } va_end (arg_ptr); return err; } gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text) { return gpgsm_status2 (ctrl, no, text, NULL); } gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec) { char buf[30]; sprintf (buf, "%u", (unsigned int)ec); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err) { char buf[30]; snprintf (buf, sizeof buf, "%u", err); if (text) return gpgsm_status2 (ctrl, no, text, buf, NULL); else return gpgsm_status2 (ctrl, no, buf, NULL); } /* Helper to notify the client about Pinentry events. Because that might disturb some older clients, this is only done when enabled via an option. Returns an gpg error code. */ gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } diff --git a/sm/sign.c b/sm/sign.c index a153b511f..e65562d54 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -1,788 +1,788 @@ /* sign.c - Sign a message * Copyright (C) 2001, 2002, 2003, 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <assert.h> #include "gpgsm.h" #include <gcrypt.h> #include <ksba.h> #include "keydb.h" #include "../common/i18n.h" /* Hash the data and return if something was hashed. Return -1 on error. */ static int hash_data (int fd, gcry_md_hd_t md) { estream_t fp; char buffer[4096]; int nread; int rc = 0; fp = es_fdopen_nc (fd, "rb"); if (!fp) { log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return -1; } do { nread = es_fread (buffer, 1, DIM(buffer), fp); gcry_md_write (md, buffer, nread); } while (nread); if (es_ferror (fp)) { log_error ("read error on fd %d: %s\n", fd, strerror (errno)); rc = -1; } es_fclose (fp); return rc; } static int hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer) { gpg_error_t err; estream_t fp; char buffer[4096]; int nread; int rc = 0; int any = 0; fp = es_fdopen_nc (fd, "rb"); if (!fp) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); return tmperr; } do { nread = es_fread (buffer, 1, DIM(buffer), fp); if (nread) { any = 1; gcry_md_write (md, buffer, nread); err = ksba_writer_write_octet_string (writer, buffer, nread, 0); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } } while (nread && !rc); if (es_ferror (fp)) { rc = gpg_error_from_syserror (); log_error ("read error on fd %d: %s\n", fd, strerror (errno)); } es_fclose (fp); if (!any) { /* We can't allow signing an empty message because it does not make much sense and more seriously, ksba_cms_build has already written the tag for data and now expects an octet string and an octet string of size 0 is illegal. */ log_error ("cannot sign an empty message\n"); rc = gpg_error (GPG_ERR_NO_DATA); } if (!rc) { err = ksba_writer_write_octet_string (writer, NULL, 0, 1); if (err) { log_error ("write failed: %s\n", gpg_strerror (err)); rc = err; } } return rc; } /* Get the default certificate which is defined as the first certificate capable of signing returned by the keyDB and has a secret key available. */ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert) { KEYDB_HANDLE hd; ksba_cert_t cert = NULL; int rc; char *p; hd = keydb_new (); if (!hd) return gpg_error (GPG_ERR_GENERAL); rc = keydb_search_first (ctrl, hd); if (rc) { keydb_release (hd); return rc; } do { rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); keydb_release (hd); return rc; } if (!gpgsm_cert_use_sign_p (cert)) { p = gpgsm_get_keygrip_hexstring (cert); if (p) { if (!gpgsm_agent_havekey (ctrl, p)) { xfree (p); keydb_release (hd); *r_cert = cert; return 0; /* got it */ } xfree (p); } } ksba_cert_release (cert); cert = NULL; } while (!(rc = keydb_search_next (ctrl, hd))); if (rc && rc != -1) log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); ksba_cert_release (cert); keydb_release (hd); return rc; } static ksba_cert_t get_default_signer (ctrl_t ctrl) { KEYDB_SEARCH_DESC desc; ksba_cert_t cert = NULL; KEYDB_HANDLE kh = NULL; int rc; if (!opt.local_user) { rc = gpgsm_get_default_cert (ctrl, &cert); if (rc) { if (rc != -1) log_debug ("failed to find default certificate: %s\n", gpg_strerror (rc)); return NULL; } return cert; } rc = classify_user_id (opt.local_user, &desc, 0); if (rc) { log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); return NULL; } kh = keydb_new (); if (!kh) return NULL; rc = keydb_search (ctrl, kh, &desc, 1); if (rc) { log_debug ("failed to find default certificate: rc=%d\n", rc); } else { rc = keydb_get_cert (kh, &cert); if (rc) { log_debug ("failed to get cert: rc=%d\n", rc); } } keydb_release (kh); return cert; } /* Depending on the options in CTRL add the certificate CERT as well as other certificate up in the chain to the Root-CA to the CMS object. */ static int add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert) { gpg_error_t err; int rc = 0; ksba_cert_t next = NULL; int n; int not_root = 0; ksba_cert_ref (cert); n = ctrl->include_certs; log_debug ("adding certificates at level %d\n", n); if (n == -2) { not_root = 1; n = -1; } if (n < 0 || n > 50) n = 50; /* We better apply an upper bound */ /* First add my own certificate unless we don't want any certificate included at all. */ if (n) { if (not_root && gpgsm_is_root_cert (cert)) err = 0; else err = ksba_cms_add_cert (cms, cert); if (err) goto ksba_failure; if (n>0) n--; } /* Walk the chain to include all other certificates. Note that a -1 used for N makes sure that there is no limit and all certs get included. */ while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, cert, &next)) ) { if (not_root && gpgsm_is_root_cert (next)) err = 0; else err = ksba_cms_add_cert (cms, next); ksba_cert_release (cert); cert = next; next = NULL; if (err) goto ksba_failure; } ksba_cert_release (cert); return rc == -1? 0: rc; ksba_failure: ksba_cert_release (cert); log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err)); return err; } /* Perform a sign operation. Sign the data received on DATA-FD in embedded mode or in detached mode when DETACHED is true. Write the signature to OUT_FP. The keys used to sign are taken from SIGNERLIST or the default one will be used if the value of this argument is NULL. */ int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, int data_fd, int detached, estream_t out_fp) { int i, rc; gpg_error_t err; gnupg_ksba_io_t b64writer = NULL; ksba_writer_t writer; ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; gcry_md_hd_t data_md = NULL; int signer; const char *algoid; int algo; ksba_isotime_t signed_at; certlist_t cl; int release_signerlist = 0; audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); kh = keydb_new (); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } ctrl->pem_name = "SIGNED MESSAGE"; rc = gnupg_ksba_create_writer (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), ctrl->pem_name, out_fp, &writer); if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); goto leave; } err = ksba_cms_new (&cms); if (err) { rc = err; goto leave; } err = ksba_cms_set_reader_writer (cms, NULL, writer); if (err) { log_debug ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* We are going to create signed data with data as encap. content */ err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); if (!err) err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); if (err) { log_debug ("ksba_cms_set_content_type failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } /* If no list of signers is given, use the default certificate. */ if (!signerlist) { ksba_cert_t cert = get_default_signer (ctrl); if (!cert) { log_error ("no default signer found\n"); gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } - /* Although we don't check for ambigious specification we will + /* Although we don't check for ambiguous specification we will check that the signer's certificate is usable and valid. */ rc = gpgsm_cert_use_sign_p (cert); if (!rc) rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL); if (rc) { char *tmpfpr; tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0); gpgsm_status2 (ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), tmpfpr, NULL); xfree (tmpfpr); goto leave; } /* That one is fine - create signerlist. */ signerlist = xtrycalloc (1, sizeof *signerlist); if (!signerlist) { rc = out_of_core (); ksba_cert_release (cert); goto leave; } signerlist->cert = cert; release_signerlist = 1; } /* Figure out the hash algorithm to use. We do not want to use the one for the certificate but if possible an OID for the plain algorithm. */ if (opt.forced_digest_algo && opt.verbose) log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo); for (i=0, cl=signerlist; cl; cl = cl->next, i++) { const char *oid; if (opt.forced_digest_algo) { oid = NULL; cl->hash_algo = opt.forced_digest_algo; } else { oid = ksba_cert_get_digest_algo (cl->cert); cl->hash_algo = oid ? gcry_md_map_name (oid) : 0; } switch (cl->hash_algo) { case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break; case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break; case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break; case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break; case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break; case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break; /* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */ case GCRY_MD_MD5: /* We don't want to use MD5. */ case 0: /* No algorithm found in cert. */ default: /* Other algorithms. */ log_info (_("hash algorithm %d (%s) for signer %d not supported;" " using %s\n"), cl->hash_algo, oid? oid: "?", i, gcry_md_algo_name (GCRY_MD_SHA1)); cl->hash_algo = GCRY_MD_SHA1; oid = "1.3.14.3.2.26"; break; } cl->hash_algo_oid = oid; } if (opt.verbose) { for (i=0, cl=signerlist; cl; cl = cl->next, i++) log_info (_("hash algorithm used for signer %d: %s (%s)\n"), i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid); } /* Gather certificates of signers and store them in the CMS object. */ for (cl=signerlist; cl; cl = cl->next) { rc = gpgsm_cert_use_sign_p (cl->cert); if (rc) goto leave; err = ksba_cms_add_signer (cms, cl->cert); if (err) { log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } rc = add_certificate_list (ctrl, cms, cl->cert); if (rc) { log_error ("failed to store list of certificates: %s\n", gpg_strerror(rc)); goto leave; } /* Set the hash algorithm we are going to use */ err = ksba_cms_add_digest_algo (cms, cl->hash_algo_oid); if (err) { log_debug ("ksba_cms_add_digest_algo failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* Check whether one of the certificates is qualified. Note that we already validated the certificate and thus the user data stored flag must be available. */ if (!opt.no_chain_validation) { for (cl=signerlist; cl; cl = cl->next) { size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cl->cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (err || !buflen) { log_error (_("checking for qualified certificate failed: %s\n"), gpg_strerror (err)); rc = err; goto leave; } if (*buffer) err = gpgsm_qualified_consent (ctrl, cl->cert); else err = gpgsm_not_qualified_warning (ctrl, cl->cert); if (err) { rc = err; goto leave; } } } /* Prepare hashing (actually we are figuring out what we have set above). */ rc = gcry_md_open (&data_md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_debug (data_md, "sign.data"); for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) { algo = gcry_md_map_name (algoid); if (!algo) { log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?"); rc = gpg_error (GPG_ERR_BUG); goto leave; } gcry_md_enable (data_md, algo); audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo); } audit_log (ctrl->audit, AUDIT_SETUP_READY); if (detached) { /* We hash the data right now so that we can store the message digest. ksba_cms_build() takes this as an flag that detached data is expected. */ unsigned char *digest; size_t digest_len; if (!hash_data (data_fd, data_md)) audit_log (ctrl->audit, AUDIT_GOT_DATA); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { digest = gcry_md_read (data_md, cl->hash_algo); digest_len = gcry_md_get_algo_dlen (cl->hash_algo); if ( !digest || !digest_len ) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } gnupg_get_isotime (signed_at); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { err = ksba_cms_set_signing_time (cms, signer, signed_at); if (err) { log_error ("ksba_cms_set_signing_time failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } /* We need to write at least a minimal list of our capabilities to try to convince some MUAs to use 3DES and not the crippled RC2. Our list is: aes128-CBC des-EDE3-CBC */ err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0); if (!err) err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0); if (err) { log_error ("ksba_cms_add_smime_capability failed: %s\n", gpg_strerror (err)); goto leave; } /* Main building loop. */ do { err = ksba_cms_build (cms, &stopreason); if (err) { log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) { /* Hash the data and store the message digest. */ unsigned char *digest; size_t digest_len; assert (!detached); rc = hash_and_copy_data (data_fd, data_md, writer); if (rc) goto leave; audit_log (ctrl->audit, AUDIT_GOT_DATA); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { digest = gcry_md_read (data_md, cl->hash_algo); digest_len = gcry_md_get_algo_dlen (cl->hash_algo); if ( !digest || !digest_len ) { log_error ("problem getting the hash of the data\n"); rc = gpg_error (GPG_ERR_BUG); goto leave; } err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); if (err) { log_error ("ksba_cms_set_message_digest failed: %s\n", gpg_strerror (err)); rc = err; goto leave; } } } else if (stopreason == KSBA_SR_NEED_SIG) { /* Compute the signature for all signers. */ gcry_md_hd_t md; rc = gcry_md_open (&md, 0, 0); if (rc) { log_error ("md_open failed: %s\n", gpg_strerror (rc)); goto leave; } if (DBG_HASHING) gcry_md_debug (md, "sign.attr"); ksba_cms_set_hash_function (cms, HASH_FNC, md); for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) { unsigned char *sigval = NULL; char *buf, *fpr; audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer); if (signer) gcry_md_reset (md); { certlist_t cl_tmp; for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next) { gcry_md_enable (md, cl_tmp->hash_algo); audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO, cl_tmp->hash_algo); } } rc = ksba_cms_hash_signed_attrs (cms, signer); if (rc) { log_debug ("hashing signed attrs failed: %s\n", gpg_strerror (rc)); gcry_md_close (md); goto leave; } rc = gpgsm_create_cms_signature (ctrl, cl->cert, md, cl->hash_algo, &sigval); if (rc) { audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc); gcry_md_close (md); goto leave; } err = ksba_cms_set_sig_val (cms, signer, sigval); xfree (sigval); if (err) { audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err); log_error ("failed to store the signature: %s\n", gpg_strerror (err)); rc = err; gcry_md_close (md); goto leave; } /* write a status message */ fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); if (!fpr) { rc = gpg_error (GPG_ERR_ENOMEM); gcry_md_close (md); goto leave; } rc = 0; { int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL); buf = xtryasprintf ("%c %d %d 00 %s %s", detached? 'D':'S', pkalgo, cl->hash_algo, signed_at, fpr); if (!buf) rc = gpg_error_from_syserror (); } xfree (fpr); if (rc) { gcry_md_close (md); goto leave; } gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); xfree (buf); audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0); } gcry_md_close (md); } } while (stopreason != KSBA_SR_READY); rc = gnupg_ksba_finish_writer (b64writer); if (rc) { log_error ("write failed: %s\n", gpg_strerror (rc)); goto leave; } audit_log (ctrl->audit, AUDIT_SIGNING_DONE); log_info ("signature created\n"); leave: if (rc) log_error ("error creating signature: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc) ); if (release_signerlist) gpgsm_release_certlist (signerlist); ksba_cms_release (cms); gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); return rc; } diff --git a/tests/gpgscm/tests.scm b/tests/gpgscm/tests.scm index 31189774a..c6c887fc6 100644 --- a/tests/gpgscm/tests.scm +++ b/tests/gpgscm/tests.scm @@ -1,762 +1,762 @@ ;; Common definitions for writing tests. ;; ;; Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. ;; Reporting. (define (echo . msg) (for-each (lambda (x) (display x) (display " ")) msg) (newline)) (define (info . msg) (apply echo msg) (flush-stdio)) (define (log . msg) (if (> (*verbose*) 0) (apply info msg))) (define (fail . msg) (apply info msg) (exit 1)) (define (skip . msg) (apply info msg) (exit 77)) (define (make-counter) (let ((c 0)) (lambda () (let ((r c)) (set! c (+ 1 c)) r)))) (define *progress-nesting* 0) (define (call-with-progress msg what) (set! *progress-nesting* (+ 1 *progress-nesting*)) (if (= 1 *progress-nesting*) (begin (info msg) (display " > ") (flush-stdio) (what (lambda (item) (display item) (display " ") (flush-stdio))) (info "< ")) (begin (what (lambda (item) (display ".") (flush-stdio))) (display " ") (flush-stdio))) (set! *progress-nesting* (- *progress-nesting* 1))) (define (for-each-p msg proc lst . lsts) (apply for-each-p' `(,msg ,proc ,(lambda (x . xs) x) ,lst ,@lsts))) (define (for-each-p' msg proc fmt lst . lsts) (call-with-progress msg (lambda (progress) (apply for-each `(,(lambda args (progress (apply fmt args)) (apply proc args)) ,lst ,@lsts))))) ;; Process management. (define CLOSED_FD -1) (define (call-with-fds what infd outfd errfd) (wait-process (stringify what) (spawn-process-fd what infd outfd errfd) #t)) (define (call what) (call-with-fds what CLOSED_FD (if (< (*verbose*) 0) STDOUT_FILENO CLOSED_FD) (if (< (*verbose*) 0) STDERR_FILENO CLOSED_FD))) ;; Accessor functions for the results of 'spawn-process'. (define :stdin car) (define :stdout cadr) (define :stderr caddr) (define :pid cadddr) (define (call-with-io what in) (let ((h (spawn-process what 0))) (es-write (:stdin h) in) (es-fclose (:stdin h)) (let* ((out (es-read-all (:stdout h))) (err (es-read-all (:stderr h))) (result (wait-process (car what) (:pid h) #t))) (es-fclose (:stdout h)) (es-fclose (:stderr h)) (if (> (*verbose*) 2) (info "Child" (:pid h) "returned:" `((command ,(stringify what)) (status ,result) (stdout ,out) (stderr ,err)))) (list result out err)))) ;; Accessor function for the results of 'call-with-io'. ':stdout' and ;; ':stderr' can also be used. (define :retcode car) (define (call-check what) (let ((result (call-with-io what ""))) (if (= 0 (:retcode result)) (:stdout result) (throw (string-append (stringify what) " failed") (:stderr result))))) (define (call-popen command input-string) (let ((result (call-with-io command input-string))) (if (= 0 (:retcode result)) (:stdout result) (throw (:stderr result))))) ;; ;; estream helpers. ;; (define (es-read-all stream) (let loop ((acc "")) (if (es-feof stream) acc (loop (string-append acc (es-read stream 4096)))))) ;; ;; File management. ;; (define (file-exists? name) (call-with-input-file name (lambda (port) #t))) (define (file=? a b) (file-equal a b #t)) (define (text-file=? a b) (file-equal a b #f)) (define (file-copy from to) (catch '() (unlink to)) (letfd ((source (open from (logior O_RDONLY O_BINARY))) (sink (open to (logior O_WRONLY O_CREAT O_BINARY) #o600))) (splice source sink))) (define (text-file-copy from to) (catch '() (unlink to)) (letfd ((source (open from O_RDONLY)) (sink (open to (logior O_WRONLY O_CREAT) #o600))) (splice source sink))) (define (path-join . components) (let loop ((acc #f) (rest (filter (lambda (s) (not (string=? "" s))) components))) (if (null? rest) acc (loop (if (string? acc) (string-append acc "/" (car rest)) (car rest)) (cdr rest))))) (assert (string=? (path-join "foo" "bar" "baz") "foo/bar/baz")) (assert (string=? (path-join "" "bar" "baz") "bar/baz")) ;; Is PATH an absolute path? (define (absolute-path? path) (or (char=? #\/ (string-ref path 0)) (and *win32* (char=? #\\ (string-ref path 0))) (and *win32* (char-alphabetic? (string-ref path 0)) (char=? #\: (string-ref path 1)) (or (char=? #\/ (string-ref path 2)) (char=? #\\ (string-ref path 2)))))) ;; Make PATH absolute. (define (canonical-path path) (if (absolute-path? path) path (path-join (getcwd) path))) (define (in-srcdir . names) (canonical-path (apply path-join (cons (getenv "abs_top_srcdir") names)))) ;; Try to find NAME in PATHS. Returns the full path name on success, ;; or raises an error. (define (path-expand name paths) (let loop ((path paths)) (if (null? path) (throw "Could not find" name "in" paths) (let* ((qualified-name (path-join (car path) name)) (file-exists (call-with-input-file qualified-name (lambda (x) #t)))) (if file-exists qualified-name (loop (cdr path))))))) ;; Expand NAME using the gpgscm load path. Use like this: ;; (load (with-path "library.scm")) (define (with-path name) (catch name (path-expand name (string-split (getenv "GPGSCM_PATH") *pathsep*)))) (define (basename path) (let ((i (string-index path #\/))) (if (equal? i #f) path (basename (substring path (+ 1 i) (string-length path)))))) (define (basename-suffix path suffix) (basename (if (string-suffix? path suffix) (substring path 0 (- (string-length path) (string-length suffix))) path))) (define (dirname path) (let ((i (string-rindex path #\/))) (if i (substring path 0 i) "."))) ;; Helper for (pipe). (define :read-end car) (define :write-end cadr) ;; let-like macro that manages file descriptors. ;; ;; (letfd <bindings> <body>) ;; ;; Bind all variables given in <bindings> and initialize each of them -;; to the given initial value, and close them after evaluting <body>. +;; to the given initial value, and close them after evaluating <body>. (define-macro (letfd bindings . body) (let bind ((bindings' bindings)) (if (null? bindings') `(begin ,@body) (let* ((binding (car bindings')) (name (car binding)) (initializer (cadr binding))) `(let ((,name ,initializer)) (finally (close ,name) ,(bind (cdr bindings')))))))) (define-macro (with-working-directory new-directory . expressions) (let ((new-dir (gensym)) (old-dir (gensym))) `(let* ((,new-dir ,new-directory) (,old-dir (getcwd))) (dynamic-wind (lambda () (if ,new-dir (chdir ,new-dir))) (lambda () ,@expressions) (lambda () (chdir ,old-dir)))))) ;; Make a temporary directory. If arguments are given, they are ;; joined using path-join, and must end in a component ending in ;; "XXXXXX". If no arguments are given, a suitable location and ;; generic name is used. Returns an absolute path. (define (mkdtemp . components) (canonical-path (_mkdtemp (if (null? components) (path-join (get-temp-path) (string-append "gpgscm-" (get-isotime) "-" (basename-suffix *scriptname* ".scm") "-XXXXXX")) (apply path-join components))))) ;; Make a temporary directory and remove it at interpreter shutdown. ;; Note that there are macros that limit the lifetime of temporary ;; directories and files to a lexical scope. Use those if possible. ;; Otherwise this works like mkdtemp. (define (mkdtemp-autoremove . components) (let ((dir (apply mkdtemp components))) (atexit (lambda () (unlink-recursively dir))) dir)) (define-macro (with-temporary-working-directory . expressions) (let ((tmp-sym (gensym))) `(let* ((,tmp-sym (mkdtemp))) (finally (unlink-recursively ,tmp-sym) (with-working-directory ,tmp-sym ,@expressions))))) (define (make-temporary-file . args) (canonical-path (path-join (mkdtemp) (if (null? args) "a" (car args))))) (define (remove-temporary-file filename) (catch '() (unlink filename)) (let ((dirname (substring filename 0 (string-rindex filename #\/)))) (catch (echo "removing temporary directory" dirname "failed") (rmdir dirname)))) ;; let-like macro that manages temporary files. ;; ;; (lettmp <bindings> <body>) ;; ;; Bind all variables given in <bindings>, initialize each of them to ;; a string representing an unique path in the filesystem, and delete -;; them after evaluting <body>. +;; them after evaluating <body>. (define-macro (lettmp bindings . body) (let bind ((bindings' bindings)) (if (null? bindings') `(begin ,@body) (let ((name (car bindings')) (rest (cdr bindings'))) `(let ((,name (make-temporary-file ,(symbol->string name)))) (finally (remove-temporary-file ,name) ,(bind rest))))))) (define (check-execution source transformer) (lettmp (sink) (transformer source sink))) (define (check-identity source transformer) (lettmp (sink) (transformer source sink) (if (not (file=? source sink)) (fail "mismatch")))) ;; ;; Monadic pipe support. ;; (define pipeM (package (define (new procs source sink producer) (package (define (dump) (write (list procs source sink producer)) (newline)) (define (add-proc command pid) (new (cons (list command pid) procs) source sink producer)) (define (commands) (map car procs)) (define (pids) (map cadr procs)) (define (set-source source') (new procs source' sink producer)) (define (set-sink sink') (new procs source sink' producer)) (define (set-producer producer') (if producer (throw "producer already set")) (new procs source sink producer')))))) (define (pipe:do . commands) (let loop ((M (pipeM::new '() CLOSED_FD CLOSED_FD #f)) (cmds commands)) (if (null? cmds) (begin (if M::producer (M::producer)) (if (not (null? M::procs)) (let* ((retcodes (wait-processes (map stringify (M::commands)) (M::pids) #t)) (results (map (lambda (p r) (append p (list r))) M::procs retcodes)) (failed (filter (lambda (x) (not (= 0 (caddr x)))) results))) (if (not (null? failed)) (throw failed))))) ; xxx nicer reporting (if (and (= 2 (length cmds)) (number? (cadr cmds))) ;; hack: if it's an fd, use it as sink (let ((M' ((car cmds) (M::set-sink (cadr cmds))))) (if (> M::source 2) (close M::source)) (if (> (cadr cmds) 2) (close (cadr cmds))) (loop M' '())) (let ((M' ((car cmds) M))) (if (> M::source 2) (close M::source)) (loop M' (cdr cmds))))))) (define (pipe:open pathname flags) (lambda (M) (M::set-source (open pathname flags)))) (define (pipe:defer producer) (lambda (M) (let* ((p (outbound-pipe)) (M' (M::set-source (:read-end p)))) (M'::set-producer (lambda () (producer (:write-end p)) (close (:write-end p))))))) (define (pipe:echo data) (pipe:defer (lambda (sink) (display data (fdopen sink "wb"))))) (define (pipe:spawn command) (lambda (M) (define (do-spawn M new-source) (let ((pid (spawn-process-fd command M::source M::sink (if (> (*verbose*) 0) STDERR_FILENO CLOSED_FD))) (M' (M::set-source new-source))) (M'::add-proc command pid))) (if (= CLOSED_FD M::sink) (let* ((p (pipe)) (M' (do-spawn (M::set-sink (:write-end p)) (:read-end p)))) (close (:write-end p)) (M'::set-sink CLOSED_FD)) (do-spawn M CLOSED_FD)))) (define (pipe:splice sink) (lambda (M) (splice M::source sink) (M::set-source CLOSED_FD))) (define (pipe:write-to pathname flags mode) (open pathname flags mode)) ;; ;; Monadic transformer support. ;; (define (tr:do . commands) (let loop ((tmpfiles '()) (source #f) (cmds commands)) (if (null? cmds) (for-each remove-temporary-file tmpfiles) (let* ((v ((car cmds) tmpfiles source)) (tmpfiles' (car v)) (sink (cadr v)) (error (caddr v))) (if error (begin (for-each remove-temporary-file tmpfiles') (apply throw error))) (loop tmpfiles' sink (cdr cmds)))))) (define (tr:open pathname) (lambda (tmpfiles source) (list tmpfiles pathname #f))) (define (tr:spawn input command) (lambda (tmpfiles source) (if (and (member '**in** command) (not source)) (fail (string-append (stringify cmd) " needs an input"))) (let* ((t (make-temporary-file)) (cmd (map (lambda (x) (cond ((equal? '**in** x) source) ((equal? '**out** x) t) (else x))) command))) (catch (list (cons t tmpfiles) t *error*) (call-popen cmd input) (if (and (member '**out** command) (not (file-exists? t))) (fail (string-append (stringify cmd) " did not produce '" t "'."))) (list (cons t tmpfiles) t #f))))) (define (tr:write-to pathname) (lambda (tmpfiles source) (rename source pathname) (list tmpfiles pathname #f))) (define (tr:pipe-do . commands) (lambda (tmpfiles source) (let ((t (make-temporary-file))) (apply pipe:do `(,@(if source `(,(pipe:open source (logior O_RDONLY O_BINARY))) '()) ,@commands ,(pipe:write-to t (logior O_WRONLY O_BINARY O_CREAT) #o600))) (list (cons t tmpfiles) t #f)))) (define (tr:assert-identity reference) (lambda (tmpfiles source) (if (not (file=? source reference)) (fail "mismatch")) (list tmpfiles source #f))) (define (tr:assert-weak-identity reference) (lambda (tmpfiles source) (if (not (text-file=? source reference)) (fail "mismatch")) (list tmpfiles source #f))) (define (tr:call-with-content function . args) (lambda (tmpfiles source) (catch (list tmpfiles source *error*) (apply function `(,(call-with-input-file source read-all) ,@args))) (list tmpfiles source #f))) ;; ;; Developing and debugging tests. ;; ;; Spawn an os shell. (define (interactive-shell) (call-with-fds `(,(getenv "SHELL") -i) 0 1 2)) ;; ;; The main test framework. ;; ;; A pool of tests. (define test-pool (package (define (new procs) (package (define (add test) (set! procs (cons test procs)) (current-environment)) (define (pid->test pid) (let ((t (filter (lambda (x) (= pid x::pid)) procs))) (if (null? t) #f (car t)))) (define (wait) (let ((unfinished (filter (lambda (t) (not t::retcode)) procs))) (if (null? unfinished) (current-environment) (let ((names (map (lambda (t) t::name) unfinished)) (pids (map (lambda (t) t::pid) unfinished))) (for-each (lambda (test retcode) (test::set-end-time!) (test:::set! 'retcode retcode)) (map pid->test pids) (wait-processes (map stringify names) pids #t))))) (current-environment)) (define (passed) (filter (lambda (p) (= 0 p::retcode)) procs)) (define (skipped) (filter (lambda (p) (= 77 p::retcode)) procs)) (define (hard-errored) (filter (lambda (p) (= 99 p::retcode)) procs)) (define (failed) (filter (lambda (p) (not (or (= 0 p::retcode) (= 77 p::retcode) (= 99 p::retcode)))) procs)) (define (report) (define (print-tests tests message) (unless (null? tests) (apply echo (cons message (map (lambda (t) t::name) tests))))) (let ((failed' (failed)) (skipped' (skipped))) (echo (length procs) "tests run," (length (passed)) "succeeded," (length failed') "failed," (length skipped') "skipped.") (print-tests failed' "Failed tests:") (print-tests skipped' "Skipped tests:") (length failed'))) (define (xml) (xx::document (xx::tag 'testsuites `((xmlns:xsi "http://www.w3.org/2001/XMLSchema-instance") ("xsi:noNamespaceSchemaLocation" "https://windyroad.com.au/dl/Open%20Source/JUnit.xsd")) (map (lambda (t) (t::xml)) procs)))))))) (define (verbosity n) (if (= 0 n) '() (cons '--verbose (verbosity (- n 1))))) (define (locate-test path) (if (absolute-path? path) path (in-srcdir path))) ;; A single test. (define test (begin ;; Private definitions. (define (isotime->junit t) "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}" "20170418T145809" (string-append (substring t 0 4) "-" (substring t 4 6) "-" (substring t 6 11) ":" (substring t 11 13) ":" (substring t 13 15))) (package (define (scm setup name path . args) ;; Start the process. (define (spawn-scm args' in out err) (spawn-process-fd `(,*argv0* ,@(verbosity (*verbose*)) ,(locate-test path) ,@(if setup (force setup) '()) ,@args' ,@args) in out err)) (new name #f spawn-scm #f #f CLOSED_FD)) (define (binary setup name path . args) ;; Start the process. (define (spawn-binary args' in out err) (spawn-process-fd `(,path ,@(if setup (force setup) '()) ,@args' ,@args) in out err)) (new name #f spawn-binary #f #f CLOSED_FD)) (define (new name directory spawn pid retcode logfd) (package ;; XXX: OO glue. (define self (current-environment)) (define (:set! key value) (eval `(set! ,key ,value) (current-environment)) (current-environment)) ;; The log is written here. (define log-file-name "not set") ;; Record time stamps. (define timestamp #f) (define start-time 0) (define end-time 0) (define (set-start-time!) (set! timestamp (isotime->junit (get-isotime))) (set! start-time (get-time))) (define (set-end-time!) (set! end-time (get-time))) (define (open-log-file) (set! log-file-name (string-append (basename name) ".log")) (catch '() (unlink log-file-name)) (open log-file-name (logior O_RDWR O_BINARY O_CREAT) #o600)) (define (run-sync . args) (set-start-time!) (letfd ((log (open-log-file))) (with-working-directory directory (let* ((p (inbound-pipe)) (pid' (spawn args 0 (:write-end p) (:write-end p)))) (close (:write-end p)) (splice (:read-end p) STDERR_FILENO log) (close (:read-end p)) (set! pid pid') (set! retcode (wait-process name pid' #t))))) (report) (current-environment)) (define (run-sync-quiet . args) (set-start-time!) (with-working-directory directory (set! pid (spawn args CLOSED_FD CLOSED_FD CLOSED_FD))) (set! retcode (wait-process name pid #t)) (set-end-time!) (current-environment)) (define (run-async . args) (set-start-time!) (let ((log (open-log-file))) (with-working-directory directory (set! pid (spawn args CLOSED_FD log log))) (set! logfd log)) (current-environment)) (define (status) (let ((t (assoc retcode '((0 PASS) (77 SKIP) (99 ERROR))))) (if (not t) 'FAIL (cadr t)))) (define (status-string) (cadr (assoc (status) '((PASS "PASS") (SKIP "SKIP") (ERROR "ERROR") (FAIL "FAIL"))))) (define (report) (unless (= logfd CLOSED_FD) (seek logfd 0 SEEK_SET) (splice logfd STDERR_FILENO) (close logfd)) (echo (string-append (status-string) ":") name)) (define (xml) (xx::tag 'testsuite `((name ,name) (time ,(- end-time start-time)) (package ,(dirname name)) (id 0) (timestamp ,timestamp) (hostname "unknown") (tests 1) (failures ,(if (eq? FAIL (status)) 1 0)) (errors ,(if (eq? ERROR (status)) 1 0))) (list (xx::tag 'properties) (xx::tag 'testcase `((name ,(basename name)) (classname ,(string-translate (dirname name) "/" ".")) (time ,(- end-time start-time))) `(,@(case (status) ((PASS) '()) ((SKIP) (list (xx::tag 'skipped))) ((ERROR) (list (xx::tag 'error '((message "Unknown error."))))) (else (list (xx::tag 'failure '((message "Unknown error.")))))))) (xx::tag 'system-out '() (list (xx::textnode (read-all (open-input-file log-file-name))))) (xx::tag 'system-err '() (list (xx::textnode ""))))))))))) ;; Run the setup target to create an environment, then run all given ;; tests in parallel. (define (run-tests-parallel tests) (let loop ((pool (test-pool::new '())) (tests' tests)) (if (null? tests') (let ((results (pool::wait))) (for-each (lambda (t) (t::report)) (reverse results::procs)) ((results::xml) (open-output-file "report.xml")) (exit (results::report))) (let ((wd (mkdtemp-autoremove)) (test (car tests'))) (test:::set! 'directory wd) (loop (pool::add (test::run-async)) (cdr tests')))))) ;; Run the setup target to create an environment, then run all given ;; tests in sequence. (define (run-tests-sequential tests) (let loop ((pool (test-pool::new '())) (tests' tests)) (if (null? tests') (let ((results (pool::wait))) ((results::xml) (open-output-file "report.xml")) (exit (results::report))) (let ((wd (mkdtemp-autoremove)) (test (car tests'))) (test:::set! 'directory wd) (loop (pool::add (test::run-sync)) (cdr tests')))))) ;; Helper to create environment caches from test functions. SETUP ;; must be a test implementing the producer side cache protocol. ;; Returns a promise containing the arguments that must be passed to a ;; test implementing the consumer side of the cache protocol. (define (make-environment-cache setup) (delay (with-temporary-working-directory (let ((tarball (make-temporary-file "environment-cache"))) (atexit (lambda () (remove-temporary-file tarball))) (setup::run-sync '--create-tarball tarball) `(--unpack-tarball ,tarball))))) ;; Command line flag handling. Returns the elements following KEY in ;; ARGUMENTS up to the next argument, or #f if KEY is not in ;; ARGUMENTS. (define (flag key arguments) (cond ((null? arguments) #f) ((string=? key (car arguments)) (let loop ((acc '()) (args (cdr arguments))) (if (or (null? args) (string-prefix? (car args) "--")) (reverse acc) (loop (cons (car args) acc) (cdr args))))) ((string=? "--" (car arguments)) #f) (else (flag key (cdr arguments))))) (assert (equal? (flag "--xxx" '("--yyy")) #f)) (assert (equal? (flag "--xxx" '("--xxx")) '())) (assert (equal? (flag "--xxx" '("--xxx" "yyy")) '("yyy"))) (assert (equal? (flag "--xxx" '("--xxx" "yyy" "zzz")) '("yyy" "zzz"))) (assert (equal? (flag "--xxx" '("--xxx" "yyy" "zzz" "--")) '("yyy" "zzz"))) (assert (equal? (flag "--xxx" '("--xxx" "yyy" "--" "zzz")) '("yyy"))) (assert (equal? (flag "--" '("--" "xxx" "yyy" "--" "zzz")) '("xxx" "yyy"))) diff --git a/tests/inittests b/tests/inittests index 1a51bdfc5..6fbccfb0f 100755 --- a/tests/inittests +++ b/tests/inittests @@ -1,99 +1,99 @@ #!/bin/sh # Copyright (C) 2002 Free Software Foundation, Inc. # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. set -e sample_certs=' cert_g10code_test1.pem cert_g10code_pete1.pem cert_g10code_theo1.pem ' private_keys=' 32100C27173EF6E9C4E9A25D3D69F86D37A4F939 ' clean_files=' gpgsm.conf gpg-agent.conf trustlist.txt pubring.kbx msg msg.sig msg.unsig ' [ -z "$srcdir" ] && srcdir=. [ -z "$GPGSM" ] && GPGSM=../sm/gpgsm if [ -d $srcdir/samplekeys ] \ && grep TESTS_ENVIRONMENT Makefile >/dev/null 2>&1; then : else # During make distclean the Makefile has already been removed, # so we need this extra test. if ! grep gnupg-test-directory testdir.stamp >/dev/null 2>&1; then echo "inittests: please cd to the tests directory first" >&2 exit 1 fi fi if [ "$1" = "--clean" ]; then if [ -d private-keys-v1.d ]; then rm private-keys-v1.d/* 2>/dev/null || true rmdir private-keys-v1.d fi rm ${clean_files} testdir.stamp 2>/dev/null || true exit 0 fi if [ "$GNUPGHOME" != "`/bin/pwd`" ]; then echo "inittests: please set GNUPGHOME to the test directory" >&2 exit 1 fi if [ -n "$GPG_AGENT_INFO" ]; then echo "inittests: please unset GPG_AGENT_INFO" >&2 exit 1 fi # A stamp file used with --clean echo gnupg-test-directory > testdir.stamp -# Create the private key directy if it does not exists and copy +# Create the private key directly if it does not exists and copy # the sample keys. [ -d private-keys-v1.d ] || mkdir private-keys-v1.d -for i in ${private_keys}; do +for i in ${private_keys}; do cat ${srcdir}/samplekeys/$i.key >private-keys-v1.d/$i.key done # Create the configuration scripts # Note, die to an expired test certificate, we need to use # the faked system time option. cat > gpgsm.conf <<EOF no-secmem-warning disable-crl-checks agent-program ../agent/gpg-agent faked-system-time 1038835799 EOF cat > gpg-agent.conf <<EOF no-grab pinentry-program /home/wk/work/pinentry/gtk/pinentry-gtk EOF cat > trustlist.txt <<EOF # CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Düsseldorf,C=DE 3CF405464F66ED4A7DF45BBDD1E4282E33BDB76E S EOF # Make sure that the sample certs are available but ignore errors here # because we are not a test script. -for i in ${sample_certs}; do +for i in ${sample_certs}; do $GPGSM --import ${srcdir}/samplekeys/$i || true done diff --git a/tests/openpgp/bug537-test.data.asc b/tests/openpgp/bug537-test.data.asc index 130dd5b9f..b6b02e9dd 100644 --- a/tests/openpgp/bug537-test.data.asc +++ b/tests/openpgp/bug537-test.data.asc @@ -1,960 +1,960 @@ This is a binary (gzip compressed) file which exhibits a problem with -the zlib decryptor. See encr-data.c:decrypt_data for a decription of +the zlib decryptor. See encr-data.c:decrypt_data for a description of the problem we solved with 1.9.92 (1.4.6). It is not easy to produce such files, but this one works. The source file is also in the BTS under the name check-data-410-1.data. The result of the decryption should yield a file with the SHA-1 hash 4336AE2A528FAE091E73E59E325B588FEE795F9B. -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.5 (GNU/Linux) hQEOA6urKKJHvid1EAP9ENEq6XQZEX7o40GuQBVRM0ZyYYV5p1nFrHqymikdMiFH QdiYfZQ3c82CZyx/AK5iBeIrHNTLmex9iIlyqMtf7Gw3BWXbEtMtol3Zcx/0ie3w QTpjl2qiPOmnO97cp1Ut5dheOUwmOuv6UTGtk0o1d6Cws2RTFJSi81GeCr4yQDoD /3qilIjJcskmLj2lTMz/Vnrnr7u9TmBiEBjcl1NutDudtuTWb58FXhlmqwHvNFBi t+/lIr4N/3mU2Z9Y+NFhJxY7R0Xvq20pNH44vhd8qxMt6N/yMxaDjY0tY0a8Hy6b Rj0TbVobRmWia4N+6ES5DzRaTOKWA2tVxk9VQptgU/FM0u0B8yvO6TfIaHOR7PnO 7j0+v7MfnRJZvjRe4S/hXJduNFLg8IPxV84KTxDRmc/QK3Sl8tQLHLAkDiQRFQSM Rm5hEzbigfYY+zOjDzWv1xbxfd348+fmALy1PszQN7qk16U85yZtx191O/d3P2gP zJcJzlfgQU1r8Od5qmrT4Lxe4N7DDIDbiB6MH9Wr3Vbm3jxqurUBDyPNrOLdsenF HbuEkfr5jtnYzTWTi0NCLajaRB0tTVa0rcOLUGEcLCXFt13wvPc+ZsS0NuJWYAu9 4+WVDyPS7tpGvUZ8Uc3HOzAgXs16m9vYN+OjI5u8IyX1shZF4Tv+bYLjxs4yZzvO DyltH1rmMYo7UMUu8vtM+hjhsQtvr/ervGQOzKTqd4txWPjmt4Lk0xk62NpDm6JD nn4P+lYHF8+EbhcPbMnvbhl7aP6IOdr7ij/f4kp/JOMhh+/ymW1Xl61E9Zf2uwT9 SOsqDgxK/bL+6ewt/OkE9+knet8ZWB2pNxSspznuv0QBAX8Xrg/ZiFy8yQNsDiu6 PVyXWqXRx/rZ1kwLB5QZSdMFLpBC5p0Ev9hiImVxsA5nX1BDglxM1SjVJpuEtrCL fK2Lky5qhm50oXxX9QVxg2btITm+fFZhM+dbEwBQxv6yiWJNcpi7PNBeNEhck9Xr fDOtz65+9cDMeHv78RNiPPewkeloiPmRWDLivX85bD6WQSDzJ9f1XhyY8eKOs8bz ymQJPtfQcBiNdI9HNTQP5pZkr4XAbOo5Ji5XHJ3JScIlpdYforit6m9orpgJpUJK K0PSNF5Yx7LlesKbJsYcuwQAvZuyrGEtTXH5rDgBgCcEHQaZR61tPu454wyLWnGI hj08LzYM4uzfmjDCQmISPY5KDSRAP5LpG/Iq5bX6yr4KUF8WrxJpBeJGmaRNUyJO P4p2Xth/uBzu40r60Z5gr1mqpR+cvlBbUiwMC4D5zWXiK24e3yRPoTzJn+J7g7De gG2P6crZgrpGsnnheYvcLin1BShctSzC7zMMql8yNKp5IYeFSd4fJmWcQ42RVf3E mQBhiXd7UUrBJcZkPL/+hxbvALBtBgNlebdwugC4+IBMcreM7TM+WilOr1A8fKcb Eu1EQZ0Rw3RMuw639GdFgE+EKgbzUFhKm9W9hdV6CLCddhlaFjFkMZWBQfMHUKHX xnHIjQxxjmThfVu8VyiJ4nfT9coLGoJNx0MbU3JNCVUzeBLuzNx8TWtvmK2ikzaY xIltoD1uRXOnYFsRrmg0+YWbtpP6FTsZQTRYpcy5MeHcyOIQmV3ieLB3yb1R7GAN FEKoCexaa0JJ3uMT8dza6Fi1Vt2Z1ZkVES3h4zfYg25FAspE7h/n1/W93dolUatb ch9wPHdjwHUghimQdoIuuRaFfbDpb6QqssJDtGrEFWm/XRii3Z08x0rtGeAdpcr6 qW6xW+rdZepPac3E8+eI6BB5Jq1IaVPD0OjySCRlFvsxIUOI2yBvBsJ47q1/6AtR zGRM7Y42B68ITEHdUCNnPbtK5G2U8XHTX98MdG2SYXAAELMhXM/zucy8AJ9kLE19 g4oVTLfpjh5/lJHcQjHBd/0lpE/BwmamKn0IF7YEJ2d6SVTDFMgo/tLS0Phu8Xs4 dHBexIX6Em+iGYZQBOFpqtDYs0OtdbjD2KAAYOI0l99TFMfAzHcf4uiZMHlONdB8 EWMQUzyLlN/5oWbihjiyPh41tZQtCQvC3vWB1mjtwEtcuEU3oqfbt42S19k+H1ok Ov0VxHX+N0kdc5IdBW7we7E0n9mSpouaSxmWIJVCt/Vhto8+F9/b20UrLF5pjnW+ erQYZ+T75BHkZyImncDOBvs1wEEqBlAK7nWW47mFKdgWeJfPTwr30BMR9F4OEnWc RLpO8No+5Kti7sGN2u+Ubo54nsTIb5AG1gcvH/mr/5FForcnbM44MHXDdeGGsQVd QYnTy+twSInhB9yPI+1DXH+dtcsvcDAc7w1F9TnBNbo50PYV0ppr237afDDSm/6B M2Zp7ye/LIWL1oRkqVuJPjPsuBtKFXNgwk97bO1agEZI433Z7BNV00mR+iAxPr2D Id1gultRXP0MZHrtUVwb+CCDbRtgBbJNcLALr8miog397dM4aEiRpf3xXAOjTZGX 1owuq1lRGd4NO2OhKwU33nzUwP8g2Uo/qDQc/P4tL6aLdUCnZkYrwHCP/lGK1ZIH 3edIiQ0afjWhVQnSoKq6raC0ddsn/h0nO/Qbm0i1m+oEsBYAGgLgYIc/iYH5bi9G jSze5BeXmU1s+Dyj3pse0T5lnDzpvRmx2i5d5umB3CSq2L7Z9vSlyzwMrNFpsOng lvH/rCxYhrKJAitj2PeJ9db/cH3flXwvyi5diYOWWjqufbZc7yckzN1VNEkiLdZk NRo3ECEtJ+0IozKG8mY3hwhclOPx/zW/3R9zrcW8clr+9d1fvq6zwK7ezwdtcO/W nOWGQZnsv4FLrgVxjrBrehQrYTfNC4ZR4u/fTqhuvfNG6cdhgavnpAbBhOuvYxZX C33SkNEtYZaUKr77HMAavGkwxFU02yNR8mkJamvpI7A2J2gDdFvK4Ua8KeXjWzqV XTblQVSw4viPFdB3PH9X2h4cr6/jhCXRPyiDIhV+VvfOowXVSH7fWXgRdwQ8lSUI IQJ1yucF5U78jAOYMYt1uXRBoRqyIJeAeFKrr6cTh5ieg+ErWsLRGFnKAcET8lzd XcX144LliJfTP/yw0LV2V5XisSLTN3tiKUMTw3ZFcZE2LSExw4Z7sGF3r022nL3z m1fAU403g0vcEqNFWWFu8i504EcGvxQcFeDYKUlE4zbMpl8X7WxPja8Rld/xiVUy Lq6SQ1QlSD22VMt8EYIkCC8q+1P5P/sIFXsFpVu3Mpih124evD1JRc7F2QKIzCuQ HSxHzTVB3DlkblMSxTLcSULWtobMcyTrxS8/6uOLGlX1kYfnEIZzDbSG19xEc6OF osqoREXcq/r3Tr4gMNV1Ql7XqmzlTwOBRUJxcTdo0/b0nJVC/5rt8qnSxve3btx9 DDS4vufEUuBl0aZz7+o9Ai5ht48T9/7p4r+hMso86cqfModHD4DLBRT4/HiFmSZo n9+V94q6JCJCANZnemVvS4V6LWGkabfHUbuTyDAFNDGnupSdbgd6tFJPxx2bl/jE nsSqU1iSZ2ugpad1hkEEcW5aUk6IhsES1LfpaMdDgQF9t4jKh5EWp97UjEO3aAfU KGff3uKl47G7HVUdig0ErCcIiChLkzRN68zAetHV4MvfKC6AuQmMOO0DIKN1mASR lad99OXO7Vfl1T4lCw71XKnnO1zWGFYqNIuxp3/18DtRBGwiJQ2faPzawIOap/7Q 2ce3QJwf6eevo7vP5BICrJZ4KcnRbeEVH5eFh/y4VnAg/tI4JbSK6PisDE1gmuWc vGbIN1pdKQsRCBo2axzZxFotqCrRSJh+WrfUbur1WDJJVoyI+1/kQIs3KpkJYCJV vwTtGDlhiy4PMegBnZaBfRjQF8s7gZVhpeWxF8T6/kiYKUm6NFXBuF4kgwC2S9aS 937B4MNZWPEAYGGjCgNqnRbO3QkbSzL1C/sQ+k7CbBqWQTNeGDeMutf1gQVyfxuf Bs+U/cXm295Vhl49Vk/zZDgF6ZrIVnKnRUQRKVa0u7mFkGLecCvScfEOS1MHw+cU JP5dv2oNAkIythmmoT3KSVR9Jij33azonLpP70xYUQ7JC4kDKSoYuK6WpbgbSmYI Aznxi5EKft8M/fqumXX5aG9D3sp84Sn8wKDXQNtN3D+UG3pINhIIhsee2ACHlSQc 4FDsQwjleBrMLODQ3i7rMUeC1NQOndFo5oVHyWJPzUGDSsIoi/X//3JxMmyZyWzK lpRUmugRcBNf3avy1PIS5cgCYa/oCApCbFv10pdUw5VxbN3qwL67C92pxjlyeyad Y9Bz1bEUZUBB/APYdn2hEzu3m76LoNHJ6D0gZrS75IcmEsX6QdVj3rXgZ7K4wCM8 lmcWuwdtu5AA9yl69MnRO4iv2tNgVZ3RMImOO/9/8rQ5BB/GaPDiqDqQ1xr3dknd JDf/9Ij2kvN+LVZEiQwGUXqouXbrMB8DBximotOOz07vOe5WUNVuizzv9dz2Jx0o rIV8pst1fFIOt4ZuQ0Oonbya+h3k06JugMXKzdfyNC/bTaYWQP8HIAVqTscgYmCW /gCjRVMcYCvdnlG7jePFAk9vKcV7MnrCvLAAd36CsJ7lU0e4VPiguCuuBb9lhqtG w5shRAPXcMlmABwiycu9MyYuqGEegWesE+XHrsWY5tImf+4bVPjf+O0c2ibRFfJ4 uSKKMaByMwM75dEDpGW6ujRcioOu842Bzf1KcvTISl5GHajAJq3bFzlyddXl8Qp3 zArbcGl8oSrehBwBXlZ8lbLh2pIxq4oWNmT3ychQbzz0X4ZqZArOpNzlKImdIY2i MX9hhI5JmZIOnFsNh0Z2JQEsw4Sb2y2fsKTB72Tgcax05MXPl3lYdMn0C8REbZD1 u165yRsUTaddI2tT5c3nadwULODgv1MG3kGFrShWwI3rZ4vqr1ou8KT/znIe79wk lgkzBbeyvQ4OGXUxCHYWBzTCwemtSmaRWNlt3ip86q3jQMvPzSZq5kpcWksltzyn 0eEqM67HKW3VhKYNYqricBJ9vueNrrKnZF9gA06tIsJdcBsi5j4PtbLwE7EXWDFR GdSB2KnvIyyWm/X+wmFtlg0VGvyIMF4CKzsB23GohponuR111EDLZAhGRGtkJMM6 ufUlR/qfiO2TDpk9oBZza02+IEHsz3iQH1oWXAA9rnYLQ2P7Q8JmcT+z8yJVV9+I HGcATWdK+6tbzCuPqU5duBe+xO0ILh1pGncORxOdM+6YHmIPGbM8UY9DFk3EGQ3a fXpNeKXdy+AtINTRlpUQcbWkiG604lchNt9EnNp8A7SSUrq/OQbio3dzIdJcatNn n4A5S077W96SUPTsntWzlWZAR2pIlXbifH3jpkP6QwF/u7wjbfBVtEu4IoYd+VT1 tXA3d3SMahaX+vJw1p8jDV0xOhiV7/NAA5u8Jsi4gYrHo5giED4sfs0pTDpET6bD k5PBSw4NANrDdGpsbJc0MmHB421MB1SE8GAjK0QtnSvrIzJxMaTsuChJxouSlegJ BhEyAjoPGXe2SXb4ZMUXmY+f+qLl0ClSGVl/Fs77jIVo6MC+vjgwsIL/PTrDe4Ah CGH430L1HqqPSJvTqrpAy08crlr5SlCIv1rZZeVzZctOC26tbxmeQXGPCxhSAZsn u2JVqFxprl16OBQDyK4/jrmICXMyRPXOvD+guisQVH09EnJuA25ORf9goovGbpfs lWlhWb+rKcFjCg0ueRYqXBfppR8m/NPutaW0kwpJUYpxRWdvvvAqIldn5gbeFsZ7 e9h2fYKujO8eUcrxpWJSoO0XCAaiuRME8cwozfLjFrZ/Gudw4T1EuXQC9zjHGeop SOHW91Z9X7DCxeYaEbC2ki6hVdKeDRpdjyFTTC7FwNPw5vwyLVGWdELpJh6ciXdb 29WoZXtHC7RB5f0pozNiJGBSA0iHu/q+DvlGuIRsSVzcFNyy9X5Qiq48uinUPDfA kh43yz67USf6JiTmVy8kXaMHbR8q32PZuLim/tAfuZCk9C6II32RRbs36FKBtGYO p0cPMWTLZARXPSusE3ObFuZI4O7NJiXNphTnd/nm4AOCdM3++wTiYZvikHj7k2tO fpWi8G15SodM5oKR+UDJNyWMQF4v57lmjxDsxLX5ACgLyu4Lt+QFP+aST7TLebha 5Iumrrf38qS30ur/f9DsF+xVBl4dbJOMLlIB3Q0GgJfMf5vd4Inb8s5GULtNN+Es 3DVBQuRv0qQZW5bLWUAk57jTKrW8R1CeaZOHnzoYiFKgf2aBlE3oUNl8dDn0i3qr tBKaCkBuiyb0T7lTpXXsJZ+7a5nPWgPjOgQSWEjuX+WFdWEDQIC9A3/fw6o9SaUs j2uGts+AhX1tqmrg7k/8Zy3TM0SJiLwSBKzD7iBp0E4+zkS3swn07XIWdcW2XHZQ cyftVJXbie4IZvRuWVwT6vrV7ceBOEL560Zl0zRkUKpg4SFi46MTWBq5mWXfQ6o9 rIhZYWRaIxaZE9tu6l0BAvNPnMVYzxx2jIphVeEDM3TQ6DQeCuvP+lXLKLnAedbS wft0ygcKlhlXPtscaQyCERxEw5FuUan3cwX5tQc5HMmn6/VWHtqhXQjQtMtKDRfl I1L5KI+F7R1h2yHDfAY5Ce+DrBKk4+SfVuJrLb49b+kBDpG1gNTOZJ9SoXvT9mo4 sRw7C4RAf+iRSsjYRHKTsQ43VF4G6Fbm8Ar2Mg357qt6cU6DEyuQ0k0gUaMRYjhS /LqVe0vT4WzbsZWDpf6hFkAP0sANu5NHwSmnX2nEd4BUwgTSl94HUYezXKEpz/7C pLhAJKoVcAPUnpTsZ/uK+5x6tK2JcDltM5OKCfMmX3Iuj6Ar4NmFFcYj+edKuiip hRHcCf25KdMuSDWO9o5MpIOvPC2X4CCM2uVDuCRWEwFtoG2CNbVzlygoYT+sSZDV uLJo49tsLUfFECAoIVkH4haePsC5yWaL6FILnLcGEIfXRVsv5MiqAOhB2p472zVc QjiVcHK+9i1z6kO7vT3oHNBI1Z//Qc2XuTNobQ9fpAtP4saCtnmR6JWpDRL8Ko+m ESAx7OnMIAvLprguxJ2DlFBc1pZfRX7JJ+yjgacOv7MBEUh/AEn4/W6vXqGKYU0/ 1Pt5ztE3YDv+u8wYklDWml03x8N8gTY/4/TNRd3E9MrpIHKOeI2ub+wMCh42Sj+I 1Z44Zzd3TaEjedigVt6z41ynWfBWwcJQGeJezL8XnQFJt3S7piPIuhJcG3+ljS7T zcpf7Z0CsLpMVmn7yIDDzGJNzTbyJTVF0Trd1BboHcy1kB4kchWhbK4OCj1AMZOx N/Kvwg4l1Pmmo2XpGj/qTxnIq4NSgDiD8aLbuFEU25b6gWNn/YxfcDxG1+MthV0/ h/f0aAldoKslMvHtQO/ZQ07guqF3BuCtDGZzp8icWKS1yMt6ZsQODD09t436ZVv9 jynIxnRipSY9KcBejAThJS+RHCwny5I5hKQqZ/xyva5QtFnDV/xGUUzMC9GXYdpe k1j8ZW7wyQSdfhzjo0EMf0vOAQ4S9SbhaOFYg1yIh7irWQ/Cxr//qaUqPwZz5Yy9 JjZYdvGEl30Tarz/GfM3DtfgH3nT/nBO47qWnbPbHsYWZiJvcBrmYn4eRmD++BIZ iJFHyWVy63KExEyVjjbBemFOMXV1S+TkDzN4lkzCYe6gCOoVNjlbiQO/C/ZnBZVU NfOibnCsgdAlkHbw6p0WGBw7E4TyXm/HNldNG16ajMu9R0kcaSElvctq2yKAzb/q fZyWuhArAt7IkCOSwaDLthiVoZrl+0PsKJhphI6wvTFJqt9PUFnUTMvuXG/iM1n1 9h6sA0VKdkgsoR2GdJbzkkrkfRuZ3ipt4HBTTD+MtWOzpJDZYuLfANgmR/jICrDp FMYs9gesKHVs8q/vYlV4CHKsoBSBAEzdb/FKmMvZnzbglBUoekDbU94JnJUe1J79 +HZXN0DZtLjLX54XqDeV0q+qsvuXiZBe1cafAERlpjTBzi/w8DCOwwkDX1NPeSkP lmv0C1e2/XqFjootZ2axJ2xDUdRgPHVAUf4UH/eBcJ7kq///7msFkBx4yhLWEBUj m7PzApT5r6gtU+UaGJcUZafIwItdnLos183R3BNL/ZalygYgWH7QPFOw/s7ptUxd ottS7jjRNtO6oZvg9eeleZvW4lcQUXAtIHPnpN1vj+K3VMVp2sVBvBw4EYaWMLGk T91beIKBWwB1QxpWTGBsGBbpUGVbiAjP3yS42FTgH1yEFlBsKDSTT1rPExTFaCpp b+nufGIl7VLoMSMdcJlRAlLLg7e8c87fxY1gxNzjvJbotrVU1xkIzOtUpvH/TtdB Jnw/HPcrETPAUQ/p2fS4xfnPerw7Z8sALis8+8VTtxcLc8km8G9OuhckqFhMSh+A TohmB8JdgI7PZXNgeGuI8R+uDphj5wmIXXZKysh5hv1GSrJ6rVqPvnV2VEqZMbfE AcVHO7Kyu0uZxKFYuU2sXONULVdgSWcoHKwZk0YZDO3bCuzn9u7V0HAKBhQrNiSW axm/1pnGdMjRa4+XB9C5zKmKp+TqlFpmUEPBNgSKsmymVhrZOeHA6iIGIpUhAzkO MDNY8fRQoE4Bak1BaNsB45QKhXVeqDOKLOK/Q4xK56N1PkEcbw9HVdYmcvb42TTv b5jC+dxQM5w7hmea3o/TDnZxxasfRurcePwTaMznGBfEEYKDz7hMOH/CZa2sgHFr rkz3uwr6Ux14zt5XMrjzoHTlWFBtZe1ukfpWj4qcQh35B5ms0EoKwtxAL1dcwxpn yTY399HHL1ZCj1zslaxyffCBTGJGXey71kC4oB6cp+pEtUM17E8iJ4UJyGCtM4CG M+cxvca3ufZr7nsHNmY3gdqkJnDmml1q5cViO0c8rh9/6T0epQqmqB4VJHhKHCYN H8Rjw+/MdkLhqPv9Wiz82Im/GI/HiGlIl0Svfv9Y/bRQXZYhimR+ZazRuExXMGit PGpWbOTX8tpr3W5unFvrmOCvOKRmovqqQuI9YOQ58UGOUYljpAxNPrSztWnBVmnS kr8mGIZ0JMZNntwBAFg4YFia0q66VTwxVpi6DsV20/NsjviS9pvxfXEYSnOaSLB0 Bq4qNF95u66T7uof3GAH9xOmOxNpnfRGZ5tbHHVUAFP/qbO9q8P+S/pjscKvRsXp rBvTU6Pgm8K9FdwftOAu7l5ZDEo6QpSY3KkLnbnGmWG/DLg2fQjE5aJeWhGWINHF YuBw5rB9j7diQ27xvZtiE1VUdiyXcnTxUrtnSJpTpPX26PvWihJinH5TXwgrcsty 2sVp23s4imtJWXqGu8aOXPbkeQjgvxxcgWOgrRmEr/VXVWfkZodk9pFS06ZR/PUD P4A6mMMJfq6xkh+lGhAyxbnmbItmPW2tnvt9yjb1G4tK3vsXZLIBpE6iLMF+FFJR YOghrfriLo2Rvx20lmW1Na7sEweqxhLx/NRccL9wUv8D0Onib0K21xxuBi45E87U QgybDjYzTHjO6TfW7MIHMYJB1jFGLls0X3B2Hm/FymxNWqkBV3df63DDC1FJgy1f GQRQXlQ7OdaDdSLh7Kn6sybM8SprPxUJFIGyWtxHZCs7QsB6iZEdncQxLQP3AP9g 7tX2BR25RB+USI7mfzkvYRRoteM/VflcQjXqUGy4/Eq7x2mbkqEm5uLXmKuVvFS4 S8wCo6sFs2fxKWHiT4O1P1I9Wri5UO0gfrAdBffWS9cOFnUwRP+IFvI3JWrMYZMD cFIRB9oIrg6Qbc4NnV2pp8Fp1qcDxYbOjC3fcxsVhlT/+v4ssY56VBXszYNX8p0u HXvsCVWs6O7SiCmrwfQ9U6Ixd8fRVd+IJF/h4YgxVD3xSyoL/tOBxGZ30NZmGukw y5PrpKUAba0U1r9cvW44CRAI+xYlaS09wRHAyrSUAZD9p+qy/vVVIaK4i2uLLSXK y9t+/dlAw2fwLOKiRCha//3z2wnkdqDsjybBNsKrUmKDPqnn3wZu9AWW4UHD7BQx 4r5C9G2q7DPtGl9fkwqxCMmfVij/P22+I9hz1yuQ6pk3cKcW3C+EDgy/BgFFoYsb 0BEBUo5wDzrGQvIe7wlpp643+qY6bTHnK6Ka6YYIj85/3qxcxIbnLVjahRei7ygY g7m2p9L3/K0yLSYvBVI0XnZt/IiK+yg7qPOElnXYjnLmk/P581flUTkcoSV69LFz mGc/gW/4TEzYzUDuNOnZmBwigO+1PXkTXew7KCip+byScy4nXZXY82M4QyQ6S8J2 2MKIzOucLbr0eLDmru17d7l4Db4ZwzL3KCirWY8fR3NGomJ1YVnySNvzvR2wzY5s px06bvDyOsTnqE9jv+egE1/OIdNWR0fYwzColuP8ITdmJr6D6fZ1jHRHW31k8XMl 26pmeGTXBCwWeA7dE7Zbud7nnR/mpvCyW3NJjb+nOILIJlvEXdp0THTuZFJljZZV VMcC2fnBNePZn/n1bGmCIPLp8Sa+BMJdITdBkOKKnZhc/KThywuhVrEqZ+//Ttsu oB3rCarEGvGC5ESPE64tfhnbfPQDWfzNWeYIqNLUMlhM6KjvBavYyO5a8TyoVd+0 HrIeFxv7J0NNjc3lwVIaXMllkfJ9QDyq/mz9QV3cTHlQcsqnh3d/uDmiCIQnC2l8 YR4yp9kyJbjkVMnNdU5nSfOpXzzaElUY73r+A3ffOKBH/E0h4Xk6S5h87v36ubqe 32tonALZRkOEgATpI+sPgyckpyzejHhaAcqy0E58XBYkprezUzCnDDT+C0YgQmH1 5/EjlLn6V4Vb07+NZsi5ORgdWXLnWqS5k51opUcTUWXG10nOeDLCqEDec42pv82X nthFcqwqMJD7s8kKewrmExDT8kANo6hpUw0hFKe83f8f5DsqlGVFizThYlfAj4Ar OF6uiwW6ABvwesm6PTpbBFNLh+lOxbJjntNhBqvnfo8p8g9IEVvWXGeJnM77A15N 88ocRiUxh+0+XptOl/SCGHna3Uz/lYqlXvtC7V42XLMITZlaT0wnT5QBaJFw2Fos 8wg0wx2V10JUiySVbI1p85MGJFTSJdEsPvoKpAFyxkFVvp8n+kI0FezD7brLAGKd jlNWOZHdfjU2G2FualZYeAE8I6IZJDGKa/r0MlRwzjKvyPXFQ4J7NlqQASQ1IW1F NC7i00LZYfWTGryK6lo5AeU/IaNVk782qKnmEAKinoRCmJiKUoU+rGGCqZjdiraC kILl9t560bvROs0E8WlUHdZpUp+GHzfbqt5JV28QddA85pc2WxDUv0HfNM6t1nLa X0znYM7mdle9U0/BtD/ZsCVW4u10/toksgMihLvtN0mV9W8ZoT7sCGWF4jpSCi+k SFzVZFh/mbQAFDCySkpKDkN57hsmPOaHkNufQRPiPPVc4yiuAElT4/HM0vTeESey 83q/5B+13ChjN0BP7uPP+mi3/+Rq9eCG2daJVYJKq93lZ7jS8NcFanWrNdmB9c82 dk7lDzJRB3Vg7FEPSqzfMpdJ+OxpU4HgTf20sDsneSjGuRe4d+fJVudxN9Zl7GU/ 4sozJFtwx1wtm+CoqZ0KE+LC2bqlADnLXFrj8rKY+N+RtBHQqdg35/lvTXKdEKfC bkPc2p2/1WPNPqF+bBwRoLRfQ7JbxRiQdU9SxKVz4zyRYKMO7VHT3toWTeT4JyLf 9hHgex+Myig+MWdrfbUliyhH+KKbWlsbQbZp/GAkCjq4LI4dPvCS4cRE3CmL8otI 3bDZR7OOR8wpYZFl9p7f1o+f73ca/YNTYtoKb0LzkeARIz6VfmyqTPzFV1/skFiQ lkCnsxxTjkrFz8T6WtsscRAxPWV1h2xC/M3PkgWuTy0X5fZ8pe3+ULAHtVi3SoH7 /a8+8VERs7uBMOFl7zMMkOSDknVdjQcYRqH6f4Aq2APlXtR9iTCQ7DfbBj7fbv8L HHl+XwgLhBdSiL5ikCdwh36j4QZspnZyaMfvpfncLe4d/E0yZvfPTfpsu6xn/ZeL zzj4Yus+peQWiB6FMyfl7s1shIKa9raPIwxgy+tClc5fyt1USdiXVjEuQ+Mw0rFI cH2duFO86R8pIy7npDOfjBh354wCcLKmYsUb8lPD6ldqPo/llDX9A2hmoT70352b 2i4jd9qy1zTfI5PRxuvpB39wBiaH6AoyjTudaF0BOYVmrdSArrvrPxU4gYPm7nko P2nuI2n/B1HyPCS53oXY2swChuywbblb10Ou09SAF0vHmJojQ7zbrue0yfealfHO jvqYZvUvUWo1p/Rx5vrzGUdaigNTkPIWVfD29O/9cAcw3RLcqi03wLSZgpBBMf8R n7kcc6qTQk73zED3YZe/hD7y7RUv0D/QqLoVYRUboX/kistRWXuyR8Tn+PhdpP2q U0zxstGkRTR6wPpk1LOckSD0N3ncpPnZIA3bQ4pd8alWb9QPO2M4+jj3dtXEvaNx H+oetjG03PDl6A4JrAQiiEdbDgAjlN0gzbsiN4r1Usjdd7A6j0H0BbHChrdDail1 r3Oy1W/2Xe6inIfRPyLvPv00lTiTTptgr5UyVB5t6wKEk1MJTZM1DrlE0u6yMd5Z Wd5CCYMhg3qtNhupJZxzgUIcUay4MCABaHVkCoUzohD+vljrXhtoee4uvAQaO3P6 rNkW49XtMfjwHvhjJ83TX46fi9xebp6ij0UknygKDNAqE8H0DRGSTGMbRDAFZeIa nV1mQ0beTdu6j2Bi8m7yHhzKXTDVmcYVhjE3mXS1O0Re23xpVTrpeQG3HAOIvghj IWJjjLFCcroz8QuOb9WDtYuPB6airfuqYOsHne4EF0YHp7YdShbJdsL/TGLi9kA/ 33lkU+HHdIn9Lh8pQC4OKZndJbIRjfB/krZvCmxgYxAl1YaGcI1TSMgNc+BX1tae Ef6QSjJGWtZ9RdSOi61vWT+Q0u5NQcXBWYpNzvJOXNyEehE1owKBgdmMwhSzImxS J1nZtCg0i3mmq5fan4gGN0qtYghNEUgPMgoiAGN12A3f+ZdkRJo897BwlYWZ8aR+ V4Vahpls6ERAeXluMuZ5bdcmZqHj2FAz0znhRkPQ3H2Li210DPfhk0UlU672EzZX 8p1Mbispy49FvY6ctxK8uf+GzQxnC1MnH/pW/bdd3gReN1NouNJtRKKtyeGuLg0x erUS6G1wXvkZKtF60KruoEpAMA0utgKj52C3X20ZzpCUV+8GwjeWhHQhl4UQNCOt kzRKZtTrzDA6J9bwTLP5Jvuyg6SWjJmQCg2m/tGykmPg/CHCsACqvlRkMqMGN5Tn mp4nFXVqAiIaY5ryrnvlTUY86Tdk+CMZ1V8tNm7Bn/Xz6QgHapKe3yM9Ta1Ex06g 1iUrZRpF371nNsRYpkkEbiM+uXOHdQ78+6+HMs4LlUSUNg9FdPRFQzQmL07ZFjNv oPHHK5Ba/QiIjlEVWZP16tHhqw17UIZAyQQUhnm6xSAmecfAswnIUHVKiM5WXJ0G RcnsUVep3qcw3+J0ZgoahaDyfsaEvPOCCJwYH8m6hUEtIL0azIufiE0T9f89+vSM jay0HUfvEg/phaW4mwBJjidMWed7+EXySF7NBGz3EGHHArOQqENAAJQ+qWKQb9e/ 8OBVZ8wt9hsAcl4W2IkYMcVIeqzYmpFCHVeja6z5HnZ4S1b9+T/Q6rWZNR3HZhSK KNsaJIRUpJRe+cAzlCOOmhTlUwy+zMktJCGWiGN5siQOkkiuqX3qagXt30w9UPLu zMyO70eIKHuh/+Ml4IDWV2JDLFtcJn9nK2VisOCUw/bZE7kZMHe2pX44QWefTGQ3 mcL+Q/2vCZZ33dSjGORGzViNvhp+bEWM54F/9Mdmc7TMHJUatmWqXDH3YeIExAHH 1zkNnqwrlfK8j5avHgRmnvFqVyBN1ei93wia4IzYq6zi0D0qikpqft4KyIbW/VxM sQsUK18rg3/A5Tsv8WWxw0hn0k0WL+ULFL+USpWIqgNv7B67SFNtUm2/yxrh4y6a KAz9vEvnKnekEuIUK6EX05HQohPDFUryFDTAzoKZCeMoeCoqAIb4LxthNdg5fzRu LeJXlIg+ZYMHsCuUmpApCqH0TuRPS1Pe1TYluzTSy+LcmY6VNCfjZi/TPzKDc5UT 8SGhhvbc3SoDsPxVA774QReFQgqD2MFEsHt59dSweha9BHBSlt4k5sHt1aw82kbG gaTIsy+ZXMmLeCyB0JX/0Vi8dtNWtK3r5qYX77HELKOi9Ske4XKhBplwzZkK9cf9 pbc2fjbFeAWVfss1VTflEag6h31TyQDVYJgKfpsIbD8fg33XUALcJ68X+boVpZ1Q N/nPPq8t8d65lHiqOGOMh/dr2F2cFlFymfeqOFhKobHuZAu4IEYB1+IRfHI0maEx 8UbF0EckCYMySOoPz4wtcT457NlvG5fpAJCHpdPtjqRYPh0L0lGrFG5yfpxbxWO1 L03srlZCwvURQWPVp32NaXm4v6GHeRBHOTe+361GFYVfCNSBOYXh7jbvjR6/szT9 iWyMt1N5XkA3Gcgt8mDsAtFS/DC3dJUChyjTRv8giD0OCyGalmjeXmazaaaN7uT7 X/df74iOkJdbF6mQ6pRKiqO8P+0fXvhsu/J0MBIrFHTNumU57P5UyvyR7KW7QdfC eGP9yQP9TqK46LHHf5RXcqNjc7CFVipTavyCnXkX118DdM4g8dvAJPvWhfwkJj4Y cBm8T654dLX5knKY8prehgHrcPzENWZIDugkzfcKjN0Baqdw6fKHzm1Ms9uVC2WT 0stIBaEchw3SClvJ2MAzXOxoaR2sfbMduiXtCcxv1keRzyW1IqEFd7c7EzQgV9Al wxGPbkpo2BQgtk4+ccdE5/WebAItEYIO4NpyEE8MqmMsicjLDiS7SxwrL44wRGuC eVCD3QEizKfoSTo98B7hAgM94LQzWbQ9TB3QzZSJTb8eqPAwNZ5dCHHJ2XlQOqYm 0Z0CKbzlvUQcEDvi+w4V4jw5i+m/fHmy+g79zUFHQBcR/H476Vp3qy/m9WfhNqUY A4FwxzwGGxDITqGqUtuT6mrOFlWXPn22CZsq+qQM5Zmr+LIGDXMtUDZFzvyho2wP 4KyxubisDys8IY1hpZe7y/jls8CJrRKYFTLbasODEejqHj1Tm+eI0DSEiYgzPIYk uHzBVXQox0tbPwZSX/dxMY/GcvdJf2nFfGYELM3Vvb14vdyd0ZZcCPzH4LP0DNO5 ndddVP0aKkFq32goHdpBT4U2SO9LPPMuL/vjsMWC2kkDV+NgsHdFDLX4tUY1EQvF 4VOxJH6rGEE+B1Hzjaotk75WK9/6QGiHwY19UxX/1IxLfLw+Sg1Ri7ADZVyp9AcG 4PQAxmPd56xo3aswl4p8j4WYMz9S63iK/xkPWj9rCjBMdsNj7JWD6LUUvQ2nPKGw erY6WB3+U7WipMZchKgOEYDGD38ooLRO+iAOAqyF69aBBlYi7O9DCA2tPzcq7AGf ZPoBQUcG15UrWGBXaXDd6MP4pmMdmnSyZrOFcVYRV+sEDXYpYObzj+HSiu6ye3yI FXjezhyrTDD1/TOpByXnwS73LbTEA9854I7r66/li0JJ5NCMR2Zc3J772q75xmky ydHJHdA/sMDAH+SLSuVw6UMAOD5VHYTHmwpi4nB6sKb7MdtD3kLQkSulboyuREw8 23Ud05JY/pqVdtx7HauIMrVblUN5fpiSAto9SizbmP0Q/2OpPCKfFN8FpjT/5JCm 2XRi0qLPPZtB4jLwpqfJQNwtI+ci9dJc2Vn1r9Jq+MxVQ2HswyfxezdIz/ag+DFT EjZAOQNQxJI+WLKB7W0ktSCij8gTIDHcMCwVg2fKFq9zsPS/3CL1OF/G0F5QRsL1 qFWPjMSv0v0Hk2INTIrePf9tx1c+lBCed6wtYbNM0CK+uKxUqw7sUsAO0KkMUNjo hCib7xGlDY7Dkl5ODpcSHIBd9hmh0ZyzvD2uwnljK0Rbb/nNK82LWqO7G6IpmpXR cgz6cGnm8OS0/sD3rJVJvIAgM42Fo1YVNsA0X+SoRuCYL7dMtaQUD3EGcMzzp1sj 3dxebd19uOKDzlqbn/fZC8NCG4H8HRtRVsZiTDVTQX32O7cvehNQCV/inSzm5kAW AXfUkYU5jmQWYjeZ/FvwgTU2voJM5ObvsIprB8kn5cFhHf1887NuSmmWhLCcyWrA aD/Kqz+2CU0Ka5egS34Urr4Vr+G0LRB9UZxkBAL14THavkqq1YASYFBnLryxC2/a Ubdhf6SBQ4gz1GfHt0bIPYgBkmM2gTztelgwJQwf8Ug3nDLH9kfvA3kVk3/k7w3m PerwVLRP6PtVxOOcns0NcnG8r1HIg1pKwyc6RbRfKuq1LQZ2ODv4D6WGq8YkgMdE zEjJhT5g7FnIPnSBhOxkcPhFb1mvvyi2pDbUCQXDoEOWaSTk5GJ/pX1wD8792N9x JbK0Mrj0G/hWPOZKMtHpMlYhW3s7hcKCHq8JS7x59jbQx+nwP9oHjoTrxmKpNCEG DYomeYbFa4nAoeYy81TnNBSnjhN54YnoWoYkqbfir/aKXVu4/rAnJf538oT+wuix YkHrGsubM73uK9wI6GR2JYipixGQ0ZW2+Wt/d3yVnv93iZ/5OE73CoBzQAe8AUeO zRKpN0xXYn7aCtRJwLjO0DM/kon1EjgLqn+JZkZolDNXDsnL3m1jRDA1a767Ba1j d8Obco79xf/RjQQ1nT27DqKf0xAo0/2f9qlqo9CKa3RKx5P+mSC+n8STh6Bvm81k wNCnOihRnWOHdt6dvlVBlmHp3BExYZZEo5HsQIR9gYhcOMiKhH8Y0bXLxAv+T//0 Y9R+Q3bRb5LwpTHxxkAjAP/GK3Cp1HYk2rfDFG8Zwc7asyexi5voAccCsrFubek4 IxxJ/wePbHMKz4x/qCb7B3FJzL7cpgGnClSUbfD31K492FM6GOGpTx/KFooGqUOI qXVq5pZZszl82TzbGoAHA3A/0df/jj+ZL9kEgGbQMNW/3GC92ge837sTJxYdGDMJ vtFiOANg+hb5qkjlTGBMeyz0pVMDA91+31NHrjJQGfCrC7kMlWTFP25vIk/LfpkT r43PrFo/fjzX1zXV42fXfJW65oskoWoO3jsTc+cO0gzhaV/j/bHdUAOEmda+nQz0 IWQeYjojGpfhe96lKLk7QSY5RLOnxCdSNk7uV7dNEnPrwRjc02N33BBTlJA8U+lP SQ3nrU7CWo1geXFRn0Mpcc9hORxm6pOWUhw4UbjJh1owbhNJwsSiktsZa7NQad/w s/ZRSMKnUQrk17D3vS9GKeoT85MzZGvYlJWVBq4fafOcaYlrvwZG4nyQJrxmDhJo zxhDUMeE3KZ5l8gZHvMCAcJdARC/8Bm8HI16ixQ0XxQoebaLcuV6x/aNBb/usivo Py8T6XgKmCPRza2xWSu/Qv4koV2Gzh7Gi+14nwfLIaJtL+NEBX9WZoEGmWDjg8sV 44I47/T7eHPEEhXcNkdTXKtWsqLEdj1ks28z2sX+WYmSKWdYPvlc47OQuv8Bx2HV 1PiMTBacvZh7AKQmUvCL8LSY/Sgy3JnAw8OxL2TvUUrQhVAwISCuVXVpvVof3NiO RZBG/00XncP2aXT780c8jw8vAdMYC2Tw+Hk2Mt9ab8BaTzt4mDp5HcAkoJoe/kZp W1lf6+qB2LBuBLhR1bhd3R10i4eAKLd/KFMj/45nWqunVmts8FF3bzMBPiS8To1J MFpAyIkZxHDvlRGJWMhefP++fBuxFuyocceP82gQp0S7QGcmW15vh+eBPwitgIj6 UIm+cEdbXbdVOCfcRy9wUVj9bLFmW5SlBGe35DOtLj7H0BP5JYlhGwKYrXzVef3L 3hEid2QYUGUUmjGvZQoPjaMS8Ih26LR6atzAiBZ3j2QcLpB985pc3GGSXdnuYfgH GZ4zVLn4IJHbAh/ovUhKzvd5NYxhhLcfmkTS4sEv7Intpx8UHY5sJwOqc4Wfy3qa GiABx1F1e4Gyfj+MB4wLzPSnrGjNjSyblPfVxyf+4kER9rXlEzIaXfFgBgmHRLXm D63TSfBMwsF/JJaden6LnMZOMOPSPpaXjOQBXtX0/5KD8zC8fPOzMw79ycZSu9Be fGlpVxlIR8WN5iSWZS2OMONZrgPXDoqhW3RTzUHnt2Q1gM6akXRme84vLRL+NqZZ qURI3uzYqv2RBktj8ZsE74/5JKV7Uoc0C/wJlvUqYi2IdCk0divq8fpyQ4Mp0513 y5UCwyWA2DdbZ1J8j4oNSV3c+vH89UWwqzFviLr9qvBEsnA8KMi6NRSCDjzpeYJh xjYVJXb6BEMIT1uvRoqmqGa7m3JSg75qnhncIksyhLeJqLo5pSB0wUGDfBnuF547 eZuxegIe317kuRXujdCOnY+aBqmXMrCtRCfe93iG8NJMRbtTvUugjiUMW2BLw9VU 7bHLdiXQNLS594NuP0QN9lVogqErm/FhSNbwkCElIpJXcD+0DPpLNShotqFn0Ehn t2RTYqPedOePhhsZAyhvN1w/+UEXNP03Iwf2nsZ1MKFyhtFasgzWo4Aw9JaqHMMM 5LioDbKFKS2BPpkvbqBIWZK9l4QVEZCqGxfMBZ5XpeUx4oUsT++YGBiHRmd045ii uAezCFbQPS/MKWMnPtyYmkjMweTskeRCDElqYmcak3Nq0geFXAIfrSg6Z3cud/24 I7Z/dXUPOCWk5O92bJ53RnBle5mzFKYo6MYLWwLu8KCz7NkL3vlkeTqkFQKgzWAU iaS9IqbLECMfzEfb5vm7hbaGnU+ylGrekAx6DcuVGS14BiLghh40dkFdg3JXH9Ro ib9B9JSm6shOHUpaPP7YVigntUY4JoIjnTwMx792daTkxT9sZYbDT0SUuD/HJkqN zaFJhd5BzosJ/XayzCNOhTE5HaXBKhk2IEejnHcolyWxJSXOru6ZEu17faLtE4zx icrppTJw2ttAO5MoKvkdrJEwuEopjU6q72ukWE6hKlExpbshKNgCUaXU7sCwaD49 Ddm7JhDlIxOfFINAZ1CaJ1Xp+5dPIhRxe1nSDnr6SG8z/yswEFTqoawxM9TE1EO4 mOM3G4he18R+UgOKxQ0svKXvLhff2j00waL81S/6cgxezFQ6nNDm1cUXZ4xGJTOR Mklrb4+tmp7n57/aZ4LNC889uRXkft+Ke7HysE9YL3NKWnWCM6AiCaB4Cu4B7O5t iUfDzUad4TtFDmDQOEy/p8eNhfH9kqFQ9NJVXidPfkA3EPRQvnH9HZ9vx026LS03 CzeiUkjNPl2nwt5jR5An5ShdbjbDIMtGQ6HcmzmRo377O0eoA1LbgW90XA82K/ML Z/Aq1TtcySql5wgxfPfWsw4z3MiqZFl3/gOumTwDOilN5Q1i6j0LNlM38POajCV0 i7Gh8Tt3TEFTZ8vJoW2l5fL9bwr7keOYOM14yogWKp1c8spRIuMZxioQNvt/FFO0 GmXApKwp+RDyEH0WIDuzn4UoLKnYNF0urd5byUuCsYePRoxAZVbXDueGUW7KRplq Se4zF/653ts/fGzS/UhsC/AZd+s1RDY5zAdqcDOrZhw7vItLtZSWl/YwRWfTSes/ uxBVRMn3c0DTd9o5wMftMz4IJcJFXq4C0kviSv1wSoKrVgShVoyI/G8phTm4d8Ih yPbCPPeb6qflow/JuppwTvVYVZyMLQ0B4WbALTFoCeznHU1S5PK25yYf2ZljgEya NUu7y8KCmCgoVlgsNKYeUuKbUfTqlRcOIp0Vt9AdOWycv97LMAm3UdoUQnyFL4MW xTH3tBvOsUi4u8syezxn/9OziVIA4zIAcS0l6UmyjDmIRmv3bm7QdNSrKTFXt9Fx O4dn330Q0DCX8y1Ffi0v+qaMU05uVDZgLPjUjy6A2l32UgLi1YbinH4OZ77JcDEh aQOscxTSsbAE9zdfh33hJqCOu1pNRcCMy1XNvw3G6qVKZSspvdsYwLi9y3LVS1BN AlUwDVfjZCORoORu9xI7tUAqxzMdwJ/YYa7G4I+ao6GTNElp/m9CJAM/FAv4+mxg e53zYwCvoMPUkFuKjToDyYkA1T5DqUw9VZgp/J0LKFg+XKtjpcrRFZsbeci7Pba/ QpaxRm4l2SbYC1G07Mh8mBWgHwVyJryTI0p2aaDgYus/jkqK7/ZMsHycU2FGRghG 5ZNJjTaBbSYY8yPeJdZ+kqPGei78wb5m9zjWSsw+Tfod4CfaFygVXb3xgOZl6Tr1 dBdhYZwfVOSKLxiNDGuukxSZhLycXmak8Fq0hLt5Fg8cQZ4qD9pL24Zb73yPSjpN tex3qgRXDyBYLCNY/7hMF71EDw59ZI+dGLXosrXxZIrOAt8CtJVWz6F+pJDzv/hx op3IXRqWPKyp2GRu3OE1W1bDceueiuCqKdaJzbmL1VhFRWWEgKJ49LPA3xTCPGV8 p20uKp5O2jMr4Qle3ae4MKaz6IH1YwcRrvBtcwKgFDEk0AXFqlgvOUZk6obEfYyk rr6y2njA8VIimB0SJjO1JkB7G6Oi9xHW9E2iSR8/zU/Fi7UZ9nll+3kInGPgNhB4 jzyJ4Cd1TH1jofrpKG5HDhikIFdi5SAk6vpPrQfizfcilJlhHRfvX8ISvimodFVs LedyTlAF9m/jaysjRtdvCmZMgmsgUZ6DaI1Bz07VywL5KmyDQN38Hz4R6GGQdB5/ BDTV38ZUe9y9QLhmCXsQG5xrmSFjOtqdiGz2bI8v+dcsiN0I7sXda1L4ZnZVeI4S h1OHM7xmlNLSvZKIUHwUkMjpxplYuxfrKTQtCF7ubqBkTpjzVb74RSthSBCrWHcu z3z5rIpZo4JRoLKLlz9IpEuvg81Ex8QMdoH6XbQ3ITDUA/aBIlgOaW4593iGeYK8 cnAMDHP2TAHhMRlxu2X6weR0mTOhJ2ugmWl21d3tfaZstqZNIzNbuYzo0kp70OyG cgBjCWaPgSC14ENte22GFXiEDJoukpl/tDcb5ZE6g7oXT0BLa8U8ID7SxJW9zXrA NiTKkrZJK8WgTvChaMc++g26ytO8NcAPlyLQG1BxdCTPjvE/DCCHtdYEY72xyjEV EJqDGxPr20QjyRyWyTHYBcofyNHfgiLhADUJiLsgERDdvM7/O8svYs//8GMPt51P uFHxNjT4pdfl3cTt+zxoRLpdMkfhjBfdYvfwR/rWgblDmdm38ymUyvNgZ66e3G/b mXOdDPCoACsiaGgDlGPO5bOTu8Cw8HC/3jrKJxWG/X/p/djqNmekQWVPLda6fE1p amkPby8ZmsVnAgB2IUvR42EYbhUaYEKBIu60+M5q+t/SfQXqTpe+G2PJElHwmdax p6fqkwjR8QRBxfuWykna7xod9GCErPh2sGMwTciJ0DJdZlBNNIBFaDECJL5NocvT aS+1RnBgWuDV1/cefRLdeTiiwRXMGhpea/up5IA+K7Mx1ugMM8Hm6MlWI4d2Mnnq nnJBOpNUq8tLe6IOftY6OEzZPfSGXH0Esuq9jp+7JbGpZO7PGWh/zElTVVrA6EHV bpp5x4sra04PL7/LTjB6nvTKEA0rI8USXhIWfqdDp3LKhUEDscA/BHf920/ny7fd 6S3R849U/n+w21NG0IAQKaJ7P+ZS1ewis/K50sptJBk501YkY5KhRSm1berglV/D 7TysDGlg2uCe4wb0hwKW5X4UVGECrKf0yCTm5s9WIcADM8BtkftIWPcAQbMeGb8H xq+47ugSVWz9M6b5akTQRicKQaZ+22RTWtqr0bcFhvdYJr36qjiRen50GG1BZGnz Z3WFW156Uajzhm2OjbZHlh7dU4Oc9n3dtHMCdDXmSQ8PmGxu++ELr1ed/UGm8YXW a7xryGV7wb5Uw190bH6qI//9lCHxmTA0hbk+dq+hsgnouIw0Jx5/2eOoZefMg8h9 soMt4val1G81gjS5JGiqqMCWsIHZ3BFFSipQCI3lmwl0Q481yfjHnK+gevfsk/Ex 7H4DJLDij9ikteF0MqTKWmbpVfFZUs7S72CFVP80QVKFra+/dhAqSF0Acavmpvmu NTVC7VnDJBptX1cHdHkfby1GgQ4j5zJY6YmbP8O01JvmdOFnP/EYM6THOKqvaW2Y 6hIBRIoDy7tP9BIUk/azpmfOnHdTYG05IBV6SRfq2HB9ewQbcZWn0JXbF+mO4sFu 8etLRnIxJaO8R3Zi5pPtDlRrW3z3KBiGQbUDYjEjGovyeraPFG/MOuGXBgw43YS9 YyNamIx32TQW8j9vLufz2Xc4oGkTplwICDDpierH/p7wiDT6N5aSx6yEu6P6/oH8 UhdTuO1MMq0IzwknCuVP827yS+fzqdVPB54hlATjKngm2W/7Pawk+YdnKaza0YwC 71c8WOF5Bt/q/5feHKTO2lk8dQfnHmySyOJ2hTxoHYPQ9xEY5BOSXHEkSs2wFKuI 8Cey6nMFokNbqTrlXR2GV4k0L7aW+/XUwS2Hy4GWIbHzR/DTc8Qk+nWpupnmne3k h2/zhI7SirICsBZzR7cMtbsRIqg5svFsIvTgWXFRXB4raCMwKU9oka4t9bhMo2CY 1n5zn3Ijeu2XHlB+3+S+dM6RiCO6eSPC/PhuiypyAniDcbamJVAjceDux2mjmzGf Q2AGwU8vtCIDkErojuszIXXMVGFVJdeaiG+JR4FyUtqkWGDywSv9lwUeuSJpTa7B VMmQ0HTz/amBzrpzKZhdfGMjd1DdLv67KDRY5AhR85pxVvyVUquw8yqFobvbZNF2 dvoY0+0nCMlsir5sS6Q0zxUVaX7YeFd0XEoPGsM5pNnEFvoJ+wU4uXBoC3vOvX9l dRGkPlUQCIMEuDZfR44tfNkcsoQikcnluCw9nNV6DLZiBkkSxNR1RMiNQYHuqA8L t7vhDr/UzOs+fUMNcGc7tdFO7ttV1hNfKRvwUQEEH6JNvpDsjzO0QixJRgBbgd0C 3oJ82dJBw7jP/znjQe8M6QIAXQ1THkl49obtKTBz3Ewgy+xgidD1CsxfwSo6ThP2 yPmxc4zXpOZpnzk7fBqum2afWWhBVSiufVWUJpidTlc2aHMpc3R39s+HTnG0FcBY WbxLHjgHFB19gdcO4ysyG8xxFN5YeuAztc4CHXTY4X5US4T4WyGyjpw1iwQlzkET GMOE94Za5Y48i8qdAuESD1UEg8ZKGBBo7wha7jiij3BXke7teOWLqPD+F/p6B3L0 26zi+nGn1vrYwFCRtjm8wyJmc0aX2Ycj8MYovaVtBVqG5OG40il33RYjRYaYU3p9 9tq/BzMCQ7sQ4SUlbaaWvl5vGYRZodWZv885SDQ9/EMj4fSWK82284trqcsV5PMH Q07tOLlqMoOy5wLXxWqSO2F9PFKhjlrtZPUXQyQMz5GQorGK47/ZncBGP5ZJbpbL PizmdFxhAoUkoODoYTknwtaPkymZKUv0aVW/d4bVO/UJAhPUkAoFKZrUOk5SxDU4 4QEtZTqaEBmuasmBqtKnCCBmm81WLBmfelA6ctUc9lhAlk7DEJKi0IieOw6m2qeb 2oFpy6srP8bbHzLa7aBf0ZUCZcvZegpeVSq5gKAe76/wiPum00AvrAluB2w7iueh dmkxhHbEdbMflVNr0nyUq0TgjUUy/a4u+Kmw2dyTjxe0yakA3Nh2wATf1E2MHKh2 DTiBfxPH37lDZqsnauYgdiP7It1nm/Vn/dqVRW90X1z/gMRyzcqTlNDNIrDfGBTe lb6CdOtKPsLMlIrRV8M6d0fuFFhO4vE5IFVvM685y0mpU4DJfuQHewFyRk1c4TDa 2WiQFc/7CLN/xKvaRKhrhJNB3ClsVjXC2uV7+aXKbXQKpEwQuT+npjp0DkORIIxg 753+cOIbxd+Y1BJ1KNi4ITRiQCgoc2FWsLHtMIPp75wEWMUai/lrOBGY8wg85YIT m3NDlCn7TbCFn8J5PGCpJG6o/Xt/MfCNeB4Y6yQHkWcUd/D0IT1cT+QvWHuWSDPb PMTBJjG5ektkZ39DHkEyUsGvtcE0V/29oZRadX3bPAzTmA1YQWKZAu1W4MTdaC53 cXyDcj1WWEjs5+OGqp+HUDpMeoxIc98+yRYjo8p5+jpu9PrPDwpzfFCGAlF0pcvR n8CkJeAmjp4b7qu9+EimxTa+QYfMx0N0cbZN+ZajgmheMI3zTxtSNylJuQsalHHd 870ve5oowQgCUzpgca99SXrUJnunGp9IAxETCaUrSYyfv619skWBQt3KORGhYRnM OtiChEfbb/4I5U0pfRky/6HhAlAQqKCgQ8+EM7WndoUKzeX5tJG+lR584lqGP5O2 xUxdFGCw+TF1+RgGEqdXdOTOMAQNzAOaI51b/gSMAGqcbh+HJ4f5SZetTfSd0J1N gSGOZd8yNxqgUyuO3ndhEs0p4IedM4y8CgHlWHM/E2FPt+9UjRBoilwbjpFdO6iu E4CrBeuH37Y6s0bnfTzyLFBB02yuXevEE8JvnvQqqQ1UuZnfRXXwK92WkNQGi/X7 LHpH3QEU9Ilt6gqgf3kj5p/eCE7wL1RVLJF28LqX772KUZ94GqJ8Aa4fJjwcidUd Uym9EEwMkCoYx1TUyysvR8TGruc6ujfdY8JXdiAI4M6hNJoeAsjU0yUTaGSG1OVS oYBTVdxTn7lmcGRZlwcDRnUpc8GjJ3d4Y9P3VlZs+F/MIIQFz/6e4SDP0zxNlLNI woKLewIbxHu8R2/P25ePw9YgfAupO28Kfvz63auiWygTF1EPhSJDwOKEInfbOqAE jQW90lK6wZ6EUI2mmNmAcZX+kimVt/Dll8sE1++T6aeA4rfQQW+qMd7G0CBEw2OB r4gibkIsYRKd2WiH/ntAxfiEQ8Q2ZPSlsmjIzc+HW4/x//aHTAYzW497DYcFmJq/ wz4ECIY8hJHbhkN9RMTenTMQHz8MlPu+lq7rwfixEfg9Fk+rcShRlWhpWW+vOnhl MvuRtU3zrlQFdZYDsUD5PIk7ggwJ4TZEAQVajI96Iv5G+Pk7Po/FAiR+eGs2CsWF P+CG/LEuCzOyZ21nXPlO/15PD4HSNCIDQL+ISw0z0cXlEmWsz18iGs4LA75T5ItN AsYMTSEponAD9G4ILbyJFjHBbwMNf/LFgAsovstU/K3H2oBouI2HRlLiYNyFkamU 9G45F6ZPVrT5ve1loQAV/AZck7saQhHLWzHas+HislTSrrFcUr9S2+vRJLJTQRyv I5HetiW1cCNPtAyNYK0FQv6mYQj01K2VALqpguHSTYz/AfQTi1/sFUUag3S40V7c JTnElgzMNtHJTDb+WF2Ln89UALXa90duaD8CpT9mAULny5EiYOE39yo2NItWNIQ2 Zx9HQcZ1t5hN4/Iv4uDfFQoBkY90Mi90WfPwiogJW+eJf8AOvFz4E6oQjvUq5PEh PR54KIOjgdzovpLfxkVvXADX5MdNAxSgpfz68kXE+mrie8zN9/8BLTmcueCLFUv5 sUgI/84rt7PmaRo8QBG/P6gcRmzW9W8p06XZWVW/HBnMXP0cClaercZrP8GskkuH GxYpLO225JmGe9uijD8BDewPxDhVaHuvgxkdsiGWUc3F55hXeluMb5i3DqBW516H bAM3JzV4Y+7RqdzWIxaQN3ygLMotwqCntha7zUBXfkpgD8djc9t70n5qw4uH7DIf oVDaTfWp5Wueccc2Ul+YEyjmop7sBzg4oXfleXDllhj6Dt4X2PnxvBTXpFNSEnSt ztVBQeVzWF76UtwqPcEC5j0attiVYtLyCWkKVnc6yNtoJ6SZtWkXvM/raKGq3H/I tN297Us1RPzWe3MfpbzF3tWYE1ITRlrKC1L03SLybLSErpu+jU2vMpsGYbRMoPwa zcQB6f4fZr/PStlUZJUwdRY3wvI/ocF2LCyFZOyRH+vyfmeMki2HuEM3gdL9vpHc /m00lqOLfs2e0+k29pv0A1bBJ6X72qqM5BU5WUS3SKZdGDHOIGrW5f30R/XPAnB4 pj1KAAS1Rd4vdZpv7zBaJumkLq4J21d6KfP+U0Oa5Q2FMRZKSjPj29IrvkLlS8FX dY7ttKkq4HMX1RgeeHhrZY4BAL195RIRNKW+rcthXLaPb81VsR1UHb6LZnFGZcUx LzhTNlvDSq3VrFPKsCc+ACAGEM1KSy+s7zbkcE6BmYJG9y9YGN8PzyigwlEOGDfA fZmv2PAhUN2/rfEsytV6n+tezNSxCjiByCrSYaggTPVxuXvsCVylpm3m5j6Vfotn RB5cMNiOA3TM8JA9RHlpt689OGep2OVt+6y7eRkah59DzbDYHGpjpkkcvpiRfvs6 7VctCnlM+yONiPsKb56SVSdhOs9VeWtmGByCmi6zCIn/TT5GpMwoUPco89CHxJmO y9+3Kjn12SDNTjTRCQOyBtkqEEH2t+KDSnhjCLwCwymPbWy9w23DlV56rVa9PAH3 zUnIHihVWOXIfWR0xEIedhv9WIm8qbarYyX+S67uO9RNHPZ+x9fulnJcG58tXf33 bWnwMMITrNQpay0n6J4ZQ2cYVobH5AyY0uyEkXPnJ1t0L+RDW95LDjC9DSiU26gC TAJDgU61V5l9oQ7x3ugbmdXL48Z/Y3zm8GEYLSsVis52E44UuEbStTzbLPWa17Gs ebrYwBbBhX2vyko8figBQmdsjPCaqTwV146Zkom9z6ffAq0bBWPefIAuosuE5f1u BCtbxsCepasgd5J3gjnm8Hzn8sSbumDPMWFSzqR34wnz/3rkwCDrxJx3Bzbo+Ho0 YKmUkiyQA7fy4jYWcpNrZvZDzHg6lMSAqfyMT3IowNHRVhXM37crkTVvLIhlp6/P zz0fBNa7gTq6FLj6D+4ajnh3Rsz0dwqClNijTXVnjYloE8YRrkKUDCoXVrYOlNc7 SdieXJgws0VfvKDCh7mKVQ5hswT70qtrAmOcuBAO5m9gDNCBZZeXhWd/YCKQMf+j U3EQwWWlcJVUshP7q8i6TrNJHo7JYre6SFGcDPt3HC/H0esuctJ5p5J7vDMZ+Ram Y2N20S+Kg4So6p/cZbZyJj2fIwu9KmqVEmRD32HsiVIot6CvM8HwP1iAQkH1tHfg HDpNfYHR2kmwqE/FGDu4Li2fMtUqR2yhB+XF7I5/SaveXKGt1zGqw7RuPdNRW/HK ox1i2w8p65WuSh/oLuqMkBABuZTILbENd0Kq5+0jfBGuYHuyWll/Ta1dR7S84For BM/nS0XaryX3JpLYxRc3TWf67AToSXTVI3JcX9BnCCnNvhQ9r4kiKZ/3R/O93joG HxmMrYNrxL5vqTLZSCjydgymm2gDG854/wv080HeB4UxEvUWfQFnoBvBCyT6dyFv 3/MBQCbejqsXx77AXN59DhTa+cASQf1ZPlcOH1CKl2j67cDFO9kcFuMqAFPWXLaz E8MWHsFkphdVfWk4FAfkfDi2X1x7crViPPLHk2LFdX/NOrDVzP81/c8yhcb8bAYd 8ZQT4N7Q3lQcCzGHHVQ9OjeGLtvrw41qfH3yUNTP/Q1dIr5xwitiz5kPdsnOfqpE LqHtMIOjJBdWBSy01TDvyUmqfppCpiSXW91SqZmbKR13t6lzIFL/BOrft8oqJHew NYj2TUjQ/l6mGRLwfNDbnIP2mp+YIcnZyQAHadAceiAtEW+FdWFFEAZh3aDy7Buh 22XuGu5mX5DnrTepTT4Eiq549HPenWaLRMIdvPVWl/D0U7I5h2j7SlOiGTR7GdKd PGPmW502CFEJYtG7T4VzTy12f0EkFLwYn+DGsKbd7botUUnIAWTVZuBNNZWpJeqa 8deURoR2mamrfgEm0BE59Cx9M1K3zNPcXouJZmxFWmabF28k3TF+LmFvSgBvvtQQ E1gy2NTsANtdrgd5MpZ0bP6bv5EuDMOmleGbu3t9LpDlXvysQyyleshaoHrGO0tO qET7Ba+/xGZt5B4fnMq/T59OIgGF4RyqubhXtyQ1+GMqbq4K5YBXxg6M+eOjbygG JnGsgP9oizHL/PGzgkSw472CtkJRqXtmLgurbjd75RxKZYkHU0UJygDpSq6Wq3d0 elO79eeZcRklFRCQHt/vAT1geG7/MgpMBJqp9qURfza6jgyvwzZ8vriHC2VBE845 T57L7LaFt9TbuqYt9qsSbN7/BRfzfe0ZnWsdIHsLxOl9kfZCo4Si5g+t6WPSgi4z pWyIKxIy+rLTxrrS906gONvTcyNSx+pz62CXyDukN4JBBx85n3SWw75YzwA7G89G sbtWksYbk1mI2Uk6Kh5YZc2dF7/3l1+nqUgjOfDWcH8ES4M3Nu1UKk9oGTfbmLJE OL0C7DXm1aHF7OeUmcFHrnR8a8kRpidN7kr/Ie47RvAOqXV1Hx5HrAz8OGLrV7rj yghMgxujF+1oBUszRQFk2/jBzRWR1AisKn05Fx5Aa4uPTNophLx5Iq40ndcXD5kW 53MIKEqYaHec+ekJqDANBGmOsVHUbyjZNYDVM0TSf0kqSorl3s9sqvQaL2LmI5Y1 7IWSrQ2/wg89u+SBioGyB23qn7dGl8gTKKZj2288NimciH2wH59vaA12Tnt2qjFv 2QHyrjOEoYiXWxxtMa4Upe8Z+r1WITlT5I+vDf1LpJZFSL7oTaxRjgQOuv467lpu hq9vnJ+yjset2T4w4iO2wDyyTlu7gByE6QTaz6kTBuPi8MCM6Kj7ICMS9Tnj5uVy kZJCoKs13sNTh+l1RJU4Afrkk3ZdmTqMVVp3iXP7gZd+Cmrq07hr31EAcNmzs3ZW 0UrVg1uqybk3GPwXXGhT8AqySuse8+Uuitse7r1gvMjGUAzowOxjkBnS2zROBQYq NdosQmvRtBoWeuRU3gevjvG1/oI6WwPvC4YSpspSROq39QSfdg5s6vuNJ84BYL60 GNY5q4x+R5bWXQUln5D0/fM3A8/oeZhbvUXA5+t596xWzKW/pH4azL+RgmpUm7Bb YiY1Vpl1q6qY68m62T2aHhKNQ8w1DtRBhI1zBc0CQrTv9PUiZAcNzT4GJphyP8Kn AODuUzVSw1aRIZjzOSTisLtBP3RM6OKMUrf2UwJ+qN1gwWqCsSDRMX8DrRat9KIj 1QCMY91HuT39SMDJ/rOfX0BWsWI5EKpDF4NObSirIZDOeNTmGJJN8iX4GGs+8QiN X77r5cvAvIeeROW2wi2vXN1w+j4VjWcbPrZwxMu9oTtnRrC6Ilk7qhKvd6JxqVlw RSDn1uaSD1yYQ6fmAyntgba/CKmAXOIzfsUCtEQl5OKwr/AMu6MJH7CdBnyrW77t mfOmM+yv8U7dPl5ODJaD5BYZFQchUQ53Tyhdh5c5kV1+Z0L9JY0glujF4fUGEg+3 XQRc+J6+5hUTfRNO/+664wDOGGUaYbVrBQ4cB2YtVHirGysQWhY66PBNfmskMOfX /s49dc1PYpMY9tujzhkhs+F2dfPFhDbIwmsflzfrqUgbObqkMiN0BmODdqJwrnvT UJSx32T2K405wc4dEm1TCD5nlFOauLicLUaGTSZULLds6aARNgBf7IwZIzTAsQuE x0nZGIi7vTctJ4r3Hs/D6Y81rb/RoUSaibWiPXTKfaqVcd5/cYlj6bbdhPYeE9sE RkjxZCVZvAB/8CobvWi/i1/n5OKGJPiEjh9SE846dLgpfnm1sWrWcbvL/QbtUD51 24Ylf4/Q/MXhKBpZlx4Yx4mnUPCbmhm4SRzWzGYy8wP7grkPxsoHgBqS2z0Nt2s/ 8qPWQdkAOunFLEoSxTsqlmkcFe+XVIvr6N7Jf7lYMUT5UTRoQU/F7wta2By9xi+d T/RY0UdSX8mSuUjZPgOpapYHN/FnJhqFoVi0do+2WjAFYKddP1EWzunH5+PE4zTe nXVviiGPu26xdHAAjCSEEdnbhRCOuQJge5l6YSxoyBALu7JXgvxo9b1OGBISFTb9 YweLYUyiXX46o0P6tS2bzIp8Ja+7Big0uo2cHK8Lbvr+0eSs+Mh3X/qxZhE1H3+W pTK2kFNOvaMi0LR3ihUx3re9ee0zY49u1sFrVODlFWexiV/EQbLQKCKVmnZ+fhtE cqCL0KLkQV0l3Gj7JW8O47Oe77dAtjVtNYXvcqD47EY2fIyNtRogHn2z9zjFksL1 KzY93itHRL5IrlrxC+how5mv8DHA1KjRL/iVEBtQTFXj9ekN2ZMVSl2I4aos/Pdq 0Gq/X5ijRmTWFXpIW5pK0Yx1rZyVFaVkRv+KmHw9+dWP77WD//AOA5L8dDOSsZAJ pYyduzAFC5LFruf7lj1gM1wWFrVxQ834Cx1hFWpB53sQsWeYxUd4uKItYtqfOKXc 12pWxyZCiVAuOviTNJRqbJ9kdt1LRxAUEzE/GOSO2s11l4pqt7WdDLLsEe6p2e3U bhqts2BFf31eRNM2ppL0vZLiEaMxfesmO1DS3vmifPQxdaGFgOKs4qP3OOvok4Sx C00wsXFgLiWBdqCDwpDpwJBLc03O2TEdcW196qpVxV6mcvphHW7IYOfN7bELuErB FHgvxzKbcTnsNAs4Bu/wSTr6Sy3etJ1HDek0Andc2zVu0ghzJ4TX0UsXdKej+mKT wbMR41buZ+DMCldGo2RaH80C1mkxfsUF9jFJaXephFt4H8/7XIIwmDpmvwZ/m+O9 T5qtZsiZBG+Z4p9GzCPIQH0kIsHxYrVKFSA6eR5fn65No0ymsiyjpo6VVlkZ8rdm UhwFz4YAAJYvAyOv/VXS+Q9A4/4No/q2i1WMa8bYkD2NvRLlmMfsQVl+zSgjfUpV 9GniChPNJQGcPyj2n4sMo3ZOvnvo1xSr8UjvV29HrbdQkD6OOlMIRXt5aDjZonHy omG873u9wLXlaBjVzLi73LR16uJmaRNsIiX3ATCHk3UTThM+ZVxqC6mQJ/ncTxla D2YWwcVKxZbTIYSH6euPMJQuqFP0vQIN7bYy9i689Gt3HS2LWwimqEnEDaZQHtld lSoKCqlyOukLubnxUFRfuZ1If+5VzGb08+2B5fl2O8LohU6zC9ovzHM9DeSlUnzi HwUgKCKJRBNiXa4PxC6rmH2onR/2CK+sFx0Sm3tqGr3h4pLEkWbTfSg54lf6S7Jz Eqv5imWQV/5hGYmi9RES/DOBRU3X1q3XPxmBXPt5XZzm/Bd0fnSRe6wQtGnPlkeR AgfzhWTLB6T9qyAPGhURd0oIynG3uzzsFRo77yu2Nbxnvl+6SMyEczdQeFQdMsYu lWv6rFABOYLDhwTyYS7YQ+sE1eReDyvIkxdEb+uuGVAw477XGAnUuviI/HJ28oyv FpjHiPwoeTMYAd89a6VcbzQEgQZ09bOrHzesVhef6E1SYG4Lzu8oEPGKI5BBaX63 2eE6clmYmKeDVeSMscRSMlaHIK1sTwMoOleWEoUf3hMKTN/evOlL966qw1yDRq5U LYQHk8YpAIL2n/SxkjCmC+Sbz27pmJo3WR74lNnSP4bFpNG7bZpS3Csjbz4a7OEQ z6R3RNpEd+9Q4XWug2MxWZXSa/cYMyeBrA//EGkwGs0rDrL+OBjOKoGy57ztUoXY J7gTySHf55qb9r3DfKDEx8Fp8YwhHdx6NdLN1guX3bU7Vi+0uaA5FqYBvNfdFQU8 TlOtwauTHt0KS1/EGh3ZLJSkojHuGxvN8DVy5TtGKex3LCgDc9C8wqjfaCkSaaPt 2OFW/hu3p8znxIDBGsKWh+3B2FVa2BvHp1ZsEbHDIzKQtGy8qyOyEzDFWYIYO+PE +ERTsCz/hGfr5N9Tvdnycei7tTLgY17FS7hAMBu+tpIJ6f+H9iIOT1mPgrN+eBrA DtSV0kB7oEUoX3ITKz17mXPUSfD4RW/Kty+hpz8MdFC4DiDKFuyehVStzwTw5plk uOvB/k5JOXJxVdQ9NLNKmbv+o5LmwXC/w4GrRWSeWKSmKOZccx6IFP7FfUYC/5nI +9MAn7xGdLDU5GMXinfeH5gCll5F2DXTSki99VU/D4r8wxV3shArc8hPFSE0k+3+ IkiP1WmIgdmN0tQzGYJuIXuCAroCYXvB6vyQwOVAhvAU0zy5ut1SJEZsiWxi9lx5 dnfxq2YQwv7PJu5zTF9bimb68AheLCXvoDPOtyEMqmLICDwwRTxuYIjn/PdTRgNg 6IvfOdtelCX2StHO9NvG/HuIdIThrJZvQf2/UZb8bSXLivp7YtOVmQulwP50L2K7 qeP5Z4/2TA1Xgqev1OyDChLxLbWbE7JmDSq6Cxchfr9cDpE4s+x9TYac5qOZ0TAu A3XMfZDnku+qrzE6Y2sR/nIggL2HBGNytLAX5NfjMtFqtXlDoVgSE5M4BThqfaGn R9M+ZuoZpSa28h3N/MpjvTm8iDg9Zc5sd42QTGXmIp3wtBDCR5behefeiIs46ytd 1tqJk2nJihcxqSh0w9CQ4sEOJKA+JcCbXmPWNk9Wb/mvdr8MNd7tRWWIG75HWGn7 tvBhup9NYu4fydR+hbtlzs+dzThi/BAFH8YfyA7aailGrbbWYc/8BV6SBsMplWUL IpL10aPx73YJWLCZEnKMoJFbSedZKlF0Xc9byiacwPBMvGGZn71nbwfQ6OdCQROu L5K56fxKXx/64GEcjFWOzFnRTshbMgkcDb7B00QurRkqT1fXRVm9ihqAzkLDJSak B9xwGhMSgHYvJfP3jfaW46R6HH2RN9fN9sgKhEilnOZ8yvpKq+AljBDQqI9BfXFH EjfslxORrDzif7suJrBLQzwMn34K6PqK0G/ha+Yq/9tV9AGCXqn7LpY4ib6CwnXD +AS/DrM/pGtySjVfCCphROQ7EGgVADf5CJ0uH9uouYphemNVqQO5yZKsUSLwiPma WKV+cFwSXcUpxbpZok7RPd7F0INiBEKO7ZVHtk5BPtNiIiRjqzQ3RCzaEgD0S1DS htJubbAbztYyGX/UynMcSz6sO1f6P3+SJoG7Dgjx9u3fVgyPEAYQq6Gpc1r+8XQ0 tDXYqs6d/tqgLjUg0Y2u2amHQOK/+OvRpm1xvGXwpNlObhKYMRW0WZ7S6F//2dcF bLFwZhWvalvAV+mPdmmBe7QjS/CSQUOtWTAv4RWRD+kBLor0EsZhrzEdVqLF7yaM KmLH4NjnV+NvUE4Fwahr175pbRAIiS86t4QMb73UcwDu6TLzIEjBdAR0k4yBZPb+ S1qkPj3EnZvbHO0LT1a1vNVghy6dYBi6bdFaDGcF7LIoprvYqfFKHFByNiuHRKIA NN7mbyZ50Rqlco/W1kALxAPMncKCOF7E74nnPpgosre2SQzwEpbnIMRCnDXk1YfJ oshU9IITmxeunSmaPeGAXta8ughblPRnV3ZQ2M1CfYS91UKnH+3RVV71Gu7QjbCZ 1EBd4w9wKXWT4SJHmh7Wlet50rh5cutjKTHlHIQgout8jN+8i6Wv/S0rN5UIAZAS NNyUbbRDJbROmlW+z3aE3RlPHWuudU+sAO3Uxxsz40oi1vqNXDImPmPA/+1L4zPA T9eokg6cOnXMFxcFX7aWhp7AlGV3elvKHnNvrUZB/123b3qbv/wKMf04avDe9BWO JqcCrrZD0fRSWJqPPxUZsiJBFu0l6h2gRyQfsuxmN04L9BehAdBptD+93mkqyYLw hl7x2JknwLC54gV0niE5gS4r203RaFbB4P1gF1t4wpoGIv2vzewSir7m6KBtyctl 9ctTX2TqvIGEF0nzrWd1Ifd2jfFQ2pZUwnLAjYSaY0m3Z8OA8TAAp6vnbIwEg+HP lxq7rlsBz0uDre5pL+kJ1RElcrJpgeJQfgFtOV+DLQvz9vfa5bMw6L4Zq45HsrFI 4yjO7ObxCrt74i5nlsZnE8j0nCKQBLfgv2GWvFccocS99ABKrUZJV3nAaQMl/GrD 6fLORFIZkzQVi58aOjLxwE5RNoV1rGeZSs3U+hlmA5Lg3Og+Ch1jMF1p46A6mIMx Y9VNV7d6m66T7ET7zbHwTMrkRw0N2jdi0xO2aRIGL/q2cc9nFNV1yFAoSXF6YZcC knZKYzaom9dp2zlLjEqPKvdhPovli95oD+bJLbov6Z8W92GbIVKhNRxIrNE2Fv1x Vv7XnDrzB43xDJmdGzsze07i2ifRevPLF8HZr0Pa1AaIKelqvJCTdt2O4xN28NtR nFO+a46llz2B4sp1Ifzq65rZjA50f8gFbmFb1SwvoOGPAKhZJn/meZof7YjSSPxI bi1ufhTCygyuhlko3m6REc7NFauuoKC+KfzM97nTeTfng9Gl9pezZNsoyRWXxOaG YAaQGzzwlbrIsCpwMRDV8XmOCGVtb2FrHUBx5qzD2HPlG9/1OGozNXouaejxnvyH FCCf45KP66bFL4CahQRk3T8zQV1cSyPkBQ6vFqPK1ok1twX+aVPGsi/sd1cRq9o8 BwPQTH4zpK21F/n5icrpjZL7vRTegzzpwvpaqI6LvcFiMxZQHN0rAm1tUUiYRf2z 6nou99HLe+wgIXqeQhT9+Br6efJDxv1LBdhP26u4YCnanpWCFkqnGk5Hqwf2XBBX ha51qFQ0b8KhDqWBU0Zrvy1SyQ3utQGszHQScRMIKlNcgxE58B1uGe4XtudpgQ1N C5P5zsrN0abgkBKLY+P07bTbalB4GjV4pSd2sSNckkgDif7bjta2HmSMXsWE0zha ueomRnkW907X9x7Har/t1/2syElwpaykmoTQ3/pYzcnv920/OvHPgcstOq5hC1Am x2pLgls8apwO8T5D0nDXyiTIOsgBnHShCRzNtjMXBQamcf0tZG4yKAk0339bhNCz rBO1RdfR86l5HSc41EAZgNjNNtxg9UG5i10kao8tuELzqQoQz45P6GYXh4CyBbGa EHRtABNHOhjuv0okrqJ2kG2r8E9KWjBfjgjYssAhtXxfgPtqyUJ3G07MEf5enCoz HnGMFF5pBStMcJJ3FNSX/gxUBbrZUGmyfMNMbGRAozlYzhWCTiEVaxIAj7P4cYYW tDnGkm7hijKU9Fy7OJ0gv47VHxDasWzMwoF9Qg0qQjCC/UwO+VToXjShdVRv6M5W yT85C7mzfLyWNjhVzeIgAjDXpqREw3lLOmBEIShDkJTirMLY76M1hIs9DpMo9ZUW mTYiMWMNCQIO7yQdvhIttT+9FSBXSdth6ZFYQi0DsU2vZlPnUl/Ky+5PTuT/FHDh htZxX5CErDVFT897H1ZVbl4nfQ07iIGJuoSHeIbXIfSGcKA0jGR3gFP3SV4DTpIa CdjureoaI1JXBCyHd1ubnbgfTCNJciglIOlQkDs9cgI0+NDsj9RdXXCZ6gfGpWes S6SeZacEcCXMd2eVJ1dc4IbC6zQRwg02pYWp/8mGSu7eocSQHSv+7WR6iAraqaER ueRpRPOY8lMlBu1kJsiw6W1qgDfbgC/G9umf2D/fHK/VFXo5xnlN7tPI8Ly2ZAWi GWgH6UxyeXg6eAnhkc2OuJMoE+19kBR5wEwmQ/jaM9mG6YVW/OJ51egZ817NBfUS o84M9iKVkRU+U0l4n6ozM1TfkR53zxsAATyq8VElM5I9r0/qtP+cpuHSiO9SBOY9 D8yei6+aUb4eJiQl4cOvzDXFtg+QoypB+MG8lTGO2VT/KzPv9TAQhZCmsHdoPKQK YRGkTmSEGhfdAs+UX1IvStmf0hoR2ymi1E7nAMdBwt+xnzj8vWxQG7N4kZaQL9sh Ymqc+l8HUubKVj64D26J4Sa6pzoVr/suyN45yomxKTef+3I0iCL0pm7MjP2z53Pf iEYyJeTshFGvji6gp8Y5+wBQvqog5elkM9BZ5DqHiK1xNxC25GM+q9BBEp8H0b0i ULHKyjCumTP70GbmSipYxTHVbZ53HZGAT7aXCsJgvxlmA8RPuCfjTwp8EkQCmV1H hVg9SRctmMUtyxqfxWOL56hlT2r295K82gh5I5P0cc/CBASD2NYFdSPnudSfyoqa ZadNpaejdfoWOicW8JSuBlhAp8R4kKIeowyHkSAWTxbk1eF0F0Pe3gHm6Nejh7ei WuYsGEGDzVrZGUuuYhcoctni0BHl1rP8pojh84RPnQ5POrVrX7IH3uKjUdnivzDX 4rTF82nkpMJJ5+bS1M2IOHoAZuM71vPR0Fy7+wP2+iuKCb94V631/VmipqP0SB/d Na/LIwn/UVeNPCSdvob1FZnXkuQOEFAE8rc65EqO/0SBUKK21oqM0oGEmA+P3ctc mnkjmuN8J7NxsdY+YdRtAcidaWBpHQhCDtIGOhp95HX472CzrrdP+0BvZUeRgoQW LSgDu0vncvirbsUMX4oLfKr6ZnOZfLN1Idt/lRMT/6YLKdDC00jxOM3dXBo1f6sE d7fj7bT4f3V5B2CY1bRaaoJncKutT9yq8KCqy6ieifdzPOveVf5u14HjnZpGHnSD UI+hZkRI9/kvL0kjOAWDiMMDxJq1IM728Mqh2Uq4ErtSlqUuoFk4EXnRmpyegyt0 S2xa61NlNTq/lWDufL9dqeys3f9fb4jLhSkawONUdDa2lUgdt8GrD7agmrg+GSV2 9248wC/2DNrFW/080ScvDpmhLWpHGvOGUs4K976ljna1B5KPmHS4fYjUOqQVoYco DlHbjdo3TsF2nUZ5OzjNcY15vgTdzE6aMguBoo3icQKCLOZnCqVUIVeMLYCzQzz6 hcLpvR2ezH+LbKJ92yCbWeqHttfFCzJ1fJd1r+1yKnWao8EmRS/KYB1o4/UJTzLT C+OwL+OF4ChOPXTmpMAHvYK765omUUem2zxRxJFOeqCk6t3n49G1vZcDi2+VflJI nRxocp4q4MMWQ3wRXMe/lW7wpe/B/Kqmma7NvUuZaWrcwbE5azsVVlfyj1Vakrtz 8TfMafzKuZxVHgf0p5X3QClZ1MLHuc+rA0DKnHUTP+bOodbsswP2MpbHt1cvS+9v BOalL3iCoY5x9u8kJ4PW2ux+Iy3j3OdwWzDPrUmnK/AMx8r0ALZEbmRHP57rCJ6e yDlmykSvTDWbVu8sgUQsS/+o9mHSV/TpZ4RSIbzJ8kxamDdK8IerAXMizB+8U/M4 Xc7kUmTtylAobuNvxNiFv3HfZBW4er0+60EukTz55EkZNHcwOagjCQX39zFgqTlu l0vmTVJJKbfEJk3CEOuNK4O3SzpssLAZ11nnY+b/VE661AEarHO8+m6NCIUmJS6K XUcFyqNbmw6+WFRlv3CAE3hSYsS5wMa6YOVrlvC5UoGOflrSBWzfl9u+WjifPMV6 T+dLwznpXgNYxh780Px3kuc1jW6812jTfcyAWYe4uGM177btSXZCZ4PHiwLGkE/O B/yRRWEVo3L2wnkF6kguwJhPFKtcJca5zYdFcKYkXNC/UaEehcB8A6SUD17DOalS ogqQj4uwYAoUg4ztupokD/+cpA2+P37XGEha9U7HJEqVYmfdVB/ix3ow1S+GbleT W2xSgP0M5HvkOPmYQPX+vcAW9scneYi1n9PxLZjZ8hf6JEnCo4NwHJtej6veR8x6 o5Zfvv0xr/EkOhBb+EEsMvqvn4XaGX1MPIz2e+E2MMUR+dhkt0razwhKJjRx0ZQd R5FZM21elnSs3fSQOtfqtVE+AZZw4RlFoDRrPDPQoaCzOHYeZMCvxBW1DTjB948/ kJMso5R2vyqjNGWAN4l7fUIcE2Rq83Su18YFtGvcF9zj+UF8o7Po+vMwWLum5yVm SLXMGZ2euRaJYH+mOl5vEmNcohZL0VJn1Ybgip1Rq5vO3xv2uilerqfbl9Y709po zMZC1sXxQAFmKYvqsHYSVCqUqKSOF+Z9GbSL2Dp2+vhJM1uTbBm0BAF8dnAax5wY Vn6PMrpL8dL9BqSXaAFM8jCYi7v2s/lx9FzGGyIWLlQYRjTfFukhQ/FDnoaAmahq Gd09VV9ZRtOEOJ7IlJN4AqJ5dq6zix3yFSWYWFBzjfYN+pOJYqaehuShGwI3FNvW elh/qkktRQ4p+PsyyZOf6hUs6Bof6w/M/ZCnupu62iIouvNxkhbMuBJalUlxfr14 zntCl5fvb5Lt4AEPbqU/gIR1R2bHFepkZhGMlNfeCg0n4zzMkIWs5Z36hbTawupx /nWXNoMWrLbmPKHel7qHJ//JXx5ojbyUZ2svAB26plBQi7J4aAotgvxYecffIqJH X9EMDdnkc7Ukz6s9JVVnNB84MdgM012Ve+mbxE0sxueF25zbNqZYWw471ePcLIg4 79lq+/hqodCwwp5dxn5tVPUMGf1wHI/QRkJcOC6HSr7t4Z/HSvzyKVsGmqrfODXc DZ0OMrrNN9srnHr6J9wlZA34/ib+0BvMMWfXo3M/FxlBxcI2Xk6ELfvyBj7TXFUY QVquQuBsCKv332vaqKVIsiy2SEXmFwttXq5hIvMEUe8r90Nr+dpJBhMS3m61Bwu1 UnW4Z/nZkT1ByfI5BJBp630pCZ+pTdMMoEEV4XW/bJeG7tPfvGJxTaMEJ3Jk9OFD LgKbM06vwPV7omzS6Br3EfzbIGNln3B4gYvtEt/GjAOMxh2lJqxA0hKRtOBGNF67 q9XEi5IHpoZWV+UyRJCCxw+nRkYwSw65k73eCC8AKvFXJp1vMg//0dbXEDBUKzQr RsjhifKUWFDXOVMLMZfafq6IXF8gfV+TA9aF+pFAkZtTdTk9YHJBf/Ae107IFsG2 XKGpXPurIBp5SZ3Zfx0a/2fzgFTcbMF5+LmWViFtpR6+1ofxCvJotPlz6O3Nhytz xkeHUlFCGlVwxPCqQ3MQKDrC8jT2p9vNgZNb94tfQf0CcR7qdn9CSRI97545dhbA 7ixCn3J7zCUoUj2LIzZ/Cd4RhNWzgECjtmtqPn/NCJ5wG9cW/yJpiaRuvmaS6kId Zh29bx7lchEdprdncDUI36SQtCjSQC85gJtM2ivD/x6j65AYVnn/PkelTuvcfbn2 igCQ7CnTMcMxLDvNUjxrrFtqFd8txKijEXmZu8P6JN/KVNwjy03+uGQfa1+gKU1i /kOHqC8rJJOnLlbk8E93qAndoHh/2x68Plfl8lu/6cMyHfQRvSm/Bqnu5cnHxeMq 8ioyIRE5ajVLd6hb+VLXk8eZfJRISAOOZxAkFAw2EQF6MSXaYJOj5bBVSHhnLEAP noH7juIJZsmPv3EdyjZgIjZ+OCq1eIwYaQVJSuwaJOWhbbBNc6N3V8s6jxeuK7ui 84z/fVmBJmOLm6KgfZldshuw+bVMKeFJf+KGQJSZmsE0eu0EPGwEHoCGyqTCVFEk rNPKES4fyHkw5+RpUOoqq8hR26+Q2fPgJ7+dUVtk9HzNQnUYJvtYJI5MPEQ82eEg 1YI6AAAjSpm8f13n5bZROQuSGiAEhEDR8+h8TZDOhdBNuILBaW+SaItZW9C+mJka od3eZ98QMuB7IBy0fgp9JncOu92I6LEAJq1EnTG/RBWx6jDuCwz3+KXxya/Qcdd2 XyWT0aWwJyMB0xdXzQDBSins5Yrpsusc3FH03jc1Z2fnNDFxIGD1GX4jfHzm5zJA 4sUOE7ch5ZWfhbfOuisjfdr7EdiRCTU/R5mwoVaKqFrljqwp0+BS9cNCDNEz1Pp8 l1IBlQmQkB6XiY8/nhYfEf6wBLRs3xrkuwXmxGxnwqhsFv1VtA9Vz0FxHAkke8mF hITDLFUKuMwGMbPMA3TA3XBlFJ7ldPRpeFakXeJqxubv4yZwJDnIsA/TT5VrKpCZ jNq1j9QChfP85yNDWXDsvlebz+b+NUGEHKrbXQTqLFrHrxC1d69c6u0iGhRz2Cen yj/ut6oMzAkesgVMIbudbbSOOh8tf6WUZswGZ9cMmIw95Vcl06h+Dyx0l6d66ZRM fQPiPVgskB+zqDfjSvRzct7Pac49HWklwmizsywnWQVMUYgwEHIIDfMZOtUbj8FF 7oxd8T+fdefH479kj7UhVQAGsPJuNzhLdfkBvmR5HXV/gBhO16hToCbxkNcrlK3/ a80qN2RyOgcglYG3bMbrJi/RLpbdO86WGFeDicqZhpongoGYxf/yMH9SqTmtqWwq ADVxpTAidXl05rFU+gK7ZcqOklaxi4DLU6Kv+65bcwk0M/xu3GYIFo3UjqyZaDc8 wQEgA1imfUs7jNUueu8QUP8oU3cDWoqP1Msi57vA466c4VZwy267MKeqTaC2brZA 2mbxRnBK49AecpdCxfXeDC7rvT1ykKm5j/TMWQMMNIwqIvTLmNOGJdjUfO6WOG2Z Ei1eEcsY4uueaCmVqLjdKwrTc/5x6IwFtAucUBtwTg1OoUiT7i+waHAvX7rX72Lo 3PVQlRwZkM/EqjbxCAG0+E1OGJVRfsyYtQaWUlCtdNhgcvyjgFBLlT2Pl7xUm6NM QzHrIMP8llW9Mmu2Y7fwlbGC3M9galTqwxNK943QALHAbO0cl0XXKYYSrQ/yXNlT aAcA+VpoDMQP0xOvr1AMKQHTwbDADDFPpDeOylRAZHoKojZm8oPvtSt+uzOmZ9MD ls205dcDZVu3ytHhZe6uv7AL1TslxfTMi/Ux+bJTmLY8bjgHZjBpwdc1LLy5y3Tb gVCeEYa18ObrRwWVu4a//o75xAj3eLUOKxIpiNsPmJjdy3XWkDnnNhSiNgWjV9bP s+9l7YeyMMQlh4bmOyZFPDfFnPFscEgkJugqKRHsc13GDPhWWr/ru7wtfUkNtMGJ nHMjxi5DIR92exqoMZMzv94IgNVtFUqpVdWigZbI2uc5HKCUS6K+Zptg3j59pVYS ofJl0JRxouDZqsnKf9k+SPOracupxbCMWyDp0GwvPtsMqYCgtEFp2pwCgGRvC1m3 tcjjEKF17W8pZHiHZ3lhOBktpXKFsVO2KhfrLF6JzuySNVXt8vGONuQ5TUTP0RGu IP1LC+uVDRIuWDWjtHAoTJEePgjZyGuhSc45jmX0WkCNc53UG+UMKaTaUO9jixN7 dv1j6GR20QETv7Kg6tR7aEZWSE9pEqAsxzYI1kxeBvmUO3fnHzSq7WOL8P0A9S2p yMvfDsW5tkGImBULkGdLtQMsMTyVKZe8LDu0SwX/uXbr82/RVgjgfDyG7c1Y0qqU 0VcGmjJkWKWWuf9TO0zcmF/h5fWcCHHpWMvx586JA/8NCPs1Rf6ayhm9Q6iOo/Zv dbl8rGe/s7fFEtMdtbZrg8WmCRzcVogNNwybeXmTcuw0TWJ2+pbXqvN38UN6PKFi kVUAV044VfsmbJf8fs1nQhRs/DC2IxyDKXi5RBYftEKeWBcrZAM0q0LuTClzksgV nS4mBL4Yv/T6juW9T68hr9EMbUwngyrK/aYwBLzlc5lwrzt4XmL6WPjHyfZK1EL+ 7McFDKUYcnZaeTqdo+ixmD72BfAhBxU7Ja5sJBcMspUEABwPlbOPk/lUUqB3A1UR ba+8qsoIuRcV2dW+czO4bakKUCNGVkdqaaXaP/fkjuMSjuyPBmEJ1cLqUNm8nfmU eBOB7bq8knArQoE7+XUwyIKyiPFZres3XE3ayNEzJ/+zBtnfrTimMQo8lzL20Has dMmdkN13yonB3OSuq4eve6As8qc9o3VtC/k/u/n5BSm08jUfT2oXwCXOf62qnoYU L+57iSOIXXRVHFTfoIfpgW+0WQoIG1MsmhiZWKqp1mxWuGIlbr7FVpU8zzcHROdA HvVZBoSvsCCf3on+J8ee58sgJWiQhDWCQOKSlRcBgdn45CLTO9YIe3wb1ur9vy9A +8gBHMfX7lYbzdeldI7K9etNZJssx39mahmIC16EfG2dN1arUKBUV5UIpfb8hxTy W7pPrT7lw5l6s0PaWU3eEdeZ3v9OMLf+8D9O96/5dh3WsL07m0KJJfZHy9zcKT4x NouIr5xO3NpxUVSdMsANYfzt9GnJqvaQ2ChorEOKet0uWd1lFyr7+itjVcOVablu RsPAyUvIFAv+tpYHQcj0Pe+CMspwBf8vfORq3QORMFnuzMbqJgjjA8ptso5/5TWh T8J1UUeXESxddoBpdJ28dHHzeEAxFd4FDCoCGl4jo1cm511nfDjjg7uyNLKyDgE8 lXNOsCLEKnlgyNrh9lmmBTw5rRhcBZ4qNSZmZIrTfO4vo91BTeT++vXb1vcNa2Nw MbnEGDD+9lCXvOl+RFivGnqvNaMfT0mMS7mQlM7eib9Pv7Kr1IBx3eqRK0jSEIa8 uojZZ66TYFCXylejEFQc5AREBTzues7zaAtcNiJ1om/GJ143rI+xksZlqjBfwdbY nLEZZbwGP8IsqsuSmFQCRXobPuwwaIzkNdDmG0D5Lz7MMMhTGTiJdfLHTz9MVQAC KOfUEWj4A1U8DIXVAdLF20XDWFerS/PRaCbeCJ0mYIj2vPjRa5omIvuEHUc70GKg K+byWYmjKnc/XiH/SDE9GJb7ndIN4mTIUwcHlp3ISsvR/dlhuk+6LTbx4cemSnvG 6AHwS2fucOFC+o0zDpzkWTi63w+Cyv7G53ul0odX3phZ7iuBe7wUAWQHIa6fZmSm 2U7Qbm8u5WZ+P3NsvDsPSxXFFD8qn1hzHMjc5RzLDqq2CrA6JyfE9a74dRU+uXpP //o3vm3bR6A1rY/Yd1GqKDyGcxwswk/S86Q/HBk0BkkjR0CEP2ch1XbNZLtKbKkN WbrS9BQpUZ4BDrUqLX1gP4SZfCqVNEolwUYQRV13Kyhg29cmxNrGjs8Mzf7w5MVO n4IcC/PBZrifg+90zKLRZySILIplJndSAb28dHcGThYZOwrZ2N3/2PPY3S6Oy9aB k95Zqx+kNL2c6bi6xuggKCQ/NvG0N+/VkNWEcLazNtIIfPlAyLY0j9qLtBChM0b7 JmxpFY3nER7yJYV5ab66RKzuao5bXl/TI6d23FZhjFLkKs9X97Yl6WJE5UQk5jWV luz1KAfa5nEzywF0CMXLBECZ/Rvcm1nSWkgASbQYNtyYi1ccSuYRNKnnBbPYVgVT me0GIMGCjcQ2l/mWAS6EwVGsGPYJIJ3Ak3916GarcQeeW9HVjeYVVBhu9xwFOhfk rCYvQ4p9KPiEAUmWmapI+Gz+Mp7ub4b3yF1doOQ6yd+Yc7X/IS+76HjWNj4AV1Cy yX/Rt0+d+ucJ96azcPBYxxYscRssi8azPD8jayeVovQU50m6y+jysaHta4ulijnD bvk1Sn59yyqb8vyImCGxOhJ3TAQGMVrlXh1Y0VrZpIhRSIX0TT6ZJzk0XSR1zHlr 43lRI7o1PIx05RlNUV0fS/ZFHahb7+NexGucgaU7trJ7szXYzp/WJF14SZU83xld rLvZCXvwoK0C+xvJWaj6na8atBpVCZ97nSwFZNLL1BTFSMgRTyWzaXKbZoIcK1Yf Hk7f0H9zEPlOafqWIe5F/Mm1PM8tckuGgswWZ4X+UAGbPDcwP9Kfl/j1/eOtgogw 5sIk4N/pqjcs3pEinZYKQxje0PAd671wxO/9QCBEyT9m9qANsxizLQ23EvZKTjXs W/ytCP+pfjZYsATdbD6XqJ9FptvPsrdue0mqrcJXD8fssqvAjrvFmb+gjZonSY/K T+rvqFMlDluUN4fHt3aXJ61PjsgGzs+uxgWbGp47y2hlsMx7g/7sY0hg8hDnIv7V u5DejLQgdV6B84zJvzgdioMvd8E9ZhpT3lwaR492MFoUjRNqbwD8ZQ1g5PDJgOPS DXxxyQGR/0a11SXSwDoc9as4MkZ7Em9OfJJjBtRvVLJGBMQZm120jxqNi8NF7yhi LOzKGx1xBetRiS2jO0DwaskVGHFWUsFa9NjKLu4JWnQudTTUpIojWaYMIP/Lclyr CmEvfRmO09OdHQDQnuS+eFK/+VBY6Z5kENXUXDO7aoWsILG9Q3O3fwn/02aqAJlk rBSHJeG4ZABxyha3AnzLv7HCfkL4Iysev79+aD2bIlzBZuFPhLnHTuoZAJca9FeN GbRfyHhAcg6Gz0cPWwy7w0pdPpONgIuW3nYm1D8RZHboKL/8hXoZYgNMZkJfig2A 9PSUS0f5QbGDHFfflOCgmNbTdIkvEphbA7yvO/+E+UrOO8XMBxQp8o33WZ0mfeJ7 ChdGo44dAltmc9kaQxJFDzyKfRliIBpsq666Dbxo88A698gtcnlRAZeXEuzcxnAl wdUoiTpVvsk6XJrszpTnetyq2CQlHr3Wk0sn7XbEebho036mzFX2tWnKaHJOGymq FbddVL2spA24uYQ80MeQV+s5ALBB05wJCUX95rvKySE7Qu7DehqJEl85CZtQy+oS uFzfcx7cAYm5KY7gH8mvqxQXeZjE4ZF03TkskdGgMBFvlH2ZEOi5mXcSQ9QEtvfv G0gL9hVWn81OS7TI6b+aJW7tpYesf1SzdE2tBOcobyPIQBMsVkgQvOj6Gd4ST8xO tLlFhV/PqmXWMxWYGy92/PJke0csGu0BR4TtpPNCKIM80CbSPYyau5BpV5X03pBs O7pd9ZjZEM20h9ouJKXXO0ctbjwxeuOCPlIiPFU0gTYVUwrTwlr/Zumjco/B6M/3 IoIGvwwRgZN1mAr9kjvwRHqs8slDp++oD+mvopCA14fEfR4/VjOh9Jlf4/j8bSBI QKiY3IOGc+JWmLgEJZBq5g9RuZY+aur7PFV2rcZJLK1bA9AvwB3OFoSrk/HowENP 3+YKdVsMrTGDTkgDwmGQv+QdUJo/WCelvg2qX1dXsXpec6s+ZgiucJOdTu5/3HD1 KzW72gavb8j4Rc6R9N9oE2bSm1IdZVPdi5hLZm1CJQL+wjr4tm1q4nAu9NO5Px/a NpJDlg9R+1SmDcUmLxY3DLvofM4Fhg/45zpEDqaqU7+GYh4+tBh3rZ2jIuLsfEA2 1Q2ChEQAA9crepRwOnfP0OnOfHtKhXd9V2cPV4CydKJRCCK7ZPOuTALmhM+AuwlX /pnDR1NImoyuw083IaM7lsvBjbCqt3d8sprniquvpMm6SP7Tl3+90Va+TmBcvCeQ sx0eLpHzcHtSE7f6GIImJzD3/T3ikgtOlGqDrESl1uHYy9KH9UZ6/rW20XzJ7kh8 VfcFIzn1fH3+jxYUsGFHStF03nGoiKR5Y+ub/v2TtbkOO1T0jrTFesxxH5auv6hW UAYoagW9cZxpCXq3LOhz0QqYttCA0DVGOA1u1BaBfHIth6ch6gls+6eRBnofzQot A7qYC819/DEj0xU/TRUoduxC0kbCDX6F71YTWFxPRbrP/rKgxebZTKcP97TOw/1a wrdEy/+Q5zvOjc+Vk/HpzzMtAlYiIVXXLu+ObNWVUIP5IqRrD15m1p1dEvtOCinu tc+eLC7FJIo8FvXFuaAJhTWkOrlISvHFZQPwwljNWPA0H0hgvPqe91gO9qc1Yz1O pTIUPEMBHY+qycVd1aZEQuVvP00AdRbo2e2Izv9kX0Q/Gj7WavgWu5zh7ie9Q2kO EDQs90jlwhT3Og15GqbdKyScZh6+VEPrrGF2lQNCCyd+bPu/MNwFY9vGoWGqWNFx /o0QFPekQljcqTcGU7FVqJkf9vEHMgh41CTnW4QSPaoN42Q80s+rtcaqJKjpj/8Y 8CHN2s6fzAHPN1mjNCGJPBSDvmCxFkTRKWbZ+cfRKI7RQ67F9/hA6N3ahbxCzIUc zLnR9FvHq8QedyYDtGMSR5FdWOY9IQecnpJML6SzhwIaI6W2anSB+1HScWcktWw1 /f9X/w5j0eK7vHrpOq+gS3Z3Gi3pyb4Blm36PFEsF9BG88dI7SoHKjXLuOPOdW/f m+8m3ryoYwFdapr+SzbgPjMGoL3dwjmcLpdBkhp2hBvjvS2teSB/qWZdwaT4s/xS OkqQkhDQ1pGivzSSl1L1Rv/cy693cNayUWY4DSCLxQtkf3Elw6i/Ol/1Gw6qmrQ6 SiZ0Et3oToKk6vLViF1Z+dqDe1YiO9j0Z8W2b4mG9+MHjft0onclJKDdrZuFdBKK 3dNAXuG8AX9UnqJXafJI1+D88tGBDWDVn4uaWcPYB8Lx2r/QIbtkQcmC+wfCMwxB 2qDMyDkcYNVM59Hn1953dm+XUhKqfch7K8zelFWCh/XosWPpkBHS4aPD94dBnvoV cy4zrMPQJpYYX3bv77NgYdiAWSGEcluhe/TO0Qa1tqVtSHtvuVxvxihIc/8/A8GX 1IVdKrH8ta6aP9UxXkltf7J1lDqWpkqzP3PmmxfYHRvfIpeOTNZY+BZ2R3sM6mjv TuQ57q6baw9BiKeGzmlTNiBMkd3IXPH67/Cob8TeXnyUnVyHwQqwRrOwqNuMHQpj jvE4pEPbB0jae9uiVDzyk653qusWGntw1Dg7Vnx2PBnvIN1pBzjVoMJu2C9as0yA 6YiC/kHtLjmlUTuj2vQIe9oH76VA3b3Xn+76GbSwGYjW7lF8/dxj1vFygNSv3ao2 +O9P16aG/whfQfDK4GSBPRFCXMQdjOpSBf7F0FkALkWW4vAJd1EHjLPJ3JD/Teuw LFOKVZ8lgDJaPJCBer+iF0bYrxWI9lS6pEN0PGKZNKmlCU8Vt94219sztcaY6qL3 dqJ6N4WG58gV4IxaRoKVfEs1r9jsF0VTDpMvD7UGaPlJWofgQdx2sq0f3HzrcBag nR9rE7SbrY87AhvG2qd4cgoF7eJsfKDmjKCDNIGN1POfqekGTx9A5Qzun190Khsf DREgKNbd1YtEAO7Yg5Uu1dFXTnXhHQ4MsvySt+kqt7XKcsUO3BXKllYpGaM9Ycj4 hlPaARldaBWwJw5aPA2IUTDxUPWFxdb+Z0TyvQcZJWueAp1EuJxZbMd2ZWMbyFr+ +OTlFTW14bZPRlMvLRK2MbsDcL7DMoeqVa7jk7ujy5Uekz7uMYQ0MVBoO9z4MMl6 D4bJCeGS+VS5+5fRrdxpKwzIkPLcSlRmbJiyBzoeSkWEDgGUAjkhtsQx6EFpZx6o G6Q3x7xEs88EzVkrIaB1H2G53NZ006fNCajWugXkEnYZmbsSbQeO3dtNzi6FCGxs cjxuLKnX6TSnJ7whAUZic+QfkewnR66PFjl4/2LGe6MJS8occUh7IwVWu2LzMSBN SjsUeiE4rj0CUC2ayb/ZHohRqn8yishyvnDMIQKLFbTyTzKpG/00IDftxzRK27cu CyOmIC99gEf3Sw0XgKMKhEimYh9nEKG5tvmgA8wNnNCkxeRlwQ+joI6FH753gV6j A1cd/Wm2xaqxLbEkWJ+2RS83zZaICsL2ADVEtMeVhLP9whXdf+OOELtDdKz8YEez GZhVZOAWpNN56LLUt1fQ7VrYxvCb5DZCBGtRW5c0S2H6CktV7MpZsSCXLC0O71hU tBA+fvJ0DnZor6oZHwSHxYLbba043npT9khqtRTjtx7SaKFh5pJDW8Wpg8lp5NYc 8Bhdmf9R1zkBoYcJRsnPrBUpfB1B7DTKDSfTfttfzdcEvs+3Cf0bVCt+1M4fKLG3 WeJtHBfGho+LmHpbOpjFUNkqK9Ax6dlhZlmxz7UCuMrXjvLBCxtAObLzxtJy2P5Q rGGUdHsVGuqXwIGYzk2D5/cFzWlNcNVrWkYSbGXp3J0vsJ5JzVV7nfK21ZyTIss5 fbeA3Vgm0Pay4ZAj96qSAZ3cdHiZmAtCYVK6vd8u1UzxBOBuKX5RnV0Bado3zVwZ 38KRTjN0Xix4yi2HGB/0OLBp1NovyjFCUjWhkiSOEFEG2ajg8mw3j0oGN4nsNbYa CxQPi/kWr8Wk6R2xMb3sIjBmPOlnvTwe0S4Cz8LLj3cpUOEcXe0qPxKfOuvE69O6 n7FDf7yp7Lf8fSoYfa0bMTHyUnNPAxOw49PhzXcVeFIXHNjzvp012V/cOS4vvDDM 3fENP/m8jh+aERRtqfvNo2Wt2rELTTPyjIX2EUmPggFTMUevRpyzbty0HcVQOuLs Ba/EWdh34OW0ZA9xZQK2cgHX+OTazHVq+C5bX/dNcSraUvZ/UreY49bD+RIDQ0n4 4vMqUCFd0lLKKsoUeFeLsq4W8v0O7rE30xztsdr/2k45Cw5ZW2wSMKg6S+GOg2w3 8tau92ZZD08bKvpS61wVHOKzDYzF8xgAevWvHPT3rANNNH1N0WiNmbIbxXXKq7Ln T0mPBma2V6RblyrCbVABteHdm8bOPN2gnEIFm8hedxRspjcV29ez2FI802E8jubm UUI64L0AJRvsHdaHS4FRExd0ZvrYUHzPT3CB/RtwmmYl0u0u01looxz+u4J9hnaB TBTkCypxbsM4nrSaqjC3FZz1JsGxi2WcGPShZw/2Y+FXye8O+b+rbaB76Bn3RPhu G15XXJBAAvxOJSmDuSapAMh1XrQBJxJfAKKJsrxzaeW5R9b1e0C4zhJKucJFkRTw 2oic72qBQhAzOpkcHgKGxNvFL/cSdKv9jdvFUZt1JhdNLYFVJzHjbhNEZwGSRLtB Q8bhbMKq+2Lc13SiiYab5eH0RFQdoK9p9MEn4ZjD3Q/Fa5MAtj2k71FSp9qF+f+0 xfsmunfcPcZRIgUrBX8RS9nuJPdxAqE2Z/894Tix9dKTVbtMjPJBqDr2DN18UpEB HH36vL21tQ8ErvC1wPjT83h7qK+Sf/7gUnXKWqUHfm5Q+nzLtv2hLqkiwoZnzyZ/ E4H8dfBkxm7GO7eNYW9cW63oO61eJyKdCy+p2qbgzQL+2sOJFahzz/wT3LQCZGOi wgfoIxAtfrUk7cjMLhJJo1S45mGL7XCVwGmmrCON3I8+sxLkyDhMnrDK3BOOzWZo O36FZpeySOAQQ/C//cIamWV2v1xpTMeixIJLh42BaV5KxlIcpMTG5X49CQWme3FB KVcEp0WAWOaIwkNygwmMxKNV3/TvlXyM3lujZ6Pea1ZEhuUyKXVsIYeiNXjM9W0m RTbPxqTj9GGDIoSyZoXiQ92Oj5Nak8+RUD6cJMMr2277jzIx6G8LbOubRFpShUR8 TAy2eylvbtlGRCVJu360QETYbRLBVyMsEksoqA3bedC2F2QTBrPa2JsIQgx6nJKR kyyOcCCSlO2tjz1NboojniGwXj9oPokQZu/SzBe1BXi8C7VpUYstaR2vq63odVyX 1w15nB17r2SQZzis3W4tKR8DQ8CdaOE/Hr8uzbRuBozN/FVG1w3EumG9BCz97h2+ mw2cT3EwZ2e5LqPn5HHswjrAvwGpjzCS43nnx9+BJyDudUxQWGcF9IifHGJMnwQK afsMnlYOi1M67zECASi5iPtnTv2TCoddu5QwhINEnKQB9xQ3jZC7NDaTKM6aKA8L iStUVMvwMJgyEjWtFwYsQ5fzyIn/4OT7SMCfqpqj9iM4L+pRp/hPUNZVDuRYx8Uf ZnXwZNOWIiQl9ryIedVo9vCa2QYWAGWmumI3fgW02OFFq8FWYTgwtJ/NKbbT79tN S9tobOQW2S2bXhP3iK90wtlFV2vca46aR25CL1On0j+VmEnISRBwpXkB+Rq5DjXZ S/lzT2HGoRPFaN+pyqxERE+OCtozCGez6Xk+90V/MvQEpkC1JnOyI4c8SLAkqEtI KSmUJYBr08nqkYGr8Dj7Kdumo2jYaRW/qX3u9erDjk0FbmrYAZQ5psK9pFzyJpVL VbSjXihB6rtA5Mn3epq+zrBxmXTpgLi1dJRupa95Wnqp2lmKIPHcn2fq0v8vK4vp qkgcXWwIdhoO+Nsb7LQ7kDR79ZTlWB4huOnkrIcPHvzDRPOEhTmEEzt69a52oeTy RJ9hMsx2In2LYU+yDhgU/97maGNLZYoI1TShcWf5bCnO2v15QnLiEx+6AXU5pFhI vD/BwhcPW/01iiKNPZRc2g0srj+ecXBQYxsUNvEwNrganH7wEaloiwTmID3rUAK6 vEA78r1Rop5UerwGZwzDZWoHXK5zp0pVMTTqs8zaZAT90RIfJ8JzrCR6pTtP+Z5P QMMr2qFM5MZXsTGtoYwy2wXxvSJ/BVH4tIWKvNaluD8W6DMtsMuomFCHBPPVu/sG yJD/6SeKsv5S5E/qAsyXHtF2xCXRzCXtpkRoVH2cIPOaB2qw0fGzb+wFNKrL9XO9 21nlPyGRXPGiA3OlZHsBPkakvNlnBGIcM8xTvaBck9xOGMGHwxtZnWJZ7jpHkSFh KENBeU8lRitEgX9pb8kyLTMeCzRvxzyGx45uy3PS8mRYq9bOFMiLLDOD2OatR3LL p7HLrQeNUvsjmV+sJxwSydexMYT7OgTm47atgkEgegwpxKOIaQqS72snjRHE7gDI Wy5y38a7xtK5MpFzE1fNtBX+aW3g1GQgUUghv/3hFh8v/k4oa2Q8Ld8Xta6yYhjB WAvfcF4Tmu5wq/j4rt4T492ek2tAnui1E+zTbNsqdBPefyPuMSxN04kzSRK9zRwT 4HDJX4ec86LhwDlXg4X8gPhFSbnLgeSdUY1JTgTrLluhHN99lUytyBq1/pvqO2LL Geztye9Y4Q60JDYisCgWHhxuz9aY2GTYb2sDVGcyxodY3biq0RDThAs5HMsfOyHA ePMGWrhEQtVigjY0i5zReTOjFk+oDMha4p+MKCh9FjEJFckvIK5f6VuBYiJSfTj6 aVJtDbOwWVNaWZzShYvLAG6+QYgIGu3Xnna574DkJBYf+akFPJLyUug3HDCQyjxc FT4W1XFQ5DOEs4c8b9pcqoOGtczr5/AJeZwUQouOWcZnUEBLtBreVMCFzyxq888B UqtOzED7aqg4bxR9pcbxr1aAGH4UICDLAS6eTiPwZxDDWc/f9ZwPUuk5XCXbx4PY S7wJhOkRn8z70nabnYnHXGqEjWAuS4jIN/UPGCzYwIY+Vvz481PL9+VBMktMf/5N FAIL0dYkx4I8XzCgQ9calYYktfpLjRuQ7xrAPthWJlEJv9Mf4U7wfFmnRdvRWvQz n+PIxM4SiemNO0T8z+jMz6rN0svu++e0eNAHme8jDWARzR5MuMkF2XRx9Mz9aD3b rwwALL4dKfsoTLYQKmm7qJ75LQBxqVf5euLsFhSEIQHyP1cqHA/Orr5XjnqtcZU4 5XMH02vNoY+2M9F8V8mxRs3Bf1rnqnd8vRG7OZ/LWX+8Xp2Y8pmW+8tkiKqi4Oj3 +9CZrBBG6aZw/OKkiw0v57EZa+9TOuWLZ7rZnAdr1jm2+GTntJP06TneJBe3r3At qQoC04uTJnaZt7D70oacaQGPaVezzPZA660GXEkfn6ESQXIlu6tDm2oou96D0F+O QGT9fOxB/x+DUQhfcdhamDRJDVbbRAWbGx9BRNR0VS/jHVrIcX6q8n05sNfJpbH4 tg/hGVnTTzOx7pd4ga60BeFRMBQz15Rrz+wOS/lAEIGYDA5uFXQLIe9JnTeAM+J+ ODJH+6yybE+FYikxIzyGUik/so9AfrftymiTHBt73WZHIQyYb0b3cz30uXiFll77 ik8f80AXbRlB3awY5n32jhfrC0js2oPhAJTd2zghi44TVoaszX57J2CWzxom3xIt qsApCyVdPqltusvWoQRepx3XB/PqIqdg0GUL7WJ9GpvRXF4XVSN7utmWkZun5eTR xRZ0PSWAJdBy5eieDw1/aZZpBs/+6HmXrpN43kL8jRNl+OgjyGNreqM+MJi9k580 CvongZyQw4sdQoA5nnJarheq6Va8zc6nTuYiOy1UFqnDIZZ62jFKzC0+aEgvegrR SBs9mgv5FCUViAGofbxgivsTVQ0VM7p/JHu6YP0qc02wSQub/fXAhcopdDRFTeOI Mv/EcgCkDlL0eU0x3O7j9Q9jMY9YuOS9CLb7oSgP+XKKRCQwFF1V1eY100q8pczU yFtrdAsjgE2vOPjSThh5mZhGaTOGb9aAcZLAIT5RmNIUAHmBdf0N51Cd3mDoXTLj dSzKPbwG14UqKLD3EegFwyq9NNhmm878vdj4viY7r5J6H+IZrgYB+EcApCIFP5EP DgoS/bPJha++aqRBVmcL5wVka8yc/et0BdkXSBokSWRraaQnelMV0yxDGWd7VdiP MYjH3LRhaszNRMYt3P+YzBggbOJ9uOLJbNoIgePfMAODHmkjdRegxU5fcTyFeB4s PNSS9afyopRC5XxnlqpiYuq8jxdbgOvxaScbPuous2ZZ9ESI8mrGmNFj9GZtIGEZ 8kF+iz/2BvrZa+iC3x8zmS/KW23yahyIuVlqPmf8Hq+zV1zUAHjQNT2i5qW3iRL+ Bdl/RYf5/6xvMVSOWVtx81eNYvrL4AbPVCsxKzXV3Kq5LRXPBMV34d4yjreJ9koB cZwG3yBTwP2soH5ltlduppmhFAmB3w3la3ZEp0HiM+yD9O4DrFj4j/p2+wjUwNxs tuZVCQrkoDT16fV91gQ3lPx+1UyFzkBdvpg5SkGYLEZLnIqhqB7Dv+A5gnTCCu3J /ow/WYQ+gT2/hlyBu+9CPhud4Dm5fPGMAOwPeAVlujzLZKFzdC03tAVqaczGhl79 DJdDDI05Sn9nrZ+RHN5KcmdOMEBwbH4YmUyOb2pE3SxB/lEK7UiDtJ6FyV7UkyIp ij3+rEbSV7OMVG+uUMnO0IfClKPOm3aBDCCeHY5EVI50wFArZaBSz4VyRmD0rmAK RfhnUMcy5M777mt8ld5Ip5lc/x5ksyxw4wXbVFP2A5KFixOg7YffWk/40IiKQ2z+ sAd4b5HyksE/limJIF4TuQhn/2QfDzKNbLK5Fat77tK9uAIZaWGoYZxDHhp8NutB JJ2JE032HyN50lsyGUo2F3o3DsBB10XLqY/JXGQqUuiDKYQ4zXklNq53cdO4ZAU5 drCc3kHlVuDCq+lJs9wKgw5w8ksJZXEgQSolUkTN1n/y4vQY4MS2UlNxonDVK4hE EGHZk79lXVVr7FnK6mGaCLEFCnRf2ROmuQaxWp+4w8mIhzPIXbAaQa720s3mCab+ 4oEVmLTmytxOs4RBvvDsF6gCti/xRsW6YfxVu/rt/Q3RprrCt3k5HqGHvJheB5f7 Kdk+LMbp6ywNsSlNZ5Tenc0fM1vpS4Jic3RRzXicwwvUMkw8DjrztbRGFuwbn0uY nl1PQ1rtpoRpGXuB3h8dstZSo26UZOwUkCTnDid6r5+sPde7sucostXGhElUITJV 31AM8QevSeeaTg3zeEAVcUonYPF0neS8ignxZN3apT6JDmoiD0XqMFZrz6aowSuN SR2DU/tcZ1/kn2RpECMBr9nhj6jr8s07ztsToIknoBqebAF/L18If63lfSRgBCKf 8F/ILIrld7Omd2thP1Vz5S23fI12h2Ua94vIveCS8XbcaEmLj//N4gBo5zq0FMeM 7U8jI4Y9GPWDThORx7Njz9UpWC5afoYoboctymAOcsSZmZ8E820frQRDBsbDNI6l PC+Xo4o77dHOX/GKcRqT76iucc8o3MNphk4lirDUL1txlNoUme5+FR96mCGuAdW4 7NgelaWExoWUBvmp8Sr51+r0nYOmMr+0iXmbMT+K2h1LI2KN2s4LZLKn7cqvNuEV kf0kqZRNrWFYoZHcGa2NK1OaJ+qXcxDYX2yp3/4NVsYHF5yMXFNJaMW/5dEyaZVG IazFfAYuC++T+6pINv1OTBvSzgp0pruZGLd9QEt1WrWuiwkLq2fBv/1wwE7UvZaL oan5gXxb+854mssyow9KRUujiJ3Soygw9FFoyj4j9Mzy4Xzj7XhQXzAcxyiStkZd zyJqEdrP6+3QBYxhuvnPIA11e9KNnYZIDcim72CVVqdCoYTxnLP8KUgJxxuiB1aa +VzzmOudHMmRY18+TLX90P+EpNhnRyc13I7qOxTK53i8Cn6xR8MoXlT82FkKPJmO 8rWVZhSY15NFfvgX12hFl+MPeHtZdxyiIHLApZw5f3xjFkHVTX7VdEk7bKjXowzN d1dVSMB+i5ovW/qJloscjlyZScU3QcTIBO7yX27SiEgkYyBx7bXgTYt5VXD/Es0W BxmQxOsmTsqKf3V7BNfrHuko7dMbn/hJQJhImPm2JH+8PEhZsPxxhXvQy1TBoVrX BnFkfa7RBqWByLHXduqtFKaumDWkq95KVRdFIX0Fn0oSvoz+483ltygR80l3mqdz xxdnUVpsSNH1HwBzedKfqCE4sOAb6ELRcddYvolSB/aWs6tVx3dsF+jBeChuIUQ1 QsnhKVTN/BphgQeo0v7+wkviRDzeJf8WdHdEE78z5FHP073eVDda+5ZASu8/g+Mk VFaN55Aqi7pNQDZDremzK3V4/mJawYn9SwX3UlEYR16tzsphhL2Ih76XxG7YYfsn 5TlqD7NoRZqZORwX5OyyNeaHZh3ArgA+R6w4ieFb4eEqGkdIg7QOtvtyCXoAdEz7 +r4STVDYDP/SEVDoA7EMPzeT85y926/qcBp1nIJ4URz9i45tdyCYQl5KKOQfTYT7 HZ1vj7Iz5HviNlvHHGfIr9dBTt+795S+5GDaEuDk089YekFIv3sMywAWSVZccLtD 1v1zwPcuu1SRI5Gc8xO42LGL8A565LbxKt8rOwhd4JUQ3SJaX+Km7nOj2qgICaMx 5D0LJl/yp7GVVaUgy3nc02lNedXLJMXVvV3g0mOongQGLjEBbMvwT9Bu69Hoo6f4 d1Dl0/3WArLXbczg8pGFUZE0HhHg0IS9sI5Nh4tsD2xA2T+w4PEywQjhXrgnWMTC mwL7Ph9gxXfxqT2lRzd3vVvTB53Pg62NpTb0cAZzi3rOMUJJ26cIoI1yq0LzjJyC 40PHF5U0XJXL9oORGDzuQrrdBhp5ND8K42OePKvR0L1Cl/tFIdw904kwtPXHAnLl iXJDk4Z87cchIKQ0kJDPS6YfRn/fcnQe/s5CrZFwgUSgqWahifE39cXV5vatH0te OrQW4xvTSKpJIOiosd2obnPZe81Gp224m9alKYWapamTu8fOCEvS56K4uVf82IGj SV15TDjEZedJ/U5O38wg1ZCWCQJhzZw1KW63iCdcb+FVRZdw9Sm9lhcL61wyc5Yp riLN/sIxNjtKrNZm3Ne02bW9dSdvaMcwF1uGUMq1kmQcGDAk4giU5EcKnQPpljpZ 67C6qXpdNFy+1njmyGSDZGCsf6ZPEh6v97hIK0L7JreGLWvuTD5y5lLVnGpWEY5X 7fsatPqWbyubUBk3Jm4Ez1555xFyOIG0LsUpPgn7hVUTVitRsUyZXQ0QruUIdsde f7pOkFNDpeW9UtHCYWVzLYd8kuZYS6vmdzPyKO+5kpMH6pnZtKrgqv73V2nJEdON PXb8FiaQNaOYV+8dIqHqN74la4HgGCB3qDIGEjwiEOnaCiy1IVyjYArmmKFz4iSx HVIf6c5vgjQlb9f3G9wu20w4eQRdfqFhu140wFYTOwHbCiDDkc4jsQ3gr2K+LO/k y5E3k1vSZAQz0R0gJYS5yI3SSBWG25esYJytRVpZt9JWv/JmCczf2pG7GBCjslmt OUKQXNede2Z6GKjCodJc/LWtYjAYZNqVfhdF9hAFZ8PxBL3id1KPTOWGAswym9td rCsz0QYXQOzzu9tyVZXf3mgbxPWj4ImiU26e3aX3CqWQpt179QfiE2FmklV5I3Vp Y59OgYF8JIuN3QPViOdzV/RmUSX8TtmnfzITK3Xc2fK1S3ySAXbJJxw+hJrmjr3r fy01APFXqCblCIwyc1OXtGG11d1IqCyvBEuB+Cumxmo0ZYfYym/C6wBX8pGWFmPB 8yehM/XvTYdypZhrprlgBP8s8fqRouR0uK402S3ySp8ln3STAG/TLODHfhtkiynp U9xs+qfR531cEbzqRw7JTT226EjipSiDbTckBv/xDsZLxiSAIRQjPYpc48nUnuD0 XBkwiQGcE0oRV+bvFCXY3qSG5v1aW2Ipg4egcPHgrelsZhgpS8TWumWi4uQA0CAo QGnNdfCE2l+R478NMB/4+MAkEqda67d/GWMHLWC5OWCMhIhK0Mpz/rMraAwHguAd M/qLumTaqcpD4Riowztej07Ql8dXzU5ifXOR+qr/I9DEKeEtTeoUcFBjQrKiI4P4 m/acGZ7PER4Jrf4Luc0lsfNz6wdbN9dCjQRNj7qM5W+VyPNNglCEXTRSHaB5+Fpr Eu8AeKwpyLLBL6VTr0o+vja+FtbJrHUvgMJqQ/M7djWX69tA6vg62np4QXU2FFnp jpFGTBlpS3kkllE/ADgfwMcQJ+w/LR8uQTNyUK8bRjeQx8+mEOmHZ+SJ5ngVLPm4 +HCuHpJr/mPJS6EUYzo2j67mVCNmKqN6YRzusW3dfIhrBoV2da0uF0WUtb+VDcBr dacBcEo265taqvWY9ZTNPhXr88NXb+qAC+K0z3CfVXWSMkVHMtrWaNnaDyN2nmZw A8yU+0PquPJleJj77jXT1mG1iix1oxVZs51SCQ+srBSGbCdzbBm5Ke6jeTr/dCBe 2zG/0/0q1DRCxDBLQiceiB439Z3zfC3EuBLnPrYqyBnedsNjj5zQ7q2kxwo56+eb /UtB3lQLiFz7mHldbwrbvGxnEOhX8eclOB0xc8Tbn7Jno4oH4zCOZKgv1BYyQp1k k57joLgoqoRlU6b6w/k9zcDDJv0Ve25G/3spRs6jTqhUAyya+ovbDBIGHNnaWb5R +xfiqa2dsENRVEfwup/WrGmJpOKOQc1tSeOzgYuxEc1SvOvKhMAcj9N7TbqrNBrl FGvgYGdkWefXAxZY00fE0iI7SbUNHA3WZB4Ia/JcVnddADDhMsvbyVblkRj7r2Rp W7O6WS3l/+pSCjVZn40R+vsM+QMVeFJnUcVzw1yR0Ofg5OWMZ+NrcDXtUI7MK9bU vBJAnU+WBFwofweQnjaOZZopZ/9knHjNyz+Zqb1TmpdO5gPEcGg24EqDvLIux8Xi mwEi5NjC9pDxP7NRVTE24d/yFgzipwEGLvOXaH+aN9Qby9YcOVrJjZHTZQ9oToij xRXsq7D1Yzu5BqzxPg11kNg7KfbBytw6r0hdmLMsl6UtiChR5HLK0jWus4H73H9D lqLRmfY9I5H6ychfzZHQjXQPfyukg226CRojMd4AZAuGwitZEmb12+SadlAOpSZB pBMCpt4o91BxGKCc9eIzV7nIn+eXViQtA5cvuTCFdmjvAH8mrcXtb0659MqF9SzI 60x4u1v3HJYi8mJHZr6RrVPayUm/fb36GiCDOaW3WvXeKhr2i7JNVTm166yXg0B/ aQcDJF4OosRW8AzRJozRbvEw8jf18d1RLxGhqh+KVspNqbTNDLPNFx7fTqqyEpXc 173orCLwFIQ9dXBrRJmC5NEBu+UFacSw5gYX6KHi+samXFibJhL7c1ev0ODMTtNg /dShXqEajGZ9dgwg3N6Wm3RkLIhkaBjgbqqPsxRN/AeOt9MKZBhYD7YEANJSgCuX ft3dzm+JKDsYNNnuWOsWZeFXB05sm/2KeKmLGR/PfW3N3MxP8J4WkTwcif9CxszA 7T3dV+91Og7BEHgNJ6PFT8nUG/avSIKwuA5WTE09ivQh7pE8FbXcibrYQh5HIQIc /gMIkTq2fZ5JTbMqE162VyEhsRtxNMWQjPVED7fRbi1KZ+Rk/ueILOA6aqHFYPBH 3X2YAStKymdn5mzFGC+QdDbd0chpgYNp6iaYWyRXXE0CQzw2Z9DeoZKjdh4UQ26o NFOryf6XBI0Ku3ve6fsKu24oKapgtilHqmP9RumwxN7bRqdT2vZTx/TVUwuqPqte BqWx4gEtDSY8Vd4MBYLwJl0IU0iX8+hJEdNFucqsGQj7P9zqCJO87pO3siSrIXyk v50GN0fge4HD93rKDA0eXPEF3IEXIEs1tyzYBWNjc5iX/lTd64rvjNQ5uQQ3SKQx 3T0Du35OhhESnr52l38pewC84TEhsBwOd2/a3n0k8iNgZjl+P7o1u6qc/HlTnFIW oHKNla3op5KUDcRVydXtL6WFGyRTx2def8LO6w09NC/yVh8aJNOqaBxpXCvaI9TM Z55Y2iF9CJJ9x7uqRvIfpPIf6+fBkgjPnklNxhtTeyLfSK4ldIXozEX8WVdBOFkS pBeEsfashP0pV2W/weKSDmxDVee0gWjciOsBIdrNNedNtjIhDtxM93+Kc7qtJ6A1 LrjyGGMhRkZXPP45cF+a2bVf3mvWWsTVwjLls3J6pDIioEo8m1tnYXEVZ2UABAzh mXFjfQjRIRoSxSRa/P5o1eJ4YbFHjXu2GLcNv2lMhx/14nPz0USoK85WOtMmXvfY AUdkG2YnKMqdotvdQRgWvmrevArOzwqeVHQOCXJxUqboepB2VRblnD5JNtFW2Lu+ ctFwa3cMIpJ2Zh/sUCtGA+ov48Px3b4BSpcXNqrsH0h6h2O5TsPOeW+in1WB1tE9 QP4Y9Gubnvs9dC/HMbivmf7MwK/OHblVNfCChmylXz7pC58TNbUncUnwSpzdjHpe SVLtfGcf3W2k50DnS2p8YGPEI1GrOPymYKhtg4k9lYA3s1iWfem9mkNGuuWCcVqN In5DEDVByzgthaz4ruw2ETYERO+jMnEujU+B2+Cr8EOv9gwRc3v3uc0s35+9hCa5 Km/RBvNB3hqxXqadfzFmXYJKsYs2eJ6PzNbbql7J5amUGUHeQ0PR0J2x/cypO7sN voa0NIQh1k1MQrsgdbRT+B5Hz6lNbHn/7nh7dcd2FsbroaUBGvZ+IIVI5TbY+GOy 9e8gXVPKSoppppoOW0OwCBxtsZZCzYOY//EvlUMImNmYhBZFihRnrsSI9l4jYGCM JuSoHvUynoUuCZB5N1H1W2uu5jAXtXFNEznMB7MVqDrsBQzzV5gE/dR09x/ykFCO s1tGtwom/IPkkbUyuAbZMHp6AIuNjed5mDvHazYSZxZLeE+B6tE4TGmLjuqA8bIO BSk4DcGo4Q1Bf5Bm2smewbid77vz8nhx9s6YCEcNHoLsxE7JZjwoM8PzkYTvsTvq umYh/3q7hx+FE7DQ6dfOfUe6eKDr5fioBU0NnIL1Yg1tdO10afwLih22pt5kX4ey eCYExpxcp1A9IKUEjir6/6eG9JfXRrIhk04aTsYPss7rXENdRNNKxudT7C8iiU5t w8oSj5VxEyO1cxMIldM/WMkTRfguw/hBzS4KuAd/zZm5AglKSdgR1+JwwpBLoop6 KjaocKdVr2uCsV6dPfb9hsDfSHhTxZF59haOVfQCSbRg7VMfkecxr6icByFimxA0 n7letTP1fDBkWWnHuvi7qFVSo9z6okAmgpdb4ztVXz723YuMjoMpU1hcD4WkEQ/7 8kAG9PoHyFRtlVshY+VJILuxSd7YoAxaCarIpoK3tSpZhx7V3BKHWBSPQ49vG6ag tlRY6T9IPmaK3bvPppVMFqhFU/zfNAwR6oeHnBYgyqPY0xamSL0c15GgTbuwSiti vfJ6+BKtywi3FYfuwN3/tTLujuUtN4mSXyVhoZqAqlQiY1Si0xWc6txXkvuuz0+R u2tM0sWNes1UZ2gFqDibzh8/Eu9h4LjXWWXhF4hkw1zw3rXKqCyjaRciYY4RaGVP 2PUax3iDiD1uiCHTyJAiYfiRlXN8PYfFb0Jj4BTTNpRL0Y67f/RL+jMtkkQei+9b OTnD8qjOsOwfTkGYfourHU2h3jlnXaFadcqRu67G8cbOS59PwK7PEYegZ6pxlkzQ 0bTS0Eu237XwJqyqc/8fJozUK+BUIy1pEZ0l1ghcjX9nTJlNkWVjsMhd66YgGyL4 2zqKj/LSFMBHdQ9K5ffAMBwiaxGjYYJR6AE8x1Uk05iL3hgUy8PIVAN3d07ov3iV 3l1Yiz+3/bGfqsdBEkudnPheE/Lt5ExsOo/hV8i0p7kiImh/EQba95JaBTFhCqcv BG77sv9srobpRPYM/OEBBnkSEqnQY5XUbHh+pwwJVf7PnBjt8i+MzrTQjwcWGoEQ Tdo5sHvCMcBtHlkA4W/LqM7KB4Q+h/QdJ9SUzPWDAyLtOzLd+Cyg0uCQ4wm+3/ol IvXbhuqUEPObH3nq1Y9zs95bW2I2fda4o/QxxV3ZcZQezLvlB62NEIMCVE+N5GQa fdLH7oFAlp38sLm8Y4RdJCuAgpLXTud3bIxiFbPdE1pp+n1MTrQl47PGahvaGBRV YDLTiedVX6zvcKzPY1uwNZU8iOeG061nOn9iH81jmzz2+jHSiHfZr6tecr+v84yA tvAhsBuhHQGvEAVEGh6olHPR1h1DXhqvlB90n2CyFNMpSwuk5X9hr/tYB7EfTRLR KT+sNtOmupfgxngkxYA/ktpuSB6l/oHEhn7QxHQcSTfVEZtfq4mkkgApS1F3s+68 Ha2rAEM/lBLtvxJ4EpcBR+ksPt+qefubG8yhoBcKZAbSmgtK9QqvNMQ5h/co+h1i rYRuqnQvg9AD2xTLYMuuyO3DEFelQo+U2ikSRT02GfiN7a8eBurp8iCkHg916f2B 3zr0IqQpbOc4DMUOs9ci44zyjFPUuGpifpwILns7lPkXDbgrDDnHCkqsJ+i/iGt4 2kQSrTOQB7ROMCwrDYq/QxwzgXBvp8VCOqH1Qsqt84zwnFfkHZnilrfveMBEYRDu c2eNoVWrlmHK5tEALJnQIrBW6OegcWolbWp6Hnp9Um18TwA4IbpPURTcrN9/EvMk Bi9nW9Mb2VkEZMl+3k7qRqxR6xFZo/uaxl8NAI9w3YQPL8+jHhfX0xdgTy+KOcum ZhEGdZG4npi07aF9/lkBr/NsUo1Z8ZbGVdt5gXGqwU+2NxBdLST3SnEdElpVhmlv c5mr2ANmx8QjM5ZW3QKodM3UujhHDi0cj5uv8j48yg83SsaR9GWWv0C9aN+YSA5g X2xjl+TB0d+cdh8EboHQVwUDMqn8sVAq80RBBgHEpH93kRDdqvDkF2OT/lgSXmdT CV8GC5ulOVCS/aM0QRLt4qkdvjrpfYEpHDvEEmo9sfMgMq72BEktWB46c/Bt0Tkt LKNzhGZYs5Kd1SSMobkRwf79cHknqpNEAEDsK3xavWBBTdgLUHu+Hkr5h20C98+e wiKe9oXDiwWZDjfjWyOORb3IIZHiUc1vIjuMDRXKQDHd4QEWsV8koHdprRd+n+3Q qgtkR/1FowOQkyE= =5l5y -----END PGP MESSAGE----- diff --git a/tests/openpgp/quick-key-manipulation.scm b/tests/openpgp/quick-key-manipulation.scm index 8d14ae166..c21abfee2 100755 --- a/tests/openpgp/quick-key-manipulation.scm +++ b/tests/openpgp/quick-key-manipulation.scm @@ -1,222 +1,222 @@ #!/usr/bin/env gpgscm ;; Copyright (C) 2016-2017 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 <http://www.gnu.org/licenses/>. (load (in-srcdir "tests" "openpgp" "defs.scm")) (load (with-path "time.scm")) (setup-environment) (define (exact id) (string-append "=" id)) (define (count-uids-of-secret-key id) (length (filter (lambda (x) (and (string=? "uid" (car x)) (not (string=? "r" (cadr x))))) (gpg-with-colons `(--with-fingerprint --list-secret-keys ,(exact id)))))) (define alpha "Alpha <alpha@invalid.example.net>") (define bravo "Bravo <bravo@invalid.example.net>") (define charlie "Charlie <charlie@invalid.example.net>") (define (key-data key) (filter (lambda (x) (or (string=? (car x) "pub") (string=? (car x) "sub"))) (gpg-with-colons `(-k ,key)))) (setenv "PINENTRY_USER_DATA" "test" #t) (info "Checking quick key generation...") (call-check `(,@GPG --quick-generate-key ,alpha)) (define keyinfo (gpg-with-colons `(-k ,(exact alpha)))) (define fpr (:fpr (assoc "fpr" keyinfo))) (assert (= 1 (count-uids-of-secret-key alpha))) (assert (not (equal? "" (:expire (assoc "pub" keyinfo))))) (info "Checking that we can add a user ID...") ;; Make sure the key capabilities don't change when we add a user id. ;; (See bug #2697.) (let ((pre (key-data (exact alpha))) (result (call-check `(,@GPG --quick-add-uid ,(exact alpha) ,bravo))) (post (key-data (exact alpha)))) (if (not (equal? pre post)) (begin (display "Key capabilities changed when adding a user id:") (newline) (display " Pre: ") (display pre) (newline) (display " Post: ") (display post) (newline) (exit 1)))) (assert (= 2 (count-uids-of-secret-key alpha))) (assert (= 2 (count-uids-of-secret-key bravo))) (info "Checking that we can mark an user ID as primary.") (call-check `(,@gpg --quick-set-primary-uid ,(exact alpha) ,alpha)) (call-check `(,@gpg --quick-set-primary-uid ,(exact alpha) ,bravo)) ;; XXX I don't know how to verify this. The keylisting does not seem ;; to indicate the primary UID. -(info "Checking that we get an error making non-existant user ID the primary one.") +(info "Checking that we get an error making non-existent user ID the primary one.") (catch '() (call-check `(,@GPG --quick-set-primary-uid ,(exact alpha) ,charlie)) (error "Expected an error, but get none.")) (info "Checking that we can revoke a user ID...") (call-check `(,@GPG --quick-revoke-uid ,(exact bravo) ,alpha)) -(info "Checking that we get an error revoking a non-existant user ID.") +(info "Checking that we get an error revoking a non-existent user ID.") (catch '() (call-check `(,@GPG --quick-revoke-uid ,(exact bravo) ,charlie)) (error "Expected an error, but get none.")) (info "Checking that we get an error revoking the last valid user ID.") (catch '() (call-check `(,@GPG --quick-revoke-uid ,(exact bravo) ,bravo)) (error "Expected an error, but get none.")) (assert (= 1 (count-uids-of-secret-key bravo))) (info "Checking that we can change the expiration time.") (define (expiration-time id) (:expire (assoc "pub" (gpg-with-colons `(-k ,id))))) ;; Remove the expiration date. (call-check `(,@gpg --quick-set-expire ,fpr "0")) (assert (equal? "" (expiration-time fpr))) ;; Make the key expire in one year. (call-check `(,@gpg --quick-set-expire ,fpr "1y")) (assert (time-matches? (+ (get-time) (years->seconds 1)) (string->number (expiration-time fpr)) (minutes->seconds 5))) ;; ;; Check --quick-addkey ;; ;; Get the subkeys. (define (get-subkeys) (filter (lambda (x) (equal? "sub" (car x))) (gpg-with-colons `(-k ,fpr)))) ;; This keeps track of the number of subkeys. (define count (length (get-subkeys))) (for-each-p "Checking that we can add subkeys..." (lambda (args check) (set! count (+ 1 count)) (call-check `(,@gpg --quick-add-key ,fpr ,@args)) (let ((subkeys (get-subkeys))) (assert (= count (length subkeys))) (if check (check (last subkeys))))) ;; A bunch of arguments... '(() (- - -) (default default never) (rsa "sign auth encr" "seconds=600") ;; GPGME uses this (rsa "auth,encr" "2") ;; "without a letter, days is assumed" ;; Sadly, the timestamp is truncated by the use of time_t on ;; systems where time_t is a signed 32 bit value. (rsa "sign" "2038-01-01") ;; unix millennium (rsa "sign" "20380101T115500") ;; unix millennium ;; Once fixed, we can use later timestamps: ;; (rsa "sign" "2105-01-01") ;; "last year GnuPG can represent is 2105" ;; (rsa "sign" "21050101T115500") ;; "last year GnuPG can represent is 2105" (rsa sign "2d") (rsa1024 sign "2w") (rsa2048 encr "2m") (rsa4096 sign,auth "2y") (future-default)) ;; ... with functions to check that the created key matches the ;; expectations (or #f for no tests). (list #f #f (lambda (subkey) (assert (equal? "" (:expire subkey)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (string-contains? (:cap subkey) "a")) (assert (string-contains? (:cap subkey) "e")) (assert (time-matches? (+ (get-time) 600) (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (string-contains? (:cap subkey) "a")) (assert (string-contains? (:cap subkey) "e")) (assert (time-matches? (+ (get-time) (days->seconds 2)) (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (time-matches? 2145916800 ;; 2038-01-01 ;; 4260207600 ;; 2105-01-01 (string->number (:expire subkey)) ;; This is off by 12h, but I guess it just ;; choses the middle of the day. (days->seconds 1)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (time-matches? 2145959700 ;; UTC 2038-01-01 11:55:00 ;; 4260254100 ;; UTC 2105-01-01 11:55:00 (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (time-matches? (+ (get-time) (days->seconds 2)) (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (= 1024 (:length subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (time-matches? (+ (get-time) (weeks->seconds 2)) (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (= 2048 (:length subkey))) (assert (string-contains? (:cap subkey) "e")) (assert (time-matches? (+ (get-time) (months->seconds 2)) (string->number (:expire subkey)) (minutes->seconds 5)))) (lambda (subkey) (assert (= 1 (:alg subkey))) (assert (= 4096 (:length subkey))) (assert (string-contains? (:cap subkey) "s")) (assert (string-contains? (:cap subkey) "a")) (assert (time-matches? (+ (get-time) (years->seconds 2)) (string->number (:expire subkey)) (minutes->seconds 5)))) #f)) diff --git a/tests/pkits/README b/tests/pkits/README index 17f03eae4..06aa97b9e 100644 --- a/tests/pkits/README +++ b/tests/pkits/README @@ -1,37 +1,37 @@ tests/pkits/README These are tests based on NIST's Public Key Interoperability Test Suite (PKITS) as downloaded on 2006-05-02 from http://csrc.nist.gov/pki/testing/x509paths.html . README - this file. PKITS_data.tar.bz2 - the original ZIP file, repackaged as a tarball. Makefile.am - Part of our build system. -import-all-certs - Run a simple import test on all certifcates +import-all-certs - Run a simple import test on all certificates validate-all-certs - Run an import and validate test on all certificates signature-verification - PKITS test 4.1 validity-periods - PKITS test 4.2 verifying-name-chaining - PKITS test 4.3 basic-certificate-revocation - PKITS test 4.4 verifying-paths-self-issued - PKITS test 4.5 verifying-basic-constraints - PKITS test 4.6 key-usage - PKITS test 4.7 certificate-policies - PKITS test 4.8 require-explicit-policy - PKITS test 4.9 policy-mappings - PKITS test 4.10 inhibit-policy-mapping - PKITS test 4.11 inhibit-any-policy - PKITS test 4.12 name-constraints - PKITS test 4.13 distribution-points - PKITS test 4.14 delta-crls - PKITS test 4.15 private-certificate-extensions - PKITS test 4.16 The password for the p12 files is "password". You may run the tests as usual with "make check" or after a plain make in this directory you may run the tests individually. When run in this way they will print easy to parse output to stdout. To run all tests in this mode, use "make run-all-tests". All test scripts create a log file with the suffix ".log" appended to the test script's name. diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c index f20d33145..00482a32e 100644 --- a/tools/gpg-connect-agent.c +++ b/tools/gpg-connect-agent.c @@ -1,2258 +1,2258 @@ /* gpg-connect-agent.c - Tool to connect to the agent. * Copyright (C) 2005, 2007, 2008, 2010 Free Software Foundation, Inc. * 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <assuan.h> #include <unistd.h> #include <assert.h> #include "../common/i18n.h" #include "../common/util.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "../common/membuf.h" #include "../common/ttyio.h" #ifdef HAVE_W32_SYSTEM # include "../common/exechelp.h" #endif #include "../common/init.h" #define CONTROL_D ('D' - 'A' + 1) #define octdigitp(p) (*(p) >= '0' && *(p) <= '7') /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oRawSocket = 'S', oTcpSocket = 'T', oExec = 'E', oRun = 'r', oSubst = 's', oNoVerbose = 500, oHomedir, oAgentProgram, oDirmngrProgram, oHex, oDecode, oNoExtConnect, oDirmngr, oUIServer, oNoAutostart, }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")), ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")), ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")), ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")), ARGPARSE_s_n (oUIServer, "uiserver", "@"), ARGPARSE_s_s (oRawSocket, "raw-socket", N_("|NAME|connect to Assuan socket NAME")), ARGPARSE_s_s (oTcpSocket, "tcp-socket", N_("|ADDR|connect to Assuan server at ADDR")), ARGPARSE_s_n (oExec, "exec", N_("run the Assuan server given on the command line")), ARGPARSE_s_n (oNoExtConnect, "no-ext-connect", N_("do not use extended connect mode")), ARGPARSE_s_s (oRun, "run", N_("|FILE|run commands from FILE on startup")), ARGPARSE_s_n (oSubst, "subst", N_("run /subst on startup")), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@" ), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_end () }; /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ int autostart; /* Start the server if not running. */ const char *homedir; /* Configuration directory name */ const char *agent_program; /* Value of --agent-program. */ const char *dirmngr_program; /* Value of --dirmngr-program. */ int hex; /* Print data lines in hex format. */ int decode; /* Decode received data lines. */ int use_dirmngr; /* Use the dirmngr and not gpg-agent. */ int use_uiserver; /* Use the standard UI server. */ const char *raw_socket; /* Name of socket to connect in raw mode. */ const char *tcp_socket; /* Name of server to connect in tcp mode. */ int exec; /* Run the pgm given on the command line. */ unsigned int connect_flags; /* Flags used for connecting. */ int enable_varsubst; /* Set if variable substitution is enabled. */ int trim_leading_spaces; } opt; /* Definitions for /definq commands and a global linked list with all the definitions. */ struct definq_s { struct definq_s *next; char *name; /* Name of inquiry or NULL for any name. */ int is_var; /* True if FILE is a variable name. */ int is_prog; /* True if FILE is a program to run. */ char file[1]; /* Name of file or program. */ }; typedef struct definq_s *definq_t; static definq_t definq_list; static definq_t *definq_list_tail = &definq_list; /* Variable definitions and glovbal table. */ struct variable_s { struct variable_s *next; char *value; /* Malloced value - always a string. */ char name[1]; /* Name of the variable. */ }; typedef struct variable_s *variable_t; static variable_t variable_table; /* To implement loops we store entire lines in a linked list. */ struct loopline_s { struct loopline_s *next; char line[1]; }; typedef struct loopline_s *loopline_t; /* This is used to store the pid of the server. */ static pid_t server_pid = (pid_t)(-1); /* The current datasink file or NULL. */ static FILE *current_datasink; /* A list of open file descriptors. */ static struct { int inuse; #ifdef HAVE_W32_SYSTEM HANDLE handle; #endif } open_fd_table[256]; /*-- local prototypes --*/ static char *substitute_line_copy (const char *buffer); static int read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr); static assuan_context_t start_agent (void); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "@GPG@-connect-agent (@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 1: case 40: p = _("Usage: @GPG@-connect-agent [options] (-h for help)"); break; case 41: p = _("Syntax: @GPG@-connect-agent [options]\n" "Connect to a running agent and send commands\n"); break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; case 33: p = "\n"; break; default: p = NULL; break; } return p; } /* Unescape STRING and returned the malloced result. The surrounding quotes must already be removed from STRING. */ static char * unescape_string (const char *string) { const unsigned char *s; int esc; size_t n; char *buffer; unsigned char *d; n = 0; for (s = (const unsigned char*)string, esc=0; *s; s++) { if (esc) { switch (*s) { case 'b': case 't': case 'v': case 'n': case 'f': case 'r': case '"': case '\'': case '\\': n++; break; case 'x': if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) n++; break; default: if (s[1] && s[2] && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) n++; break; } esc = 0; } else if (*s == '\\') esc = 1; else n++; } buffer = xmalloc (n+1); d = (unsigned char*)buffer; for (s = (const unsigned char*)string, esc=0; *s; s++) { if (esc) { switch (*s) { case 'b': *d++ = '\b'; break; case 't': *d++ = '\t'; break; case 'v': *d++ = '\v'; break; case 'n': *d++ = '\n'; break; case 'f': *d++ = '\f'; break; case 'r': *d++ = '\r'; break; case '"': *d++ = '\"'; break; case '\'': *d++ = '\''; break; case '\\': *d++ = '\\'; break; case 'x': if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) { s++; *d++ = xtoi_2 (s); s++; } break; default: if (s[1] && s[2] && octdigitp (s) && octdigitp (s+1) && octdigitp (s+2)) { *d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2); s += 2; } break; } esc = 0; } else if (*s == '\\') esc = 1; else *d++ = *s; } *d = 0; return buffer; } /* Do the percent unescaping and return a newly malloced string. If WITH_PLUS is set '+' characters will be changed to space. */ static char * unpercent_string (const char *string, int with_plus) { const unsigned char *s; unsigned char *buffer, *p; size_t n; n = 0; for (s=(const unsigned char *)string; *s; s++) { if (*s == '%' && s[1] && s[2]) { s++; n++; s++; } else if (with_plus && *s == '+') n++; else n++; } buffer = xmalloc (n+1); p = buffer; for (s=(const unsigned char *)string; *s; s++) { if (*s == '%' && s[1] && s[2]) { s++; *p++ = xtoi_2 (s); s++; } else if (with_plus && *s == '+') *p++ = ' '; else *p++ = *s; } *p = 0; return (char*)buffer; } static const char * set_var (const char *name, const char *value) { variable_t var; for (var = variable_table; var; var = var->next) if (!strcmp (var->name, name)) break; if (!var) { var = xmalloc (sizeof *var + strlen (name)); var->value = NULL; strcpy (var->name, name); var->next = variable_table; variable_table = var; } xfree (var->value); var->value = value? xstrdup (value) : NULL; return var->value; } static void set_int_var (const char *name, int value) { char numbuf[35]; snprintf (numbuf, sizeof numbuf, "%d", value); set_var (name, numbuf); } /* Return the value of a variable. That value is valid until a variable of the name is changed. Return NULL if not found. Note that envvars are copied to our variable list at the first access and not at oprogram start. */ static const char * get_var (const char *name) { variable_t var; const char *s; if (!*name) return ""; for (var = variable_table; var; var = var->next) if (!strcmp (var->name, name)) break; if (!var && (s = getenv (name))) return set_var (name, s); if (!var || !var->value) return NULL; return var->value; } /* Perform some simple arithmetic operations. Caller must release the return value. On error the return value is NULL. */ static char * arithmetic_op (int operator, const char *operands) { long result, value; char numbuf[35]; while ( spacep (operands) ) operands++; if (!*operands) return NULL; result = strtol (operands, NULL, 0); while (*operands && !spacep (operands) ) operands++; if (operator == '!') result = !result; while (*operands) { while ( spacep (operands) ) operands++; if (!*operands) break; value = strtol (operands, NULL, 0); while (*operands && !spacep (operands) ) operands++; switch (operator) { case '+': result += value; break; case '-': result -= value; break; case '*': result *= value; break; case '/': if (!value) return NULL; result /= value; break; case '%': if (!value) return NULL; result %= value; break; case '!': result = !value; break; case '|': result = result || value; break; case '&': result = result && value; break; default: log_error ("unknown arithmetic operator '%c'\n", operator); return NULL; } } snprintf (numbuf, sizeof numbuf, "%ld", result); return xstrdup (numbuf); } /* Extended version of get_var. This returns a malloced string and understand the function syntax: "func args". Defined functions are get - Return a value described by the next argument: cwd - The current working directory. homedir - The gnupg homedir. sysconfdir - GnuPG's system configuration directory. bindir - GnuPG's binary directory. libdir - GnuPG's library directory. libexecdir - GnuPG's library directory for executable files. datadir - GnuPG's data directory. serverpid - The PID of the current server. unescape ARGS Remove C-style escapes from string. Note that "\0" and "\x00" terminate the string implictly. Use "\x7d" to represent the closing brace. The args start right after the first space after the function name. unpercent ARGS unpercent+ ARGS Remove percent style ecaping from string. Note that "%00 terminates the string implicitly. Use "%7d" to represetn the closing brace. The args start right after the first space after the function name. "unpercent+" also maps '+' to space. percent ARGS percent+ ARGS Escape the args using the percent style. Tabs, formfeeds, linefeeds, carriage return, and the plus sign are also escaped. "percent+" also maps spaces to plus characters. errcode ARG Assuming ARG is an integer, return the gpg-error code. errsource ARG Assuming ARG is an integer, return the gpg-error source. errstring ARG Assuming ARG is an integer return a formatted fpf error string. Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg" */ static char * get_var_ext (const char *name) { static int recursion_count; const char *s; char *result; char *p; char *free_me = NULL; int intvalue; if (recursion_count > 50) { log_error ("variables nested too deeply\n"); return NULL; } recursion_count++; free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL; if (free_me) name = free_me; for (s=name; *s && !spacep (s); s++) ; if (!*s) { s = get_var (name); result = s? xstrdup (s): NULL; } else if ( (s - name) == 3 && !strncmp (name, "get", 3)) { while ( spacep (s) ) s++; if (!strcmp (s, "cwd")) { result = gnupg_getcwd (); if (!result) log_error ("getcwd failed: %s\n", strerror (errno)); } else if (!strcmp (s, "homedir")) result = xstrdup (gnupg_homedir ()); else if (!strcmp (s, "sysconfdir")) result = xstrdup (gnupg_sysconfdir ()); else if (!strcmp (s, "bindir")) result = xstrdup (gnupg_bindir ()); else if (!strcmp (s, "libdir")) result = xstrdup (gnupg_libdir ()); else if (!strcmp (s, "libexecdir")) result = xstrdup (gnupg_libexecdir ()); else if (!strcmp (s, "datadir")) result = xstrdup (gnupg_datadir ()); else if (!strcmp (s, "serverpid")) result = xasprintf ("%d", (int)server_pid); else { log_error ("invalid argument '%s' for variable function 'get'\n", s); log_info ("valid are: cwd, " "{home,bin,lib,libexec,data}dir, serverpid\n"); result = NULL; } } else if ( (s - name) == 8 && !strncmp (name, "unescape", 8)) { s++; result = unescape_string (s); } else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9)) { s++; result = unpercent_string (s, 0); } else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10)) { s++; result = unpercent_string (s, 1); } else if ( (s - name) == 7 && !strncmp (name, "percent", 7)) { s++; result = percent_escape (s, "+\t\r\n\f\v"); } else if ( (s - name) == 8 && !strncmp (name, "percent+", 8)) { s++; result = percent_escape (s, "+\t\r\n\f\v"); for (p=result; *p; p++) if (*p == ' ') *p = '+'; } else if ( (s - name) == 7 && !strncmp (name, "errcode", 7)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%d", gpg_err_code (intvalue)); } else if ( (s - name) == 9 && !strncmp (name, "errsource", 9)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%d", gpg_err_source (intvalue)); } else if ( (s - name) == 9 && !strncmp (name, "errstring", 9)) { s++; intvalue = (int)strtol (s, NULL, 0); result = xasprintf ("%s <%s>", gpg_strerror (intvalue), gpg_strsource (intvalue)); } else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name)) { result = arithmetic_op (*name, s+1); } else { log_error ("unknown variable function '%.*s'\n", (int)(s-name), name); result = NULL; } xfree (free_me); recursion_count--; return result; } /* Substitute variables in LINE and return a new allocated buffer if required. The function might modify LINE if the expanded version fits into it. */ static char * substitute_line (char *buffer) { char *line = buffer; char *p, *pend; const char *value; size_t valuelen, n; char *result = NULL; char *freeme = NULL; while (*line) { p = strchr (line, '$'); if (!p) return result; /* No more variables. */ if (p[1] == '$') /* Escaped dollar sign. */ { memmove (p, p+1, strlen (p+1)+1); line = p + 1; continue; } if (p[1] == '{') { int count = 0; for (pend=p+2; *pend; pend++) { if (*pend == '{') count++; else if (*pend == '}') { if (--count < 0) break; } } if (!*pend) return result; /* Unclosed - don't substitute. */ } else { for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++) ; } if (p[1] == '{' && *pend == '}') { int save = *pend; *pend = 0; freeme = get_var_ext (p+2); value = freeme; *pend++ = save; } else if (*pend) { int save = *pend; *pend = 0; value = get_var (p+1); *pend = save; } else value = get_var (p+1); if (!value) value = ""; valuelen = strlen (value); if (valuelen <= pend - p) { memcpy (p, value, valuelen); p += valuelen; n = pend - p; if (n) memmove (p, p+n, strlen (p+n)+1); line = p; } else { char *src = result? result : buffer; char *dst; dst = xmalloc (strlen (src) + valuelen + 1); n = p - src; memcpy (dst, src, n); memcpy (dst + n, value, valuelen); n += valuelen; strcpy (dst + n, pend); line = dst + n; xfree (result); result = dst; } xfree (freeme); freeme = NULL; } return result; } /* Same as substitute_line but do not modify BUFFER. */ static char * substitute_line_copy (const char *buffer) { char *result, *p; p = xstrdup (buffer?buffer:""); result = substitute_line (p); if (!result) result = p; else xfree (p); return result; } static void assign_variable (char *line, int syslet) { char *name, *p, *tmp, *free_me, *buffer; /* Get the name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; if (!*p) set_var (name, NULL); /* Remove variable. */ else if (syslet) { free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL; if (free_me) p = free_me; buffer = xmalloc (4 + strlen (p) + 1); strcpy (stpcpy (buffer, "get "), p); tmp = get_var_ext (buffer); xfree (buffer); set_var (name, tmp); xfree (tmp); xfree (free_me); } else { tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL; if (tmp) { set_var (name, tmp); xfree (tmp); } else set_var (name, p); } } static void show_variables (void) { variable_t var; for (var = variable_table; var; var = var->next) if (var->value) printf ("%-20s %s\n", var->name, var->value); } /* Store an inquire response pattern. Note, that this function may change the content of LINE. We assume that leading white spaces are already removed. */ static void add_definq (char *line, int is_var, int is_prog) { definq_t d; char *name, *p; /* Get name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; d = xmalloc (sizeof *d + strlen (p) ); strcpy (d->file, p); d->is_var = is_var; d->is_prog = is_prog; if ( !strcmp (name, "*")) d->name = NULL; else d->name = xstrdup (name); d->next = NULL; *definq_list_tail = d; definq_list_tail = &d->next; } -/* Show all inquiry defintions. */ +/* Show all inquiry definitions. */ static void show_definq (void) { definq_t d; for (d=definq_list; d; d = d->next) if (d->name) printf ("%-20s %c %s\n", d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file); for (d=definq_list; d; d = d->next) if (!d->name) printf ("%-20s %c %s\n", "*", d->is_var? 'v': d->is_prog? 'p':'f', d->file); } /* Clear all inquiry definitions. */ static void clear_definq (void) { while (definq_list) { definq_t tmp = definq_list->next; xfree (definq_list->name); xfree (definq_list); definq_list = tmp; } definq_list_tail = &definq_list; } static void do_sendfd (assuan_context_t ctx, char *line) { FILE *fp; char *name, *mode, *p; int rc, fd; /* Get file name. */ name = line; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get mode. */ mode = p; if (!*mode) mode = "r"; else { for (p=mode; *p && !spacep (p); p++) ; if (*p) *p++ = 0; } /* Open and send. */ fp = fopen (name, mode); if (!fp) { log_error ("can't open '%s' in \"%s\" mode: %s\n", name, mode, strerror (errno)); return; } fd = fileno (fp); if (opt.verbose) log_error ("file '%s' opened in \"%s\" mode, fd=%d\n", name, mode, fd); rc = assuan_sendfd (ctx, INT2FD (fd) ); if (rc) log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc)); fclose (fp); } static void do_recvfd (assuan_context_t ctx, char *line) { (void)ctx; (void)line; log_info ("This command has not yet been implemented\n"); } static void do_open (char *line) { FILE *fp; char *varname, *name, *mode, *p; int fd; #ifdef HAVE_W32_SYSTEM if (server_pid == (pid_t)(-1)) { log_error ("the pid of the server is unknown\n"); log_info ("use command \"/serverpid\" first\n"); return; } #endif /* Get variable name. */ varname = line; for (p=varname; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get file name. */ name = p; for (p=name; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; /* Get mode. */ mode = p; if (!*mode) mode = "r"; else { for (p=mode; *p && !spacep (p); p++) ; if (*p) *p++ = 0; } /* Open and send. */ fp = fopen (name, mode); if (!fp) { log_error ("can't open '%s' in \"%s\" mode: %s\n", name, mode, strerror (errno)); return; } fd = fileno (fp); if (fd >= 0 && fd < DIM (open_fd_table)) { open_fd_table[fd].inuse = 1; #ifdef HAVE_W32CE_SYSTEM # warning fixme: implement our pipe emulation. #endif #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) { HANDLE prochandle, handle, newhandle; handle = (void*)_get_osfhandle (fd); prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid); if (!prochandle) { log_error ("failed to open the server process\n"); close (fd); return; } if (!DuplicateHandle (GetCurrentProcess(), handle, prochandle, &newhandle, 0, TRUE, DUPLICATE_SAME_ACCESS )) { log_error ("failed to duplicate the handle\n"); close (fd); CloseHandle (prochandle); return; } CloseHandle (prochandle); open_fd_table[fd].handle = newhandle; } if (opt.verbose) log_info ("file '%s' opened in \"%s\" mode, fd=%d (libc=%d)\n", name, mode, (int)open_fd_table[fd].handle, fd); set_int_var (varname, (int)open_fd_table[fd].handle); #else if (opt.verbose) log_info ("file '%s' opened in \"%s\" mode, fd=%d\n", name, mode, fd); set_int_var (varname, fd); #endif } else { log_error ("can't put fd %d into table\n", fd); close (fd); } } static void do_close (char *line) { int fd = atoi (line); #ifdef HAVE_W32_SYSTEM int i; for (i=0; i < DIM (open_fd_table); i++) if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd) break; if (i < DIM (open_fd_table)) fd = i; else { log_error ("given fd (system handle) has not been opened\n"); return; } #endif if (fd < 0 || fd >= DIM (open_fd_table)) { log_error ("invalid fd\n"); return; } if (!open_fd_table[fd].inuse) { log_error ("given fd has not been opened\n"); return; } #ifdef HAVE_W32_SYSTEM CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */ #endif close (fd); open_fd_table[fd].inuse = 0; } static void do_showopen (void) { int i; for (i=0; i < DIM (open_fd_table); i++) if (open_fd_table[i].inuse) { #ifdef HAVE_W32_SYSTEM printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i); #else printf ("%-15d\n", i); #endif } } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { membuf_t *mb = opaque; put_membuf (mb, buffer, length); return 0; } /* Get the pid of the server and store it locally. */ static void do_serverpid (assuan_context_t ctx) { int rc; membuf_t mb; char *buffer; init_membuf (&mb, 100); rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb, NULL, NULL, NULL, NULL); put_membuf (&mb, "", 1); buffer = get_membuf (&mb, NULL); if (rc || !buffer) log_error ("command \"%s\" failed: %s\n", "GETINFO pid", gpg_strerror (rc)); else { server_pid = (pid_t)strtoul (buffer, NULL, 10); if (opt.verbose) log_info ("server's PID is %lu\n", (unsigned long)server_pid); } xfree (buffer); } /* Return true if the command is either "HELP" or "SCD HELP". */ static int help_cmd_p (const char *line) { if (!ascii_strncasecmp (line, "SCD", 3) && (spacep (line+3) || !line[3])) { for (line += 3; spacep (line); line++) ; } return (!ascii_strncasecmp (line, "HELP", 4) && (spacep (line+4) || !line[4])); } /* gpg-connect-agent's entry point. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int no_more_options = 0; assuan_context_t ctx; char *line, *p; char *tmpline; size_t linesize; int rc; int cmderr; const char *opt_run = NULL; gpgrt_stream_t script_fp = NULL; int use_tty, keep_line; struct { int collecting; loopline_t head; loopline_t *tail; loopline_t current; unsigned int nestlevel; int oneshot; char *condition; } loopstack[20]; int loopidx; char **cmdline_commands = NULL; early_system_init (); gnupg_rl_initialize (); set_strusage (my_strusage); log_set_prefix ("gpg-connect-agent", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (0); opt.autostart = 1; opt.connect_flags = 1; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oNoAutostart: opt.autostart = 0; break; case oHex: opt.hex = 1; break; case oDecode: opt.decode = 1; break; case oDirmngr: opt.use_dirmngr = 1; break; case oUIServer: opt.use_uiserver = 1; break; case oRawSocket: opt.raw_socket = pargs.r.ret_str; break; case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break; case oExec: opt.exec = 1; break; case oNoExtConnect: opt.connect_flags &= ~(1); break; case oRun: opt_run = pargs.r.ret_str; break; case oSubst: opt.enable_varsubst = 1; opt.trim_leading_spaces = 1; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); /* --uiserver is a shortcut for a specific raw socket. This comes in particular handy on Windows. */ if (opt.use_uiserver) { opt.raw_socket = make_absfilename (gnupg_homedir (), "S.uiserver", NULL); } /* 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]); } use_tty = (gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout))); if (opt.exec) { if (!argc) { log_error (_("option \"%s\" requires a program " "and optional arguments\n"), "--exec" ); exit (1); } } else if (argc) cmdline_commands = argv; if (opt.exec && opt.raw_socket) { opt.raw_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--raw-socket", "--exec"); } if (opt.exec && opt.tcp_socket) { opt.tcp_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--tcp-socket", "--exec"); } if (opt.tcp_socket && opt.raw_socket) { opt.tcp_socket = NULL; log_info (_("option \"%s\" ignored due to \"%s\"\n"), "--tcp-socket", "--raw-socket"); } if (opt_run && !(script_fp = gpgrt_fopen (opt_run, "r"))) { log_error ("cannot open run file '%s': %s\n", opt_run, strerror (errno)); exit (1); } if (opt.exec) { assuan_fd_t no_close[3]; no_close[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); no_close[1] = assuan_fd_from_posix_fd (log_get_fd ()); no_close[2] = ASSUAN_INVALID_FD; rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_pipe_connect (ctx, *argv, (const char **)argv, no_close, NULL, NULL, (opt.connect_flags & 1) ? ASSUAN_PIPE_CONNECT_FDPASSING : 0); if (rc) { log_error ("assuan_pipe_connect_ext failed: %s\n", gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("server '%s' started\n", *argv); } else if (opt.raw_socket) { rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_socket_connect (ctx, opt.raw_socket, 0, (opt.connect_flags & 1) ? ASSUAN_SOCKET_CONNECT_FDPASSING : 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", opt.raw_socket, gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to socket '%s' established\n", opt.raw_socket); } else if (opt.tcp_socket) { char *url; url = xstrconcat ("assuan://", opt.tcp_socket, NULL); rc = assuan_new (&ctx); if (rc) { log_error ("assuan_new failed: %s\n", gpg_strerror (rc)); exit (1); } rc = assuan_socket_connect (ctx, opt.tcp_socket, 0, 0); if (rc) { log_error ("can't connect to server '%s': %s\n", opt.tcp_socket, gpg_strerror (rc)); exit (1); } if (opt.verbose) log_info ("connection to socket '%s' established\n", url); xfree (url); } else ctx = start_agent (); /* See whether there is a line pending from the server (in case assuan did not run the initial handshaking). */ if (assuan_pending_line (ctx)) { rc = read_and_print_response (ctx, 0, &cmderr); if (rc) log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); } for (loopidx=0; loopidx < DIM (loopstack); loopidx++) loopstack[loopidx].collecting = 0; loopidx = -1; line = NULL; linesize = 0; keep_line = 1; for (;;) { int n; size_t maxlength = 2048; assert (loopidx < (int)DIM (loopstack)); if (loopidx >= 0 && loopstack[loopidx].current) { keep_line = 0; xfree (line); line = xstrdup (loopstack[loopidx].current->line); n = strlen (line); /* Never go beyond of the final /end. */ if (loopstack[loopidx].current->next) loopstack[loopidx].current = loopstack[loopidx].current->next; else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) ; else log_fatal ("/end command vanished\n"); } else if (cmdline_commands && *cmdline_commands && !script_fp) { keep_line = 0; xfree (line); line = xstrdup (*cmdline_commands); cmdline_commands++; n = strlen (line); if (n >= maxlength) maxlength = 0; } else if (use_tty && !script_fp) { keep_line = 0; xfree (line); line = tty_get ("> "); n = strlen (line); if (n==1 && *line == CONTROL_D) n = 0; if (n >= maxlength) maxlength = 0; } else { if (!keep_line) { xfree (line); line = NULL; linesize = 0; keep_line = 1; } n = gpgrt_read_line (script_fp ? script_fp : gpgrt_stdin, &line, &linesize, &maxlength); } if (n < 0) { log_error (_("error reading input: %s\n"), strerror (errno)); if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; log_error ("stopping script execution\n"); continue; } exit (1); } if (!n) { /* EOF */ if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; if (opt.verbose) log_info ("end of script\n"); continue; } break; } if (!maxlength) { log_error (_("line too long - skipped\n")); continue; } if (memchr (line, 0, n)) log_info (_("line shortened due to embedded Nul character\n")); if (line[n-1] == '\n') line[n-1] = 0; if (opt.trim_leading_spaces) { const char *s = line; while (spacep (s)) s++; if (s != line) { for (p=line; *s;) *p++ = *s++; *p = 0; n = p - line; } } if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting) { loopline_t ll; ll = xmalloc (sizeof *ll + strlen (line)); ll->next = NULL; strcpy (ll->line, line); *loopstack[loopidx+1].tail = ll; loopstack[loopidx+1].tail = &ll->next; if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4))) loopstack[loopidx+1].nestlevel--; else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6))) loopstack[loopidx+1].nestlevel++; if (loopstack[loopidx+1].nestlevel) continue; /* We reached the corresponding /end. */ loopstack[loopidx+1].collecting = 0; loopidx++; } if (*line == '/') { /* Handle control commands. */ char *cmd = line+1; for (p=cmd; *p && !spacep (p); p++) ; if (*p) *p++ = 0; while (spacep (p)) p++; if (!strcmp (cmd, "let")) { assign_variable (p, 0); } else if (!strcmp (cmd, "slet")) { /* Deprecated - never used in a released version. */ assign_variable (p, 1); } else if (!strcmp (cmd, "showvar")) { show_variables (); } else if (!strcmp (cmd, "definq")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 1, 0); xfree (tmpline); } else add_definq (p, 1, 0); } else if (!strcmp (cmd, "definqfile")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 0, 0); xfree (tmpline); } else add_definq (p, 0, 0); } else if (!strcmp (cmd, "definqprog")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { add_definq (tmpline, 0, 1); xfree (tmpline); } else add_definq (p, 0, 1); } else if (!strcmp (cmd, "datafile")) { const char *fname; if (current_datasink) { if (current_datasink != stdout) fclose (current_datasink); current_datasink = NULL; } tmpline = opt.enable_varsubst? substitute_line (p) : NULL; fname = tmpline? tmpline : p; if (fname && !strcmp (fname, "-")) current_datasink = stdout; else if (fname && *fname) { current_datasink = fopen (fname, "wb"); if (!current_datasink) log_error ("can't open '%s': %s\n", fname, strerror (errno)); } xfree (tmpline); } else if (!strcmp (cmd, "showdef")) { show_definq (); } else if (!strcmp (cmd, "cleardef")) { clear_definq (); } else if (!strcmp (cmd, "echo")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { puts (tmpline); xfree (tmpline); } else puts (p); } else if (!strcmp (cmd, "sendfd")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_sendfd (ctx, tmpline); xfree (tmpline); } else do_sendfd (ctx, p); continue; } else if (!strcmp (cmd, "recvfd")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_recvfd (ctx, tmpline); xfree (tmpline); } else do_recvfd (ctx, p); continue; } else if (!strcmp (cmd, "open")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_open (tmpline); xfree (tmpline); } else do_open (p); } else if (!strcmp (cmd, "close")) { tmpline = opt.enable_varsubst? substitute_line (p) : NULL; if (tmpline) { do_close (tmpline); xfree (tmpline); } else do_close (p); } else if (!strcmp (cmd, "showopen")) { do_showopen (); } else if (!strcmp (cmd, "serverpid")) { do_serverpid (ctx); } else if (!strcmp (cmd, "hex")) opt.hex = 1; else if (!strcmp (cmd, "nohex")) opt.hex = 0; else if (!strcmp (cmd, "decode")) opt.decode = 1; else if (!strcmp (cmd, "nodecode")) opt.decode = 0; else if (!strcmp (cmd, "subst")) { opt.enable_varsubst = 1; opt.trim_leading_spaces = 1; } else if (!strcmp (cmd, "nosubst")) opt.enable_varsubst = 0; else if (!strcmp (cmd, "run")) { char *p2; for (p2=p; *p2 && !spacep (p2); p2++) ; if (*p2) *p2++ = 0; while (spacep (p2)) p++; if (*p2) { log_error ("syntax error in run command\n"); if (script_fp) { gpgrt_fclose (script_fp); script_fp = NULL; } } else if (script_fp) { log_error ("cannot nest run commands - stop\n"); gpgrt_fclose (script_fp); script_fp = NULL; } else if (!(script_fp = gpgrt_fopen (p, "r"))) { log_error ("cannot open run file '%s': %s\n", p, strerror (errno)); } else if (opt.verbose) log_info ("running commands from '%s'\n", p); } else if (!strcmp (cmd, "while")) { if (loopidx+2 >= (int)DIM(loopstack)) { log_error ("blocks are nested too deep\n"); /* We should better die or break all loop in this case as recovering from this error won't be easy. */ } else { loopstack[loopidx+1].head = NULL; loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; loopstack[loopidx+1].current = NULL; loopstack[loopidx+1].nestlevel = 1; loopstack[loopidx+1].oneshot = 0; loopstack[loopidx+1].condition = xstrdup (p); loopstack[loopidx+1].collecting = 1; } } else if (!strcmp (cmd, "if")) { if (loopidx+2 >= (int)DIM(loopstack)) { log_error ("blocks are nested too deep\n"); } else { /* Note that we need to evaluate the condition right away and not just at the end of the block as we do with a WHILE. */ loopstack[loopidx+1].head = NULL; loopstack[loopidx+1].tail = &loopstack[loopidx+1].head; loopstack[loopidx+1].current = NULL; loopstack[loopidx+1].nestlevel = 1; loopstack[loopidx+1].oneshot = 1; loopstack[loopidx+1].condition = substitute_line_copy (p); loopstack[loopidx+1].collecting = 1; } } else if (!strcmp (cmd, "end")) { if (loopidx < 0) log_error ("stray /end command encountered - ignored\n"); else { char *tmpcond; const char *value; long condition; /* Evaluate the condition. */ tmpcond = xstrdup (loopstack[loopidx].condition); if (loopstack[loopidx].oneshot) { xfree (loopstack[loopidx].condition); loopstack[loopidx].condition = xstrdup ("0"); } tmpline = substitute_line (tmpcond); value = tmpline? tmpline : tmpcond; /* "true" or "yes" are commonly used to mean TRUE; all other strings will evaluate to FALSE due to the strtoul. */ if (!ascii_strcasecmp (value, "true") || !ascii_strcasecmp (value, "yes")) condition = 1; else condition = strtol (value, NULL, 0); xfree (tmpline); xfree (tmpcond); if (condition) { /* Run loop. */ loopstack[loopidx].current = loopstack[loopidx].head; } else { /* Cleanup. */ while (loopstack[loopidx].head) { loopline_t tmp = loopstack[loopidx].head->next; xfree (loopstack[loopidx].head); loopstack[loopidx].head = tmp; } loopstack[loopidx].tail = NULL; loopstack[loopidx].current = NULL; loopstack[loopidx].nestlevel = 0; loopstack[loopidx].collecting = 0; loopstack[loopidx].oneshot = 0; xfree (loopstack[loopidx].condition); loopstack[loopidx].condition = NULL; loopidx--; } } } else if (!strcmp (cmd, "bye")) { break; } else if (!strcmp (cmd, "sleep")) { gnupg_sleep (1); } else if (!strcmp (cmd, "help")) { puts ( "Available commands:\n" "/echo ARGS Echo ARGS.\n" "/let NAME VALUE Set variable NAME to VALUE.\n" "/showvar Show all variables.\n" "/definq NAME VAR Use content of VAR for inquiries with NAME.\n" "/definqfile NAME FILE Use content of FILE for inquiries with NAME.\n" "/definqprog NAME PGM Run PGM for inquiries with NAME.\n" "/datafile [NAME] Write all D line content to file NAME.\n" "/showdef Print all definitions.\n" "/cleardef Delete all definitions.\n" "/sendfd FILE MODE Open FILE and pass descriptor to server.\n" "/recvfd Receive FD from server and print.\n" "/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n" "/close FD Close file with descriptor FD.\n" "/showopen Show descriptors of all open files.\n" "/serverpid Retrieve the pid of the server.\n" "/[no]hex Enable hex dumping of received data lines.\n" "/[no]decode Enable decoding of received data lines.\n" "/[no]subst Enable variable substitution.\n" "/run FILE Run commands from FILE.\n" "/if VAR Begin conditional block controlled by VAR.\n" "/while VAR Begin loop controlled by VAR.\n" "/end End loop or condition\n" "/bye Terminate gpg-connect-agent.\n" "/help Print this help."); } else log_error (_("unknown command '%s'\n"), cmd ); continue; } if (opt.verbose && script_fp) puts (line); tmpline = opt.enable_varsubst? substitute_line (line) : NULL; if (tmpline) { rc = assuan_write_line (ctx, tmpline); xfree (tmpline); } else rc = assuan_write_line (ctx, line); if (rc) { log_info (_("sending line failed: %s\n"), gpg_strerror (rc) ); break; } if (*line == '#' || !*line) continue; /* Don't expect a response for a comment line. */ rc = read_and_print_response (ctx, help_cmd_p (line), &cmderr); if (rc) log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) ); if ((rc || cmderr) && script_fp) { log_error ("stopping script execution\n"); gpgrt_fclose (script_fp); script_fp = NULL; } /* FIXME: If the last command was BYE or the server died for some other reason, we won't notice until we get the next input command. Probing the connection with a non-blocking read could help to notice termination or other problems early. */ } if (opt.verbose) log_info ("closing connection to agent\n"); /* XXX: We would like to release the context here, but libassuan nicely says good bye to the server, which results in a SIGPIPE if the server died. Unfortunately, libassuan does not ignore SIGPIPE when used with UNIX sockets, hence we simply leak the context here. */ if (0) assuan_release (ctx); else gpgrt_annotate_leaked_object (ctx); xfree (line); return 0; } /* Handle an Inquire from the server. Return False if it could not be handled; in this case the caller shll complete the operation. LINE is the complete line as received from the server. This function may change the content of LINE. */ static int handle_inquire (assuan_context_t ctx, char *line) { const char *name; definq_t d; FILE *fp = NULL; char buffer[1024]; int rc, n; /* Skip the command and trailing spaces. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; /* Get the name. */ name = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* Now match it against our list. The second loop is there to detect the match-all entry. */ for (d=definq_list; d; d = d->next) if (d->name && !strcmp (d->name, name)) break; if (!d) for (d=definq_list; d; d = d->next) if (!d->name) break; if (!d) { if (opt.verbose) log_info ("no handler for inquiry '%s' found\n", name); return 0; } if (d->is_var) { char *tmpvalue = get_var_ext (d->file); if (tmpvalue) rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue)); else rc = assuan_send_data (ctx, "", 0); xfree (tmpvalue); if (rc) log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); } else { if (d->is_prog) { #ifdef HAVE_W32CE_SYSTEM fp = NULL; #else fp = popen (d->file, "r"); #endif if (!fp) log_error ("error executing '%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry '%s' by running '%s'\n", name, d->file); } else { fp = fopen (d->file, "rb"); if (!fp) log_error ("error opening '%s': %s\n", d->file, strerror (errno)); else if (opt.verbose) log_error ("handling inquiry '%s' by returning content of '%s'\n", name, d->file); } if (!fp) return 0; while ( (n = fread (buffer, 1, sizeof buffer, fp)) ) { rc = assuan_send_data (ctx, buffer, n); if (rc) { log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); break; } } if (ferror (fp)) log_error ("error reading from '%s': %s\n", d->file, strerror (errno)); } rc = assuan_send_data (ctx, NULL, 0); if (rc) log_error ("sending data back failed: %s\n", gpg_strerror (rc) ); if (d->is_var) ; else if (d->is_prog) { #ifndef HAVE_W32CE_SYSTEM if (pclose (fp)) log_error ("error running '%s': %s\n", d->file, strerror (errno)); #endif } else fclose (fp); return 1; } /* Read all response lines from server and print them. Returns 0 on success or an assuan error code. If WITHHASH istrue, comment lines are printed. Sets R_GOTERR to true if the command did not returned OK. */ static int read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr) { char *line; size_t linelen; gpg_error_t rc; int i, j; int need_lf = 0; *r_goterr = 0; for (;;) { do { rc = assuan_read_line (ctx, &line, &linelen); if (rc) return rc; if ((withhash || opt.verbose > 1) && *line == '#') { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } while (*line == '#' || !linelen); if (linelen >= 1 && line[0] == 'D' && line[1] == ' ') { if (current_datasink) { const unsigned char *s; int c = 0; for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) { if (*s == '%' && j+2 < linelen) { s++; j++; c = xtoi_2 ( s ); s++; j++; } else c = *s; putc (c, current_datasink); } } else if (opt.hex) { for (i=2; i < linelen; ) { int save_i = i; printf ("D[%04X] ", i-2); for (j=0; j < 16 ; j++, i++) { if (j == 8) putchar (' '); if (i < linelen) printf (" %02X", ((unsigned char*)line)[i]); else fputs (" ", stdout); } fputs (" ", stdout); i= save_i; for (j=0; j < 16; j++, i++) { unsigned int c = ((unsigned char*)line)[i]; if ( i >= linelen ) putchar (' '); else if (isascii (c) && isprint (c) && !iscntrl (c)) putchar (c); else putchar ('.'); } putchar ('\n'); } } else if (opt.decode) { const unsigned char *s; int need_d = 1; int c = 0; for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ ) { if (need_d) { fputs ("D ", stdout); need_d = 0; } if (*s == '%' && j+2 < linelen) { s++; j++; c = xtoi_2 ( s ); s++; j++; } else c = *s; if (c == '\n') need_d = 1; putchar (c); } need_lf = (c != '\n'); } else { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } else { if (need_lf) { if (!current_datasink || current_datasink != stdout) putchar ('\n'); need_lf = 0; } if (linelen >= 1 && line[0] == 'S' && (line[1] == '\0' || line[1] == ' ')) { if (!current_datasink || current_datasink != stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (!current_datasink || current_datasink != stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } set_int_var ("?", 0); return 0; } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { int errval; errval = strtol (line+3, NULL, 10); if (!errval) errval = -1; set_int_var ("?", errval); if (!current_datasink || current_datasink != stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } *r_goterr = 1; return 0; } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { if (!current_datasink || current_datasink != stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } if (!handle_inquire (ctx, line)) assuan_write_line (ctx, "CANCEL"); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (line[3] == '\0' || line[3] == ' ')) { if (!current_datasink || current_datasink != stdout) { fwrite (line, linelen, 1, stdout); putchar ('\n'); } /* Received from server, thus more responses are expected. */ } else return gpg_error (GPG_ERR_ASS_INV_RESPONSE); } } } /* Connect to the agent and send the standard options. */ static assuan_context_t start_agent (void) { gpg_error_t err; assuan_context_t ctx; session_env_t session_env; session_env = session_env_new (); if (!session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); if (opt.use_dirmngr) err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program, opt.autostart, !opt.quiet, 0, NULL, NULL); else err = start_new_gpg_agent (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, NULL, NULL, session_env, opt.autostart, !opt.quiet, 0, NULL, NULL); session_env_release (session_env); if (err) { if (!opt.autostart && (gpg_err_code (err) == (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR : GPG_ERR_NO_AGENT))) { /* In the no-autostart case we don't make gpg-connect-agent fail on a missing server. */ log_info (opt.use_dirmngr? _("no dirmngr running in this session\n"): _("no gpg-agent running in this session\n")); exit (0); } else { log_error (_("error sending standard options: %s\n"), gpg_strerror (err)); exit (1); } } return ctx; } diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 0ef3cb434..f608f7a2c 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,4165 +1,4165 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 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 GnuPG; if not, see <https://www.gnu.org/licenses/>. */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <assert.h> #include <errno.h> #include <time.h> #include <stdarg.h> #ifdef HAVE_SIGNAL_H # include <signal.h> #endif #include <ctype.h> #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN 1 # include <windows.h> #else # include <pwd.h> # include <grp.h> #endif /* For log_logv(), asctimestamp(), gnupg_get_time (). */ #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "gpgconf.h" /* There is a problem with gpg 1.4 under Windows: --gpgconf-list returns a plain filename without escaping. As long as we have not fixed that we need to use gpg2. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) #define GPGNAME "gpg2" #else #define GPGNAME GPG_NAME #endif /* TODO: Components: Add more components and their options. Robustness: Do more validation. Call programs to do validation for us. Add options to change backend binary path. Extract binary path for some backends from gpgsm/gpg config. */ #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void gc_error (int status, int errnum, const char *fmt, ...) \ __attribute__ ((format (printf, 3, 4))); #endif /* Output a diagnostic message. If ERRNUM is not 0, then the output is followed by a colon, a white space, and the error string for the error number ERRNUM. In any case the output is finished by a newline. The message is prepended by the program name, a colon, and a whitespace. The output may be further formatted or redirected by the jnlib logging facility. */ void gc_error (int status, int errnum, const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); log_logv (GPGRT_LOG_ERROR, fmt, arg_ptr); va_end (arg_ptr); if (errnum) log_printf (": %s\n", strerror (errnum)); else log_printf ("\n"); if (status) { log_printf (NULL); log_printf ("fatal error (exit status %i)\n", status); exit (status); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); static void dirmngr_runtime_change (int killflag); /* Backend configuration. Backends are used to decide how the default and current value of an option can be determined, and how the option can be changed. To every option in every component belongs exactly one backend that controls and determines the option. Some backends are programs from the GPG system. Others might be implemented by GPGConf itself. If you change this enum, don't forget to update GC_BACKEND below. */ typedef enum { /* Any backend, used for find_option (). */ GC_BACKEND_ANY, /* The Gnu Privacy Guard. */ GC_BACKEND_GPG, /* The Gnu Privacy Guard for S/MIME. */ GC_BACKEND_GPGSM, /* The GPG Agent. */ GC_BACKEND_GPG_AGENT, /* The GnuPG SCDaemon. */ GC_BACKEND_SCDAEMON, /* The GnuPG directory manager. */ GC_BACKEND_DIRMNGR, /* The LDAP server list file for the director manager. */ GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST, /* The Pinentry (not a part of GnuPG, proper). */ GC_BACKEND_PINENTRY, /* The number of the above entries. */ GC_BACKEND_NR } gc_backend_t; /* To be able to implement generic algorithms for the various backends, we collect all information about them in this struct. */ static struct { /* The name of the backend. */ const char *name; /* The name of the program that acts as the backend. Some backends don't have an associated program, but are implemented directly by GPGConf. In this case, PROGRAM is NULL. */ char *program; /* The module name (GNUPG_MODULE_NAME_foo) as defined by ../common/util.h. This value is used to get the actual installed path of the program. 0 is used if no backend program is available. */ char module_name; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The option name for the configuration filename of this backend. This must be an absolute filename. It can be an option from a different backend (but then ordering of the options might matter). Note: This must be unique among all components. */ const char *option_config_filename; /* If this is a file backend rather than a program backend, then this is the name of the option associated with the file. */ const char *option_name; } gc_backend[GC_BACKEND_NR] = { { NULL }, /* GC_BACKEND_ANY dummy entry. */ { GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG, NULL, GPGCONF_NAME "-" GPG_NAME ".conf" }, { GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM, NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" }, { GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT, gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" }, { SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON, scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" }, { DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR, dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" }, { DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0, NULL, "ldapserverlist-file", "LDAP Server" }, { "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY, NULL, GPGCONF_NAME "-pinentry.conf" }, }; /* Option configuration. */ /* An option might take an argument, or not. Argument types can be basic or complex. Basic types are generic and easy to validate. Complex types provide more specific information about the intended use, but can be difficult to validate. If you add to this enum, don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* Basic argument types. */ /* No argument. */ GC_ARG_TYPE_NONE = 0, /* A String argument. */ GC_ARG_TYPE_STRING = 1, /* A signed integer argument. */ GC_ARG_TYPE_INT32 = 2, /* An unsigned integer argument. */ GC_ARG_TYPE_UINT32 = 3, /* ADD NEW BASIC TYPE ENTRIES HERE. */ /* Complex argument types. */ /* A complete filename. */ GC_ARG_TYPE_FILENAME = 32, /* An LDAP server in the format HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ GC_ARG_TYPE_LDAP_SERVER = 33, /* A 40 character fingerprint. */ GC_ARG_TYPE_KEY_FPR = 34, /* A user ID or key ID or fingerprint for a certificate. */ GC_ARG_TYPE_PUB_KEY = 35, /* A user ID or key ID or fingerprint for a certificate with a key. */ GC_ARG_TYPE_SEC_KEY = 36, /* A alias list made up of a key, an equal sign and a space separated list of values. */ GC_ARG_TYPE_ALIAS_LIST = 37, /* ADD NEW COMPLEX TYPE ENTRIES HERE. */ /* The number of the above entries. */ GC_ARG_TYPE_NR } gc_arg_type_t; /* For every argument, we record some information about it in the following struct. */ static struct { /* For every argument type exists a basic argument type that can be used as a fallback for input and validation purposes. */ gc_arg_type_t fallback; /* Human-readable name of the type. */ const char *name; } gc_arg_type[GC_ARG_TYPE_NR] = { /* The basic argument types have their own types as fallback. */ { GC_ARG_TYPE_NONE, "none" }, { GC_ARG_TYPE_STRING, "string" }, { GC_ARG_TYPE_INT32, "int32" }, { GC_ARG_TYPE_UINT32, "uint32" }, /* Reserved basic type entries for future extension. */ { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, /* The complex argument types have a basic type as fallback. */ { GC_ARG_TYPE_STRING, "filename" }, { GC_ARG_TYPE_STRING, "ldap server" }, { GC_ARG_TYPE_STRING, "key fpr" }, { GC_ARG_TYPE_STRING, "pub key" }, { GC_ARG_TYPE_STRING, "sec key" }, { GC_ARG_TYPE_STRING, "alias list" }, }; /* Every option has an associated expert level, than can be used to hide advanced and expert options from beginners. If you add to this list, don't forget to update GC_LEVEL below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* The basic options should always be displayed. */ GC_LEVEL_BASIC, /* The advanced options may be hidden from beginners. */ GC_LEVEL_ADVANCED, /* The expert options should only be displayed to experts. */ GC_LEVEL_EXPERT, /* The invisible options should normally never be displayed. */ GC_LEVEL_INVISIBLE, /* The internal options are never exported, they mark options that are recorded for internal use only. */ GC_LEVEL_INTERNAL, /* ADD NEW ENTRIES HERE. */ /* The number of the above entries. */ GC_LEVEL_NR } gc_expert_level_t; /* A description for each expert level. */ static struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the backends are defined by gc-opt-flags.h, included above. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ /* Some entries in the option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. */ #define GC_OPT_FLAG_GROUP (1UL << 0) /* The ARG_OPT flag for an option indicates that the argument is optional. This is never set for GC_ARG_TYPE_NONE options. */ #define GC_OPT_FLAG_ARG_OPT (1UL << 1) /* The LIST flag for an option indicates that the option can occur several times. A comma separated list of arguments is used as the argument value. */ #define GC_OPT_FLAG_LIST (1UL << 2) /* A human-readable description for each flag. */ static struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* To each option, or group marker, the information in the GC_OPTION struct is provided. If you change this, don't forget to update the option list of each component. */ struct gc_option { /* If this is NULL, then this is a terminator in an array of unknown length. Otherwise, if this entry is a group marker (see FLAGS), then this is the name of the group described by this entry. Otherwise it is the name of the option described by this entry. The name must not contain a colon. */ const char *name; /* The option flags. If the GROUP flag is set, then this entry is a group marker, not an option, and only the fields LEVEL, DESC_DOMAIN and DESC are valid. In all other cases, this entry describes a new option and all fields are valid. */ unsigned long flags; /* The expert level. This field is valid for options and groups. A group has the expert level of the lowest-level option in the group. */ gc_expert_level_t level; /* A gettext domain in which the following description can be found. If this is NULL, then DESC is not translated. Valid for groups and options. Note that we try to keep the description of groups within the gnupg domain. IMPORTANT: If you add a new domain please make sure to add a code set switching call to the function my_dgettext further below. */ const char *desc_domain; /* A gettext description for this group or option. If it starts with a '|', then the string up to the next '|' describes the argument, and the description follows the second '|'. In general enclosing these description in N_() is not required because the description should be identical to the one in the help menu of the respective program. */ const char *desc; /* The following fields are only valid for options. */ /* The type of the option argument. */ gc_arg_type_t arg_type; /* The backend that implements this option. */ gc_backend_t backend; /* The following fields are set to NULL at startup (because all option's are declared as static variables). They are at the end of the list so that they can be omitted from the option declarations. */ /* This is true if the option is supported by this version of the backend. */ int active; /* The default value for this option. This is NULL if the option is not present in the backend, the empty string if no default is available, and otherwise a quoted string. */ char *default_value; /* The default argument is only valid if the "optional arg" flag is set, and specifies the default argument (value) that is used if the argument is omitted. */ char *default_arg; /* The current value of this option. */ char *value; /* The new flags for this option. The only defined flag is actually GC_OPT_FLAG_DEFAULT, and it means that the option should be deleted. In this case, NEW_VALUE is NULL. */ unsigned long new_flags; /* The new value of this option. */ char *new_value; }; typedef struct gc_option gc_option_t; /* Use this macro to terminate an option list. */ #define GC_OPTION_NULL { NULL } #ifndef BUILD_WITH_AGENT #define gc_options_gpg_agent NULL #else /* The options of the GC_COMPONENT_GPG_AGENT component. */ static gc_option_t gc_options_gpg_agent[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-" GPG_AGENT_NAME ".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not use the SCdaemon", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable ssh support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "enable putty support", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT }, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|expire cached PINs after N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|expire SSH keys after N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|set maximum PIN cache lifetime to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|set maximum SSH key lifetime to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "allow passphrase to be prompted through Emacs", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "disallow caller to override the pinentry", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "do not grab keyboard and mouse", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Passphrase policy", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options enforcing a passphrase policy") }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("do not allow bypassing the passphrase policy"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|set minimal required length for new passphrases to N"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|require at least N non-alpha characters for a new passphrase"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|FILE|check new passphrases against pattern in FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("|N|expire the passphrase after N days"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", N_("do not allow the reuse of old passphrases"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|N|set the Pinentry timeout to N seconds"), GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, GC_OPTION_NULL }; #endif /*BUILD_WITH_AGENT*/ #ifndef BUILD_WITH_SCDAEMON #define gc_options_scdaemon NULL #else /* The options of the GC_COMPONENT_SCDAEMON component. */ static gc_option_t gc_options_scdaemon[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"SCDAEMON_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|connect to reader at port N", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as ct-API driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use NAME as PC/SC driver", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, "gnupg", "do not use the internal CCID driver", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "do not use a reader's pinpad", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "enable-pinpad-varlen", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "use variable length input for pinpad", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, { "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "|N|disconnect the card after N seconds of inactivity", GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, { "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write a log to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, "gnupg", "deny the use of admin card commands", GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, GC_OPTION_NULL }; #endif /*BUILD_WITH_SCDAEMON*/ #ifndef BUILD_WITH_GPG #define gc_options_gpg NULL #else /* The options of the GC_COMPONENT_GPG component. */ static gc_option_t gc_options_gpg[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"GPG_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|use NAME as default secret key"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|encrypt to user ID NAME as well"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, "gnupg", N_("|SPEC|set up email aliases"), GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG }, /* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */ /* NULL, NULL, */ /* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */ { "Keyserver", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration for Keyservers") }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("|URL|use keyserver at URL"), /* Deprecated - use dirmngr */ GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("allow PKA lookups (DNS requests)"), GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"), GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, GC_OPTION_NULL }; #endif /*BUILD_WITH_GPG*/ #ifndef BUILD_WITH_GPGSM #define gc_options_gpgsm NULL #else /* The options of the GC_COMPONENT_GPGSM component. */ static gc_option_t gc_options_gpgsm[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"GPGSM_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|use NAME as default secret key"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|NAME|encrypt to user ID NAME as well"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "use system's dirmngr if available", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("disable all access to the dirmngr"), GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"), GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", N_("|SPEC|use this keyserver to lookup keys"), GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM }, { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "gnupg", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM }, { "Security", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the security") }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "never consult a CRL", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", N_("do not check CRLs for root certificates"), GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "check validity using OCSP", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|N|number of certificates to include", GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not check certificate policies", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", "fetch missing issuer certificates", GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "|NAME|use cipher algorithm NAME", GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, GC_OPTION_NULL }; #endif /*BUILD_WITH_GPGSM*/ #ifndef BUILD_WITH_DIRMNGR #define gc_options_dirmngr NULL #else /* The options of the GC_COMPONENT_DIRMNGR component. */ static gc_option_t gc_options_dirmngr[] = { /* The configuration file to which we write the changes. */ { GPGCONF_NAME"-"DIRMNGR_NAME".conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "Monitor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the diagnostic output") }, { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "dirmngr", "verbose", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "be somewhat more quiet", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Format", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the format of the output") }, { "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "sh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "csh-style command output", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Configuration", GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "dirmngr", "|FILE|read options from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Options useful for debugging") }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, "dirmngr", "|LEVEL|set the debugging level to LEVEL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not detach from the console", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", N_("|FILE|write server mode logs to FILE"), GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, { "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, NULL, NULL, GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "Enforcement", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the interactivity and enforcement") }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "run without asking a user", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "force loading of outdated CRLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "allow online software version check", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Tor", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the use of Tor") }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "route all network traffic via TOR", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "Keyserver", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration for Keyservers") }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "gnupg", N_("|URL|use keyserver at URL"), GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "HTTP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for HTTP servers") }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of HTTP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore HTTP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|redirect all HTTP requests to URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", N_("use system's HTTP proxy setting"), GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "LDAP", GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Configuration of LDAP servers to use") }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "inhibit the use of LDAP", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore LDAP CRL distribution points", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|HOST|use HOST for LDAP queries", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "do not use fallback hosts with --ldap-proxy", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "add new servers discovered in CRL distribution points" " to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|set LDAP timeout to N seconds", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, /* The following entry must not be removed, as it is required for the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */ { "ldapserverlist-file", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, "dirmngr", "|FILE|read LDAP server list from FILE", GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR }, /* This entry must come after at least one entry for GC_BACKEND_DIRMNGR in this component, so that the entry for "ldapserverlist-file will be initialized before this one. */ { "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, "gnupg", N_("LDAP server list"), GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "|N|do not return more than N items in one query", GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, { "OCSP", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, "gnupg", N_("Configuration for OCSP") }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, "dirmngr", "allow sending OCSP requests", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "ignore certificate contained OCSP service URLs", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|URL|use OCSP responder at URL", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "dirmngr", "|FPR|OCSP response signed by FPR", GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, GC_OPTION_NULL }; #endif /*BUILD_WITH_DIRMNGR*/ /* The options of the GC_COMPONENT_PINENTRY component. */ static gc_option_t gc_options_pinentry[] = { /* A dummy option to allow gc_component_list_components to find the pinentry backend. Needs to be a conf file. */ { GPGCONF_NAME"-pinentry.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY }, GC_OPTION_NULL }; /* Component system. Each component is a set of options that can be configured at the same time. If you change this, don't forget to update GC_COMPONENT below. */ typedef enum { /* The classic GPG for OpenPGP. */ GC_COMPONENT_GPG, /* The GPG Agent. */ GC_COMPONENT_GPG_AGENT, /* The Smardcard Daemon. */ GC_COMPONENT_SCDAEMON, /* GPG for S/MIME. */ GC_COMPONENT_GPGSM, /* The LDAP Directory Manager for CRLs. */ GC_COMPONENT_DIRMNGR, /* The external Pinentry. */ GC_COMPONENT_PINENTRY, /* The number of components. */ GC_COMPONENT_NR } gc_component_t; /* The information associated with each component. */ static struct { /* The name of this component. Must not contain a colon (':') character. */ const char *name; /* The gettext domain for the description DESC. If this is NULL, then the description is not translated. */ const char *desc_domain; /* The description for this domain. */ const char *desc; /* The list of options for this component, terminated by GC_OPTION_NULL. */ gc_option_t *options; } gc_component[] = { { "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg }, { "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent }, { "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon }, { "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm }, { "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr }, { "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry } }; /* Structure used to collect error output of the backend programs. */ struct error_line_s; typedef struct error_line_s *error_line_t; struct error_line_s { error_line_t next; /* Link to next item. */ const char *fname; /* Name of the config file (points into BUFFER). */ unsigned int lineno; /* Line number of the config file. */ const char *errtext; /* Text of the error message (points into BUFFER). */ char buffer[1]; /* Helper buffer. */ }; /* Initialization and finalization. */ static void gc_option_free (gc_option_t *o) { if (o == NULL || o->name == NULL) return; xfree (o->value); gc_option_free (o + 1); } static void gc_components_free (void) { int i; for (i = 0; i < DIM (gc_component); i++) gc_option_free (gc_component[i].options); } void gc_components_init (void) { atexit (gc_components_free); } /* Engine specific support. */ static void gpg_agent_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[5]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; int i = 0; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[i++] = "--homedir"; argv[i++] = abs_homedir; } argv[i++] = "--no-autostart"; argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; argv[i++] = NULL; if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[1], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } static void scdaemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; int i = 0; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[i++] = "--homedir"; argv[i++] = abs_homedir; } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO scd_running"; argv[i++] = "/if ${! $?}"; argv[i++] = "scd killscd"; argv[i++] = "/end"; argv[i++] = NULL; if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[4], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } static void dirmngr_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; pid_t pid = (pid_t)(-1); char *abs_homedir = NULL; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[0] = "--no-autostart"; argv[1] = "--dirmngr"; argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; if (gnupg_default_homedir_p ()) argv[3] = NULL; else { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) err = gpg_error_from_syserror (); argv[3] = "--homedir"; argv[4] = abs_homedir; argv[5] = NULL; } if (!err) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[2], gpg_strerror (err)); gnupg_release_process (pid); xfree (abs_homedir); } /* Launch the gpg-agent or the dirmngr if not already running. */ gpg_error_t gc_component_launch (int component) { gpg_error_t err; const char *pgmname; const char *argv[3]; int i; pid_t pid; if (component < 0) { err = gc_component_launch (GC_COMPONENT_GPG_AGENT); if (!err) err = gc_component_launch (GC_COMPONENT_DIRMNGR); return err; } if (!(component == GC_COMPONENT_GPG_AGENT || component == GC_COMPONENT_DIRMNGR)) { es_fputs (_("Component not suitable for launching"), es_stderr); es_putc ('\n', es_stderr); exit (1); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (component == GC_COMPONENT_DIRMNGR) argv[i++] = "--dirmngr"; argv[i++] = "NOP"; argv[i] = NULL; err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) err = gnupg_wait_process (pgmname, pid, 1, NULL); if (err) gc_error (0, 0, "error running '%s%s%s': %s", pgmname, component == GC_COMPONENT_DIRMNGR? " --dirmngr":"", " NOP", gpg_strerror (err)); gnupg_release_process (pid); return err; } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { int runtime[GC_BACKEND_NR]; gc_option_t *option; gc_backend_t backend; /* Set a flag for the backends to be reloaded. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) runtime[backend] = 0; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } } else { assert (component < GC_COMPONENT_NR); option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } /* Do the restart for the selected backends. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (1); } } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { int runtime[GC_BACKEND_NR]; gc_option_t *option; gc_backend_t backend; /* Set a flag for the backends to be reloaded. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) runtime[backend] = 0; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } } else { assert (component < GC_COMPONENT_NR); option = gc_component[component].options; for (; option && option->name; option++) runtime[option->backend] = 1; } /* Do the reload for all selected backends. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (0); } } /* More or less Robust version of dgettext. It has the side effect of switching the codeset to utf-8 because this is what we want to - output. In theory it is posible to keep the original code set and + output. In theory it is possible to keep the original code set and switch back for regular disgnostic output (redefine "_(" for that) but given the natur of this tool, being something invoked from other pograms, it does not make much sense. */ static const char * my_dgettext (const char *domain, const char *msgid) { #ifdef USE_SIMPLE_GETTEXT if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; gettext_use_utf8 (1); } if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; /* FIXME: we have no dgettext, thus we can't switch. */ text = (char*)gettext (msgid); return text ? text : msgid; } else return msgid; #elif defined(ENABLE_NLS) if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; bind_textdomain_codeset (PACKAGE_GT, "utf-8"); bindtextdomain (DIRMNGR_NAME, LOCALEDIR); bind_textdomain_codeset (DIRMNGR_NAME, "utf-8"); } /* Note: This is a hack to actually use the gnupg2 domain as long we are in a transition phase where gnupg 1.x and 1.9 may coexist. */ if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; text = dgettext (domain, msgid); return text ? text : msgid; } else return msgid; #else (void)domain; return msgid; #endif } /* Percent-Escape special characters. The string is valid until the next invocation of the function. */ char * gc_percent_escape (const char *src) { static char *esc_str; static int esc_str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (esc_str_len < new_len) { char *new_esc_str = realloc (esc_str, new_len); if (!new_esc_str) gc_error (1, errno, "can not escape string"); esc_str = new_esc_str; esc_str_len = new_len; } dst = esc_str; while (*src) { if (*src == '%') { *(dst++) = '%'; *(dst++) = '2'; *(dst++) = '5'; } else if (*src == ':') { /* The colon is used as field separator. */ *(dst++) = '%'; *(dst++) = '3'; *(dst++) = 'a'; } else if (*src == ',') { /* The comma is used as list separator. */ *(dst++) = '%'; *(dst++) = '2'; *(dst++) = 'c'; } else if (*src == '\n') { /* The newline is problematic in a line-based format. */ *(dst++) = '%'; *(dst++) = '0'; *(dst++) = 'a'; } else *(dst++) = *(src); src++; } *dst = '\0'; return esc_str; } /* Percent-Deescape special characters. The string is valid until the next invocation of the function. */ static char * percent_deescape (const char *src) { static char *str; static int str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (str_len < new_len) { char *new_str = realloc (str, new_len); if (!new_str) gc_error (1, errno, "can not deescape string"); str = new_str; str_len = new_len; } dst = str; while (*src) { if (*src == '%') { int val = hextobyte (src + 1); if (val < 0) gc_error (1, 0, "malformed end of string %s", src); *(dst++) = (char) val; src += 3; } else *(dst++) = *(src++); } *dst = '\0'; return str; } /* List all components that are available. */ void gc_component_list_components (estream_t out) { gc_component_t component; gc_option_t *option; gc_backend_t backend; int backend_seen[GC_BACKEND_NR]; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { option = gc_component[component].options; if (option) { for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; pgmname = ""; for (; option && option->name; option++) { if ((option->flags & GC_OPT_FLAG_GROUP)) continue; backend = option->backend; if (backend_seen[backend]) continue; backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (gc_backend[backend].program && !gc_backend[backend].module_name) continue; pgmname = gnupg_module_name (gc_backend[backend].module_name); break; } desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].name, gc_percent_escape (desc)); es_fprintf (out, "%s\n", gc_percent_escape (pgmname)); } } } static int all_digits_p (const char *p, size_t len) { if (!len) return 0; /* No. */ for (; len; len--, p++) if (!isascii (*p) || !isdigit (*p)) return 0; /* No. */ return 1; /* Yes. */ } /* Collect all error lines from stream FP. Only lines prefixed with TAG are considered. Returns a list of error line items (which may be empty). There is no error return. */ static error_line_t collect_error_output (estream_t fp, const char *tag) { char buffer[1024]; char *p, *p2, *p3; int c, cont_line; unsigned int pos; error_line_t eitem, errlines, *errlines_tail; size_t taglen = strlen (tag); errlines = NULL; errlines_tail = &errlines; pos = 0; cont_line = 0; while ((c=es_getc (fp)) != EOF) { buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) ; /*Ignore continuations of previous line. */ else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') { /* "gpgsm: foo:4: bla" */ /* Yep, we are interested in this line. */ p = buffer + taglen + 1; while (*p == ' ' || *p == '\t') p++; trim_trailing_spaces (p); /* Get rid of extra CRs. */ if (!*p) ; /* Empty lines are ignored. */ else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':')) && all_digits_p (p2+1, p3 - (p2+1))) { /* Line in standard compiler format. */ p3++; while (*p3 == ' ' || *p3 == '\t') p3++; eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = eitem->buffer; eitem->buffer[p2-p] = 0; eitem->errtext = eitem->buffer + (p3 - p); /* (we already checked that there are only ascii digits followed by a colon) */ eitem->lineno = 0; for (p2++; isdigit (*p2); p2++) eitem->lineno = eitem->lineno*10 + (*p2 - '0'); *errlines_tail = eitem; errlines_tail = &eitem->next; } else { /* Other error output. */ eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = NULL; eitem->errtext = eitem->buffer; eitem->lineno = 0; *errlines_tail = eitem; errlines_tail = &eitem->next; } } pos = 0; /* If this was not a complete line mark that we are in a continuation. */ cont_line = (c != '\n'); } } /* We ignore error lines not terminated by a LF. */ return errlines; } /* Check the options of a single component. Returns 0 if everything is OK. */ int gc_component_check_options (int component, estream_t out, const char *conf_file) { gpg_error_t err; unsigned int result; int backend_seen[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; const char *pgmname; const char *argv[4]; int i; pid_t pid; int exitcode; estream_t errfp; error_line_t errlines; for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; option = gc_component[component].options; for (; option && option->name; option++) { if ((option->flags & GC_OPT_FLAG_GROUP)) continue; backend = option->backend; if (backend_seen[backend]) continue; backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (!gc_backend[backend].program) continue; if (!gc_backend[backend].module_name) continue; break; } if (! option || ! option->name) return 0; pgmname = gnupg_module_name (gc_backend[backend].module_name); i = 0; if (conf_file) { argv[i++] = "--options"; argv[i++] = conf_file; } if (component == GC_COMPONENT_PINENTRY) argv[i++] = "--version"; else argv[i++] = "--gpgconf-test"; argv[i++] = NULL; result = 0; errlines = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, NULL, &errfp, &pid); if (err) result |= 1; /* Program could not be run. */ else { errlines = collect_error_output (errfp, gc_component[component].name); if (gnupg_wait_process (pgmname, pid, 1, &exitcode)) { if (exitcode == -1) result |= 1; /* Program could not be run or it terminated abnormally. */ result |= 2; /* Program returned an error. */ } gnupg_release_process (pid); es_fclose (errfp); } /* If the program could not be run, we can't tell whether the config file is good. */ if (result & 1) result |= 2; if (out) { const char *desc; error_line_t errptr; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].name, gc_percent_escape (desc)); es_fputs (gc_percent_escape (pgmname), out); es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2)); for (errptr = errlines; errptr; errptr = errptr->next) { if (errptr != errlines) es_fputs ("\n:::::", out); /* Continuation line. */ if (errptr->fname) es_fputs (gc_percent_escape (errptr->fname), out); es_putc (':', out); if (errptr->fname) es_fprintf (out, "%u", errptr->lineno); es_putc (':', out); es_fputs (gc_percent_escape (errptr->errtext), out); es_putc (':', out); } es_putc ('\n', out); } while (errlines) { error_line_t tmp = errlines->next; xfree (errlines); errlines = tmp; } return result; } /* Check all components that are available. */ void gc_check_programs (estream_t out) { gc_component_t component; for (component = 0; component < GC_COMPONENT_NR; component++) gc_component_check_options (component, out, NULL); } /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name) { gc_component_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].options && !strcmp (name, gc_component[idx].name)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; if (option->desc) { desc = my_dgettext (option->desc_domain, option->desc); if (*desc == '|') { const char *arg_tail = strchr (&desc[1], '|'); if (arg_tail) { int arg_len = arg_tail - &desc[1]; arg_name = xmalloc (arg_len + 1); memcpy (arg_name, &desc[1], arg_len); arg_name[arg_len] = '\0'; desc = arg_tail + 1; } } } /* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY FIELDS. */ /* The name field. */ es_fprintf (out, "%s", option->name); /* The flags field. */ es_fprintf (out, ":%lu", option->flags); if (opt.verbose) { es_putc (' ', out); if (!option->flags) es_fprintf (out, "none"); else { unsigned long flags = option->flags; unsigned long flag = 0; unsigned long first = 1; while (flags) { if (flags & 1) { if (first) first = 0; else es_putc (',', out); es_fprintf (out, "%s", gc_flag[flag].name); } flags >>= 1; flag++; } } } /* The level field. */ es_fprintf (out, ":%u", option->level); if (opt.verbose) es_fprintf (out, " %s", gc_level[option->level].name); /* The description field. */ es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : ""); /* The type field. */ es_fprintf (out, ":%u", option->arg_type); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[option->arg_type].name); /* The alternate type field. */ es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[gc_arg_type[option->arg_type].fallback].name); /* The argument name field. */ es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : ""); xfree (arg_name); /* The default value field. */ es_fprintf (out, ":%s", option->default_value ? option->default_value : ""); /* The default argument field. */ es_fprintf (out, ":%s", option->default_arg ? option->default_arg : ""); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST) && option->value) /* The special format "1,1,1,1,...,1" is converted to a number here. */ es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2)); else es_fprintf (out, ":%s", option->value ? option->value : ""); /* ADD NEW FIELDS HERE. */ es_putc ('\n', out); } /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, estream_t out) { const gc_option_t *option = gc_component[component].options; while (option && option->name) { /* Do not output unknown or internal options. */ if (!(option->flags & GC_OPT_FLAG_GROUP) && (!option->active || option->level == GC_LEVEL_INTERNAL)) { option++; continue; } if (option->flags & GC_OPT_FLAG_GROUP) { const gc_option_t *group_option = option + 1; gc_expert_level_t level = GC_LEVEL_NR; /* The manual states that the group level is always the minimum of the levels of all contained options. Due to different active options, and because it is hard to maintain manually, we calculate it here. The value in the global static table is ignored. */ while (group_option->name) { if (group_option->flags & GC_OPT_FLAG_GROUP) break; if (group_option->level < level) level = group_option->level; group_option++; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ memcpy (&opt_copy, option, sizeof (opt_copy)); opt_copy.level = level; list_one_option (&opt_copy, out); } } else list_one_option (option, out); option++; } } /* Find the option NAME in component COMPONENT, for the backend BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */ static gc_option_t * find_option (gc_component_t component, const char *name, gc_backend_t backend) { gc_option_t *option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && !strcmp (option->name, name) && (backend == GC_BACKEND_ANY || option->backend == backend)) break; option++; } return option->name ? option : NULL; } /* Determine the configuration filename for the component COMPONENT and backend BACKEND. */ static char * get_config_filename (gc_component_t component, gc_backend_t backend) { char *filename = NULL; gc_option_t *option = find_option (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); assert (option); assert (option->arg_type == GC_ARG_TYPE_FILENAME); assert (!(option->flags & GC_OPT_FLAG_LIST)); if (!option->active || !option->default_value) gc_error (1, 0, "Option %s, needed by backend %s, was not initialized", gc_backend[backend].option_config_filename, gc_backend[backend].name); if (option->value && *option->value) filename = percent_deescape (&option->value[1]); else if (option->default_value && *option->default_value) filename = percent_deescape (&option->default_value[1]); else filename = ""; #if HAVE_W32CE_SYSTEM if (!(filename[0] == '/' || filename[0] == '\\')) #elif defined(HAVE_DOSISH_SYSTEM) if (!(filename[0] && filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\'))) #else if (filename[0] != '/') #endif gc_error (1, 0, "Option %s, needed by backend %s, is not absolute", gc_backend[backend].option_config_filename, gc_backend[backend].name); return filename; } /* Retrieve the options for the component COMPONENT from backend BACKEND, which we already know is a program-type backend. */ static void retrieve_options_from_program (gc_component_t component, gc_backend_t backend) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; int exitcode; pid_t pid; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t config; char *config_filename; pgmname = (gc_backend[backend].module_name ? gnupg_module_name (gc_backend[backend].module_name) : gc_backend[backend].program ); argv[0] = "--gpgconf-list"; argv[1] = NULL; err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0, NULL, &outfp, NULL, &pid); if (err) { gc_error (1, 0, "could not gather active options from '%s': %s", pgmname, gpg_strerror (err)); } while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { gc_option_t *option; char *linep; unsigned long flags = 0; char *default_value = NULL; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s from %s", line, pgmname); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s from %s", line, pgmname); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; if (flags & GC_OPT_FLAG_DEFAULT) default_value = linep; linep = end; } /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { if (option->active) gc_error (1, errno, "option %s returned twice from %s", line, pgmname); option->active = 1; option->flags |= flags; if (default_value && *default_value) option->default_value = xstrdup (default_value); } } if (length < 0 || es_ferror (outfp)) gc_error (1, errno, "error reading from %s", pgmname); if (es_fclose (outfp)) gc_error (1, errno, "error closing %s", pgmname); err = gnupg_wait_process (pgmname, pid, 1, &exitcode); if (err) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); gnupg_release_process (pid); /* At this point, we can parse the configuration file. */ config_filename = get_config_filename (component, backend); config = es_fopen (config_filename, "r"); if (!config) { if (errno != ENOENT) gc_error (0, errno, "warning: can not open config file %s", config_filename); } else { while ((length = es_read_line (config, &line, &line_len, NULL)) > 0) { char *name; char *value; gc_option_t *option; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; value = name; while (*value && *value != ' ' && *value != '\t' && *value != '#' && *value != '\r' && *value != '\n') value++; if (*value == ' ' || *value == '\t') { char *end; *(value++) = '\0'; while (*value == ' ' || *value == '\t') value++; end = value; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; while (end > value && (end[-1] == ' ' || end[-1] == '\t')) end--; *end = '\0'; } else *value = '\0'; /* Look up the option in the component and install the configuration data. */ option = find_option (component, line, backend); if (option) { char *opt_value; if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { if (*value) gc_error (0, 0, "warning: ignoring argument %s for option %s", value, name); opt_value = xstrdup ("1"); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) opt_value = xasprintf ("\"%s", gc_percent_escape (value)); else { /* FIXME: Verify that the number is sane. */ opt_value = xstrdup (value); } /* Now enter the option into the table. */ if (!(option->flags & GC_OPT_FLAG_LIST)) { if (option->value) xfree (option->value); option->value = opt_value; } else { if (!option->value) option->value = opt_value; else { char *old = option->value; option->value = xasprintf ("%s,%s", old, opt_value); xfree (old); xfree (opt_value); } } } } if (length < 0 || es_ferror (config)) gc_error (1, errno, "error reading from %s", config_filename); if (es_fclose (config)) gc_error (1, errno, "error closing %s", config_filename); } xfree (line); } /* Retrieve the options for the component COMPONENT from backend BACKEND, which we already know is of type file list. */ static void retrieve_options_from_file (gc_component_t component, gc_backend_t backend) { gc_option_t *list_option; gc_option_t *config_option; char *list_filename; gpgrt_stream_t list_file; char *line = NULL; size_t line_len = 0; ssize_t length; char *list = NULL; list_option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (list_option); assert (!list_option->active); list_filename = get_config_filename (component, backend); list_file = gpgrt_fopen (list_filename, "r"); if (!list_file) gc_error (0, errno, "warning: can not open list file %s", list_filename); else { while ((length = gpgrt_read_line (list_file, &line, &line_len, NULL)) > 0) { char *start; char *end; char *new_list; start = line; while (*start == ' ' || *start == '\t') start++; if (!*start || *start == '#' || *start == '\r' || *start == '\n') continue; end = start; while (*end && *end != '#' && *end != '\r' && *end != '\n') end++; /* Walk back to skip trailing white spaces. Looks evil, but works because of the conditions on START and END imposed at this point (END is at least START + 1, and START is not a whitespace character). */ while (*(end - 1) == ' ' || *(end - 1) == '\t') end--; *end = '\0'; /* FIXME: Oh, no! This is so lame! Should use realloc and really append. */ if (list) { new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start)); xfree (list); list = new_list; } else list = xasprintf ("\"%s", gc_percent_escape (start)); } if (length < 0 || gpgrt_ferror (list_file)) gc_error (1, errno, "can not read list file %s", list_filename); } list_option->active = 1; list_option->value = list; /* Fix up the read-only flag. */ config_option = find_option (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); if (config_option->flags & GC_OPT_FLAG_NO_CHANGE) list_option->flags |= GC_OPT_FLAG_NO_CHANGE; if (list_file && gpgrt_fclose (list_file)) gc_error (1, errno, "error closing %s", list_filename); xfree (line); } /* Retrieve the currently active options and their defaults from all involved backends for this component. Using -1 for component will retrieve all options from all components. */ void gc_component_retrieve_options (int component) { int process_all = 0; int backend_seen[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; for (backend = 0; backend < GC_BACKEND_NR; backend++) backend_seen[backend] = 0; if (component == -1) { process_all = 1; component = 0; assert (component < GC_COMPONENT_NR); } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ option = gc_component[component].options; while (option && option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP)) { backend = option->backend; if (backend_seen[backend]) { option++; continue; } backend_seen[backend] = 1; assert (backend != GC_BACKEND_ANY); if (gc_backend[backend].program) retrieve_options_from_program (component, backend); else retrieve_options_from_file (component, backend); } option++; } } while (process_all && ++component < GC_COMPONENT_NR); } /* Perform a simple validity check based on the type. Return in * NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of * type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode * is used. */ static void option_check_validity (gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; if (!option->active) gc_error (1, 0, "option %s not supported by backend %s", option->name, gc_backend[option->backend].name); if (option->new_flags || option->new_value) gc_error (1, 0, "option %s already changed", option->name); if (flags & GC_OPT_FLAG_DEFAULT) { if (*new_value) gc_error (1, 0, "argument %s provided for deleted option %s", new_value, option->name); return; } /* GC_ARG_TYPE_NONE options have special list treatment. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { char *tail; gpg_err_set_errno (0); *new_value_nr = strtoul (new_value, &tail, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*tail) gc_error (1, 0, "garbage after argument for option %s", option->name); if (!(option->flags & GC_OPT_FLAG_LIST)) { if (*new_value_nr != 1) gc_error (1, 0, "argument for non-list option %s of type 0 " "(none) must be 1", option->name); } else { if (*new_value_nr == 0) gc_error (1, 0, "argument for option %s of type 0 (none) " "must be positive", option->name); } return; } arg = new_value; do { if (*arg == '\0' || (*arg == ',' && !verbatim)) { if (!(option->flags & GC_OPT_FLAG_ARG_OPT)) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST)) gc_error (1, 0, "list found for non-list option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { if (*arg != '"' && !verbatim) gc_error (1, 0, "string argument for option %s must begin " "with a quote (\") character", option->name); /* FIXME: We do not allow empty string arguments for now, as we do not quote arguments in configuration files, and thus no argument is indistinguishable from the empty string. */ if (arg[1] == '\0' || (arg[1] == ',' && !verbatim)) gc_error (1, 0, "empty string argument for option %s is " "currently not allowed. Please report this!", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { long res; gpg_err_set_errno (0); res = strtol (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32) { unsigned long res; gpg_err_set_errno (0); res = strtoul (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = verbatim? strchr (arg, ',') : NULL; if (arg) arg++; } while (arg && *arg); } #ifdef HAVE_W32_SYSTEM int copy_file (const char *src_name, const char *dst_name) { #define BUF_LEN 4096 char buffer[BUF_LEN]; int len; gpgrt_stream_t src; gpgrt_stream_t dst; src = gpgrt_fopen (src_name, "r"); if (src == NULL) return -1; dst = gpgrt_fopen (dst_name, "w"); if (dst == NULL) { int saved_err = errno; gpgrt_fclose (src); gpg_err_set_errno (saved_err); return -1; } do { int written; len = gpgrt_fread (buffer, 1, BUF_LEN, src); if (len == 0) break; written = gpgrt_fwrite (buffer, 1, len, dst); if (written != len) break; } while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst)); if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src)) { int saved_errno = errno; gpgrt_fclose (src); gpgrt_fclose (dst); unlink (dst_name); gpg_err_set_errno (saved_errno); return -1; } if (gpgrt_fclose (dst)) gc_error (1, errno, "error closing %s", dst_name); if (gpgrt_fclose (src)) gc_error (1, errno, "error closing %s", src_name); return 0; } #endif /* HAVE_W32_SYSTEM */ /* Create and verify the new configuration file for the specified * backend and component. Returns 0 on success and -1 on error. This * function may store pointers to malloced strings in SRC_FILENAMEP, * DEST_FILENAMEP, and ORIG_FILENAMEP. Those must be freed by the * caller. The strings refer to three versions of the configuration * file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_file (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; char *arg; char *cur_arg = NULL; option = find_option (component, gc_backend[backend].option_name, GC_BACKEND_ANY); assert (option); assert (option->active); assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE); /* FIXME. Throughout the function, do better error reporting. */ /* Note that get_config_filename() calls percent_deescape(), so we call this before processing the arguments. */ dest_filename = xstrdup (get_config_filename (component, backend)); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); arg = option->new_value; if (arg && arg[0] == '\0') arg = NULL; else if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_file_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char *endp; char saved_end; endp = start; end = endp; /* Search for the end of the line. */ while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n') { endp++; if (*endp && *endp != ' ' && *endp != '\t' && *endp != '\r' && *endp != '\n' && *endp != '#') end = endp + 1; } saved_end = *end; *end = '\0'; if ((option->new_flags & GC_OPT_FLAG_DEFAULT) || !cur_arg || strcmp (start, cur_arg)) disable = 1; else { /* Find next argument. */ if (arg) { char *arg_end; arg++; arg_end = strchr (arg, ','); if (arg_end) *arg_end = '\0'; cur_arg = percent_deescape (arg); if (arg_end) { *arg_end = ','; arg = arg_end + 1; } else arg = NULL; } else cur_arg = NULL; } *end = saved_end; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_file_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_file_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the arguments we are going to add. Now, dump the new arguments and write the end marker, possibly followed by the rest of the original file. */ while (cur_arg) { gpgrt_fprintf (src_file, "%s\n", cur_arg); /* Find next argument. */ if (arg) { char *end; arg++; end = strchr (arg, ','); if (end) *end = '\0'; cur_arg = percent_deescape (arg); if (end) { *end = ','; arg = end + 1; } else arg = NULL; } else cur_arg = NULL; } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_file_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_file_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_file_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_file_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_file_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_file_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Create and verify the new configuration file for the specified * backend and component. Returns 0 on success and -1 on error. If * VERBATIM is set the profile mode is used. This function may store * pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and * ORIG_FILENAMEP. Those must be freed by the caller. The strings * refer to three versions of the configuration file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_program (gc_component_t component, gc_backend_t backend, char **src_filenamep, char **dest_filenamep, char **orig_filenamep, int verbatim) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; /* Special hack for gpg, see below. */ int utf8strings_seen = 0; /* FIXME. Throughout the function, do better error reporting. */ dest_filename = xstrdup (get_config_filename (component, backend)); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. */ fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } else if (backend == GC_BACKEND_GPG && in_marker && ! strcmp ("utf8-strings\n", line)) { /* Strip duplicated entries. */ if (utf8strings_seen) disable = 1; else utf8strings_seen = 1; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char saved_end; end = start; while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n' && *end != '#') end++; saved_end = *end; *end = '\0'; option = find_option (component, start, backend); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the options we are going to change. Now, dump the changed options (except for those we are going to revert to their default), and write the end marker, possibly followed by the rest of the original file. */ /* We have to turn on UTF8 strings for GnuPG. */ if (backend == GC_BACKEND_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; while (option->name) { if (!(option->flags & GC_OPT_FLAG_GROUP) && option->backend == backend && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { assert (*arg == '1'); gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; if (!verbatim) { log_assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; } else end = NULL; gpgrt_fprintf (src_file, "%s %s\n", option->name, verbatim? arg : percent_deescape (arg)); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; gpgrt_fprintf (src_file, "%s %s\n", option->name, arg); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } option++; } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Common code for gc_component_change_options and * gc_process_gpgconf_conf. If VERBATIM is set the profile parsing * mode is used. */ static void change_one_value (gc_option_t *option, int *runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (option, flags, new_value, &new_value_nr, verbatim); if (option->flags & GC_OPT_FLAG_RUNTIME) runtime[option->backend] = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && (option->flags & GC_OPT_FLAG_LIST)) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ assert (new_value_nr > 0); option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1); str = option->new_value; *(str++) = '1'; while (--new_value_nr > 0) { *(str++) = ','; *(str++) = '1'; } *(str++) = '\0'; } else option->new_value = xstrdup (new_value); } } /* Read the modifications from IN and apply them. If IN is NULL the modifications are expected to already have been set to the global table. If VERBATIM is set the profile mode is used. */ void gc_component_change_options (int component, estream_t in, estream_t out, int verbatim) { int err = 0; int block = 0; int runtime[GC_BACKEND_NR]; char *src_filename[GC_BACKEND_NR]; char *dest_filename[GC_BACKEND_NR]; char *orig_filename[GC_BACKEND_NR]; gc_backend_t backend; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; if (component == GC_COMPONENT_PINENTRY) return; /* Dummy component for now. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) { runtime[backend] = 0; src_filename[backend] = NULL; dest_filename[backend] = NULL; orig_filename[backend] = NULL; } if (in) { /* Read options from the file IN. */ while ((length = es_read_line (in, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *new_value = ""; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s", line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s", line); linep = end; } /* Don't allow setting of the no change flag. */ flags &= ~GC_OPT_FLAG_NO_CHANGE; /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; new_value = linep; linep = end; } option = find_option (component, line, GC_BACKEND_ANY); if (!option) gc_error (1, 0, "unknown option %s", line); if ((option->flags & GC_OPT_FLAG_NO_CHANGE)) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (option, runtime, flags, new_value, 0); } if (length < 0 || gpgrt_ferror (in)) gc_error (1, errno, "error reading stream 'in'"); } /* Now that we have collected and locally verified the changes, write them out to new configuration files, verify them externally, and then commit them. */ option = gc_component[component].options; while (option && option->name) { /* Go on if we have already seen this backend, or if there is nothing to do. */ if (src_filename[option->backend] || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_backend[option->backend].program) { err = change_options_program (component, option->backend, &src_filename[option->backend], &dest_filename[option->backend], &orig_filename[option->backend], verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename[option->backend]); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } else err = change_options_file (component, option->backend, &src_filename[option->backend], &dest_filename[option->backend], &orig_filename[option->backend]); if (err) break; option++; } /* We are trying to atomically commit all changes. Unfortunately, we cannot rely on gnupg_rename_file to manage the signals for us, doing so would require us to pass NULL as BLOCK to any subsequent call to it. Instead, we just manage the signal handling manually. */ block = 1; gnupg_block_all_signals (); if (! err && ! opt.dry_run) { int i; for (i = 0; i < GC_BACKEND_NR; i++) { if (src_filename[i]) { /* FIXME: Make a verification here. */ assert (dest_filename[i]); if (orig_filename[i]) err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL); else { #ifdef HAVE_W32_SYSTEM /* We skip the unlink if we expect the file not to be there. */ err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL); #else /* HAVE_W32_SYSTEM */ /* This is a bit safer than rename() because we expect DEST_FILENAME not to be there. If it happens to be there, this will fail. */ err = link (src_filename[i], dest_filename[i]); if (!err) err = unlink (src_filename[i]); #endif /* !HAVE_W32_SYSTEM */ } if (err) break; xfree (src_filename[i]); src_filename[i] = NULL; } } } if (err || opt.dry_run) { int i; int saved_errno = errno; /* An error occurred or a dry-run is requested. */ for (i = 0; i < GC_BACKEND_NR; i++) { if (src_filename[i]) { /* The change was not yet committed. */ unlink (src_filename[i]); if (orig_filename[i]) unlink (orig_filename[i]); } else { /* The changes were already committed. FIXME: This is a tad dangerous, as we don't know if we don't overwrite a version of the file that is even newer than the one we just installed. */ if (orig_filename[i]) gnupg_rename_file (orig_filename[i], dest_filename[i], NULL); else unlink (dest_filename[i]); } } if (err) gc_error (1, saved_errno, "could not commit changes"); /* Fall-through for dry run. */ goto leave; } /* If it all worked, notify the daemons of the changes. */ if (opt.runtime) for (backend = 0; backend < GC_BACKEND_NR; backend++) { if (runtime[backend] && gc_backend[backend].runtime_change) (*gc_backend[backend].runtime_change) (0); } /* Move the per-process backup file into its place. */ for (backend = 0; backend < GC_BACKEND_NR; backend++) if (orig_filename[backend]) { char *backup_filename; assert (dest_filename[backend]); backup_filename = xasprintf ("%s.%s.bak", dest_filename[backend], GPGCONF_NAME); gnupg_rename_file (orig_filename[backend], backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); for (backend = 0; backend < GC_BACKEND_NR; backend++) { xfree (src_filename[backend]); xfree (dest_filename[backend]); xfree (orig_filename[backend]); } } /* Check whether USER matches the current user of one of its group. This function may change USER. Returns true is there is a match. */ static int key_matches_user_or_group (char *user) { char *group; if (*user == '*' && user[1] == 0) return 1; /* A single asterisk matches all users. */ group = strchr (user, ':'); if (group) *group++ = 0; #ifdef HAVE_W32_SYSTEM /* Under Windows we don't support groups. */ if (group && *group) gc_error (0, 0, _("Note that group specifications are ignored\n")); #ifndef HAVE_W32CE_SYSTEM if (*user) { static char *my_name; if (!my_name) { char tmp[1]; DWORD size = 1; GetUserNameA (tmp, &size); my_name = xmalloc (size); if (!GetUserNameA (my_name, &size)) gc_error (1,0, "error getting current user name: %s", w32_strerror (-1)); } if (!strcmp (user, my_name)) return 1; /* Found. */ } #endif /*HAVE_W32CE_SYSTEM*/ #else /*!HAVE_W32_SYSTEM*/ /* First check whether the user matches. */ if (*user) { static char *my_name; if (!my_name) { struct passwd *pw = getpwuid ( getuid () ); if (!pw) gc_error (1, errno, "getpwuid failed for current user"); my_name = xstrdup (pw->pw_name); } if (!strcmp (user, my_name)) return 1; /* Found. */ } /* If that failed, check whether a group matches. */ if (group && *group) { static char *my_group; static char **my_supgroups; int n; if (!my_group) { struct group *gr = getgrgid ( getgid () ); if (!gr) gc_error (1, errno, "getgrgid failed for current user"); my_group = xstrdup (gr->gr_name); } if (!strcmp (group, my_group)) return 1; /* Found. */ if (!my_supgroups) { int ngids; gid_t *gids; ngids = getgroups (0, NULL); gids = xcalloc (ngids+1, sizeof *gids); ngids = getgroups (ngids, gids); if (ngids < 0) gc_error (1, errno, "getgroups failed for current user"); my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups); for (n=0; n < ngids; n++) { struct group *gr = getgrgid ( gids[n] ); if (!gr) gc_error (1, errno, "getgrgid failed for supplementary group"); my_supgroups[n] = xstrdup (gr->gr_name); } xfree (gids); } for (n=0; my_supgroups[n]; n++) if (!strcmp (group, my_supgroups[n])) return 1; /* Found. */ } #endif /*!HAVE_W32_SYSTEM*/ return 0; /* No match. */ } /* Read and process the global configuration file for gpgconf. This optional file is used to update our internal tables at runtime and may also be used to set new default values. If FNAME is NULL the default name will be used. With UPDATE set to true the internal tables are actually updated; if not set, only a syntax check is done. If DEFAULTS is true the global options are written to the configuration files. If LISTFP is set, no changes are done but the configuration file is printed to LISTFP in a colon separated format. Returns 0 on success or if the config file is not present; -1 is returned on error. */ int gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults, estream_t listfp) { int result = 0; char *line = NULL; size_t line_len = 0; ssize_t length; gpgrt_stream_t config; int lineno = 0; int in_rule = 0; int got_match = 0; int runtime[GC_BACKEND_NR]; int backend_id, component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) runtime[backend_id] = 0; config = gpgrt_fopen (fname, "r"); if (!config) { /* Do not print an error if the file is not available, except when running in syntax check mode. */ if (errno != ENOENT || !update) { gc_error (0, errno, "can not open global config file '%s'", fname); result = -1; } xfree (fname); return result; } while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0) { char *key, *component, *option, *flags, *value; char *empty; gc_option_t *option_info = NULL; char *p; int is_continuation; lineno++; key = line; while (*key == ' ' || *key == '\t') key++; if (!*key || *key == '#' || *key == '\r' || *key == '\n') continue; is_continuation = (key != line); /* Parse the key field. */ if (!is_continuation && got_match) break; /* Finish after the first match. */ else if (!is_continuation) { in_rule = 0; for (p=key+1; *p && !strchr (" \t\r\n", *p); p++) ; if (!*p) { gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno); result = -1; continue; } *p++ = 0; component = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { component = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*component == ' ' || *component == '\t') component++; for (p=component; *p && !strchr (" \t\r\n", *p); p++) ; if (p == component) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (component); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); result = -1; } /* Parse the option name. */ while (*option == ' ' || *option == '\t') option++; for (p=option; *p && !strchr (" \t\r\n", *p); p++) ; if (p == option) { gc_error (0, 0, "missing option at '%s', line %d", fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { option_info = find_option (component_id, option, GC_BACKEND_ANY); if (!option_info) { gc_error (0, 0, "unknown option at '%s', line %d", fname, lineno); result = -1; } } /* Parse the optional flags. */ while (*flags == ' ' || *flags == '\t') flags++; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { gc_error (0, 0, "syntax error in rule at '%s', line %d", fname, lineno); result = -1; continue; } *p++ = 0; value = p; } else /* No flags given. */ { value = flags; flags = NULL; } /* Parse the optional value. */ while (*value == ' ' || *value == '\t') value++; for (p=value; *p && !strchr ("\r\n", *p); p++) ; if (p == value) value = empty; /* No value given; let it point to an empty string. */ else { /* Strip trailing white space. */ *p = 0; for (p--; p > value && (*p == ' ' || *p == '\t'); p--) *p = 0; } /* Check flag combinations. */ if (!flags) ; else if (!strcmp (flags, "default")) { if (*value) { gc_error (0, 0, "flag \"default\" may not be combined " "with a value at '%s', line %d", fname, lineno); result = -1; } } else if (!strcmp (flags, "change")) ; else if (!strcmp (flags, "no-change")) ; else { gc_error (0, 0, "unknown flag at '%s', line %d", fname, lineno); result = -1; } /* In list mode we print out all records. */ if (listfp && !result) { /* If this is a new ruleset, print a key record. */ if (!is_continuation) { char *group = strchr (key, ':'); if (group) { *group++ = 0; if ((p = strchr (group, ':'))) *p = 0; /* We better strip any extra stuff. */ } es_fprintf (listfp, "k:%s:", gc_percent_escape (key)); es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):""); } /* All other lines are rule records. */ es_fprintf (listfp, "r:::%s:%s:%s:", gc_component[component_id].name, option_info->name? option_info->name : "", flags? flags : ""); if (value != empty) es_fprintf (listfp, "\"%s", gc_percent_escape (value)); es_putc ('\n', listfp); } /* Check whether the key matches but do this only if we are not running in syntax check mode. */ if ( update && !result && !listfp && (got_match || (key && key_matches_user_or_group (key))) ) { int newflags = 0; got_match = 1; /* Apply the flags from gpgconf.conf. */ if (!flags) ; else if (!strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; else if (!strcmp (flags, "no-change")) option_info->flags |= GC_OPT_FLAG_NO_CHANGE; else if (!strcmp (flags, "change")) option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE; if (defaults) { /* Here we explicitly allow updating the value again. */ if (newflags) { option_info->new_flags = 0; } if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (option_info, runtime, newflags, value, 0); } } } if (length < 0 || gpgrt_ferror (config)) { gc_error (0, errno, "error reading from '%s'", fname); result = -1; } if (gpgrt_fclose (config)) gc_error (0, errno, "error closing '%s'", fname); xfree (line); /* If it all worked, process the options. */ if (!result && update && defaults && !listfp) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 0); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) if (runtime[backend_id] && gc_backend[backend_id].runtime_change) (*gc_backend[backend_id].runtime_change) (0); } } xfree (fname); return result; } /* * Apply the profile FNAME to all known configure files. */ gpg_error_t gc_apply_profile (const char *fname) { gpg_error_t err; char *fname_buffer = NULL; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t fp; int lineno = 0; int runtime[GC_BACKEND_NR]; int backend_id; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) runtime[backend_id] = 0; if (!(!strcmp (fname, "-") || strchr (fname, '/') #ifdef HAVE_W32_SYSTEM || strchr (fname, '\\') #endif || strchr (fname, '.'))) { /* FNAME looks like a standard profile name. Check whether one * is installed and use that instead of the given file name. */ fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S, fname, ".prf", NULL); if (!access (fname_buffer, F_OK)) fname = fname_buffer; } fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info ("applying profile '%s'\n", fname); err = 0; while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0) { char *name, *flags, *value; gc_option_t *option_info = NULL; char *p; lineno++; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; trim_trailing_spaces (name); /* Check whether this is a new section. */ if (*name == '[') { name++; skip_section = 0; /* New section: Get the name of the component. */ p = strchr (name, ']'); if (!p) { error_count++; log_info ("%s:%d:%d: error: syntax error in section tag\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } *p++ = 0; if (*p) log_info ("%s:%d:%d: warning: garbage after section tag\n", fname, lineno, (int)(p - line)); trim_spaces (name); component_id = gc_component_find (name); if (component_id < 0) { log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n", fname, lineno, (int)(name - line), name ); skip_section = 1; } continue; } if (skip_section) continue; if (component_id < 0) { error_count++; log_info ("%s:%d:%d: error: not in a valid section\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } /* Parse the option name. */ for (p = name; *p && !spacep (p); p++) ; *p++ = 0; value = p; option_info = find_option (component_id, name, GC_BACKEND_ANY); if (!option_info) { error_count++; log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n", fname, lineno, (int)(name - line), name, gc_component[component_id].name); continue; } /* Parse the optional flags. */ trim_spaces (value); flags = value; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { log_info ("%s:%d:%d: warning: invalid flag specification\n", fname, lineno, (int)(p - line)); continue; } *p++ = 0; value = p; trim_spaces (value); } else /* No flags given. */ flags = NULL; /* Set required defaults. */ if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE && !*value) value = "1"; /* Check and save this option. */ newflags = 0; if (flags && !strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; if (newflags) option_info->new_flags = 0; if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (option_info, runtime, newflags, value, 1); } if (length < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); error_count++; log_error (_("%s:%u: read error: %s\n"), fname, lineno, gpg_strerror (err)); } if (es_fclose (fp)) log_error (_("error closing '%s'\n"), fname); if (error_count) log_error (_("error parsing '%s'\n"), fname); xfree (line); /* If it all worked, process the options. */ if (!err) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 1); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++) if (runtime[backend_id] && gc_backend[backend_id].runtime_change) (*gc_backend[backend_id].runtime_change) (0); } } xfree (fname_buffer); return err; } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index d6bf9a26a..223655567 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,807 +1,807 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 2016 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', oNoVerbose = 500, oHomedir, oBuilddir, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aListDirs, aLaunch, aKill, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, aReload }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, { aApplyDefaults, "apply-defaults", 256, N_("apply global default values") }, { aApplyProfile, "apply-profile", 256, N_("|FILE|update configuration files using FILE") }, { aListDirs, "list-dirs", 256, N_("get the configuration directories for @GPGCONF@") }, { aListConfig, "list-config", 256, N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, { aQuerySWDB, "query-swdb", 256, N_("query the software version database") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, { aCreateSocketDir, "create-socketdir", 256, "@"}, { aRemoveSocketDir, "remove-socketdir", 256, "@"}, { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, {0} }; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "@GPGCONF@ (@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 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } static void list_dirs (estream_t fp, char **names) { static struct { const char *name; const char *(*fnc)(void); const char *extra; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, { "socketdir", gnupg_socketdir, NULL }, { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; for (idx = 0; idx < DIM (list); idx++) { s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!names) es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } xfree (tmp); } } /* Check whether NAME is valid argument for query_swdb(). Valid names * start with a letter and contain only alphanumeric characters or an * underscore. */ static int valid_swdb_name_p (const char *name) { if (!name || !*name || !alphap (name)) return 0; for (name++; *name; name++) if (!alnump (name) && *name != '_') return 0; return 1; } /* Query the SWDB file. If necessary and possible this functions asks * the dirmngr to load an updated version of that file. The caller * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and * optional the currently installed version in CURRENT_VERSION. The * output written to OUT is a colon delimited line with these fields: * * name :: The name of the package * curvers:: The installed version if given. * status :: This value tells the status of the software package * '-' :: No information available * (error or CURRENT_VERSION not given) * '?' :: Unknown NAME * 'u' :: Update available * 'c' :: The version is Current * 'n' :: The current version is already Newer than the * available one. * urgency :: If the value is greater than zero an urgent update is required. * error :: 0 on success or an gpg_err_code_t * Common codes seen: * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. * GPG_ERR_ENOENT :: The SWDB file is not available. * GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file. * filedate:: Date of the swdb file (yyyymmddThhmmss) * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) * version :: The version string from the swdb. * reldate :: Release date of that version (yyyymmddThhmmss) * size :: Size of the package in bytes. * hash :: SHA-2 hash of the package. * */ static void query_swdb (estream_t out, const char *name, const char *current_version) { gpg_error_t err; const char *search_name; char *fname = NULL; estream_t fp = NULL; char *line = NULL; char *self_version = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char *fields[2]; char *p; gnupg_isotime_t filedate = {0}; gnupg_isotime_t verified = {0}; char *value_ver = NULL; gnupg_isotime_t value_date = {0}; char *value_size = NULL; char *value_sha2 = NULL; unsigned long value_size_ul = 0; int status, i; if (!valid_swdb_name_p (name)) { log_error ("error in package name '%s': %s\n", name, gpg_strerror (GPG_ERR_INV_NAME)); goto leave; } if (!strcmp (name, "gnupg")) search_name = "gnupg21"; else if (!strcmp (name, "gnupg1")) search_name = "gnupg1"; else search_name = name; if (!current_version && !strcmp (name, "gnupg")) { /* Use our own version but string a possible beta string. */ self_version = xstrdup (PACKAGE_VERSION); p = strchr (self_version, '-'); if (p) *p = 0; current_version = self_version; } if (current_version && (strchr (current_version, ':') || compare_version_strings (current_version, NULL))) { log_error ("error in version string '%s': %s\n", current_version, gpg_strerror (GPG_ERR_INV_ARG)); goto leave; } fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } - /* Note that the parser uses the first occurance of a matching + /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (!*filedate && !strcmp (fields[0], ".filedate")) { string2isotime (filedate, fields[1]); continue; } if (!*verified && !strcmp (fields[0], ".verified")) { string2isotime (verified, fields[1]); continue; } /* Tokenize the name. */ p = strrchr (fields[0], '_'); if (!p) continue; /* Name w/o an underscore. */ *p++ = 0; /* Wait for the requested name. */ if (!strcmp (fields[0], search_name)) { if (!strcmp (p, "ver") && !value_ver) value_ver = xstrdup (fields[1]); else if (!strcmp (p, "date") && !*value_date) string2isotime (value_date, fields[1]); else if (!strcmp (p, "size") && !value_size) value_size = xstrdup (fields[1]); else if (!strcmp (p, "sha2") && !value_sha2) value_sha2 = xstrdup (fields[1]); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (!*filedate || !*verified) { err = gpg_error (GPG_ERR_INV_TIME); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); goto leave; } if (!value_ver) { es_fprintf (out, "%s:%s:?:::::::::\n", name, current_version? current_version : ""); goto leave; } if (value_size) { gpg_err_set_errno (0); value_size_ul = strtoul (value_size, &p, 10); if (errno) value_size_ul = 0; else if (*p == 'k') value_size_ul *= 1024; } err = 0; status = '-'; if (compare_version_strings (value_ver, NULL)) err = gpg_error (GPG_ERR_INV_VALUE); else if (!current_version) ; else if (!(i = compare_version_strings (value_ver, current_version))) status = 'c'; else if (i > 0) status = 'u'; else status = 'n'; es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", name, current_version? current_version : "", status, err, filedate, verified, value_ver, value_date, value_size_ul, value_sha2? value_sha2 : ""); leave: xfree (value_ver); xfree (value_size); xfree (value_sha2); xfree (line); es_fclose (fp); xfree (fname); xfree (self_version); } /* gpgconf main. */ int main (int argc, char **argv) { gpg_error_t err; ARGPARSE_ARGS pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gc_components_init (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = 1; /* Do not remove the args. */ while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aApplyProfile: case aListConfig: case aCheckConfig: case aQuerySWDB: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } if (log_get_errorcount (0)) exit (2); /* 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]); } fname = argc ? *argv : NULL; switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); exit (2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) exit (1); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp), 0); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); exit (2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) exit (1); } else { gc_component_kill (-1); } } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } else if (cmd == aLaunch) { if (gc_component_launch (idx)) exit (1); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname || !strcmp (fname, "all")) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); exit (1); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) exit (1); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) exit (1); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); exit (2); } gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) exit (1); break; case aApplyProfile: gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) exit (1); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL); break; case aQuerySWDB: /* Query the software version database. */ if (!fname || argc > 2) { es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", GPGCONF_NAME); exit (2); } get_outfp (&outfp); query_swdb (outfp, fname, argc > 1? argv[1] : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ xfree (socketdir); socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (rmdir (socketdir)) { /* If the director is not empty we first try to delet * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY || gpg_err_code (err) == GPG_ERR_EEXIST) { static const char * const names[] = { GPG_AGENT_SOCK_NAME, GPG_AGENT_EXTRA_SOCK_NAME, GPG_AGENT_BROWSER_SOCK_NAME, GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, DIRMNGR_SOCK_NAME }; int i; char *p; for (i=0; i < DIM(names); i++) { p = strconcat (socketdir , "/", names[i], NULL); if (p) gnupg_remove (p); xfree (p); } if (rmdir (socketdir)) gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", socketdir, gpg_strerror (err)); else gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } xfree (socketdir); } break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); return 0; } diff --git a/tools/mail-signed-keys b/tools/mail-signed-keys index 3c564f11c..263b8e535 100755 --- a/tools/mail-signed-keys +++ b/tools/mail-signed-keys @@ -1,114 +1,114 @@ #!/bin/sh # Copyright (C) 2000, 2001 Free Software Foundation, Inc. # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# FIXME: Use only valid email addreses, extract only given keys +# FIXME: Use only valid email addresses, extract only given keys dryrun=0 if [ "$1" = "--dry-run" ]; then dryrun=1 shift fi if [ -z "$1" -o -z "$2" -o -z "$3" ]; then echo "usage: mail-signed-keys keyring signedby signame" >&2 exit 1 fi signame="$3" if [ ! -f $1 ]; then echo "mail-signed-keys: '$1': no such file" >&2 exit 1 fi [ -f '.#tdb.tmp' ] && rm '.#tdb.tmp' ro="--homedir . --no-options --trustdb-name=./.#tdb.tmp --dry-run --lock-never --no-default-keyring --keyring $1" signedby=`gpg $ro --list-keys --with-colons $2 \ 2>/dev/null | awk -F: '$1=="pub" {print $5; exit 0}'` if [ -z "$signedby" ]; then echo "mail-signed-keys: '$2': no such signator" >&2 exit 1 fi if [ "$dryrun" = "0" ]; then echo "About to send the keys signed by $signedby" >&2 echo -n "to their owners. Do you really want to do this? (y/N)" >&2 read [ "$REPLY" != "y" -a "$REPLY" != "Y" ] && exit 0 fi gpg $ro --check-sigs --with-colons 2>/dev/null \ | awk -F: -v signedby="$signedby" -v gpgopt="$ro" \ -v dryrun="$dryrun" -v signame="$signame" ' BEGIN { sendmail="/usr/lib/sendmail -oi -t " } $1 == "pub" { nextkid=$5; nextuid=$10 if( uidcount > 0 ) { myflush() } kid=nextkid; uid=nextuid; next } $1 == "uid" { uid=$10 ; next } $1 == "sig" && $2 == "!" && $5 == signedby { uids[uidcount++] = uid; next } END { if( uidcount > 0 ) { myflush() } } function myflush() { if ( kid == signedby ) { uidcount=0; return } print "sending key " substr(kid,9) " to" | "cat >&2" for(i=0; i < 1; i++ ) { print " " uids[i] | "cat >&2" if( dryrun == 0 ) { if( i == 0 ) { printf "To: %s", uids[i] | sendmail } else { printf ",\n %s", uids[i] | sendmail } } } if(dryrun == 0) { printf "\n" | sendmail print "Subject: I signed your key " substr(kid,9) | sendmail print "" | sendmail print "Hi," | sendmail print "" | sendmail print "Here you get back the signed key." | sendmail print "I already sent them to the keyservers." | sendmail print "" | sendmail print "Peace," | sendmail print " " signame | sendmail print "" | sendmail cmd = "gpg " gpgopt " --export -a " kid " 2>/dev/null" while( (cmd | getline) > 0 ) { print | sendmail } print "" | sendmail close(cmd) close( sendmail ) } uidcount=0 } ' diff --git a/tools/symcryptrun.c b/tools/symcryptrun.c index 563e56bc3..54976cae5 100644 --- a/tools/symcryptrun.c +++ b/tools/symcryptrun.c @@ -1,1035 +1,1035 @@ /* symcryptrun.c - Tool to call simple symmetric encryption tools. * Copyright (C) 2005, 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ /* Sometimes simple encryption tools are already in use for a long time and there is a desire to integrate them into the GnuPG framework. The protocols and encryption methods might be non-standard or not even properly documented, so that a full-fledged encryption tool with an interface like gpg is not doable. This simple wrapper program provides a solution: It operates by calling the encryption/decryption module and providing the passphrase for a key (or even the key directly) using the standard pinentry mechanism through gpg-agent. */ /* This program is invoked in the following way: symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE \ [--decrypt | --encrypt] For encryption, the plain text must be provided on STDIN, and the ciphertext will be output to STDOUT. For decryption vice versa. CLASS can currently only be "confucius". PROGRAM must be the path to the crypto engine. KEYFILE must contain the secret key, which may be protected by a passphrase. The passphrase is retrieved via the pinentry program. The GPG Agent _must_ be running before starting symcryptrun. The possible exit status codes: 0 Success 1 Some error occurred 2 No valid passphrase was provided 3 The operation was canceled by the user Other classes may be added in the future. */ #define SYMC_BAD_PASSPHRASE 2 #define SYMC_CANCELED 3 #include <config.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <assert.h> #include <signal.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #ifdef HAVE_PTY_H #include <pty.h> #else #ifdef HAVE_TERMIOS_H #include <termios.h> #endif #ifdef HAVE_UTIL_H #include <util.h> #endif #ifdef HAVE_LIBUTIL_H #include <libutil.h> #endif #endif #ifdef HAVE_UTMP_H #include <utmp.h> #endif #include <ctype.h> #ifdef HAVE_LOCALE_H #include <locale.h> #endif #ifdef HAVE_LANGINFO_CODESET #include <langinfo.h> #endif #include <gpg-error.h> #include "../common/i18n.h" #include "../common/util.h" #include "../common/init.h" #include "../common/sysutils.h" /* FIXME: Bah. For spwq_secure_free. */ #define SIMPLE_PWQUERY_IMPLEMENTATION 1 #include "../common/simple-pwquery.h" /* From simple-gettext.c. */ /* We assume to have 'unsigned long int' value with at least 32 bits. */ #define HASHWORDBITS 32 /* The so called 'hashpjw' function by P.J. Weinberger [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools, 1986, 1987 Bell Telephone Laboratories, Inc.] */ static __inline__ ulong hash_string( const char *str_param ) { unsigned long int hval, g; const char *str = str_param; hval = 0; while (*str != '\0') { hval <<= 4; hval += (unsigned long int) *str++; g = hval & ((unsigned long int) 0xf << (HASHWORDBITS - 4)); if (g != 0) { hval ^= g >> (HASHWORDBITS - 8); hval ^= g; } } return hval; } /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oNoVerbose = 500, oOptions, oNoOptions, oLogFile, oHomedir, oClass, oProgram, oKeyfile, oDecrypt, oEncrypt, oInput }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@\nCommands:\n ") }, { oDecrypt, "decrypt", 0, N_("decryption modus") }, { oEncrypt, "encrypt", 0, N_("encryption modus") }, { 302, NULL, 0, N_("@\nOptions:\n ") }, { oClass, "class", 2, N_("tool class (confucius)") }, { oProgram, "program", 2, N_("program filename") }, { oKeyfile, "keyfile", 2, N_("secret key file (required)") }, { oInput, "inputfile", 2, N_("input file name (default stdin)") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oLogFile, "log-file", 2, N_("use a log file for the server") }, { oOptions, "options" , 2, N_("|FILE|read options from FILE") }, /* Hidden options. */ { oNoVerbose, "no-verbose", 0, "@" }, { oHomedir, "homedir", 2, "@" }, { oNoOptions, "no-options", 0, "@" },/* shortcut for --options /dev/null */ {0} }; /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ int quiet; /* Be extra quiet. */ const char *homedir; /* Configuration directory name */ char *class; char *program; char *keyfile; char *input; } opt; /* Print usage information and provide strings for help. */ static const char * my_strusage (int level) { const char *p; switch (level) { case 11: p = "symcryptrun (@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 1: case 40: p = _("Usage: symcryptrun [options] (-h for help)"); break; case 41: p = _("Syntax: symcryptrun --class CLASS --program PROGRAM " "--keyfile KEYFILE [options...] COMMAND [inputfile]\n" "Call a simple symmetric encryption tool\n"); break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; case 33: p = "\n"; break; default: p = NULL; break; } return p; } /* This is in the GNU C library in unistd.h. */ #ifndef TEMP_FAILURE_RETRY /* Evaluate EXPRESSION, and repeat as long as it returns -1 with 'errno' set to EINTR. */ # define TEMP_FAILURE_RETRY(expression) \ (__extension__ \ ({ long int __result; \ do __result = (long int) (expression); \ while (__result == -1L && errno == EINTR); \ __result; })) #endif /* Unlink a file, and shred it if SHRED is true. */ int remove_file (char *name, int shred) { if (!shred) return unlink (name); else { int status; pid_t pid; pid = fork (); if (pid == 0) { /* Child. */ /* -f forces file to be writable, and -u unlinks it afterwards. */ char *args[] = { SHRED, "-uf", name, NULL }; execv (SHRED, args); _exit (127); } else if (pid < 0) { /* Fork failed. */ status = -1; } else { /* Parent. */ if (TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)) != pid) status = -1; } if (!WIFEXITED (status)) { log_error (_("%s on %s aborted with status %i\n"), SHRED, name, status); unlink (name); return 1; } else if (WEXITSTATUS (status)) { log_error (_("%s on %s failed with status %i\n"), SHRED, name, WEXITSTATUS (status)); unlink (name); return 1; } return 0; } } /* Class Confucius. "Don't worry that other people don't know you; worry that you don't know other people." Analects--1.16. */ /* Create temporary directory with mode 0700. Returns a dynamically allocated string with the filename of the directory. */ static char * confucius_mktmpdir (void) { char *name, *p; p = getenv ("TMPDIR"); if (!p || !*p) p = "/tmp"; if (p[strlen (p) - 1] == '/') name = xstrconcat (p, "gpg-XXXXXX", NULL); else name = xstrconcat (p, "/", "gpg-XXXXXX", NULL); if (!name || !gnupg_mkdtemp (name)) { log_error (_("can't create temporary directory '%s': %s\n"), name?name:"", strerror (errno)); return NULL; } return name; } /* Buffer size for I/O operations. */ #define CONFUCIUS_BUFSIZE 4096 /* Buffer size for output lines. */ #define CONFUCIUS_LINESIZE 4096 /* Copy the file IN to OUT, either of which may be "-". If PLAIN is true, and the copying fails, and OUT is not STDOUT, then shred the file instead unlinking it. */ static int confucius_copy_file (char *infile, char *outfile, int plain) { FILE *in; int in_is_stdin = 0; FILE *out; int out_is_stdout = 0; char data[CONFUCIUS_BUFSIZE]; ssize_t data_len; if (infile[0] == '-' && infile[1] == '\0') { /* FIXME: Is stdin in binary mode? */ in = stdin; in_is_stdin = 1; } else { in = fopen (infile, "rb"); if (!in) { log_error (_("could not open %s for writing: %s\n"), infile, strerror (errno)); return 1; } } if (outfile[0] == '-' && outfile[1] == '\0') { /* FIXME: Is stdout in binary mode? */ out = stdout; out_is_stdout = 1; } else { out = fopen (outfile, "wb"); if (!out) { log_error (_("could not open %s for writing: %s\n"), infile, strerror (errno)); return 1; } } /* Now copy the data. */ while ((data_len = fread (data, 1, sizeof (data), in)) > 0) { if (fwrite (data, 1, data_len, out) != data_len) { log_error (_("error writing to %s: %s\n"), outfile, strerror (errno)); goto copy_err; } } if (data_len < 0 || ferror (in)) { log_error (_("error reading from %s: %s\n"), infile, strerror (errno)); goto copy_err; } /* Close IN if appropriate. */ if (!in_is_stdin && fclose (in) && ferror (in)) { log_error (_("error closing %s: %s\n"), infile, strerror (errno)); goto copy_err; } /* Close OUT if appropriate. */ if (!out_is_stdout && fclose (out) && ferror (out)) { log_error (_("error closing %s: %s\n"), infile, strerror (errno)); goto copy_err; } return 0; copy_err: if (!out_is_stdout) remove_file (outfile, plain); return 1; } /* Get a passphrase in secure storage (if possible). If AGAIN is true, then this is a repeated attempt. If CANCELED is not a null pointer, it will be set to true or false, depending on if the user - canceled the operation or not. On error (including cancelation), a + canceled the operation or not. On error (including cancellation), a null pointer is returned. The passphrase must be deallocated with confucius_drop_pass. CACHEID is the ID to be used for passphrase caching and can be NULL to disable caching. */ char * confucius_get_pass (const char *cacheid, int again, int *canceled) { int err; char *pw; char *orig_codeset; if (canceled) *canceled = 0; orig_codeset = i18n_switchto_utf8 (); pw = simple_pwquery (cacheid, again ? _("does not match - try again"):NULL, _("Passphrase:"), NULL, 0, &err); i18n_switchback (orig_codeset); if (!pw) { if (err) log_error (_("error while asking for the passphrase: %s\n"), gpg_strerror (err)); else { log_info (_("cancelled\n")); if (canceled) *canceled = 1; } } return pw; } /* Drop a passphrase retrieved with confucius_get_pass. */ void confucius_drop_pass (char *pass) { if (pass) spwq_secure_free (pass); } /* Run a confucius crypto engine. If MODE is oEncrypt, encryption is requested. If it is oDecrypt, decryption is requested. INFILE and OUTFILE are the temporary files used in the process. */ int confucius_process (int mode, char *infile, char *outfile, int argc, char *argv[]) { char **args; int cstderr[2]; int master; int slave; int res; pid_t pid; pid_t wpid; int tries = 0; char cacheid[40]; signal (SIGPIPE, SIG_IGN); if (!opt.program) { log_error (_("no --program option provided\n")); return 1; } if (mode != oDecrypt && mode != oEncrypt) { log_error (_("only --decrypt and --encrypt are supported\n")); return 1; } if (!opt.keyfile) { log_error (_("no --keyfile option provided\n")); return 1; } /* Generate a hash from the keyfile name for caching. */ snprintf (cacheid, sizeof (cacheid), "confucius:%lu", hash_string (opt.keyfile)); cacheid[sizeof (cacheid) - 1] = '\0'; args = malloc (sizeof (char *) * (10 + argc)); if (!args) { log_error (_("cannot allocate args vector\n")); return 1; } args[0] = opt.program; args[1] = (mode == oEncrypt) ? "-m1" : "-m2"; args[2] = "-q"; args[3] = infile; args[4] = "-z"; args[5] = outfile; args[6] = "-s"; args[7] = opt.keyfile; args[8] = (mode == oEncrypt) ? "-af" : "-f"; args[9 + argc] = NULL; while (argc--) args[9 + argc] = argv[argc]; if (pipe (cstderr) < 0) { log_error (_("could not create pipe: %s\n"), strerror (errno)); free (args); return 1; } if (openpty (&master, &slave, NULL, NULL, NULL) == -1) { log_error (_("could not create pty: %s\n"), strerror (errno)); close (cstderr[0]); close (cstderr[1]); free (args); return -1; } /* We don't want to deal with the worst case scenarios. */ assert (master > 2); assert (slave > 2); assert (cstderr[0] > 2); assert (cstderr[1] > 2); pid = fork (); if (pid < 0) { log_error (_("could not fork: %s\n"), strerror (errno)); close (master); close (slave); close (cstderr[0]); close (cstderr[1]); free (args); return 1; } else if (pid == 0) { /* Child. */ /* Close the parent ends. */ close (master); close (cstderr[0]); /* Change controlling terminal. */ if (login_tty (slave)) { /* It's too early to output a debug message. */ _exit (1); } dup2 (cstderr[1], 2); close (cstderr[1]); /* Now kick off the engine program. */ execv (opt.program, args); log_error (_("execv failed: %s\n"), strerror (errno)); _exit (1); } else { /* Parent. */ char buffer[CONFUCIUS_LINESIZE]; int buffer_len = 0; fd_set fds; int slave_closed = 0; int stderr_closed = 0; close (slave); close (cstderr[1]); free (args); /* Listen on the output FDs. */ do { FD_ZERO (&fds); if (!slave_closed) FD_SET (master, &fds); if (!stderr_closed) FD_SET (cstderr[0], &fds); res = select (FD_SETSIZE, &fds, NULL, NULL, NULL); if (res < 0) { log_error (_("select failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } if (FD_ISSET (cstderr[0], &fds)) { /* We got some output on stderr. This is just passed through via the logging facility. */ res = read (cstderr[0], &buffer[buffer_len], sizeof (buffer) - buffer_len - 1); if (res < 0) { log_error (_("read failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } else { char *newline; buffer_len += res; for (;;) { buffer[buffer_len] = '\0'; newline = strchr (buffer, '\n'); if (newline) { *newline = '\0'; log_error ("%s\n", buffer); buffer_len -= newline + 1 - buffer; memmove (buffer, newline + 1, buffer_len); } else if (buffer_len == sizeof (buffer) - 1) { /* Overflow. */ log_error ("%s\n", buffer); buffer_len = 0; } else break; } if (res == 0) stderr_closed = 1; } } else if (FD_ISSET (master, &fds)) { char data[512]; res = read (master, data, sizeof (data)); if (res < 0) { if (errno == EIO) { /* Slave-side close leads to readable fd and EIO. */ slave_closed = 1; } else { log_error (_("pty read failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); close (master); close (cstderr[0]); return 1; } } else if (res == 0) /* This never seems to be what happens on slave-side close. */ slave_closed = 1; else { /* Check for password prompt. */ if (data[res - 1] == ':') { char *pass; int canceled; /* If this is not the first attempt, the passphrase seems to be wrong, so clear the cache. */ if (tries) simple_pwclear (cacheid); pass = confucius_get_pass (cacheid, tries ? 1 : 0, &canceled); if (!pass) { kill (pid, SIGTERM); close (master); close (cstderr[0]); return canceled ? SYMC_CANCELED : 1; } write (master, pass, strlen (pass)); write (master, "\n", 1); confucius_drop_pass (pass); tries++; } } } } while (!stderr_closed || !slave_closed); close (master); close (cstderr[0]); wpid = waitpid (pid, &res, 0); if (wpid < 0) { log_error (_("waitpid failed: %s\n"), strerror (errno)); kill (pid, SIGTERM); /* State of cached password is unclear. Just remove it. */ simple_pwclear (cacheid); return 1; } else { /* Shouldn't happen, as we don't use WNOHANG. */ assert (wpid != 0); if (!WIFEXITED (res)) { log_error (_("child aborted with status %i\n"), res); /* State of cached password is unclear. Just remove it. */ simple_pwclear (cacheid); return 1; } if (WEXITSTATUS (res)) { /* The passphrase was wrong. Remove it from the cache. */ simple_pwclear (cacheid); /* We probably exceeded our number of attempts at guessing the password. */ if (tries >= 3) return SYMC_BAD_PASSPHRASE; else return 1; } return 0; } } /* Not reached. */ } /* Class confucius main program. If MODE is oEncrypt, encryption is requested. If it is oDecrypt, decryption is requested. The other parameters are taken from the global option data. */ int confucius_main (int mode, int argc, char *argv[]) { int res; char *tmpdir; char *infile; int infile_from_stdin = 0; char *outfile; tmpdir = confucius_mktmpdir (); if (!tmpdir) return 1; if (opt.input && !(opt.input[0] == '-' && opt.input[1] == '\0')) infile = xstrdup (opt.input); else { infile_from_stdin = 1; /* TMPDIR + "/" + "in" + "\0". */ infile = malloc (strlen (tmpdir) + 1 + 2 + 1); if (!infile) { log_error (_("cannot allocate infile string: %s\n"), strerror (errno)); rmdir (tmpdir); return 1; } strcpy (infile, tmpdir); strcat (infile, "/in"); } /* TMPDIR + "/" + "out" + "\0". */ outfile = malloc (strlen (tmpdir) + 1 + 3 + 1); if (!outfile) { log_error (_("cannot allocate outfile string: %s\n"), strerror (errno)); free (infile); rmdir (tmpdir); return 1; } strcpy (outfile, tmpdir); strcat (outfile, "/out"); if (infile_from_stdin) { /* Create INFILE and fill it with content. */ res = confucius_copy_file ("-", infile, mode == oEncrypt); if (res) { free (outfile); free (infile); rmdir (tmpdir); return res; } } /* Run the engine and thus create the output file, handling passphrase retrieval. */ res = confucius_process (mode, infile, outfile, argc, argv); if (res) { remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return res; } /* Dump the output file to stdout. */ res = confucius_copy_file (outfile, "-", mode == oDecrypt); if (res) { remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return res; } remove_file (outfile, mode == oDecrypt); if (infile_from_stdin) remove_file (infile, mode == oEncrypt); free (outfile); free (infile); rmdir (tmpdir); return 0; } /* symcryptrun's entry point. */ int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; unsigned configlineno; int mode = 0; int res; char *logfile = NULL; int default_config = 1; early_system_init (); set_strusage (my_strusage); log_set_prefix ("symcryptrun", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* 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= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oOptions) { /* Yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) gnupg_set_homedir (pargs.r.ret_str); } if (default_config) configname = make_filename (gnupg_homedir (), "symcryptrun.conf", NULL ); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (!default_config) { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(1); } xfree (configname); configname = NULL; } default_config = 0; } /* Parse the command line. */ while (optfile_parse (configfp, configname, &configlineno, &pargs, opts)) { switch (pargs.r_opt) { case oDecrypt: mode = oDecrypt; break; case oEncrypt: mode = oEncrypt; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oClass: opt.class = pargs.r.ret_str; break; case oProgram: opt.program = pargs.r.ret_str; break; case oKeyfile: opt.keyfile = pargs.r.ret_str; break; case oInput: opt.input = pargs.r.ret_str; break; case oLogFile: logfile = pargs.r.ret_str; break; case oOptions: /* Config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoOptions: break; /* no-options */ case oHomedir: /* Ignore this option here. */; break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (!mode) log_error (_("either %s or %s must be given\n"), "--decrypt", "--encrypt"); if (log_get_errorcount (0)) exit (1); if (logfile) log_set_file (logfile); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); setup_libgcrypt_logging (); gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); /* Tell simple-pwquery about the standard socket name. */ { char *tmp = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); simple_pw_set_socket (tmp); xfree (tmp); } if (!opt.class) { log_error (_("no class provided\n")); res = 1; } else if (!strcmp (opt.class, "confucius")) { res = confucius_main (mode, argc, argv); } else { log_error (_("class %s is not supported\n"), opt.class); res = 1; } return res; } diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c index f8b6fdc60..fc58d1428 100644 --- a/tools/watchgnupg.c +++ b/tools/watchgnupg.c @@ -1,519 +1,519 @@ /* watchgnupg.c - Socket server for GnuPG logs * Copyright (C) 2003, 2004, 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 <https://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <string.h> #include <errno.h> #include <stdarg.h> #include <assert.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <arpa/inet.h> #include <fcntl.h> #include <time.h> #ifdef HAVE_SYS_SELECT_H # include <sys/select.h> #endif #define PGM "watchgnupg" /* Allow for a standalone build on most systems. */ #ifdef VERSION #define MYVERSION_LINE PGM " ("GNUPG_NAME") " VERSION #define BUGREPORT_LINE "\nReport bugs to <bug-gnupg@gnu.org>.\n" #else #define MYVERSION_LINE PGM " (standalone build) " __DATE__ #define BUGREPORT_LINE "" #endif #if !defined(SUN_LEN) || !defined(PF_LOCAL) || !defined(AF_LOCAL) #define GNUPG_COMMON_NEED_AFLOCAL #include "../common/mischelp.h" #endif static int verbose; static int time_only; static void die (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); exit (1); } static void err (const char *format, ...) { va_list arg_ptr; fflush (stdout); fprintf (stderr, "%s: ", PGM); va_start (arg_ptr, format); vfprintf (stderr, format, arg_ptr); va_end (arg_ptr); putc ('\n', stderr); } static void * xmalloc (size_t n) { void *p = malloc (n); if (!p) die ("out of core"); return p; } static void * xcalloc (size_t n, size_t m) { void *p = calloc (n, m); if (!p) die ("out of core"); return p; } static void * xrealloc (void *old, size_t n) { void *p = realloc (old, n); if (!p) die ("out of core"); return p; } struct client_s { struct client_s *next; int fd; size_t size; /* Allocated size of buffer. */ size_t len; /* Current length of buffer. */ unsigned char *buffer; /* Buffer to with data already read. */ }; typedef struct client_s *client_t; /* The list of all connected peers. */ static client_t client_list; static void print_fd_and_time (int fd) { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); if (time_only) printf ("%3d - %02d:%02d:%02d ", fd, tp->tm_hour, tp->tm_min, tp->tm_sec ); else printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ", fd, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec ); } /* Print LINE for the client identified by C. Calling this function with LINE set to NULL, will flush the internal buffer. */ static void print_line (client_t c, const char *line) { const char *s; size_t n; if (!line) { if (c->buffer && c->len) { print_fd_and_time (c->fd); fwrite (c->buffer, c->len, 1, stdout); putc ('\n', stdout); c->len = 0; } return; } while ((s = strchr (line, '\n'))) { print_fd_and_time (c->fd); if (c->buffer && c->len) { fwrite (c->buffer, c->len, 1, stdout); c->len = 0; } fwrite (line, s - line + 1, 1, stdout); line = s + 1; } n = strlen (line); if (n) { if (c->len + n >= c->size) { c->size += ((n + 255) & ~255); c->buffer = (c->buffer ? xrealloc (c->buffer, c->size) : xmalloc (c->size)); } memcpy (c->buffer + c->len, line, n); c->len += n; } } static void setup_client (int server_fd, int is_un) { struct sockaddr_un addr_un; struct sockaddr_in addr_in; struct sockaddr *addr; socklen_t addrlen; int fd; client_t client; if (is_un) { addr = (struct sockaddr *)&addr_un; addrlen = sizeof addr_un; } else { addr = (struct sockaddr *)&addr_in; addrlen = sizeof addr_in; } fd = accept (server_fd, addr, &addrlen); if (fd == -1) { printf ("[accepting %s connection failed: %s]\n", is_un? "local":"tcp", strerror (errno)); } else if (fd >= FD_SETSIZE) { close (fd); printf ("[connection request denied: too many connections]\n"); } else { for (client = client_list; client && client->fd != -1; client = client->next) ; if (!client) { client = xcalloc (1, sizeof *client); client->next = client_list; client_list = client; } client->fd = fd; printf ("[client at fd %d connected (%s)]\n", client->fd, is_un? "local":"tcp"); } } static void print_version (int with_help) { fputs (MYVERSION_LINE "\n" "Copyright (C) 2017 Free Software Foundation, Inc.\n" "License GPLv3+: " "GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law.\n", stdout); if (with_help) fputs ("\n" "Usage: " PGM " [OPTIONS] SOCKETNAME\n" " " PGM " [OPTIONS] PORT [SOCKETNAME]\n" "Open the local socket SOCKETNAME (or the TCP port PORT)\n" "and display log messages\n" "\n" " --tcp listen on a TCP port and optionally on a local socket\n" " --force delete an already existing socket file\n" " --verbose enable extra informational output\n" " --time-only print only the time; not a full timestamp\n" " --version print version of the program and exit\n" " --help display this help and exit\n" BUGREPORT_LINE, stdout ); exit (0); } int main (int argc, char **argv) { int last_argc = -1; int force = 0; int tcp = 0; struct sockaddr_un srvr_addr_un; struct sockaddr_in srvr_addr_in; struct sockaddr *addr_in = NULL; struct sockaddr *addr_un = NULL; socklen_t addrlen_in, addrlen_un; unsigned short port; int server_un, server_in; int flags; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--")) { argc--; argv++; break; } else if (!strcmp (*argv, "--version")) print_version (0); else if (!strcmp (*argv, "--help")) print_version (1); else if (!strcmp (*argv, "--verbose")) { verbose = 1; argc--; argv++; } else if (!strcmp (*argv, "--time-only")) { time_only = 1; argc--; argv++; } else if (!strcmp (*argv, "--force")) { force = 1; argc--; argv++; } else if (!strcmp (*argv, "--tcp")) { tcp = 1; argc--; argv++; } } if (!((!tcp && argc == 1) || (tcp && (argc == 1 || argc == 2)))) { fprintf (stderr, "usage: " PGM " socketname\n" " " PGM " --tcp port [socketname]\n"); exit (1); } if (tcp) { port = atoi (*argv); argc--; argv++; } else { port = 0; } setvbuf (stdout, NULL, _IOLBF, 0); if (tcp) { int i = 1; server_in = socket (PF_INET, SOCK_STREAM, 0); if (server_in == -1) die ("socket(PF_INET) failed: %s\n", strerror (errno)); if (setsockopt (server_in, SOL_SOCKET, SO_REUSEADDR, (unsigned char *)&i, sizeof (i))) err ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); if (verbose) fprintf (stderr, "listening on port %hu\n", port); } else server_in = -1; if (argc) { server_un = socket (PF_LOCAL, SOCK_STREAM, 0); if (server_un == -1) die ("socket(PF_LOCAL) failed: %s\n", strerror (errno)); if (verbose) fprintf (stderr, "listening on socket '%s'\n", *argv); } else server_un = -1; /* We better set the listening socket to non-blocking so that we don't get bitten by race conditions in accept. The should not happen for Unix Domain sockets but well, shit happens. */ if (server_in != -1) { flags = fcntl (server_in, F_GETFL, 0); if (flags == -1) die ("fcntl (F_GETFL) failed: %s\n", strerror (errno)); if ( fcntl (server_in, F_SETFL, (flags | O_NONBLOCK)) == -1) die ("fcntl (F_SETFL) failed: %s\n", strerror (errno)); } if (server_un != -1) { flags = fcntl (server_un, F_GETFL, 0); if (flags == -1) die ("fcntl (F_GETFL) failed: %s\n", strerror (errno)); if ( fcntl (server_un, F_SETFL, (flags | O_NONBLOCK)) == -1) die ("fcntl (F_SETFL) failed: %s\n", strerror (errno)); } if (tcp) { memset (&srvr_addr_in, 0, sizeof srvr_addr_in); srvr_addr_in.sin_family = AF_INET; srvr_addr_in.sin_port = htons (port); srvr_addr_in.sin_addr.s_addr = htonl (INADDR_ANY); addr_in = (struct sockaddr *)&srvr_addr_in; addrlen_in = sizeof srvr_addr_in; } if (argc) { memset (&srvr_addr_un, 0, sizeof srvr_addr_un); srvr_addr_un.sun_family = AF_LOCAL; strncpy (srvr_addr_un.sun_path, *argv, sizeof (srvr_addr_un.sun_path)-1); srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path) - 1] = 0; addr_un = (struct sockaddr *)&srvr_addr_un; addrlen_un = SUN_LEN (&srvr_addr_un); } else addrlen_un = 0; /* Silent gcc. */ if (server_in != -1 && bind (server_in, addr_in, addrlen_in)) die ("bind to port %hu failed: %s\n", port, strerror (errno)); again: if (server_un != -1 && bind (server_un, addr_un, addrlen_un)) { if (errno == EADDRINUSE && force) { force = 0; remove (srvr_addr_un.sun_path); goto again; } else die ("bind to '%s' failed: %s\n", *argv, strerror (errno)); } if (server_in != -1 && listen (server_in, 5)) die ("listen on inet failed: %s\n", strerror (errno)); if (server_un != -1 && listen (server_un, 5)) die ("listen on local failed: %s\n", strerror (errno)); for (;;) { fd_set rfds; int max_fd; client_t client; /* Usually we don't have that many connections, thus it is okay - to set them allways from scratch and don't maintain an active + to set them always from scratch and don't maintain an active fd_set. */ FD_ZERO (&rfds); max_fd = -1; if (server_in != -1) { FD_SET (server_in, &rfds); max_fd = server_in; } if (server_un != -1) { FD_SET (server_un, &rfds); if (server_un > max_fd) max_fd = server_un; } for (client = client_list; client; client = client->next) if (client->fd != -1) { FD_SET (client->fd, &rfds); if (client->fd > max_fd) max_fd = client->fd; } if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0) continue; /* Ignore any errors. */ if (server_in != -1 && FD_ISSET (server_in, &rfds)) setup_client (server_in, 0); if (server_un != -1 && FD_ISSET (server_un, &rfds)) setup_client (server_un, 1); for (client = client_list; client; client = client->next) if (client->fd != -1 && FD_ISSET (client->fd, &rfds)) { char line[256]; int n; n = read (client->fd, line, sizeof line - 1); if (n < 0) { int save_errno = errno; print_line (client, NULL); /* flush */ printf ("[client at fd %d read error: %s]\n", client->fd, strerror (save_errno)); close (client->fd); client->fd = -1; } else if (!n) { print_line (client, NULL); /* flush */ close (client->fd); printf ("[client at fd %d disconnected]\n", client->fd); client->fd = -1; } else { line[n] = 0; print_line (client, line); } } } return 0; } /* Local Variables: compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c" End: */ diff --git a/tools/wks-util.c b/tools/wks-util.c index d78e01d00..8eab8344b 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -1,417 +1,417 @@ -/* wks-utils.c - Common helper fucntions for wks tools +/* wks-utils.c - Common helper functions for wks tools * Copyright (C) 2016 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 <https://www.gnu.org/licenses/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "../common/util.h" #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/mbox-util.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* The stream to output the status information. Output is disabled if this is NULL. */ static estream_t statusfp; /* Set the status FD. */ void wks_set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the outout of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void wks_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } /* Helper for wks_list_key. */ static void list_key_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); } /* Run gpg on KEY and store the primary fingerprint at R_FPR and the * list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL * is stored at R_FPR and R_MBOXES and an error code is returned. */ gpg_error_t wks_list_key (estream_t key, char **r_fpr, strlist_t *r_mboxes) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t listing; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char **fields = NULL; int nfields; int lnr; char *mbox = NULL; char *fpr = NULL; strlist_t mboxes = NULL; *r_fpr = NULL; *r_mboxes = NULL; /* Open a memory stream. */ listing = es_fopenmem (0, "w+b"); if (!listing) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (!opt.verbose) ccparray_put (&ccp, "--quiet"); else if (opt.verbose > 1) ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--with-colons"); ccparray_put (&ccp, "--dry-run"); ccparray_put (&ccp, "--import-options=import-minimal,import-show"); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, NULL, listing, list_key_status_cb, NULL); if (err) { log_error ("import failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (listing); lnr = 0; maxlen = 2048; /* Set limit. */ while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) { lnr++; if (!maxlen) { log_error ("received line too long\n"); err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; /* log_debug ("line '%s'\n", line); */ xfree (fields); fields = strtokenize (line, ":"); if (!fields) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); goto leave; } for (nfields = 0; fields[nfields]; nfields++) ; if (!nfields) { err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (!strcmp (fields[0], "sec")) { /* gpg may return "sec" as the first record - but we do not * accept secret keys. */ err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (lnr == 1 && strcmp (fields[0], "pub")) { /* First record is not a public key. */ err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (lnr > 1 && !strcmp (fields[0], "pub")) { /* More than one public key. */ err = gpg_error (GPG_ERR_TOO_MANY); goto leave; } if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) break; /* We can stop parsing here. */ if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr) { fpr = xtrystrdup (fields[9]); if (!fpr) { err = gpg_error_from_syserror (); goto leave; } } else if (!strcmp (fields[0], "uid") && nfields > 9) { /* Fixme: Unescape fields[9] */ xfree (mbox); mbox = mailbox_from_userid (fields[9]); if (mbox && !append_to_strlist_try (&mboxes, mbox)) { err = gpg_error_from_syserror (); goto leave; } } } if (len < 0 || es_ferror (listing)) { err = gpg_error_from_syserror (); log_error ("error reading memory stream\n"); goto leave; } *r_fpr = fpr; fpr = NULL; *r_mboxes = mboxes; mboxes = NULL; leave: xfree (fpr); xfree (mboxes); xfree (mbox); xfree (fields); es_free (line); xfree (argv); es_fclose (listing); return err; } /* Helper to write mail to the output(s). */ gpg_error_t wks_send_mime (mime_maker_t mime) { gpg_error_t err; estream_t mail; /* Without any option we take a short path. */ if (!opt.use_sendmail && !opt.output) { es_set_binary (es_stdout); return mime_maker_make (mime, es_stdout); } mail = es_fopenmem (0, "w+b"); if (!mail) { err = gpg_error_from_syserror (); return err; } err = mime_maker_make (mime, mail); if (!err && opt.output) { es_rewind (mail); err = send_mail_to_file (mail, opt.output); } if (!err && opt.use_sendmail) { es_rewind (mail); err = send_mail (mail); } es_fclose (mail); return err; } /* Parse the policy flags by reading them from STREAM and storing them * into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are * ignored. */ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) { enum tokens { TOK_MAILBOX_ONLY, TOK_DANE_ONLY, TOK_AUTH_SUBMIT, TOK_MAX_PENDING }; static struct { const char *name; enum tokens token; } keywords[] = { { "mailbox-only", TOK_MAILBOX_ONLY }, { "dane-only", TOK_DANE_ONLY }, { "auth-submit", TOK_AUTH_SUBMIT }, { "max-pending", TOK_MAX_PENDING } }; gpg_error_t err = 0; int lnr = 0; char line[1024]; char *p, *keyword, *value; int i, n; memset (flags, 0, sizeof *flags); while (es_fgets (line, DIM(line)-1, stream) ) { lnr++; n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); break; } trim_trailing_spaces (line); /* Skip empty and comment lines. */ for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; if (*p == ':') { err = gpg_error (GPG_ERR_SYNTAX); break; } keyword = p; value = NULL; if ((p = strchr (p, ':'))) { /* Colon found: Keyword with value. */ *p++ = 0; for (; spacep (p); p++) ; if (!*p) { err = gpg_error (GPG_ERR_MISSING_VALUE); break; } value = p; } for (i=0; i < DIM (keywords); i++) if (!ascii_strcasecmp (keywords[i].name, keyword)) break; if (!(i < DIM (keywords))) { if (ignore_unknown) continue; err = gpg_error (GPG_ERR_INV_NAME); break; } switch (keywords[i].token) { case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break; case TOK_DANE_ONLY: flags->dane_only = 1; break; case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break; case TOK_MAX_PENDING: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* FIXME: Define whether these are seconds, hours, or days * and decide whether to allow other units. */ flags->max_pending = atoi (value); break; } } if (!err && !es_feof (stream)) err = gpg_error_from_syserror (); leave: if (err) log_error ("error reading '%s', line %d: %s\n", es_fname_get (stream), lnr, gpg_strerror (err)); return err; }