diff --git a/agent/call-scd.c b/agent/call-scd.c index 0fc0d3f00..c58199539 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1,1164 +1,1164 @@ /* 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #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; assuan_context_t ctx; /* NULL or session context for the SCdaemon used with this connection. */ unsigned int in_use: 1; /* CTX is in use. */ unsigned int invalid:1; /* CTX is invalid, should be released. */ }; /* 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. */ /* The next fields are used by inq_writekey_parm. */ const unsigned char *keydata; size_t keydatalen; }; static int start_scd (ctrl_t ctrl) { return daemon_start (DAEMON_SCD, ctrl); } static gpg_error_t unlock_scd (ctrl_t ctrl, gpg_error_t err) { return daemon_unlock (DAEMON_SCD, ctrl, err); } static assuan_context_t daemon_ctx (ctrl_t ctrl) { return daemon_type_ctx (DAEMON_SCD, ctrl); } /* This handler is a helper for pincache_put_cb but may also be called * directly for that status code with ARGS being the arguments after * the status keyword (and with white space removed). */ static gpg_error_t handle_pincache_put (const char *args) { gpg_error_t err; const char *s, *key, *pin; char *keybuf = NULL; size_t keylen; key = s = args; while (*s && !spacep (s)) s++; keylen = s - key; if (keylen < 3) { /* At least we need 2 slashes and slot number. */ log_error ("%s: ignoring invalid key\n", __func__); err = 0; goto leave; } keybuf = xtrymalloc (keylen+1); if (!keybuf) { err = gpg_error_from_syserror (); goto leave; } memcpy (keybuf, key, keylen); keybuf[keylen] = 0; key = keybuf; while (spacep (s)) s++; pin = s; if (!*pin) { /* No value - flush the cache. The cache module knows aboput * the structure of the key to flush only parts. */ log_debug ("%s: flushing cache '%s'\n", __func__, key); agent_put_cache (NULL, key, CACHE_MODE_PIN, NULL, -1); err = 0; goto leave; } log_debug ("%s: caching '%s'->'%s'\n", __func__, key, pin); agent_put_cache (NULL, key, CACHE_MODE_PIN, pin, -1); err = 0; leave: xfree (keybuf); return err; } /* This status callback is to intercept the PINCACHE_PUT status * messages. OPAQUE is not used. */ static gpg_error_t pincache_put_cb (void *opaque, const char *line) { const char *s; (void)opaque; s = has_leading_keyword (line, "PINCACHE_PUT"); if (s) return handle_pincache_put (s); else return 0; } /* Handle a PINCACHE_GET inquiry. ARGS are the arguments of the * inquiry which should be a single string with the key for the cached * value. CTX is the Assuan handle. */ static gpg_error_t handle_pincache_get (const char *args, assuan_context_t ctx) { gpg_error_t err; const char *key; char *pin = NULL; log_debug ("%s: enter '%s'\n", __func__, args); key = args; if (strlen (key) < 5) { /* We need at least 2 slashes, one slot number and two 1 byte strings.*/ err = gpg_error (GPG_ERR_INV_REQUEST); log_debug ("%s: key too short\n", __func__); goto leave; } pin = agent_get_cache (NULL, key, CACHE_MODE_PIN); if (!pin || !*pin) { xfree (pin); err = 0; /* Not found is indicated by sending no data back. */ log_debug ("%s: not cached\n", __func__); goto leave; } log_debug ("%s: cache returned '%s'\n", __func__, pin); err = assuan_send_data (ctx, pin, strlen (pin)); leave: xfree (pin); return err; } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct learn_parm_s *parm = opaque; gpg_error_t err = 0; 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 == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) err = handle_pincache_put (line); else if (keywordlen && *line) { parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); } return err; } /* 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 (daemon_ctx (ctrl), "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) { gpg_error_t err = 0; 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; } else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) err = handle_pincache_put (line); return err; } /* Return the serial number of the card or an appropriate error. The * serial number is returned as a hexstring. If the serial number is * not required by the caller R_SERIALNO can be NULL; this might be * useful to test whether a card is available. */ 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 --all"); else snprintf (line, DIM(line), "SERIALNO --demand=%s", demand); rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); return unlock_scd (ctrl, rc); } if (r_serialno) *r_serialno = serialno; else xfree (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; 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 ((s = has_leading_keyword (line, "PINCACHE_GET"))) { rc = handle_pincache_get (s, parm->ctx); } 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 ""; } } /* 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; /* FIXME: In the mdalgo case (INDATA,INDATALEN) might be long and * thus we can't convey it on a single Assuan line. */ if (!mdalgo) gpg_error (GPG_ERR_NOT_IMPLEMENTED); if (indatalen*2 + 50 > DIM(line)) return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, pincache_put_cb, NULL); if (rc) return unlock_scd (ctrl, rc); init_membuf (&data, 1024); inqparm.ctx = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 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 (daemon_ctx (ctrl), line, put_membuf_cb, &data, inq_needpin, &inqparm, pincache_put_cb, NULL); 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) { gpg_error_t err = 0; int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } else if ((s=has_leading_keyword (line, "PINCACHE_PUT"))) err = handle_pincache_put (line); return err; } /* 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 (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); } init_membuf (&data, 1024); inqparm.ctx = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; snprintf (line, DIM(line), "PKDECRYPT %s", keyid); rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, inq_needpin, &inqparm, padding_info_cb, r_padding); 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 (daemon_ctx (ctrl), line, put_membuf_cb, &data, NULL, NULL, pincache_put_cb, 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); } struct readkey_status_parm_s { char *keyref; }; static gpg_error_t readkey_status_cb (void *opaque, const char *line) { struct readkey_status_parm_s *parm = opaque; gpg_error_t err = 0; char *line_buffer = NULL; const char *s; if ((s = has_leading_keyword (line, "KEYPAIRINFO")) && !parm->keyref) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] * * Here we only need the keyref. We use only the first received * KEYPAIRINFO; it is possible to receive several if there are * two or more active cards with the same key. */ - char *fields[2]; + const char *fields[2]; int nfields; line_buffer = xtrystrdup (line); if (!line_buffer) { err = gpg_error_from_syserror (); goto leave; } if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2) goto leave; /* Not enough args; invalid status line - skip. */ parm->keyref = xtrystrdup (fields[1]); if (!parm->keyref) err = gpg_error_from_syserror (); } else err = pincache_put_cb (NULL, line); leave: xfree (line_buffer); return err; } /* Read a key with ID (keyref or keygrip) and return it in a malloced * buffer pointed to by R_BUF as a valid S-expression. If R_KEYREF is * not NULL the keyref is stored there. */ int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf, char **r_keyref) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; struct readkey_status_parm_s parm; memset (&parm, 0, sizeof parm); *r_buf = NULL; if (r_keyref) *r_keyref = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line), "READKEY%s -- %s", r_keyref? " --info":"", id); rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, NULL, NULL, readkey_status_cb, &parm); if (rc) { xfree (get_membuf (&data, &len)); xfree (parm.keyref); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) { xfree (parm.keyref); return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); } if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (parm.keyref); xfree (*r_buf); *r_buf = NULL; return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } if (r_keyref) *r_keyref = parm.keyref; else xfree (parm.keyref); 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); } /* Call scd to write a key to a card under the id KEYREF. */ gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; (void)serialno; /* NULL or a number to check for the correct card. * But is is not implemented. */ err = start_scd (ctrl); if (err) return err; snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", keyref); parms.ctx = daemon_ctx (ctrl); parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; parms.getpin_cb_desc= NULL; parms.passthru = 0; parms.keydata = keydata; parms.keydatalen = keydatalen; err = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, inq_writekey_parms, &parms, pincache_put_cb, NULL); return unlock_scd (ctrl, err); } /* 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) { gpg_error_t err = 0; 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; } else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) err = handle_pincache_put (line); return err; } /* 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, const char *keygrip) { 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); if (keygrip == NULL) stpcpy (stpcpy (line, "GETATTR "), name); else snprintf (line, sizeof line, "GETATTR %s %s", name, keygrip); err = start_scd (ctrl); if (err) return err; err = assuan_transact (daemon_ctx (ctrl), 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_keyinfo_parm_s { int error; struct card_key_info_s *list; }; /* Callback function for agent_card_keylist. */ static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen)) { const char *s; int n; struct card_key_info_s *keyinfo; struct card_key_info_s **l_p = &parm->list; while ((*l_p)) l_p = &(*l_p)->next; keyinfo = xtrycalloc (1, sizeof *keyinfo); if (!keyinfo) { alloc_error: if (!parm->error) parm->error = gpg_error_from_syserror (); return 0; } for (n=0,s=line; hexdigitp (s); s++, n++) ; if (n != 40) { parm_error: if (!parm->error) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); return 0; } memcpy (keyinfo->keygrip, line, 40); keyinfo->keygrip[40] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (*line++ != 'T') goto parm_error; if (!*line) goto parm_error; while (spacep (line)) line++; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n) goto parm_error; keyinfo->serialno = xtrymalloc (n+1); if (!keyinfo->serialno) goto alloc_error; memcpy (keyinfo->serialno, line, n); keyinfo->serialno[n] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (!*line) goto parm_error; keyinfo->idstr = xtrystrdup (line); if (!keyinfo->idstr) goto alloc_error; *l_p = keyinfo; } else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) err = handle_pincache_put (line); return err; } void agent_card_free_keyinfo (struct card_key_info_s *l) { struct card_key_info_s *l_next; for (; l; l = l_next) { l_next = l->next; xfree (l->serialno); xfree (l->idstr); xfree (l); } } /* Call the scdaemon to check if a key of KEYGRIP is available, or retrieve list of available keys on cards. With CAP, we can limit keys with specified capability. On success, the allocated structure is stored at RESULT. On error, an error code is returned and NULL is stored at RESULT. */ gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip, int cap, struct card_key_info_s **result) { int err; struct card_keyinfo_parm_s parm; char line[ASSUAN_LINELENGTH]; char *list_option; *result = NULL; switch (cap) { case 0: list_option = "--list"; break; case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break; case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break; case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break; default: return gpg_error (GPG_ERR_INV_VALUE); } memset (&parm, 0, sizeof parm); snprintf (line, sizeof line, "KEYINFO %s", keygrip ? keygrip : list_option); err = start_scd (ctrl); if (err) return err; err = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, card_keyinfo_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else agent_card_free_keyinfo (parm.list); return unlock_scd (ctrl, err); } static gpg_error_t pass_status_thru (void *opaque, const char *line) { gpg_error_t err = 0; 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++; /* We do not want to pass PINCACHE_PUT through. */ if (!strcmp (keyword, "PINCACHE_PUT")) err = handle_pincache_put (line); else assuan_write_status (ctx, keyword, line); } return err; } 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 = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = NULL; inqparm.passthru = assuan_context; inqparm.keydata = NULL; inqparm.keydatalen = 0; saveflag = assuan_get_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS); assuan_set_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS, 1); rc = assuan_transact (daemon_ctx (ctrl), cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); assuan_set_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS, saveflag); if (rc) { return unlock_scd (ctrl, rc); } return unlock_scd (ctrl, 0); } diff --git a/agent/command.c b/agent/command.c index cb4f22bd6..5073aa700 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,3981 +1,3981 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001-2011 Free Software Foundation, Inc. * Copyright (C) 2001-2013 Werner Koch * 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 . */ /* FIXME: we should not use the default assuan buffering but setup some buffering in secure mempory to protect session keys etc. */ #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include #include "../common/i18n.h" #include "cvt-openpgp.h" #include "../common/ssh-utils.h" #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum allowed size of the inquired ciphertext. */ #define MAXLEN_CIPHERTEXT 4096 /* Maximum allowed size of the key parameters. */ #define MAXLEN_KEYPARAM 1024 /* Maximum allowed size of key data as used in inquiries (bytes). */ #define MAXLEN_KEYDATA 8192 /* Maximum length of a secret to store under one key. */ #define MAXLEN_PUT_SECRET 4096 /* The size of the import/export KEK key (in bytes). */ #define KEYWRAP_KEYSIZE (128/8) /* A shortcut to call assuan_set_error using an gpg_err_code_t and a text string. */ #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Check that the maximum digest length we support has at least the length of the keygrip. */ #if MAX_DIGEST_LEN < 20 #error MAX_DIGEST_LEN shorter than keygrip #endif /* Data used to associate an Assuan context with local server data. This is this modules local part of the server_control_s struct. */ struct server_local_s { /* Our Assuan context. */ assuan_context_t assuan_ctx; /* If this flag is true, the passphrase cache is used for signing operations. It defaults to true but may be set on a per connection base. The global option opt.ignore_cache_for_signing takes precedence over this flag. */ unsigned int use_cache_for_signing : 1; /* Flag to suppress I/O logging during a command. */ unsigned int pause_io_logging : 1; /* Flag indicating that the connection is from ourselves. */ unsigned int connect_from_self : 1; /* Helper flag for io_monitor to allow suppressing of our own * greeting in some cases. See io_monitor for details. */ unsigned int greeting_seen : 1; /* If this flag is set to true the agent will be terminated after the end of the current session. */ unsigned int stopme : 1; /* Flag indicating whether pinentry notifications shall be done. */ unsigned int allow_pinentry_notify : 1; /* An allocated description for the next key operation. This is used if a pinnetry needs to be popped up. */ char *keydesc; /* Malloced KEK (Key-Encryption-Key) for the import_key command. */ void *import_key; /* Malloced KEK for the export_key command. */ void *export_key; /* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */ int allow_fully_canceled; /* Last CACHE_NONCE sent as status (malloced). */ char *last_cache_nonce; /* Last PASSWD_NONCE sent as status (malloced). */ char *last_passwd_nonce; /* Per connection cache of the keyinfo from the cards. The * eventcounters for cards at the time the info was fetched is * stored here as a freshness indicator. */ struct { struct card_key_info_s *ki; unsigned int eventno; unsigned int maybe_key_change; } last_card_keyinfo; }; /* An entry for the getval/putval commands. */ struct putval_item_s { struct putval_item_s *next; size_t off; /* Offset to the value into DATA. */ size_t len; /* Length of the value. */ char d[1]; /* Key | Nul | value. */ }; /* A list of key value pairs fpr the getval/putval commands. */ static struct putval_item_s *putval_list; /* To help polling clients, we keep track of the number of certain events. This structure keeps those counters. The counters are integers and there should be no problem if they are overflowing as callers need to check only whether a counter changed. The actual values are not meaningful. */ struct { /* Incremented if any of the other counters below changed. */ unsigned int any; /* Incremented if a key is added or removed from the internal privat key database. */ unsigned int key; /* Incremented if a change of the card readers stati has been detected. */ unsigned int card; /* Internal counter to track possible changes to a key. * FIXME: This should be replaced by generic notifications from scd. */ unsigned int maybe_key_change; } eventcounter; /* Local prototypes. */ static int command_has_option (const char *cmd, const char *cmdopt); /* Release the memory buffer MB but first wipe out the used memory. */ static void clear_outbuf (membuf_t *mb) { void *p; size_t n; p = get_membuf (mb, &n); if (p) { wipememory (p, n); xfree (p); } } /* Write the content of memory buffer MB as assuan data to CTX and wipe the buffer out afterwards. */ static gpg_error_t write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) { gpg_error_t ae; void *p; size_t n; p = get_membuf (mb, &n); if (!p) return out_of_core (); ae = assuan_send_data (ctx, p, n); memset (p, 0, n); xfree (p); return ae; } /* Clear the nonces used to enable the passphrase cache for certain multi-command command sequences. */ static void clear_nonce_cache (ctrl_t ctrl) { if (ctrl->server_local->last_cache_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = NULL; } if (ctrl->server_local->last_passwd_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = NULL; } } /* This function is called by Libassuan whenever the client sends a reset. It has been registered similar to the other Assuan commands. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; xfree (ctrl->digest.data); ctrl->digest.data = NULL; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; clear_nonce_cache (ctrl); return 0; } /* Replace all '+' by a blank in the string S. */ static void plus_to_blank (char *s) { for (; *s; s++) { if (*s == '+') *s = ' '; } } /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int parse_hexstring (assuan_context_t ctx, const char *string, size_t *len) { const char *p; size_t n; /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; if (*p != ' ' && *p != '\t' && *p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); *len = n; return 0; } /* Parse the keygrip in STRING into the provided buffer BUF. BUF must provide space for 20 bytes. BUF is not changed if the function returns an error. */ static int parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf) { int rc; size_t n = 0; rc = parse_hexstring (ctx, string, &n); if (rc) return rc; n /= 2; if (n != 20) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip"); if (hex2bin (string, buf, 20) < 0) return set_error (GPG_ERR_BUG, "hex2bin"); return 0; } /* Parse the TTL from STRING. Leading and trailing spaces are * skipped. The value is constrained to -1 .. MAXINT. On error 0 is * returned, else the number of bytes scanned. */ static size_t parse_ttl (const char *string, int *r_ttl) { const char *string_orig = string; long ttl; char *pend; ttl = strtol (string, &pend, 10); string = pend; if (string == string_orig || !(spacep (string) || !*string) || ttl < -1L || (int)ttl != (long)ttl) { *r_ttl = 0; return 0; } while (spacep (string) || *string== '\n') string++; *r_ttl = (int)ttl; return string - string_orig; } /* Write an Assuan status line. KEYWORD is the first item on the * status line. The following arguments are all separated by a space * in the output. The last argument must be a NULL. Linefeeds and * carriage returns characters (which are not allowed in an Assuan * status line) are silently quoted in C-style. */ gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); va_end (arg_ptr); return err; } /* This function is similar to print_assuan_status but takes a CTRL arg instead of an assuan context as first argument. */ gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } /* Helper to notify the client about a launched Pinentry. Because that might disturb some older clients, this is only done if enabled via an option. Returns an gpg error code. */ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra) { char line[256]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s", pid, extra?" ":"", extra? extra:""); return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } /* An agent progress callback for Libgcrypt. This has been registered * to be called via the progress dispatcher mechanism from * gpg-agent.c */ static void progress_cb (ctrl_t ctrl, const char *what, int printchar, int current, int total) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) ; else if (printchar == '\n' && what && !strcmp (what, "primegen")) agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what); else agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d", what, printchar=='\n'?'X':printchar, current, total); } /* Helper to print a message while leaving a command. Note that this * function does not call assuan_set_error; the caller may do this * prior to calling us. */ 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 = "?"; /* Not all users of gpg-agent know about the fully canceled error code; map it back if needed. */ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!ctrl->server_local->allow_fully_canceled) err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); } /* Most code from common/ does not know the error source, thus we fix this here. */ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN) err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err)); 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; } static const char hlp_geteventcounter[] = "GETEVENTCOUNTER\n" "\n" "Return a status line named EVENTCOUNTER with the current values\n" "of all event counters. The values are decimal numbers in the range\n" "0 to UINT_MAX and wrapping around to 0. The actual values should\n" "not be relied upon, they shall only be used to detect a change.\n" "\n" "The currently defined counters are:\n" "\n" "ANY - Incremented with any change of any of the other counters.\n" "KEY - Incremented for added or removed private keys.\n" "CARD - Incremented for changes of the card readers stati."; static gpg_error_t cmd_geteventcounter (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u", eventcounter.any, eventcounter.key, eventcounter.card); } /* This function should be called once for all key removals or additions. This function is assured not to do any context switches. */ void bump_key_eventcounter (void) { eventcounter.key++; eventcounter.any++; } /* This function should be called for all card reader status changes. This function is assured not to do any context switches. */ void bump_card_eventcounter (void) { eventcounter.card++; eventcounter.any++; } static const char hlp_istrusted[] = "ISTRUSTED \n" "\n" "Return OK when we have an entry with this fingerprint in our\n" "trustlist"; static gpg_error_t cmd_istrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; /* Parse the fingerprint value. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; rc = agent_istrusted (ctrl, fpr, NULL); if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) return rc; else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF ) return gpg_error (GPG_ERR_NOT_TRUSTED); else return leave_cmd (ctx, rc); } static const char hlp_listtrusted[] = "LISTTRUSTED\n" "\n" "List all entries from the trustlist."; static gpg_error_t cmd_listtrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = agent_listtrusted (ctx); return leave_cmd (ctx, rc); } static const char hlp_martrusted[] = "MARKTRUSTED \n" "\n" "Store a new key in into the trustlist."; static gpg_error_t cmd_marktrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; int flag; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the fingerprint value */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (!spacep (p) || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; while (spacep (p)) p++; flag = *p++; if ( (flag != 'S' && flag != 'P') || !spacep (p) ) return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S"); while (spacep (p)) p++; rc = agent_marktrusted (ctrl, p, fpr, flag); return leave_cmd (ctx, rc); } static const char hlp_havekey[] = "HAVEKEY \n" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { gpg_error_t err; unsigned char buf[20]; do { err = parse_keygrip (ctx, line, buf); if (err) return err; if (!agent_key_available (buf)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') line++; while (*line == ' ' || *line == '\t') line++; } while (*line); /* No leave_cmd() here because errors are expected and would clutter the log. */ return gpg_error (GPG_ERR_NO_SECKEY); } static const char hlp_sigkey[] = "SIGKEY \n" "SETKEY \n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) return rc; ctrl->have_keygrip = 1; return 0; } static const char hlp_setkeydesc[] = "SETKEYDESC plus_percent_escaped_string\n" "\n" "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n" "or EXPORT_KEY operation if this operation requires a passphrase. If\n" "this command is not used a default text will be used. Note, that\n" "this description implicitly selects the label used for the entry\n" "box; if the string contains the string PIN (which in general will\n" "not be translated), \"PIN\" is used, otherwise the translation of\n" "\"passphrase\" is used. The description string should not contain\n" "blanks unless they are percent or '+' escaped.\n" "\n" "The description is only valid for the next PKSIGN, PKDECRYPT,\n" "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation."; static gpg_error_t cmd_setkeydesc (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *desc, *p; for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage; we might late use it for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ plus_to_blank (desc); xfree (ctrl->server_local->keydesc); if (ctrl->restricted) { ctrl->server_local->keydesc = strconcat ((ctrl->restricted == 2 ? _("Note: Request from the web browser.") : _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL); } else ctrl->server_local->keydesc = xtrystrdup (desc); if (!ctrl->server_local->keydesc) return out_of_core (); return 0; } static const char hlp_sethash[] = "SETHASH (--hash=)|() ]\n" "SETHASH [--pss] --inquire\n" "\n" "The client can use this command to tell the server about the data\n" "(which usually is a hash) to be signed. The option --inquire is\n" "used to ask back for to-be-signed data in case of PureEdDSA or\n" "with --pss for pre-formatted rsaPSS."; static gpg_error_t cmd_sethash (assuan_context_t ctx, char *line) { gpg_error_t err; size_t n; char *p; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; int opt_inquire, opt_pss; /* Parse the alternative hash options which may be used instead of the algo number. */ if (has_option_name (line, "--hash")) { if (has_option (line, "--hash=sha1")) algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=rmd160")) algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=md5")) algo = GCRY_MD_MD5; else if (has_option (line, "--hash=tls-md5sha1")) algo = MD_USER_TLS_MD5SHA1; else if (has_option (line, "--hash=none")) algo = 0; else { err = set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); goto leave; } } else algo = 0; opt_pss = has_option (line, "--pss"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); if (!algo && !opt_inquire) { /* No hash option has been given: require an algo number instead */ algo = (int)strtoul (line, &endp, 10); for (line = endp; *line == ' ' || *line == '\t'; line++) ; if (!algo || gcry_md_test_algo (algo)) { err = set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); goto leave; } } xfree (ctrl->digest.data); ctrl->digest.data = NULL; ctrl->digest.algo = algo; ctrl->digest.raw_value = 0; ctrl->digest.is_pss = opt_pss; if (opt_inquire) { /* We limit the to-be-signed data to some reasonable size which * may eventually allow us to pass that even to smartcards. */ size_t maxlen = 2048; if (algo) { err = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and an algo are specified"); goto leave; } err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!err) err = assuan_inquire (ctx, "TBSDATA", &buf, &n, maxlen); if (err) goto leave; ctrl->digest.data = buf; ctrl->digest.valuelen = n; } else { /* Parse the hash value. */ n = 0; err = parse_hexstring (ctx, line, &n); if (err) goto leave; n /= 2; if (algo == MD_USER_TLS_MD5SHA1 && n == 36) ; else if (n != 16 && n != 20 && n != 24 && n != 28 && n != 32 && n != 48 && n != 64) { err = set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); goto leave; } if (n > MAX_DIGEST_LEN) { err = set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); goto leave; } buf = ctrl->digest.value; ctrl->digest.valuelen = n; for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); for (; n < ctrl->digest.valuelen; n++) buf[n] = 0; } leave: return leave_cmd (ctx, err); } static const char hlp_pksign[] = "PKSIGN [] []\n" "\n" "Perform the actual sign operation. Neither input nor output are\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { gpg_error_t err; cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; char *cache_nonce = NULL; char *p; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); if (opt.ignore_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc, &outbuf, cache_mode); if (err) clear_outbuf (&outbuf); else err = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_pkdecrypt[] = "PKDECRYPT []\n" "\n" "Perform the actual decrypt operation. Input is not\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; membuf_t outbuf; int padding; (void)line; /* First inquire the data to decrypt */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT); if (!rc) rc = assuan_inquire (ctx, "CIPHERTEXT", &value, &valuelen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, value, valuelen, &outbuf, &padding); xfree (value); if (rc) clear_outbuf (&outbuf); else { if (padding != -1) rc = print_assuan_status (ctx, "PADDING", "%d", padding); else rc = 0; if (!rc) rc = write_and_clear_outbuf (ctx, &outbuf); } xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, rc); } static const char hlp_genkey[] = "GENKEY [--no-protection] [--preset] [--timestamp=]\n" " [--inq-passwd] [--passwd-nonce=] []\n" "\n" "Generate a new key, store the secret part and return the public\n" "part. Here is an example transaction:\n" "\n" " C: GENKEY\n" " S: INQUIRE KEYPARAM\n" " C: D (genkey (rsa (nbits 3072)))\n" " C: END\n" " S: D (public-key\n" " S: D (rsa (n 326487324683264) (e 10001)))\n" " S: OK key created\n" "\n" "If the --preset option is used the passphrase for the generated\n" "key will be added to the cache. If --inq-passwd is used an inquire\n" "with the keyword NEWPASSWD is used to request the passphrase for the\n" "new key. If a --passwd-nonce is used, the corresponding cached\n" "passphrase is used to protect the new key. If --timestamp is given\n" "its value is recorded as the key's creation time; the value is\n" "expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; char *cache_nonce = NULL; char *passwd_nonce = NULL; int opt_preset; int opt_inq_passwd; size_t n; char *p, *pend; const char *s; time_t opt_timestamp; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); no_protection = has_option (line, "--no-protection"); opt_preset = has_option (line, "--preset"); opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { rc = gpg_error_from_syserror (); goto leave; } } if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); eventcounter.maybe_key_change++; /* First inquire the parameters */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM); if (!rc) rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; init_membuf (&outbuf, 512); /* If requested, ask for the password to be used for the key. If this is not used the regular Pinentry mechanism is used. */ if (opt_inq_passwd && !no_protection) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256); assuan_end_confidential (ctx); if (rc) goto leave; if (!*newpasswd) { /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; no_protection = 1; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); rc = agent_genkey (ctrl, cache_nonce, opt_timestamp, (char*)value, valuelen, no_protection, newpasswd, opt_preset, &outbuf); leave: if (newpasswd) { /* Assuan_inquire does not allow us to read into secure memory thus we need to wipe it ourself. */ wipememory (newpasswd, strlen (newpasswd)); xfree (newpasswd); } xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, rc); } static const char hlp_readkey[] = "READKEY \n" " --card \n" "\n" "Return the public key for the given keygrip or keyid.\n" "With --card, private key file with card information will be created."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_pkey = NULL; unsigned char *pkbuf = NULL; char *serialno = NULL; size_t pkbuflen; const char *opt_card; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_card = has_option_name (line, "--card"); line = skip_options (line); if (opt_card) { const char *keyid = opt_card; rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL); if (rc) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (rc)); goto leave; } rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL); if (rc) goto leave; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen); if (rc) goto leave; if (!gcry_pk_get_keygrip (s_pkey, grip)) { rc = gcry_pk_testkey (s_pkey); if (rc == 0) rc = gpg_error (GPG_ERR_INTERNAL); goto leave; } rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0); if (rc) goto leave; rc = assuan_send_data (ctx, pkbuf, pkbuflen); } else { rc = parse_keygrip (ctx, line, grip); if (rc) goto leave; rc = agent_public_key_from_file (ctrl, grip, &s_pkey); if (!rc) { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (pkbuflen); pkbuf = xtrymalloc (pkbuflen); if (!pkbuf) rc = gpg_error_from_syserror (); else { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, pkbuflen); rc = assuan_send_data (ctx, pkbuf, pkbuflen); } } } leave: xfree (serialno); xfree (pkbuf); gcry_sexp_release (s_pkey); return leave_cmd (ctx, rc); } static const char hlp_keyinfo[] = "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] \n" "\n" "Return information about the key specified by the KEYGRIP. If the\n" "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" "--list is given the keygrip is ignored and information about all\n" "available keys are returned. If --ssh-list is given information\n" "about all keys listed in the sshcontrol are returned. With --with-ssh\n" "information from sshcontrol is always added to the info. Unless --data\n" "is given, the information is returned as a status line using the format:\n" "\n" " KEYINFO \n" "\n" "KEYGRIP is the keygrip.\n" "\n" "TYPE is describes the type of the key:\n" " 'D' - Regular key stored on disk,\n" " 'T' - Key is stored on a smartcard (token),\n" " 'X' - Unknown type,\n" " '-' - Key is missing.\n" "\n" "SERIALNO is an ASCII string with the serial number of the\n" " smartcard. If the serial number is not known a single\n" " dash '-' is used instead.\n" "\n" "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n" " is not known a dash is used instead.\n" "\n" "CACHED is 1 if the passphrase for the key was found in the key cache.\n" " If not, a '-' is used instead.\n" "\n" "PROTECTION describes the key protection type:\n" " 'P' - The key is protected with a passphrase,\n" " 'C' - The key is not protected,\n" " '-' - Unknown protection.\n" "\n" "FPR returns the formatted ssh-style fingerprint of the key. It is only\n" " printed if the option --ssh-fpr has been used. If ALGO is not given\n" " to that option the default ssh fingerprint algo is used. Without the\n" " option a '-' is printed.\n" "\n" "TTL is the TTL in seconds for that key or '-' if n/a.\n" "\n" "FLAGS is a word consisting of one-letter flags:\n" " 'D' - The key has been disabled,\n" " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n" " 'c' - Use of the key needs to be confirmed,\n" " 'A' - The key is available on card,\n" " '-' - No flags given.\n" "\n" "More information may be added in the future."; static gpg_error_t do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, int data, int with_ssh_fpr, int in_ssh, int ttl, int disabled, int confirm, int on_card) { gpg_error_t err; char hexgrip[40+1]; char *fpr = NULL; int keytype; unsigned char *shadow_info = NULL; unsigned char *shadow_info_type = NULL; char *serialno = NULL; char *idstr = NULL; const char *keytypestr; const char *cached; const char *protectionstr; char *pw; int missing_key = 0; char ttlbuf[20]; char flagsbuf[5]; err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info, &shadow_info_type); if (err) { if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) missing_key = 1; else goto leave; } /* Reformat the grip so that we use uppercase as good style. */ bin2hex (grip, 20, hexgrip); if (ttl > 0) snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl); else strcpy (ttlbuf, "-"); *flagsbuf = 0; if (disabled) strcat (flagsbuf, "D"); if (in_ssh) strcat (flagsbuf, "S"); if (confirm) strcat (flagsbuf, "c"); if (on_card) strcat (flagsbuf, "A"); if (!*flagsbuf) strcpy (flagsbuf, "-"); if (missing_key) { protectionstr = "-"; keytypestr = "-"; } else { switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: protectionstr = "C"; keytypestr = "D"; break; case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; break; case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; break; default: protectionstr = "-"; keytypestr = "X"; break; } } /* Compute the ssh fingerprint if requested. */ if (with_ssh_fpr) { gcry_sexp_t key; if (!agent_raw_key_from_file (ctrl, grip, &key)) { ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr); gcry_sexp_release (key); } } /* Here we have a little race by doing the cache check separately from the retrieval function. Given that the cache flag is only a hint, it should not really matter. */ pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL); cached = pw ? "1" : "-"; xfree (pw); if (shadow_info) { if (strcmp (shadow_info_type, "t1-v1") == 0) { err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); if (err) goto leave; } else { log_error ("unrecognised shadow key type %s\n", shadow_info_type); err = GPG_ERR_BAD_KEY; goto leave; } } if (!data) err = agent_write_status (ctrl, "KEYINFO", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf, NULL); else { char *string; string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf); if (!string) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, string, strlen(string)); xfree (string); } leave: xfree (fpr); xfree (shadow_info_type); xfree (shadow_info); xfree (serialno); xfree (idstr); return err; } /* Entry into the command KEYINFO. This function handles the * command option processing. For details see hlp_keyinfo above. */ static gpg_error_t cmd_keyinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int err; unsigned char grip[20]; DIR *dir = NULL; int list_mode; int opt_data, opt_ssh_fpr, opt_with_ssh; ssh_control_file_t cf = NULL; char hexgrip[41]; int disabled, ttl, confirm, is_ssh; struct card_key_info_s *keyinfo_on_cards; struct card_key_info_s *l; int on_card; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--ssh-list")) list_mode = 2; else list_mode = has_option (line, "--list"); opt_data = has_option (line, "--data"); if (has_option_name (line, "--ssh-fpr")) { if (has_option (line, "--ssh-fpr=md5")) opt_ssh_fpr = GCRY_MD_MD5; else if (has_option (line, "--ssh-fpr=sha1")) opt_ssh_fpr = GCRY_MD_SHA1; else if (has_option (line, "--ssh-fpr=sha256")) opt_ssh_fpr = GCRY_MD_SHA256; else opt_ssh_fpr = opt.ssh_fingerprint_digest; } else opt_ssh_fpr = 0; opt_with_ssh = has_option (line, "--with-ssh"); line = skip_options (line); if (opt_with_ssh || list_mode == 2) cf = ssh_open_control_file (); /* Take the keyinfo for cards from our local cache. Actually this * cache could be a global one but then we would need to employ * reference counting. */ if (ctrl->server_local->last_card_keyinfo.ki && ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card && (ctrl->server_local->last_card_keyinfo.maybe_key_change == eventcounter.maybe_key_change)) { keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki; } else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards)) { agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki); ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards; ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card; ctrl->server_local->last_card_keyinfo.maybe_key_change = eventcounter.maybe_key_change; } if (list_mode == 2) { if (cf) { while (!ssh_read_control_file (cf, hexgrip, &disabled, &ttl, &confirm)) { if (hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, hexgrip, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, ttl, disabled, confirm, on_card); if (err) goto leave; } } err = 0; } else if (list_mode) { char *dirname; struct dirent *dir_entry; dirname = make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } dir = opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); xfree (dirname); goto leave; } xfree (dirname); while ( (dir_entry = readdir (dir)) ) { if (strlen (dir_entry->d_name) != 44 || strcmp (dir_entry->d_name + 40, ".key")) continue; strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, hexgrip, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, hexgrip, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm, on_card); if (err) goto leave; } err = 0; } else { err = parse_keygrip (ctx, line, grip); if (err) goto leave; disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, line, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } on_card = 0; for (l = keyinfo_on_cards; l; l = l->next) if (!memcmp (l->keygrip, line, 40)) on_card = 1; err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm, on_card); } leave: ssh_close_control_file (cf); if (dir) closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) leave_cmd (ctx, err); return err; } /* Helper for cmd_get_passphrase. */ static int send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw) { size_t n; int rc; assuan_begin_confidential (ctx); n = strlen (pw); if (via_data) rc = assuan_send_data (ctx, pw, n); else { char *p = xtrymalloc_secure (n*2+1); if (!p) rc = gpg_error_from_syserror (); else { bin2hex (pw, n, p); rc = assuan_set_okay_line (ctx, p); xfree (p); } } return rc; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_passphrase_cmp_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); } static const char hlp_get_passphrase[] = "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n" " [--qualitybar] [--newsymkey] \n" " [ ]\n" "\n" "This function is usually used to ask for a passphrase to be used\n" "for conventional encryption, but may also be used by programs which\n" "need specal handling of passphrases. This command uses a syntax\n" "which helps clients to use the agent with minimum effort. The\n" "agent either returns with an error or with a OK followed by the hex\n" "encoded passphrase. Note that the length of the strings is\n" "implicitly limited by the maximum length of a command.\n" "\n" "If the option \"--data\" is used the passphrase is returned by usual\n" "data lines and not on the okay line.\n" "\n" "If the option \"--check\" is used the passphrase constraints checks as\n" "implemented by gpg-agent are applied. A check is not done if the\n" "passphrase has been found in the cache.\n" "\n" "If the option \"--no-ask\" is used and the passphrase is not in the\n" "cache the user will not be asked to enter a passphrase but the error\n" "code GPG_ERR_NO_DATA is returned. \n" "\n" "If the option\"--newsymkey\" is used the agent asks for a new passphrase\n" "to be used in symmetric-only encryption. This must not be empty.\n" "\n" "If the option \"--qualitybar\" is used a visual indication of the\n" "entered passphrase quality is shown. (Unless no minimum passphrase\n" "length has been configured.)"; static gpg_error_t cmd_get_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *pw; char *response = NULL; char *response2 = NULL; char *cacheid = NULL; /* May point into LINE. */ char *desc = NULL; /* Ditto */ char *prompt = NULL; /* Ditto */ char *errtext = NULL; /* Ditto */ const char *desc2 = _("Please re-enter this passphrase"); char *p; int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey; int opt_repeat = 0; char *entry_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_data = has_option (line, "--data"); opt_check = has_option (line, "--check"); opt_no_ask = has_option (line, "--no-ask"); if (has_option_name (line, "--repeat")) { p = option_value (line, "--repeat"); if (p) opt_repeat = atoi (p); else opt_repeat = 1; } opt_qualbar = has_option (line, "--qualitybar"); opt_newsymkey = has_option (line, "--newsymkey"); line = skip_options (line); cacheid = line; p = strchr (cacheid, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; errtext = p; p = strchr (errtext, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; prompt = p; p = strchr (prompt, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* Ignore trailing garbage. */ } } } if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); if (!desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (cacheid, "X")) cacheid = NULL; if (!strcmp (errtext, "X")) errtext = NULL; if (!strcmp (prompt, "X")) prompt = NULL; if (!strcmp (desc, "X")) desc = NULL; pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL; if (pw) { rc = send_back_passphrase (ctx, opt_data, pw); xfree (pw); goto leave; } else if (opt_no_ask) { rc = gpg_error (GPG_ERR_NO_DATA); goto leave; } /* Note, that we only need to replace the + characters and should * leave the other escaping in place because the escaped string is * send verbatim to the pinentry which does the unescaping (but not * the + replacing) */ if (errtext) plus_to_blank (errtext); if (prompt) plus_to_blank (prompt); if (desc) plus_to_blank (desc); /* If opt_repeat is 2 or higher we can't use our pin_entry_info_s * based method but fallback to the old simple method. It is * anyway questionable whether this extra repeat count makes any * real sense. */ if (opt_newsymkey && opt_repeat < 2) { /* We do not want to break any existing usage of this command * and thus we introduced the option --newsymkey to make this * command more useful to query the passphrase for symmetric * encryption. */ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { rc = gpg_error_from_syserror (); goto leave; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { rc = gpg_error_from_syserror (); goto leave; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = opt_qualbar; pi->with_repeat = opt_repeat; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_passphrase_cmp_cb; pi2->check_cb_arg = pi->pin; for (;;) /* (degenerated for-loop) */ { xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi); if (rc) goto leave; xfree (entry_errtext); entry_errtext = NULL; /* We don't allow an empty passpharse in this mode. */ if (check_passphrase_constraints (ctrl, pi->pin, 1, &entry_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; continue; } if (*pi->pin && !pi->repeat_okay && ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK && opt_repeat) { /* The passphrase is empty and the pinentry did not * already run the repetition check, do it here. This * is only called when using an old and simple pinentry. * It is neither called in loopback mode because the * caller does any passphrase repetition by herself nor if * no repetition was requested. */ xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, L_("Please re-enter this passphrase"), prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi2); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered passphrase one did not match and * the user did not hit cancel. */ entry_errtext = xtrystrdup (L_("does not match - try again")); if (!entry_errtext) { rc = gpg_error_from_syserror (); goto leave; } continue; } } break; } if (!rc && *pi->pin) { /* Return the passphrase. */ if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0); rc = send_back_passphrase (ctx, opt_data, pi->pin); } } else { next_try: xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, NULL); xfree (entry_errtext); entry_errtext = NULL; if (!rc) { int i; if (opt_check && check_passphrase_constraints (ctrl, response,0,&entry_errtext)) { goto next_try; } for (i = 0; i < opt_repeat; i++) { if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) break; xfree (response2); response2 = NULL; rc = agent_get_passphrase (ctrl, &response2, desc2, prompt, errtext, 0, cacheid, CACHE_MODE_USER, NULL); if (rc) break; if (strcmp (response2, response)) { entry_errtext = try_percent_escape (_("does not match - try again"), NULL); if (!entry_errtext) { rc = gpg_error_from_syserror (); break; } goto next_try; } } if (!rc) { if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0); rc = send_back_passphrase (ctx, opt_data, response); } } } leave: xfree (response); xfree (response2); xfree (entry_errtext); xfree (pi2); xfree (pi); return leave_cmd (ctx, rc); } static const char hlp_clear_passphrase[] = "CLEAR_PASSPHRASE [--mode=normal] \n" "\n" "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" "added by the agent. The --mode=ssh option is used for a cacheid\n" "added for ssh.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; cache_mode_t cache_mode = CACHE_MODE_USER; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--mode=normal")) cache_mode = CACHE_MODE_NORMAL; else if (has_option (line, "--mode=ssh")) cache_mode = CACHE_MODE_SSH; line = skip_options (line); /* parse the stuff */ for (p=line; *p == ' '; p++) ; cacheid = p; p = strchr (cacheid, ' '); if (p) *p = 0; /* ignore garbage */ if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0); agent_clear_passphrase (ctrl, cacheid, cache_mode); return 0; } static const char hlp_get_confirmation[] = "GET_CONFIRMATION \n" "\n" "This command may be used to ask for a simple confirmation.\n" "DESCRIPTION is displayed along with a Okay and Cancel button. This\n" "command uses a syntax which helps clients to use the agent with\n" "minimum effort. The agent either returns with an error or with a\n" "OK. Note, that the length of DESCRIPTION is implicitly limited by\n" "the maximum length of a command. DESCRIPTION should not contain\n" "any spaces, those must be encoded either percent escaped or simply\n" "as '+'."; static gpg_error_t cmd_get_confirmation (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the stuff */ for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage -may be later used for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (desc, "X")) desc = NULL; /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (desc) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); return leave_cmd (ctx, rc); } static const char hlp_learn[] = "LEARN [--send] [--sendinfo] [--force]\n" "\n" "Learn something about the currently inserted smartcard. With\n" "--sendinfo information about the card is returned; with --send\n" "the available certificates are returned as D lines; with --force\n" "private key storage will be updated by the result."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int send, sendinfo, force; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); return leave_cmd (ctx, err); } static const char hlp_passwd[] = "PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset]\n" " [--verify] \n" "\n" "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n" "--preset is used then the new passphrase will be added to the cache.\n" "If --verify is used the command asks for the passphrase and verifies\n" "that the passphrase valid.\n"; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int c; char *cache_nonce = NULL; char *passwd_nonce = NULL; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *shadow_info = NULL; char *passphrase = NULL; char *pend; int opt_preset, opt_verify; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_preset = has_option (line, "--preset"); cache_nonce = option_value (line, "--cache-nonce"); opt_verify = has_option (line, "--verify"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; ctrl->in_passwd++; err = agent_key_from_file (ctrl, opt_verify? NULL : cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, &passphrase); if (err) ; else if (shadow_info) { log_error ("changing a smartcard PIN is not yet supported\n"); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else if (opt_verify) { /* All done. */ if (passphrase) { if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } else { char *newpass = NULL; if (passwd_nonce) newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); err = agent_protect_and_store (ctrl, s_skey, &newpass); if (!err && passphrase) { /* A passphrase existed on the old key and the change was successful. Return a nonce for that old passphrase to let the caller try to unprotect the other subkeys with the same key. */ if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } if (newpass) { /* If we have a new passphrase (which might be empty) we store it under a passwd nonce so that the caller may send that nonce again to use it for another key. */ if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, newpass, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } if (!err && opt_preset) { char hexgrip[40+1]; bin2hex(grip, 20, hexgrip); err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass, ctrl->cache_ttl_opt_preset); } xfree (newpass); } ctrl->in_passwd--; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; leave: xfree (passphrase); gcry_sexp_release (s_skey); xfree (shadow_info); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, err); } static const char hlp_preset_passphrase[] = "PRESET_PASSPHRASE [--inquire] []\n" "\n" "Set the cached passphrase/PIN for the key identified by the keygrip\n" "to passwd for the given time, where -1 means infinite and 0 means\n" "the default (currently only a timeout of -1 is allowed, which means\n" "to never expire it). If passwd is not provided, ask for it via the\n" "pinentry module unless --inquire is passed in which case the passphrase\n" "is retrieved from the client via a server inquire.\n"; static gpg_error_t cmd_preset_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *grip_clear = NULL; unsigned char *passphrase = NULL; int ttl; size_t len; int opt_inquire; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!opt.allow_preset_passphrase) return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) return gpg_error (GPG_ERR_MISSING_VALUE); *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; /* Currently, only infinite timeouts are allowed. */ ttl = -1; if (line[0] != '-' || line[1] != '1') return gpg_error (GPG_ERR_NOT_IMPLEMENTED); line++; line++; while (!(*line != ' ' && *line != '\t')) line++; /* Syntax check the hexstring. */ len = 0; rc = parse_hexstring (ctx, line, &len); if (rc) return rc; line[len] = '\0'; /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) { if (opt_inquire) { rc = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and passphrase specified"); goto leave; } /* Do in-place conversion. */ passphrase = line; if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL)) rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); } else if (opt_inquire) { /* Note that the passphrase will be truncated at any null byte and the * limit is 480 characters. */ size_t maxlen = 480; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!rc) rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); } else rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required"); if (!rc) { rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (opt_inquire) xfree (passphrase); } leave: return leave_cmd (ctx, rc); } static const char hlp_scd[] = "SCD \n" " \n" "This is a general quote command to redirect everything to the\n" "SCdaemon."; static gpg_error_t cmd_scd (assuan_context_t ctx, char *line) { int rc; #ifdef BUILD_WITH_SCDAEMON ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* All SCD prefixed commands may change a key. */ eventcounter.maybe_key_change++; rc = divert_generic_cmd (ctrl, line, ctx); #else (void)ctx; (void)line; rc = gpg_error (GPG_ERR_NOT_SUPPORTED); #endif return rc; } static const char hlp_keywrap_key[] = "KEYWRAP_KEY [--clear] \n" "\n" "Return a key to wrap another key. For now the key is returned\n" "verbatim and thus makes not much sense because an eavesdropper on\n" "the gpg-agent connection will see the key as well as the wrapped key.\n" "However, this function may either be equipped with a public key\n" "mechanism or not used at all if the key is a pre-shared key. In any\n" "case wrapping the import and export of keys is a requirement for\n" "certain cryptographic validations and thus useful. The key persists\n" "until a RESET command but may be cleared using the option --clear.\n" "\n" "Supported modes are:\n" " --import - Return a key to import a key into gpg-agent\n" " --export - Return a key to export a key from gpg-agent"; static gpg_error_t cmd_keywrap_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clearopt = has_option (line, "--clear"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); assuan_begin_confidential (ctx); if (has_option (line, "--import")) { xfree (ctrl->server_local->import_key); if (clearopt) ctrl->server_local->import_key = NULL; else if (!(ctrl->server_local->import_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); } else if (has_option (line, "--export")) { xfree (ctrl->server_local->export_key); if (clearopt) ctrl->server_local->export_key = NULL; else if (!(ctrl->server_local->export_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE"); assuan_end_confidential (ctx); return leave_cmd (ctx, err); } static const char hlp_import_key[] = "IMPORT_KEY [--unattended] [--force] [--timestamp=]\n" " []\n" "\n" "Import a secret key into the key store. The key is expected to be\n" "encrypted using the current session's key wrapping key (cf. command\n" "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" "key data. The unwrapped key must be a canonical S-expression. The\n" "option --unattended tries to import the key as-is without any\n" "re-encryption. An existing key can be overwritten with --force.\n" "If --timestamp is given its value is recorded as the key's creation\n" "time; the value is expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_import_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int opt_unattended; time_t opt_timestamp; int force; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *key = NULL; size_t keylen, realkeylen; char *passphrase = NULL; unsigned char *finalkey = NULL; size_t finalkeylen; unsigned char grip[20]; gcry_sexp_t openpgp_sexp = NULL; char *cache_nonce = NULL; char *p; const char *s; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!ctrl->server_local->import_key) { err = gpg_error (GPG_ERR_MISSING_KEY); goto leave; } opt_unattended = has_option (line, "--unattended"); force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); eventcounter.maybe_key_change++; assuan_begin_confidential (ctx); err = assuan_inquire (ctx, "KEYDATA", &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (err) goto leave; if (wrappedkeylen < 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto leave; } keylen = wrappedkeylen - 8; key = xtrymalloc_secure (keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); if (err) goto leave; err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); if (err) goto leave; gcry_cipher_close (cipherhd); cipherhd = NULL; xfree (wrappedkey); wrappedkey = NULL; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ err = keygrip_from_canon_sexp (key, realkeylen, grip); if (err) { /* This might be due to an unsupported S-expression format. Check whether this is openpgp-private-key and trigger that import code. */ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen)) { const char *tag; size_t taglen; tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen); if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) ; else { gcry_sexp_release (openpgp_sexp); openpgp_sexp = NULL; } } if (!openpgp_sexp) goto leave; /* Note that ERR is still set. */ } if (openpgp_sexp) { /* In most cases the key is encrypted and thus the conversion function from the OpenPGP format to our internal format will ask for a passphrase. That passphrase will be returned and used to protect the key using the same code as for regular key import. */ xfree (key); key = NULL; err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip, ctrl->server_local->keydesc, cache_nonce, &key, opt_unattended? NULL : &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ if (passphrase) { log_assert (!opt_unattended); if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } else if (opt_unattended) { err = set_error (GPG_ERR_ASS_PARAMETER, "\"--unattended\" may only be used with OpenPGP keys"); goto leave; } else { if (!force && !agent_key_available (grip)) err = gpg_error (GPG_ERR_EEXIST); else { char *prompt = xtryasprintf (_("Please enter the passphrase to protect the " "imported object within the %s system."), GNUPG_NAME); if (!prompt) err = gpg_error_from_syserror (); else err = agent_ask_new_passphrase (ctrl, prompt, &passphrase); xfree (prompt); } if (err) goto leave; } if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count, -1); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, force, NULL, NULL, opt_timestamp); } else err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL, opt_timestamp); leave: gcry_sexp_release (openpgp_sexp); xfree (finalkey); xfree (passphrase); xfree (key); gcry_cipher_close (cipherhd); xfree (wrappedkey); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_export_key[] = "EXPORT_KEY [--cache-nonce=] [--openpgp] \n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n" "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" "compatible passphrase-protected form. Without --openpgp, the secret key\n" "material will be exported in the clear (after prompting the user to unlock\n" "it, if needed).\n"; static gpg_error_t cmd_export_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *key = NULL; size_t keylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; int openpgp; char *cache_nonce; char *passphrase = NULL; unsigned char *shadow_info = NULL; char *pend; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); openpgp = has_option (line, "--openpgp"); cache_nonce = option_value (line, "--cache-nonce"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); if (!ctrl->server_local->export_key) { err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?"); goto leave; } err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Get the key from the file. With the openpgp flag we also ask for the passphrase so that we can use it to re-encrypt it. */ err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, openpgp ? &passphrase : NULL); if (err) goto leave; if (shadow_info) { /* Key is on a smartcard. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } if (openpgp) { /* The openpgp option changes the key format into the OpenPGP key transfer format. The result is already a padded canonical S-expression. */ if (!passphrase) { err = agent_ask_new_passphrase (ctrl, _("This key (or subkey) is not protected with a passphrase." " Please enter a new passphrase to export it."), &passphrase); if (err) goto leave; } err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); if (!err && passphrase) { if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } } } else { /* Convert into a canonical S-expression and wrap that. */ err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); } if (err) goto leave; gcry_sexp_release (s_skey); s_skey = NULL; err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); if (err) goto leave; 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; assuan_begin_confidential (ctx); err = assuan_send_data (ctx, wrappedkey, wrappedkeylen); assuan_end_confidential (ctx); leave: xfree (cache_nonce); xfree (passphrase); xfree (wrappedkey); gcry_cipher_close (cipherhd); xfree (key); gcry_sexp_release (s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_delete_key[] = "DELETE_KEY [--force|--stub-only] \n" "\n" "Delete a secret key from the key store. If --force is used\n" "and a loopback pinentry is allowed, the agent will not ask\n" "the user for confirmation. If --stub-only is used the key will\n" "only be deleted if it is a reference to a token."; static gpg_error_t cmd_delete_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int force, stub_only; unsigned char grip[20]; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); stub_only = has_option (line, "--stub-only"); line = skip_options (line); eventcounter.maybe_key_change++; /* If the use of a loopback pinentry has been disabled, we assume * that a silent deletion of keys shall also not be allowed. */ if (!opt.allow_loopback_pinentry) force = 0; err = parse_keygrip (ctx, line, grip); if (err) goto leave; err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force, stub_only); if (err) goto leave; leave: xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } #if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" #else #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" #endif static const char hlp_keytocard[] = "KEYTOCARD [--force] []\n" "\n" "TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n" "SERIALNO is used for checking; use \"-\" to disable the check."; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; - char *argv[5]; + const char *argv[5]; int argc; unsigned char grip[20]; const char *serialno, *timestamp_str, *keyref; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; unsigned char *shadow_info = NULL; time_t timestamp; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); line = skip_options (line); argc = split_fields (line, argv, DIM (argv)); if (argc < 3) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = parse_keygrip (ctx, argv[0], grip); if (err) goto leave; if (agent_key_available (grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Note that checking of the s/n is currently not implemented but we * want to provide a clean interface if we ever implement it. */ serialno = argv[1]; if (!strcmp (serialno, "-")) serialno = NULL; keyref = argv[2]; /* FIXME: Default to the creation time as stored in the private * key. The parameter is here so that gpg can make sure that the * timestamp as used for key creation (and thus the openPGP * fingerprint) is used. */ timestamp_str = argc > 3? argv[3] : "19700101T000000"; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) goto leave; if (shadow_info) { /* Key is already on a smartcard - we can't extract it. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } /* Note: We can't use make_canon_sexp because we need to allocate a * few extra bytes for our hack below. */ keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); goto leave; } gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); s_skey = NULL; keydatalen--; /* Decrement for last '\0'. */ /* Hack to insert the timestamp "created-at" into the private key. */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen); xfree (keydata); leave: gcry_sexp_release (s_skey); xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_get_secret[] = "GET_SECRET \n" "\n" "Return the secret value stored under KEY\n"; static gpg_error_t cmd_get_secret (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char *p, *key; char *value = NULL; size_t valuelen; /* For now we allow this only for local connections. */ if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } line = skip_options (line); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); goto leave; } } if (!*key) { err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); goto leave; } value = agent_get_cache (ctrl, key, CACHE_MODE_DATA); if (!value) { err = gpg_error (GPG_ERR_NO_DATA); goto leave; } valuelen = percent_unescape_inplace (value, 0); err = assuan_send_data (ctx, value, valuelen); wipememory (value, valuelen); leave: xfree (value); return leave_cmd (ctx, err); } static const char hlp_put_secret[] = "PUT_SECRET [--clear] []\n" "\n" "This commands stores a secret under KEY in gpg-agent's in-memory\n" "cache. The TTL must be explicitly given by TTL and the options\n" "from the configuration file are not used. The value is either given\n" "percent-escaped as 3rd argument or if not given inquired by gpg-agent\n" "using the keyword \"SECRET\".\n" "The option --clear removes the secret from the cache." ""; static gpg_error_t cmd_put_secret (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int opt_clear; unsigned char *value = NULL; size_t valuelen = 0; size_t n; char *p, *key, *ttlstr; unsigned char *valstr; int ttl; char *string = NULL; /* For now we allow this only for local connections. */ if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } opt_clear = has_option (line, "--clear"); line = skip_options (line); for (p=line; *p == ' '; p++) ; key = p; ttlstr = NULL; valstr = NULL; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { ttlstr = p; p = strchr (ttlstr, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) valstr = p; } } } if (!*key) { err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); goto leave; } if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl))) { err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given"); goto leave; } if (valstr && opt_clear) { err = set_error (GPG_ERR_ASS_PARAMETER, "value not expected with --clear"); goto leave; } if (valstr) { valuelen = percent_unescape_inplace (valstr, 0); value = NULL; } else /* Inquire the value to store */ { err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET); if (!err) err = assuan_inquire (ctx, "SECRET", &value, &valuelen, MAXLEN_PUT_SECRET); if (err) goto leave; } /* Our cache expects strings and thus we need to turn the buffer * into a string. Instead of resorting to base64 encoding we use a * special percent escaping which only quoted the Nul and the * percent character. */ string = percent_data_escape (0, NULL, value? value : valstr, valuelen); if (!string) { err = gpg_error_from_syserror (); goto leave; } err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl); leave: if (string) { wipememory (string, strlen (string)); xfree (string); } if (value) { wipememory (value, valuelen); xfree (value); } return leave_cmd (ctx, err); } static const char hlp_getval[] = "GETVAL \n" "\n" "Return the value for KEY from the special environment as created by\n" "PUTVAL."; static gpg_error_t cmd_getval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *p; struct putval_item_s *vl; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list; vl; vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Got an entry. */ rc = assuan_send_data (ctx, vl->d+vl->off, vl->len); else return gpg_error (GPG_ERR_NO_DATA); return leave_cmd (ctx, rc); } static const char hlp_putval[] = "PUTVAL []\n" "\n" "The gpg-agent maintains a kind of environment which may be used to\n" "store key/value pairs in it, so that they can be retrieved later.\n" "This may be used by helper daemons to daemonize themself on\n" "invocation and register them with gpg-agent. Callers of the\n" "daemon's service may now first try connect to get the information\n" "for that service from gpg-agent through the GETVAL command and then\n" "try to connect to that daemon. Only if that fails they may start\n" "an own instance of the service daemon. \n" "\n" "KEY is an arbitrary symbol with the same syntax rules as keys\n" "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n" "corresponding value; they should be similar to the values of\n" "envronment variables but gpg-agent does not enforce any\n" "restrictions. If that value is not given any value under that KEY\n" "is removed from this special environment."; static gpg_error_t cmd_putval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *value = NULL; size_t valuelen = 0; char *p; struct putval_item_s *vl, *vlprev; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { value = p; p = strchr (value, ' '); if (p) *p = 0; valuelen = percent_plus_unescape_inplace (value, 0); } } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Delete old entry. */ { if (vlprev) vlprev->next = vl->next; else putval_list = vl->next; xfree (vl); } if (valuelen) /* Add entry. */ { vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen); if (!vl) rc = gpg_error_from_syserror (); else { vl->len = valuelen; vl->off = strlen (key) + 1; strcpy (vl->d, key); memcpy (vl->d + vl->off, value, valuelen); vl->next = putval_list; putval_list = vl; } } return leave_cmd (ctx, rc); } static const char hlp_updatestartuptty[] = "UPDATESTARTUPTTY\n" "\n" "Set startup TTY and X11 DISPLAY variables to the values of this\n" "session. This command is useful to pull future pinentries to\n" "another screen. It is only required because there is no way in the\n" "ssh-agent protocol to convey this information."; static gpg_error_t cmd_updatestartuptty (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; session_env_t se; char *lc_ctype = NULL; char *lc_messages = NULL; int iterator; const char *name; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); se = session_env_new (); if (!se) err = gpg_error_from_syserror (); iterator = 0; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { const char *value = session_env_getenv (ctrl->session_env, name); if (value) err = session_env_setenv (se, name, value); } if (!err && ctrl->lc_ctype) if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype))) err = gpg_error_from_syserror (); if (!err && ctrl->lc_messages) if (!(lc_messages = xtrystrdup (ctrl->lc_messages))) err = gpg_error_from_syserror (); if (err) { session_env_release (se); xfree (lc_ctype); xfree (lc_messages); } else { session_env_release (opt.startup_env); opt.startup_env = se; xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = lc_ctype; xfree (opt.startup_lc_messages); opt.startup_lc_messages = lc_messages; } return err; } static const char hlp_killagent[] = "KILLAGENT\n" "\n" "Stop the agent."; static gpg_error_t cmd_killagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloadagent[] = "RELOADAGENT\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); agent_sighup_action (); return 0; } static const char hlp_getinfo[] = "GETINFO \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" " socket_name - Return the name of the socket.\n" " ssh_socket_name - Return the name of the ssh socket.\n" " scd_running - Return OK if the SCdaemon is already running.\n" " s2k_time - Return the time in milliseconds required for S2K.\n" " s2k_count - Return the standard S2K count.\n" " s2k_count_cal - Return the calibrated S2K count.\n" " std_env_names - List the names of the standard environment.\n" " std_session_env - List the standard session environment.\n" " std_startup_env - List the standard startup environment.\n" " getenv NAME - Return value of envvar NAME.\n" " connections - Return number of active connections.\n" " jent_active - Returns OK if Libgcrypt's JENT is active.\n" " restricted - Returns OK if the connection is in restricted mode.\n" " cmd_has_option CMD OPT\n" " - Returns OK if command CMD has option OPT.\n"; 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 (!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_FALSE); } } } } else if (!strcmp (line, "s2k_count")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE); } else if (ctrl->restricted) { rc = gpg_error (GPG_ERR_FORBIDDEN); } /* All sub-commands below are not allowed in restricted mode. */ 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 = get_agent_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "ssh_socket_name")) { const char *s = get_agent_ssh_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "scd_running")) { rc = agent_daemon_check_running (DAEMON_SCD)? 0:gpg_error (GPG_ERR_FALSE); } else if (!strcmp (line, "std_env_names")) { int iterator; const char *name; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { rc = assuan_send_data (ctx, name, strlen (name)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); if (rc) break; } } else if (!strcmp (line, "std_session_env") || !strcmp (line, "std_startup_env")) { int iterator; const char *name, *value; char *string; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { value = session_env_getenv_or_default (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL); if (value) { string = xtryasprintf ("%s=%s", name, value); if (!string) rc = gpg_error_from_syserror (); else { rc = assuan_send_data (ctx, string, strlen (string)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (rc) break; } } } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else rc = assuan_send_data (ctx, s, strlen (s)); } } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_agent_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "jent_active")) { #if GCRYPT_VERSION_NUMBER >= 0x010800 char *buf; - char *fields[5]; + const char *fields[5]; buf = gcry_get_config (0, "rng-type"); if (buf && split_fields_colon (buf, fields, DIM (fields)) >= 5 && atoi (fields[4]) > 0) rc = 0; else rc = gpg_error (GPG_ERR_FALSE); gcry_free (buf); #else rc = gpg_error (GPG_ERR_FALSE); #endif } else if (!strcmp (line, "s2k_count_cal")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "s2k_time")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } /* This function is called by Libassuan to parse the OPTION command. It has been registered similar to the other Assuan 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, "agent-awareness")) { /* The value is a version string telling us of which agent version the caller is aware of. */ ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); } /* All options below are not allowed in restricted mode. */ else if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (ctrl->session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (ctrl->session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = xtrystrdup (value); if (!ctrl->lc_ctype) return out_of_core (); } else if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "use-cache-for-signing")) ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0; else if (!strcmp (key, "allow-pinentry-notify")) ctrl->server_local->allow_pinentry_notify = 1; else if (!strcmp (key, "pinentry-mode")) { int tmp = parse_pinentry_mode (value); if (tmp == -1) err = gpg_error (GPG_ERR_INV_VALUE); else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else ctrl->pinentry_mode = tmp; } else if (!strcmp (key, "cache-ttl-opt-preset")) { ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0; } else if (!strcmp (key, "s2k-count")) { ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0; if (ctrl->s2k_count && ctrl->s2k_count < 65536) { ctrl->s2k_count = 0; } } else if (!strcmp (key, "pretend-request-origin")) { log_assert (!ctrl->restricted); switch (parse_request_origin (value)) { case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break; case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break; case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break; default: err = gpg_error (GPG_ERR_INV_VALUE); /* Better pretend to be remote in case of a bad value. */ ctrl->restricted = 1; break; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } /* Called by libassuan after all commands. ERR is the error from the last assuan operation and not the one returned from the command. */ static void post_cmd_notify (assuan_context_t ctx, gpg_error_t err) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)err; /* Switch off any I/O monitor controlled logging pausing. */ ctrl->server_local->pause_io_logging = 0; } /* This function is called by libassuan for all I/O. We use it here to disable logging for the GETEVENTCOUNTER commands. This is so that the debug output won't get cluttered by this primitive command. */ static unsigned int io_monitor (assuan_context_t ctx, void *hook, int direction, const char *line, size_t linelen) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) hook; /* We want to suppress all Assuan log messages for connections from * self. However, assuan_get_pid works only after * assuan_accept. Now, assuan_accept already logs a line ending with * the process id. We use this hack here to get the peers pid so * that we can compare it to our pid. We should add an assuan * function to return the pid for a file descriptor and use that to * detect connections to self. */ if (ctx && !ctrl->server_local->greeting_seen && direction == ASSUAN_IO_TO_PEER) { ctrl->server_local->greeting_seen = 1; if (linelen > 32 && !strncmp (line, "OK Pleased to meet you, process ", 32) && strtoul (line+32, NULL, 10) == getpid ()) return ASSUAN_IO_MONITOR_NOLOG; } /* Do not log self-connections. This makes the log cleaner because * we won't see the check-our-own-socket calls. */ if (ctx && ctrl->server_local->connect_from_self) return ASSUAN_IO_MONITOR_NOLOG; /* Note that we only check for the uppercase name. This allows the user to see the logging for debugging if using a non-upercase command name. */ if (ctx && direction == ASSUAN_IO_FROM_PEER && linelen >= 15 && !strncmp (line, "GETEVENTCOUNTER", 15) && (linelen == 15 || spacep (line+15))) { ctrl->server_local->pause_io_logging = 1; } return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "GET_PASSPHRASE")) { if (!strcmp (cmdopt, "repeat")) return 1; if (!strcmp (cmdopt, "newsymkey")) return 1; } return 0; } /* Tell Libassuan about our commands. Also register the other Assuan handlers. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter }, { "ISTRUSTED", cmd_istrusted, hlp_istrusted }, { "HAVEKEY", cmd_havekey, hlp_havekey }, { "KEYINFO", cmd_keyinfo, hlp_keyinfo }, { "SIGKEY", cmd_sigkey, hlp_sigkey }, { "SETKEY", cmd_sigkey, hlp_sigkey }, { "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc }, { "SETHASH", cmd_sethash, hlp_sethash }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt }, { "GENKEY", cmd_genkey, hlp_genkey }, { "READKEY", cmd_readkey, hlp_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted }, { "MARKTRUSTED", cmd_marktrusted, hlp_martrusted }, { "LEARN", cmd_learn, hlp_learn }, { "PASSWD", cmd_passwd, hlp_passwd }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd, hlp_scd }, { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key }, { "IMPORT_KEY", cmd_import_key, hlp_import_key }, { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "DELETE_KEY", cmd_delete_key, hlp_delete_key }, { "GET_SECRET", cmd_get_secret, hlp_get_secret }, { "PUT_SECRET", cmd_put_secret, hlp_put_secret }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty }, { "KILLAGENT", cmd_killagent, hlp_killagent }, { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, { 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_register_post_cmd_notify (ctx, post_cmd_notify); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple piper server, otherwise it is a regular server. CTRL is the control structure for this connection; it has only the basic initialization. */ void start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) { int rc; assuan_context_t ctx = NULL; if (ctrl->restricted) { if (agent_copy_startup_env (ctrl)) return; } rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); agent_exit (2); } if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_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 if (listen_fd != GNUPG_INVALID_FD) { rc = assuan_init_socket_server (ctx, listen_fd, 0); /* FIXME: Need to call assuan_sock_set_nonce for Windows. But this branch is currently not used. */ } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); agent_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); agent_exit (2); } assuan_set_pointer (ctx, ctrl); ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->assuan_ctx = ctx; ctrl->server_local->use_cache_for_signing = 1; ctrl->digest.data = NULL; ctrl->digest.raw_value = 0; ctrl->digest.is_pss = 0; assuan_set_io_monitor (ctx, io_monitor, NULL); agent_set_progress_cb (progress_cb, ctrl); for (;;) { assuan_peercred_t client_creds; /* Note: Points into CTX. */ pid_t pid; rc = assuan_accept (ctx); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_get_peercred (ctx, &client_creds); if (rc) { if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) ; else log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc)); pid = assuan_get_pid (ctx); ctrl->client_uid = -1; } else { #ifdef HAVE_W32_SYSTEM pid = assuan_get_pid (ctx); ctrl->client_uid = -1; #else pid = client_creds->pid; ctrl->client_uid = client_creds->uid; #endif } ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid; ctrl->server_local->connect_from_self = (pid == getpid ()); rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Clear the keyinfo cache. */ agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki); /* Reset the nonce caches. */ clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ agent_reset_daemon (ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (ctrl); /* Cleanup. */ assuan_release (ctx); xfree (ctrl->server_local->keydesc); xfree (ctrl->server_local->import_key); xfree (ctrl->server_local->export_key); if (ctrl->server_local->stopme) agent_exit (0); xfree (ctrl->server_local); ctrl->server_local = NULL; } /* Helper for the pinentry loopback mode. It merely passes the parameters on to the client. */ gpg_error_t pinentry_loopback(ctrl_t ctrl, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length) { gpg_error_t rc; assuan_context_t ctx = ctrl->server_local->assuan_ctx; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length); if (rc) return rc; assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, keyword, buffer, size, max_length); assuan_end_confidential (ctx); return rc; } /* Helper for the pinentry loopback mode to ask confirmation or just to show message. */ gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc, int ask_confirmation, const char *ok, const char *notok) { gpg_error_t err = 0; assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (desc) err = print_assuan_status (ctx, "SETDESC", "%s", desc); if (!err && ok) err = print_assuan_status (ctx, "SETOK", "%s", ok); if (!err && notok) err = print_assuan_status (ctx, "SETNOTOK", "%s", notok); if (!err) err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0", NULL, NULL, 0); return err; } diff --git a/common/stringhelp.c b/common/stringhelp.c index d7bb6bc37..a324bfbbc 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -1,1663 +1,1665 @@ /* stringhelp.c - standard string helper functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch * Copyright (C) 2015 g10 Code GmbH * * 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 . */ #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #include #include "util.h" #include "common-defs.h" #include "utf8conv.h" #include "sysutils.h" #include "stringhelp.h" #define tohex_lower(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'a')) /* Sometimes we want to avoid mixing slashes and backslashes on W32 and prefer backslashes. There is usual no problem with mixing them, however a very few W32 API calls can't grok plain slashes. Printing filenames with mixed slashes also looks a bit strange. This function has no effext on POSIX. */ static inline char * change_slashes (char *name) { #ifdef HAVE_DOSISH_SYSTEM char *p; if (strchr (name, '\\')) { for (p=name; *p; p++) if (*p == '/') *p = '\\'; } #endif /*HAVE_DOSISH_SYSTEM*/ return name; } /* * Check whether STRING starts with KEYWORD. The keyword is * delimited by end of string, a space or a tab. Returns NULL if not * found or a pointer into STRING to the next non-space character * after the KEYWORD (which may be end of string). */ char * has_leading_keyword (const char *string, const char *keyword) { size_t n = strlen (keyword); if (!strncmp (string, keyword, n) && (!string[n] || string[n] == ' ' || string[n] == '\t')) { string += n; while (*string == ' ' || *string == '\t') string++; return (char*)string; } return NULL; } /* * Look for the substring SUB in buffer and return a pointer to that * substring in BUFFER or NULL if not found. * Comparison is case-insensitive. */ const char * memistr (const void *buffer, size_t buflen, const char *sub) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buffer; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if ( toupper (*t) == toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && toupper (*t) == toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } const char * ascii_memistr ( 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 (ascii_toupper (*t) == ascii_toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && ascii_toupper (*t) == ascii_toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = (const unsigned char *)buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } /* This function is similar to strncpy(). However it won't copy more than N - 1 characters and makes sure that a '\0' is appended. With N given as 0, nothing will happen. With DEST given as NULL, memory will be allocated using xmalloc (i.e. if it runs out of core the function terminates). Returns DES or a pointer to the allocated memory. */ char * mem2str( char *dest , const void *src , size_t n ) { char *d; const char *s; if( n ) { if( !dest ) dest = xmalloc( n ) ; d = dest; s = src ; for(n--; n && *s; n-- ) *d++ = *s++; *d = '\0' ; } return dest ; } /**************** * remove leading and trailing white spaces */ char * trim_spaces( char *str ) { char *string, *p, *mark; string = str; /* find first non space character */ for( p=string; *p && isspace( *(byte*)p ) ; p++ ) ; /* move characters */ for( (mark = NULL); (*string = *p); string++, p++ ) if( isspace( *(byte*)p ) ) { if( !mark ) mark = string ; } else mark = NULL ; if( mark ) *mark = '\0' ; /* remove trailing spaces */ return str ; } /* Same as trim_spaces but only condider, space, tab, cr and lf as space. */ char * ascii_trim_spaces (char *str) { char *string, *p, *mark; string = str; /* Find first non-ascii space character. */ for (p=string; *p && ascii_isspace (*p); p++) ; /* Move characters. */ for (mark=NULL; (*string = *p); string++, p++ ) { if (ascii_isspace (*p)) { if (!mark) mark = string; } else mark = NULL ; } if (mark) *mark = '\0' ; /* Remove trailing spaces. */ return str ; } /**************** * remove trailing white spaces */ char * trim_trailing_spaces( char *string ) { char *p, *mark; for( mark = NULL, p = string; *p; p++ ) { if( isspace( *(byte*)p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) *mark = '\0' ; return string ; } unsigned trim_trailing_chars( byte *line, unsigned len, const char *trimchars ) { byte *p, *mark; unsigned n; for(mark=NULL, p=line, n=0; n < len; n++, p++ ) { if( strchr(trimchars, *p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) { *mark = 0; return mark - line; } return len; } /**************** * remove trailing white spaces and return the length of the buffer */ unsigned trim_trailing_ws( byte *line, unsigned len ) { return trim_trailing_chars( line, len, " \t\r\n" ); } size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ) { const unsigned char *p, *mark; size_t n; for( mark=NULL, p=line, n=0; n < len; n++, p++ ) { if (strchr (trimchars, *p )) { if( !mark ) mark = p; } else mark = NULL; } if (mark) return mark - line; return len; } /* * Return the length of line ignoring trailing white-space. */ size_t length_sans_trailing_ws (const unsigned char *line, size_t len) { return length_sans_trailing_chars (line, len, " \t\r\n"); } /* * Extract from a given path the filename component. This function * terminates the process on memory shortage. */ char * make_basename(const char *filepath, const char *inputpath) { #ifdef __riscos__ return riscos_make_basename(filepath, inputpath); #else char *p; (void)inputpath; /* Only required for riscos. */ if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup(filepath); } return xstrdup(p+1); #endif } /* * Extract from a given filename the path prepended to it. If there * isn't a path prepended to the filename, a dot is returned ('.'). * This function terminates the process on memory shortage. */ char * make_dirname(const char *filepath) { char *dirname; int dirname_length; char *p; if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup("."); } dirname_length = p-filepath; dirname = xmalloc(dirname_length+1); strncpy(dirname, filepath, dirname_length); dirname[dirname_length] = 0; return dirname; } static char * get_pwdir (int xmode, const char *name) { char *result = NULL; #ifdef HAVE_PWD_H struct passwd *pwd = NULL; if (name) { #ifdef HAVE_GETPWNAM /* Fixme: We should use getpwnam_r if available. */ pwd = getpwnam (name); #endif } else { #ifdef HAVE_GETPWUID /* Fixme: We should use getpwuid_r if available. */ pwd = getpwuid (getuid()); #endif } if (pwd) { if (xmode) result = xstrdup (pwd->pw_dir); else result = xtrystrdup (pwd->pw_dir); } #else /*!HAVE_PWD_H*/ /* No support at all. */ (void)xmode; (void)name; #endif /*HAVE_PWD_H*/ return result; } /* xmode 0 := Return NULL on error 1 := Terminate on error 2 := Make sure that name is absolute; return NULL on error 3 := Make sure that name is absolute; terminate on error */ static char * do_make_filename (int xmode, const char *first_part, va_list arg_ptr) { const char *argv[32]; int argc; size_t n; int skip = 1; char *home_buffer = NULL; char *name, *home, *p; int want_abs; want_abs = !!(xmode & 2); xmode &= 1; n = strlen (first_part) + 1; argc = 0; while ( (argv[argc] = va_arg (arg_ptr, const char *)) ) { n += strlen (argv[argc]) + 1; if (argc >= DIM (argv)-1) { if (xmode) BUG (); gpg_err_set_errno (EINVAL); return NULL; } argc++; } n++; home = NULL; if (*first_part == '~') { if (first_part[1] == '/' || !first_part[1]) { /* This is the "~/" or "~" case. */ home = getenv("HOME"); if (!home) home = home_buffer = get_pwdir (xmode, NULL); if (home && *home) n += strlen (home); } else { /* This is the "~username/" or "~username" case. */ char *user; if (xmode) user = xstrdup (first_part+1); else { user = xtrystrdup (first_part+1); if (!user) return NULL; } p = strchr (user, '/'); if (p) *p = 0; skip = 1 + strlen (user); home = home_buffer = get_pwdir (xmode, user); xfree (user); if (home) n += strlen (home); else skip = 1; } } if (xmode) name = xmalloc (n); else { name = xtrymalloc (n); if (!name) { xfree (home_buffer); return NULL; } } if (home) p = stpcpy (stpcpy (name, home), first_part + skip); else p = stpcpy (name, first_part); xfree (home_buffer); for (argc=0; argv[argc]; argc++) { /* Avoid a leading double slash if the first part was "/". */ if (!argc && name[0] == '/' && !name[1]) p = stpcpy (p, argv[argc]); else p = stpcpy (stpcpy (p, "/"), argv[argc]); } if (want_abs) { #ifdef HAVE_DRIVE_LETTERS p = strchr (name, ':'); if (p) p++; else p = name; #else p = name; #endif if (*p != '/' #ifdef HAVE_DRIVE_LETTERS && *p != '\\' #endif ) { home = gnupg_getcwd (); if (!home) { if (xmode) { fprintf (stderr, "\nfatal: getcwd failed: %s\n", strerror (errno)); exit(2); } xfree (name); return NULL; } n = strlen (home) + 1 + strlen (name) + 1; if (xmode) home_buffer = xmalloc (n); else { home_buffer = xtrymalloc (n); if (!home_buffer) { xfree (home); xfree (name); return NULL; } } if (p == name) p = home_buffer; else /* Windows case. */ { memcpy (home_buffer, p, p - name + 1); p = home_buffer + (p - name + 1); } /* Avoid a leading double slash if the cwd is "/". */ if (home[0] == '/' && !home[1]) strcpy (stpcpy (p, "/"), name); else strcpy (stpcpy (stpcpy (p, home), "/"), name); xfree (home); xfree (name); name = home_buffer; /* Let's do a simple compression to catch the most common case of using "." for gpg's --homedir option. */ n = strlen (name); if (n > 2 && name[n-2] == '/' && name[n-1] == '.') name[n-2] = 0; } } return change_slashes (name); } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_filename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (1, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_filename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (0, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_absfilename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (3, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_absfilename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (2, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Compare whether the filenames are identical. This is a special version of strcmp() taking the semantics of filenames in account. Note that this function works only on the supplied names without considering any context like the current directory. See also same_file_p(). */ int compare_filenames (const char *a, const char *b) { #ifdef HAVE_DOSISH_SYSTEM for ( ; *a && *b; a++, b++ ) { if (*a != *b && (toupper (*(const unsigned char*)a) != toupper (*(const unsigned char*)b) ) && !((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/'))) break; } if ((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/')) return 0; else return (toupper (*(const unsigned char*)a) - toupper (*(const unsigned char*)b)); #else return strcmp(a,b); #endif } /* Convert a base-10 number in STRING into a 64 bit unsigned int * value. Leading white spaces are skipped but no error checking is * done. Thus it is similar to atoi(). */ uint64_t string_to_u64 (const char *string) { uint64_t val = 0; while (spacep (string)) string++; for (; digitp (string); string++) { val *= 10; val += *string - '0'; } return val; } /* Convert 2 hex characters at S to a byte value. Return this value or -1 if there is an error. */ int hextobyte (const char *s) { int c; if ( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if ( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if ( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if ( *s >= '0' && *s <= '9' ) c += *s - '0'; else if ( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if ( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } /* Given a string containing an UTF-8 encoded text, return the number of characters in this string. It differs from strlen in that it only counts complete UTF-8 characters. SIZE is the maximum length of the string in bytes. If SIZE is -1, then a NUL character is taken to be the end of the string. Note, that this function does not take combined characters into account. */ size_t utf8_charcount (const char *s, int len) { size_t n; if (len == 0) return 0; for (n=0; *s; s++) { if ( (*s&0xc0) != 0x80 ) /* Exclude continuation bytes: 10xxxxxx */ n++; if (len != -1) { len --; if (len == 0) break; } } return n; } /**************************************************** ********** W32 specific functions **************** ****************************************************/ #ifdef HAVE_W32_SYSTEM const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); #ifdef HAVE_W32CE_SYSTEM /* There is only a wchar_t FormatMessage. It does not make much sense to play the conversion game; we print only the code. */ snprintf (strerr, sizeof strerr, "ec=%d", (int)GetLastError ()); #else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, DIM (strerr)-1, NULL); { /* Strip the CR,LF - we want just the string. */ size_t n = strlen (strerr); if (n > 2 && strerr[n-2] == '\r' && strerr[n-1] == '\n' ) strerr[n-2] = 0; } #endif return strerr; } #endif /*HAVE_W32_SYSTEM*/ /**************************************************** ******** Locale insensitive ctype functions ******** ****************************************************/ /* FIXME: replace them by a table lookup and macros */ int ascii_isupper (int c) { return c >= 'A' && c <= 'Z'; } int ascii_islower (int c) { return c >= 'a' && c <= 'z'; } int ascii_toupper (int c) { if (c >= 'a' && c <= 'z') c &= ~0x20; return c; } int ascii_tolower (int c) { if (c >= 'A' && c <= 'Z') c |= 0x20; return c; } /* Lowercase all ASCII characters in S. */ char * ascii_strlwr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'A' && *p <= 'Z') *p |= 0x20; return s; } /* Upcase all ASCII characters in S. */ char * ascii_strupr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'a' && *p <= 'z') *p &= ~0x20; return s; } int ascii_strcasecmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b && ascii_toupper(*a) != ascii_toupper(*b)) break; } return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } int ascii_strncasecmp (const char *a, const char *b, size_t n) { const unsigned char *p1 = (const unsigned char *)a; const unsigned char *p2 = (const unsigned char *)b; unsigned char c1, c2; if (p1 == p2 || !n ) return 0; do { c1 = ascii_tolower (*p1); c2 = ascii_tolower (*p2); if ( !--n || c1 == '\0') break; ++p1; ++p2; } while (c1 == c2); return c1 - c2; } int ascii_memcasecmp (const void *a_arg, const void *b_arg, size_t n ) { const char *a = a_arg; const char *b = b_arg; if (a == b) return 0; for ( ; n; n--, a++, b++ ) { if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } return 0; } int ascii_strcmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b ) break; } return *a == *b? 0 : (*(signed char *)a - *(signed char *)b); } void * ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle) { if (!nneedle) return (void*)haystack; /* finding an empty needle is really easy */ if (nneedle <= nhaystack) { const char *a = haystack; const char *b = a + nhaystack - nneedle; for (; a <= b; a++) { if ( !ascii_memcasecmp (a, needle, nneedle) ) return (void *)a; } } return NULL; } /********************************************* ********** missing string functions ********* *********************************************/ #ifndef HAVE_STPCPY char * stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #endif #ifndef HAVE_STRPBRK /* Find the first occurrence in S of any character in ACCEPT. Code taken from glibc-2.6/string/strpbrk.c (LGPLv2.1+) and modified. */ char * strpbrk (const char *s, const char *accept) { while (*s != '\0') { const char *a = accept; while (*a != '\0') if (*a++ == *s) return (char *) s; ++s; } return NULL; } #endif /*!HAVE_STRPBRK*/ #ifndef HAVE_STRSEP /* Code taken from glibc-2.2.1/sysdeps/generic/strsep.c. */ char * strsep (char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; /* A frequent case is when the delimiter string contains only one character. Here we don't need to call the expensive 'strpbrk' function and instead work using 'strchr'. */ if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') end = NULL; else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr (begin + 1, ch); } } else /* Find the end of the token. */ end = strpbrk (begin, delim); if (end) { /* Terminate the token and set *STRINGP past NUL character. */ *end++ = '\0'; *stringp = end; } else /* No more delimiters; this is the last token. */ *stringp = NULL; return begin; } #endif /*HAVE_STRSEP*/ #ifndef HAVE_STRLWR char * strlwr(char *s) { char *p; for(p=s; *p; p++ ) *p = tolower(*p); return s; } #endif #ifndef HAVE_STRCASECMP int strcasecmp( const char *a, const char *b ) { for( ; *a && *b; a++, b++ ) { if( *a != *b && toupper(*a) != toupper(*b) ) break; } return *(const byte*)a - *(const byte*)b; } #endif /**************** * mingw32/cpd has a memicmp() */ #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ) { for( ; n; n--, a++, b++ ) if( *a != *b && toupper(*(const byte*)a) != toupper(*(const byte*)b) ) return *(const byte *)a - *(const byte*)b; return 0; } #endif #ifndef HAVE_MEMRCHR void * memrchr (const void *buffer, int c, size_t n) { const unsigned char *p = buffer; for (p += n; n ; n--) if (*--p == c) return (void *)p; return NULL; } #endif /*HAVE_MEMRCHR*/ /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. */ static char * do_percent_escape (const char *str, const char *extra, int die) { int i, j; char *ptr; if (!str) return NULL; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || str[i] == '\n' || (extra && strchr (extra, str[i]))) j++; if (die) ptr = xmalloc (i + 2 * j + 1); else { ptr = xtrymalloc (i + 2 * j + 1); if (!ptr) return NULL; } i = 0; while (*str) { if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (*str == '\n') { /* The newline is problematic in a line-based format. */ ptr[i++] = '%'; ptr[i++] = '0'; ptr[i++] = 'a'; } else if (extra && strchr (extra, *str)) { ptr[i++] = '%'; ptr[i++] = tohex_lower ((*str>>4)&15); ptr[i++] = tohex_lower (*str&15); } else ptr[i++] = *str; str++; } ptr[i] = '\0'; return ptr; } /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. This function terminates the process on memory shortage. */ char * percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 1); } /* Same as percent_escape but return NULL instead of exiting on memory error. */ char * try_percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 0); } static char * do_strconcat (const char *s1, va_list arg_ptr) { const char *argv[48]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) { gpg_err_set_errno (EINVAL); return NULL; } argc++; } needed++; buffer = xtrymalloc (needed); if (buffer) { for (p = buffer, argc=0; argv[argc]; argc++) p = stpcpy (p, argv[argc]); } return buffer; } /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char * strconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xtrystrdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } /* Same as strconcat but terminate the process with an error message if something goes wrong. */ char * xstrconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xstrdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } if (!result) { if (errno == EINVAL) fputs ("\nfatal: too many args for xstrconcat\n", stderr); else fputs ("\nfatal: out of memory\n", stderr); exit (2); } return result; } /* Split a string into fields at DELIM. REPLACEMENT is the character to replace the delimiter with (normally: '\0' so that each field is NUL terminated). The caller is responsible for freeing the result. Note: this function modifies STRING! If you need the original value, then you should pass a copy to this function. If malloc fails, this function returns NULL. */ char ** strsplit (char *string, char delim, char replacement, int *count) { int fields = 1; char *t; char **result; /* First, count the number of fields. */ for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) fields ++; result = xtrycalloc ((fields + 1), sizeof (*result)); if (! result) return NULL; result[0] = string; fields = 1; for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) { result[fields ++] = t + 1; *t = replacement; } if (count) *count = fields; return result; } /* Tokenize STRING using the set of delimiters in DELIM. Leading * spaces and tabs are removed from all tokens. The caller must xfree * the result. * * Returns: A malloced and NULL delimited array with the tokens. On * memory error NULL is returned and ERRNO is set. */ char ** strtokenize (const char *string, const char *delim) { const char *s; size_t fields; size_t bytes, n; char *buffer; char *p, *px, *pend; char **result; /* Count the number of fields. */ for (fields = 1, s = strpbrk (string, delim); s; s = strpbrk (s + 1, delim)) fields++; fields++; /* Add one for the terminating NULL. */ /* Allocate an array for all fields, a terminating NULL, and space for a copy of the string. */ bytes = fields * sizeof *result; if (bytes / sizeof *result != fields) { gpg_err_set_errno (ENOMEM); return NULL; } n = strlen (string) + 1; bytes += n; if (bytes < n) { gpg_err_set_errno (ENOMEM); return NULL; } result = xtrymalloc (bytes); if (!result) return NULL; buffer = (char*)(result + fields); /* Copy and parse the string. */ strcpy (buffer, string); for (n = 0, p = buffer; (pend = strpbrk (p, delim)); p = pend + 1) { *pend = 0; while (spacep (p)) p++; for (px = pend - 1; px >= p && spacep (px); px--) *px = 0; result[n++] = p; } while (spacep (p)) p++; for (px = p + strlen (p) - 1; px >= p && spacep (px); px--) *px = 0; result[n++] = p; result[n] = NULL; assert ((char*)(result + n + 1) == buffer); return result; } /* Split a string into space delimited fields and remove leading and * trailing spaces from each field. A pointer to each field is stored * in ARRAY. Stop splitting at ARRAYSIZE fields. The function * modifies STRING. The number of parsed fields is returned. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int -split_fields (char *string, char **array, int arraysize) +split_fields (char *string, const char **array, int arraysize) { int n = 0; - char *p, *pend; + const char *p; + char *pend; for (p = string; *p == ' '; p++) ; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ' '); if (!pend) break; *pend++ = 0; for (p = pend; *p == ' '; p++) ; } while (*p); return n; } /* Split a string into colon delimited fields A pointer to each field * is stored in ARRAY. Stop splitting at ARRAYSIZE fields. The * function modifies STRING. The number of parsed fields is returned. * Note that leading and trailing spaces are not removed from the fields. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int -split_fields_colon (char *string, char **array, int arraysize) +split_fields_colon (char *string, const char **array, int arraysize) { int n = 0; - char *p, *pend; + const char *p; + char *pend; p = string; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ':'); if (!pend) break; *pend++ = 0; p = pend; } while (*p); return n; } /* Version number parsing. */ /* This function parses the first portion of the version number S and stores it in *NUMBER. On success, this function returns a pointer into S starting with the first character, which is not part of the initial number portion; on failure, NULL is returned. */ static const char* parse_version_number (const char *s, int *number) { int val = 0; if (*s == '0' && digitp (s+1)) return NULL; /* Leading zeros are not allowed. */ for (; digitp (s); s++) { val *= 10; val += *s - '0'; } *number = val; return val < 0 ? NULL : s; } /* This function breaks up the complete string-representation of the version number S, which is of the following structure: .[.]. The major, minor, and micro number components will be stored in *MAJOR, *MINOR and *MICRO. If MICRO is not given 0 is used instead. On success, the last component, the patch level, will be returned; in failure, NULL will be returned. */ static const char * parse_version_string (const char *s, int *major, int *minor, int *micro) { s = parse_version_number (s, major); if (!s || *s != '.') return NULL; s++; s = parse_version_number (s, minor); if (!s) return NULL; if (*s == '.') { s++; s = parse_version_number (s, micro); if (!s) return NULL; } else *micro = 0; return s; /* Patchlevel. */ } /* Compare the version string MY_VERSION to the version string * REQ_VERSION. Returns -1, 0, or 1 if MY_VERSION is found, * respectively, to be less than, to match, or be greater than * REQ_VERSION. This function works for three and two part version * strings; for a two part version string the micro part is assumed to * be 0. Patch levels are compared as strings. If a version number * is invalid INT_MIN is returned. If REQ_VERSION is given as NULL * the function returns 0 if MY_VERSION is parsable version string. */ int compare_version_strings (const char *my_version, const char *req_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_patch, *rq_patch; int result; if (!my_version) return INT_MIN; my_patch = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_patch) return INT_MIN; if (!req_version) return 0; /* MY_VERSION can be parsed. */ rq_patch = parse_version_string (req_version, &rq_major, &rq_minor,&rq_micro); if (!rq_patch) return INT_MIN; if (my_major == rq_major) { if (my_minor == rq_minor) { if (my_micro == rq_micro) result = strcmp (my_patch, rq_patch); else result = my_micro - rq_micro; } else result = my_minor - rq_minor; } else result = my_major - rq_major; return !result? 0 : result < 0 ? -1 : 1; } /* Format a string so that it fits within about TARGET_COLS columns. * TEXT_IN is copied to a new buffer, which is returned. Normally, * target_cols will be 72 and max_cols is 80. On error NULL is * returned and ERRNO is set. */ char * format_text (const char *text_in, int target_cols, int max_cols) { /* const int do_debug = 0; */ /* The character under consideration. */ char *p; /* The start of the current line. */ char *line; /* The last space that we saw. */ char *last_space = NULL; int last_space_cols = 0; int copied_last_space = 0; char *text; text = xtrystrdup (text_in); if (!text) return NULL; p = line = text; while (1) { /* The number of columns including any trailing space. */ int cols; p = p + strcspn (p, "\n "); if (! p) /* P now points to the NUL character. */ p = &text[strlen (text)]; if (*p == '\n') /* Pass through any newlines. */ { p ++; line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 1; continue; } /* Have a space or a NUL. Note: we don't count the trailing space. */ cols = utf8_charcount (line, (uintptr_t) p - (uintptr_t) line); if (cols < target_cols) { if (! *p) /* Nothing left to break. */ break; last_space = p; last_space_cols = cols; p ++; /* Skip any immediately following spaces. If we break: "... foo bar ..." between "foo" and "bar" then we want: "... foo\nbar ...", which means that the left space has to be the first space after foo, not the last space before bar. */ while (*p == ' ') p ++; } else { int cols_with_left_space; int cols_with_right_space; int left_penalty; int right_penalty; cols_with_left_space = last_space_cols; cols_with_right_space = cols; /* if (do_debug) */ /* log_debug ("Breaking: '%.*s'\n", */ /* (int) ((uintptr_t) p - (uintptr_t) line), line); */ /* The number of columns away from TARGET_COLS. We prefer to underflow than to overflow. */ left_penalty = target_cols - cols_with_left_space; right_penalty = 2 * (cols_with_right_space - target_cols); if (cols_with_right_space > max_cols) /* Add a large penalty for each column that exceeds max_cols. */ right_penalty += 4 * (cols_with_right_space - max_cols); /* if (do_debug) */ /* log_debug ("Left space => %d cols (penalty: %d); " */ /* "right space => %d cols (penalty: %d)\n", */ /* cols_with_left_space, left_penalty, */ /* cols_with_right_space, right_penalty); */ if (last_space_cols && left_penalty <= right_penalty) { /* Prefer the left space. */ /* if (do_debug) */ /* log_debug ("Breaking at left space.\n"); */ p = last_space; } else { /* if (do_debug) */ /* log_debug ("Breaking at right space.\n"); */ } if (! *p) break; *p = '\n'; p ++; if (*p == ' ') { int spaces; for (spaces = 1; p[spaces] == ' '; spaces ++) ; memmove (p, &p[spaces], strlen (&p[spaces]) + 1); } line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 0; } } /* Chop off any trailing space. */ trim_trailing_chars (text, strlen (text), " "); /* If we inserted the trailing newline, then remove it. */ if (! copied_last_space && *text && text[strlen (text) - 1] == '\n') text[strlen (text) - 1] = '\0'; return text; } diff --git a/common/stringhelp.h b/common/stringhelp.h index 42bb19aaf..c5f252bbc 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -1,170 +1,170 @@ /* stringhelp.h * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2006, 2007, 2009 Free Software Foundation, Inc. * 2015 g10 Code GmbH * * 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 . */ #ifndef GNUPG_COMMON_STRINGHELP_H #define GNUPG_COMMON_STRINGHELP_H #include #include "types.h" /*-- stringhelp.c --*/ char *has_leading_keyword (const char *string, const char *keyword); const char *memistr (const void *buf, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); char *ascii_trim_spaces (char *string); char *trim_trailing_spaces( char *string ); unsigned int trim_trailing_chars( unsigned char *line, unsigned len, const char *trimchars); unsigned int trim_trailing_ws( unsigned char *line, unsigned len ); size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ); size_t length_sans_trailing_ws (const unsigned char *line, size_t len); char *make_basename(const char *filepath, const char *inputpath); char *make_dirname(const char *filepath); char *make_filename( const char *first_part, ... ) GPGRT_ATTR_SENTINEL(0); char *make_filename_try (const char *first_part, ... ) GPGRT_ATTR_SENTINEL(0); char *make_absfilename (const char *first_part, ...) GPGRT_ATTR_SENTINEL(0); char *make_absfilename_try (const char *first_part, ...) GPGRT_ATTR_SENTINEL(0); int compare_filenames( const char *a, const char *b ); uint64_t string_to_u64 (const char *string); int hextobyte (const char *s); size_t utf8_charcount (const char *s, int len); #ifdef HAVE_W32_SYSTEM const char *w32_strerror (int ec); #endif int ascii_isupper (int c); int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); char *ascii_strlwr (char *s); char *ascii_strupr (char *s); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); int ascii_memcasecmp( const void *a, const void *b, size_t n ); const char *ascii_memistr ( const void *buf, size_t buflen, const char *sub); void *ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle); #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ); #endif #ifndef HAVE_STPCPY char *stpcpy(char *a,const char *b); #endif #ifndef HAVE_STRPBRK char *strpbrk (const char *s, const char *accept); #endif #ifndef HAVE_STRSEP char *strsep (char **stringp, const char *delim); #endif #ifndef HAVE_STRLWR char *strlwr(char *a); #endif #ifndef HAVE_STRTOUL # define strtoul(a,b,c) ((unsigned long)strtol((a),(b),(c))) #endif #ifndef HAVE_MEMMOVE # define memmove(d, s, n) bcopy((s), (d), (n)) #endif #ifndef HAVE_STRICMP # define stricmp(a,b) strcasecmp( (a), (b) ) #endif #ifndef HAVE_MEMRCHR void *memrchr (const void *buffer, int c, size_t n); #endif #ifndef HAVE_ISASCII static inline int isascii (int c) { return (((c) & ~0x7f) == 0); } #endif /* !HAVE_ISASCII */ #ifndef STR # define STR(v) #v #endif #define STR2(v) STR(v) /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL, also replace all characters given in EXTRA. The "try_" variant fails with NULL if not enough memory can be allocated. */ char *percent_escape (const char *str, const char *extra); char *try_percent_escape (const char *str, const char *extra); /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char *strconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); /* Ditto, but die on error. */ char *xstrconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); char **strsplit (char *string, char delim, char replacement, int *count); /* Tokenize STRING using the set of delimiters in DELIM. */ char **strtokenize (const char *string, const char *delim); /* Split STRING into space delimited fields and store them in the * provided ARRAY. */ -int split_fields (char *string, char **array, int arraysize); +int split_fields (char *string, const char **array, int arraysize); /* Split STRING into colon delimited fields and store them in the * provided ARRAY. */ -int split_fields_colon (char *string, char **array, int arraysize); +int split_fields_colon (char *string, const char **array, int arraysize); /* Return True if MYVERSION is greater or equal than REQ_VERSION. */ int compare_version_strings (const char *my_version, const char *req_version); /* Format a string so that it fits within about TARGET_COLS columns. */ char *format_text (const char *text, int target_cols, int max_cols); /*-- mapstrings.c --*/ const char *map_static_macro_string (const char *string); #endif /*GNUPG_COMMON_STRINGHELP_H*/ diff --git a/common/t-stringhelp.c b/common/t-stringhelp.c index 7c6fb8022..6f7f6f7af 100644 --- a/common/t-stringhelp.c +++ b/common/t-stringhelp.c @@ -1,1080 +1,1080 @@ /* t-stringhelp.c - Regression tests for stringhelp.c * Copyright (C) 2007 Free Software Foundation, Inc. * 2015 g10 Code GmbH * * 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 . */ #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #include #include #include "t-support.h" #include "stringhelp.h" static char *home_buffer; const char * gethome (void) { if (!home_buffer) { char *home = getenv("HOME"); if(home) home_buffer = xstrdup (home); #if defined(HAVE_GETPWUID) && defined(HAVE_PWD_H) else { struct passwd *pwd; pwd = getpwuid (getuid()); if (pwd) home_buffer = xstrdup (pwd->pw_dir); } #endif } return home_buffer; } static char * mygetcwd (void) { char *buffer; size_t size = 100; for (;;) { buffer = xmalloc (size+1); #ifdef HAVE_W32CE_SYSTEM strcpy (buffer, "/"); /* Always "/". */ return buffer; #else if (getcwd (buffer, size) == buffer) return buffer; xfree (buffer); if (errno != ERANGE) { fprintf (stderr,"error getting current cwd: %s\n", strerror (errno)); exit (2); } size *= 2; #endif } } static void test_percent_escape (void) { char *result; static struct { const char *extra; const char *value; const char *expected; } tests[] = { { NULL, "", "" }, { NULL, "%", "%25" }, { NULL, "%%", "%25%25" }, { NULL, " %", " %25" }, { NULL, ":", "%3a" }, { NULL, " :", " %3a" }, { NULL, ": ", "%3a " }, { NULL, " : ", " %3a " }, { NULL, "::", "%3a%3a" }, { NULL, ": :", "%3a %3a" }, { NULL, "%:", "%25%3a" }, { NULL, ":%", "%3a%25" }, { "\\\n:", ":%", "%3a%25" }, { "\\\n:", "\\:%", "%5c%3a%25" }, { "\\\n:", "\n:%", "%0a%3a%25" }, { "\\\n:", "\xff:%", "\xff%3a%25" }, { "\\\n:", "\xfe:%", "\xfe%3a%25" }, { "\\\n:", "\x01:%", "\x01%3a%25" }, { "\x01", "\x01:%", "%01%3a%25" }, { "\xfe", "\xfe:%", "%fe%3a%25" }, { "\xfe", "\xff:%", "\xff%3a%25" }, { NULL, NULL, NULL } }; int testno; result = percent_escape (NULL, NULL); if (result) fail (0); for (testno=0; tests[testno].value; testno++) { result = percent_escape (tests[testno].value, tests[testno].extra); if (!result) fail (testno); else if (strcmp (result, tests[testno].expected)) fail (testno); xfree (result); } } static void test_compare_filenames (void) { struct { const char *a; const char *b; int result; } tests[] = { { "", "", 0 }, { "", "a", -1 }, { "a", "", 1 }, { "a", "a", 0 }, { "a", "aa", -1 }, { "aa", "a", 1 }, { "a", "b", -1 }, #ifdef HAVE_W32_SYSTEM { "a", "A", 0 }, { "A", "a", 0 }, { "foo/bar", "foo\\bar", 0 }, { "foo\\bar", "foo/bar", 0 }, { "foo\\", "foo/", 0 }, { "foo/", "foo\\", 0 }, #endif /*HAVE_W32_SYSTEM*/ { NULL, NULL, 0} }; int testno, result; for (testno=0; tests[testno].a; testno++) { result = compare_filenames (tests[testno].a, tests[testno].b); result = result < 0? -1 : result > 0? 1 : 0; if (result != tests[testno].result) fail (testno); } } static void test_strconcat (void) { char *out; out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", NULL); if (!out) fail (0); else xfree (out); out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); out = strconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); #if __GNUC__ < 4 /* gcc 4.0 has a sentinel attribute. */ out = strconcat (NULL); if (!out || *out) fail (1); #endif out = strconcat (NULL, NULL); if (!out || *out) fail (1); xfree (out); out = strconcat ("", NULL); if (!out || *out) fail (1); xfree (out); out = strconcat ("", "", NULL); if (!out || *out) fail (2); xfree (out); out = strconcat ("a", "b", NULL); if (!out || strcmp (out, "ab")) fail (3); xfree (out); out = strconcat ("a", "b", "c", NULL); if (!out || strcmp (out, "abc")) fail (3); xfree (out); out = strconcat ("a", "b", "cc", NULL); if (!out || strcmp (out, "abcc")) fail (4); xfree (out); out = strconcat ("a1", "b1", "c1", NULL); if (!out || strcmp (out, "a1b1c1")) fail (4); xfree (out); out = strconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); out = strconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); } static void test_xstrconcat (void) { char *out; out = xstrconcat ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", NULL); if (!out) fail (0); xfree (out); #if __GNUC__ < 4 /* gcc 4.0 has a sentinel attribute. */ out = xstrconcat (NULL); if (!out) fail (1); #endif out = xstrconcat (NULL, NULL); if (!out) fail (1); xfree (out); out = xstrconcat ("", NULL); if (!out || *out) fail (1); xfree (out); out = xstrconcat ("", "", NULL); if (!out || *out) fail (2); xfree (out); out = xstrconcat ("a", "b", NULL); if (!out || strcmp (out, "ab")) fail (3); xfree (out); out = xstrconcat ("a", "b", "c", NULL); if (!out || strcmp (out, "abc")) fail (3); xfree (out); out = xstrconcat ("a", "b", "cc", NULL); if (!out || strcmp (out, "abcc")) fail (4); xfree (out); out = xstrconcat ("a1", "b1", "c1", NULL); if (!out || strcmp (out, "a1b1c1")) fail (4); xfree (out); out = xstrconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); out = xstrconcat ("", " long b ", "", "--even-longer--", NULL); if (!out || strcmp (out, " long b --even-longer--")) fail (5); xfree (out); } static void test_make_filename_try (void) { char *out; const char *home = gethome (); size_t homelen = home? strlen (home):0; out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", NULL); if (out) fail (0); else if (errno != EINVAL) fail (0); xfree (out); out = make_filename_try ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "1", "2", NULL); if (!out || strcmp (out, "1/2/3/4/5/6/7/8/9/10/" "1/2/3/4/5/6/7/8/9/10/" "1/2/3/4/5/6/7/8/9/10/" "1/2")) fail (0); xfree (out); out = make_filename_try ("foo", "~/bar", "baz/cde", NULL); if (!out || strcmp (out, "foo/~/bar/baz/cde")) fail (1); xfree (out); out = make_filename_try ("foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("/foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "/foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("//foo", "~/bar", "baz/cde/", NULL); if (!out || strcmp (out, "//foo/~/bar/baz/cde/")) fail (1); xfree (out); out = make_filename_try ("", "~/bar", "baz/cde", NULL); if (!out || strcmp (out, "/~/bar/baz/cde")) fail (1); xfree (out); out = make_filename_try ("~/foo", "bar", NULL); if (!out) fail (2); else if (home) { if (strlen (out) < homelen + 7) fail (2); else if (strncmp (out, home, homelen)) fail (2); else if (strcmp (out+homelen, "/foo/bar")) fail (2); } else { if (strcmp (out, "~/foo/bar")) fail (2); } xfree (out); out = make_filename_try ("~", "bar", NULL); if (!out) fail (2); else if (home) { if (strlen (out) < homelen + 3) fail (2); else if (strncmp (out, home, homelen)) fail (2); else if (strcmp (out+homelen, "/bar")) fail (2); } else { if (strcmp (out, "~/bar")) fail (2); } xfree (out); } static void test_make_absfilename_try (void) { char *out; char *cwd = mygetcwd (); size_t cwdlen = strlen (cwd); out = make_absfilename_try ("foo", "bar", NULL); if (!out) fail (0); else if (strlen (out) < cwdlen + 7) fail (0); else if (strncmp (out, cwd, cwdlen)) fail (0); else if (strcmp (out+cwdlen, "/foo/bar")) fail (0); xfree (out); out = make_absfilename_try ("./foo", NULL); if (!out) fail (1); else if (strlen (out) < cwdlen + 5) fail (1); else if (strncmp (out, cwd, cwdlen)) fail (1); else if (strcmp (out+cwdlen, "/./foo")) fail (1); xfree (out); out = make_absfilename_try (".", NULL); if (!out) fail (2); else if (strlen (out) < cwdlen) fail (2); else if (strncmp (out, cwd, cwdlen)) fail (2); else if (strcmp (out+cwdlen, "")) fail (2); xfree (out); xfree (cwd); } static void test_strsplit (void) { struct { const char *s; char delim; char replacement; const char *fields_expected[10]; } tv[] = { { "a:bc:cde:fghi:jklmn::foo:", ':', '\0', { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL } }, { ",a,bc,,def,", ',', '!', { "!a!bc!!def!", "a!bc!!def!", "bc!!def!", "!def!", "def!", "", NULL } }, { "", ':', ',', { "", NULL } } }; int tidx; for (tidx = 0; tidx < DIM(tv); tidx++) { char *s2; int field_count; char **fields; int field_count_expected; int i; /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; /* We need to copy s since strsplit modifies it in place. */ s2 = xstrdup (tv[tidx].s); fields = strsplit (s2, tv[tidx].delim, tv[tidx].replacement, &field_count); if (field_count != field_count_expected) fail (tidx * 1000); for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i]) != 0) { printf ("For field %d, expected '%s', but got '%s'\n", i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } xfree (fields); xfree (s2); } } static void test_strtokenize (void) { struct { const char *s; const char *delim; const char *fields_expected[10]; } tv[] = { { "", ":", { "", NULL } }, { "a", ":", { "a", NULL } }, { ":", ":", { "", "", NULL } }, { "::", ":", { "", "", "", NULL } }, { "a:b:c", ":", { "a", "b", "c", NULL } }, { "a:b:", ":", { "a", "b", "", NULL } }, { "a:b", ":", { "a", "b", NULL } }, { "aa:b:cd", ":", { "aa", "b", "cd", NULL } }, { "aa::b:cd", ":", { "aa", "", "b", "cd", NULL } }, { "::b:cd", ":", { "", "", "b", "cd", NULL } }, { "aa: : b:cd ", ":", { "aa", "", "b", "cd", NULL } }, { " aa: : b: cd ", ":", { "aa", "", "b", "cd", NULL } }, { " ", ":", { "", NULL } }, { " :", ":", { "", "", NULL } }, { " : ", ":", { "", "", NULL } }, { ": ", ":", { "", "", NULL } }, { ": x ", ":", { "", "x", NULL } }, { "a:bc:cde:fghi:jklmn::foo:", ":", { "a", "bc", "cde", "fghi", "jklmn", "", "foo", "", NULL } }, { ",a,bc,,def,", ",", { "", "a", "bc", "", "def", "", NULL } }, { " a ", " ", { "", "a", "", NULL } }, { " ", " ", { "", "", NULL } }, { "", " ", { "", NULL } } }; int tidx; for (tidx = 0; tidx < DIM(tv); tidx++) { char **fields; int field_count; int field_count_expected; int i; for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; fields = strtokenize (tv[tidx].s, tv[tidx].delim); if (!fields) fail (tidx * 1000); else { for (field_count = 0; fields[field_count]; field_count++) ; if (field_count != field_count_expected) fail (tidx * 1000); else { for (i = 0; i < field_count_expected; i++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("For field %d, expected '%s', but got '%s'\n", i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } } xfree (fields); } } static void test_split_fields (void) { struct { const char *s; int nfields; const char *fields_expected[10]; } tv[] = { { "a bc cde fghi jklmn foo ", 6, { "a", "bc", "cde", "fghi", "jklmn", "foo", NULL } }, { " a bc def ", 2, { "a", "bc", "def", NULL } }, { " a bc def ", 3, { "a", "bc", "def", NULL } }, { " a bc def ", 4, { "a", "bc", "def", NULL } }, { "", 0, { NULL } } }; int tidx; - char *fields[10]; + const char *fields[10]; int field_count_expected, nfields, field_count, i; char *s2; for (tidx = 0; tidx < DIM(tv); tidx++) { nfields = tv[tidx].nfields; assert (nfields <= DIM (fields)); /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; if (field_count_expected > nfields) field_count_expected = nfields; /* We need to copy s since split_fields modifies in place. */ s2 = xstrdup (tv[tidx].s); field_count = split_fields (s2, fields, nfields); if (field_count != field_count_expected) { printf ("%s: tidx %d: expected %d, got %d\n", __func__, tidx, field_count_expected, field_count); fail (tidx * 1000); } else { for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("%s: tidx %d, field %d: expected '%s', got '%s'\n", __func__, tidx, i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } xfree (s2); } } static void test_split_fields_colon (void) { struct { const char *s; int nfields; const char *fields_expected[10]; } tv[] = { { "a:bc:cde:fghi:jklmn: foo ", 6, { "a", "bc", "cde", "fghi", "jklmn", " foo ", NULL } }, { " a:bc: def ", 2, { " a", "bc", NULL } }, { " a:bc :def ", 3, { " a", "bc ", "def ", NULL } }, { " a:bc: def ", 4, { " a", "bc", " def ", NULL } }, { "", 0, { NULL } } }; int tidx; - char *fields[10]; + const char *fields[10]; int field_count_expected, nfields, field_count, i; char *s2; for (tidx = 0; tidx < DIM(tv); tidx++) { nfields = tv[tidx].nfields; assert (nfields <= DIM (fields)); /* Count the fields. */ for (field_count_expected = 0; tv[tidx].fields_expected[field_count_expected]; field_count_expected ++) ; if (field_count_expected > nfields) field_count_expected = nfields; /* We need to copy s since split_fields modifies in place. */ s2 = xstrdup (tv[tidx].s); field_count = split_fields_colon (s2, fields, nfields); if (field_count != field_count_expected) { printf ("%s: tidx %d: expected %d, got %d\n", __func__, tidx, field_count_expected, field_count); fail (tidx * 1000); } else { for (i = 0; i < field_count_expected; i ++) if (strcmp (tv[tidx].fields_expected[i], fields[i])) { printf ("%s: tidx %d, field %d: expected '%s', got '%s'\n", __func__, tidx, i, tv[tidx].fields_expected[i], fields[i]); fail (tidx * 1000 + i + 1); } } xfree (s2); } } static char * stresc (char *s) { char *p; int l = strlen (s) + 1; for (p = s; *p; p ++) if (*p == '\n') l ++; p = xmalloc (l); for (l = 0; *s; s ++, l ++) { if (*s == ' ') p[l] = '_'; else if (*p == '\n') { p[l ++] = '\\'; p[l ++] = 'n'; p[l] = '\n'; } else p[l] = *s; } p[l] = *s; return p; } static void test_format_text (void) { struct test { int target_cols, max_cols; char *input; char *expected; }; struct test tests[] = { { 10, 12, "", "", }, { 10, 12, " ", "", }, { 10, 12, " ", "", }, { 10, 12, " \n ", " \n", }, { 10, 12, " \n \n ", " \n \n", }, { 10, 12, "0123456789 0123456789 0", "0123456789\n0123456789\n0", }, { 10, 12, " 0123456789 0123456789 0 ", " 0123456789\n0123456789\n0", }, { 10, 12, "01 34 67 90 23 56 89 12 45 67 89 1", "01 34 67\n90 23 56\n89 12 45\n67 89 1" }, { 10, 12, "01 34 67 90 23 56 89 12 45 67 89 1", "01 34 67\n90 23 56\n89 12 45\n67 89 1" }, { 72, 80, "Warning: if you think you've seen more than 10 messages " "signed by this key, then this key might be a forgery! " "Carefully examine the email address for small variations " "(e.g., additional white space). If the key is suspect, " "then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as being bad.\n", "Warning: if you think you've seen more than 10 messages signed by this\n" "key, then this key might be a forgery! Carefully examine the email\n" "address for small variations (e.g., additional white space). If the key\n" "is suspect, then use 'gpg --tofu-policy bad \"FINGERPRINT\"' to mark it as\n" "being bad.\n" }, { 72, 80, "Normally, there is only a single key associated with an email " "address. 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 key, you should talk to or " "call the person to make sure this new key is legitimate.", "Normally, there is only a single key associated with an email " "address.\nHowever, people sometimes generate a new key if " "their key is too old or\nthey think it might be compromised. " "Alternatively, a new key may indicate\na man-in-the-middle " "attack! Before accepting this key, you should talk\nto or " "call the person to make sure this new key is legitimate.", } }; int i; int failed = 0; for (i = 0; i < sizeof (tests) / sizeof (tests[0]); i ++) { struct test *test = &tests[i]; char *result = format_text (test->input, test->target_cols, test->max_cols); if (!result) { fail (1); exit (2); } if (strcmp (result, test->expected) != 0) { printf ("%s: Test #%d failed.\nExpected: '%s'\nResult: '%s'\n", __func__, i + 1, stresc (test->expected), stresc (result)); failed ++; } xfree (result); } if (failed) fail(0); } static void test_compare_version_strings (void) { struct { const char *a; const char *b; int okay; } tests[] = { { "1.0.0", "1.0.0", 0 }, { "1.0.0-", "1.0.0", 1 }, { "1.0.0-1", "1.0.0", 1 }, { "1.0.0.1", "1.0.0", 1 }, { "1.0.0", "1.0.1", -1 }, { "1.0.0-", "1.0.1", -1 }, { "1.0.0-1", "1.0.1", -1 }, { "1.0.0.1", "1.0.1", -1 }, { "1.0.0", "1.1.0", -1 }, { "1.0.0-", "1.1.0", -1 }, { "1.0.0-1", "1.1.0", -1 }, { "1.0.0.1", "1.1.0", -1 }, { "1.0.0", "1.0.0-", -1 }, { "1.0.0", "1.0.0-1", -1 }, { "1.0.0", "1.0.0.1", -1 }, { "1.1.0", "1.0.0", 1 }, { "1.1.1", "1.1.0", 1 }, { "1.1.2", "1.1.2", 0 }, { "1.1.2", "1.0.2", 1 }, { "1.1.2", "0.0.2", 1 }, { "1.1.2", "1.1.3", -1 }, { "0.99.1", "0.9.9", 1 }, { "0.9.1", "0.91.0", -1 }, { "1.5.3", "1.5", 1 }, { "1.5.0", "1.5", 0 }, { "1.4.99", "1.5", -1 }, { "1.5", "1.4.99", 1 }, { "1.5", "1.5.0", 0 }, { "1.5", "1.5.1", -1 }, { "1.5.3-x17", "1.5-23", 1 }, { "1.5.3a", "1.5.3", 1 }, { "1.5.3a", "1.5.3b", -1 }, { "3.1.4-ab", "3.1.4-ab", 0 }, { "3.1.4-ab", "3.1.4-ac", -1 }, { "3.1.4-ac", "3.1.4-ab", 1 }, { "3.1.4-ab", "3.1.4-abb", -1 }, { "3.1.4-abb", "3.1.4-ab", 1 }, { "", "", INT_MIN }, { NULL, "", INT_MIN }, { "1.2.3", "", INT_MIN }, { "1.2.3", "2", INT_MIN }, /* Test cases for validity of A. */ { "", NULL, INT_MIN }, { "1", NULL, INT_MIN }, { "1.", NULL, 0 }, { "1.0", NULL, 0 }, { "1.0.", NULL, 0 }, { "a1.2", NULL, INT_MIN }, { NULL, NULL, INT_MIN } }; int idx; int res; for (idx=0; idx < DIM(tests); idx++) { res = compare_version_strings (tests[idx].a, tests[idx].b); /* printf ("test %d: '%s' '%s' %d -> %d\n", */ /* idx, tests[idx].a, tests[idx].b, tests[idx].okay, res); */ if (res != tests[idx].okay) fail (idx); } } int main (int argc, char **argv) { (void)argc; (void)argv; test_percent_escape (); test_compare_filenames (); test_strconcat (); test_xstrconcat (); test_make_filename_try (); test_make_absfilename_try (); test_strsplit (); test_strtokenize (); test_split_fields (); test_split_fields_colon (); test_compare_version_strings (); test_format_text (); xfree (home_buffer); return !!errcount; } diff --git a/dirmngr/loadswdb.c b/dirmngr/loadswdb.c index fb883722a..e9ced8af2 100644 --- a/dirmngr/loadswdb.c +++ b/dirmngr/loadswdb.c @@ -1,405 +1,405 @@ /* loadswdb.c - Load the swdb file from versions.gnupg.org * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * 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 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]; + const 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 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, KS_HTTP_FETCH_NOCACHE, &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]; + const 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/g10/call-agent.c b/g10/call-agent.c index 42e2d3720..1fa77e7be 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1,3063 +1,3063 @@ /* 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 * Copyright (C) 2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "gpg.h" #include #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" #include "../common/ttyio.h" #define CONTROL_D ('D' - 'A' + 1) static assuan_context_t agent_ctx = NULL; static int did_early_card_test; struct confirm_parm_s { char *desc; char *ok; char *notok; }; struct default_inq_parm_s { ctrl_t ctrl; assuan_context_t ctx; struct { u32 *keyid; u32 *mainkeyid; int pubkey_algo; } keyinfo; struct confirm_parm_s *confirm; }; 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; const char *s; 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 ()) { 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 if ((s = has_leading_keyword (line, "CONFIRM")) && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK && parm->confirm) { int ask = atoi (s); int yes; if (ask) { yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc); if (yes) err = assuan_send_data (parm->ctx, NULL, 0); else err = gpg_error (GPG_ERR_NOT_CONFIRMED); } else { tty_printf ("%s", parm->confirm->desc); err = assuan_send_data (parm->ctx, NULL, 0); } } 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) { return warn_server_version_mismatch (ctx, servername, mode, write_status_strings2, NULL, !opt.quiet); } #define FLAG_FOR_CARD_SUPPRESS_ERRORS 2 /* 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 flag_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); } } /* Pass on the request origin. */ if (opt.request_origin) { char *tmp = xasprintf ("OPTION pretend-request-origin=%s", str_request_origin (opt.request_origin)); rc = assuan_transact (agent_ctx, tmp, NULL, NULL, NULL, NULL, NULL, NULL); xfree (tmp); if (rc) { log_error ("setting request origin '%s' failed: %s\n", str_request_origin (opt.request_origin), gpg_strerror (rc)); write_status_error ("set_request_origin", rc); } } /* In DE_VS mode under Windows we require that the JENT RNG * is active. */ #ifdef HAVE_W32_SYSTEM if (!rc && opt.compliance == CO_DE_VS) { if (assuan_transact (agent_ctx, "GETINFO jent_active", NULL, NULL, NULL, NULL, NULL, NULL)) { rc = gpg_error (GPG_ERR_FORBIDDEN); log_error (_("%s is not compliant with %s mode\n"), GPG_AGENT_NAME, gnupg_compliance_option_string (opt.compliance)); write_status_error ("random-compliance", rc); } } #endif /*HAVE_W32_SYSTEM*/ } } if (!rc && flag_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); if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!rc) rc = assuan_transact (agent_ctx, opt.flags.use_only_openpgp_card? "SCD SERIALNO openpgp" : "SCD SERIALNO", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) { 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 card 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 or 32 byte hexencoded string and put it into the provided * FPRLEN byte long buffer FPR in binary format. Returns the actual * used length of the FPR buffer or 0 on error. */ static unsigned int unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || !(n == 40 || n == 64)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) fpr[n] = xtoi_2 (s); return (n == 20 || n == 32)? n : 0; } /* 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->manufacturer_name); info->manufacturer_name = 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->cafpr1len = info->cafpr2len = info->cafpr3len = 0; info->fpr1len = info->fpr2len = info->fpr3len = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } for (i=0; i < DIM(info->supported_keyalgo); i++) { free_strlist (info->supported_keyalgo[i]); info->supported_keyalgo[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; char *endp; 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, "bt")) parm->extcap.bt = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = 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->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1); else if (no == 2) parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2); else if (no == 3) parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof 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 == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { const char *hexgrp = line; int no; while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (strncmp (line, "OPENPGP.", 8)) ; else if ((no = atoi (line+8)) == 1) unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1); else if (no == 2) unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2); else if (no == 3) unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3); } 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->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1); else if (no == 2) parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2); else if (no == 3) parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof 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); } else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12)) { xfree (parm->manufacturer_name); parm->manufacturer_name = NULL; parm->manufacturer_id = strtoul (line, &endp, 0); while (endp && spacep (endp)) endp++; if (endp && *endp) parm->manufacturer_name = xstrdup (endp); } else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) { unsigned char *data = unescape_status_string (line); if (data[2] != 0x03) parm->kdf_do_enabled = 0; else if (data[22] != 0x85) parm->kdf_do_enabled = 1; else parm->kdf_do_enabled = 2; xfree (data); } else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4) && strchr("123", keyword[4])) { unsigned char *data; int no = keyword[4] - '1'; log_assert (no >= 0 && no <= 2); data = unescape_status_string (line); parm->uif[no] = (data[0] != 0xff); xfree (data); } else if (keywordlen == 13 && !memcmp (keyword, "KEY-ATTR-INFO", 13)) { if (!strncmp (line, "OPENPGP.", 8)) { int no; line += 8; no = atoi (line); if (no >= 1 && no <= 3) { no--; line++; while (spacep (line)) line++; append_to_strlist (&parm->supported_keyalgo[no], xstrdup (line)); } } /* Skip when it's not "OPENPGP.[123]". */ } return 0; } /* Call the scdaemon to learn about a smartcard. Note that in * contradiction to the function's name, gpg-agent's LEARN command is * used and not the low-level "SCD LEARN". * Used by: * card-util.c * keyedit_menu * card_store_key_with_backup (Woth force to remove secret key data) */ 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; } struct keypairinfo_cb_parm_s { keypair_info_t kpinfo; keypair_info_t *kpinfo_tail; }; /* Callback for the agent_scd_keypairinfo function. */ static gpg_error_t scd_keypairinfo_status_cb (void *opaque, const char *line) { struct keypairinfo_cb_parm_s *parm = opaque; gpg_error_t err = 0; const char *keyword = line; int keywordlen; char *line_buffer = NULL; keypair_info_t kpi = NULL; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] */ - char *fields[4]; + const char *fields[4]; int nfields; const char *hexgrp, *keyref, *usage; time_t atime; u32 keytime; line_buffer = xtrystrdup (line); if (!line_buffer) { err = gpg_error_from_syserror (); goto leave; } if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2) goto leave; /* not enough args - invalid status line - ignore */ hexgrp = fields[0]; keyref = fields[1]; if (nfields > 2) usage = fields[2]; else usage = ""; if (nfields > 3) { atime = parse_timestamp (fields[3], NULL); if (atime == (time_t)(-1)) atime = 0; keytime = atime; } else keytime = 0; kpi = xtrycalloc (1, sizeof *kpi); if (!kpi) { err = gpg_error_from_syserror (); goto leave; } if (*hexgrp == 'X' && !hexgrp[1]) *kpi->keygrip = 0; /* No hexgrip. */ else if (strlen (hexgrp) == 2*KEYGRIP_LEN) mem2str (kpi->keygrip, hexgrp, sizeof kpi->keygrip); else { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } if (!*keyref) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } kpi->idstr = xtrystrdup (keyref); if (!kpi->idstr) { err = gpg_error_from_syserror (); goto leave; } /* Parse and set the usage. */ for (; *usage; usage++) { switch (*usage) { case 's': kpi->usage |= GCRY_PK_USAGE_SIGN; break; case 'c': kpi->usage |= GCRY_PK_USAGE_CERT; break; case 'a': kpi->usage |= GCRY_PK_USAGE_AUTH; break; case 'e': kpi->usage |= GCRY_PK_USAGE_ENCR; break; } } kpi->keytime = keytime; /* Append to the list. */ *parm->kpinfo_tail = kpi; parm->kpinfo_tail = &kpi->next; kpi = NULL; } leave: free_keypair_info (kpi); xfree (line_buffer); return err; } /* Read the keypairinfo lines of the current card directly from * scdaemon. The list is returned as a string made up of the keygrip, * a space and the keyref. The flags of the string carry the usage * bits. If KEYREF is not NULL, only a single string is returned * which matches the given keyref. */ gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, keypair_info_t *r_list) { gpg_error_t err; struct keypairinfo_cb_parm_s parm; struct default_inq_parm_s inq_parm; char line[ASSUAN_LINELENGTH]; *r_list = NULL; err= start_agent (ctrl, 1); if (err) return err; memset (&inq_parm, 0, sizeof inq_parm); inq_parm.ctx = agent_ctx; parm.kpinfo = NULL; parm.kpinfo_tail = &parm.kpinfo; if (keyref) snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref); else snprintf (line, DIM(line), "SCD LEARN --keypairinfo"); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &inq_parm, scd_keypairinfo_status_cb, &parm); if (!err && !parm.kpinfo) err = gpg_error (GPG_ERR_NO_DATA); if (err) free_keypair_info (parm.kpinfo); else *r_list = parm.kpinfo; return err; } /* 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. * Used by: * card-util.c */ 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; } /* Used by: * card_store_subkey * card_store_key_with_backup */ 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; } /* Object used with the agent_scd_getattr_one. */ struct getattr_one_parm_s { const char *keyword; /* Keyword to look for. */ char *data; /* Malloced and unescaped data. */ gpg_error_t err; /* Error code or 0 on success. */ }; /* Callback for agent_scd_getattr_one. */ static gpg_error_t getattr_one_status_cb (void *opaque, const char *line) { struct getattr_one_parm_s *parm = opaque; const char *s; if (parm->data) return 0; /* We want only the first occurrence. */ if ((s=has_leading_keyword (line, parm->keyword))) { parm->data = percent_plus_unescape (s, 0xff); if (!parm->data) parm->err = gpg_error_from_syserror (); } return 0; } /* Simplified version of agent_scd_getattr. This function returns * only the first occurrence of the attribute NAME and stores it at * R_VALUE. A nul in the result is silennly replaced by 0xff. On * error NULL is stored at R_VALUE. */ gpg_error_t agent_scd_getattr_one (const char *name, char **r_value) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s inqparm; struct getattr_one_parm_s parm; *r_value = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&inqparm, 0, sizeof inqparm); inqparm.ctx = agent_ctx; memset (&parm, 0, sizeof parm); parm.keyword = name; /* 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); err = start_agent (NULL, 1); if (err) return err; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &inqparm, getattr_one_status_cb, &parm); if (!err && parm.err) err = parm.err; else if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *r_value = parm.data; else xfree (parm.data); return err; } /* 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 command. * * Used by: * build_sk_list * enum_secret_keys * get_signature_count * card-util.c * generate_keypair (KEY-ATTR) * card_store_key_with_backup (SERIALNO) * generate_card_subkeypair (KEY-ATTR) */ 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. * Used by: * card-util.c */ gpg_error_t agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen) { gpg_error_t err; const unsigned char *value = value_arg; char line[ASSUAN_LINELENGTH]; char *p; struct default_inq_parm_s parm; memset (&parm, 0, sizeof parm); 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; err = start_agent (NULL, 1); if (!err) { parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); } status_sc_op_failure (err); return err; } /* 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. * Used by: * card-util.c */ 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; } /* 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. * Used by: * gen_card_key */ 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. With DEMAND the active * card is switched to the card with that serialno. * Used by: * card-util.c * build_sk_list * enum_secret_keys */ int agent_scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS)); 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. * Used by: * card-util.c */ 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; } /* Callback for the agent_scd_readkey function. */ static gpg_error_t readkey_status_cb (void *opaque, const char *line) { u32 *keytimep = opaque; gpg_error_t err = 0; const char *args; char *line_buffer = NULL; /* FIXME: Get that info from the KEYPAIRINFO line. */ if ((args = has_leading_keyword (line, "KEYPAIRINFO")) && !*keytimep) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] * * Note that we use only the first valid KEYPAIRINFO line. More * lines are possible if a second card carries the same key. */ - char *fields[4]; + const char *fields[4]; int nfields; time_t atime; line_buffer = xtrystrdup (line); if (!line_buffer) { err = gpg_error_from_syserror (); goto leave; } if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 4) goto leave; /* not enough args - ignore */ if (nfields > 3) { atime = parse_timestamp (fields[3], NULL); if (atime == (time_t)(-1)) atime = 0; *keytimep = atime; } else *keytimep = 0; } leave: xfree (line_buffer); return err; } /* This is a variant of agent_readkey which sends a READKEY command * directly Scdaemon. On success a new s-expression is stored at * R_RESULT. If R_KEYTIME is not NULL the key cresation time of an * OpenPGP card is stored there - if that is not known 0 is stored. * In the latter case it is allowed to pass NULL for R_RESULT. */ gpg_error_t agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr, gcry_sexp_t *r_result, u32 *r_keytime) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *buf; size_t len, buflen; struct default_inq_parm_s dfltparm; u32 keytime; memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctx = agent_ctx; if (r_result) *r_result = NULL; if (r_keytime) *r_keytime = 0; err = start_agent (ctrl, 1); if (err) return err; init_membuf (&data, 1024); snprintf (line, DIM(line), "SCD READKEY --info%s -- %s", r_result? "":"-only", keyrefstr); keytime = 0; err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, readkey_status_cb, &keytime); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &buflen); if (!buf) return gpg_error_from_syserror (); if (r_result) err = gcry_sexp_new (r_result, buf, buflen, 0); else err = 0; xfree (buf); if (!err && r_keytime) *r_keytime = keytime; return 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; } /* Return a list of currently available cards. * Used by: * card-util.c * skclist.c */ 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 | FLAG_FOR_CARD_SUPPRESS_ERRORS); 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; } struct card_keyinfo_parm_s { int error; keypair_info_t list; }; /* Callback function for agent_card_keylist. */ static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen)) { const char *s; int n; keypair_info_t keyinfo; keypair_info_t *l_p = &parm->list; while ((*l_p)) l_p = &(*l_p)->next; keyinfo = xtrycalloc (1, sizeof *keyinfo); if (!keyinfo) { alloc_error: if (!parm->error) parm->error = gpg_error_from_syserror (); return 0; } for (n=0,s=line; hexdigitp (s); s++, n++) ; if (n != 40) { parm_error: if (!parm->error) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); return 0; } memcpy (keyinfo->keygrip, line, 40); keyinfo->keygrip[40] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (*line++ != 'T') goto parm_error; if (!*line) goto parm_error; while (spacep (line)) line++; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n) goto parm_error; keyinfo->serialno = xtrymalloc (n+1); if (!keyinfo->serialno) goto alloc_error; memcpy (keyinfo->serialno, line, n); keyinfo->serialno[n] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (!*line) goto parm_error; keyinfo->idstr = xtrystrdup (line); if (!keyinfo->idstr) goto alloc_error; *l_p = keyinfo; } return err; } /* Free a keypair info list. */ void free_keypair_info (keypair_info_t l) { keypair_info_t l_next; for (; l; l = l_next) { l_next = l->next; xfree (l->serialno); xfree (l->idstr); xfree (l); } } /* Call the scdaemon to check if a key of KEYGRIP is available, or retrieve list of available keys on cards. With CAP, we can limit keys with specified capability. On success, the allocated structure is stored at RESULT. On error, an error code is returned and NULL is stored at RESULT. */ gpg_error_t agent_scd_keyinfo (const char *keygrip, int cap, keypair_info_t *result) { int err; struct card_keyinfo_parm_s parm; char line[ASSUAN_LINELENGTH]; char *list_option; *result = NULL; switch (cap) { case 0: list_option = "--list"; break; case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break; case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break; case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break; default: return gpg_error (GPG_ERR_INV_VALUE); } memset (&parm, 0, sizeof parm); snprintf (line, sizeof line, "SCD KEYINFO %s", keygrip ? keygrip : list_option); err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS); if (err) return err; err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, card_keyinfo_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_keypair_info (parm.list); return err; } /* 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. * Used by: * card-util.c */ 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. * Used by: * card-util.c */ 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; } /* 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 error code returned. Only called from passphrase.c:passphrase_get - see there for more comments on this ugly API. */ gpg_error_t agent_get_passphrase (const char *cache_id, const char *err_msg, const char *prompt, const char *desc_msg, int newsymkey, 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; int have_newsymkey; 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); have_newsymkey = !(assuan_transact (agent_ctx, "GETINFO cmd_has_option GET_PASSPHRASE newsymkey", NULL, NULL, NULL, NULL, NULL, NULL)); 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; /* CHECK && REPEAT or NEWSYMKEY is here an indication that a new * passphrase for symmetric encryption is requested; if the agent * supports this we enable the modern API by also passing --newsymkey. */ snprintf (line, DIM(line), "GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s", repeat, ((repeat && check) || newsymkey)? " --check":"", (have_newsymkey && newsymkey)? " --newsymkey":"", 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. On error * print a warning and return a default value. */ unsigned long agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; unsigned long count = 0; err = start_agent (NULL, 0); if (err) goto leave; 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 { count = strtoul (buf, NULL, 10); xfree (buf); } } leave: if (err || count < 65536) { /* Don't print an error if an older agent is used. */ if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); /* Default to 65536 which was used up to 2.0.13. */ count = 65536; } return count; } struct keyinfo_data_parm_s { char *serialno; int is_smartcard; int passphrase_cached; int cleartext; int card_available; }; static gpg_error_t keyinfo_status_cb (void *opaque, const char *line) { struct keyinfo_data_parm_s *data = opaque; char *s; if ((s = has_leading_keyword (line, "KEYINFO")) && data) { /* Parse the arguments: * 0 1 2 3 4 5 * * * 6 7 8 * */ - char *fields[9]; + const char *fields[9]; if (split_fields (s, fields, DIM (fields)) == 9) { data->is_smartcard = (fields[1][0] == 'T'); if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-")) data->serialno = xtrystrdup (fields[2]); /* '1' for cached */ data->passphrase_cached = (fields[4][0] == '1'); /* 'P' for protected, 'C' for clear */ data->cleartext = (fields[5][0] == 'C'); /* 'A' for card is available */ data->card_available = (fields[8][0] == 'A'); } } return 0; } /* Ask the agent whether a secret key for the given public key is available. Returns 0 if not available. Bigger value is preferred. */ int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char *hexgrip; struct keyinfo_data_parm_s keyinfo; memset (&keyinfo, 0, sizeof keyinfo); err = start_agent (ctrl, 0); if (err) return err; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) return err; snprintf (line, sizeof line, "KEYINFO %s", hexgrip); xfree (hexgrip); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, keyinfo_status_cb, &keyinfo); xfree (keyinfo.serialno); if (err) return 0; if (keyinfo.card_available) return 4; if (keyinfo.passphrase_cached) return 3; if (keyinfo.is_smartcard) return 2; return 1; } /* 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[KEYGRIP_LEN]; 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; } /* 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. TIMESTAMP is the creation time of the key or zero. */ 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, time_t timestamp, 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 timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ 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 (timestamp) { strcpy (timestamparg, " --timestamp="); epoch2isotime (timestamparg+13, timestamp); } else *timestamparg = 0; 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%s", *timestamparg? timestamparg : "", 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. * Values from FROMCARD: * 0 - Standard * 1 - The key is read from the current card * via the agent and a stub file is created. */ 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; if (fromcard) snprintf (line, DIM(line), "READKEY --card -- %s", hexkeygrip); else snprintf (line, DIM(line), "READKEY -- %s", 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:""); if (DBG_CLOCK) log_clock ("enter signing"); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, default_inq_cb, &dfltparm, NULL, NULL); if (DBG_CLOCK) log_clock ("leave signing"); 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; } buf = get_membuf (&data, &len); if (!buf) return gpg_error_from_syserror (); if (len == 0 || *buf != '(') { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */ { xfree (buf); return gpg_error (GPG_ERR_INV_SEXP); } while (buf[len-1] == 0) len--; if (buf[len-1] != ')') return gpg_error (GPG_ERR_INV_SEXP); len--; /* Drop the final close-paren. */ p = buf + 8; /* Skip leading parenthesis and the value tag. */ len -= 8; /* Count only the data of the second part. */ 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, u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp) { gpg_error_t err; struct import_key_parm_s parm; struct cache_nonce_parm_s cn_parm; char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ char line[ASSUAN_LINELENGTH]; 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; err = start_agent (ctrl, 0); if (err) return err; dfltparm.ctx = agent_ctx; if (timestamp) { strcpy (timestamparg, " --timestamp="); epoch2isotime (timestamparg+13, timestamp); } else *timestamparg = 0; 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%s", *timestamparg? timestamparg : "", 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, u32 *keyid, u32 *mainkeyid, int pubkey_algo) { 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; dfltparm.keyinfo.keyid = keyid; dfltparm.keyinfo.mainkeyid = mainkeyid; dfltparm.keyinfo.pubkey_algo = pubkey_algo; *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; } /* Status callback for handling confirmation. */ static gpg_error_t confirm_status_cb (void *opaque, const char *line) { struct confirm_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "SETDESC"))) { xfree (parm->desc); parm->desc = unescape_status_string (s); } else if ((s = has_leading_keyword (line, "SETOK"))) { xfree (parm->ok); parm->ok = unescape_status_string (s); } else if ((s = has_leading_keyword (line, "SETNOTOK"))) { xfree (parm->notok); parm->notok = unescape_status_string (s); } 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; struct confirm_parm_s confirm_parm; memset (&confirm_parm, 0, sizeof confirm_parm); memset (&dfltparm, 0, sizeof dfltparm); dfltparm.ctrl = ctrl; dfltparm.confirm = &confirm_parm; 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, confirm_status_cb, &confirm_parm); xfree (confirm_parm.desc); xfree (confirm_parm.ok); xfree (confirm_parm.notok); 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/tools/card-call-scd.c b/tools/card-call-scd.c index 0b529f06a..b56f40a1c 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1,1684 +1,1684 @@ /* card-call-scd.c - IPC calls to scdaemon. * Copyright (C) 2019, 2020 g10 Code GmbH * 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "../common/util.h" #include "../common/membuf.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define START_AGENT_NO_STARTUP_CMDS 1 #define START_AGENT_SUPPRESS_ERRORS 2 struct default_inq_parm_s { 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 card_cardlist_parm_s { gpg_error_t error; int with_apps; strlist_t list; }; 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; }; /* * File local variables */ /* The established context to the agent. Note that all calls to * scdaemon are routed via the agent and thus we only need to care * about the IPC with the agent. */ static assuan_context_t agent_ctx; /* * Local prototypes */ static gpg_error_t learn_status_cb (void *opaque, const char *line); /* Release the card info structure INFO. */ void release_card_info (card_info_t info) { int i; if (!info) return; xfree (info->reader); info->reader = NULL; xfree (info->cardtype); info->cardtype = NULL; xfree (info->manufacturer_name); info->manufacturer_name = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->dispserialno); info->dispserialno = NULL; xfree (info->apptypestr); info->apptypestr = NULL; info->apptype = APP_TYPE_NONE; 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->cafpr1len = info->cafpr2len = info->cafpr3len = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } while (info->kinfo) { key_info_t kinfo = info->kinfo->next; xfree (info->kinfo); info->kinfo = kinfo; } info->chvusage[0] = info->chvusage[1] = 0; for (i=0; i < DIM(info->supported_keyalgo); i++) { free_strlist (info->supported_keyalgo[i]); info->supported_keyalgo[i] = NULL; } } /* Map an application type string to an integer. */ static app_type_t map_apptypestr (const char *string) { app_type_t result; if (!string) result = APP_TYPE_NONE; else if (!ascii_strcasecmp (string, "OPENPGP")) result = APP_TYPE_OPENPGP; else if (!ascii_strcasecmp (string, "NKS")) result = APP_TYPE_NKS; else if (!ascii_strcasecmp (string, "DINSIG")) result = APP_TYPE_DINSIG; else if (!ascii_strcasecmp (string, "P15")) result = APP_TYPE_P15; else if (!ascii_strcasecmp (string, "GELDKARTE")) result = APP_TYPE_GELDKARTE; else if (!ascii_strcasecmp (string, "SC-HSM")) result = APP_TYPE_SC_HSM; else if (!ascii_strcasecmp (string, "PIV")) result = APP_TYPE_PIV; else result = APP_TYPE_UNKNOWN; return result; } /* Return a string representation of the application type. */ const char * app_type_string (app_type_t app_type) { const char *result = "?"; switch (app_type) { case APP_TYPE_NONE: result = "None"; break; case APP_TYPE_OPENPGP: result = "OpenPGP"; break; case APP_TYPE_NKS: result = "NetKey"; break; case APP_TYPE_DINSIG: result = "DINSIG"; break; case APP_TYPE_P15: result = "P15"; break; case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; case APP_TYPE_SC_HSM: result = "SC-HSM"; break; case APP_TYPE_PIV: result = "PIV"; break; case APP_TYPE_UNKNOWN: result = "Unknown"; break; } return result; } /* If RC is not 0, write an appropriate status message. */ static gpg_error_t status_sc_op_failure (gpg_error_t err) { switch (gpg_err_code (err)) { case 0: break; case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); break; default: gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL); break; } return err; } /* 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; (void)parm; 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 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) { return warn_server_version_mismatch (ctx, servername, mode, gnupg_status_strings, NULL, !opt.quiet); } /* Try to connect to the agent via socket or fork it off and work by * pipes. Handle the server's initial greeting. */ static gpg_error_t start_agent (unsigned int flags) { gpg_error_t err; int started = 0; if (agent_ctx) err = 0; else { started = 1; err = 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 (err) == GPG_ERR_NO_AGENT) { static int shown; if (!shown) { shown = 1; log_info (_("no gpg-agent running in this session\n")); } } else if (!err && !(err = 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); } } if (started && !err && !(flags & START_AGENT_NO_STARTUP_CMDS)) { /* Request the serial number of the card for an early test. */ struct card_info_s info; memset (&info, 0, sizeof info); if (!(flags & START_AGENT_SUPPRESS_ERRORS)) err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!err) err = assuan_transact (agent_ctx, "SCD SERIALNO --all", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (err && !(flags & START_AGENT_SUPPRESS_ERRORS)) { switch (gpg_err_code (err)) { case GPG_ERR_NOT_SUPPORTED: case GPG_ERR_NO_SCDAEMON: gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */ break; case GPG_ERR_OBJ_TERM_STATE: /* Card is in termination state. */ gnupg_status_printf (STATUS_CARDCTRL, "7"); break; default: gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */ break; } } if (!err && info.serialno) gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno); release_card_info (&info); } return err; } /* 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 or 32 byte hexencoded string and put it into the provided * FPRLEN byte long buffer FPR in binary format. Returns the actual * used length of the FPR buffer or 0 on error. */ static unsigned int unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || !(n == 40 || n == 64)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) fpr[n] = xtoi_2 (s); return (n == 20 || n == 32)? n : 0; } /* Take the serial number from LINE and return it verbatim in a newly * allocated string. We make sure that only hex characters are * returned. Returns NULL on error. */ 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; } /* Send an APDU to the current card. On success the status word is * stored at R_SW inless R_SW is NULL. 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. If * R_DATA is not NULL the data without the status code is stored * there. Caller must release it. If OPTIONS is not NULL, this will * be passed verbatim to the SCDaemon's APDU command. */ gpg_error_t scd_apdu (const char *hexapdu, const char *options, unsigned int *r_sw, unsigned char **r_data, size_t *r_datalen) { gpg_error_t err; if (r_data) *r_data = NULL; if (r_datalen) *r_datalen = 0; err = start_agent (START_AGENT_NO_STARTUP_CMDS); 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; int no_sw; init_membuf (&mb, 256); no_sw = (options && (strstr (options, "--dump-atr") || strstr (options, "--data-atr"))); snprintf (line, DIM(line), "SCD APDU %s%s%s", options?options:"", options?" -- ":"", 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 < (no_sw?1:2)) /* Ooops */ err = gpg_error (GPG_ERR_CARD); else { if (r_sw) *r_sw = no_sw? 0 : buf16_to_uint (data+datalen-2); if (r_data && r_datalen) { *r_data = data; *r_datalen = datalen - (no_sw?0:2); data = NULL; } } xfree (data); } } return err; } /* 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++; /* FIXME: Should we use has_leading_keyword? */ 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; } /* Make the card with SERIALNO the current one. */ gpg_error_t scd_switchcard (const char *serialno) { int err; char line[ASSUAN_LINELENGTH]; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; snprintf (line, DIM(line), "SCD SWITCHCARD -- %s", serialno); return assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } /* Make the app APPNAME the one on the card. */ gpg_error_t scd_switchapp (const char *appname) { int err; char line[ASSUAN_LINELENGTH]; if (appname && !*appname) appname = NULL; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s", appname? " ":"", appname? appname:""); return assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } /* For historical reasons OpenPGP cards simply use the numbers 1 to 3 * for the . Other cards and future versions of * scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1" * instead of "1". This is a helper to cope with that. */ static const char * parse_keyref_helper (const char *string) { if (*string == '1' && spacep (string+1)) return "OPENPGP.1"; else if (*string == '2' && spacep (string+1)) return "OPENPGP.2"; else if (*string == '3' && spacep (string+1)) return "OPENPGP.3"; else return string; } /* Create a new key info object with KEYREF. All fields but the * keyref are zeroed out. Never returns NULL. The created object is * appended to the list at INFO. */ static key_info_t create_kinfo (card_info_t info, const char *keyref) { key_info_t kinfo, ki; kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref)); strcpy (kinfo->keyref, keyref); if (!info->kinfo) info->kinfo = kinfo; else { for (ki=info->kinfo; ki->next; ki = ki->next) ; ki->next = kinfo; } return kinfo; } /* The status callback to handle the LEARN and GETATTR commands. */ static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct card_info_s *parm = opaque; const char *keyword = line; int keywordlen; char *line_buffer = NULL; /* In case we need a copy. */ char *pline, *endp; key_info_t kinfo; const char *keyref; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; switch (keywordlen) { case 3: if (!memcmp (keyword, "KDF", 3)) { parm->kdf_do_enabled = 1; } break; case 5: if (!memcmp (keyword, "UIF-", 4) && strchr("123", keyword[4])) { unsigned char *data; int no = keyword[4] - '1'; log_assert (no >= 0 && no <= 2); data = unescape_status_string (line); /* I am not sure why we test for 0xff but we better keep * that in case of bogus card versions which did not * initialize that DO correctly. */ parm->uif[no] = (data[0] == 0xff)? 0 : data[0]; xfree (data); } break; case 6: if (!memcmp (keyword, "READER", keywordlen)) { xfree (parm->reader); parm->reader = unescape_status_string (line); } else if (!memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; unsigned long number; 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, "bt")) parm->extcap.bt = abool; else if (!strcmp (p, "kdf")) parm->extcap.kdf = abool; else if (!strcmp (p, "si")) parm->status_indicator = strtoul (p2, NULL, 10); else if (!strcmp (p, "pd")) parm->extcap.private_dos = abool; else if (!strcmp (p, "mcl3")) parm->extcap.mcl3 = strtoul (p2, NULL, 10); else if (!strcmp (p, "sm")) { /* Unfortunately this uses OpenPGP algorithm * ids so that we need to map them to Gcrypt * ids. The mapping is limited to what * OpenPGP cards support. Other cards * should use a different tag than "sm". */ parm->extcap.sm = 1; number = strtoul (p2, NULL, 10); switch (number) { case CIPHER_ALGO_3DES: parm->extcap.smalgo = GCRY_CIPHER_3DES; break; case CIPHER_ALGO_AES: parm->extcap.smalgo = GCRY_CIPHER_AES; break; case CIPHER_ALGO_AES192: parm->extcap.smalgo = GCRY_CIPHER_AES192; break; case CIPHER_ALGO_AES256: parm->extcap.smalgo = GCRY_CIPHER_AES256; break; default: /* Unknown algorithm; dont claim SM support. */ parm->extcap.sm = 0; break; } } } } xfree (buf); } } else if (!memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1len = unhexify_fpr (line, parm->cafpr1, sizeof parm->cafpr1); else if (no == 2) parm->cafpr2len = unhexify_fpr (line, parm->cafpr2, sizeof parm->cafpr2); else if (no == 3) parm->cafpr3len = unhexify_fpr (line, parm->cafpr3, sizeof parm->cafpr3); } break; case 7: if (!memcmp (keyword, "APPTYPE", keywordlen)) { xfree (parm->apptypestr); parm->apptypestr = unescape_status_string (line); parm->apptype = map_apptypestr (parm->apptypestr); } else if (!memcmp (keyword, "KEY-FPR", keywordlen)) { /* The format of such a line is: * KEY-FPR */ const char *fpr; line_buffer = pline = xstrdup (line); keyref = parse_keyref_helper (pline); while (*pline && !spacep (pline)) pline++; if (*pline) *pline++ = 0; /* Terminate keyref. */ while (spacep (pline)) /* Skip to the fingerprint. */ pline++; fpr = pline; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* No: new entry. */ kinfo = create_kinfo (parm, keyref); else /* Existing entry - clear the fpr. */ memset (kinfo->fpr, 0, sizeof kinfo->fpr); /* Set or update or the fingerprint. */ kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr); } break; case 8: if (!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 (!memcmp (keyword, "CARDTYPE", keywordlen)) { xfree (parm->cardtype); parm->cardtype = unescape_status_string (line); } else if (!memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) { char keyrefbuf[20]; int keyno, algo, n; const char *curve; unsigned int nbits; /* To prepare for future changes we allow for a full OpenPGP * keyref here. */ if (!ascii_strncasecmp (line, "OPENPGP.", 8)) line += 8; /* Note that KEY-ATTR returns OpenPGP algorithm numbers but * we want to use the Gcrypt numbers here. A compatible * change would be to add another parameter along with a * magic algo number to indicate that. */ algo = PUBKEY_ALGO_RSA; keyno = n = 0; sscanf (line, "%d %d %n", &keyno, &algo, &n); algo = map_openpgp_pk_to_gcry (algo); if (keyno < 1 || keyno > 3) ; /* Out of range - ignore. */ else { snprintf (keyrefbuf, sizeof keyrefbuf, "OPENPGP.%d", keyno); keyref = keyrefbuf; kinfo = find_kinfo (parm, keyref); if (!kinfo) /* No: new entry. */ kinfo = create_kinfo (parm, keyref); /* Although we could use the the value at %n directly as * keyalgo string, we want to use the standard * keyalgo_string function and thus we reconstruct it * here to make sure the displayed form of the curve * names is used. */ nbits = 0; curve = NULL; if (algo == GCRY_PK_ECDH || algo == GCRY_PK_ECDSA || algo == GCRY_PK_EDDSA || algo == GCRY_PK_ECC) { curve = openpgp_is_curve_supported (line + n, NULL, NULL); } else /* For rsa we see here for example "rsa2048". */ { if (line[n] && line[n+1] && line[n+2]) nbits = strtoul (line+n+3, NULL, 10); } kinfo->keyalgo = get_keyalgo_string (algo, nbits, curve); kinfo->keyalgo_id = algo; } } break; case 9: if (!memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (!memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (!memcmp (keyword, "CHV-USAGE", keywordlen)) { unsigned int byte1, byte2; byte1 = byte2 = 0; sscanf (line, "%x %x", &byte1, &byte2); parm->chvusage[0] = byte1; parm->chvusage[1] = byte2; } break; case 10: if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (!memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (!memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) while (spacep (p)) p++; if (!buf) ; else if (parm->apptype == APP_TYPE_OPENPGP) { 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->chvinfo[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } } else { for (i=0; *p && i < DIM (parm->chvinfo); i++) { parm->chvinfo[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } } xfree (buf); } else if (!memcmp (keyword, "APPVERSION", keywordlen)) { unsigned int val = 0; sscanf (line, "%x", &val); parm->appversion = val; } break; case 11: if (!memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [usage] [keytime] */ - char *fields[4]; + const char *fields[4]; int nfields; const char *hexgrp, *usage; time_t keytime; line_buffer = pline = xstrdup (line); if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2) goto leave; /* not enough args - invalid status line. */ hexgrp = fields[0]; keyref = fields[1]; if (nfields > 2) usage = fields[2]; else usage = ""; if (nfields > 3) keytime = parse_timestamp (fields[3], NULL); else keytime = 0; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* New entry. */ kinfo = create_kinfo (parm, keyref); else /* Existing entry - clear grip and usage */ { memset (kinfo->grip, 0, sizeof kinfo->grip); kinfo->usage = 0; } /* Set or update the grip. Note that due to the * calloc/memset an erroneous too short grip will be nul * padded on the right. */ unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip); /* Parse and set the usage. */ for (; *usage; usage++) { switch (*usage) { case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break; case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break; case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break; case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break; } } /* Store the keytime. */ kinfo->created = keytime == (time_t)(-1)? 0 : (u32)keytime; } else if (!memcmp (keyword, "CARDVERSION", keywordlen)) { unsigned int val = 0; sscanf (line, "%x", &val); parm->cardversion = val; } break; case 12: if (!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); } else if (!memcmp (keyword, "MANUFACTURER", 12)) { xfree (parm->manufacturer_name); parm->manufacturer_name = NULL; parm->manufacturer_id = strtoul (line, &endp, 0); while (endp && spacep (endp)) endp++; if (endp && *endp) parm->manufacturer_name = xstrdup (endp); } break; case 13: if (!memcmp (keyword, "$DISPSERIALNO", keywordlen)) { xfree (parm->dispserialno); parm->dispserialno = unescape_status_string (line); } else if (!memcmp (keyword, "KEY-ATTR-INFO", keywordlen)) { if (!strncmp (line, "OPENPGP.", 8)) { int no; line += 8; no = atoi (line); if (no >= 1 && no <= 3) { no--; line++; while (spacep (line)) line++; append_to_strlist (&parm->supported_keyalgo[no], xstrdup (line)); } } /* Skip when it's not "OPENPGP.[123]". */ } break; default: /* Unknown. */ break; } leave: xfree (line_buffer); return 0; } /* Call the scdaemon to learn about a smartcard. This fills INFO * with data from the card. */ gpg_error_t scd_learn (card_info_t info) { gpg_error_t err; struct default_inq_parm_s parm; struct card_info_s dummyinfo; if (!info) info = &dummyinfo; memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "SCD LEARN --force", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); /* Also try to get some other key attributes. */ if (!err) { info->initialized = 1; err = scd_getattr ("KEY-ATTR", info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ err = scd_getattr ("$DISPSERIALNO", info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ } if (info == &dummyinfo) release_card_info (info); return err; } /* 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 command. */ gpg_error_t scd_getattr (const char *name, struct card_info_s *info) { gpg_error_t err; 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); err = start_agent (0); if (err) return err; parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, learn_status_cb, info); return err; } /* Send an setattr command to the SCdaemon. */ gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen) { gpg_error_t err; char *tmp; char *line = NULL; struct default_inq_parm_s parm; if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); tmp = strconcat ("SCD SETATTR ", name, " ", NULL); if (!tmp) { err = gpg_error_from_syserror (); goto leave; } line = percent_data_escape (1, tmp, value, valuelen); xfree (tmp); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 10 > ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } err = start_agent (0); if (err ) goto leave; memset (&parm, 0, sizeof parm); parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); leave: xfree (line); return status_sc_op_failure (err); } /* 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) { gpg_error_t err; struct writecert_parm_s *parm = opaque; if (has_leading_keyword (line, "CERTDATA")) { err = assuan_send_data (parm->dflt->ctx, parm->certdata, parm->certdatalen); } else err = default_inq_cb (parm->dflt, line); return err; } /* Send a WRITECERT command to the SCdaemon. */ gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct writecert_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; memset (&parms, 0, sizeof parms); snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.certdata = certdata; parms.certdatalen = certdatalen; err = assuan_transact (agent_ctx, line, NULL, NULL, inq_writecert_parms, &parms, NULL, NULL); return status_sc_op_failure (err); } /* Send a WRITEKEY command to the agent (so that the agent can fetch * the key to write). KEYGRIP is the hexified keygrip of the source * key which will be written to the slot KEYREF. FORCE must be true * to overwrite an existing key. */ gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip) { gpg_error_t err; struct default_inq_parm_s parm; char line[ASSUAN_LINELENGTH]; memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; /* Note: We don't send the s/n but "-" because gpg-agent has * currently no use for it. */ /* FIXME: For OpenPGP we should provide the creation time. */ snprintf (line, sizeof line, "KEYTOCARD%s %s - %s", force? " --force":"", keygrip, keyref); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); return status_sc_op_failure (err); } /* 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)) { if (createtime) *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { gnupg_status_printf (STATUS_PROGRESS, "%s", 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 * SCDAEMON. On success, creation time is stored back to CREATETIME. */ gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; gnupg_isotime_t tbuf; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; if (createtime && *createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", algo? "--algo=":"", algo? algo:"", keyref); dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, scd_genkey_cb, createtime); return status_sc_op_failure (err); } /* Return the serial number of the card or an appropriate error. The * serial number is returned as a hexstring. If DEMAND is not NULL * the reader with the a card of the serial number DEMAND is * requested. */ gpg_error_t scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; if (!demand) strcpy (line, "SCD SERIALNO --all"); else snprintf (line, DIM(line), "SCD SERIALNO --demand=%s --all", demand); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (err) { xfree (serialno); return err; } if (r_serialno) *r_serialno = serialno; else xfree (serialno); return 0; } /* Send a READCERT command to the SCdaemon. */ gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_buf = NULL; err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; init_membuf (&data, 2048); snprintf (line, sizeof line, "SCD READCERT %s", certidstr); 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; } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error_from_syserror (); return 0; } /* Send a READKEY command to the SCdaemon. On success a new * s-expression is stored at R_RESULT. */ gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *buf; size_t len, buflen; *r_result = NULL; err = start_agent (0); if (err) return err; init_membuf (&data, 1024); snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); err = assuan_transact (agent_ctx, line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) { xfree (get_membuf (&data, &len)); return err; } buf = get_membuf (&data, &buflen); if (!buf) return gpg_error_from_syserror (); err = gcry_sexp_new (r_result, buf, buflen, 0); xfree (buf); return err; } /* Callback function for 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)) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); if (parm->with_apps) { /* Format of the stored string is the S/N, a space, and a * space separated list of appnames. */ if (*s && (*s != ' ' || spacep (s+1) || !s[1])) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else /* We assume the rest of the line is well formatted. */ add_to_strlist (&parm->list, line); } else { if (*s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } } return 0; } /* Return the serial numbers of all cards currently inserted. */ gpg_error_t scd_cardlist (strlist_t *result) { gpg_error_t err; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; err = assuan_transact (agent_ctx, "SCD GETINFO card_list", 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 err; } /* Return the serial numbers and appnames of the current card or, with * ALL given has true, of all cards currently inserted. */ gpg_error_t scd_applist (strlist_t *result, int all) { gpg_error_t err; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; parm.with_apps = 1; err = assuan_transact (agent_ctx, all ? "SCD GETINFO all_active_apps" /**/: "SCD GETINFO active_apps", 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 err; } /* Change the PIN of a card or reset the retry counter. If NULLPIN is * set the TCOS specific NullPIN is changed. */ gpg_error_t scd_change_pin (const char *pinref, int reset_mode, int nullpin) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, sizeof line, "SCD PASSWD%s %s", nullpin? " --nullpin": reset_mode? " --reset":"", pinref); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return status_sc_op_failure (err); } /* Perform a CHECKPIN operation. SERIALNO should be the serial * number of the card - optionally followed by the fingerprint; * however the fingerprint is ignored here. */ gpg_error_t scd_checkpin (const char *serialno) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return status_sc_op_failure (err); } /* Return the S2K iteration count as computed by gpg-agent. On error * print a warning and return a default value. */ unsigned long agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; unsigned long count = 0; err = start_agent (0); if (err) goto leave; 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 { count = strtoul (buf, NULL, 10); xfree (buf); } } leave: if (err || count < 65536) { /* Don't print an error if an older agent is used. */ if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); /* Default to 65536 which was used up to 2.0.13. */ count = 65536; } return count; } diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c index a723739f1..ece7edc48 100644 --- a/tools/card-yubikey.c +++ b/tools/card-yubikey.c @@ -1,446 +1,446 @@ /* card-yubikey.c - Yubikey specific functions. * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include "../common/util.h" #include "../common/i18n.h" #include "../common/tlv.h" #include "../common/ttyio.h" #include "gpg-card.h" /* Object to describe requested interface options. */ struct iface_s { unsigned int usb:1; unsigned int nfc:1; }; /* Bit flags as used by the fields in struct ykapps_s. */ #define YKAPP_USB_SUPPORTED 0x01 #define YKAPP_USB_ENABLED 0x02 #define YKAPP_NFC_SUPPORTED 0x04 #define YKAPP_NFC_ENABLED 0x08 #define YKAPP_SELECTED 0x80 /* Selected by the command. */ /* An object to describe the applications on a Yubikey. Each field * has 8 bits to hold the above flag values. */ struct ykapps_s { unsigned int otp:8; unsigned int u2f:8; unsigned int opgp:8; unsigned int piv:8; unsigned int oath:8; unsigned int fido2:8; }; /* Helper to parse an unsigned integer config value consisting of bit * flags. TAG select the config item and MASK is the mask ORed into * the value for a set bit. The function modifies YK. */ static gpg_error_t parse_ul_config_value (struct ykapps_s *yk, const unsigned char *config, size_t configlen, int tag, unsigned int mask) { const unsigned char *s; size_t n; unsigned long ul = 0; int i; s = find_tlv (config, configlen, tag, &n); if (s && n) { if (n > sizeof ul) { log_error ("too large integer in Yubikey config tag %02x detected\n", tag); if (opt.verbose) log_printhex (config, configlen, "config:"); return gpg_error (GPG_ERR_CARD); } for (i=0; i < n; i++) { ul <<=8; ul |= s[i]; } if (ul & 0x01) yk->otp |= mask; if (ul & 0x02) yk->u2f |= mask; if (ul & 0x08) yk->opgp |= mask; if (ul & 0x10) yk->piv |= mask; if (ul & 0x20) yk->oath |= mask; if (ul & 0x200) yk->fido2 |= mask; } return 0; } /* Create an unsigned integer config value for TAG from the data in YK * and store it the provided 4 byte buffer RESULT. If ENABLE is true * the respective APP_SELECTED bit in YK sets the corresponding bit * flags, it is is false that bit flag is cleared. IF APP_SELECTED is * not set the bit flag is not changed. */ static void set_ul_config_value (struct ykapps_s *yk, unsigned int bitflag, int tag, unsigned int enable, unsigned char *result) { unsigned long ul = 0; /* First set the current values. */ if ((yk->otp & bitflag)) ul |= 0x01; if ((yk->u2f & bitflag)) ul |= 0x02; if ((yk->opgp & bitflag)) ul |= 0x08; if ((yk->piv & bitflag)) ul |= 0x10; if ((yk->oath & bitflag)) ul |= 0x20; if ((yk->fido2 & bitflag)) ul |= 0x200; /* Then enable or disable the bits according to the selection flag. */ if (enable) { if ((yk->otp & YKAPP_SELECTED)) ul |= 0x01; if ((yk->u2f & YKAPP_SELECTED)) ul |= 0x02; if ((yk->opgp & YKAPP_SELECTED)) ul |= 0x08; if ((yk->piv & YKAPP_SELECTED)) ul |= 0x10; if ((yk->oath & YKAPP_SELECTED)) ul |= 0x20; if ((yk->fido2 & YKAPP_SELECTED)) ul |= 0x200; } else { if ((yk->otp & YKAPP_SELECTED)) ul &= ~0x01; if ((yk->u2f & YKAPP_SELECTED)) ul &= ~0x02; if ((yk->opgp & YKAPP_SELECTED)) ul &= ~0x08; if ((yk->piv & YKAPP_SELECTED)) ul &= ~0x10; if ((yk->oath & YKAPP_SELECTED)) ul &= ~0x20; if ((yk->fido2 & YKAPP_SELECTED)) ul &= ~0x200; } /* Make sure that we do not disable the CCID transport. Without * CCID we won't have any way to change the configuration again. We * would instead need one of the other Yubikey tools to enable an * application and thus its transport again. */ if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20))) { log_info ("Enabling PIV to have at least one CCID transport\n"); ul |= 0x10; } result[0] = tag; result[1] = 2; result[2] = ul >> 8; result[3] = ul; } /* Print the info from YK. */ static void yk_list (estream_t fp, struct ykapps_s *yk) { if (opt.interactive) tty_fprintf (fp, ("Application USB NFC\n" "-----------------------\n")); tty_fprintf (fp, "OTP %s %s\n", (yk->otp & YKAPP_USB_SUPPORTED)? (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->otp & YKAPP_NFC_SUPPORTED)? (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); tty_fprintf (fp, "U2F %s %s\n", (yk->otp & YKAPP_USB_SUPPORTED)? (yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->otp & YKAPP_NFC_SUPPORTED)? (yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); tty_fprintf (fp, "OPGP %s %s\n", (yk->opgp & YKAPP_USB_SUPPORTED)? (yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->opgp & YKAPP_NFC_SUPPORTED)? (yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); tty_fprintf (fp, "PIV %s %s\n", (yk->piv & YKAPP_USB_SUPPORTED)? (yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->piv & YKAPP_NFC_SUPPORTED)? (yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); tty_fprintf (fp, "OATH %s %s\n", (yk->oath & YKAPP_USB_SUPPORTED)? (yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->oath & YKAPP_NFC_SUPPORTED)? (yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); tty_fprintf (fp, "FIDO2 %s %s\n", (yk->fido2 & YKAPP_USB_SUPPORTED)? (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ", (yk->fido2 & YKAPP_NFC_SUPPORTED)? (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- "); } /* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */ static gpg_error_t yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface, const unsigned char *config, size_t configlen, int enable) { gpg_error_t err = 0; unsigned char apdu[100]; unsigned int apdulen; /* const unsigned char *s; */ /* size_t n; */ char *hexapdu = NULL; apdulen = 0; apdu[apdulen++] = 0x00; apdu[apdulen++] = 0x1c; /* Write Config instruction. */ apdu[apdulen++] = 0x00; apdu[apdulen++] = 0x00; apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */ apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */ /* The ykman tool has no way to set NFC and USB flags in one go. * Reasoning about the Yubikey's firmware it seems plausible that * combining should work. Let's try it here if the user called for * setting both interfaces. */ if (iface->nfc) { set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen); apdulen += 4; } if (iface->usb) { set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen); apdulen += 4; /* Yubikey's ykman copies parts of the config data when writing * the config for USB. Below is a commented example on how that * can be done. */ (void)config; (void)configlen; /* Copy the device flags. */ /* s = find_tlv (config, configlen, 0x08, &n); */ /* if (s && n) */ /* { */ /* s -= 2; */ /* n += 2; */ /* if (apdulen + n > sizeof apdu) */ /* { */ /* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */ /* goto leave; */ /* } */ /* memcpy (apdu+apdulen, s, n); */ /* apdulen += n; */ /* } */ } if (iface->nfc || iface->usb) { if (apdulen + 2 > sizeof apdu) { err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); goto leave; } /* Disable the next two lines to let the card reboot. Not doing * this is however more convenient for this tool because further * commands don't end up with an error. It seems to be better * that a "reset" command from gpg-card-tool is run at the * user's discretion. */ /* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */ /* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */ /* Fixup the lngth bytes. */ apdu[4] = apdulen - 6 + 1; apdu[5] = apdulen - 6; hexapdu = bin2hex (apdu, apdulen, NULL); if (!hexapdu) err = gpg_error_from_syserror (); else err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL); } leave: xfree (hexapdu); return err; } /* Implementation part of cmd_yubikey. ARGV is an array of size ARGc * with the argumets given to the yubikey command. Note that ARGV has * no terminating NULL so that ARGC must be considered. FP is the * stream to output information. This function must only be called on * Yubikeys. */ gpg_error_t -yubikey_commands (card_info_t info, estream_t fp, int argc, char *argv[]) +yubikey_commands (card_info_t info, estream_t fp, int argc, const char *argv[]) { gpg_error_t err; enum {ykLIST, ykENABLE, ykDISABLE } cmd; struct iface_s iface = {0,0}; struct ykapps_s ykapps = {0}; unsigned char *config = NULL; size_t configlen; int i; if (!argc) return gpg_error (GPG_ERR_SYNTAX); /* Parse command. */ if (!ascii_strcasecmp (argv[0], "list")) cmd = ykLIST; else if (!ascii_strcasecmp (argv[0], "enable")) cmd = ykENABLE; else if (!ascii_strcasecmp (argv[0], "disable")) cmd = ykDISABLE; else { err = gpg_error (GPG_ERR_UNKNOWN_COMMAND); goto leave; } if (info->cardversion < 0x050000 && cmd != ykLIST) { log_info ("Sub-command '%s' is only support by Yubikey-5 and later\n", argv[0]); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Parse interface if needed. */ if (cmd == ykLIST) iface.usb = iface.nfc = 1; else if (argc < 2) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } else if (!ascii_strcasecmp (argv[1], "usb")) iface.usb = 1; else if (!ascii_strcasecmp (argv[1], "nfc")) iface.nfc = 1; else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*")) iface.usb = iface.nfc = 1; else { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Parse list of applications. */ for (i=2; i < argc; i++) { if (!ascii_strcasecmp (argv[i], "otp")) ykapps.otp = 0x80; else if (!ascii_strcasecmp (argv[i], "u2f")) ykapps.u2f = 0x80; else if (!ascii_strcasecmp (argv[i], "opgp") ||!ascii_strcasecmp (argv[i], "openpgp")) ykapps.opgp = 0x80; else if (!ascii_strcasecmp (argv[i], "piv")) ykapps.piv = 0x80; else if (!ascii_strcasecmp (argv[i], "oath") || !ascii_strcasecmp (argv[i], "oauth")) ykapps.oath = 0x80; else if (!ascii_strcasecmp (argv[i], "fido2")) ykapps.fido2 = 0x80; else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*")) { ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath = ykapps.fido2 = 0x80; } else { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } } /* Select the Yubikey Manager application. */ err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0, NULL, NULL); if (err) goto leave; /* Send the read config command. */ err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen); if (err) goto leave; if (!configlen || *config > configlen - 1) { /* The length byte is shorter than the actual length. */ log_error ("Yubikey returned improper config data\n"); log_printhex (config, configlen, "config:"); err = gpg_error (GPG_ERR_CARD); goto leave; } if (configlen-1 > *config) { log_info ("Extra config data ignored\n"); log_printhex (config, configlen, "config:"); } configlen = *config; err = parse_ul_config_value (&ykapps, config+1, configlen, 0x01, YKAPP_USB_SUPPORTED); if (!err) err = parse_ul_config_value (&ykapps, config+1, configlen, 0x03, YKAPP_USB_ENABLED); if (!err) err = parse_ul_config_value (&ykapps, config+1, configlen, 0x0d, YKAPP_NFC_SUPPORTED); if (!err) err = parse_ul_config_value (&ykapps, config+1, configlen, 0x0e, YKAPP_NFC_ENABLED); if (err) goto leave; switch (cmd) { case ykLIST: yk_list (fp, &ykapps); break; case ykENABLE: err = yk_enable_disable (&ykapps, &iface, config+1, configlen, 1); break; case ykDISABLE: err = yk_enable_disable (&ykapps, &iface, config+1, configlen, 0); break; } leave: xfree (config); return err; } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 9238b4759..ba48905e2 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,4079 +1,4079 @@ /* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019, 2020 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 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. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif /*HAVE_LIBREADLINE*/ #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/ttyio.h" #include "../common/server-help.h" #include "../common/openpgpdefs.h" #include "../common/tlv.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define HISTORYNAME ".gpg-card_history" /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oNoAutostart, oAgentProgram, oDisplay, oTTYname, oTTYtype, oXauthority, oLCctype, oLCmessages, oNoKeyLookup, oNoHistory, oChUid, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", "use --no-key-lookup for \"list\""), ARGPARSE_s_n (oNoHistory,"no-history", "do not use the command history file"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* An object to create lists of labels and keyrefs. */ struct keyinfolabel_s { const char *label; const char *keyref; }; typedef struct keyinfolabel_s *keyinfolabel_t; /* Helper for --chuid. */ static const char *changeuser; /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 /* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 #define OPENPGP_KDF_DATA_LENGTH_MAX 110 /* Local prototypes. */ static void show_keysize_warning (void); static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. With a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; } 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)); } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; 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 oNoKeyLookup: opt.no_key_lookup = 1; break; case oNoHistory: opt.no_history = 1; break; case oChUid: changeuser = pargs->r.ret_str; break; default: pargs->err = 2; break; } } } /* gpg-card main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; char **command_list = NULL; int cmdidx; char *command; gnupg_reopen_std ("gpg-card"); gpgrt_set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", gpg_strerror (gpg_error_from_syserror ())); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ if (log_get_errorcount (0)) exit (2); /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.gpgsm_program) opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); /* Now build the list of commands. We guess the size of the array * by assuming each item is a complete command. Obviously this will * be rarely the case, but it is less code to allocate a possible * too large array. */ command_list = xcalloc (argc+1, sizeof *command_list); cmdidx = 0; command = NULL; while (argc) { for ( ; argc && strcmp (*argv, "--"); argc--, argv++) { if (!command) command = xstrdup (*argv); else { char *tmp = xstrconcat (command, " ", *argv, NULL); xfree (command); command = tmp; } } if (argc) { /* Skip the double dash. */ argc--; argv++; } if (command) { command_list[cmdidx++] = command; command = NULL; } } opt.interactive = !cmdidx; if (!opt.interactive) opt.no_history = 1; if (opt.interactive) { interactive_loop (); err = 0; } else { struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) { err = dispatch_command (info, command); if (err) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* This was a "quit". */ else if (command && !opt.quiet) log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); if (command_list) { for (cmdidx=0; command_list[cmdidx]; cmdidx++) xfree (command_list[cmdidx]); xfree (command_list); } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on * success returns 0 and stores the number of bytes read at R_BUFLEN * and the address of a newly allocated buffer at R_BUFFER. A * complementary nul byte is always appended to the data but not * counted; this allows to pass NULL for R-BUFFER and consider the * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { gpg_error_t err; estream_t fp; char *data; int n; *r_buffer = NULL; if (r_buflen) *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } data = xtrymalloc (MAX_GET_DATA_FROM_FILE); if (!data) { err = gpg_error_from_syserror (); log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); es_fclose (fp); return err; } n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { err = gpg_error_from_syserror (); tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (data); return err; } data[n] = 0; *r_buffer = data; if (r_buflen) *r_buflen = n; return 0; } /* Fixup the ENODEV error from scdaemon which we may see after * removing a card due to scdaemon scanning for readers with cards. * We also map the CAERD REMOVED error to the more useful CARD_NOT * PRESENT. */ static gpg_error_t fixup_scd_errors (gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); return err; } /* Set the card removed flag from INFO depending on ERR. This does * not clear the flag. */ static gpg_error_t maybe_set_card_removed (card_info_t info, gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) info->card_removed = 1; return err; } /* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on * success. */ static gpg_error_t put_data_to_file (const char *fname, const void *buffer, size_t length) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "wb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (length && es_fwrite (buffer, length, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); es_fclose (fp); return err; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); return err; } return 0; } /* Return a malloced string with the number opf the menu PROMPT. * Control-D is mapped to "Q". */ static char * get_selection (const char *prompt) { char *answer; tty_printf ("\n"); tty_printf ("%s", prompt); tty_printf ("\n"); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); if (*answer == CONTROL_D) strcpy (answer, "q"); return answer; } /* Simply prints TEXT to the output. Returns 0 as a convenience. * This is a separate function so that it can be extended to run * less(1) or so. The extra arguments are int values terminated by a * 0 to indicate card application types supported with this command. * If none are given (just the final 0), this is a general * command. */ static gpg_error_t print_help (const char *text, ...) { estream_t fp; va_list arg_ptr; int value; int any = 0; fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) tty_fprintf (fp, "[Supported by: "); tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; } /* Print an (OpenPGP) fingerprint. */ static void print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { int i; if (fpr) { for (i=0; i < fprlen ; i++, fpr++) tty_fprintf (fp, "%02X", *fpr); } else tty_fprintf (fp, " [none]"); tty_fprintf (fp, "\n"); } /* Print the keygrip GRP. */ static void print_keygrip (estream_t fp, const unsigned char *grp) { int i; for (i=0; i < 20 ; i++, grp++) tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } /* Print a string but avoid printing control characters. */ static void print_string (estream_t fp, const char *text, const char *name) { tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ if (name && *name) { if (fp) print_utf8_buffer2 (fp, name, strlen (name), '\n'); else tty_print_utf8_string2 (NULL, name, strlen (name), 0); } else tty_fprintf (fp, _("[not set]")); tty_fprintf (fp, "\n"); } /* Print an ISO formatted name or "[not set]". */ static void print_isoname (estream_t fp, const char *name) { if (name && *name) { char *p, *given, *buf; buf = xstrdup (name); given = strstr (buf, "<<"); for (p=buf; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; if (fp) print_utf8_buffer2 (fp, given, strlen (given), '\n'); else tty_print_utf8_string2 (NULL, given, strlen (given), 0); if (*buf) tty_fprintf (fp, " "); } if (fp) print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); else tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); xfree (buf); } else { tty_fprintf (fp, _("[not set]")); } tty_fprintf (fp, "\n"); } /* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int mem_is_zero (const char *mem, unsigned int memlen) { int i; for (i=0; i < memlen && !mem[i]; i++) ; return (i == memlen); } /* Helper to list a single keyref. LABEL_KEYREF is a fallback key * reference if no info is available; it may be NULL. */ static void list_one_kinfo (card_info_t info, key_info_t kinfo, const char *label_keyref, estream_t fp, int no_key_lookup) { gpg_error_t err; key_info_t firstkinfo = info->kinfo; keyblock_t keyblock = NULL; keyblock_t kb; pubkey_t pubkey; userid_t uid; key_info_t ki; const char *s; gcry_sexp_t s_pkey; int any; if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo); goto leave; } print_keygrip (fp, kinfo->grip); tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); if (kinfo->usage) { any = 0; tty_fprintf (fp, " ("); if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) { tty_fprintf (fp, "sign"); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_CERT)) { tty_fprintf (fp, "%scert", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) { tty_fprintf (fp, "%sauth", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) { tty_fprintf (fp, "%sencr", any?",":""); any=1; } tty_fprintf (fp, ")"); } tty_fprintf (fp, "\n"); if (!(err = scd_readkey (kinfo->keyref, &s_pkey))) { char *tmp = pubkey_algo_string (s_pkey, NULL); tty_fprintf (fp, " algorithm ..: %s\n", tmp); xfree (tmp); gcry_sexp_release (s_pkey); s_pkey = NULL; } else { maybe_set_card_removed (info, err); tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo); } if (kinfo->fprlen && kinfo->created) { tty_fprintf (fp, " stored fpr .: "); print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } if (no_key_lookup) err = 0; else err = get_matching_keys (kinfo->grip, (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), &keyblock); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err)); goto leave; } for (kb = keyblock; kb; kb = kb->next) { tty_fprintf (fp, " used for ...: %s\n", kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); pubkey = kb->keys; if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) { /* If this is not the primary key print the primary * key's fingerprint or a reference to it. */ tty_fprintf (fp, " main key .: "); for (ki=firstkinfo; ki; ki = ki->next) if (pubkey->grip_valid && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) break; if (ki) { /* Fixme: Replace mapping by a table lookup. */ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) s = "this"; else if (!strcmp (ki->keyref, "OPENPGP.1")) s = "Signature key"; else if (!strcmp (ki->keyref, "OPENPGP.2")) s = "Encryption key"; else if (!strcmp (ki->keyref, "OPENPGP.3")) s = "Authentication key"; else s = NULL; if (s) tty_fprintf (fp, "<%s>\n", s); else tty_fprintf (fp, "\n", ki->keyref); } else /* Print the primary key as fallback. */ print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); /* Find the primary or subkey of that key. */ for (; pubkey; pubkey = pubkey->next) if (pubkey->grip_valid && !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) break; if (pubkey) { tty_fprintf (fp, " fpr ......: "); print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); tty_fprintf (fp, " created ..: %s\n", isotimestamp (pubkey->created)); } } for (uid = kb->uids; uid; uid = uid->next) { print_string (fp, " user id ..: ", uid->value); } } } else { tty_fprintf (fp, " [none]\n"); if (label_keyref) tty_fprintf (fp, " keyref .....: %s\n", label_keyref); if (kinfo) tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo); } leave: release_keyblock (keyblock); } /* List all keyinfo in INFO using the list of LABELS. */ static void list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp, int no_key_lookup) { key_info_t kinfo; int idx, i, j; /* Print the keyinfo. We first print those we known and then all * remaining item. */ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) kinfo->xflag = 0; if (labels) { for (idx=0; labels[idx].label; idx++) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); list_one_kinfo (info, kinfo, labels[idx].keyref, fp, no_key_lookup); if (kinfo) kinfo->xflag = 1; } } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { if (kinfo->xflag) continue; tty_fprintf (fp, "Key %s", kinfo->keyref); for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1) tty_fprintf (fp, j? ".":" "); tty_fprintf (fp, ":"); list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup); } } /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp, int no_key_lookup) { static struct keyinfolabel_s keyinfolabels[] = { { "Signature key ....:", "OPENPGP.1" }, { "Encryption key....:", "OPENPGP.2" }, { "Authentication key:", "OPENPGP.3" }, { NULL, NULL } }; if (!info->serialno || strncmp (info->serialno, "D27600012401", 12) || strlen (info->serialno) != 32 ) { tty_fprintf (fp, "invalid OpenPGP card\n"); return; } tty_fprintf (fp, "Name of cardholder: "); print_isoname (fp, info->disp_name); print_string (fp, "Language prefs ...: ", info->disp_lang); tty_fprintf (fp, "Salutation .......: %s\n", info->disp_sex == 1? _("Mr."): info->disp_sex == 2? _("Ms.") : ""); print_string (fp, "URL of public key : ", info->pubkey_url); print_string (fp, "Login data .......: ", info->login_data); if (info->private_do[0]) print_string (fp, "Private DO 1 .....: ", info->private_do[0]); if (info->private_do[1]) print_string (fp, "Private DO 2 .....: ", info->private_do[1]); if (info->private_do[2]) print_string (fp, "Private DO 3 .....: ", info->private_do[2]); if (info->private_do[3]) print_string (fp, "Private DO 4 .....: ", info->private_do[3]); if (info->cafpr1len) { tty_fprintf (fp, "CA fingerprint %d .:", 1); print_shax_fpr (fp, info->cafpr1, info->cafpr1len); } if (info->cafpr2len) { tty_fprintf (fp, "CA fingerprint %d .:", 2); print_shax_fpr (fp, info->cafpr2, info->cafpr2len); } if (info->cafpr3len) { tty_fprintf (fp, "CA fingerprint %d .:", 3); print_shax_fpr (fp, info->cafpr3, info->cafpr3len); } tty_fprintf (fp, "Signature PIN ....: %s\n", info->chv1_cached? _("not forced"): _("forced")); tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); tty_fprintf (fp, "PIN retry counter : %d %d %d\n", info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); tty_fprintf (fp, "Capabilities .....:"); if (info->extcap.ki) tty_fprintf (fp, " key-import"); if (info->extcap.aac) tty_fprintf (fp, " algo-change"); if (info->extcap.bt) tty_fprintf (fp, " button"); if (info->extcap.sm) tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo)); if (info->extcap.private_dos) tty_fprintf (fp, " priv-data"); tty_fprintf (fp, "\n"); if (info->extcap.kdf) { tty_fprintf (fp, "KDF setting ......: %s\n", info->kdf_do_enabled ? "on" : "off"); } if (info->extcap.bt) { tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off"); } list_all_kinfo (info, keyinfolabels, fp, no_key_lookup); } /* List PIV card specific data. */ static void list_piv (card_info_t info, estream_t fp, int no_key_lookup) { static struct keyinfolabel_s keyinfolabels[] = { { "PIV authentication:", "PIV.9A" }, { "Card authenticat. :", "PIV.9E" }, { "Digital signature :", "PIV.9C" }, { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; const char *s; int i; if (info->chvusage[0] || info->chvusage[1]) { tty_fprintf (fp, "PIN usage policy .:"); if ((info->chvusage[0] & 0x40)) tty_fprintf (fp, " app-pin"); if ((info->chvusage[0] & 0x20)) tty_fprintf (fp, " global-pin"); if ((info->chvusage[0] & 0x10)) tty_fprintf (fp, " occ"); if ((info->chvusage[0] & 0x08)) tty_fprintf (fp, " vci"); if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) tty_fprintf (fp, " pairing"); if (info->chvusage[1] == 0x10) tty_fprintf (fp, " primary:card"); else if (info->chvusage[1] == 0x20) tty_fprintf (fp, " primary:global"); tty_fprintf (fp, "\n"); } tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo); i++) { if (info->chvinfo[i] > 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup); } /* List Netkey card specific data. */ static void list_nks (card_info_t info, estream_t fp, int no_key_lookup) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; const char *s; int i; tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo); i++) { if (info->chvinfo[i] >= 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -4: s = "[nullpin]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup); } static void print_a_version (estream_t fp, const char *prefix, unsigned int value) { unsigned int a, b, c, d; a = ((value >> 24) & 0xff); b = ((value >> 16) & 0xff); c = ((value >> 8) & 0xff); d = ((value ) & 0xff); if (a) tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); else if (b) tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); else if (c) tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); else tty_fprintf (fp, "%s %u\n", prefix, d); } /* Print all available information about the current card. With * NO_KEY_LOOKUP the sometimes expensive listing of all matching * OpenPGP and X.509 keys is not done */ static void list_card (card_info_t info, int no_key_lookup) { estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", info->reader? info->reader : "[none]"); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); if (info->cardversion) print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", info->serialno? info->serialno : "[none]"); tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); if (info->appversion) print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); if (info->manufacturer_name && info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s (%x)\n", info->manufacturer_name, info->manufacturer_id); else if (info->manufacturer_name && !info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name); else if (info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id); switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp, no_key_lookup); break; case APP_TYPE_PIV: list_piv (info, fp, no_key_lookup); break; case APP_TYPE_NKS: list_nks (info, fp, no_key_lookup); break; default: break; } } /* Helper for cmd_list. */ static void print_card_list (estream_t fp, card_info_t info, strlist_t cards, int only_current) { int count; strlist_t sl; size_t snlen; int star; const char *s; for (count = 0, sl = cards; sl; sl = sl->next, count++) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); star = (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)); } else star = 0; if (!only_current || star) tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d); } } /* The LIST command. This also updates INFO if needed. */ static gpg_error_t cmd_list (card_info_t info, char *argstr) { gpg_error_t err; int opt_cards, opt_apps, opt_info, opt_no_key_lookup; strlist_t cards = NULL; strlist_t sl; estream_t fp = opt.interactive? NULL : es_stdout; const char *cardsn = NULL; char *appstr = NULL; int count; int need_learn = 0; if (!info) return print_help ("LIST [--cards] [--apps] [--info] [--no-key-lookup] [N] [APP]\n\n" "Show the content of the current card.\n" "With N given select and list the N-th card;\n" "with APP also given select that application.\n" "To select an APP on the current card use '-' for N.\n" "The S/N of the card may be used instead of N.\n" " --cards lists available cards\n" " --apps lists additional card applications\n" " --info selects a card and prints its s/n\n" " --no-key-lookup does not list matching OpenPGP or X.509 keys\n" , 0); opt_cards = has_leading_option (argstr, "--cards"); opt_apps = has_leading_option (argstr, "--apps"); opt_info = has_leading_option (argstr, "--info"); opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup"); argstr = skip_options (argstr); if (opt.no_key_lookup) opt_no_key_lookup = 1; if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1))) { if (*argstr == '-' && (argstr[1] || spacep (argstr+1))) argstr++; /* Keep current card. */ else { cardsn = argstr; while (hexdigitp (argstr)) argstr++; if (*argstr && !spacep (argstr)) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (*argstr) *argstr++ = 0; } while (spacep (argstr)) argstr++; if (*argstr) { appstr = argstr; while (*argstr && !spacep (argstr)) argstr++; while (spacep (argstr)) argstr++; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } } } else if (*argstr) { /* First argument needs to be a digit. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (!info->serialno || info->need_sn_cmd) { /* This is probably the first call or was explictly requested. * We need to send a SERIALNO command to scdaemon so that our * session knows all cards. */ err = scd_serialno (NULL, NULL); if (err) goto leave; info->need_sn_cmd = 0; need_learn = 1; } if (opt_cards || opt_apps) { /* Note that with option --apps CARDS is here the list of all * apps. Format is "SERIALNO APPNAME {APPNAME}". We print the * card number in the first column. */ if (opt_apps) err = scd_applist (&cards, opt_cards); else err = scd_cardlist (&cards); if (err) goto leave; print_card_list (fp, info, cards, 0); } else { if (cardsn) { int i, cardno; err = scd_cardlist (&cards); if (err) goto leave; /* Switch to the requested card. */ for (i=0; digitp (cardsn+i); i++) ; if (i && i < 4 && !cardsn[i]) { /* Looks like an index into the card list. */ cardno = atoi (cardsn); for (count = 0, sl = cards; sl; sl = sl->next, count++) if (count == cardno) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } else /* S/N of card specified. */ { for (sl = cards; sl; sl = sl->next) if (!ascii_strcasecmp (sl->d, cardsn)) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } err = scd_switchcard (sl->d); need_learn = 1; } else /* show app list. */ { err = scd_applist (&cards, 1); if (err) goto leave; } if (appstr && *appstr) { /* Switch to the requested app. */ err = scd_switchapp (appstr); if (err) goto leave; need_learn = 1; } if (need_learn) err = scd_learn (info); else err = 0; if (err) ; else if (opt_info) print_card_list (fp, info, cards, 1); else { size_t snlen; const char *s; /* First get the list of active cards and check whether the * current card is still in the list. If not the card has * been removed. Note that during the listing the card * remove state might also be detected but only if an access * to the scdaemon is required; it is anyway better to test * that before starting a listing. */ free_strlist (cards); err = scd_cardlist (&cards); if (err) goto leave; for (sl = cards; sl; sl = sl->next) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); if (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)) break; } } if (!sl) { info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); goto leave; } list_card (info, opt_no_key_lookup); } } leave: free_strlist (cards); return err; } /* The VERIFY command. */ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err; const char *pinref; if (!info) return print_help ("verify [chvid]", 0); if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = info->serialno; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else return gpg_error (GPG_ERR_MISSING_VALUE); err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); return err; } static gpg_error_t cmd_authenticate (card_info_t info, char *argstr) { gpg_error_t err; int opt_setkey; int opt_raw; char *string = NULL; char *key = NULL; size_t keylen; if (!info) return print_help ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" "Perform a mutual authentication either by reading the key\n" "from FILE or by taking it from the command line. Without\n" "the option --raw the key is expected to be hex encoded.\n" "To install a new administration key --setkey is used; this\n" "requires a prior authentication with the old key.", APP_TYPE_PIV, 0); if (info->apptype != APP_TYPE_PIV) { log_info ("Note: This is a PIV only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } opt_setkey = has_leading_option (argstr, "--setkey"); opt_raw = has_leading_option (argstr, "--raw"); argstr = skip_options (argstr); if (*argstr == '<') /* Read key from a file. */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &string, NULL); if (err) goto leave; } if (opt_raw) { key = string? string : xstrdup (argstr); string = NULL; keylen = strlen (key); } else { key = hex_to_buffer (string? string: argstr, &keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } } err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); leave: if (key) { wipememory (key, keylen); xfree (key); } xfree (string); return err; } /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) { char *name; int i; for (;;) { name = tty_get (prompt); trim_spaces (name); tty_kill_prompt (); if (!*name || *name == CONTROL_D) { if (*name == CONTROL_D) tty_fprintf (NULL, "\n"); xfree (name); return NULL; } for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) ; /* The name must be in Latin-1 and not UTF-8 - lacking the code * to ensure this we restrict it to ASCII. */ if (name[i]) tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); else if (strchr (name, '<')) tty_printf (_("Error: The \"<\" character may not be used.\n")); else if (strstr (name, " ")) tty_printf (_("Error: Double spaces are not allowed.\n")); else return name; xfree (name); } } /* The NAME command. */ static gpg_error_t cmd_name (card_info_t info, const char *argstr) { gpg_error_t err; char *surname, *givenname; char *isoname, *p; if (!info) return print_help ("name [--clear]\n\n" "Set the name field of an OpenPGP card. With --clear the stored\n" "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } again: if (!strcmp (argstr, "--clear")) isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ else { surname = ask_one_name (_("Cardholder's surname: ")); givenname = ask_one_name (_("Cardholder's given name: ")); if (!surname || !givenname || (!*surname && !*givenname)) { xfree (surname); xfree (givenname); return gpg_error (GPG_ERR_CANCELED); } isoname = xstrconcat (surname, "<<", givenname, NULL); xfree (surname); xfree (givenname); for (p=isoname; *p; p++) if (*p == ' ') *p = '<'; if (strlen (isoname) > 39 ) { log_info (_("Error: Combined name too long " "(limit is %d characters).\n"), 39); xfree (isoname); goto again; } } err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); xfree (isoname); return err; } static gpg_error_t cmd_url (card_info_t info, const char *argstr) { gpg_error_t err; char *url; if (!info) return print_help ("URL [--clear]\n\n" "Set the URL data object. That data object can be used by\n" "the FETCH command to retrieve the full public key. The\n" "option --clear deletes the content of that data object.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!strcmp (argstr, "--clear")) url = xstrdup (" "); /* No real way to clear; set to space instead. */ else { url = tty_get (_("URL to retrieve public key: ")); trim_spaces (url); tty_kill_prompt (); if (!*url || *url == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr ("PUBKEY-URL", url, strlen (url)); leave: xfree (url); return err; } /* Fetch the key from the URL given on the card or try to get it from * the default keyserver. */ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; key_info_t kinfo; if (!info) return print_help ("FETCH\n\n" "Retrieve a key using the URL data object or if that is missing\n" "using the fingerprint.", APP_TYPE_OPENPGP, 0); if (info->pubkey_url && *info->pubkey_url) { /* strlist_t sl = NULL; */ /* add_to_strlist (&sl, info.pubkey_url); */ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else err = gpg_error (GPG_ERR_NO_DATA); return err; } static gpg_error_t cmd_login (card_info_t info, char *argstr) { gpg_error_t err; char *data; size_t datalen; if (!info) return print_help ("LOGIN [--clear] [< FILE]\n\n" "Set the login data object. If FILE is given the data is\n" "is read from that file. This allows for binary data.\n" "The option --clear deletes the login data.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) { data = xstrdup (" "); /* kludge. */ datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { data = tty_get (_("Login data (account name): ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } datalen = strlen (data); } err = scd_setattr ("LOGIN-DATA", data, datalen); leave: xfree (data); return err; } static gpg_error_t cmd_lang (card_info_t info, const char *argstr) { gpg_error_t err; char *data, *p; if (!info) return print_help ("LANG [--clear]\n\n" "Change the language info for the card. This info can be used\n" "by applications for a personalized greeting. Up to 4 two-digit\n" "language identifiers can be entered as a preference. The option\n" "--clear removes all identifiers. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) data = xstrdup (" "); /* Note that we need two spaces here. */ else { again: data = tty_get (_("Language preferences: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (strlen (data) > 8 || (strlen (data) & 1)) { log_info (_("Error: invalid length of preference string.\n")); xfree (data); goto again; } for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) ; if (*p) { log_info (_("Error: invalid characters in preference string.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-LANG", data, strlen (data)); leave: xfree (data); return err; } static gpg_error_t cmd_salut (card_info_t info, const char *argstr) { gpg_error_t err; char *data = NULL; const char *str; if (!info) return print_help ("SALUT [--clear]\n\n" "Change the salutation info for the card. This info can be used\n" "by applications for a personalized greeting. The option --clear\n" "removes this data object. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); again: if (!strcmp (argstr, "--clear")) str = "9"; else { data = tty_get (_("Salutation (M = Mr., F = Ms., or space): ")); trim_spaces (data); tty_kill_prompt (); if (*data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!*data) str = "9"; else if ((*data == 'M' || *data == 'm') && !data[1]) str = "1"; else if ((*data == 'F' || *data == 'f') && !data[1]) str = "2"; else { tty_printf (_("Error: invalid response.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-SEX", str, 1); leave: xfree (data); return err; } static gpg_error_t cmd_cafpr (card_info_t info, char *argstr) { gpg_error_t err; char *data = NULL; const char *s; int i, c; unsigned char fpr[32]; int fprlen; int fprno; int opt_clear = 0; if (!info) return print_help ("CAFPR [--clear] N\n\n" "Change the CA fingerprint number N. N must be in the\n" "range 1 to 3. The option --clear clears the specified\n" "CA fingerprint N or all of them if N is 0 or not given.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { fprno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else fprno = 0; if (opt_clear && !fprno) ; /* Okay: clear all fprs. */ else if (fprno < 1 || fprno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } again: if (opt_clear) { memset (fpr, 0, 20); fprlen = 20; } else { xfree (data); data = tty_get (_("CA fingerprint: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } for (i=0, s=data; i < sizeof fpr && *s; ) { while (spacep(s)) s++; if (*s == ':') s++; while (spacep(s)) s++; c = hextobyte (s); if (c == -1) break; fpr[i++] = c; s += 2; } fprlen = i; if ((fprlen != 20 && fprlen != 32) || *s) { log_error (_("Error: invalid formatted fingerprint.\n")); goto again; } } if (!fprno) { log_assert (opt_clear); err = scd_setattr ("CA-FPR-1", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-2", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-3", fpr, fprlen); } else err = scd_setattr (fprno==1?"CA-FPR-1": fprno==2?"CA-FPR-2": fprno==3?"CA-FPR-3":"x", fpr, fprlen); leave: xfree (data); return err; } static gpg_error_t cmd_privatedo (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *do_name = NULL; char *data = NULL; size_t datalen; int do_no; if (!info) return print_help ("PRIVATEDO [--clear] N [< FILE]\n\n" "Change the private data object N. N must be in the\n" "range 1 to 4. If FILE is given the data is is read\n" "from that file. The option --clear clears the data.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { do_no = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else do_no = 0; if (do_no < 1 || do_no > 4) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_name = xasprintf ("PRIVATE-DO-%d", do_no); if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else if (*argstr) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } else { data = tty_get (_("Private DO data: ")); trim_spaces (data); tty_kill_prompt (); datalen = strlen (data); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr (do_name, data, datalen); leave: xfree (do_name); xfree (data); return err; } static gpg_error_t cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; int opt_openpgp; char *certref_buffer = NULL; char *certref; char *data = NULL; size_t datalen; estream_t key = NULL; if (!info) return print_help ("WRITECERT CERTREF '<' FILE\n" "WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n" "WRITECERT --clear CERTREF\n\n" "Write a certificate for key 3. The option --clear removes\n" "the certificate from the card. The option --openpgp expects\n" "a keyblock and stores it encapsulated in a CMS container; the\n" "keyblock is taken from FILE or directly from the key with FPR", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); goto leave; } certref = certref_buffer = xstrdup ("OPENPGP.3"); } else /* Upcase the certref; prepend cardtype if needed. */ { if (!strchr (certref, '.')) certref_buffer = xstrconcat (app_type_string (info->apptype), ".", certref, NULL); else certref_buffer = xstrdup (certref); ascii_strupr (certref_buffer); certref = certref_buffer; } if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) { struct b64state b64; err = b64dec_start (&b64, ""); if (!err) err = b64dec_proc (&b64, data, datalen, &datalen); if (!err) err = b64dec_finish (&b64); if (err) goto leave; } } else if (opt_openpgp && *argstr) { err = get_minimal_openpgp_key (&key, argstr); if (err) goto leave; if (es_fclose_snatch (key, (void*)&data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_openpgp && !opt_clear) { tlv_builder_t tb; void *tmpder; size_t tmpderlen; tb = tlv_builder_new (0); if (!tb) { err = gpg_error_from_syserror (); goto leave; } tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10); tlv_builder_add_tag (tb, CLASS_CONTEXT, 0); tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen); tlv_builder_add_end (tb); tlv_builder_add_end (tb); err = tlv_builder_finalize (tb, &tmpder, &tmpderlen); if (err) goto leave; xfree (data); data = tmpder; datalen = tmpderlen; } err = scd_writecert (certref, data, datalen); leave: es_fclose (key); xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; char *certref_buffer = NULL; char *certref; void *data = NULL; size_t datalen, dataoff; const char *fname; int opt_openpgp; if (!info) return print_help ("READCERT [--openpgp] CERTREF > FILE\n\n" "Read the certificate for key CERTREF and store it in FILE.\n" "With option \"--openpgp\" an OpenPGP keyblock is expected\n" "and stored in FILE.\n", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); goto leave; } certref = certref_buffer = xstrdup ("OPENPGP.3"); } if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; fname = argstr; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } dataoff = 0; err = scd_readcert (certref, &data, &datalen); if (err) goto leave; if (opt_openpgp) { /* Check whether DATA contains an OpenPGP keyblock and put only * this into FILE. If the data is something different, return * an error. */ const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; p = data; n = datalen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) goto not_openpgp; /* Does not start with a sequence. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) goto not_openpgp; /* No Object ID. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ if (objlen != 10 || memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen)) goto not_openpgp; /* Wrong Object ID. */ p += objlen; n -= objlen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_CONTEXT && tag == 0 && cons)) goto not_openpgp; /* Not a [0] context tag. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons)) goto not_openpgp; /* Not an octet string. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ dataoff = p - (const unsigned char*)data; datalen = objlen; } err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen); goto leave; not_openpgp: err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_writekey (card_info_t info, char *argstr) { gpg_error_t err; int opt_force; - char *argv[2]; + const char *argv[2]; int argc; char *keyref_buffer = NULL; - char *keyref; - char *keygrip; + const char *keyref; + const char *keygrip; if (!info) return print_help ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" "Write a private key object identified by KEYGRIP to slot KEYREF.\n" "Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_force = has_leading_option (argstr, "--force"); argstr = skip_options (argstr); argc = split_fields (argstr, argv, DIM (argv)); if (argc < 2) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Upcase the keyref; prepend cardtype if needed. */ keyref = argv[0]; if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; /* Get the keygrip. */ keygrip = argv[1]; if (strlen (keygrip) != 40 && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) { log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writekey (keyref, opt_force, keygrip); leave: xfree (keyref_buffer); return err; } static gpg_error_t cmd_forcesig (card_info_t info) { gpg_error_t err; int newstate; if (!info) return print_help ("FORCESIG\n\n" "Toggle the forcesig flag of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } newstate = !info->chv1_cached; err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); if (err) goto leave; /* Read it back to be sure we have the right toggle state the next * time. */ err = scd_getattr ("CHV-STATUS", info); leave: return err; } /* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) { gpg_error_t err = 0; *forced_chv1 = !info->chv1_cached; if (*forced_chv1) { /* Switch off the forced mode so that during key generation we * don't get bothered with PIN queries for each self-signature. */ err = scd_setattr ("CHV-STATUS-1", "\x01", 1); if (err) { log_error ("error clearing forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = -1; /* Not changed. */ goto leave; } } /* Check the PIN now, so that we won't get asked later for each * binding signature. */ err = scd_checkpin (info->serialno); if (err) log_error ("error checking the PIN: %s\n", gpg_strerror (err)); leave: return err; } /* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { gpg_error_t err; /* Note the possible values stored at FORCED_CHV1: * 0 - forcesig was not enabled. * 1 - forcesig was enabled - enable it again. * -1 - We have not changed anything. */ if (*forced_chv1 == 1) { /* Switch back to forced state. */ err = scd_setattr ("CHV-STATUS-1", "", 1); if (err) log_error ("error setting forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = 0; } } /* Ask whether existing keys shall be overwritten. With NULL used for * KINFO it will ask for all keys, other wise for the given key. */ static gpg_error_t ask_replace_keys (key_info_t kinfo) { gpg_error_t err; char *answer; tty_printf ("\n"); if (kinfo) log_info (_("Note: key %s is already stored on the card!\n"), kinfo->keyref); else log_info (_("Note: Keys are already stored on the card!\n")); tty_printf ("\n"); if (kinfo) answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref); else answer = tty_get (_("Replace existing keys? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; xfree (answer); return err; } /* Implementation of cmd_generate for OpenPGP cards to generate all * standard keys at once. */ static gpg_error_t generate_all_openpgp_card_keys (card_info_t info, char **algos) { gpg_error_t err; int forced_chv1 = -1; int want_backup; char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; if (info->extcap.ki) { xfree (answer); answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else want_backup = 0; kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) ) { err = ask_replace_keys (NULL); if (err) goto leave; } /* If no displayed name has been set, we assume that this is a fresh * card and print a hint about the default PINs. */ if (!info->disp_name || !*info->disp_name) { tty_printf ("\n"); tty_printf (_("Please note that the factory settings of the PINs are\n" " PIN = '%s' Admin PIN = '%s'\n" "You should change them using the command --change-pin\n"), OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); tty_printf ("\n"); } err = check_pin_for_key_operation (info, &forced_chv1); if (err) goto leave; (void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */ /* FIXME: We need to divert to a function which spawns gpg which * will then create the key. This also requires new features in * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ (void)want_backup; err = scd_genkey ("OPENPGP.1", 1, NULL, NULL); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Create a single key. This is a helper for cmd_generate. */ static gpg_error_t generate_key (card_info_t info, const char *keyref, int force, const char *algo) { gpg_error_t err; key_info_t kinfo; if (info->apptype == APP_TYPE_OPENPGP) { kinfo = find_kinfo (info, keyref); if (!kinfo) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } if (!force && kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen)) { err = ask_replace_keys (NULL); if (err) goto leave; force = 1; } } err = scd_genkey (keyref, force, algo, NULL); leave: return err; } static gpg_error_t cmd_generate (card_info_t info, char *argstr) { static char * const valid_algos[] = { "rsa2048", "rsa3072", "rsa4096", "", "nistp256", "nistp384", "nistp521", "", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "", "ed25519", "cv25519", NULL }; gpg_error_t err; int opt_force; char *p; char **opt_algo = NULL; /* Malloced. */ char *keyref_buffer = NULL; /* Malloced. */ char *keyref; /* Points into argstr or keyref_buffer. */ int i, j; if (!info) return print_help ("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n" "Create a new key on a card.\n" "Use --force to overwrite an existing key.\n" "Use \"help\" for ALGO to get a list of known algorithms.\n" "For OpenPGP cards several algos may be given.\n" "Note that the OpenPGP key generation is done interactively\n" "unless a single ALGO or KEYREF are given.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); opt_force = has_leading_option (argstr, "--force"); err = get_option_value (argstr, "--algo", &p); if (err) goto leave; if (p) { opt_algo = strtokenize (p, "+"); if (!opt_algo) { err = gpg_error_from_syserror (); xfree (p); goto leave; } xfree (p); } argstr = skip_options (argstr); keyref = argstr; if ((argstr = strchr (keyref, ' '))) { *argstr++ = 0; trim_spaces (keyref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = keyref + strlen (keyref); if (!*keyref) keyref = NULL; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_algo) { /* opt_algo is an array of algos. */ for (i=0; opt_algo[i]; i++) { for (j=0; valid_algos[j]; j++) if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i])) break; if (!valid_algos[j]) { int lf = 1; if (!ascii_strcasecmp (opt_algo[i], "help")) log_info ("Known algorithms:\n"); else { log_info ("Invalid algorithm '%s' given. Use one of:\n", opt_algo[i]); err = gpg_error (GPG_ERR_PUBKEY_ALGO); } for (i=0; valid_algos[i]; i++) { if (!*valid_algos[i]) lf = 1; else if (lf) { lf = 0; log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } else log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } log_printf ("\n"); show_keysize_warning (); goto leave; } } } /* Upcase the keyref; if it misses the cardtype, prepend it. */ if (keyref) { if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; } /* Special checks. */ if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) && info->cardversion >= 0x040200 && info->cardversion < 0x040305) { log_error ("On-chip key generation on this YubiKey has been blocked.\n"); log_info ("Please see for details\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP && !keyref && (!opt_algo || (opt_algo[0] && opt_algo[1]))) { /* With no algo requested or more than one algo requested and no * keyref given we create all keys. */ if (opt_force || keyref) log_info ("Note: OpenPGP key generation is interactive.\n"); err = generate_all_openpgp_card_keys (info, opt_algo); } else if (!keyref) err = gpg_error (GPG_ERR_INV_ID); else if (opt_algo && opt_algo[0] && opt_algo[1]) { log_error ("only one algorithm expected as value for --algo.\n"); err = gpg_error (GPG_ERR_INV_ARG); } else err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL); if (!err) { err = scd_learn (info); if (err) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); } leave: xfree (opt_algo); xfree (keyref_buffer); return err; } /* Change a PIN. */ static gpg_error_t cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err = 0; char *answer = NULL; const char *pinref = NULL; int reset_mode = 0; int nullpin = 0; int menu_used = 0; if (!info) return print_help ("PASSWD [--reset|--nullpin] [PINREF]\n\n" "Change or unblock the PINs. Note that in interactive mode\n" "and without a PINREF a menu is presented for certain cards;\n" "in non-interactive and without a PINREF a default value is\n" "used for these cards. The option --reset is used with TCOS\n" "cards to reset the PIN using the PUK or vice versa; --nullpin\n" "is used for these cards to set the intial PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (has_option (argstr, "--reset")) reset_mode = 1; else if (has_option (argstr, "--nullpin")) nullpin = 1; argstr = skip_options (argstr); /* If --reset or --nullpin has been given we force non-interactive mode. */ if (*argstr || reset_mode || nullpin) { pinref = argstr; if (!*pinref) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } } else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - unblock and set new a PIN\n" "3 - change the Admin PIN\n" "4 - set the Reset Code\n" "Q - quit\n"); if (strlen (answer) != 1) continue; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "OPENPGP.1"; else if (*answer == '2') { pinref = "OPENPGP.1"; reset_mode = 1; } else if (*answer == '3') pinref = "OPENPGP.3"; else if (*answer == '4') { pinref = "OPENPGP.2"; reset_mode = 1; } } } else if (info->apptype == APP_TYPE_OPENPGP) pinref = "OPENPGP.1"; else if (opt.interactive && info->apptype == APP_TYPE_PIV) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - change the PUK\n" "3 - change the Global PIN\n" "Q - quit\n"); if (strlen (answer) != 1) ; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "PIV.80"; else if (*answer == '2') pinref = "PIV.81"; else if (*answer == '3') pinref = "PIV.00"; } } else if (opt.interactive && info->apptype == APP_TYPE_NKS) { int for_qualified = 0; menu_used = 1; for (;;) { xfree (answer); answer = get_selection (" 1 - Standard PIN/PUK\n" " 2 - PIN/PUK for qualified signature\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) break; else if (!strcmp (answer, "2")) { for_qualified = 1; break; } } log_assert (DIM (info->chvinfo) >= 4); if (info->chvinfo[for_qualified? 2 : 0] == -4) { while (!pinref) { xfree (answer); answer = get_selection ("The NullPIN is still active on this card.\n" "You need to choose and set a PIN first.\n" "\n" " 1 - Set your PIN\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; nullpin = 1; } } } else { while (!pinref) { xfree (answer); answer = get_selection (" 1 - change PIN\n" " 2 - reset PIN\n" " 3 - change PUK\n" " 4 - reset PUK\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; } else if (!strcmp (answer, "2")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; reset_mode = 1; } else if (!strcmp (answer, "3")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; } else if (!strcmp (answer, "4")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; reset_mode = 1; } } } } else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = scd_change_pin (pinref, reset_mode, nullpin); if (err) { if (!opt.interactive && !menu_used && !opt.verbose) ; else if (!ascii_strcasecmp (pinref, "PIV.81")) log_error ("Error changing the PUK.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_error ("Error unblocking the PIN.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_error ("Error setting the Reset Code.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_error ("Error changing the Admin PIN.\n"); else if (reset_mode) log_error ("Error resetting the PIN.\n"); else log_error ("Error changing the PIN.\n"); } else { if (!opt.interactive && !opt.verbose) ; else if (!ascii_strcasecmp (pinref, "PIV.81")) log_info ("PUK changed.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_info ("PIN unblocked and new PIN set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_info ("Reset Code set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_info ("Admin PIN changed.\n"); else if (reset_mode) log_info ("PIN resetted.\n"); else log_info ("PIN changed.\n"); /* Update the CHV status. */ err = scd_getattr ("CHV-STATUS", info); } leave: xfree (answer); return err; } static gpg_error_t cmd_unblock (card_info_t info) { gpg_error_t err = 0; if (!info) return print_help ("UNBLOCK\n\n" "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" "cards prior to version 2 can't use this; instead the PASSWD\n" "command can be used to set a new PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (info->apptype == APP_TYPE_OPENPGP) { if (!info->is_v2) { log_error (_("This command is only available for version 2 cards\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (!info->chvinfo[1]) { log_error (_("Reset Code not or not anymore available\n")); err = gpg_error (GPG_ERR_PIN_BLOCKED); } else { err = scd_change_pin ("OPENPGP.2", 0, 0); if (!err) log_info ("PIN changed.\n"); } } else if (info->apptype == APP_TYPE_PIV) { /* Unblock the Application PIN. */ err = scd_change_pin ("PIV.80", 1, 0); if (!err) log_info ("PIN unblocked and changed.\n"); } else { log_info ("Unblocking not supported for '%s'.\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } return err; } /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t cmd_factoryreset (card_info_t info) { gpg_error_t err; char *answer = NULL; int termstate = 0; int any_apdu = 0; int is_yubikey = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" "Do a complete reset of some OpenPGP and PIV cards. This\n" "deletes all data and keys and resets the PINs to their default.\n" "This is mainly used by developers with scratch cards. Don't\n" "worry, you need to confirm before the command proceeds.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); /* We support the factory reset for most OpenPGP cards and Yubikeys * with the PIV application. */ if (info->apptype == APP_TYPE_OPENPGP) ; else if (info->apptype == APP_TYPE_PIV && info->cardtype && !strcmp (info->cardtype, "yubikey")) is_yubikey = 1; else return gpg_error (GPG_ERR_NOT_SUPPORTED); /* For an OpenPGP card the code below basically does the same what * this gpg-connect-agent script does: * * scd reset * scd serialno undefined * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 e6 00 00 * scd apdu 00 44 00 00 * scd reset * /echo Card has been reset to factory defaults * * For a PIV application on a Yubikey it merely issues the Yubikey * specific resset command. */ err = scd_learn (info); if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) termstate = 1; else if (err) { log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); goto leave; } if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!termstate || is_yubikey) { if (!is_yubikey) { if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it * is not possible to select a card application in * termination state. */ log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } } tty_printf ("\n"); log_info (_("Note: This command destroys all keys stored on the card!\n")); tty_printf ("\n"); xfree (answer); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); trim_spaces (answer); if (*answer == CONTROL_D || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } xfree (answer); answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); tty_kill_prompt (); trim_spaces (answer); if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (is_yubikey) { /* The PIV application si already selected, we only need to * send the special reset APDU after having blocked PIN and * PUK. Note that blocking the PUK is done using the * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff, NULL, NULL); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } else /* OpenPGP card. */ { any_apdu = 1; /* We need to select a card application before we can send APDUs * to the card without scdaemon doing anything on its own. */ err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, NULL, NULL); if (err) goto leave; /* Do some dummy verifies with wrong PINs to set the retry * counter to zero. We can't easily use the card version 2.1 * feature of presenting the admin PIN to allow the terminate * command because there is no machinery in scdaemon to catch * the verify command and ask for the PIN when the "APDU" * command is used. * Here, the length of dummy wrong PIN is 32-byte, also * supporting authentication with KDF DO. */ for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } } if (!is_yubikey) { any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; /* Then, connect the card again. */ err = scd_serialno (NULL, NULL); if (!err) info->need_sn_cmd = 0; leave: if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); /* FIXME: We need a better solution in the case that the card is * in a termination state, i.e. the card was removed before the * activate was sent. The best solution I found with v2.1 * Zeitcontrol card was to kill scdaemon and the issue this * sequence with gpg-connect-agent: * scd reset * scd serialno undefined * scd apdu 00A4040006D27600012401 (returns error) * scd apdu 00440000 * Then kill scdaemon again and issue: * scd reset * scd serialno openpgp */ } xfree (answer); return err; } /* Generate KDF data. This is a helper for cmd_kdfsetup. */ static gpg_error_t gen_kdf_data (unsigned char *data, int single_salt) { gpg_error_t err; const unsigned char h0[] = { 0x81, 0x01, 0x03, 0x82, 0x01, 0x08, 0x83, 0x04 }; const unsigned char h1[] = { 0x84, 0x08 }; const unsigned char h2[] = { 0x85, 0x08 }; const unsigned char h3[] = { 0x86, 0x08 }; const unsigned char h4[] = { 0x87, 0x20 }; const unsigned char h5[] = { 0x88, 0x20 }; unsigned char *p, *salt_user, *salt_admin; unsigned char s2k_char; unsigned int iterations; unsigned char count_4byte[4]; p = data; s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; count_4byte[2] = (iterations >> 8) & 0xff; count_4byte[3] = (iterations & 0xff); memcpy (p, h0, sizeof h0); p += sizeof h0; memcpy (p, count_4byte, sizeof count_4byte); p += sizeof count_4byte; memcpy (p, h1, sizeof h1); salt_user = (p += sizeof h1); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; if (single_salt) salt_admin = salt_user; else { memcpy (p, h2, sizeof h2); p += sizeof h2; gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; memcpy (p, h3, sizeof h3); salt_admin = (p += sizeof h3); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; } memcpy (p, h4, sizeof h4); p += sizeof h4; err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, strlen (OPENPGP_USER_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_user, 8, iterations, 32, p); p += 32; if (!err) { memcpy (p, h5, sizeof h5); p += sizeof h5; err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, strlen (OPENPGP_ADMIN_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_admin, 8, iterations, 32, p); } return err; } static gpg_error_t cmd_kdfsetup (card_info_t info, char *argstr) { gpg_error_t err; unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; int single = (*argstr != 0); if (!info) return print_help ("KDF-SETUP\n\n" "Prepare the OpenPGP card KDF feature for this card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!info->extcap.kdf) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gen_kdf_data (kdf_data, single); if (err) goto leave; err = scd_setattr ("KDF", kdf_data, single ? OPENPGP_KDF_DATA_LENGTH_MIN /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); if (err) goto leave; err = scd_getattr ("KDF", info); leave: return err; } static void show_keysize_warning (void) { static int shown; if (shown) return; shown = 1; tty_printf (_("Note: There is no guarantee that the card supports the requested\n" " key type or size. If the key generation does not succeed,\n" " please check the documentation of your card to see which\n" " key types and sizes are supported.\n") ); } static gpg_error_t cmd_uif (card_info_t info, char *argstr) { gpg_error_t err; int keyno; char name[50]; unsigned char data[2]; char *answer = NULL; int opt_yes; if (!info) return print_help ("UIF N [on|off|permanent]\n\n" "Change the User Interaction Flag. N must in the range 1 to 3.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (!info->extcap.bt) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } opt_yes = has_leading_option (argstr, "--yes"); argstr = skip_options (argstr); if (digitp (argstr)) { keyno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else keyno = 0; if (keyno < 1 || keyno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if ( !strcmp (argstr, "off") ) data[0] = 0x00; else if ( !strcmp (argstr, "on") ) data[0] = 0x01; else if ( !strcmp (argstr, "permanent") ) data[0] = 0x02; else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } data[1] = 0x20; log_assert (keyno - 1 < DIM(info->uif)); if (info->uif[keyno-1] == 2) { log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"), "permanent"); err = gpg_error (GPG_ERR_INV_STATE); goto leave; } if (data[0] == 0x02) { if (opt.interactive) { tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; } else if (!opt_yes) { log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); log_info (_("Please use \"uif --yes %d %s\"\n"), keyno, "permanent"); err = gpg_error (GPG_ERR_CANCELED); } else err = 0; if (err) goto leave; } snprintf (name, sizeof name, "UIF-%d", keyno); err = scd_setattr (name, data, 2); if (!err) /* Read all UIF attributes again. */ err = scd_getattr ("UIF", info); leave: xfree (answer); return err; } static gpg_error_t cmd_yubikey (card_info_t info, char *argstr) { gpg_error_t err, err2; estream_t fp = opt.interactive? NULL : es_stdout; - char *words[20]; + const char *words[20]; int nwords; if (!info) return print_help ("YUBIKEY args\n\n" "Various commands pertaining to Yubikey tokens with being:\n" "\n" " LIST \n" "\n" "List supported and enabled applications.\n" "\n" " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" "\n" "Enable or disable the specified or all applications on the\n" "given interface.", 0); argstr = skip_options (argstr); if (!info->cardtype || strcmp (info->cardtype, "yubikey")) { log_info ("This command can only be used with Yubikeys.\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } nwords = split_fields (argstr, words, DIM (words)); if (nwords < 1) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Note that we always do a learn to get a chance to the card back * into a usable state. */ err = yubikey_commands (info, fp, nwords, words); err2 = scd_learn (info); if (err2) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); leave: return err; } static gpg_error_t cmd_apdu (card_info_t info, char *argstr) { gpg_error_t err; estream_t fp = opt.interactive? NULL : es_stdout; int with_atr; int handle_more; const char *s; const char *exlenstr; int exlenstrlen; char *options = NULL; unsigned int sw; unsigned char *result = NULL; size_t i, j, resultlen; if (!info) return print_help ("APDU [--more] [--exlen[=N]] \n" "\n" "Send an APDU to the current card. 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.\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", 0); if (has_option (argstr, "--dump-atr")) with_atr = 2; else with_atr = has_option (argstr, "--atr"); handle_more = has_option (argstr, "--more"); exlenstr = has_option_name (argstr, "--exlen"); exlenstrlen = 0; if (exlenstr) { for (s=exlenstr; *s && !spacep (s); s++) exlenstrlen++; } argstr = skip_options (argstr); if (with_atr || handle_more || exlenstr) options = xasprintf ("%s%s%s%.*s", with_atr == 2? " --dump-atr": with_atr? " --data-atr":"", handle_more?" --more":"", exlenstr?" --exlen=":"", exlenstrlen, exlenstr?exlenstr:""); err = scd_apdu (argstr, options, &sw, &result, &resultlen); if (err) goto leave; if (!with_atr) log_info ("Statusword: 0x%04x\n", sw); for (i=0; i < resultlen; ) { size_t save_i = i; tty_fprintf (fp, "D[%04X] ", (unsigned int)i); for (j=0; j < 16 ; j++, i++) { if (j == 8) tty_fprintf (fp, " "); if (i < resultlen) tty_fprintf (fp, " %02X", result[i]); else tty_fprintf (fp, " "); } tty_fprintf (fp, " "); i = save_i; for (j=0; j < 16; j++, i++) { unsigned int c = result[i]; if ( i >= resultlen ) tty_fprintf (fp, " "); else if (isascii (c) && isprint (c) && !iscntrl (c)) tty_fprintf (fp, "%c", c); else tty_fprintf (fp, "."); } tty_fprintf (fp, "\n"); } leave: xfree (result); xfree (options); return err; } static gpg_error_t cmd_history (card_info_t info, char *argstr) { int opt_list, opt_clear; opt_list = has_option (argstr, "--list"); opt_clear = has_option (argstr, "--clear"); if (!info || !(opt_list || opt_clear)) return print_help ("HISTORY --list\n" " List the command history\n" "HISTORY --clear\n" " Clear the command history", 0); if (opt_list) tty_printf ("Sorry, history listing not yet possible\n"); if (opt_clear) tty_read_history (NULL, 0); return 0; } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdHISTORY, cmdINVCMD }; static struct { const char *name; enum cmdids id; const char *desc; } cmds[] = { { "quit" , cmdQUIT, N_("quit this menu")}, { "q" , cmdQUIT, NULL }, { "bye" , cmdQUIT, NULL }, { "help" , cmdHELP, N_("show this help")}, { "?" , cmdHELP, NULL }, { "list" , cmdLIST, N_("list all available data")}, { "l" , cmdLIST, NULL }, { "name" , cmdNAME, N_("change card holder's name")}, { "url" , cmdURL, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN, N_("change the login name")}, { "lang" , cmdLANG, N_("change the language preferences")}, { "salutation",cmdSALUT, N_("change card holder's salutation")}, { "salut" , cmdSALUT, NULL }, { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, N_("generate new keys")}, { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTH, N_("authenticate to the card")}, { "auth" , cmdAUTH, NULL }, { "reset" , cmdRESET, N_("send a reset to the card daemon")}, { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, { "uif", cmdUIF, N_("change the User Interaction Flag")}, { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "apdu", cmdAPDU, NULL}, { "history", cmdHISTORY, N_("manage the command history")}, { NULL, cmdINVCMD, NULL } }; /* The command line command dispatcher. */ static gpg_error_t dispatch_command (card_info_t info, const char *orig_command) { gpg_error_t err = 0; enum cmdids cmd; /* The command. */ char *command; /* A malloced copy of ORIG_COMMAND. */ char *argstr; /* The argument as a string. */ int i; int ignore_error; if ((ignore_error = *orig_command == '-')) orig_command++; command = xstrdup (orig_command); argstr = NULL; if ((argstr = strchr (command, ' '))) { *argstr++ = 0; trim_spaces (command); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (command, cmds[i].name )) break; cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ /* Make sure we have valid strings for the args. They are allowed * to be modified and must thus point to a buffer. */ if (!argstr) argstr = command + strlen (command); /* For most commands we need to make sure that we have a card. */ if (!info) ; /* Help mode */ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD) && !info->initialized) { err = scd_learn (info); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); goto leave; } } if (info) info->card_removed = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Stop processing.", 0); else { err = gpg_error (GPG_ERR_EOF); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) dispatch_command (NULL, argstr); else { es_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); es_printf ("Prefix a command with a dash to ignore its error.\n"); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */ case cmdINVCMD: default: log_error (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ leave: /* Return GPG_ERR_EOF only if its origin was "quit". */ es_fflush (es_stdout); if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) err = gpg_error (GPG_ERR_GENERAL); if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { err = fixup_scd_errors (err); if (ignore_error) { log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); err = 0; } else { log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } xfree (command); return err; } /* The interactive main loop. */ static void interactive_loop (void) { gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; char *historyname = NULL; /* In the interactive mode we do not want to print the program prefix. */ log_set_prefix (NULL, 0); if (!opt.no_history) { historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL); if (tty_read_history (historyname, 500)) log_info ("error reading '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); } for (;;) { if (help_arg) { /* Clear info to indicate helpmode */ info = NULL; } else if (!info) { /* Get out of help. */ info = &info_buffer; help_arg = NULL; redisplay = 0; } else if (redisplay) { err = cmd_list (info, ""); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); } else { tty_printf("\n"); redisplay = 0; } } if (!info) { /* Copy the pending help arg into our answer. Note that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; xfree (answer); answer = p; } else { do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); } argstr = NULL; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((argstr = strchr (answer,' '))) { *argstr++ = 0; trim_spaces (answer); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; } /* Make sure we have valid strings for the args. They are * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdHISTORY || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ if (!info) ; /* In help mode. */ else if (redisplay) { cmd = cmdLIST; } else if (!info->serialno) { /* Without a serial number most commands won't work. * Catch it here. */ if (cmd == cmdRESET || cmd == cmdLIST) info->need_sn_cmd = 1; else { tty_printf ("\n"); tty_printf ("Serial number missing\n"); continue; } } } if (info) info->card_removed = 0; err = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Leave this tool.", 0); else { tty_printf ("\n"); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) help_arg = argstr; /* Trigger help for a command. */ else { tty_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdHISTORY: err = cmd_history (info, argstr); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (gpg_err_code (err) == GPG_ERR_CANCELED) tty_fprintf (NULL, "\n"); else if (err) { const char *s = "?"; for (i=0; cmds[i].name; i++ ) if (cmd == cmds[i].id) { s = cmds[i].name; break; } err = fixup_scd_errors (err); log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } /* End of main menu loop. */ leave: if (historyname && tty_write_history (historyname)) log_info ("error writing '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); release_card_info (info); xfree (historyname); xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's 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)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. The support for help completion * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 8c8bc0671..1622f2d5f 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -1,250 +1,250 @@ /* gpg-card.h - Common definitions for the gpg-card-tool * Copyright (C) 2019, 2020 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 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. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef GNUPG_GPG_CARD_H #define GNUPG_GPG_CARD_H #include "../common/session-env.h" #include "../common/strlist.h" /* We keep all global options in the structure OPT. */ EXTERN_UNLESS_MAIN_MODULE struct { int interactive; int verbose; unsigned int debug; int quiet; int with_colons; const char *gpg_program; const char *gpgsm_program; const char *agent_program; int autostart; int no_key_lookup; /* Assume --no-key-lookup for "list". */ int no_history; /* Do not use the command line history. */ /* Options passed to the gpg-agent: */ session_env_t session_env; char *lc_ctype; char *lc_messages; } opt; /* Debug values and macros. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) /* The maximum length of a binary fingerprint. */ #define MAX_FINGERPRINT_LEN 32 /* * Data structures to store keyblocks (aka certificates). */ struct pubkey_s { struct pubkey_s *next; /* The next key. */ unsigned char grip[KEYGRIP_LEN]; unsigned char fpr[MAX_FINGERPRINT_LEN]; unsigned char fprlen; /* The used length of a FPR. */ time_t created; /* The creation date of the key. */ unsigned int grip_valid:1;/* The grip is valid. */ unsigned int requested: 1;/* This is the requested grip. */ }; typedef struct pubkey_s *pubkey_t; struct userid_s { struct userid_s *next; char *value; /* Malloced. */ }; typedef struct userid_s *userid_t; struct keyblock_s { struct keyblock_s *next; /* Allow to link several keyblocks. */ int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */ pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */ userid_t uids; /* The list of user ids. */ }; typedef struct keyblock_s *keyblock_t; /* Enumeration of the known card application types. */ typedef enum { APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ APP_TYPE_OPENPGP, APP_TYPE_NKS, APP_TYPE_DINSIG, APP_TYPE_P15, APP_TYPE_GELDKARTE, APP_TYPE_SC_HSM, APP_TYPE_PIV, APP_TYPE_UNKNOWN /* Unknown by this tool. */ } app_type_t; /* An object to store information pertaining to a key pair as stored * on a card. This is commonly used as a linked list with all keys * known for the current card. */ struct key_info_s { struct key_info_s *next; unsigned char grip[20];/* The keygrip. */ unsigned char xflag; /* Temporary flag to help processing a list. */ /* OpenPGP card and possible other cards keyalgo string (an atom) * and the id of the algorithm. */ const char *keyalgo; enum gcry_pk_algos keyalgo_id; /* The three next items are mostly useful for OpenPGP cards. */ unsigned char fprlen; /* Use length of the next item. */ unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ u32 created; /* The time the key was created. */ unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ }; typedef struct key_info_s *key_info_t; /* * The object used to store information about a card. */ struct card_info_s { int initialized; /* True if a learn command was successful. */ int need_sn_cmd; /* The SERIALNO command needs to be issued. */ int card_removed; /* Helper flag set by some listing functions. */ int error; /* private. */ char *reader; /* Reader information. */ char *cardtype; /* NULL or type of the card. */ unsigned int cardversion; /* Firmware version of the card. */ char *apptypestr; /* Malloced application type string. */ app_type_t apptype;/* Translated from APPTYPESTR. */ unsigned int appversion; /* Version of the application. */ unsigned int manufacturer_id; char *manufacturer_name; /* malloced. */ char *serialno; /* malloced hex string. */ char *dispserialno;/* malloced string. */ char *disp_name; /* malloced. */ char *disp_lang; /* malloced. */ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ char *pubkey_url; /* malloced. */ char *login_data; /* malloced. */ char *private_do[4]; /* malloced. */ char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ char cafpr2len; char cafpr3len; char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; key_info_t kinfo; /* Linked list with all keypair related data. */ unsigned long sig_counter; int chv1_cached; /* For openpgp this is true if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ int is_v2; /* True if this is a v2 openpgp card. */ int chvmaxlen[4]; /* Maximum allowed length of a CHV. */ int chvinfo[4]; /* Allowed retries for the CHV; 0 = blocked. */ unsigned char chvusage[2]; /* Data object 5F2F */ struct { unsigned int ki:1; /* Key import available. */ unsigned int aac:1; /* Algorithm attributes are changeable. */ unsigned int kdf:1; /* KDF object to support PIN hashing available. */ unsigned int bt:1; /* Button for confirmation available. */ unsigned int sm:1; /* Secure messaging available. */ unsigned int smalgo:15;/* Secure messaging cipher algorithm. */ unsigned int private_dos:1;/* Support fpr private use DOs. */ unsigned int mcl3:16; /* Max. length for a OpenPGP card cert.3 */ } extcap; unsigned int status_indicator; int kdf_do_enabled; /* True if card has a KDF object. */ int uif[3]; /* True if User Interaction Flag is on. */ /* 1 = on, 2 = permanent on. */ strlist_t supported_keyalgo[3]; }; typedef struct card_info_s *card_info_t; /*-- card-keys.c --*/ void release_keyblock (keyblock_t keyblock); void flush_keyblock_cache (void); gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, keyblock_t *r_keyblock); gpg_error_t test_get_matching_keys (const char *hexgrip); gpg_error_t get_minimal_openpgp_key (estream_t *r_key, const char *fingerprint); /*-- card-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); gpg_error_t send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, unsigned char **r_data, size_t *r_datalen); /*-- card-call-scd.c --*/ void release_card_info (card_info_t info); const char *app_type_string (app_type_t app_type); gpg_error_t scd_apdu (const char *hexapdu, const char *options, unsigned int *r_sw, unsigned char **r_data, size_t *r_datalen); gpg_error_t scd_switchcard (const char *serialno); gpg_error_t scd_switchapp (const char *appname); gpg_error_t scd_learn (card_info_t info); gpg_error_t scd_getattr (const char *name, struct card_info_s *info); gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen); gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen); gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); gpg_error_t scd_cardlist (strlist_t *result); gpg_error_t scd_applist (strlist_t *result, int all); gpg_error_t scd_change_pin (const char *pinref, int reset_mode, int nullpin); gpg_error_t scd_checkpin (const char *serialno); unsigned long agent_get_s2k_count (void); /*-- card-yubikey.c --*/ gpg_error_t yubikey_commands (card_info_t info, - estream_t fp, int argc, char *argv[]); + estream_t fp, int argc, const char *argv[]); #endif /*GNUPG_GPG_CARD_H*/ diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 5f765d3c1..b5a1753bf 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -1,1596 +1,1596 @@ /* gpg-wks-client.c - A client for the Web Key Service protocols. * Copyright (C) 2016 Werner Koch * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/mbox-util.h" #include "../common/name-value.h" #include "call-dirmngr.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oDirectory = 'C', oDebug = 500, aSupported, aCheck, aCreate, aReceive, aRead, aInstallKey, aRemoveKey, aPrintWKDHash, aPrintWKDURL, oGpgProgram, oSend, oFakeSubmissionAddr, oStatusFD, oWithColons, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_c (aSupported, "supported", ("check whether provider supports WKS")), ARGPARSE_c (aCheck, "check", ("check whether a key is available")), ARGPARSE_c (aCreate, "create", ("create a publication request")), ARGPARSE_c (aReceive, "receive", ("receive a MIME confirmation request")), ARGPARSE_c (aRead, "read", ("receive a plain text confirmation request")), ARGPARSE_c (aInstallKey, "install-key", "install a key into a directory"), ARGPARSE_c (aRemoveKey, "remove-key", "remove a key from a directory"), ARGPARSE_c (aPrintWKDHash, "print-wkd-hash", "Print the WKD identifier for the given user ids"), ARGPARSE_c (aPrintWKDURL, "print-wkd-url", "Print the WKD URL for the given user id"), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_s (oDirectory, "directory", "@"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MIME_VALUE , "mime" }, { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* Value of the option --fake-submission-addr. */ const char *fake_submission_addr; static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text); static gpg_error_t command_supported (char *userid); static gpg_error_t command_check (char *userid); static gpg_error_t command_send (const char *fingerprint, const char *userid); static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint); static gpg_error_t read_confirmation_request (estream_t msg); static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "LGPL-2.1-or-later"; break; case 11: p = "gpg-wks-client"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-wks-client [command] [options] [args]\n" "Client for the Web Key Service\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), gpgrt_strusage (11), text); exit (2); } /* Command line parsing. */ static enum cmd_and_opt_values parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { enum cmd_and_opt_values cmd = 0; int no_more_options = 0; while (!no_more_options && gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oSend: opt.use_sendmail = 1; break; case oOutput: opt.output = pargs->r.ret_str; break; case oFakeSubmissionAddr: fake_submission_addr = pargs->r.ret_str; break; case oStatusFD: wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case aSupported: case aCreate: case aReceive: case aRead: case aCheck: case aInstallKey: case aRemoveKey: case aPrintWKDHash: case aPrintWKDURL: cmd = pargs->r_opt; break; default: pargs->err = ARGPARSE_PRINT_ERROR; break; } } return cmd; } /* gpg-wks-client main. */ int main (int argc, char **argv) { gpg_error_t err, delayed_err; gpgrt_argparse_t pargs; enum cmd_and_opt_values cmd; gnupg_reopen_std ("gpg-wks-client"); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; cmd = parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); 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]); } /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.directory) opt.directory = "openpgpkey"; /* Tell call-dirmngr what options we want. */ set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), 1); /* Check that the top directory exists. */ if (cmd == aInstallKey || cmd == aRemoveKey) { struct stat sb; if (stat (opt.directory, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing directory '%s': %s\n", opt.directory, gpg_strerror (err)); goto leave; } if (!S_ISDIR(sb.st_mode)) { log_error ("error accessing directory '%s': %s\n", opt.directory, "not a directory"); err = gpg_error (GPG_ERR_ENOENT); goto leave; } } /* Run the selected command. */ switch (cmd) { case aSupported: if (opt.with_colons) { for (; argc; argc--, argv++) command_supported (*argv); err = 0; } else { if (argc != 1) wrong_args ("--supported DOMAIN"); err = command_supported (argv[0]); if (err && gpg_err_code (err) != GPG_ERR_FALSE) log_error ("checking support failed: %s\n", gpg_strerror (err)); } break; case aCreate: if (argc != 2) wrong_args ("--create FINGERPRINT USER-ID"); err = command_send (argv[0], argv[1]); if (err) log_error ("creating request failed: %s\n", gpg_strerror (err)); break; case aReceive: if (argc) wrong_args ("--receive < MIME-DATA"); err = wks_receive (es_stdin, command_receive_cb, NULL); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aRead: if (argc) wrong_args ("--read < WKS-DATA"); err = read_confirmation_request (es_stdin); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aCheck: if (argc != 1) wrong_args ("--check USER-ID"); err = command_check (argv[0]); break; case aInstallKey: if (!argc) err = wks_cmd_install_key (NULL, NULL); else if (argc == 2) err = wks_cmd_install_key (*argv, argv[1]); else wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); err = wks_cmd_remove_key (*argv); break; case aPrintWKDHash: case aPrintWKDURL: if (!argc) { if (cmd == aPrintWKDHash) err = proc_userid_from_stdin (wks_cmd_print_wkd_hash, "printing WKD hash"); else err = proc_userid_from_stdin (wks_cmd_print_wkd_url, "printing WKD URL"); } else { for (err = delayed_err = 0; !err && argc; argc--, argv++) { if (cmd == aPrintWKDHash) err = wks_cmd_print_wkd_hash (*argv); else err = wks_cmd_print_wkd_url (*argv); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { /* Diagnostic already printed. */ delayed_err = err; err = 0; } else if (err) log_error ("printing hash failed: %s\n", gpg_strerror (err)); } if (!err) err = delayed_err; } break; default: gpgrt_usage (1); err = 0; break; } leave: if (err) wks_write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else wks_write_status (STATUS_SUCCESS, NULL); return (err || log_get_errorcount (0))? 1:0; } /* Read user ids from stdin and call FUNC for each user id. TEXT is * used for error messages. */ static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text) { gpg_error_t err = 0; gpg_error_t delayed_err = 0; char line[2048]; size_t n = 0; /* If we are on a terminal disable buffering to get direct response. */ if (gnupg_isatty (es_fileno (es_stdin)) && gnupg_isatty (es_fileno (es_stdout))) { es_setvbuf (es_stdin, NULL, _IONBF, 0); es_setvbuf (es_stdout, NULL, _IOLBF, 0); } while (es_fgets (line, sizeof line - 1, es_stdin)) { n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); log_error ("error reading stdin: %s\n", gpg_strerror (err)); break; } trim_spaces (line); err = func (line); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { delayed_err = err; err = 0; } else if (err) log_error ("%s failed: %s\n", text, gpg_strerror (err)); } if (es_ferror (es_stdin)) { err = gpg_error_from_syserror (); log_error ("error reading stdin: %s\n", gpg_strerror (err)); goto leave; } leave: if (!err) err = delayed_err; return err; } /* Add the user id UID to the key identified by FINGERPRINT. */ static gpg_error_t add_user_id (const char *fingerprint, const char *uid) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; 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, "--always-trust"); ccparray_put (&ccp, "--quick-add-uid"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, uid); 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, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("adding user id failed: %s\n", gpg_strerror (err)); goto leave; } leave: xfree (argv); return err; } struct decrypt_stream_parm_s { char *fpr; char *mainfpr; int otrust; }; static void decrypt_stream_status_cb (void *opaque, const char *keyword, char *args) { struct decrypt_stream_parm_s *decinfo = opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr) { - char *fields[3]; + const char *fields[3]; if (split_fields (args, fields, DIM (fields)) >= 3) { decinfo->fpr = xstrdup (fields[0]); decinfo->mainfpr = xstrdup (fields[1]); decinfo->otrust = *fields[2]; } } } /* Decrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. */ static gpg_error_t decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, estream_t input) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; *r_output = NULL; memset (decinfo, 0, sizeof *decinfo); output = es_fopenmem (0, "w+b"); if (!output) { 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"); /* We limit the output to 64 KiB to avoid DoS using compression * tricks. A regular client will anyway only send a minimal key; * that is one w/o key signatures and attribute packets. */ ccparray_put (&ccp, "--max-output=0x10000"); 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, "--decrypt"); ccparray_put (&ccp, "--"); 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, input, NULL, output, decrypt_stream_status_cb, decinfo); if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust)) err = gpg_error (GPG_ERR_INV_ENGINE); if (err) { log_error ("decryption failed: %s\n", gpg_strerror (err)); goto leave; } else if (opt.verbose) log_info ("decryption succeeded\n"); es_rewind (output); *r_output = output; output = NULL; leave: if (err) { xfree (decinfo->fpr); xfree (decinfo->mainfpr); memset (decinfo, 0, sizeof *decinfo); } es_fclose (output); xfree (argv); return err; } /* Return the submission address for the address or just the domain in * ADDRSPEC. The submission address is stored as a malloced string at * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain * are stored. The caller needs to free them with wks_free_policy. * The function returns an error code on failure to find a submission * address or policy file. Note: The function may store NULL at * R_SUBMISSION_ADDRESS but return success to indicate that the web * key directory is supported but not the web key service. As per WKD * specs a policy file is always required and will thus be return on * success. */ static gpg_error_t get_policy_and_sa (const char *addrspec, int silent, policy_flags_t *r_policy, char **r_submission_address) { gpg_error_t err; estream_t mbuf = NULL; const char *domain; const char *s; policy_flags_t policy = NULL; char *submission_to = NULL; *r_submission_address = NULL; *r_policy = NULL; domain = strchr (addrspec, '@'); if (domain) domain++; if (opt.with_colons) { s = domain? domain : addrspec; es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); es_putc (':', es_stdout); } /* We first try to get the submission address from the policy file * (this is the new method). If both are available we check that * they match and print a warning if not. In the latter case we * keep on using the one from the submission-address file. */ err = wkd_get_policy_flags (addrspec, &mbuf); if (err && gpg_err_code (err) != GPG_ERR_NO_DATA && gpg_err_code (err) != GPG_ERR_NO_NAME) { if (!opt.with_colons) log_error ("error reading policy flags for '%s': %s\n", domain, gpg_strerror (err)); goto leave; } if (!mbuf) { if (!opt.with_colons) log_error ("provider for '%s' does NOT support the Web Key Directory\n", addrspec); err = gpg_error (GPG_ERR_FALSE); goto leave; } policy = xtrycalloc (1, sizeof *policy); if (!policy) err = gpg_error_from_syserror (); else err = wks_parse_policy (policy, mbuf, 1); es_fclose (mbuf); mbuf = NULL; if (err) goto leave; err = wkd_get_submission_address (addrspec, &submission_to); if (err && !policy->submission_address) { if (!silent && !opt.with_colons) log_error (_("error looking up submission address for domain '%s'" ": %s\n"), domain, gpg_strerror (err)); if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) log_error (_("this domain probably doesn't support WKS.\n")); goto leave; } if (submission_to && policy->submission_address && ascii_strcasecmp (submission_to, policy->submission_address)) log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", submission_to, policy->submission_address); if (!submission_to && policy->submission_address) { submission_to = xtrystrdup (policy->submission_address); if (!submission_to) { err = gpg_error_from_syserror (); goto leave; } } leave: *r_submission_address = submission_to; submission_to = NULL; *r_policy = policy; policy = NULL; if (opt.with_colons) { if (*r_policy && !*r_submission_address) es_fprintf (es_stdout, "1:0::"); else if (*r_policy && *r_submission_address) es_fprintf (es_stdout, "1:1::"); else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) es_fprintf (es_stdout, "0:0:%d:", err); else es_fprintf (es_stdout, "0:0::"); if (*r_policy) { es_fprintf (es_stdout, "%u:%u:%u:", (*r_policy)->protocol_version, (*r_policy)->auth_submit, (*r_policy)->mailbox_only); } es_putc ('\n', es_stdout); } xfree (submission_to); wks_free_policy (policy); xfree (policy); es_fclose (mbuf); return err; } /* Check whether the provider supports the WKS protocol. */ static gpg_error_t command_supported (char *userid) { gpg_error_t err; char *addrspec = NULL; char *submission_to = NULL; policy_flags_t policy = NULL; if (!strchr (userid, '@')) { char *tmp = xstrconcat ("foo@", userid, NULL); addrspec = mailbox_from_userid (tmp, 0); xfree (tmp); } else addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); if (err || !submission_to) { if (!submission_to || gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST ) { /* FALSE is returned if we already figured out that even the * Web Key Directory is not supported and thus printed an * error message. */ if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE && !opt.with_colons) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) log_info ("provider for '%s' does NOT support WKS\n", addrspec); else log_info ("provider for '%s' does NOT support WKS (%s)\n", addrspec, gpg_strerror (err)); } err = gpg_error (GPG_ERR_FALSE); if (!opt.with_colons) log_inc_errorcount (); } goto leave; } if (opt.verbose && !opt.with_colons) log_info ("provider for '%s' supports WKS\n", addrspec); leave: wks_free_policy (policy); xfree (policy); xfree (submission_to); xfree (addrspec); return err; } /* Check whether the key for USERID is available in the WKD. */ static gpg_error_t command_check (char *userid) { gpg_error_t err; char *addrspec = NULL; estream_t key = NULL; char *fpr = NULL; uidinfo_list_t mboxes = NULL; uidinfo_list_t sl; int found = 0; addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = wkd_get_key (addrspec, &key); switch (gpg_err_code (err)) { case 0: if (opt.verbose) log_info ("public key for '%s' found via WKD\n", addrspec); /* Fixme: Check that the key contains the user id. */ break; case GPG_ERR_NO_DATA: /* No such key. */ if (opt.verbose) log_info ("public key for '%s' NOT found via WKD\n", addrspec); err = gpg_error (GPG_ERR_NO_PUBKEY); log_inc_errorcount (); break; case GPG_ERR_UNKNOWN_HOST: if (opt.verbose) log_info ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; default: log_error ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); break; } if (err) goto leave; /* Look closer at the key. */ err = wks_list_key (key, &fpr, &mboxes); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (opt.verbose) log_info ("fingerprint: %s\n", fpr); for (sl = mboxes; sl; sl = sl->next) { if (sl->mbox && !strcmp (sl->mbox, addrspec)) found = 1; if (opt.verbose) { log_info (" user-id: %s\n", sl->uid); log_info (" created: %s\n", asctimestamp (sl->created)); if (sl->mbox) log_info (" addr-spec: %s\n", sl->mbox); } } if (!found) { log_error ("public key for '%s' has no user id with the mail address\n", addrspec); err = gpg_error (GPG_ERR_CERT_REVOKED); } leave: xfree (fpr); free_uidinfo_list (mboxes); es_fclose (key); xfree (addrspec); return err; } /* Locate the key by fingerprint and userid and send a publication * request. */ static gpg_error_t command_send (const char *fingerprint, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; char *addrspec = NULL; estream_t key = NULL; estream_t keyenc = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; policy_flags_t policy = NULL; int no_encrypt = 0; int posteo_hack = 0; const char *domain; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; if (classify_user_id (fingerprint, &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } err = wks_get_key (&key, fingerprint, addrspec, 0); if (err) goto leave; domain = strchr (addrspec, '@'); log_assert (domain); domain++; /* Get the submission address. */ if (fake_submission_addr) { policy = xcalloc (1, sizeof *policy); submission_to = xstrdup (fake_submission_addr); err = 0; } else { err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); if (err) goto leave; if (!submission_to) { log_error (_("this domain probably doesn't support WKS.\n")); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } } log_info ("submitting request to '%s'\n", submission_to); if (policy->auth_submit) log_info ("no confirmation required for '%s'\n", addrspec); /* In case the key has several uids with the same addr-spec we will * use the newest one. */ err = wks_list_key (key, NULL, &uidlist); if (err) { log_error ("error parsing key: %s\n",gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) continue; /* UID has more than just the mailbox. */ if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (opt.verbose) log_info ("submitting key with user id '%s'\n", thisuid->uid); /* If we have more than one user id we need to filter the key to * include only THISUID. */ if (uidlist->next) { estream_t newkey; es_rewind (key); err = wks_filter_uid (&newkey, key, thisuid->uid, 0); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (key); key = newkey; } if (policy->mailbox_only && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) { log_info ("Warning: policy requires 'mailbox-only'" " - adding user id '%s'\n", addrspec); err = add_user_id (fingerprint, addrspec); if (err) goto leave; /* Need to get the key again. This time we request filtering * for the full user id, so that we do not need check and filter * the key again. */ es_fclose (key); key = NULL; err = wks_get_key (&key, fingerprint, addrspec, 1); if (err) goto leave; } /* Hack to support posteo but let them disable this by setting the * new policy-version flag. */ if (policy->protocol_version < 3 && !ascii_strcasecmp (domain, "posteo.de")) { log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); no_encrypt = 1; posteo_hack = 1; } /* Encrypt the key part. */ if (!no_encrypt) { es_rewind (key); err = encrypt_response (&keyenc, key, submission_to, fingerprint); if (err) goto leave; es_fclose (key); key = NULL; } /* Send the key. */ err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", addrspec); if (err) goto leave; err = mime_maker_add_header (mime, "To", submission_to); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publishing request"); if (err) goto leave; /* Tell server which draft we support. */ err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (no_encrypt) { void *data; size_t datalen, n; if (posteo_hack) { /* Needs a multipart/mixed with one(!) attachment. It does * not grok a non-multipart mail. */ err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; } err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys"); if (err) goto leave; if (es_fclose_snatch (key, &data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; /* We need to skip over the first line which has a content-type * header not needed here. */ for (n=0; n < datalen ; n++) if (((const char *)data)[n] == '\n') { n++; break; } err = mime_maker_add_body_data (mime, (char*)data + n, datalen - n); xfree (data); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &keyenc); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); xfree (submission_to); free_uidinfo_list (uidlist); es_fclose (keyenc); es_fclose (key); wks_free_policy (policy); xfree (policy); xfree (addrspec); return err; } static void encrypt_response_status_cb (void *opaque, const char *keyword, char *args) { gpg_error_t *failure = opaque; - char *fields[2]; + const char *fields[2]; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "FAILURE")) { if (split_fields (args, fields, DIM (fields)) >= 2 && !strcmp (fields[0], "encrypt")) *failure = strtoul (fields[1], NULL, 10); } } /* Encrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT * (so that the sent message may later be inspected by the user). We * currently retrieve that key from the WKD, DANE, or from "local". * "local" is last to prefer the latest key version but use a local * copy in case we are working offline. It might be useful for the * server to send the fingerprint of its encryption key - or even the * entire key back. */ static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; gpg_error_t gpg_err = 0; *r_output = NULL; output = es_fopenmem (0, "w+b"); if (!output) { 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, "--armor"); ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ if (fake_submission_addr) ccparray_put (&ccp, "--auto-key-locate=clear,local"); else ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local"); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, addrspec); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, "--encrypt"); ccparray_put (&ccp, "--"); 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, input, NULL, output, encrypt_response_status_cb, &gpg_err); if (err) { if (gpg_err) err = gpg_err; log_error ("encryption failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (output); *r_output = output; output = NULL; leave: es_fclose (output); xfree (argv); return err; } static gpg_error_t send_confirmation_response (const char *sender, const char *address, const char *nonce, int encrypt, const char *fingerprint) { gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; mime_maker_t mime = NULL; body = es_fopenmem (0, "w+b"); if (!body) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* It is fine to use 8 bit encoding because that is encrypted and * only our client will see it. */ if (encrypt) { es_fputs ("Content-Type: application/vnd.gnupg.wks\n" "Content-Transfer-Encoding: 8bit\n" "\n", body); } es_fprintf (body, ("type: confirmation-response\n" "sender: %s\n" "address: %s\n" "nonce: %s\n"), sender, address, nonce); es_rewind (body); if (encrypt) { err = encrypt_response (&bodyenc, body, sender, fingerprint); if (err) goto leave; es_fclose (body); body = NULL; } err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", address); if (err) goto leave; err = mime_maker_add_header (mime, "To", sender); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publication confirmation"); if (err) goto leave; err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (encrypt) { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "application/vnd.gnupg.wks"); if (err) goto leave; err = mime_maker_add_stream (mime, &body); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); es_fclose (bodyenc); es_fclose (body); return err; } /* Reply to a confirmation request. The MSG has already been * decrypted and we only need to send the nonce back. MAINFPR is * either NULL or the primary key fingerprint of the key used to * decrypt the request. */ static gpg_error_t process_confirmation_request (estream_t msg, const char *mainfpr) { gpg_error_t err; nvc_t nvc; nve_t item; const char *value, *sender, *address, *fingerprint, *nonce; err = nvc_parse (&nvc, NULL, msg); if (err) { log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); goto leave; } if (DBG_MIME) { log_debug ("request follows:\n"); nvc_write (nvc, log_get_stream ()); } /* Check that this is a confirmation request. */ if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) && !strcmp (value, "confirmation-request"))) { if (item && value) log_error ("received unexpected wks message '%s'\n", value); else log_error ("received invalid wks message: %s\n", "'type' missing"); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); goto leave; } /* Get the fingerprint. */ if (!((item = nvc_lookup (nvc, "fingerprint:")) && (value = nve_value (item)) && strlen (value) >= 40)) { log_error ("received invalid wks message: %s\n", "'fingerprint' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } fingerprint = value; /* Check that the fingerprint matches the key used to decrypt the * message. In --read mode or with the old format we don't have the * decryption key; thus we can't bail out. */ if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint)) { log_info ("target fingerprint: %s\n", fingerprint); log_info ("but decrypted with: %s\n", mainfpr); log_error ("confirmation request not decrypted with target key\n"); if (mainfpr) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } } /* Get the address. */ if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'address' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } address = value; /* FIXME: Check that the "address" matches the User ID we want to * publish. */ /* Get the sender. */ if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'sender' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } sender = value; /* FIXME: Check that the "sender" matches the From: address. */ /* Get the nonce. */ if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) && strlen (value) > 16)) { log_error ("received invalid wks message: %s\n", "'nonce' missing or too short"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } nonce = value; /* Send the confirmation. If no key was found, try again without * encryption. */ err = send_confirmation_response (sender, address, nonce, 1, fingerprint); if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) { log_info ("no encryption key found - sending response in the clear\n"); err = send_confirmation_response (sender, address, nonce, 0, NULL); } leave: nvc_release (nvc); return err; } /* Read a confirmation request and decrypt it if needed. This * function may not be used with a mail or MIME message but only with * the actual encrypted or plaintext WKS data. */ static gpg_error_t read_confirmation_request (estream_t msg) { gpg_error_t err; int c; estream_t plaintext = NULL; /* We take a really simple approach to check whether MSG is * encrypted: We know that an encrypted message is always armored * and thus starts with a few dashes. It is even sufficient to * check for a single dash, because that can never be a proper first * WKS data octet. We need to skip leading spaces, though. */ while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n') ; if (c == EOF) { log_error ("can't process an empty message\n"); return gpg_error (GPG_ERR_INV_DATA); } if (es_ungetc (c, msg) != c) { log_error ("error ungetting octet from message\n"); return gpg_error (GPG_ERR_INTERNAL); } if (c != '-') err = process_confirmation_request (msg, NULL); else { struct decrypt_stream_parm_s decinfo; err = decrypt_stream (&plaintext, &decinfo, msg); if (err) log_error ("decryption failed: %s\n", gpg_strerror (err)); else if (decinfo.otrust != 'u') { err = gpg_error (GPG_ERR_WRONG_SECKEY); log_error ("key used to decrypt the confirmation request" " was not generated by us\n"); } else err = process_confirmation_request (plaintext, decinfo.mainfpr); xfree (decinfo.fpr); xfree (decinfo.mainfpr); } es_fclose (plaintext); return err; } /* Called from the MIME receiver to process the plain text data in MSG. */ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t msg, unsigned int flags) { gpg_error_t err; (void)opaque; (void)flags; if (!strcmp (mediatype, "application/vnd.gnupg.wks")) err = read_confirmation_request (msg); else { log_info ("ignoring unexpected message of type '%s'\n", mediatype); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); } return err; } diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index d6cc14c28..4dae7b49d 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,3353 +1,3353 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 Werner Koch * Copyright (C) 2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GnuPG; if not, see . */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN 1 # include #else # include # include #endif #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/status.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 #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_LOGLVL_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); gpgconf_failure (gpg_error_from_errno (errnum)); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); static void dirmngr_runtime_change (int killflag); /* STRING_ARRAY is a malloced array with malloced strings. It is used * a space to store strings so that other objects may point to these * strings. It shall never be shrinked or any items changes. * STRING_ARRAY itself may be reallocated to increase the size of the * table. STRING_ARRAY_USED is the number of items currently used, * STRING_ARRAY_SIZE is the number of calloced slots. */ static char **string_array; static size_t string_array_used; static size_t string_array_size; /* 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 const 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 const struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the components 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 emitted option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. Note that this is internally also known as a header line. */ #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) /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* A human-readable description for each flag. */ static const struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* Each option we want to support in gpgconf has the needed * information in a static list per componenet. This struct describes * the info for a single option. */ struct known_option_s { /* If this is NULL, then this is a terminator in an array of unknown * length. 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. */ unsigned long flags; /* The expert level. */ gc_expert_level_t level; /* The complex type of the option argument; the default of 0 is used * for a standard type as returned by --dump-option-table. */ gc_arg_type_t arg_type; }; typedef struct known_option_s known_option_t; /* The known options of the GC_COMPONENT_GPG_AGENT component. */ static known_option_t known_options_gpg_agent[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, /**/ GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_SCDAEMON component. */ static known_option_t known_options_scdaemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "application-priority", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { NULL } }; /* The known options of the GC_COMPONENT_GPG component. */ static known_option_t known_options_gpg[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, GC_ARG_TYPE_ALIAS_LIST}, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, /* The next is a pseudo option which we read via --gpgconf-list */ { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_GPGSM component. */ static known_option_t known_options_gpgsm[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, GC_ARG_TYPE_LDAP_SERVER }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, /* Pseudo option follows. */ { "default_pubkey_algo", (GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_DIRMNGR component. */ static known_option_t known_options_dirmngr[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_PINENTRY component. */ static known_option_t known_options_pinentry[] = { { NULL } }; /* Our main option info object. We copy all required information from the * gpgrt_opt_t items but convert the flags value to bit flags. */ struct gc_option_s { const char *name; /* The same as gpgrt_opt_t.long_opt. */ const char *desc; /* The same as gpgrt_opt_t.description. */ unsigned int is_header:1; /* This is a header item. */ unsigned int is_list:1; /* This is a list style option. */ unsigned int opt_arg:1; /* The option's argument is optional. */ unsigned int runtime:1; /* The option is runtime changeable. */ unsigned int gpgconf_list:1; /* Mentioned by --gpgconf-list. */ unsigned int has_default:1; /* The option has a default value. */ unsigned int def_in_desc:1; /* The default is in the descrition. */ unsigned int no_arg_desc:1; /* The argument has a default ???. */ unsigned int no_change:1; /* User shall not change the option. */ unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */ unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */ /* The expert level - copied from known_options. */ gc_expert_level_t level; /* The complex type - copied from known_options. */ gc_arg_type_t arg_type; /* The default value for this option. This is NULL if the option is not present in the component, the empty string if no default is available, and otherwise a quoted string. This is currently malloced.*/ char *default_value; /* 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_s gc_option_t; /* The information associated with each component. */ static struct { /* The name of the component. Some components don't have an * associated program, but are implemented directly by GPGConf. In * this case, PROGRAM is NULL. */ char *program; /* The displayed 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 of this component. */ const char *desc; /* 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 program for the component * is available. */ char module_name; /* The name for the configuration filename of this component. */ const char *option_config_filename; /* The static table of known options for this component. */ known_option_t *known_options; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The table of known options as read from the component including * header lines and such. This is suitable to be passed to * gpgrt_argparser. Will be filled in by * retrieve_options_from_program. */ gpgrt_opt_t *opt_table; /* The full table including data from OPT_TABLE. The end of the * table is marked by NULL entry for NAME. Will be filled in by * retrieve_options_from_program. */ gc_option_t *options; } gc_component[GC_COMPONENT_NR] = { /* Note: The order of the items must match the order given in the * gc_component_id_t enumeration. The order is often used by * frontends to display the backend options thus do not change the * order without considering the user experience. */ { NULL }, /* DUMMY for GC_COMPONENT_ANY */ { GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"), GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf", known_options_gpg }, { GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"), GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf", known_options_gpgsm }, { GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"), GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf", known_options_gpg_agent, gpg_agent_runtime_change }, { SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"), GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", known_options_scdaemon, scdaemon_runtime_change}, { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", known_options_dirmngr, dirmngr_runtime_change }, { "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"), GNUPG_MODULE_NAME_PINENTRY, NULL, known_options_pinentry } }; /* Structure used to collect error output of the component 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); int i = 0; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_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); } 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); 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 ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_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); } 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); 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 { argv[3] = "--homedir"; argv[4] = gnupg_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); } /* 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[5]; 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)) { log_error ("%s\n", _("Component not suitable for launching")); gpgconf_failure (0); } if (gc_component_check_options (component, NULL, NULL)) { log_error (_("Configuration file of component %s is broken\n"), gc_component[component].name); if (!opt.quiet) log_info (_("Note: Use the command \"%s%s\" to get details.\n"), gc_component[component].name, " --gpgconf-test"); gpgconf_failure (0); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } 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; } static void do_runtime_change (int component, int killflag) { int runtime[GC_COMPONENT_NR] = { 0 }; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) runtime [component] = 1; } else { log_assert (component >= 0 && component < GC_COMPONENT_NR); runtime [component] = 1; } /* Do the restart for the selected components. */ for (component = GC_COMPONENT_NR-1; component >= 0; component--) { if (runtime[component] && gc_component[component].runtime_change) (*gc_component[component].runtime_change) (killflag); } } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { do_runtime_change (component, 1); } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { do_runtime_change (component, 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 possible to keep the original code set and switch back for regular diagnostic output (redefine "_(" for that) but given the nature of this tool, being something invoked from other programs, 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 = xrealloc (esc_str, new_len); 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 = xrealloc (str, new_len); 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_id_t component; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { if (!gc_component[component].program) continue; if (gc_component[component].module_name) pgmname = gnupg_module_name (gc_component[component].module_name); else pgmname = ""; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, 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. If CONF_FILE is NULL the * standard config file is used. If OUT is not NULL the output is * written to that stream. 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; const char *pgmname; const char *argv[5]; int i; pid_t pid; int exitcode; estream_t errfp; error_line_t errlines; log_assert (component >= 0 && component < GC_COMPONENT_NR); if (!gc_component[component].program) return 0; if (!gc_component[component].module_name) return 0; pgmname = gnupg_module_name (gc_component[component].module_name); i = 0; if (!gnupg_default_homedir_p () && component != GC_COMPONENT_PINENTRY) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } 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].program, 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_id_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_id_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].program && !strcmp (name, gc_component[idx].program)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (gc_component_id_t component, const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; unsigned long flags; const char *desc_domain = gc_component[component].desc_domain; if (option->desc) { desc = my_dgettext (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. */ flags = 0; if (option->is_header) flags |= GC_OPT_FLAG_GROUP; if (option->is_list) flags |= GC_OPT_FLAG_LIST; if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME; if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT; if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC; if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC; if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE; es_fprintf (out, ":%lu", flags); if (opt.verbose) { es_putc (' ', out); if (!flags) es_fprintf (out, "none"); else { 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. This was never used and is thus empty. */ es_fprintf (out, ":"); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_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; for ( ; option && option->name; option++) { /* Do not output unknown or internal options. */ if (!option->is_header && option->level == GC_LEVEL_INTERNAL) continue; if (option->is_header) { 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. */ for ( ; group_option->name; group_option++) { if (group_option->is_header) break; if (group_option->level < level) level = group_option->level; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ opt_copy = *option; opt_copy.level = level; list_one_option (component, &opt_copy, out); } } else list_one_option (component, option, out); } } /* Return true if the option NAME is known and that we want it as * gpgconf managed option. */ static known_option_t * is_known_option (gc_component_id_t component, const char *name) { known_option_t *option = gc_component[component].known_options; if (option) { for (; option->name; option++) if (!strcmp (option->name, name)) break; } return (option && option->name)? option : NULL; } /* Find the option NAME in component COMPONENT. Returns pointer to * the option descriptor or NULL if not found. */ static gc_option_t * find_option (gc_component_id_t component, const char *name) { gc_option_t *option = gc_component[component].options; if (option) { for (; option->name; option++) { if (!option->is_header && !strcmp (option->name, name)) return option; } } return NULL; } /* Retrieve the options for the component COMPONENT. With * ONLY_INSTALLED set components which are not installed are silently * ignored. */ static void retrieve_options_from_program (gc_component_id_t component, int only_installed) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; int exitcode; pid_t pid; known_option_t *known_option; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; const char *config_name; gpgrt_argparse_t pargs; int dummy_argc; char *twopartconfig_name = NULL; gpgrt_opt_t *opt_table = NULL; /* A malloced option table. */ size_t opt_table_used = 0; /* Its current length. */ size_t opt_table_size = 0; /* Its allocated length. */ gc_option_t *opt_info = NULL; /* A malloced options table. */ size_t opt_info_used = 0; /* Its current length. */ size_t opt_info_size = 0; /* Its allocated length. */ int i; pgmname = (gc_component[component].module_name ? gnupg_module_name (gc_component[component].module_name) : gc_component[component].program ); if (only_installed && access (pgmname, X_OK)) { return; /* The component is not installed. */ } /* First we need to read the option table from the program. */ argv[0] = "--dump-option-table"; 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 option table from '%s': %s", pgmname, gpg_strerror (err)); } while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { - char *fields[4]; - char *optname, *optdesc; + const char *fields[4]; + const char *optname, *optdesc; unsigned int optflags; int short_opt; gc_arg_type_t arg_type; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; if (split_fields_colon (line, fields, DIM (fields)) < 4) { gc_error (0,0, "WARNING: invalid line in option table of '%s'\n", pgmname); continue; } optname = fields[0]; short_opt = atoi (fields[1]); if (short_opt < 1) { gc_error (0,0, "WARNING: bad short option in option table of '%s'\n", pgmname); continue; } optflags = strtoul (fields[2], NULL, 10); if ((optflags & ARGPARSE_OPT_HEADER)) known_option = NULL; /* We want all header-only options. */ else if ((known_option = is_known_option (component, optname))) ; /* Yes we want this one. */ else continue; /* No need to store this option description. */ /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_table_used + 1 >= opt_table_size) { /* Note that this also does the initial allocation. */ opt_table_size += 128; opt_table = xreallocarray (opt_table, opt_table_used, opt_table_size, sizeof *opt_table); } /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_info_used + 1 >= opt_info_size) { /* Note that this also does the initial allocation. */ opt_info_size += 128; opt_info = xreallocarray (opt_info, opt_info_used, opt_info_size, sizeof *opt_info); } /* The +1 here accounts for the two items we are going to add to * the global string table. */ if (string_array_used + 1 >= string_array_size) { string_array_size += 256; string_array = xreallocarray (string_array, string_array_used, string_array_size, sizeof *string_array); } - string_array[string_array_used++] = optname = xstrdup (fields[0]); - string_array[string_array_used++] = optdesc = xstrdup (fields[3]); + optname = string_array[string_array_used++] = xstrdup (fields[0]); + optdesc = string_array[string_array_used++] = xstrdup (fields[3]); /* Create an option table which can then be supplied to * gpgrt_parser. Unfortunately there is no private pointer in * the public option table struct so that we can't add extra * data we need here. Thus we need to build up another table * for such info and for ease of use we also copy the tehre the * data from the option table. It is not possible to use the * known_option_s for this because that one does not carry * header lines and it might also be problematic to use such * static tables for caching options and default values. */ opt_table[opt_table_used].long_opt = optname; opt_table[opt_table_used].short_opt = short_opt; opt_table[opt_table_used].description = optdesc; opt_table[opt_table_used].flags = optflags; opt_table_used++; /* Note that as per argparser specs the opt_table uses "@" to * specifify an empty description. In the DESC script of * options (opt_info_t) we want to have a real empty string. */ opt_info[opt_info_used].name = optname; if (*optdesc == '@' && !optdesc[1]) opt_info[opt_info_used].desc = optdesc+1; else opt_info[opt_info_used].desc = optdesc; /* Unfortunately we need to remap the types. */ switch ((optflags & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break; case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break; default: arg_type = GC_ARG_TYPE_NONE; break; } opt_info[opt_info_used].arg_type = arg_type; if ((optflags & ARGPARSE_OPT_HEADER)) opt_info[opt_info_used].is_header = 1; if (known_option) { if ((known_option->flags & GC_OPT_FLAG_LIST)) opt_info[opt_info_used].is_list = 1; /* FIXME: The next can also be taken from opt_table->flags. * We need to check the code whether both specifications match. */ if ((known_option->flags & GC_OPT_FLAG_ARG_OPT)) opt_info[opt_info_used].opt_arg = 1; if ((known_option->flags & GC_OPT_FLAG_RUNTIME)) opt_info[opt_info_used].runtime = 1; opt_info[opt_info_used].level = known_option->level; /* Override the received argtype by a complex type. */ if (known_option->arg_type) opt_info[opt_info_used].arg_type = known_option->arg_type; } opt_info_used++; } 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); log_assert (opt_table_used == opt_info_used); 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); /* Make the gpgrt option table and the internal option table available. */ gc_component[component].opt_table = opt_table; gc_component[component].options = opt_info; /* Now read the default options. */ 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) { 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); if (option) { if (option->gpgconf_list) gc_error (1, errno, "option %s returned twice from \"%s --gpgconf-list\"", line, pgmname); option->gpgconf_list = 1; if ((flags & GC_OPT_FLAG_DEFAULT)) option->has_default = 1; if ((flags & GC_OPT_FLAG_DEF_DESC)) option->def_in_desc = 1; if ((flags & GC_OPT_FLAG_NO_ARG_DESC)) option->no_arg_desc = 1; if ((flags & GC_OPT_FLAG_NO_CHANGE)) option->no_change = 1; 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_name = gc_component[component].option_config_filename; if (!config_name) gc_error (1, 0, "name of config file for %s is not known\n", pgmname); if (!gnupg_default_homedir_p ()) { /* This is not the default homedir. We need to take an absolute * config name for the user config file; gpgrt_argparser * fortunately supports this. */ char *tmp = make_filename (gnupg_homedir (), config_name, NULL); twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL); xfree (tmp); config_name = twopartconfig_name; } memset (&pargs, 0, sizeof pargs); dummy_argc = 0; pargs.argc = &dummy_argc; pargs.flags = (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER | ARGPARSE_FLAG_WITHATTR); if (opt.verbose) pargs.flags |= ARGPARSE_FLAG_VERBOSE; while (gpgrt_argparser (&pargs, opt_table, config_name)) { char *opt_value; if (pargs.r_type & ARGPARSE_OPT_IGNORE) { /* log_debug ("ignored\n"); */ continue; } if (pargs.r_opt == ARGPARSE_CONFFILE) { /* log_debug ("current conffile='%s'\n", */ /* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */ continue; } /* We only have the short option. Search in the option table * for the long option name. */ for (i=0; opt_table[i].short_opt; i++) if (opt_table[i].short_opt == pargs.r_opt) break; if (!opt_table[i].short_opt || !opt_table[i].long_opt) continue; /* No or only a short option - ignore. */ /* Look up the option from the config file in our list of * supported options. */ option= find_option (component, opt_table[i].long_opt); if (!option) continue; /* We don't want to handle this option. */ option->attr_ignore = !!(pargs.r_type & ARGPARSE_ATTR_IGNORE); option->attr_force = !!(pargs.r_type & ARGPARSE_ATTR_FORCE); switch ((pargs.r_type & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: opt_value = xasprintf ("%d", pargs.r.ret_int); break; case ARGPARSE_TYPE_LONG: opt_value = xasprintf ("%ld", pargs.r.ret_long); break; case ARGPARSE_TYPE_ULONG: opt_value = xasprintf ("%lu", pargs.r.ret_ulong); break; case ARGPARSE_TYPE_STRING: opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str)); break; default: /* ARGPARSE_TYPE_NONE or any unknown type. */ opt_value = xstrdup ("1"); /* Make sure we have some value. */ break; } /* Now enter the value read from the config file into the table. */ if (!option->is_list) { xfree (option->value); option->value = opt_value; } else if (!option->value) /* LIST but first item. */ option->value = opt_value; else { char *old = option->value; option->value = xstrconcat (old, ",", opt_value, NULL); xfree (old); xfree (opt_value); } } xfree (line); xfree (twopartconfig_name); } /* Retrieve the currently active options and their defaults for this component. Using -1 for component will retrieve all options from all installed components. */ void gc_component_retrieve_options (int component) { int process_all = 0; if (component == -1) { process_all = 1; component = 0; } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ if (gc_component[component].program) retrieve_options_from_program (component, process_all); } 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_component_id_t component, gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; (void)component; 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->is_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->opt_arg) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !option->is_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 * 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_id_t component, 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. */ if (!gc_component[component].option_config_filename) gc_error (1, 0, "name of config file for %s is not known\n", gc_component[component].name); dest_filename = make_absfilename (gnupg_homedir (), gc_component[component].option_config_filename, NULL); 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 (component == GC_COMPONENT_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); *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 (component == GC_COMPONENT_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; for ( ; option->name; option++) { if (!option->is_header && 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) { log_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; } log_assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } } 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_component_id_t component, gc_option_t *option, int *r_runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (component, option, flags, new_value, &new_value_nr, verbatim); if (option->runtime) *r_runtime = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ log_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 = 0; char *src_filename = NULL; char *dest_filename = NULL; char *orig_filename = NULL; 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. */ 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); if (!option) gc_error (1, 0, "unknown option %s", line); if (option->no_change) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (component, 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 there is nothing to do. */ if (src_filename || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_component[component].program) { err = change_options_program (component, &src_filename, &dest_filename, &orig_filename, verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } 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) { if (src_filename) { /* FIXME: Make a verification here. */ log_assert (dest_filename); if (orig_filename) err = gnupg_rename_file (src_filename, dest_filename, 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, dest_filename, 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, dest_filename); if (!err) err = unlink (src_filename); #endif /* !HAVE_W32_SYSTEM */ } if (!err) { xfree (src_filename); src_filename = NULL; } } } if (err || opt.dry_run) { int saved_errno = errno; /* An error occurred or a dry-run is requested. */ if (src_filename) { /* The change was not yet committed. */ unlink (src_filename); if (orig_filename) unlink (orig_filename); } 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) gnupg_rename_file (orig_filename, dest_filename, NULL); else unlink (dest_filename); } 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) do_runtime_change (component, 0); /* Move the per-process backup file into its place. */ if (orig_filename) { char *backup_filename; log_assert (dest_filename); backup_filename = xasprintf ("%s.%s.bak", dest_filename, GPGCONF_NAME); gnupg_rename_file (orig_filename, backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); xfree (src_filename); xfree (dest_filename); xfree (orig_filename); } /* Check whether USER matches the current user or 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_COMPONENT_NR] = { 0 }; int component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); 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't 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, *compname, *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; gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing rule", GPG_ERR_SYNTAX, fname, lineno); continue; } *p++ = 0; compname = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { compname = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*compname == ' ' || *compname == '\t') compname++; for (p=compname; *p && !strchr (" \t\r\n", *p); p++) ; if (p == compname) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " " missing component", GPG_ERR_NO_NAME, fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (compname); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown component", GPG_ERR_UNKNOWN_NAME, 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); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing option", GPG_ERR_INV_NAME, fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { /* We need to make sure that we got the option list for the * component. */ if (!gc_component[component_id].options) gc_component_retrieve_options (component_id); option_info = find_option (component_id, option); if (!option_info) { gc_error (0, 0, "unknown option '%s' at '%s', line %d", option, fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown option", GPG_ERR_UNKNOWN_OPTION, 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); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "syntax error in rule", GPG_ERR_SYNTAX, 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->no_change = 1; else if (!strcmp (flags, "change")) option_info->no_change = 0; 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 (component_id, 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 (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_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_COMPONENT_NR] = { 0 }; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; 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); 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 (component_id, 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 (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname_buffer); return err; } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 539461d80..346a6585d 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,959 +1,959 @@ /* 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/status.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, oStatusFD, oShowSocket, oChUid, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aListDirs, aLaunch, aKill, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, aReload, aShowCodepages }; /* The list of commands and options. */ static gpgrt_opt_t 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, "@"}, ARGPARSE_c (aShowCodepages, "show-codepages", "@"), { 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") }, ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end(), }; /* The stream to output the status information. Status Output is disabled if * this is NULL. */ static estream_t statusfp; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGCONF@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 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; } /* Set the status FD. */ static void 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 output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void gpgconf_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); } 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); } #ifdef HAVE_W32_SYSTEM tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, "HomeDir"); if (tmp) { es_fflush (fp); log_info ("Warning: homedir taken from registry key (%s %s)\n", GNUPG_REGISTRY_DIR, "HomeDir"); xfree (tmp); } #endif /*HAVE_W32_SYSTEM*/ } /* 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 :: Corrupted 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]; + const 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 = GNUPG_SWDB_TAG; 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 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; gpgrt_argparse_t pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; int show_socket = 0; const char *changeuser = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); gpgrt_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 = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (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 oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oShowSocket: show_socket = 1; break; case oChUid: changeuser = pargs.r.ret_str; 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: case aShowCodepages: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) gpgconf_failure (GPG_ERR_USER_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; /* If requested switch to the requested user or die. */ if (changeuser && (err = gnupg_chuid (changeuser, 0))) gpgconf_failure (err); /* Set the configuraton directories for use by gpgrt_argparser. We * don't have a configuration file for this program but we have code * which reads the component's config files. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); 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); gpgconf_failure (GPG_ERR_USER_2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } 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)) gpgconf_failure (0); 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); gpgconf_failure (GPG_ERR_USER_2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) gpgconf_failure (0); } 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); gpgconf_failure (0); } else if (cmd == aLaunch) { err = gc_component_launch (idx); if (show_socket) { char *names[2]; if (idx == GC_COMPONENT_GPG_AGENT) names[0] = "agent-socket"; else if (idx == GC_COMPONENT_DIRMNGR) names[0] = "dirmngr-socket"; else names[0] = NULL; names[1] = NULL; get_outfp (&outfp); list_dirs (outfp, names); } if (err) gpgconf_failure (0); } 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); gpgconf_failure (0); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) gpgconf_failure (0); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) gpgconf_failure (0); 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); gpgconf_failure (GPG_ERR_USER_2); } gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) gpgconf_failure (0); break; case aApplyProfile: gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) gpgconf_failure (0); 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); gpgconf_failure (GPG_ERR_USER_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 delete * 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; case aShowCodepages: #ifdef HAVE_W32_SYSTEM { get_outfp (&outfp); es_fprintf (outfp, "Console: CP%u\n", GetConsoleOutputCP ()); es_fprintf (outfp, "ANSI: CP%u\n", GetACP ()); es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ()); } #endif break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); if (log_get_errorcount (0)) gpgconf_failure (0); else gpgconf_write_status (STATUS_SUCCESS, NULL); return 0; } void gpgconf_failure (gpg_error_t err) { log_flush (); if (!err) err = gpg_error (GPG_ERR_GENERAL); gpgconf_write_status (STATUS_FAILURE, "- %u", gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); } diff --git a/tools/wks-util.c b/tools/wks-util.c index 22ad9881b..30461f850 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -1,1230 +1,1230 @@ /* wks-utils.c - Common helper functions for wks tools * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * 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 Lesser General Public License for more details. */ #include #include #include #include #include #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/zb32.h" #include "../common/userids.h" #include "../common/mbox-util.h" #include "../common/sysutils.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 output 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); } /* Append UID to LIST and return the new item. On success LIST is * updated. On error ERRNO is set and NULL returned. */ static uidinfo_list_t append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created) { uidinfo_list_t r, sl; sl = xtrymalloc (sizeof *sl + strlen (uid)); if (!sl) return NULL; strcpy (sl->uid, uid); sl->created = created; sl->mbox = mailbox_from_userid (uid, 0); sl->next = NULL; if (!*list) *list = sl; else { for (r = *list; r->next; r = r->next ) ; r->next = sl; } return sl; } /* Free the list of uid infos at LIST. */ void free_uidinfo_list (uidinfo_list_t list) { while (list) { uidinfo_list_t tmp = list->next; xfree (list->mbox); xfree (list); list = tmp; } } struct get_key_status_parm_s { const char *fpr; int found; int count; }; static void get_key_status_cb (void *opaque, const char *keyword, char *args) { struct get_key_status_parm_s *parm = opaque; /*log_debug ("%s: %s\n", keyword, args);*/ if (!strcmp (keyword, "EXPORTED")) { parm->count++; if (!ascii_strcasecmp (args, parm->fpr)) parm->found = 1; } } /* Get a key by fingerprint from gpg's keyring and make sure that the * mail address ADDRSPEC is included in the key. If EXACT is set the * returned user id must match Addrspec exactly and not just in the * addr-spec (mailbox) part. The key is returned as a new memory * stream at R_KEY. */ gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, int exact) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t key = NULL; struct get_key_status_parm_s parm; char *filterexp = NULL; memset (&parm, 0, sizeof parm); *r_key = NULL; key = es_fopenmem (0, "w+b"); if (!key) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } /* Prefix the key with the MIME content type. */ es_fputs ("Content-Type: application/pgp-keys\n" "\n", key); filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } 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, "--armor"); ccparray_put (&ccp, "--export-options=export-minimal"); ccparray_put (&ccp, "--export-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--export"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } parm.fpr = fingerprint; err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, key, get_key_status_cb, &parm); if (!err && parm.count > 1) err = gpg_error (GPG_ERR_TOO_MANY); else if (!err && !parm.found) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) { log_error ("export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (key); *r_key = key; key = NULL; leave: es_fclose (key); xfree (argv); xfree (filterexp); return err; } /* Helper for wks_list_key and wks_filter_uid. */ static void 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. * 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; } /* Run gpg as a filter on KEY and write the output to a new stream * stored at R_NEWKEY. The new key will contain only the user id UID. * Returns 0 on success. Only one key is expected in KEY. If BINARY * is set the resulting key is returned as a binary (non-armored) * keyblock. */ gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, int binary) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t newkey; char *filterexp = NULL; *r_newkey = NULL; /* Open a memory stream. */ newkey = es_fopenmem (0, "w+b"); if (!newkey) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* Prefix the key with the MIME content type. */ if (!binary) es_fputs ("Content-Type: application/pgp-keys\n" "\n", newkey); filterexp = es_bsprintf ("keep-uid=uid= %s", uid); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } 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"); if (!binary) ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--import-options=import-export"); ccparray_put (&ccp, "--import-filter"); ccparray_put (&ccp, filterexp); 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, newkey, key_status_cb, NULL); if (err) { log_error ("import/export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (newkey); *r_newkey = newkey; newkey = NULL; leave: xfree (filterexp); xfree (argv); es_fclose (newkey); 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 set 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; } void wks_free_policy (policy_flags_t policy) { if (policy) { xfree (policy->submission_address); memset (policy, 0, sizeof *policy); } } /* Write the content of SRC to the new file FNAME. */ static gpg_error_t write_to_file (estream_t src, const char *fname) { gpg_error_t err; estream_t dst; char buffer[4096]; size_t nread, written; dst = es_fopen (fname, "wb"); if (!dst) return gpg_error_from_syserror (); do { nread = es_fread (buffer, 1, sizeof buffer, src); if (!nread) break; written = es_fwrite (buffer, 1, nread, dst); if (written != nread) break; } while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) { err = gpg_error_from_syserror (); es_fclose (dst); gnupg_remove (fname); return err; } if (es_fclose (dst)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); return err; } return 0; } /* Return the filename and optionally the addrspec for USERID at * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If * HASH_ONLY is set only the has is returned at R_FNAME and no file is * created. */ gpg_error_t wks_fname_from_userid (const char *userid, int hash_only, char **r_fname, char **r_addrspec) { gpg_error_t err; char *addrspec = NULL; const char *domain; char *hash = NULL; const char *s; char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ *r_fname = NULL; if (r_addrspec) *r_addrspec = NULL; addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { if (opt.verbose || hash_only) log_info ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } domain = strchr (addrspec, '@'); log_assert (domain); domain++; /* Hash user ID and create filename. */ s = strchr (addrspec, '@'); log_assert (s); gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); hash = zb32_encode (shaxbuf, 8*20); if (!hash) { err = gpg_error_from_syserror (); goto leave; } if (hash_only) { *r_fname = hash; hash = NULL; err = 0; } else { *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); if (!*r_fname) err = gpg_error_from_syserror (); else err = 0; } leave: if (r_addrspec && addrspec) *r_addrspec = addrspec; else xfree (addrspec); xfree (hash); return err; } /* Compute the the full file name for the key with ADDRSPEC and return * it at R_FNAME. */ gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec) { gpg_error_t err; char *hash; const char *domain; char sha1buf[20]; char *fname; struct stat sb; *r_fname = NULL; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); hash = zb32_encode (sha1buf, 8*20); if (!hash) return gpg_error_from_syserror (); /* Try to create missing directories below opt.directory. */ fname = make_filename_try (opt.directory, domain, NULL); if (fname && stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); fname = make_filename_try (opt.directory, domain, "hu", NULL); if (fname && stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr--r--") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) xfree (fname); else *r_fname = fname; /* Okay. */ xfree (hash); return err; } /* Make sure that a policy file exists for addrspec. Directories must * already exist. */ static gpg_error_t ensure_policy_file (const char *addrspec) { gpg_error_t err; const char *domain; char *fname; estream_t fp; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "policy", NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) goto leave; /* First a quick check whether it already exists. */ if (!access (fname, F_OK)) { err = 0; /* File already exists. */ goto leave; } err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = 0; else { log_error ("domain %s: problem with '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } /* Now create the file. */ fp = es_fopen (fname, "wxb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EEXIST) err = 0; /* Was created between the access() and fopen(). */ else log_error ("domain %s: error creating '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } es_fprintf (fp, "# Policy flags for domain %s\n", domain); if (es_ferror (fp) || es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("policy file '%s' created\n", fname); /* Make sure the policy file world readable. */ if (gnupg_chmod (fname, "-rw-rw-r--")) { err = gpg_error_from_syserror (); log_error ("can't set permissions of '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: xfree (fname); return err; } /* Helper form wks_cmd_install_key. */ static gpg_error_t install_key_from_spec_file (const char *fname) { gpg_error_t err; estream_t fp; char *line = NULL; size_t linelen = 0; size_t maxlen = 2048; - char *fields[2]; + const char *fields[2]; unsigned int lnr = 0; if (!fname || !strcmp (fname, "")) fp = es_stdin; else fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } while (es_read_line (fp, &line, &linelen, &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; } lnr++; trim_spaces (line); if (!*line || *line == '#') continue; if (split_fields (line, fields, DIM(fields)) < 2) { log_error ("error reading '%s': syntax error at line %u\n", fname, lnr); continue; } err = wks_cmd_install_key (fields[0], fields[1]); if (err) goto leave; } if (es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: if (fp != es_stdin) es_fclose (fp); es_free (line); return err; } /* Install a single key into the WKD by reading FNAME and extracting * USERID. If USERID is NULL FNAME is expected to be a list of fpr * mbox lines and for each line the respective key will be * installed. */ gpg_error_t wks_cmd_install_key (const char *fname, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; estream_t fp = NULL; char *addrspec = NULL; char *fpr = NULL; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; char *huname = NULL; int any; if (!userid) return install_key_from_spec_file (fname); addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (!classify_user_id (fname, &desc, 1) && desc.mode == KEYDB_SEARCH_MODE_FPR) { /* FNAME looks like a fingerprint. Get the key from the * standard keyring. */ err = wks_get_key (&fp, fname, addrspec, 0); if (err) { log_error ("error getting key '%s' (uid='%s'): %s\n", fname, addrspec, gpg_strerror (err)); goto leave; } } else /* Take it from the file */ { fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* List the key so that we can figure out the newest UID with the * requested addrspec. */ err = wks_list_key (fp, &fpr, &uidlist); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; any = 0; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (ascii_strcasecmp (uid->mbox, addrspec)) continue; /* Not the requested addrspec. */ any = 1; if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (!any) { log_error ("public key in '%s' has no mail address '%s'\n", fname, addrspec); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (opt.verbose) log_info ("using key with user id '%s'\n", thisuid->uid); { estream_t fp2; es_rewind (fp); err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (fp); fp = fp2; } /* Hash user ID and create filename. */ err = wks_compute_hu_fname (&huname, addrspec); if (err) goto leave; /* Now that wks_compute_hu_fname has created missing directories we * can create a policy file if it does not exist. */ err = ensure_policy_file (addrspec); if (err) goto leave; /* Publish. */ err = write_to_file (fp, huname); if (err) { log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); goto leave; } /* Make sure it is world readable. */ if (gnupg_chmod (huname, "-rwxr--r--")) log_error ("can't set permissions of '%s': %s\n", huname, gpg_strerror (gpg_err_code_from_syserror())); if (!opt.quiet) log_info ("key %s published for '%s'\n", fpr, addrspec); leave: xfree (huname); free_uidinfo_list (uidlist); xfree (fpr); xfree (addrspec); es_fclose (fp); return err; } /* Remove the key with mail address in USERID. */ gpg_error_t wks_cmd_remove_key (const char *userid) { gpg_error_t err; char *addrspec = NULL; char *fname = NULL; err = wks_fname_from_userid (userid, 0, &fname, &addrspec); if (err) goto leave; if (gnupg_remove (fname)) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (!opt.quiet) log_info ("key for '%s' is not installed\n", addrspec); log_inc_errorcount (); err = 0; } else log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("key for '%s' removed\n", addrspec); err = 0; leave: xfree (fname); xfree (addrspec); return err; } /* Print the WKD hash for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_hash (const char *userid) { gpg_error_t err; char *addrspec, *fname; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; es_printf ("%s %s\n", fname, addrspec); xfree (fname); xfree (addrspec); return err; } /* Print the WKD URL for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_url (const char *userid) { gpg_error_t err; char *addrspec, *fname; char *domain; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; domain = strchr (addrspec, '@'); if (domain) *domain++ = 0; es_printf ("https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s\n", domain, domain, fname, addrspec); xfree (fname); xfree (addrspec); return err; }