/* Return a new string holding the format serial number and issuer ("#SN/issuer"). No filtering on invalid characters is done. Caller must release the string. On memory failure NULL is returned. */ char * gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer) { char *p, *p1; if (sn && issuer) { p1 = gpgsm_format_serial (sn); if (!p1) p = xtrystrdup ("[invalid SN]"); else { p = xtrymalloc (strlen (p1) + strlen (issuer) + 2 + 1); if (p) { *p = '#'; strcpy (stpcpy (stpcpy (p+1, p1),"/"), issuer); } xfree (p1); } } else p = xtrystrdup ("[invalid SN/issuer]"); return p; }
/* Allocate and initialize the context for the base64 decoder. If TITLE is NULL a plain base64 decoding is done. If it is the empty string the decoder will skip everything until a "-----BEGIN " line has been seen, decoding ends at a "----END " line. */ gpgrt_b64state_t _gpgrt_b64dec_start (const char *title) { gpgrt_b64state_t state; char *t = NULL; if (title) { t = xtrystrdup (title); if (!t) return NULL; } state = xtrycalloc (1, sizeof (struct _gpgrt_b64state)); if (!state) { xfree (t); return NULL; } if (t) { state->title = t; state->idx = s_init; } else state->idx = s_b64_0; state->using_decoder = 1; return state; }
/* 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; }
/* Return the mailbox (local-part@domain) form a standard user id. All plain ASCII characters in the result are converted to lowercase. Caller must free the result. Returns NULL if no valid mailbox was found (or we are out of memory). */ char * mailbox_from_userid (const char *userid) { const char *s, *s_end; size_t len; char *result = NULL; s = strchr (userid, '<'); if (s) { /* Seems to be a standard user id. */ s++; s_end = strchr (s, '>'); if (s_end && s_end > s) { len = s_end - s; result = xtrymalloc (len + 1); if (!result) return NULL; /* Ooops - out of core. */ strncpy (result, s, len); result[len] = 0; /* Apply some basic checks on the address. We do not use is_valid_mailbox because those checks are too strict. */ if (string_count_chr (result, '@') != 1 /* Need exactly one '@. */ || *result == '@' /* local-part missing. */ || result[len-1] == '@' /* domain missing. */ || result[len-1] == '.' /* ends with a dot. */ || string_has_ctrl_or_space (result) || has_dotdot_after_at (result)) { xfree (result); result = NULL; errno = EINVAL; } } else errno = EINVAL; } else if (is_valid_mailbox (userid)) { /* The entire user id is a mailbox. Return that one. Note that this fallback method has some restrictions on the valid syntax of the mailbox. However, those who want weird addresses should know about it and use the regular <...> syntax. */ result = xtrystrdup (userid); } else errno = EINVAL; return result? ascii_strlwr (result): NULL; }
/* Return the host name and the port (0 if none was given) from the URL. Return NULL on error or if host is not included in the URL. */ char * host_and_port_from_url (const char *url, int *port) { const char *s, *s2; char *buf, *p; int n; s = url; *port = 0; /* Find the scheme */ if ( !(s2 = strchr (s, ':')) || s2 == s ) return NULL; /* No scheme given. */ s = s2+1; /* Find the hostname */ if (*s != '/') return NULL; /* Does not start with a slash. */ s++; if (*s != '/') return NULL; /* No host name. */ s++; buf = xtrystrdup (s); if (!buf) { log_error (_("malloc failed: %s\n"), strerror (errno)); return NULL; } if ((p = strchr (buf, '/'))) *p++ = 0; strlwr (buf); if ((p = strchr (p, ':'))) { *p++ = 0; *port = atoi (p); } /* Remove quotes and make sure that no Nul has been encoded. */ if ((n = remove_percent_escapes (buf)) < 0 || n != strlen (buf) ) { log_error (_("bad URL encoding detected\n")); xfree (buf); return NULL; } return buf; }
/* Initialize the context for the base64 decoder. If TITLE is NULL a plain base64 decoding is done. If it is the empty string the decoder will skip everything until a "-----BEGIN " line has been seen, decoding ends at a "----END " line. Not yet implemented: If TITLE is either "PGP" or begins with "PGP " the PGP armor lines are skipped as well. */ gpg_error_t b64dec_start (struct b64state *state, const char *title) { memset (state, 0, sizeof *state); if (title) { if (!strncmp (title, "PGP", 3) && (!title[3] || title[3] == ' ')) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); state->title = xtrystrdup (title); if (!state->title) return gpg_error_from_syserror (); state->idx = s_init; } else state->idx = s_b64_0; return 0; }
char * gnupg_get_help_string (const char *key, int only_current_locale) { static const char *locname; char *result; if (!locname) { char *buffer, *p; int count = 0; const char *s = gnupg_messages_locale_name (); buffer = xtrystrdup (s); if (!buffer) locname = ""; else { for (p = buffer; *p; p++) if (*p == '.' || *p == '@' || *p == '/' /*(safeguard)*/) *p = 0; else if (*p == '_') { if (count++) *p = 0; /* Also cut at a underscore in the territory. */ } locname = buffer; } } if (!key || !*key) return NULL; result = findkey_locale (key, locname, only_current_locale, gnupg_sysconfdir ()); if (!result) result = findkey_locale (key, locname, only_current_locale, gnupg_datadir ()); if (result) trim_trailing_spaces (result); return result; }
/* Create the container described by the filename FNAME and the keyblob information in TUPLES. */ gpg_error_t be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples, unsigned int *r_id) { gpg_error_t err; int dummy; char *containername = NULL; char *mountpoint = NULL; err = be_encfs_get_detached_name (fname, &containername, &dummy); if (err) goto leave; mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX"); if (!mountpoint) { err = gpg_error_from_syserror (); goto leave; } if (!gnupg_mkdtemp (mountpoint)) { err = gpg_error_from_syserror (); log_error (_("can't create directory '%s': %s\n"), "/tmp/.#g13_XXXXXX", gpg_strerror (err)); goto leave; } err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint, tuples, r_id); /* In any case remove the temporary mount point. */ if (rmdir (mountpoint)) log_error ("error removing temporary mount point '%s': %s\n", mountpoint, gpg_strerror (gpg_error_from_syserror ())); leave: xfree (containername); xfree (mountpoint); return err; }
/* Retrieve the serial number and the time of the last update of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL and the time of update is returned in STAMP. If no update time is available the returned value is 0. Caller must free SERIAL unless the function returns an error. If STAMP is not of interest, NULL may be passed. */ gpg_error_t app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp) { char *buf; if (!app || !serial) return gpg_error (GPG_ERR_INV_VALUE); *serial = NULL; if (stamp) *stamp = 0; /* not available */ if (!app->serialnolen) buf = xtrystrdup ("FF7F00"); else buf = bin2hex (app->serialno, app->serialnolen, NULL); if (!buf) return gpg_error_from_syserror (); *serial = buf; 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 (*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 (*keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); rc = 0; } /* 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) + 100); if (!pi) return gpg_error_from_syserror (); pi->max_length = 100; 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); if (!rc) { assert (arg.unprotected_key); if (arg.change_required) { 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 { agent_put_cache (hexgrip, cache_mode, pi->pin, lookup_ttl? lookup_ttl (hexgrip) : 0); if (r_passphrase && *pi->pin) *r_passphrase = xtrystrdup (pi->pin); } xfree (*keybuf); *keybuf = arg.unprotected_key; } xfree (pi); return rc; }
static gpg_error_t keyserver_get (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc, struct keyserver_spec *keyserver) { gpg_error_t err = 0; char **pattern; int idx, npat; estream_t datastream; /* Create an array filled with a search pattern for each key. The array is delimited by a NULL entry. */ pattern = xtrycalloc (ndesc+1, sizeof *pattern); if (!pattern) return gpg_error_from_syserror (); for (npat=idx=0; idx < ndesc; idx++) { int quiet = 0; if (desc[idx].mode == KEYDB_SEARCH_MODE_FPR20 || desc[idx].mode == KEYDB_SEARCH_MODE_FPR16) { pattern[npat] = xtrymalloc (2+2*20+1); if (!pattern[npat]) err = gpg_error_from_syserror (); else { strcpy (pattern[npat], "0x"); bin2hex (desc[idx].u.fpr, desc[idx].mode == KEYDB_SEARCH_MODE_FPR20? 20 : 16, pattern[npat]+2); npat++; } } else if(desc[idx].mode == KEYDB_SEARCH_MODE_LONG_KID) { pattern[npat] = xtryasprintf ("0x%08lX%08lX", (ulong)desc[idx].u.kid[0], (ulong)desc[idx].u.kid[1]); if (!pattern[npat]) err = gpg_error_from_syserror (); else npat++; } else if(desc[idx].mode == KEYDB_SEARCH_MODE_SHORT_KID) { pattern[npat] = xtryasprintf ("0x%08lX", (ulong)desc[idx].u.kid[1]); if (!pattern[npat]) err = gpg_error_from_syserror (); else npat++; } else if(desc[idx].mode == KEYDB_SEARCH_MODE_EXACT) { /* FIXME: We don't need this. It is used as a dummy by keyserver_fetch which passes an entire URL. Better use a separate function here. */ pattern[npat] = xtrystrdup ("0x0000000000000000"); if (!pattern[npat]) err = gpg_error_from_syserror (); else { npat++; quiet = 1; } } else if (desc[idx].mode == KEYDB_SEARCH_MODE_NONE) continue; else BUG(); if (err) { for (idx=0; idx < npat; idx++) xfree (pattern[idx]); xfree (pattern); return err; } if (!quiet && keyserver) { if (keyserver->host) log_info (_("requesting key %s from %s server %s\n"), keystr_from_desc (&desc[idx]), keyserver->scheme, keyserver->host); else log_info (_("requesting key %s from %s\n"), keystr_from_desc (&desc[idx]), keyserver->uri); } } err = gpg_dirmngr_ks_get (ctrl, pattern, &datastream); for (idx=0; idx < npat; idx++) xfree (pattern[idx]); xfree (pattern); if (!err) { void *stats_handle; stats_handle = import_new_stats_handle(); /* FIXME: Check whether this comment should be moved to dirmngr. Slurp up all the key data. In the future, it might be nice to look for KEY foo OUTOFBAND and FAILED indicators. It's harmless to ignore them, but ignoring them does make gpg complain about "no valid OpenPGP data found". One way to do this could be to continue parsing this line-by-line and make a temp iobuf for each key. */ import_keys_es_stream (ctrl, datastream, stats_handle, NULL, NULL, opt.keyserver_options.import_options); import_print_stats (stats_handle); import_release_stats_handle (stats_handle); } es_fclose (datastream); return err; }
/* Send the assuan commands pertaining to the pinentry environment. The OPT_* arguments are optional and may be used to override the defaults taken from the current locale. */ gpg_error_t send_pinentry_environment (assuan_context_t ctx, gpg_err_source_t errsource, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env) { gpg_error_t err = 0; #if defined(HAVE_SETLOCALE) char *old_lc = NULL; #endif char *dft_lc = NULL; const char *dft_ttyname; int iterator; const char *name, *assname, *value; int is_default; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { value = session_env_getenv_or_default (session_env, name, NULL); if (!value) continue; if (assname) err = send_one_option (ctx, errsource, assname, value, 0); else { err = send_one_option (ctx, errsource, name, value, 1); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Server too old; can't pass the new envvars. */ } if (err) return err; } dft_ttyname = session_env_getenv_or_default (session_env, "GPG_TTY", &is_default); if (dft_ttyname && !is_default) dft_ttyname = NULL; /* We need the default value. */ /* Send the value for LC_CTYPE. */ #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_CTYPE, ""); #endif if (opt_lc_ctype || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-ctype", opt_lc_ctype ? opt_lc_ctype : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) if (old_lc) { setlocale (LC_CTYPE, old_lc); xfree (old_lc); } #endif if (err) return err; /* Send the value for LC_MESSAGES. */ #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_MESSAGES, ""); #endif if (opt_lc_messages || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-messages", opt_lc_messages ? opt_lc_messages : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) if (old_lc) { setlocale (LC_MESSAGES, old_lc); xfree (old_lc); } #endif if (err) return err; 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, "validation-model")) { int i = gpgsm_parse_validation_model (value); if ( i >= 0 && i <= 1 ) 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 err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; }
/* Send an HTTP request. On success returns an estream object at R_FP. HOSTPORTSTR is only used for diagnostics. If HTTPHOST is not NULL it will be used as HTTP "Host" header. If POST_CB is not NULL a post request is used and that callback is called to allow writing the post data. */ static gpg_error_t send_request (ctrl_t ctrl, const char *request, const char *hostportstr, const char *httphost, unsigned int httpflags, gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value, estream_t *r_fp) { gpg_error_t err; http_session_t session = NULL; http_t http = NULL; int redirects_left = MAX_REDIRECTS; estream_t fp = NULL; char *request_buffer = NULL; *r_fp = NULL; err = http_session_new (&session, NULL); if (err) goto leave; http_session_set_log_cb (session, cert_log_cb); once_more: err = http_open (&http, post_cb? HTTP_REQ_POST : HTTP_REQ_GET, request, httphost, /* fixme: AUTH */ NULL, (httpflags | (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)), ctrl->http_proxy, session, NULL, /*FIXME curl->srvtag*/NULL); if (!err) { fp = http_get_write_ptr (http); /* Avoid caches to get the most recent copy of the key. We set both the Pragma and Cache-Control versions of the header, so we're good with both HTTP 1.0 and 1.1. */ es_fputs ("Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n", fp); if (post_cb) err = post_cb (post_cb_value, http); if (!err) { http_start_data (http); if (es_ferror (fp)) err = gpg_error_from_syserror (); } } if (err) { /* Fixme: After a redirection we show the old host name. */ log_error (_("error connecting to '%s': %s\n"), hostportstr, gpg_strerror (err)); goto leave; } /* Wait for the response. */ dirmngr_tick (ctrl); err = http_wait_response (http); if (err) { log_error (_("error reading HTTP response for '%s': %s\n"), hostportstr, gpg_strerror (err)); goto leave; } if (http_get_tls_info (http, NULL)) { /* Update the httpflags so that a redirect won't fallback to an unencrypted connection. */ httpflags |= HTTP_FLAG_FORCE_TLS; } switch (http_get_status_code (http)) { case 200: err = 0; break; /* Success. */ case 301: case 302: case 307: { const char *s = http_get_header (http, "Location"); log_info (_("URL '%s' redirected to '%s' (%u)\n"), request, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (request_buffer); request_buffer = xtrystrdup (s); if (request_buffer) { request = request_buffer; http_close (http, 0); http = NULL; goto once_more; } err = gpg_error_from_syserror (); } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } goto leave; default: log_error (_("error accessing '%s': http status %u\n"), request, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } /* FIXME: We should register a permanent redirection and whether a host has ever used TLS so that future calls will always use TLS. */ fp = http_get_read_ptr (http); if (!fp) { err = gpg_error (GPG_ERR_BUG); goto leave; } /* Return the read stream and close the HTTP context. */ *r_fp = fp; http_close (http, 1); http = NULL; leave: http_close (http, 0); http_session_release (session); xfree (request_buffer); return err; }
/* Run the encfs tool. */ static gpg_error_t run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd, const char *rawdir, const char *mountpoint, tupledesc_t tuples, unsigned int *r_id) { gpg_error_t err; encfs_parm_t parm; runner_t runner = NULL; int outbound[2] = { -1, -1 }; int inbound[2] = { -1, -1 }; const char *pgmname; const char *argv[10]; pid_t pid = (pid_t)(-1); int idx; (void)ctrl; parm = xtrycalloc (1, sizeof *parm); if (!parm) { err = gpg_error_from_syserror (); goto leave; } parm->cmd = cmd; parm->tuples = ref_tupledesc (tuples); parm->mountpoint = xtrystrdup (mountpoint); if (!parm->mountpoint) { err = gpg_error_from_syserror (); goto leave; } err = runner_new (&runner, "encfs"); if (err) goto leave; err = gnupg_create_inbound_pipe (inbound); if (!err) err = gnupg_create_outbound_pipe (outbound); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); goto leave; } pgmname = ENCFS; idx = 0; argv[idx++] = "-f"; if (opt.verbose) argv[idx++] = "-v"; argv[idx++] = "--stdinpass"; argv[idx++] = "--annotate"; argv[idx++] = rawdir; argv[idx++] = mountpoint; argv[idx++] = NULL; assert (idx <= DIM (argv)); err = gnupg_spawn_process_fd (pgmname, argv, outbound[0], -1, inbound[1], &pid); if (err) { log_error ("error spawning '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } close (outbound[0]); outbound[0] = -1; close ( inbound[1]); inbound[1] = -1; runner_set_fds (runner, inbound[0], outbound[1]); inbound[0] = -1; /* Now owned by RUNNER. */ outbound[1] = -1; /* Now owned by RUNNER. */ runner_set_handler (runner, encfs_handler, encfs_handler_cleanup, parm); parm = NULL; /* Now owned by RUNNER. */ runner_set_pid (runner, pid); pid = (pid_t)(-1); /* The process is now owned by RUNNER. */ err = runner_spawn (runner); if (err) goto leave; *r_id = runner_get_rid (runner); log_info ("running '%s' in the background\n", pgmname); leave: if (inbound[0] != -1) close (inbound[0]); if (inbound[1] != -1) close (inbound[1]); if (outbound[0] != -1) close (outbound[0]); if (outbound[1] != -1) close (outbound[1]); if (pid != (pid_t)(-1)) { gnupg_wait_process (pgmname, pid, 1, NULL); gnupg_release_process (pid); } runner_release (runner); encfs_handler_cleanup (parm); return err; }
/* Parse an expression. The expression syntax is: * * [<lc>] {{<flag>} PROPNAME <op> VALUE [<lc>]} * * 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. <lc> 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 <op> a space after * the <op> is required. A VALUE is terminated by an <lc> unless the * "--" <flag> is used in which case the VALUE spans to the end of the * expression. <op> 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 <flag> 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 { 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; }
/** * ksba_oid_to_str: * @buffer: A BER encoded OID * @length: The length of this OID * * Take a buffer with an object identifier in BER encoding and return * a string representing this OID. We do not use the ASN.1 syntax * here but delimit the arcs with dots, so it is easier to parse in * most cases. This dotted-decimal notation is also known as LDAPOID * string and described in RFC-2251. * * The function returns an empty string for an empty buffer and does * no interpretation of the OID. The caller must free the returned * string using ksba_free() or the function he has registered as a * replacement. * * * Return value: A allocated string or NULL in case of memory problem. **/ char * ksba_oid_to_str (const char *buffer, size_t length) { const unsigned char *buf = buffer; char *string, *p; int n = 0; unsigned long val, valmask; valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1)); /* 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; } 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"); }
/* Store the key for the last successful cache hit. That value is used by agent_get_cache if the requested KEY is given as NULL. NULL may be used to remove that key. */ void agent_store_cache_hit (const char *key) { xfree (last_stored_cache_key); last_stored_cache_key = key? xtrystrdup (key) : NULL; }
/* Mount the container with name FILENAME at MOUNTPOINT. */ gpg_error_t g13_mount_container (ctrl_t ctrl, const char *filename, const char *mountpoint) { gpg_error_t err; dotlock_t lock; void *enckeyblob = NULL; size_t enckeybloblen; void *keyblob = NULL; size_t keybloblen; tupledesc_t tuples = NULL; size_t n; const unsigned char *value; int conttype; unsigned int rid; char *mountpoint_buffer = NULL; /* A quick check to see whether the container exists. */ if (access (filename, R_OK)) return gpg_error_from_syserror (); if (!mountpoint) { mountpoint_buffer = xtrystrdup ("/tmp/g13-XXXXXX"); if (!mountpoint_buffer) return gpg_error_from_syserror (); if (!gnupg_mkdtemp (mountpoint_buffer)) { err = gpg_error_from_syserror (); log_error (_("can't create directory '%s': %s\n"), "/tmp/g13-XXXXXX", gpg_strerror (err)); xfree (mountpoint_buffer); return err; } mountpoint = mountpoint_buffer; } /* Try to take a lock. */ lock = dotlock_create (filename, 0); if (!lock) { xfree (mountpoint_buffer); return gpg_error_from_syserror (); } if (dotlock_take (lock, 0)) { err = gpg_error_from_syserror (); goto leave; } else err = 0; /* Check again that the file exists. */ { struct stat sb; if (stat (filename, &sb)) { err = gpg_error_from_syserror (); goto leave; } } /* Read the encrypted keyblob. */ err = read_keyblob (filename, &enckeyblob, &enckeybloblen); if (err) goto leave; /* Decrypt that keyblob and store it in a tuple descriptor. */ err = decrypt_keyblob (ctrl, enckeyblob, enckeybloblen, &keyblob, &keybloblen); if (err) goto leave; xfree (enckeyblob); enckeyblob = NULL; err = create_tupledesc (&tuples, keyblob, keybloblen); if (!err) keyblob = NULL; else { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) log_error ("unknown keyblob version\n"); goto leave; } if (opt.verbose) dump_keyblob (tuples); value = find_tuple (tuples, KEYBLOB_TAG_CONTTYPE, &n); if (!value || n != 2) conttype = 0; else conttype = (value[0] << 8 | value[1]); if (!be_is_supported_conttype (conttype)) { log_error ("content type %d is not supported\n", conttype); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = be_mount_container (ctrl, conttype, filename, mountpoint, tuples, &rid); if (!err) { err = mountinfo_add_mount (filename, mountpoint, conttype, rid, !!mountpoint_buffer); /* Fixme: What shall we do if this fails? Add a provisional mountinfo entry first and remove it on error? */ if (!err) { char *tmp = percent_plus_escape (mountpoint); if (!tmp) err = gpg_error_from_syserror (); else { g13_status (ctrl, STATUS_MOUNTPOINT, tmp, NULL); xfree (tmp); } } } leave: destroy_tupledesc (tuples); xfree (keyblob); xfree (enckeyblob); dotlock_destroy (lock); xfree (mountpoint_buffer); return err; }
/* * r_sig = (sig-val * (<algo> * (<param_name1> <mpi>) * ... * (<param_namen> <mpi>) * )) * The sexp must be in canonical form. * Fixme: The code is mostly duplicated from cms.c * Note, that <algo> must be given as a stringified OID or the special * string "rsa" which is translated to sha1WithRSAEncryption */ gpg_error_t ksba_certreq_set_sig_val (ksba_certreq_t cr, ksba_const_sexp_t sigval) { const char *s, *endp; unsigned long n; if (!cr) return gpg_error (GPG_ERR_INV_VALUE); s = sigval; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = strtoul (s, (char**)&endp, 10); s = endp; if (!n || *s!=':') return gpg_error (GPG_ERR_INV_SEXP); /* we don't allow empty lengths */ s++; if (n != 7 || memcmp (s, "sig-val", 7)) return gpg_error (GPG_ERR_UNKNOWN_SEXP); s += 7; if (*s != '(') return gpg_error (digitp (s)? GPG_ERR_UNKNOWN_SEXP : GPG_ERR_INV_SEXP); s++; /* break out the algorithm ID */ n = strtoul (s, (char**)&endp, 10); s = endp; if (!n || *s != ':') return gpg_error (GPG_ERR_INV_SEXP); /* we don't allow empty lengths */ s++; xfree (cr->sig_val.algo); if (n==3 && s[0] == 'r' && s[1] == 's' && s[2] == 'a') { /* kludge to allow "rsa" to be passed as algorithm name */ cr->sig_val.algo = xtrystrdup ("1.2.840.113549.1.1.5"); if (!cr->sig_val.algo) return gpg_error (GPG_ERR_ENOMEM); } else { cr->sig_val.algo = xtrymalloc (n+1); if (!cr->sig_val.algo) return gpg_error (GPG_ERR_ENOMEM); memcpy (cr->sig_val.algo, s, n); cr->sig_val.algo[n] = 0; } s += n; /* And now the values - FIXME: For now we only support one */ /* fixme: start loop */ if (*s != '(') return gpg_error (digitp (s)? GPG_ERR_UNKNOWN_SEXP : GPG_ERR_INV_SEXP); s++; n = strtoul (s, (char**)&endp, 10); s = endp; if (!n || *s != ':') return gpg_error (GPG_ERR_INV_SEXP); s++; s += n; /* ignore the name of the parameter */ if (!digitp(s)) return gpg_error (GPG_ERR_UNKNOWN_SEXP); /* but may also be an invalid one */ n = strtoul (s, (char**)&endp, 10); s = endp; if (!n || *s != ':') return gpg_error (GPG_ERR_INV_SEXP); s++; if (n > 1 && !*s) { /* We might have a leading zero due to the way we encode MPIs - this zero should not go into the BIT STRING. */ s++; n--; } xfree (cr->sig_val.value); cr->sig_val.value = xtrymalloc (n); if (!cr->sig_val.value) return gpg_error (GPG_ERR_ENOMEM); memcpy (cr->sig_val.value, s, n); cr->sig_val.valuelen = n; s += n; if ( *s != ')') return gpg_error (GPG_ERR_UNKNOWN_SEXP); /* but may also be an invalid one */ s++; /* fixme: end loop over parameters */ /* we need 2 closing parenthesis */ if ( *s != ')' || s[1] != ')') return gpg_error (GPG_ERR_INV_SEXP); return 0; }
/* 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_SUBMISSION_ADDRESS, TOK_MAILBOX_ONLY, TOK_DANE_ONLY, TOK_AUTH_SUBMIT, TOK_MAX_PENDING, TOK_PROTOCOL_VERSION }; static struct { const char *name; enum tokens token; } keywords[] = { { "submission-address", TOK_SUBMISSION_ADDRESS }, { "mailbox-only", TOK_MAILBOX_ONLY }, { "dane-only", TOK_DANE_ONLY }, { "auth-submit", TOK_AUTH_SUBMIT }, { "max-pending", TOK_MAX_PENDING }, { "protocol-version", TOK_PROTOCOL_VERSION } }; 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_SUBMISSION_ADDRESS: if (!value || !*value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } xfree (flags->submission_address); flags->submission_address = xtrystrdup (value); if (!flags->submission_address) { err = gpg_error_from_syserror (); goto leave; } break; 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; case TOK_PROTOCOL_VERSION: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } flags->protocol_version = 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; }
/* 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. * R_FPR may be NULL if the fingerprint is not needed. */ gpg_error_t wks_list_key (estream_t key, char **r_fpr, uidinfo_list_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 *fpr = NULL; uidinfo_list_t mboxes = NULL; if (r_fpr) *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, 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] */ if (!append_to_uidinfo_list (&mboxes, fields[9], parse_timestamp (fields[5], NULL))) { 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; } if (!fpr) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (r_fpr) { *r_fpr = fpr; fpr = NULL; } *r_mboxes = mboxes; mboxes = NULL; leave: xfree (fpr); free_uidinfo_list (mboxes); xfree (fields); es_free (line); xfree (argv); es_fclose (listing); return err; }
/* Parse the /etc/gnupg/g13tab for user USERNAME. Return a table for the user on success. Return NULL on error and print diagnostics. */ static tab_item_t parse_g13tab (const char *username) { gpg_error_t err; int c, n; char line[512]; char *p; char *fname; estream_t fp; int lnr; char **words = NULL; tab_item_t table = NULL; tab_item_t *tabletail, ti; fname = make_filename (gnupg_sysconfdir (), G13_NAME"tab", NULL); 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; } tabletail = &table; err = 0; lnr = 0; 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; /* Parse the line. The format is * <username> <blockdev> [<label>|"-" [<mountpoint>]] */ xfree (words); words = strtokenize (p, " \t"); if (!words) { err = gpg_error_from_syserror (); break; } if (!words[0] || !words[1]) { log_error (_("file '%s', line %d: %s\n"), fname, lnr, gpg_strerror (GPG_ERR_SYNTAX)); continue; } if (!(*words[1] == '/' || !strncmp (words[1], "PARTUUID=", 9) || !strncmp (words[1], "partuuid=", 9))) { log_error (_("file '%s', line %d: %s\n"), fname, lnr, "Invalid block device syntax"); continue; } if (words[2]) { if (strlen (words[2]) > 16 || strchr (words[2], '/')) { log_error (_("file '%s', line %d: %s\n"), fname, lnr, "Label too long or invalid syntax"); continue; } if (words[3] && *words[3] != '/') { log_error (_("file '%s', line %d: %s\n"), fname, lnr, "Invalid mountpoint syntax"); continue; } } if (strcmp (words[0], username)) continue; /* Skip entries for other usernames! */ ti = xtrymalloc (sizeof *ti + strlen (words[1])); if (!ti) { err = gpg_error_from_syserror (); break; } ti->next = NULL; ti->label = NULL; ti->mountpoint = NULL; strcpy (ti->blockdev, *words[1]=='/'? words[1] : words[1]+9); if (words[2]) { if (strcmp (words[2], "-") && !(ti->label = xtrystrdup (words[2]))) { err = gpg_error_from_syserror (); xfree (ti); break; } if (words[3] && !(ti->mountpoint = xtrystrdup (words[3]))) { err = gpg_error_from_syserror (); xfree (ti->label); xfree (ti); break; } } *tabletail = ti; tabletail = &ti->next; } if (!err && !es_feof (fp)) err = gpg_error_from_syserror (); if (err) log_error (_("error reading '%s', line %d: %s\n"), fname, lnr, gpg_strerror (err)); leave: xfree (words); es_fclose (fp); xfree (fname); if (err) { release_tab_items (table); return NULL; } return table; }
/* Construct an OCSP request, send it to the configured OCSP responder and parse the response. On success the OCSP context may be used to further process the reponse. */ static gpg_error_t do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_md_hd_t md, const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert) { gpg_error_t err; unsigned char *request, *response; size_t requestlen, responselen; http_t http; ksba_ocsp_response_status_t response_status; const char *t; int redirects_left = 2; char *free_this = NULL; (void)ctrl; if (opt.disable_http) { log_error (_("OCSP request not possible due to disabled HTTP\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } err = ksba_ocsp_add_target (ocsp, cert, issuer_cert); if (err) { log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err)); return err; } { size_t n; unsigned char nonce[32]; n = ksba_ocsp_set_nonce (ocsp, NULL, 0); if (n > sizeof nonce) n = sizeof nonce; gcry_create_nonce (nonce, n); ksba_ocsp_set_nonce (ocsp, nonce, n); } err = ksba_ocsp_build_request (ocsp, &request, &requestlen); if (err) { log_error (_("error building OCSP request: %s\n"), gpg_strerror (err)); return err; } once_more: err = http_open (&http, HTTP_REQ_POST, url, NULL, (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0), opt.http_proxy, NULL, NULL, NULL); if (err) { log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } es_fprintf (http_get_write_ptr (http), "Content-Type: application/ocsp-request\r\n" "Content-Length: %lu\r\n", (unsigned long)requestlen ); http_start_data (http); if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1) { err = gpg_error_from_errno (errno); log_error ("error sending request to '%s': %s\n", url, strerror (errno)); http_close (http, 0); xfree (request); xfree (free_this); return err; } xfree (request); request = NULL; err = http_wait_response (http); if (err || http_get_status_code (http) != 200) { if (err) log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); else { switch (http_get_status_code (http)) { case 301: case 302: { const char *s = http_get_header (http, "Location"); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (free_this); url = NULL; free_this = xtrystrdup (s); if (!free_this) err = gpg_error_from_errno (errno); else { url = free_this; http_close (http, 0); goto once_more; } } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } break; default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); break; } } http_close (http, 0); xfree (free_this); return err; } err = read_response (http_get_read_ptr (http), &response, &responselen); http_close (http, 0); if (err) { log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } err = ksba_ocsp_parse_response (ocsp, response, responselen, &response_status); if (err) { log_error (_("error parsing OCSP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (response); xfree (free_this); return err; } switch (response_status) { case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break; case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break; case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break; case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break; case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break; case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break; case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break; case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break; case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break; default: t = "[unknown status]"; break; } if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS) { if (opt.verbose) log_info (_("OCSP responder at '%s' status: %s\n"), url, t); err = ksba_ocsp_hash_response (ocsp, response, responselen, HASH_FNC, md); if (err) log_error (_("hashing the OCSP response for '%s' failed: %s\n"), url, gpg_strerror (err)); } else { log_error (_("OCSP responder at '%s' status: %s\n"), url, t); err = gpg_error (GPG_ERR_GENERAL); } xfree (response); xfree (free_this); return err; }
/* Get the key from URL which is expected to specify a http style scheme. On success R_FP has an open stream to read the data. */ gpg_error_t ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; http_session_t session = NULL; http_t http = NULL; int redirects_left = MAX_REDIRECTS; estream_t fp = NULL; char *request_buffer = NULL; err = http_session_new (&session, NULL); if (err) goto leave; http_session_set_log_cb (session, cert_log_cb); *r_fp = NULL; once_more: err = http_open (&http, HTTP_REQ_GET, url, /* httphost */ NULL, /* fixme: AUTH */ NULL, 0, /* fixme: proxy*/ NULL, session, NULL, /*FIXME curl->srvtag*/NULL); if (!err) { fp = http_get_write_ptr (http); /* Avoid caches to get the most recent copy of the key. We set both the Pragma and Cache-Control versions of the header, so we're good with both HTTP 1.0 and 1.1. */ es_fputs ("Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n", fp); http_start_data (http); if (es_ferror (fp)) err = gpg_error_from_syserror (); } if (err) { /* Fixme: After a redirection we show the old host name. */ log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); goto leave; } /* Wait for the response. */ dirmngr_tick (ctrl); err = http_wait_response (http); if (err) { log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); goto leave; } switch (http_get_status_code (http)) { case 200: err = 0; break; /* Success. */ case 301: case 302: case 307: { const char *s = http_get_header (http, "Location"); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (request_buffer); request_buffer = xtrystrdup (s); if (request_buffer) { url = request_buffer; http_close (http, 0); http = NULL; goto once_more; } err = gpg_error_from_syserror (); } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } goto leave; default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } fp = http_get_read_ptr (http); if (!fp) { err = gpg_error (GPG_ERR_BUG); goto leave; } /* Return the read stream and close the HTTP context. */ *r_fp = fp; http_close (http, 1); http = NULL; leave: http_close (http, 0); http_session_release (session); xfree (request_buffer); return err; }
/* Add the host AI under the NAME into the HOSTTABLE. If PORT is not zero, it specifies which port to use to talk to the host for PROTOCOL. If NAME specifies a pool (as indicated by IS_POOL), update the given reference table accordingly. */ static void add_host (const char *name, int is_pool, const dns_addrinfo_t ai, enum ks_protocol protocol, unsigned short port) { gpg_error_t tmperr; char *tmphost; int idx, tmpidx; hostinfo_t host; int i; idx = find_hostinfo (name); host = hosttable[idx]; if (is_pool) { /* For a pool immediately convert the address to a string. */ tmperr = resolve_dns_addr (ai->addr, ai->addrlen, (DNS_NUMERICHOST | DNS_WITHBRACKET), &tmphost); } else if (!is_ip_address (name)) { /* This is a hostname. Use the name as given without going * through resolve_dns_addr. */ tmphost = xtrystrdup (name); if (!tmphost) tmperr = gpg_error_from_syserror (); else tmperr = 0; } else { /* Do a PTR lookup on AI. If a name was not found the function * returns the numeric address (with brackets). */ tmperr = resolve_dns_addr (ai->addr, ai->addrlen, DNS_WITHBRACKET, &tmphost); } if (tmperr) { log_info ("resolve_dns_addr failed while checking '%s': %s\n", name, gpg_strerror (tmperr)); } else if (host->pool_len + 1 >= MAX_POOL_SIZE) { log_error ("resolve_dns_addr for '%s': '%s'" " [index table full - ignored]\n", name, tmphost); } else { if (!is_pool && is_ip_address (name)) /* Update the original entry. */ tmpidx = idx; else tmpidx = find_hostinfo (tmphost); log_info ("resolve_dns_addr for '%s': '%s'%s\n", name, tmphost, tmpidx == -1? "" : " [already known]"); if (tmpidx == -1) /* Create a new entry. */ tmpidx = create_new_hostinfo (tmphost); if (tmpidx == -1) { log_error ("map_host for '%s' problem: %s - '%s' [ignored]\n", name, strerror (errno), tmphost); } else /* Set or update the entry. */ { if (port) hosttable[tmpidx]->port[protocol] = port; if (ai->family == AF_INET6) { hosttable[tmpidx]->v6 = 1; } else if (ai->family == AF_INET) { hosttable[tmpidx]->v4 = 1; } else BUG (); /* If we updated the main entry, we're done. */ if (idx == tmpidx) goto leave; /* If we updated an existing entry, we're done. */ for (i = 0; i < host->pool_len; i++) if (host->pool[i] == tmpidx) goto leave; /* Otherwise, we need to add it to the pool. Check if there is space. */ if (host->pool_len + 1 > host->pool_size) { int *new_pool; size_t new_size; if (host->pool_size == 0) new_size = 4; else new_size = host->pool_size * 2; new_pool = xtryrealloc (host->pool, new_size * sizeof *new_pool); if (new_pool == NULL) goto leave; host->pool = new_pool; host->pool_size = new_size; } /* Finally, add it. */ log_assert (host->pool_len < host->pool_size); host->pool[host->pool_len++] = tmpidx; } } leave: xfree (tmphost); }
/* Callback function to try the unprotection from the passpharse query code. */ static int try_unprotect_cb (struct pin_entry_info_s *pi) { struct try_unprotect_arg_s *arg = pi->check_cb_arg; 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 (arg->protected_key, pi->pin, protected_at, &arg->unprotected_key, &dummy); if (err) return err; if (!opt.max_passphrase_days || arg->ctrl->in_passwd) return 0; /* No regular passphrase change required. */ if (!*protected_at) { /* No protection date known - must force passphrase change. */ desc = xtrystrdup (_("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 (_("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 (arg->ctrl, desc, _("Change passphrase"), NULL, 0); if (!err) arg->change_required = 1; } else { err = agent_get_confirmation (arg->ctrl, desc, _("Change passphrase"), _("I'll change it later"), 0); if (!err) arg->change_required = 1; else if (gpg_err_code (err) == GPG_ERR_CANCELED) err = 0; } xfree (desc); } return 0; }
/* Map the host name NAME to the actual to be used host name. This * allows us to manage round robin DNS names. We use our own strategy * to choose one of the hosts. For example we skip those hosts which * failed for some time and we stick to one host for a time * independent of DNS retry times. If FORCE_RESELECT is true a new * host is always selected. If SRVTAG is NULL no service record * lookup will be done, if it is set that service name is used. The * selected host is stored as a malloced string at R_HOST; on error * NULL is stored. If we know the port used by the selected host from * a service record, a string representation is written to R_PORTSTR, * otherwise it is left untouched. If R_HTTPFLAGS is not NULL it will * receive flags which are to be passed to http_open. If R_HTTPHOST * is not NULL a malloced name of the host is stored there; this might * be different from R_HOST in case it has been selected from a * pool. */ static gpg_error_t map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect, enum ks_protocol protocol, char **r_host, char *r_portstr, unsigned int *r_httpflags, char **r_httphost) { gpg_error_t err = 0; hostinfo_t hi; int idx; dns_addrinfo_t aibuf, ai; int is_pool; int new_hosts = 0; char *cname; *r_host = NULL; if (r_httpflags) *r_httpflags = 0; if (r_httphost) *r_httphost = NULL; /* No hostname means localhost. */ if (!name || !*name) { *r_host = xtrystrdup ("localhost"); return *r_host? 0 : gpg_error_from_syserror (); } /* See whether the host is in our table. */ idx = find_hostinfo (name); if (idx == -1) { idx = create_new_hostinfo (name); if (idx == -1) return gpg_error_from_syserror (); hi = hosttable[idx]; hi->onion = is_onion_address (name); } else hi = hosttable[idx]; is_pool = hi->pool != NULL; if (srvtag && !is_ip_address (name) && ! hi->onion && ! (hi->did_srv_lookup & 1 << protocol)) { struct srventry *srvs; unsigned int srvscount; /* Check for SRV records. */ err = get_dns_srv (name, srvtag, NULL, &srvs, &srvscount); if (err) { if (gpg_err_code (err) == GPG_ERR_ECONNREFUSED) tor_not_running_p (ctrl); return err; } if (srvscount > 0) { int i; if (! is_pool) is_pool = srvscount > 1; for (i = 0; i < srvscount; i++) { err = resolve_dns_name (srvs[i].target, 0, AF_UNSPEC, SOCK_STREAM, &ai, &cname); if (err) continue; dirmngr_tick (ctrl); add_host (name, is_pool, ai, protocol, srvs[i].port); new_hosts = 1; } xfree (srvs); } hi->did_srv_lookup |= 1 << protocol; } if (! hi->did_a_lookup && ! hi->onion) { /* Find all A records for this entry and put them into the pool list - if any. */ err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname); if (err) { log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err)); err = 0; } else { /* First figure out whether this is a pool. For a pool we use a different strategy than for a plain server: We use the canonical name of the pool as the virtual host along with the IP addresses. If it is not a pool, we use the specified name. */ if (! is_pool) is_pool = arecords_is_pool (aibuf); if (is_pool && cname) { hi->cname = cname; cname = NULL; } for (ai = aibuf; ai; ai = ai->next) { if (ai->family != AF_INET && ai->family != AF_INET6) continue; if (opt.disable_ipv4 && ai->family == AF_INET) continue; if (opt.disable_ipv6 && ai->family == AF_INET6) continue; dirmngr_tick (ctrl); add_host (name, is_pool, ai, 0, 0); new_hosts = 1; } hi->did_a_lookup = 1; } xfree (cname); free_dns_addrinfo (aibuf); } if (new_hosts) hostinfo_sort_pool (hi); if (hi->pool) { /* Deal with the pool name before selecting a host. */ if (r_httphost) { *r_httphost = xtrystrdup (hi->cname? hi->cname : hi->name); if (!*r_httphost) return gpg_error_from_syserror (); } /* If the currently selected host is now marked dead, force a re-selection . */ if (force_reselect) hi->poolidx = -1; else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead) hi->poolidx = -1; /* Select a host if needed. */ if (hi->poolidx == -1) { hi->poolidx = select_random_host (hi); if (hi->poolidx == -1) { log_error ("no alive host found in pool '%s'\n", name); if (r_httphost) { xfree (*r_httphost); *r_httphost = NULL; } return gpg_error (GPG_ERR_NO_KEYSERVER); } } assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size); hi = hosttable[hi->poolidx]; assert (hi); } else if (r_httphost && is_ip_address (hi->name)) { /* This is a numerical IP address and not a pool. We want to * find the canonical name so that it can be used in the HTTP * Host header. Fixme: We should store that name in the * hosttable. */ char *host; err = resolve_dns_name (hi->name, 0, 0, SOCK_STREAM, &aibuf, NULL); if (!err) { for (ai = aibuf; ai; ai = ai->next) { if ((!opt.disable_ipv6 && ai->family == AF_INET6) || (!opt.disable_ipv4 && ai->family == AF_INET)) { err = resolve_dns_addr (ai->addr, ai->addrlen, 0, &host); if (!err) { /* Okay, we return the first found name. */ *r_httphost = host; break; } } } } free_dns_addrinfo (aibuf); } if (hi->dead) { log_error ("host '%s' marked as dead\n", hi->name); if (r_httphost) { xfree (*r_httphost); *r_httphost = NULL; } return gpg_error (GPG_ERR_NO_KEYSERVER); } if (r_httpflags) { /* If the hosttable does not indicate that a certain host supports IPv<N>, we explicit set the corresponding http flags. The reason for this is that a host might be listed in a pool as not v6 only but actually support v6 when later the name is resolved by our http layer. */ if (!hi->v4) *r_httpflags |= HTTP_FLAG_IGNORE_IPv4; if (!hi->v6) *r_httpflags |= HTTP_FLAG_IGNORE_IPv6; /* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion addresses because the http module detects this itself. This also allows us to use an onion address without Tor mode being enabled. */ } *r_host = xtrystrdup (hi->name); if (!*r_host) { err = gpg_error_from_syserror (); if (r_httphost) { xfree (*r_httphost); *r_httphost = NULL; } return err; } if (hi->port[protocol]) snprintf (r_portstr, 6 /* five digits and the sentinel */, "%hu", hi->port[protocol]); return 0; }
/* Issue an SCD SERIALNO openpgp command and if SERIALNO is not NULL ask the user to insert the requested card. */ gpg_error_t select_openpgp (const char *serialno) { gpg_error_t err; /* Send the serialno command to initialize the connection. Without a given S/N we don't care about the data returned. If the card has already been initialized, this is a very fast command. We request the openpgp card because that is what we expect. Note that an opt.limit_card_insert_tries of 1 means: No tries at all whereas 0 means do not limit the number of tries. Due to the sue of a pinentry prompt with a cancel option we use it here in a boolean sense. */ if (!serialno || opt.limit_card_insert_tries == 1) err = assuan_transact (agent_ctx, "SCD SERIALNO openpgp", NULL, NULL, NULL, NULL, NULL, NULL); else { char *this_sn = NULL; char *desc; int ask; char *want_sn; char *p; want_sn = xtrystrdup (serialno); if (!want_sn) return gpg_error_from_syserror (); p = strchr (want_sn, '/'); if (p) *p = 0; do { ask = 0; err = assuan_transact (agent_ctx, "SCD SERIALNO openpgp", NULL, NULL, NULL, NULL, get_serialno_cb, &this_sn); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) ask = 1; else if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) ask = 2; else if (err) ; else if (this_sn) { if (strcmp (want_sn, this_sn)) ask = 2; } xfree (this_sn); this_sn = NULL; if (ask) { char *formatted = NULL; char *ocodeset = i18n_switchto_utf8 (); if (!strncmp (want_sn, "D27600012401", 12) && strlen (want_sn) == 32 ) formatted = xtryasprintf ("(%.4s) %.8s", want_sn + 16, want_sn + 20); err = 0; desc = xtryasprintf ("%s:\n\n" " \"%s\"", ask == 1 ? _("Please insert the card with serial number") : _("Please remove the current card and " "insert the one with serial number"), formatted? formatted : want_sn); if (!desc) err = gpg_error_from_syserror (); xfree (formatted); i18n_switchback (ocodeset); if (!err) err = gpg_agent_get_confirmation (desc); xfree (desc); } } while (ask && !err); xfree (want_sn); } return err; }
/* Map the host name NAME to the actual to be used host name. This allows us to manage round robin DNS names. We use our own strategy to choose one of the hosts. For example we skip those hosts which failed for some time and we stick to one host for a time independent of DNS retry times. If FORCE_RESELECT is true a new host is always selected. The selected host is stored as a malloced string at R_HOST; on error NULL is stored. If R_HTTPFLAGS is not NULL it will receive flags which are to be passed to http_open. If R_POOLNAME is not NULL a malloced name of the pool is stored or NULL if it is not a pool. */ static gpg_error_t map_host (ctrl_t ctrl, const char *name, int force_reselect, char **r_host, unsigned int *r_httpflags, char **r_poolname) { gpg_error_t err = 0; hostinfo_t hi; int idx; *r_host = NULL; if (r_httpflags) *r_httpflags = 0; if (r_poolname) *r_poolname = NULL; /* No hostname means localhost. */ if (!name || !*name) { *r_host = xtrystrdup ("localhost"); return *r_host? 0 : gpg_error_from_syserror (); } /* See whether the host is in our table. */ idx = find_hostinfo (name); if (idx == -1) { /* We never saw this host. Allocate a new entry. */ struct addrinfo hints, *aibuf, *ai; int *reftbl; size_t reftblsize; int refidx; int is_pool = 0; reftblsize = 100; reftbl = xtrymalloc (reftblsize * sizeof *reftbl); if (!reftbl) return gpg_error_from_syserror (); refidx = 0; idx = create_new_hostinfo (name); if (idx == -1) { err = gpg_error_from_syserror (); xfree (reftbl); return err; } hi = hosttable[idx]; /* Find all A records for this entry and put them into the pool list - if any. */ memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; /* We can't use the 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. */ if (!getaddrinfo (name, NULL, &hints, &aibuf)) { int n_v6, n_v4; /* First figure out whether this is a pool. For a pool we use a different strategy than for a plains erver: We use the canonical name of the pool as the virtual host along with the IP addresses. If it is not a pool, we use the specified name. */ n_v6 = n_v4 = 0; for (ai = aibuf; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET6) n_v6++; else if (ai->ai_family != AF_INET) n_v4++; } if (n_v6 > 1 || n_v4 > 1) is_pool = 1; if (is_pool && aibuf->ai_canonname) hi->cname = xtrystrdup (aibuf->ai_canonname); for (ai = aibuf; ai; ai = ai->ai_next) { char tmphost[NI_MAXHOST + 2]; int tmpidx; int is_numeric; int ec; int i; if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; dirmngr_tick (ctrl); if (!is_pool && !is_ip_address (name)) { /* This is a hostname but not a pool. Use the name as given without going through getnameinfo. */ if (strlen (name)+1 > sizeof tmphost) { ec = EAI_SYSTEM; gpg_err_set_errno (EINVAL); } else { ec = 0; strcpy (tmphost, name); } is_numeric = 0; } else ec = my_getnameinfo (ai, tmphost, sizeof tmphost, 0, &is_numeric); if (ec) { log_info ("getnameinfo failed while checking '%s': %s\n", name, gai_strerror (ec)); } else if (refidx+1 >= reftblsize) { log_error ("getnameinfo returned for '%s': '%s'" " [index table full - ignored]\n", name, tmphost); } else { tmpidx = find_hostinfo (tmphost); log_info ("getnameinfo returned for '%s': '%s'%s\n", name, tmphost, tmpidx == -1? "" : " [already known]"); if (tmpidx == -1) /* Create a new entry. */ tmpidx = create_new_hostinfo (tmphost); if (tmpidx == -1) { log_error ("map_host for '%s' problem: %s - '%s'" " [ignored]\n", name, strerror (errno), tmphost); } else /* Set or update the entry. */ { char *ipaddr = NULL; if (!is_numeric) { ec = my_getnameinfo (ai, tmphost, sizeof tmphost, 1, &is_numeric); if (!ec && !(ipaddr = xtrystrdup (tmphost))) ec = EAI_SYSTEM; if (ec) log_info ("getnameinfo failed: %s\n", gai_strerror (ec)); } if (ai->ai_family == AF_INET6) { hosttable[tmpidx]->v6 = 1; xfree (hosttable[tmpidx]->v6addr); hosttable[tmpidx]->v6addr = ipaddr; } else if (ai->ai_family == AF_INET) { hosttable[tmpidx]->v4 = 1; xfree (hosttable[tmpidx]->v4addr); hosttable[tmpidx]->v4addr = ipaddr; } else BUG (); for (i=0; i < refidx; i++) if (reftbl[i] == tmpidx) break; if (!(i < refidx) && tmpidx != idx) reftbl[refidx++] = tmpidx; } } } freeaddrinfo (aibuf); } reftbl[refidx] = -1; if (refidx && is_pool) { assert (!hi->pool); hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl); if (!hi->pool) { err = gpg_error_from_syserror (); log_error ("shrinking index table in map_host failed: %s\n", gpg_strerror (err)); xfree (reftbl); return err; } qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool); } else xfree (reftbl); } hi = hosttable[idx]; if (hi->pool) { /* Deal with the pool name before selecting a host. */ if (r_poolname && hi->cname) { *r_poolname = xtrystrdup (hi->cname); if (!*r_poolname) return gpg_error_from_syserror (); } /* If the currently selected host is now marked dead, force a re-selection . */ if (force_reselect) hi->poolidx = -1; else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead) hi->poolidx = -1; /* Select a host if needed. */ if (hi->poolidx == -1) { hi->poolidx = select_random_host (hi->pool); if (hi->poolidx == -1) { log_error ("no alive host found in pool '%s'\n", name); if (r_poolname) { xfree (*r_poolname); *r_poolname = NULL; } return gpg_error (GPG_ERR_NO_KEYSERVER); } } assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size); hi = hosttable[hi->poolidx]; assert (hi); } if (hi->dead) { log_error ("host '%s' marked as dead\n", hi->name); if (r_poolname) { xfree (*r_poolname); *r_poolname = NULL; } return gpg_error (GPG_ERR_NO_KEYSERVER); } if (r_httpflags) { /* If the hosttable does not indicate that a certain host supports IPv<N>, we explicit set the corresponding http flags. The reason for this is that a host might be listed in a pool as not v6 only but actually support v6 when later the name is resolved by our http layer. */ if (!hi->v4) *r_httpflags |= HTTP_FLAG_IGNORE_IPv4; if (!hi->v6) *r_httpflags |= HTTP_FLAG_IGNORE_IPv6; } *r_host = xtrystrdup (hi->name); if (!*r_host) { err = gpg_error_from_syserror (); if (r_poolname) { xfree (*r_poolname); *r_poolname = NULL; } return err; } return 0; }