diff --git a/NEWS b/NEWS index 10296ff0..fd9b20c7 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,8 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_] gpgme_op_adduid NEW. gpgme_op_revuid_start NEW. gpgme_op_revuid NEW. + gpgme_op_keysign_start NEW. + gpgme_op_keysign NEW. gpgme_genkey_result_t EXTENDED: New fields pubkey and seckey. gpgme_signature_t EXTENDED: New field key. gpgme_key_t EXTENDED: New field fpr. @@ -50,6 +52,8 @@ Noteworthy changes in version 1.7.0 (unreleased) [C25/A14/R_] GPGME_CREATE_WANTPUB NEW. GPGME_CREATE_WANTSEC NEW. GPGME_CREATE_FORCE NEW. + GPGME_KEYSIGN_LOCAL NEW. + GPGME_KEYSIGN_LFSEP NEW. Noteworthy changes in version 1.6.0 (2015-08-26) [C25/A14/R0] diff --git a/src/Makefile.am b/src/Makefile.am index d541f87c..39752b30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,7 +84,7 @@ main_sources = \ op-support.c \ encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \ sign.c passphrase.c progress.c \ - key.c keylist.c trust-item.c trustlist.c \ + key.c keylist.c keysign.c trust-item.c trustlist.c \ import.c export.c genkey.c delete.c edit.c getauditlog.c \ opassuan.c passwd.c spawn.c assuan-support.c \ engine.h engine-backend.h engine.c engine-gpg.c status-table.c \ diff --git a/src/context.h b/src/context.h index de69a7a6..c099d668 100644 --- a/src/context.h +++ b/src/context.h @@ -38,7 +38,7 @@ typedef enum OPDATA_DECRYPT, OPDATA_SIGN, OPDATA_ENCRYPT, OPDATA_PASSPHRASE, OPDATA_IMPORT, OPDATA_GENKEY, OPDATA_KEYLIST, OPDATA_EDIT, OPDATA_VERIFY, OPDATA_TRUSTLIST, OPDATA_ASSUAN, OPDATA_VFS_MOUNT, - OPDATA_PASSWD, OPDATA_EXPORT + OPDATA_PASSWD, OPDATA_EXPORT, OPDATA_KEYSIGN } ctx_op_data_id_t; diff --git a/src/engine-assuan.c b/src/engine-assuan.c index 5f0ea06d..6f11cc02 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -775,6 +775,7 @@ struct engine_ops _gpgme_engine_ops_assuan = NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ + NULL, /* keysign */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ diff --git a/src/engine-backend.h b/src/engine-backend.h index ec1a58c5..ed3e3031 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -98,6 +98,10 @@ struct engine_ops int secret_only, int reserved, gpgme_keylist_mode_t mode, int engine_flags); + gpgme_error_t (*keysign) (void *engine, + gpgme_key_t key, const char *userid, + unsigned long expires, unsigned int flags, + gpgme_ctx_t ctx); gpgme_error_t (*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, diff --git a/src/engine-g13.c b/src/engine-g13.c index 7f225eb2..0da00f7b 100644 --- a/src/engine-g13.c +++ b/src/engine-g13.c @@ -792,6 +792,7 @@ struct engine_ops _gpgme_engine_ops_g13 = NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ + NULL, /* keysign */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 534d5d10..dc0906dd 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -206,14 +206,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 (); @@ -221,7 +223,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; @@ -243,24 +248,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); } @@ -1606,7 +1623,8 @@ 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; } @@ -2643,6 +2661,71 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only, } +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_sign (void *engine, gpgme_data_t in, gpgme_data_t out, gpgme_sig_mode_t mode, int use_armor, int use_textmode, @@ -2816,6 +2899,7 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_import, gpg_keylist, gpg_keylist_ext, + gpg_keysign, gpg_sign, gpg_trustlist, gpg_verify, diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c index 015ef001..8be76cb2 100644 --- a/src/engine-gpgconf.c +++ b/src/engine-gpgconf.c @@ -957,6 +957,7 @@ struct engine_ops _gpgme_engine_ops_gpgconf = NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ + NULL, /* keysign */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 071626a9..aae9d28d 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -2074,6 +2074,7 @@ struct engine_ops _gpgme_engine_ops_gpgsm = gpgsm_import, gpgsm_keylist, gpgsm_keylist_ext, + NULL, /* keysign */ gpgsm_sign, NULL, /* trustlist */ gpgsm_verify, diff --git a/src/engine-spawn.c b/src/engine-spawn.c index e2ee8ba6..82dbc0bf 100644 --- a/src/engine-spawn.c +++ b/src/engine-spawn.c @@ -460,6 +460,7 @@ struct engine_ops _gpgme_engine_ops_spawn = NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ + NULL, /* keysign */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index 2817527f..827c3475 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -1364,6 +1364,7 @@ struct engine_ops _gpgme_engine_ops_uiserver = NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ + NULL, /* keysign */ uiserver_sign, NULL, /* trustlist */ uiserver_verify, diff --git a/src/engine.c b/src/engine.c index b3bbcab7..47bb23ce 100644 --- a/src/engine.c +++ b/src/engine.c @@ -794,6 +794,22 @@ _gpgme_engine_op_genkey (engine_t engine, } +gpgme_error_t +_gpgme_engine_op_keysign (engine_t engine, gpgme_key_t key, const char *userid, + unsigned long expires, unsigned int flags, + gpgme_ctx_t ctx) +{ + if (!engine) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!engine->ops->keysign) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return (*engine->ops->keysign) (engine->engine, + key, userid, expires, flags, ctx); +} + + gpgme_error_t _gpgme_engine_op_import (engine_t engine, gpgme_data_t keydata, gpgme_key_t *keyarray) diff --git a/src/engine.h b/src/engine.h index eb37da8f..6f338351 100644 --- a/src/engine.h +++ b/src/engine.h @@ -121,6 +121,11 @@ gpgme_error_t _gpgme_engine_op_genkey (engine_t engine, unsigned int extraflags, gpgme_data_t pubkey, gpgme_data_t seckey); +gpgme_error_t _gpgme_engine_op_keysign (engine_t engine, + gpgme_key_t key, const char *userid, + unsigned long expires, + unsigned int flags, + gpgme_ctx_t ctx); gpgme_error_t _gpgme_engine_op_import (engine_t engine, gpgme_data_t keydata, gpgme_key_t *keyarray); diff --git a/src/gpgme.def b/src/gpgme.def index 4c7ff414..f987b38d 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -237,6 +237,8 @@ EXPORTS gpgme_op_adduid @177 gpgme_op_revuid_start @178 gpgme_op_revuid @179 + gpgme_op_keysign_start @180 + gpgme_op_keysign @181 ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index a26b4324..121e2ce1 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -1921,10 +1921,32 @@ gpgme_error_t gpgme_op_delete_start (gpgme_ctx_t ctx, const gpgme_key_t key, gpgme_error_t gpgme_op_delete (gpgme_ctx_t ctx, const gpgme_key_t key, int allow_secret); + +/* + * Key signing interface + */ + +/* Flags for the key signing functions. */ +#define GPGME_KEYSIGN_LOCAL (1 << 7) /* Create a local signature. */ +#define GPGME_KEYSIGN_LFSEP (1 << 8) /* Indicate LF separated user ids. */ +#define GPGME_KEYSIGN_NOEXPIRE (1 << 9) /* Force no expiration. */ + + +/* Sign the USERID of KEY using the current set of signers. */ +gpgme_error_t gpgme_op_keysign_start (gpgme_ctx_t ctx, + gpgme_key_t key, const char *userid, + unsigned long expires, + unsigned int flags); +gpgme_error_t gpgme_op_keysign (gpgme_ctx_t ctx, + gpgme_key_t key, const char *userid, + unsigned long expires, + unsigned int flags); + + /* - * Key Edit interface + * Key edit interface */ /* Edit the key KEY. Send status and command requests to FNC and diff --git a/src/keysign.c b/src/keysign.c new file mode 100644 index 00000000..7d08c11e --- /dev/null +++ b/src/keysign.c @@ -0,0 +1,218 @@ +/* keysign.c - OpenPGP key signing + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME 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. + * + * GPGME 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 . + */ + +#if HAVE_CONFIG_H +#include +#endif +#include +#include +#include + +#include "gpgme.h" +#include "debug.h" +#include "context.h" +#include "ops.h" +#include "util.h" + + +typedef struct +{ + /* The error code from a FAILURE status line or 0. */ + gpg_error_t failure_code; + + /* The error code from certain ERROR status lines or 0. */ + gpg_error_t error_code; + +} *op_data_t; + + +static void +release_op_data (void *hook) +{ + op_data_t opd = (op_data_t) hook; + + (void)opd; +} + + +/* Parse an error status line. Return the error location and the + error code. The function may modify ARGS. */ +static char * +parse_error (char *args, gpg_error_t *r_err) +{ + char *where = strchr (args, ' '); + char *which; + + if (where) + { + *where = '\0'; + which = where + 1; + + where = strchr (which, ' '); + if (where) + *where = '\0'; + + where = args; + } + else + { + *r_err = trace_gpg_error (GPG_ERR_INV_ENGINE); + return NULL; + } + + *r_err = atoi (which); + + return where; +} + + +static gpgme_error_t +keysign_status_handler (void *priv, gpgme_status_code_t code, char *args) +{ + gpgme_ctx_t ctx = (gpgme_ctx_t) priv; + gpgme_error_t err; + void *hook; + op_data_t opd; + char *loc; + + /* Pipe the status code through the progress status handler. */ + err = _gpgme_progress_status_handler (ctx, code, args); + if (err) + return err; + + err = _gpgme_op_data_lookup (ctx, OPDATA_KEYSIGN, &hook, -1, NULL); + opd = hook; + if (err) + return err; + + switch (code) + { + case GPGME_STATUS_ERROR: + loc = parse_error (args, &err); + if (!loc) + return err; + if (!opd->error_code) + opd->error_code = err; + break; + + case GPGME_STATUS_FAILURE: + opd->failure_code = _gpgme_parse_failure (args); + break; + + case GPGME_STATUS_EOF: + if (opd->error_code) + return opd->error_code; + else if (opd->failure_code) + return opd->failure_code; + break; + + case GPGME_STATUS_INQUIRE_MAXLEN: + if (ctx->status_cb && !ctx->full_status) + { + err = ctx->status_cb (ctx->status_cb_value, "INQUIRE_MAXLEN", args); + if (err) + return err; + } + break; + + default: + break; + } + return 0; +} + + +/* Sign the USERID of KEY using the current set of signers. If USERID + * is NULL, sign all user ids. To put several user ids into USERID, + * separate them by LF and set the flag GPGME_KEYSIGN_LFSEP. */ +static gpgme_error_t +keysign_start (gpgme_ctx_t ctx, int synchronous, + gpgme_key_t key, const char *userid, + unsigned long expires, unsigned int flags) +{ + gpgme_error_t err; + void *hook; + op_data_t opd; + + if (ctx->protocol != GPGME_PROTOCOL_OPENPGP) + return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); + + err = _gpgme_op_reset (ctx, synchronous); + if (err) + return err; + + if (!key) + return gpg_error (GPG_ERR_INV_ARG); + + err = _gpgme_op_data_lookup (ctx, OPDATA_KEYSIGN, &hook, + sizeof (*opd), release_op_data); + opd = hook; + if (err) + return err; + + _gpgme_engine_set_status_handler (ctx->engine, keysign_status_handler, ctx); + + if (ctx->passphrase_cb) + { + err = _gpgme_engine_set_command_handler + (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); + if (err) + return err; + } + + return _gpgme_engine_op_keysign (ctx->engine, + key, userid, expires, flags, ctx); +} + + +/* Sign the USERID of KEY using the current set of signers. */ +gpgme_error_t +gpgme_op_keysign_start (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, + unsigned long expires, unsigned int flags) +{ + gpgme_error_t err; + + TRACE_BEG3 (DEBUG_CTX, "gpgme_op_keysign_start", ctx, + "key=%p, uid='%s' flags=0x%x", key, userid, flags); + + if (!ctx) + return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); + + err = keysign_start (ctx, 0, key, userid, expires, flags); + return TRACE_ERR (err); +} + + +gpgme_error_t +gpgme_op_keysign (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, + unsigned long expires, unsigned int flags) +{ + gpgme_error_t err; + + TRACE_BEG3 (DEBUG_CTX, "gpgme_op_keysign", ctx, + "key=%p, uid='%s' flags=0x%x", key, userid, flags); + + if (!ctx) + return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); + + err = keysign_start (ctx, 1, key, userid, expires, flags); + if (!err) + err = _gpgme_wait_one (ctx); + return TRACE_ERR (err); +} diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 8193ee4d..d86eee8f 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -111,6 +111,8 @@ GPGME_1.1 { gpgme_op_adduid; gpgme_op_revuid_start; gpgme_op_revuid; + gpgme_op_keysign_start; + gpgme_op_keysign; }; diff --git a/tests/Makefile.am b/tests/Makefile.am index f3a16045..1370efdc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -32,7 +32,8 @@ LDADD = ../src/libgpgme.la @GPG_ERROR_LIBS@ noinst_HEADERS = run-support.h noinst_PROGRAMS = $(TESTS) run-keylist run-export run-import run-sign \ - run-verify run-encrypt run-identify run-decrypt run-genkey + run-verify run-encrypt run-identify run-decrypt run-genkey \ + run-keysign if RUN_GPG_TESTS diff --git a/tests/run-keysign.c b/tests/run-keysign.c new file mode 100644 index 00000000..f5a13e42 --- /dev/null +++ b/tests/run-keysign.c @@ -0,0 +1,261 @@ +/* run-keysign.c - Test tool to sign a key + * Copyright (C) 2016 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME 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. + * + * GPGME 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 . + */ + +/* We need to include config.h so that we know whether we are building + with large file system (LFS) support. */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +#define PGM "run-keysign" + +#include "run-support.h" + + +static int verbose; + + +static gpg_error_t +status_cb (void *opaque, const char *keyword, const char *value) +{ + (void)opaque; + fprintf (stderr, "status_cb: %s %s\n", nonnull(keyword), nonnull(value)); + return 0; +} + + +static unsigned long +parse_expire_string (const char *string) +{ + unsigned long seconds; + + if (!string || !*string || !strcmp (string, "none") + || !strcmp (string, "never") || !strcmp (string, "-")) + seconds = 0; + else if (strspn (string, "01234567890") == strlen (string)) + seconds = strtoul (string, NULL, 10); + else + { + fprintf (stderr, PGM ": invalid value '%s'\n", string); + exit (1); + } + + return seconds; +} + + + +static int +show_usage (int ex) +{ + fputs ("usage: " PGM " [options] FPR USERIDS\n\n" + "Options:\n" + " --verbose run in verbose mode\n" + " --status print status lines from the backend\n" + " --loopback use a loopback pinentry\n" + " --signer NAME use key NAME for signing\n" + " --local create a local signature\n" + " --noexpire force no expiration\n" + " --expire EPOCH expire the signature at EPOCH\n" + , stderr); + exit (ex); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + gpgme_error_t err; + gpgme_ctx_t ctx; + gpgme_protocol_t protocol = GPGME_PROTOCOL_OpenPGP; + const char *signer_string = NULL; + int print_status = 0; + int use_loopback = 0; + const char *userid; + unsigned int flags = 0; + unsigned long expire = 0; + gpgme_key_t thekey; + int i; + size_t n; + char *userid_buffer = NULL; + + if (argc) + { argc--; argv++; } + + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + show_usage (0); + else if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--status")) + { + print_status = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--signer")) + { + argc--; argv++; + if (!argc) + show_usage (1); + signer_string = *argv; + argc--; argv++; + } + else if (!strcmp (*argv, "--loopback")) + { + use_loopback = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--local")) + { + flags |= GPGME_KEYSIGN_LOCAL; + argc--; argv++; + } + else if (!strcmp (*argv, "--noexpire")) + { + flags |= GPGME_KEYSIGN_NOEXPIRE; + argc--; argv++; + } + else if (!strcmp (*argv, "--expire")) + { + argc--; argv++; + if (!argc) + show_usage (1); + expire = parse_expire_string (*argv); + argc--; argv++; + } + else if (!strncmp (*argv, "--", 2)) + show_usage (1); + } + + if (!argc) + show_usage (1); + userid = argv[0]; + argc--; argv++; + + init_gpgme (protocol); + + err = gpgme_new (&ctx); + fail_if_err (err); + gpgme_set_protocol (ctx, protocol); + gpgme_set_armor (ctx, 1); + if (print_status) + { + gpgme_set_status_cb (ctx, status_cb, NULL); + gpgme_set_ctx_flag (ctx, "full-status", "1"); + } + if (use_loopback) + { + gpgme_set_pinentry_mode (ctx, GPGME_PINENTRY_MODE_LOOPBACK); + gpgme_set_passphrase_cb (ctx, passphrase_cb, NULL); + } + + if (signer_string) + { + gpgme_key_t akey; + + err = gpgme_get_key (ctx, signer_string, &akey, 1); + if (err) + { + fprintf (stderr, PGM ": error getting signer key '%s': %s\n", + signer_string, gpg_strerror (err)); + exit (1); + } + err = gpgme_signers_add (ctx, akey); + if (err) + { + fprintf (stderr, PGM ": error adding signer key: %s\n", + gpg_strerror (err)); + exit (1); + } + gpgme_key_unref (akey); + } + + + err = gpgme_get_key (ctx, userid, &thekey, 0); + if (err) + { + fprintf (stderr, PGM ": error getting key for '%s': %s\n", + userid, gpg_strerror (err)); + exit (1); + } + + if (argc > 1) + { + /* Several user ids given */ + for (i=0, n = 0; i < argc; i++) + n += strlen (argv[1]) + 1; + n++; + userid_buffer = malloc (n); + if (!userid_buffer) + { + fprintf (stderr, PGM ": malloc failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + exit (1); + } + *userid_buffer = 0; + for (i=0; i < argc; i++) + { + strcat (userid_buffer, argv[i]); + strcat (userid_buffer, "\n"); + } + userid = userid_buffer; + flags |= GPGME_KEYSIGN_LFSEP; + } + else if (argc) + { + /* One user id given */ + userid = *argv; + } + else + { + /* No user id given. */ + userid = NULL; + } + + err = gpgme_op_keysign (ctx, thekey, userid, expire, flags); + if (err) + { + fprintf (stderr, PGM ": gpgme_op_adduid failed: %s\n", + gpg_strerror (err)); + exit (1); + } + + free (userid_buffer); + gpgme_key_unref (thekey); + gpgme_release (ctx); + return 0; +}