X-Git-Url: http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gpgme.git;a=blobdiff_plain;f=src%2Fengine-gpg.c;h=3b9a6ff5e6f8befcfab776fad79b1d2735523a3e;hp=ede098ef96395c3e4de0b601e1ff02cb77a23c09;hb=fed024eff1091056647296ac589a0c88c2be41bb;hpb=991cde9e79fec70aad093ded383c5574d30f9388 diff --git a/src/engine-gpg.c b/src/engine-gpg.c index ede098ef..3b9a6ff5 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -16,7 +16,7 @@ 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 . + License along with this program; if not, see . */ #if HAVE_CONFIG_H @@ -42,6 +42,7 @@ #include "priv-io.h" #include "sema.h" #include "debug.h" +#include "data.h" #include "engine-backend.h" @@ -73,11 +74,16 @@ struct fd_data_map_s }; +/* NB.: R_LINE is allocated an gpgrt function and thus gpgrt_free + * shall be used to release it. This takes care of custom memory + * allocators and avoids problems on Windows with different runtimes + * used for libgpg-error/gpgrt and gpgme. */ typedef gpgme_error_t (*colon_preprocessor_t) (char *line, char **rline); struct engine_gpg { char *file_name; + char *version; char *lc_messages; char *lc_ctype; @@ -95,6 +101,8 @@ struct engine_gpg int eof; engine_status_handler_t fnc; void *fnc_value; + gpgme_status_cb_t mon_cb; + void *mon_cb_value; void *tag; } status; @@ -135,6 +143,10 @@ struct engine_gpg struct gpgme_io_cbs io_cbs; gpgme_pinentry_mode_t pinentry_mode; + char request_origin[10]; + + /* NULL or the data object fed to --override_session_key-fd. */ + gpgme_data_t override_session_key; }; typedef struct engine_gpg *engine_gpg_t; @@ -202,14 +214,16 @@ close_notify_handler (int fd, void *opaque) /* If FRONT is true, push at the front of the list. Use this for options added late in the process. */ static gpgme_error_t -_add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp) +_add_arg (engine_gpg_t gpg, const char *prefix, const char *arg, size_t arglen, + int front, int *arg_locp) { struct arg_and_data_s *a; + size_t prefixlen = prefix? strlen (prefix) : 0; assert (gpg); assert (arg); - a = malloc (sizeof *a + strlen (arg)); + a = malloc (sizeof *a + prefixlen + arglen); if (!a) return gpg_error_from_syserror (); @@ -217,7 +231,10 @@ _add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp) a->dup_to = -1; a->arg_locp = arg_locp; - strcpy (a->arg, arg); + if (prefixlen) + memcpy (a->arg, prefix, prefixlen); + memcpy (a->arg + prefixlen, arg, arglen); + a->arg[prefixlen + arglen] = 0; if (front) { a->next = gpg->arglist; @@ -239,24 +256,36 @@ _add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp) return 0; } + static gpgme_error_t add_arg_ext (engine_gpg_t gpg, const char *arg, int front) { - return _add_arg (gpg, arg, front, NULL); + return _add_arg (gpg, NULL, arg, strlen (arg), front, NULL); } - static gpgme_error_t add_arg_with_locp (engine_gpg_t gpg, const char *arg, int *locp) { - return _add_arg (gpg, arg, 0, locp); + return _add_arg (gpg, NULL, arg, strlen (arg), 0, locp); } - static gpgme_error_t add_arg (engine_gpg_t gpg, const char *arg) { - return add_arg_ext (gpg, arg, 0); + return _add_arg (gpg, NULL, arg, strlen (arg), 0, NULL); +} + +static gpgme_error_t +add_arg_pfx (engine_gpg_t gpg, const char *prefix, const char *arg) +{ + return _add_arg (gpg, prefix, arg, strlen (arg), 0, NULL); +} + +static gpgme_error_t +add_arg_len (engine_gpg_t gpg, const char *prefix, + const char *arg, size_t arglen) +{ + return _add_arg (gpg, prefix, arg, arglen, 0, NULL); } @@ -291,6 +320,15 @@ add_data (engine_gpg_t gpg, gpgme_data_t data, int dup_to, int inbound) return 0; } + +/* Return true if the engine's version is at least VERSION. */ +static int +have_gpg_version (engine_gpg_t gpg, const char *version) +{ + return _gpgme_compare_versions (gpg->version, version); +} + + static char * gpg_get_version (const char *file_name) @@ -386,6 +424,8 @@ gpg_release (void *engine) if (gpg->file_name) free (gpg->file_name); + if (gpg->version) + free (gpg->version); if (gpg->lc_messages) free (gpg->lc_messages); @@ -396,8 +436,7 @@ gpg_release (void *engine) { struct arg_and_data_s *next = gpg->arglist->next; - if (gpg->arglist) - free (gpg->arglist); + free (gpg->arglist); gpg->arglist = next; } @@ -410,18 +449,22 @@ gpg_release (void *engine) if (gpg->cmd.keyword) free (gpg->cmd.keyword); + gpgme_data_release (gpg->override_session_key); + free (gpg); } static gpgme_error_t -gpg_new (void **engine, const char *file_name, const char *home_dir) +gpg_new (void **engine, const char *file_name, const char *home_dir, + const char *version) { engine_gpg_t gpg; gpgme_error_t rc = 0; char *dft_display = NULL; char dft_ttyname[64]; char *dft_ttytype = NULL; + char *env_tty = NULL; gpg = calloc (1, sizeof *gpg); if (!gpg) @@ -437,6 +480,16 @@ gpg_new (void **engine, const char *file_name, const char *home_dir) } } + if (version) + { + gpg->version = strdup (version); + if (!gpg->version) + { + rc = gpg_error_from_syserror (); + goto leave; + } + } + gpg->argtail = &gpg->arglist; gpg->status.fd[0] = -1; gpg->status.fd[1] = -1; @@ -501,6 +554,8 @@ gpg_new (void **engine, const char *file_name, const char *home_dir) rc = add_arg (gpg, "utf8"); if (!rc) rc = add_arg (gpg, "--enable-progress-filter"); + if (!rc && have_gpg_version (gpg, "2.1.11")) + rc = add_arg (gpg, "--exit-on-status-write-error"); if (rc) goto leave; @@ -514,16 +569,28 @@ gpg_new (void **engine, const char *file_name, const char *home_dir) rc = add_arg (gpg, dft_display); free (dft_display); + if (rc) + goto leave; } - if (isatty (1)) + rc = _gpgme_getenv ("GPG_TTY", &env_tty); + if (isatty (1) || env_tty || rc) { - int err; + int err = 0; - err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); - if (err) - rc = gpg_error_from_errno (err); + if (rc) + goto leave; + else if (env_tty) + { + snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); + free (env_tty); + } else + err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); + + /* Even though isatty() returns 1, ttyname_r() may fail in many + ways, e.g., when /dev/pts is not accessible under chroot. */ + if (!err) { if (*dft_ttyname) { @@ -548,9 +615,9 @@ gpg_new (void **engine, const char *file_name, const char *home_dir) free (dft_ttytype); } + if (rc) + goto leave; } - if (rc) - goto leave; } leave: @@ -562,6 +629,24 @@ gpg_new (void **engine, const char *file_name, const char *home_dir) } +/* Copy flags from CTX into the engine object. */ +static void +gpg_set_engine_flags (void *engine, const gpgme_ctx_t ctx) +{ + engine_gpg_t gpg = engine; + + if (ctx->request_origin && have_gpg_version (gpg, "2.2.6")) + { + if (strlen (ctx->request_origin) + 1 > sizeof gpg->request_origin) + strcpy (gpg->request_origin, "xxx"); /* Too long - force error */ + else + strcpy (gpg->request_origin, ctx->request_origin); + } + else + *gpg->request_origin = 0; +} + + static gpgme_error_t gpg_set_locale (void *engine, int category, const char *value) { @@ -607,6 +692,17 @@ gpg_set_locale (void *engine, int category, const char *value) return 0; } +/* This sets a status callback for monitoring status lines before they + * are passed to a caller set handler. */ +static void +gpg_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value) +{ + engine_gpg_t gpg = engine; + + gpg->status.mon_cb = cb; + gpg->status.mon_cb_value = cb_value; +} + /* Note, that the status_handler is allowed to modifiy the args value. */ @@ -779,7 +875,7 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; if (!gpg->cmd.used) argc++; /* --batch */ - argc += 1; /* --no-sk-comment */ + argc += 2; /* --no-sk-comments, --request-origin */ argv = calloc (argc + 1, sizeof *argv); if (!argv) @@ -827,7 +923,21 @@ build_argv (engine_gpg_t gpg, const char *pgmname) argc++; } - if (gpg->pinentry_mode) + if (*gpg->request_origin) + { + argv[argc] = _gpgme_strconcat ("--request-origin=", + gpg->request_origin, NULL); + if (!argv[argc]) + { + int saved_err = gpg_error_from_syserror (); + free (fd_data_map); + free_argv (argv); + return saved_err; + } + argc++; + } + + if (gpg->pinentry_mode && have_gpg_version (gpg, "2.1.0")) { const char *s = NULL; switch (gpg->pinentry_mode) @@ -864,7 +974,7 @@ build_argv (engine_gpg_t gpg, const char *pgmname) } argc++; } - argv[argc] = strdup ("--no-sk-comment"); + argv[argc] = strdup ("--no-sk-comments"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); @@ -1017,6 +1127,7 @@ read_status (engine_gpg_t gpg) size_t bufsize = gpg->status.bufsize; char *buffer = gpg->status.buffer; size_t readpos = gpg->status.readpos; + gpgme_error_t err; assert (buffer); if (bufsize - readpos < 256) @@ -1035,15 +1146,20 @@ read_status (engine_gpg_t gpg) if (!nread) { + err = 0; gpg->status.eof = 1; + if (gpg->status.mon_cb) + err = gpg->status.mon_cb (gpg->status.mon_cb_value, "", ""); if (gpg->status.fnc) - { - gpgme_error_t err; - err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, ""); - if (err) - return err; - } - return 0; + { + char emptystring[1] = {0}; + err = gpg->status.fnc (gpg->status.fnc_value, + GPGME_STATUS_EOF, emptystring); + if (gpg_err_code (err) == GPG_ERR_FALSE) + err = 0; /* Drop special error code. */ + } + + return err; } while (nread > 0) @@ -1069,6 +1185,15 @@ read_status (engine_gpg_t gpg) *rest++ = 0; r = _gpgme_parse_status (buffer + 9); + if (gpg->status.mon_cb && r != GPGME_STATUS_PROGRESS) + { + /* Note that we call the monitor even if we do + * not know the status code (r < 0). */ + err = gpg->status.mon_cb (gpg->status.mon_cb_value, + buffer + 9, rest); + if (err) + return err; + } if (r >= 0) { if (gpg->cmd.used @@ -1097,9 +1222,10 @@ read_status (engine_gpg_t gpg) } else if (gpg->status.fnc) { - gpgme_error_t err; err = gpg->status.fnc (gpg->status.fnc_value, r, rest); + if (gpg_err_code (err) == GPG_ERR_FALSE) + err = 0; /* Drop special error code. */ if (err) return err; } @@ -1242,10 +1368,26 @@ read_colon_line (engine_gpg_t gpg) } assert (gpg->colon.fnc); - gpg->colon.fnc (gpg->colon.fnc_value, line ? line : buffer); - if (line) - free (line); - } + if (line) + { + char *linep = line; + char *endp; + + do + { + endp = strchr (linep, '\n'); + if (endp) + *endp++ = 0; + gpg->colon.fnc (gpg->colon.fnc_value, linep); + linep = endp; + } + while (linep && *linep); + + gpgrt_free (line); + } + else + gpg->colon.fnc (gpg->colon.fnc_value, buffer); + } /* To reuse the buffer for the next line we have to shift the remaining data to the buffer start and @@ -1420,14 +1562,89 @@ start (engine_gpg_t gpg) } +/* Add the --input-size-hint option if requested. */ +static gpgme_error_t +add_input_size_hint (engine_gpg_t gpg, gpgme_data_t data) +{ + gpgme_error_t err; + gpgme_off_t value = _gpgme_data_get_size_hint (data); + char numbuf[50]; /* Large enough for even 2^128 in base-10. */ + char *p; + + if (!value || !have_gpg_version (gpg, "2.1.15")) + return 0; + + err = add_arg (gpg, "--input-size-hint"); + if (!err) + { + p = numbuf + sizeof numbuf; + *--p = 0; + do + { + *--p = '0' + (value % 10); + value /= 10; + } + while (value); + err = add_arg (gpg, p); + } + return err; +} + + static gpgme_error_t -gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +gpg_decrypt (void *engine, + gpgme_decrypt_flags_t flags, + gpgme_data_t ciph, gpgme_data_t plain, + int export_session_key, const char *override_session_key, + int auto_key_retrieve) { engine_gpg_t gpg = engine; gpgme_error_t err; err = add_arg (gpg, "--decrypt"); + if (!err && (flags & GPGME_DECRYPT_UNWRAP)) + { + if (!have_gpg_version (gpg, "2.1.12")) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + err = add_arg (gpg, "--unwrap"); + } + + if (!err && export_session_key) + err = add_arg (gpg, "--show-session-key"); + + if (!err && auto_key_retrieve) + err = add_arg (gpg, "--auto-key-retrieve"); + + if (!err && override_session_key && *override_session_key) + { + if (have_gpg_version (gpg, "2.1.16")) + { + gpgme_data_release (gpg->override_session_key); + TRACE2 (DEBUG_ENGINE, "override", gpg, "seskey='%s' len=%zu\n", + override_session_key, + strlen (override_session_key)); + + err = gpgme_data_new_from_mem (&gpg->override_session_key, + override_session_key, + strlen (override_session_key), 1); + if (!err) + { + err = add_arg (gpg, "--override-session-key-fd"); + if (!err) + err = add_data (gpg, gpg->override_session_key, -2, 0); + } + } + else + { + /* Using that option may leak the session key via ps(1). */ + err = add_arg (gpg, "--override-session-key"); + if (!err) + err = add_arg (gpg, override_session_key); + } + } + /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--output"); @@ -1436,23 +1653,30 @@ gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) if (!err) err = add_data (gpg, plain, 1, 1); if (!err) + err = add_input_size_hint (gpg, ciph); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, ciph, -1, 0); if (!err) - start (gpg); + err = start (gpg); return err; } static gpgme_error_t -gpg_delete (void *engine, gpgme_key_t key, int allow_secret) +gpg_delete (void *engine, gpgme_key_t key, unsigned int flags) { engine_gpg_t gpg = engine; - gpgme_error_t err; + gpgme_error_t err = 0; + int allow_secret = flags & GPGME_DELETE_ALLOW_SECRET; + int force = flags & GPGME_DELETE_FORCE; - err = add_arg (gpg, allow_secret ? "--delete-secret-and-public-key" - : "--delete-key"); + if (force) + err = add_arg (gpg, "--yes"); + if (!err) + err = add_arg (gpg, allow_secret ? "--delete-secret-and-public-key" + : "--delete-key"); if (!err) err = add_arg (gpg, "--"); if (!err) @@ -1464,7 +1688,7 @@ gpg_delete (void *engine, gpgme_key_t key, int allow_secret) } if (!err) - start (gpg); + err = start (gpg); return err; } @@ -1475,6 +1699,8 @@ gpg_passwd (void *engine, gpgme_key_t key, unsigned int flags) engine_gpg_t gpg = engine; gpgme_error_t err; + (void)flags; + if (!key || !key->subkeys || !key->subkeys->fpr) return gpg_error (GPG_ERR_INV_CERT_OBJ); @@ -1482,7 +1708,7 @@ gpg_passwd (void *engine, gpgme_key_t key, unsigned int flags) if (!err) err = add_arg (gpg, key->subkeys->fpr); if (!err) - start (gpg); + err = start (gpg); return err; } @@ -1505,8 +1731,26 @@ append_args_from_signers (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */) err = add_arg (gpg, s); } gpgme_key_unref (key); - if (err) break; + if (err) + break; + } + return err; +} + + +static gpgme_error_t +append_args_from_sender (engine_gpg_t gpg, gpgme_ctx_t ctx) +{ + gpgme_error_t err; + + if (ctx->sender && have_gpg_version (gpg, "2.1.15")) + { + err = add_arg (gpg, "--sender"); + if (!err) + err = add_arg (gpg, ctx->sender); } + else + err = 0; return err; } @@ -1654,18 +1898,39 @@ gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t ciph, int use_armor) { engine_gpg_t gpg = engine; - gpgme_error_t err; - int symmetric = !recp; + gpgme_error_t err = 0; - err = add_arg (gpg, symmetric ? "--symmetric" : "--encrypt"); + if (recp) + err = add_arg (gpg, "--encrypt"); + + if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || !recp)) + err = add_arg (gpg, "--symmetric"); if (!err && use_armor) err = add_arg (gpg, "--armor"); + if (!err && (flags & GPGME_ENCRYPT_WRAP)) + { + /* gpg is current not able to detect already compressed + * packets. Thus when using + * gpg --unwrap -d | gpg --no-literal -e + * the encryption would add an additional compression layer. + * We better suppress that. */ + flags |= GPGME_ENCRYPT_NO_COMPRESS; + err = add_arg (gpg, "--no-literal"); + } + if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS)) err = add_arg (gpg, "--compress-algo=none"); - if (!symmetric) + if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS)) + err = add_arg (gpg, "--throw-keyids"); + + if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME + && have_gpg_version (gpg, "2.1.14")) + err = add_arg (gpg, "--mimemode"); + + if (recp) { /* If we know that all recipients are valid (full or ultimate trust) we can suppress further checks. */ @@ -1694,6 +1959,8 @@ gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, err = add_arg (gpg, gpgme_data_get_file_name (plain)); } if (!err) + err = add_input_size_hint (gpg, plain); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, plain, -1, 0); @@ -1712,10 +1979,13 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[], gpgme_ctx_t ctx /* FIXME */) { engine_gpg_t gpg = engine; - gpgme_error_t err; - int symmetric = !recp; + gpgme_error_t err = 0; - err = add_arg (gpg, symmetric ? "--symmetric" : "--encrypt"); + if (recp) + err = add_arg (gpg, "--encrypt"); + + if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || !recp)) + err = add_arg (gpg, "--symmetric"); if (!err) err = add_arg (gpg, "--sign"); @@ -1725,7 +1995,14 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[], if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS)) err = add_arg (gpg, "--compress-algo=none"); - if (!symmetric) + if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS)) + err = add_arg (gpg, "--throw-keyids"); + + if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME + && have_gpg_version (gpg, "2.1.14")) + err = add_arg (gpg, "--mimemode"); + + if (recp) { /* If we know that all recipients are valid (full or ultimate trust) we can suppress further checks. */ @@ -1743,6 +2020,9 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[], err = append_args_from_signers (gpg, ctx); if (!err) + err = append_args_from_sender (gpg, ctx); + + if (!err) err = append_args_from_sig_notations (gpg, ctx); /* Tell the gpg object about the data. */ @@ -1760,6 +2040,8 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[], err = add_arg (gpg, gpgme_data_get_file_name (plain)); } if (!err) + err = add_input_size_hint (gpg, plain); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, plain, -1, 0); @@ -1778,7 +2060,8 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode, gpgme_error_t err = 0; if ((mode & ~(GPGME_EXPORT_MODE_EXTERN - |GPGME_EXPORT_MODE_MINIMAL))) + |GPGME_EXPORT_MODE_MINIMAL + |GPGME_EXPORT_MODE_SECRET))) return gpg_error (GPG_ERR_NOT_SUPPORTED); if ((mode & GPGME_EXPORT_MODE_MINIMAL)) @@ -1792,7 +2075,10 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode, } else { - err = add_arg (gpg, "--export"); + if ((mode & GPGME_EXPORT_MODE_SECRET)) + err = add_arg (gpg, "--export-secret-keys"); + else + err = add_arg (gpg, "--export"); if (!err && use_armor) err = add_arg (gpg, "--armor"); if (!err) @@ -1846,33 +2132,229 @@ gpg_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode, } + +/* Helper to add algo, usage, and expire to the list of args. */ static gpgme_error_t -gpg_genkey (void *engine, gpgme_data_t help_data, int use_armor, - gpgme_data_t pubkey, gpgme_data_t seckey) +gpg_add_algo_usage_expire (engine_gpg_t gpg, + const char *algo, + unsigned long expires, + unsigned int flags) { - engine_gpg_t gpg = engine; - gpgme_error_t err; + gpg_error_t err; - if (!gpg) - return gpg_error (GPG_ERR_INV_VALUE); + /* This condition is only required to allow the use of gpg < 2.1.16 */ + if (algo + || (flags & (GPGME_CREATE_SIGN | GPGME_CREATE_ENCR + | GPGME_CREATE_CERT | GPGME_CREATE_AUTH + | GPGME_CREATE_NOEXPIRE)) + || expires) + { + err = add_arg (gpg, algo? algo : "default"); + if (!err) + { + char tmpbuf[5*4+1]; + snprintf (tmpbuf, sizeof tmpbuf, "%s%s%s%s", + (flags & GPGME_CREATE_SIGN)? " sign":"", + (flags & GPGME_CREATE_ENCR)? " encr":"", + (flags & GPGME_CREATE_CERT)? " cert":"", + (flags & GPGME_CREATE_AUTH)? " auth":""); + err = add_arg (gpg, *tmpbuf? tmpbuf : "default"); + } + if (!err) + { + if ((flags & GPGME_CREATE_NOEXPIRE)) + err = add_arg (gpg, "never"); + else if (expires == 0) + err = add_arg (gpg, "-"); + else + { + char tmpbuf[8+20]; + snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expires); + err = add_arg (gpg, tmpbuf); + } + } + } + else + err = 0; - /* We need a special mechanism to get the fd of a pipe here, so that - we can use this for the %pubring and %secring parameters. We - don't have this yet, so we implement only the adding to the - standard keyrings. */ - if (pubkey || seckey) - return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + return err; +} + + +static gpgme_error_t +gpg_createkey_from_param (engine_gpg_t gpg, + gpgme_data_t help_data, unsigned int extraflags) +{ + gpgme_error_t err; err = add_arg (gpg, "--gen-key"); - if (!err && use_armor) + if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) err = add_arg (gpg, "--armor"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, help_data, -1, 0); + if (!err) + err = start (gpg); + return err; +} + + +static gpgme_error_t +gpg_createkey (engine_gpg_t gpg, + const char *userid, const char *algo, + unsigned long expires, + unsigned int flags, + unsigned int extraflags) +{ + gpgme_error_t err; + + err = add_arg (gpg, "--quick-gen-key"); + if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) + err = add_arg (gpg, "--armor"); + if (!err && (flags & GPGME_CREATE_NOPASSWD)) + { + err = add_arg (gpg, "--passphrase"); + if (!err) + err = add_arg (gpg, ""); + if (!err) + err = add_arg (gpg, "--batch"); + } + if (!err && (flags & GPGME_CREATE_FORCE)) + err = add_arg (gpg, "--yes"); + if (!err) + err = add_arg (gpg, "--"); + if (!err) + err = add_arg (gpg, userid); + + if (!err) + err = gpg_add_algo_usage_expire (gpg, algo, expires, flags); + + if (!err) + err = start (gpg); + return err; +} + + +static gpgme_error_t +gpg_addkey (engine_gpg_t gpg, + const char *algo, + unsigned long expires, + gpgme_key_t key, + unsigned int flags, + unsigned int extraflags) +{ + gpgme_error_t err; + + if (!key || !key->fpr) + return gpg_error (GPG_ERR_INV_ARG); + + err = add_arg (gpg, "--quick-addkey"); + if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) + err = add_arg (gpg, "--armor"); + if (!err && (flags & GPGME_CREATE_NOPASSWD)) + { + err = add_arg (gpg, "--passphrase"); + if (!err) + err = add_arg (gpg, ""); + if (!err) + err = add_arg (gpg, "--batch"); + } + if (!err) + err = add_arg (gpg, "--"); + if (!err) + err = add_arg (gpg, key->fpr); + + if (!err) + err = gpg_add_algo_usage_expire (gpg, algo, expires, flags); if (!err) err = start (gpg); + return err; +} + + +static gpgme_error_t +gpg_adduid (engine_gpg_t gpg, + gpgme_key_t key, + const char *userid, + unsigned int extraflags) +{ + gpgme_error_t err; + + if (!key || !key->fpr || !userid) + return gpg_error (GPG_ERR_INV_ARG); + + if ((extraflags & GENKEY_EXTRAFLAG_SETPRIMARY)) + { + if (!have_gpg_version (gpg, "2.1.20")) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + err = add_arg (gpg, "--quick-set-primary-uid"); + } + else if ((extraflags & GENKEY_EXTRAFLAG_REVOKE)) + err = add_arg (gpg, "--quick-revuid"); + else + err = add_arg (gpg, "--quick-adduid"); + + if (!err) + err = add_arg (gpg, "--"); + if (!err) + err = add_arg (gpg, key->fpr); + if (!err) + err = add_arg (gpg, userid); + + if (!err) + err = start (gpg); + return err; +} + + +static gpgme_error_t +gpg_genkey (void *engine, + const char *userid, const char *algo, + unsigned long reserved, unsigned long expires, + gpgme_key_t key, unsigned int flags, + gpgme_data_t help_data, unsigned int extraflags, + gpgme_data_t pubkey, gpgme_data_t seckey) +{ + engine_gpg_t gpg = engine; + gpgme_error_t err; + + (void)reserved; + + if (!gpg) + return gpg_error (GPG_ERR_INV_VALUE); + + /* If HELP_DATA is given the use of the old interface + * (gpgme_op_genkey) has been requested. The other modes are: + * + * USERID && !KEY - Create a new keyblock. + * !USERID && KEY - Add a new subkey to KEY (gpg >= 2.1.14) + * USERID && KEY && !ALGO - Add a new user id to KEY (gpg >= 2.1.14). + * or set a flag on a user id. + */ + if (help_data) + { + /* We need a special mechanism to get the fd of a pipe here, so + that we can use this for the %pubring and %secring + parameters. We don't have this yet, so we implement only the + adding to the standard keyrings. */ + if (pubkey || seckey) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else + err = gpg_createkey_from_param (gpg, help_data, extraflags); + } + else if (!have_gpg_version (gpg, "2.1.13")) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else if (userid && !key) + err = gpg_createkey (gpg, userid, algo, expires, flags, extraflags); + else if (!userid && key) + err = gpg_addkey (gpg, algo, expires, key, flags, extraflags); + else if (userid && key && !algo) + err = gpg_adduid (gpg, key, userid, extraflags); + else + err = gpg_error (GPG_ERR_INV_VALUE); return err; } @@ -2071,6 +2553,7 @@ gpg_keylist_preprocess (char *line, char **r_line) #define NR_FIELDS 16 char *field[NR_FIELDS]; int fields = 0; + size_t n; *r_line = NULL; @@ -2106,16 +2589,34 @@ gpg_keylist_preprocess (char *line, char **r_line) pub:::::: as defined in 5.2. Machine Readable Indexes of the OpenPGP - HTTP Keyserver Protocol (draft). + HTTP Keyserver Protocol (draft). Modern versions of the SKS + keyserver return the fingerprint instead of the keyid. We + detect this here and use the v4 fingerprint format to convert + it to a key id. We want: pub:o::::::::::::: */ - if (asprintf (r_line, "pub:o%s:%s:%s:%s:%s:%s::::::::", - field[6], field[3], field[2], field[1], - field[4], field[5]) < 0) - return gpg_error_from_syserror (); + n = strlen (field[1]); + if (n > 16) + { + if (gpgrt_asprintf (r_line, + "pub:o%s:%s:%s:%s:%s:%s::::::::\n" + "fpr:::::::::%s:", + field[6], field[3], field[2], field[1] + n - 16, + field[4], field[5], field[1]) < 0) + return gpg_error_from_syserror (); + } + else + { + if (gpgrt_asprintf (r_line, + "pub:o%s:%s:%s:%s:%s:%s::::::::", + field[6], field[3], field[2], field[1], + field[4], field[5]) < 0) + return gpg_error_from_syserror (); + } + return 0; case RT_UID: @@ -2126,6 +2627,9 @@ gpg_keylist_preprocess (char *line, char **r_line) as defined in 5.2. Machine Readable Indexes of the OpenPGP HTTP Keyserver Protocol (draft). + For an ldap keyserver the format is: + uid: + We want: uid:o::::::::: */ @@ -2160,15 +2664,24 @@ gpg_keylist_preprocess (char *line, char **r_line) { *dst++ = '\\'; *dst++ = '\\'; + src++; } else *(dst++) = *(src++); } *dst = '\0'; - if (asprintf (r_line, "uid:o%s::::%s:%s:::%s:", - field[4], field[2], field[3], uid) < 0) - return gpg_error_from_syserror (); + if (fields < 4) + { + if (gpgrt_asprintf (r_line, "uid:o::::::::%s:", uid) < 0) + return gpg_error_from_syserror (); + } + else + { + if (gpgrt_asprintf (r_line, "uid:o%s::::%s:%s:::%s:", + field[4], field[2], field[3], uid) < 0) + return gpg_error_from_syserror (); + } } return 0; @@ -2188,12 +2701,26 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, gpg_error_t err; err = add_arg (gpg, "--with-colons"); - if (!err) - err = add_arg (gpg, "--fixed-list-mode"); - if (!err) - err = add_arg (gpg, "--with-fingerprint"); - if (!err) - err = add_arg (gpg, "--with-fingerprint"); + + /* Since gpg 2.1.15 fingerprints are always printed, thus there is + * no more need to explicitly request them. */ + if (!have_gpg_version (gpg, "2.1.15")) + { + if (!err) + err = add_arg (gpg, "--fixed-list-mode"); + if (!err) + err = add_arg (gpg, "--with-fingerprint"); + if (!err) + err = add_arg (gpg, "--with-fingerprint"); + } + + if (!err && (mode & GPGME_KEYLIST_MODE_WITH_TOFU) + && have_gpg_version (gpg, "2.1.16")) + err = add_arg (gpg, "--with-tofu-info"); + + if (!err && (mode & GPGME_KEYLIST_MODE_WITH_SECRET)) + err = add_arg (gpg, "--with-secret"); + if (!err && (mode & GPGME_KEYLIST_MODE_SIGS) && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) @@ -2202,6 +2729,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, if (!err) err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); } + if (!err) { if ( (mode & GPGME_KEYLIST_MODE_EXTERN) ) @@ -2233,6 +2761,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, ? "--check-sigs" : "--list-keys")); } } + if (!err) err = add_arg (gpg, "--"); @@ -2242,11 +2771,13 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, static gpgme_error_t gpg_keylist (void *engine, const char *pattern, int secret_only, - gpgme_keylist_mode_t mode) + gpgme_keylist_mode_t mode, int engine_flags) { engine_gpg_t gpg = engine; gpgme_error_t err; + (void)engine_flags; + err = gpg_keylist_build_options (gpg, secret_only, mode); if (!err && pattern && *pattern) @@ -2261,11 +2792,13 @@ gpg_keylist (void *engine, const char *pattern, int secret_only, static gpgme_error_t gpg_keylist_ext (void *engine, const char *pattern[], int secret_only, - int reserved, gpgme_keylist_mode_t mode) + int reserved, gpgme_keylist_mode_t mode, int engine_flags) { engine_gpg_t gpg = engine; gpgme_error_t err; + (void)engine_flags; + if (reserved) return gpg_error (GPG_ERR_INV_VALUE); @@ -2285,6 +2818,143 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only, static gpgme_error_t +gpg_keylist_data (void *engine, gpgme_data_t data) +{ + engine_gpg_t gpg = engine; + gpgme_error_t err; + + if (!have_gpg_version (gpg, "2.1.14")) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + err = add_arg (gpg, "--with-colons"); + if (!err) + err = add_arg (gpg, "--with-fingerprint"); + if (!err) + err = add_arg (gpg, "--import-options"); + if (!err) + err = add_arg (gpg, "import-show"); + if (!err) + err = add_arg (gpg, "--dry-run"); + if (!err) + err = add_arg (gpg, "--import"); + if (!err) + err = add_arg (gpg, "--"); + if (!err) + err = add_data (gpg, data, -1, 0); + + if (!err) + err = start (gpg); + + return err; +} + + +static gpgme_error_t +gpg_keysign (void *engine, gpgme_key_t key, const char *userid, + unsigned long expire, unsigned int flags, + gpgme_ctx_t ctx) +{ + engine_gpg_t gpg = engine; + gpgme_error_t err; + const char *s; + + if (!key || !key->fpr) + return gpg_error (GPG_ERR_INV_ARG); + + if (!have_gpg_version (gpg, "2.1.12")) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + if ((flags & GPGME_KEYSIGN_LOCAL)) + err = add_arg (gpg, "--quick-lsign-key"); + else + err = add_arg (gpg, "--quick-sign-key"); + + if (!err) + err = append_args_from_signers (gpg, ctx); + + /* If an expiration time has been given use that. If none has been + * given the default from gpg.conf is used. To make sure not to set + * an expiration time at all the flag GPGME_KEYSIGN_NOEXPIRE can be + * used. */ + if (!err && (expire || (flags & GPGME_KEYSIGN_NOEXPIRE))) + { + char tmpbuf[8+20]; + + if ((flags & GPGME_KEYSIGN_NOEXPIRE)) + expire = 0; + snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expire); + err = add_arg (gpg, "--default-cert-expire"); + if (!err) + err = add_arg (gpg, tmpbuf); + } + + if (!err) + err = add_arg (gpg, "--"); + + if (!err) + err = add_arg (gpg, key->fpr); + if (!err && userid) + { + if ((flags & GPGME_KEYSIGN_LFSEP)) + { + for (; !err && (s = strchr (userid, '\n')); userid = s + 1) + if ((s - userid)) + err = add_arg_len (gpg, "=", userid, s - userid); + if (!err && *userid) + err = add_arg_pfx (gpg, "=", userid); + } + else + err = add_arg_pfx (gpg, "=", userid); + } + + if (!err) + err = start (gpg); + + return err; +} + + +static gpgme_error_t +gpg_tofu_policy (void *engine, gpgme_key_t key, gpgme_tofu_policy_t policy) +{ + engine_gpg_t gpg = engine; + gpgme_error_t err; + const char *policystr = NULL; + + if (!key || !key->fpr) + return gpg_error (GPG_ERR_INV_ARG); + + switch (policy) + { + case GPGME_TOFU_POLICY_NONE: break; + case GPGME_TOFU_POLICY_AUTO: policystr = "auto"; break; + case GPGME_TOFU_POLICY_GOOD: policystr = "good"; break; + case GPGME_TOFU_POLICY_BAD: policystr = "bad"; break; + case GPGME_TOFU_POLICY_ASK: policystr = "ask"; break; + case GPGME_TOFU_POLICY_UNKNOWN: policystr = "unknown"; break; + } + if (!policystr) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!have_gpg_version (gpg, "2.1.10")) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + err = add_arg (gpg, "--tofu-policy"); + if (!err) + err = add_arg (gpg, "--"); + if (!err) + err = add_arg (gpg, policystr); + if (!err) + err = add_arg (gpg, key->fpr); + + if (!err) + err = start (gpg); + + return err; +} + + +static gpgme_error_t gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, gpgme_sig_mode_t mode, int use_armor, int use_textmode, int include_certs, gpgme_ctx_t ctx /* FIXME */) @@ -2292,6 +2962,8 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, engine_gpg_t gpg = engine; gpgme_error_t err; + (void)include_certs; + if (mode == GPGME_SIG_MODE_CLEAR) err = add_arg (gpg, "--clearsign"); else @@ -2301,13 +2973,21 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, err = add_arg (gpg, "--detach"); if (!err && use_armor) err = add_arg (gpg, "--armor"); - if (!err && use_textmode) - err = add_arg (gpg, "--textmode"); + if (!err) + { + if (gpgme_data_get_encoding (in) == GPGME_DATA_ENCODING_MIME + && have_gpg_version (gpg, "2.1.14")) + err = add_arg (gpg, "--mimemode"); + else if (use_textmode) + err = add_arg (gpg, "--textmode"); + } } if (!err) err = append_args_from_signers (gpg, ctx); if (!err) + err = append_args_from_sender (gpg, ctx); + if (!err) err = append_args_from_sig_notations (gpg, ctx); if (gpgme_data_get_file_name (in)) @@ -2320,6 +3000,8 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, /* Tell the gpg object about the data. */ if (!err) + err = add_input_size_hint (gpg, in); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, in, -1, 0); @@ -2327,7 +3009,7 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, err = add_data (gpg, out, 1, 1); if (!err) - start (gpg); + err = start (gpg); return err; } @@ -2357,19 +3039,26 @@ gpg_trustlist (void *engine, const char *pattern) static gpgme_error_t gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, - gpgme_data_t plaintext) + gpgme_data_t plaintext, gpgme_ctx_t ctx) { engine_gpg_t gpg = engine; - gpgme_error_t err = 0; + gpgme_error_t err; + + err = append_args_from_sender (gpg, ctx); + if (!err && ctx->auto_key_retrieve) + err = add_arg (gpg, "--auto-key-retrieve"); - if (plaintext) + if (err) + ; + else if (plaintext) { /* Normal or cleartext signature. */ - err = add_arg (gpg, "--output"); if (!err) err = add_arg (gpg, "-"); if (!err) + err = add_input_size_hint (gpg, sig); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, sig, -1, 0); @@ -2380,6 +3069,8 @@ gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, { err = add_arg (gpg, "--verify"); if (!err) + err = add_input_size_hint (gpg, signed_text); + if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, sig, -1, 0); @@ -2426,13 +3117,14 @@ struct engine_ops _gpgme_engine_ops_gpg = /* Member functions. */ gpg_release, NULL, /* reset */ + gpg_set_status_cb, gpg_set_status_handler, gpg_set_command_handler, gpg_set_colon_line_handler, gpg_set_locale, NULL, /* set_protocol */ + gpg_set_engine_flags, /* set_engine_flags */ gpg_decrypt, - gpg_decrypt, /* decrypt_verify */ gpg_delete, gpg_edit, gpg_encrypt, @@ -2443,6 +3135,9 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_import, gpg_keylist, gpg_keylist_ext, + gpg_keylist_data, + gpg_keysign, + gpg_tofu_policy, /* tofu_policy */ gpg_sign, gpg_trustlist, gpg_verify, @@ -2450,6 +3145,8 @@ struct engine_ops _gpgme_engine_ops_gpg = NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ + NULL, /* conf_dir */ + NULL, /* query_swdb */ gpg_set_io_cbs, gpg_io_event, gpg_cancel,