X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Fgpgme-tool.c;h=19dedb6f7447a48ce1b135a8031503d6d3de671d;hb=dda3702a9024a08da7bb949e15b63a47d23d59f5;hp=4ed16cc2b46eb7b602286b4ccfd66e9e3ed48d78;hpb=ef640ca8efea293aa725f92fcc33a7b151b2627e;p=gpgme.git diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 4ed16cc..19dedb6 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -1,23 +1,21 @@ -/* gpgme-tool.c - GnuPG Made Easy. - Copyright (C) 2000 Werner Koch (dd9jn) - Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH +/* gpgme-tool.c - Assuan server exposing GnuPG Made Easy operations. + Copyright (C) 2009, 2010 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, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - 02111-1307, USA. */ + License along with this program; if not, see . + */ #if HAVE_CONFIG_H #include @@ -30,7 +28,9 @@ #include #include #include +#ifdef HAVE_LOCALE_H #include +#endif #ifdef HAVE_ARGP_H #include #endif @@ -39,6 +39,25 @@ #include "gpgme.h" +/* GCC attributes. */ +#if __GNUC__ >= 4 +# define GT_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a))) +#else +# define GT_GCC_A_SENTINEL(a) +#endif + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) +# define GT_GCC_A_PRINTF(f, a) __attribute__ ((format (printf,f,a))) +#else +# define GT_GCC_A_PRINTF(f, a) +#endif + +#define DIM(v) (sizeof(v)/sizeof((v)[0])) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + #ifndef HAVE_ARGP_H /* Minimal argp implementation. */ @@ -94,7 +113,11 @@ struct argp_state void *pstate; }; -#define ARGP_ERR_UNKNOWN E2BIG +#ifdef EDEADLK +# define ARGP_ERR_UNKNOWN EDEADLK /* POSIX */ +#else +# define ARGP_ERR_UNKNOWN EDEADLOCK /* *GNU/kFreebsd does not define this) */ +#endif #define ARGP_KEY_ARG 0 #define ARGP_KEY_ARGS 0x1000006 #define ARGP_KEY_END 0x1000001 @@ -135,6 +158,11 @@ struct argp | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR) +void argp_error (const struct argp_state *state, + const char *fmt, ...) GT_GCC_A_PRINTF(2, 3); + + + char * _argp_pname (char *name) { @@ -346,10 +374,10 @@ argp_parse (const struct argp *argp, int argc, *arg = '\0'; arg++; } - + if (state.argv[idx][1] != '-') key = state.argv[idx][1]; - + while (! found && opt->key) { if (key == opt->key @@ -397,7 +425,7 @@ argp_parse (const struct argp *argp, int argc, rc = argp->parser (ARGP_KEY_ARGS, NULL, &state); if (rc == ARGP_ERR_UNKNOWN) { - argp_error (&state, "Too many arguments", state.argv[idx]); + argp_error (&state, "Too many arguments"); goto argperror; } if (! rc && state.next == old_next) @@ -425,7 +453,7 @@ argp_parse (const struct argp *argp, int argc, rc = argp->parser (ARGP_KEY_FINI, NULL, &state); if (rc && rc != ARGP_ERR_UNKNOWN) goto argperror; - + rc = 0; argp->parser (ARGP_KEY_SUCCESS, NULL, &state); @@ -450,6 +478,13 @@ argp_parse (const struct argp *argp, int argc, FILE *log_stream; char *program_name = "gpgme-tool"; +#define spacep(p) (*(p) == ' ' || *(p) == '\t') + + +void log_error (int status, gpg_error_t errnum, + const char *fmt, ...) GT_GCC_A_PRINTF(3,4); + + void log_init (void) { @@ -475,6 +510,662 @@ log_error (int status, gpg_error_t errnum, const char *fmt, ...) } +/* Note that it is sufficient to allocate the target string D as long + as the source string S, i.e.: strlen(s)+1;. D == S is allowed. */ +static void +strcpy_escaped_plus (char *d, const char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 (s); + s += 2; + } + else if (*s == '+') + *d++ = ' ', s++; + else + *d++ = *s++; + } + *d = 0; +} + + +/* Check whether the option NAME appears in LINE. */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + +/* Skip over options. It is assumed that leading spaces have been + removed (this is the case for lines passed to a handler from + assuan). Blanks after the options are also removed. */ +static char * +skip_options (char *line) +{ + while ( *line == '-' && line[1] == '-' ) + { + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + } + return line; +} + + + + +typedef gpg_error_t (*result_xml_write_cb_t) (void *hook, const void *buf, + size_t len); + +struct result_xml_state +{ + int indent; + result_xml_write_cb_t cb; + void *hook; + +#define MAX_TAGS 20 + int next_tag; + char *tag[MAX_TAGS]; + int had_data[MAX_TAGS]; +}; + + +void +result_init (struct result_xml_state *state, int indent, + result_xml_write_cb_t cb, void *hook) +{ + memset (state, '\0', sizeof (*state)); + state->indent = indent; + state->cb = cb; + state->hook = hook; +} + + +gpg_error_t +result_xml_indent (struct result_xml_state *state) +{ + char spaces[state->indent + 1]; + int i; + for (i = 0; i < state->indent; i++) + spaces[i] = ' '; + spaces[i] = '\0'; + return (*state->cb) (state->hook, spaces, i); +} + + +gpg_error_t +result_xml_tag_start (struct result_xml_state *state, char *name, ...) +{ + result_xml_write_cb_t cb = state->cb; + void *hook = state->hook; + va_list ap; + char *attr; + char *attr_val; + + va_start (ap, name); + + if (state->next_tag > 0) + { + if (! state->had_data[state->next_tag - 1]) + { + (*cb) (hook, ">\n", 2); + (*cb) (hook, NULL, 0); + } + state->had_data[state->next_tag - 1] = 1; + } + + result_xml_indent (state); + (*cb) (hook, "<", 1); + (*cb) (hook, name, strlen (name)); + + state->tag[state->next_tag] = name; + state->had_data[state->next_tag] = 0; + state->indent += 2; + state->next_tag++; + + while (1) + { + attr = va_arg (ap, char *); + if (attr == NULL) + break; + + attr_val = va_arg (ap, char *); + if (attr_val == NULL) + attr_val = "(null)"; + + (*cb) (hook, " ", 1); + (*cb) (hook, attr, strlen (attr)); + (*cb) (hook, "=\"", 2); + (*cb) (hook, attr_val, strlen (attr_val)); + (*cb) (hook, "\"", 1); + } + va_end (ap); + return 0; +} + + +gpg_error_t +result_xml_tag_data (struct result_xml_state *state, char *data) +{ + result_xml_write_cb_t cb = state->cb; + void *hook = state->hook; + + if (state->had_data[state->next_tag - 1]) + { + (*cb) (hook, "\n", 2); + (*cb) (hook, NULL, 0); + result_xml_indent (state); + } + else + (*cb) (hook, ">", 1); + state->had_data[state->next_tag - 1] = 2; + + (*cb) (hook, data, strlen (data)); + + return 0; +} + + +gpg_error_t +result_xml_tag_end (struct result_xml_state *state) +{ + result_xml_write_cb_t cb = state->cb; + void *hook = state->hook; + + state->next_tag--; + state->indent -= 2; + + if (state->had_data[state->next_tag]) + { + if (state->had_data[state->next_tag] == 1) + result_xml_indent (state); + (*cb) (hook, "tag[state->next_tag], + strlen (state->tag[state->next_tag])); + (*cb) (hook, ">\n", 2); + (*cb) (hook, NULL, 0); + } + else + { + (*cb) (hook, " />\n", 4); + (*cb) (hook, NULL, 0); + } + return 0; +} + + +gpg_error_t +result_add_error (struct result_xml_state *state, char *name, gpg_error_t err) +{ + char code[20]; + char msg[1024]; + snprintf (code, sizeof (code) - 1, "0x%x", err); + snprintf (msg, sizeof (msg) - 1, "%s <%s>", + gpg_strerror (err), gpg_strsource (err)); + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_data (state, msg); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_pubkey_algo (struct result_xml_state *state, + char *name, gpgme_pubkey_algo_t algo) +{ + char code[20]; + char msg[80]; + snprintf (code, sizeof (code) - 1, "0x%x", algo); + snprintf (msg, sizeof (msg) - 1, "%s", + gpgme_pubkey_algo_name (algo)); + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_data (state, msg); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_hash_algo (struct result_xml_state *state, + char *name, gpgme_hash_algo_t algo) +{ + char code[20]; + char msg[80]; + snprintf (code, sizeof (code) - 1, "0x%x", algo); + snprintf (msg, sizeof (msg) - 1, "%s", + gpgme_hash_algo_name (algo)); + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_data (state, msg); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_keyid (struct result_xml_state *state, char *name, char *keyid) +{ + result_xml_tag_start (state, name, NULL); + result_xml_tag_data (state, keyid); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_fpr (struct result_xml_state *state, char *name, char *fpr) +{ + result_xml_tag_start (state, name, NULL); + result_xml_tag_data (state, fpr); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_timestamp (struct result_xml_state *state, char *name, + unsigned int timestamp) +{ + char code[20]; + + snprintf (code, sizeof (code) - 1, "%ui", timestamp); + result_xml_tag_start (state, name, "unix", code, NULL); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_sig_mode (struct result_xml_state *state, char *name, + gpgme_sig_mode_t sig_mode) +{ + char *mode; + char code[20]; + + snprintf (code, sizeof (code) - 1, "%i", sig_mode); + switch (sig_mode) + { + case GPGME_SIG_MODE_NORMAL: + mode = "normal"; + break; + case GPGME_SIG_MODE_DETACH: + mode = "detach"; + break; + case GPGME_SIG_MODE_CLEAR: + mode = "clear"; + break; + default: + mode = "unknown"; + } + + result_xml_tag_start (state, name, "type", mode, "value", code, NULL); + result_xml_tag_data (state, mode); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_value (struct result_xml_state *state, + char *name, unsigned int val) +{ + char code[20]; + + snprintf (code, sizeof (code) - 1, "0x%x", val); + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_string (struct result_xml_state *state, + char *name, char *str) +{ + result_xml_tag_start (state, name, NULL); + result_xml_tag_data (state, str); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_encrypt_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_encrypt_result_t res = gpgme_op_encrypt_result (ctx); + gpgme_invalid_key_t inv_recp; + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "encrypt-result", NULL); + + inv_recp = res->invalid_recipients; + if (inv_recp) + { + result_xml_tag_start (&state, "invalid-recipients", NULL); + + while (inv_recp) + { + result_xml_tag_start (&state, "invalid-key", NULL); + if (inv_recp->fpr) + result_add_fpr (&state, "fpr", inv_recp->fpr); + result_add_error (&state, "reason", inv_recp->reason); + result_xml_tag_end (&state); + inv_recp = inv_recp->next; + } + result_xml_tag_end (&state); + } + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_decrypt_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_decrypt_result_t res = gpgme_op_decrypt_result (ctx); + gpgme_recipient_t recp; + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "decrypt-result", NULL); + + if (res->file_name) + { + result_xml_tag_start (&state, "file-name", NULL); + result_xml_tag_data (&state, res->file_name); + result_xml_tag_end (&state); + } + if (res->unsupported_algorithm) + { + result_xml_tag_start (&state, "unsupported-alogorithm", NULL); + result_xml_tag_data (&state, res->unsupported_algorithm); + result_xml_tag_end (&state); + } + if (res->wrong_key_usage) + { + result_xml_tag_start (&state, "wrong-key-usage", NULL); + result_xml_tag_end (&state); + } + + recp = res->recipients; + if (recp) + { + result_xml_tag_start (&state, "recipients", NULL); + while (recp) + { + result_xml_tag_start (&state, "recipient", NULL); + result_add_keyid (&state, "keyid", recp->keyid); + result_add_pubkey_algo (&state, "pubkey-algo", recp->pubkey_algo); + result_add_error (&state, "status", recp->status); + result_xml_tag_end (&state); + recp = recp->next; + } + result_xml_tag_end (&state); + } + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_sign_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_sign_result_t res = gpgme_op_sign_result (ctx); + gpgme_invalid_key_t inv_key; + gpgme_new_signature_t new_sig; + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "sign-result", NULL); + + inv_key = res->invalid_signers; + if (inv_key) + { + result_xml_tag_start (&state, "invalid-signers", NULL); + + while (inv_key) + { + result_xml_tag_start (&state, "invalid-key", NULL); + if (inv_key->fpr) + result_add_fpr (&state, "fpr", inv_key->fpr); + result_add_error (&state, "reason", inv_key->reason); + result_xml_tag_end (&state); + inv_key = inv_key->next; + } + result_xml_tag_end (&state); + } + + new_sig = res->signatures; + if (new_sig) + { + result_xml_tag_start (&state, "signatures", NULL); + + while (new_sig) + { + result_xml_tag_start (&state, "new-signature", NULL); + result_add_sig_mode (&state, "type", new_sig->type); + result_add_pubkey_algo (&state, "pubkey-algo", new_sig->pubkey_algo); + result_add_hash_algo (&state, "hash-algo", new_sig->hash_algo); + result_add_timestamp (&state, "timestamp", new_sig->timestamp); + if (new_sig->fpr) + result_add_fpr (&state, "fpr", new_sig->fpr); + result_add_value (&state, "sig-class", new_sig->sig_class); + + result_xml_tag_end (&state); + new_sig = new_sig->next; + } + result_xml_tag_end (&state); + } + + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_verify_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_verify_result_t res = gpgme_op_verify_result (ctx); + gpgme_signature_t sig; + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "verify-result", NULL); + + if (res->file_name) + { + result_xml_tag_start (&state, "file-name", NULL); + result_xml_tag_data (&state, res->file_name); + result_xml_tag_end (&state); + } + + sig = res->signatures; + if (sig) + { + result_xml_tag_start (&state, "signatures", NULL); + + while (sig) + { + result_xml_tag_start (&state, "signature", NULL); + + /* FIXME: Could be done better. */ + result_add_value (&state, "summary", sig->summary); + if (sig->fpr) + result_add_fpr (&state, "fpr", sig->fpr); + result_add_error (&state, "status", sig->status); + /* FIXME: notations */ + result_add_timestamp (&state, "timestamp", sig->timestamp); + result_add_timestamp (&state, "exp-timestamp", sig->exp_timestamp); + result_add_value (&state, "wrong-key-usage", sig->wrong_key_usage); + result_add_value (&state, "pka-trust", sig->pka_trust); + result_add_value (&state, "chain-model", sig->chain_model); + result_add_value (&state, "validity", sig->validity); + result_add_error (&state, "validity-reason", sig->validity_reason); + result_add_pubkey_algo (&state, "pubkey-algo", sig->pubkey_algo); + result_add_hash_algo (&state, "hash-algo", sig->hash_algo); + if (sig->pka_address) + result_add_string (&state, "pka_address", sig->pka_address); + + result_xml_tag_end (&state); + sig = sig->next; + } + result_xml_tag_end (&state); + } + + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_import_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_import_result_t res = gpgme_op_import_result (ctx); + gpgme_import_status_t stat; + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "import-result", NULL); + + result_add_value (&state, "considered", res->considered); + result_add_value (&state, "no-user-id", res->no_user_id); + result_add_value (&state, "imported", res->imported); + result_add_value (&state, "imported-rsa", res->imported_rsa); + result_add_value (&state, "unchanged", res->unchanged); + result_add_value (&state, "new-user-ids", res->new_user_ids); + result_add_value (&state, "new-sub-keys", res->new_sub_keys); + result_add_value (&state, "new-signatures", res->new_signatures); + result_add_value (&state, "new-revocations", res->new_revocations); + result_add_value (&state, "secret-read", res->secret_read); + result_add_value (&state, "secret-imported", res->secret_imported); + result_add_value (&state, "secret-unchanged", res->secret_unchanged); + result_add_value (&state, "skipped-new-keys", res->skipped_new_keys); + result_add_value (&state, "not-imported", res->not_imported); + + stat = res->imports; + if (stat) + { + result_xml_tag_start (&state, "imports", NULL); + + while (stat) + { + result_xml_tag_start (&state, "import-status", NULL); + + if (stat->fpr) + result_add_fpr (&state, "fpr", stat->fpr); + result_add_error (&state, "result", stat->result); + /* FIXME: Could be done better. */ + result_add_value (&state, "status", stat->status); + + result_xml_tag_end (&state); + stat = stat->next; + } + result_xml_tag_end (&state); + } + + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_genkey_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_genkey_result_t res = gpgme_op_genkey_result (ctx); + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "genkey-result", NULL); + + result_add_value (&state, "primary", res->primary); + result_add_value (&state, "sub", res->sub); + if (res->fpr) + result_add_fpr (&state, "fpr", res->fpr); + + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_keylist_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_keylist_result_t res = gpgme_op_keylist_result (ctx); + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "keylist-result", NULL); + + result_add_value (&state, "truncated", res->truncated); + + result_xml_tag_end (&state); + + return 0; +} + + +gpg_error_t +result_vfs_mount_to_xml (gpgme_ctx_t ctx, int indent, + result_xml_write_cb_t cb, void *hook) +{ + struct result_xml_state state; + gpgme_vfs_mount_result_t res = gpgme_op_vfs_mount_result (ctx); + + if (! res) + return 0; + + result_init (&state, indent, cb, hook); + result_xml_tag_start (&state, "vfs-mount-result", NULL); + + result_add_string (&state, "mount-dir", res->mount_dir); + + result_xml_tag_end (&state); + + return 0; +} + typedef enum status { @@ -518,7 +1209,8 @@ typedef struct gpgme_tool *gpgme_tool_t; /* Forward declaration. */ -void gt_write_status (gpgme_tool_t gt, status_t status, ...); +void gt_write_status (gpgme_tool_t gt, + status_t status, ...) GT_GCC_A_SENTINEL(0); void _gt_progress_cb (void *opaque, const char *what, @@ -528,7 +1220,7 @@ _gt_progress_cb (void *opaque, const char *what, char buf[100]; snprintf (buf, sizeof (buf), "0x%02x %i %i", type, current, total); - gt_write_status (gt, STATUS_PROGRESS, what, buf); + gt_write_status (gt, STATUS_PROGRESS, what, buf, NULL); } @@ -540,8 +1232,8 @@ _gt_gpgme_new (gpgme_tool_t gt, gpgme_ctx_t *ctx) err = gpgme_new (ctx); if (err) return err; - gpgme_set_progress_cb (*ctx, _gt_progress_cb, gt); - return 0; + gpgme_set_progress_cb (*ctx, _gt_progress_cb, gt); + return 0; } @@ -589,7 +1281,7 @@ gt_get_key (gpgme_tool_t gt, const char *pattern, gpgme_key_t *r_key) if (!gt || !r_key || !pattern) return gpg_error (GPG_ERR_INV_VALUE); - + ctx = gt->ctx; err = gpgme_new (&listctx); @@ -651,10 +1343,10 @@ gt_get_key (gpgme_tool_t gt, const char *pattern, gpgme_key_t *r_key) } } gpgme_release (listctx); - + if (! err) - gt_write_status (gt, STATUS_RECIPIENT, - ((*r_key)->subkeys && (*r_key)->subkeys->fpr) ? + gt_write_status (gt, STATUS_RECIPIENT, + ((*r_key)->subkeys && (*r_key)->subkeys->fpr) ? (*r_key)->subkeys->fpr : "invalid", NULL); return err; } @@ -698,7 +1390,7 @@ gt_reset (gpgme_tool_t gt) { gpg_error_t err; gpgme_ctx_t ctx; - + err = _gt_gpgme_new (gt, &ctx); if (err) return err; @@ -746,7 +1438,7 @@ gt_write_status (gpgme_tool_t gt, status_t status, ...) gpg_error_t -gt_write_data (gpgme_tool_t gt, void *buf, size_t len) +gt_write_data (gpgme_tool_t gt, const void *buf, size_t len) { return gt->write_data (gt->write_data_hook, buf, len); } @@ -763,7 +1455,7 @@ gt_get_engine_info (gpgme_tool_t gt, gpgme_protocol_t proto) gt_write_status (gt, STATUS_ENGINE, gpgme_get_protocol_name (info->protocol), info->file_name, info->version, - info->req_version, info->home_dir); + info->req_version, info->home_dir, NULL); info = info->next; } return 0; @@ -790,7 +1482,7 @@ gt_protocol_from_name (const char *name) return GPGME_PROTOCOL_UNKNOWN; } - + gpg_error_t gt_set_protocol (gpgme_tool_t gt, gpgme_protocol_t proto) { @@ -880,7 +1572,7 @@ gt_get_keylist_mode (gpgme_tool_t gt) const char *modes[NR_KEYLIST_MODES + 1]; int idx = 0; gpgme_keylist_mode_t mode = gpgme_get_keylist_mode (gt->ctx); - + if (mode & GPGME_KEYLIST_MODE_LOCAL) modes[idx++] = "local"; if (mode & GPGME_KEYLIST_MODE_EXTERN) @@ -896,7 +1588,7 @@ gt_get_keylist_mode (gpgme_tool_t gt) modes[idx++] = NULL; gt_write_status (gt, STATUS_KEYLIST_MODE, modes[0], modes[1], modes[2], - modes[3], modes[4], modes[5], modes[6]); + modes[3], modes[4], modes[5], modes[6], NULL); return 0; } @@ -945,9 +1637,9 @@ gt_sign_encrypt (gpgme_tool_t gt, gpgme_encrypt_flags_t flags, gpg_error_t err; if (sign) - err = gpgme_op_encrypt (gt->ctx, gt->recipients, flags, plain, cipher); - else err = gpgme_op_encrypt_sign (gt->ctx, gt->recipients, flags, plain, cipher); + else + err = gpgme_op_encrypt (gt->ctx, gt->recipients, flags, plain, cipher); gt_recipients_clear (gt); @@ -997,22 +1689,22 @@ gt_genkey (gpgme_tool_t gt, const char *parms, gpgme_data_t public, gpg_error_t gt_import_keys (gpgme_tool_t gt, char *fpr[]) { - gpg_error_t err; + gpg_error_t err = 0; int cnt; int idx; gpgme_key_t *keys; - + cnt = 0; while (fpr[cnt]) cnt++; - + if (! cnt) return gpg_error (GPG_ERR_INV_VALUE); keys = malloc ((cnt + 1) * sizeof (gpgme_key_t)); if (! keys) return gpg_error_from_syserror (); - + for (idx = 0; idx < cnt; idx++) { err = gpgme_get_key (gt->ctx, fpr[idx], &keys[idx], 0); @@ -1024,7 +1716,7 @@ gt_import_keys (gpgme_tool_t gt, char *fpr[]) keys[cnt] = NULL; err = gpgme_op_import_keys (gt->ctx, keys); } - + /* Rollback. */ while (--idx >= 0) gpgme_key_unref (keys[idx]); @@ -1094,7 +1786,27 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags) } -// TODO +static const char hlp_passwd[] = + "PASSWD \n" + "\n" + "Ask the backend to change the passphrase for the key\n" + "specified by USER-ID."; +gpg_error_t +gt_passwd (gpgme_tool_t gt, char *fpr) +{ + gpg_error_t err; + gpgme_key_t key; + + err = gpgme_get_key (gt->ctx, fpr, &key, 0); + if (err) + return gpg_err_code (err) == GPG_ERR_EOF? gpg_error (GPG_ERR_NO_PUBKEY):err; + + err = gpgme_op_passwd (gt->ctx, key, 0); + gpgme_key_unref (key); + return err; +} + + #define GT_RESULT_ENCRYPT 0x1 #define GT_RESULT_DECRYPT 0x2 #define GT_RESULT_SIGN 0x4 @@ -1108,31 +1820,41 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags) gpg_error_t gt_result (gpgme_tool_t gt, unsigned int flags) { + static const char xml_preamble1[] = "\n"; + static const char xml_preamble2[] = "\n"; + static const char xml_end[] = "\n"; + int indent = 2; + + gt_write_data (gt, xml_preamble1, sizeof (xml_preamble1)); + gt_write_data (gt, NULL, 0); + gt_write_data (gt, xml_preamble2, sizeof (xml_preamble2)); + gt_write_data (gt, NULL, 0); if (flags & GT_RESULT_ENCRYPT) - { - gpgme_encrypt_result_t res = gpgme_op_encrypt_result (gt->ctx); - if (res) - { - gpgme_invalid_key_t invrec = res->invalid_recipients; - while (invrec) - { - gt_write_status (gt, STATUS_ENCRYPT_RESULT, "invalid_recipient", - invrec->fpr, invrec->reason); - invrec = invrec->next; - } - } - } + result_encrypt_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_DECRYPT) + result_decrypt_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_SIGN) + result_sign_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_VERIFY) + result_verify_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_IMPORT) + result_import_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_GENKEY) + result_genkey_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + if (flags & GT_RESULT_KEYLIST) + result_keylist_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); if (flags & GT_RESULT_VFS_MOUNT) - { - gpgme_vfs_mount_result_t res = gpgme_op_vfs_mount_result (gt->ctx); - if (res) - { - gt_write_data (gt, "vfs_mount\n", 10); - gt_write_data (gt, "mount_dir:", 10); - gt_write_data (gt, res->mount_dir, strlen (res->mount_dir)); - gt_write_data (gt, "\n", 1); - } - } + result_vfs_mount_to_xml (gt->ctx, indent, + (result_xml_write_cb_t) gt_write_data, gt); + gt_write_data (gt, xml_end, sizeof (xml_end)); return 0; } @@ -1149,7 +1871,15 @@ struct server gpgme_data_encoding_t input_enc; gpgme_data_encoding_t output_enc; + assuan_fd_t input_fd; + char *input_filename; + FILE *input_stream; + assuan_fd_t output_fd; + char *output_filename; + FILE *output_stream; assuan_fd_t message_fd; + char *message_filename; + FILE *message_stream; gpgme_data_encoding_t message_enc; }; @@ -1170,6 +1900,33 @@ server_write_data (void *hook, const void *buf, size_t len) } +/* Wrapper around assuan_command_parse_fd to also handle a + "file=FILENAME" argument. On success either a filename is returned + at FILENAME or a file descriptor at RFD; the other one is set to + NULL respective ASSUAN_INVALID_FD. */ +static gpg_error_t +server_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd, + char **filename) +{ + *rfd = ASSUAN_INVALID_FD; + *filename = NULL; + + if (! strncasecmp (line, "file=", 5)) + { + char *term; + *filename = strdup (line + 5); + if (!*filename) + return gpg_error_from_syserror(); + term = strchr (*filename, ' '); + if (term) + *term = '\0'; + return 0; + } + else + return assuan_command_parse_fd (ctx, line, rfd); +} + + static gpgme_data_encoding_t server_data_encoding (const char *line) { @@ -1190,12 +1947,24 @@ server_data_encoding (const char *line) static gpgme_error_t -server_data_obj (assuan_fd_t fd, gpgme_data_encoding_t encoding, - gpgme_data_t *data) +server_data_obj (assuan_fd_t fd, char *fn, int out, + gpgme_data_encoding_t encoding, + gpgme_data_t *data, FILE **fs) { gpgme_error_t err; - err = gpgme_data_new_from_fd (data, fd); + *fs = NULL; + if (fn) + { + *fs = fopen (fn, out ? "wb" : "rb"); + if (!*fs) + return gpg_error_from_syserror (); + + err = gpgme_data_new_from_stream (data, *fs); + } + else + err = gpgme_data_new_from_fd (data, (int) fd); + if (err) return err; return gpgme_data_set_encoding (*data, encoding); @@ -1208,14 +1977,65 @@ server_reset_fds (struct server *server) /* assuan closes the input and output FDs for us when doing a RESET, but we use this same function after commands, so repeat it here. */ - assuan_close_input_fd (server->assuan_ctx); - assuan_close_output_fd (server->assuan_ctx); - if (server->message_fd != -1) + if (server->input_fd != ASSUAN_INVALID_FD) + { +#if HAVE_W32_SYSTEM + CloseHandle (server->input_fd); +#else + close (server->input_fd); +#endif + server->input_fd = ASSUAN_INVALID_FD; + } + if (server->output_fd != ASSUAN_INVALID_FD) + { +#if HAVE_W32_SYSTEM + CloseHandle (server->output_fd); +#else + close (server->output_fd); +#endif + server->output_fd = ASSUAN_INVALID_FD; + } + if (server->message_fd != ASSUAN_INVALID_FD) { /* FIXME: Assuan should provide a close function. */ +#if HAVE_W32_SYSTEM + CloseHandle (server->message_fd); +#else close (server->message_fd); - server->message_fd = -1; +#endif + server->message_fd = ASSUAN_INVALID_FD; + } + if (server->input_filename) + { + free (server->input_filename); + server->input_filename = NULL; + } + if (server->output_filename) + { + free (server->output_filename); + server->output_filename = NULL; + } + if (server->message_filename) + { + free (server->message_filename); + server->message_filename = NULL; + } + if (server->input_stream) + { + fclose (server->input_stream); + server->input_stream = NULL; + } + if (server->output_stream) + { + fclose (server->output_stream); + server->output_stream = NULL; + } + if (server->message_stream) + { + fclose (server->message_stream); + server->message_stream = NULL; } + server->input_enc = GPGME_DATA_ENCODING_NONE; server->output_enc = GPGME_DATA_ENCODING_NONE; server->message_enc = GPGME_DATA_ENCODING_NONE; @@ -1231,7 +2051,8 @@ reset_notify (assuan_context_t ctx, char *line) return 0; } -static const char hlp_version[] = + +static const char hlp_version[] = "VERSION []\n" "\n" "Call the function gpgme_check_version."; @@ -1251,6 +2072,10 @@ cmd_version (assuan_context_t ctx, char *line) } +static const char hlp_engine[] = + "ENGINE []\n" + "\n" + "Get information about a GPGME engine (a.k.a. protocol)."; static gpg_error_t cmd_engine (assuan_context_t ctx, char *line) { @@ -1259,10 +2084,11 @@ cmd_engine (assuan_context_t ctx, char *line) } -static const char hlp_protocol[] = +static const char hlp_protocol[] = "PROTOCOL []\n" "\n" - "With NAME, set the protocol. Without return the current protocol."; + "With NAME, set the protocol. Without, return the current\n" + "protocol."; static gpg_error_t cmd_protocol (assuan_context_t ctx, char *line) { @@ -1274,6 +2100,11 @@ cmd_protocol (assuan_context_t ctx, char *line) } +static const char hlp_sub_protocol[] = + "SUB_PROTOCOL []\n" + "\n" + "With NAME, set the sub-protocol. Without, return the\n" + "current sub-protocol."; static gpg_error_t cmd_sub_protocol (assuan_context_t ctx, char *line) { @@ -1285,6 +2116,11 @@ cmd_sub_protocol (assuan_context_t ctx, char *line) } +static const char hlp_armor[] = + "ARMOR [true|false]\n" + "\n" + "With 'true' or 'false', turn output ASCII armoring on or\n" + "off. Without, return the current armoring status."; static gpg_error_t cmd_armor (assuan_context_t ctx, char *line) { @@ -1292,11 +2128,11 @@ cmd_armor (assuan_context_t ctx, char *line) if (line && *line) { int flag = 0; - + if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes") || line[0] == '1') flag = 1; - + return gt_set_armor (server->gt, flag); } else @@ -1304,6 +2140,11 @@ cmd_armor (assuan_context_t ctx, char *line) } +static const char hlp_textmode[] = + "TEXTMODE [true|false]\n" + "\n" + "With 'true' or 'false', turn text mode on or off.\n" + "Without, return the current text mode status."; static gpg_error_t cmd_textmode (assuan_context_t ctx, char *line) { @@ -1315,7 +2156,7 @@ cmd_textmode (assuan_context_t ctx, char *line) if (! strcasecmp (line, "true") || ! strcasecmp (line, "yes") || line[0] == '1') flag = 1; - + return gt_set_textmode (server->gt, flag); } else @@ -1323,6 +2164,13 @@ cmd_textmode (assuan_context_t ctx, char *line) } +static const char hlp_include_certs[] = + "INCLUDE_CERTS [default|]\n" + "\n" + "With DEFAULT or N, set how many certificates should be\n" + "included in the next S/MIME signed message. See the\n" + "GPGME documentation for details on the meaning of" + "various N. Without either, return the current setting."; static gpg_error_t cmd_include_certs (assuan_context_t ctx, char *line) { @@ -1331,12 +2179,12 @@ cmd_include_certs (assuan_context_t ctx, char *line) if (line && *line) { int include_certs = 0; - + if (! strcasecmp (line, "default")) include_certs = GPGME_INCLUDE_CERTS_DEFAULT; else include_certs = atoi (line); - + return gt_set_include_certs (server->gt, include_certs); } else @@ -1344,6 +2192,11 @@ cmd_include_certs (assuan_context_t ctx, char *line) } +static const char hlp_keylist_mode[] = + "KEYLIST_MODE [local] [extern] [sigs] [sig_notations]\n" + " [ephemeral] [validate]\n" + "\n" + "Set the mode for the next KEYLIST command."; static gpg_error_t cmd_keylist_mode (assuan_context_t ctx, char *line) { @@ -1352,7 +2205,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line) if (line && *line) { gpgme_keylist_mode_t mode = 0; - + if (strstr (line, "local")) mode |= GPGME_KEYLIST_MODE_LOCAL; if (strstr (line, "extern")) @@ -1365,7 +2218,7 @@ cmd_keylist_mode (assuan_context_t ctx, char *line) mode |= GPGME_KEYLIST_MODE_EPHEMERAL; if (strstr (line, "validate")) mode |= GPGME_KEYLIST_MODE_VALIDATE; - + return gt_set_keylist_mode (server->gt, mode); } else @@ -1373,40 +2226,81 @@ cmd_keylist_mode (assuan_context_t ctx, char *line) } +static const char hlp_input[] = + "INPUT [|FILE=]\n" + "\n" + "Set the input for the next command. Use either the\n" + "Assuan file descriptor FD or a filesystem PATH."; static gpg_error_t -input_notify (assuan_context_t ctx, char *line) +cmd_input (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t sysfd; + char *filename; + + err = server_parse_fd (ctx, line, &sysfd, &filename); + if (err) + return err; + server->input_fd = sysfd; + server->input_filename = filename; server->input_enc = server_data_encoding (line); return 0; } +static const char hlp_output[] = + "OUTPUT [|FILE=]\n" + "\n" + "Set the output for the next command. Use either the\n" + "Assuan file descriptor FD or a filesystem PATH."; static gpg_error_t -output_notify (assuan_context_t ctx, char *line) +cmd_output (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t sysfd; + char *filename; + + err = server_parse_fd (ctx, line, &sysfd, &filename); + if (err) + return err; + server->output_fd = sysfd; + server->output_filename = filename; server->output_enc = server_data_encoding (line); return 0; } +static const char hlp_message[] = + "MESSAGE [|FILE=]\n" + "\n" + "Set the plaintext message for the next VERIFY command\n" + "with a detached signature. Use either the Assuan file\n" + "descriptor FD or a filesystem PATH."; static gpg_error_t cmd_message (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t sysfd; + char *filename; - err = assuan_command_parse_fd (ctx, line, &sysfd); + err = server_parse_fd (ctx, line, &sysfd, &filename); if (err) return err; server->message_fd = sysfd; + server->message_filename = filename; server->message_enc = server_data_encoding (line); return 0; } +static const char hlp_recipient[] = + "RECIPIENT \n" + "\n" + "Add the key matching PATTERN to the list of recipients\n" + "for the next encryption command."; static gpg_error_t cmd_recipient (assuan_context_t ctx, char *line) { @@ -1416,6 +2310,11 @@ cmd_recipient (assuan_context_t ctx, char *line) } +static const char hlp_signer[] = + "SIGNER \n" + "\n" + "Add the key with FINGERPRINT to the list of signers to\n" + "be used for the next signing command."; static gpg_error_t cmd_signer (assuan_context_t ctx, char *line) { @@ -1425,6 +2324,11 @@ cmd_signer (assuan_context_t ctx, char *line) } +static const char hlp_signers_clear[] = + "SIGNERS_CLEAR\n" + "\n" + "Clear the list of signers specified by previous SIGNER\n" + "commands."; static gpg_error_t cmd_signers_clear (assuan_context_t ctx, char *line) { @@ -1440,28 +2344,34 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify) struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t inp_fd; + char *inp_fn; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t inp_data; gpgme_data_t out_data; - inp_fd = assuan_get_input_fd (ctx); - if (inp_fd == ASSUAN_INVALID_FD) + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; - out_fd = assuan_get_output_fd (ctx); - if (out_fd == ASSUAN_INVALID_FD) + out_fd = server->output_fd; + out_fn = server->output_filename; + if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; - - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) { gpgme_data_release (inp_data); return err; } - err = gt_decrypt_verify (server->gt, inp_data, out_data, verify); + err = gt_decrypt_verify (server->gt, inp_data, out_data, verify); gpgme_data_release (inp_data); gpgme_data_release (out_data); @@ -1472,6 +2382,12 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify) } +static const char hlp_decrypt[] = + "DECRYPT\n" + "\n" + "Decrypt the object set by the last INPUT command and\n" + "write the decrypted message to the object set by the\n" + "last OUTPUT command."; static gpg_error_t cmd_decrypt (assuan_context_t ctx, char *line) { @@ -1479,6 +2395,12 @@ cmd_decrypt (assuan_context_t ctx, char *line) } +static const char hlp_decrypt_verify[] = + "DECRYPT_VERIFY\n" + "\n" + "Decrypt the object set by the last INPUT command and\n" + "verify any embedded signatures. Write the decrypted\n" + "message to the object set by the last OUTPUT command."; static gpg_error_t cmd_decrypt_verify (assuan_context_t ctx, char *line) { @@ -1492,7 +2414,9 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign) struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t inp_fd; + char *inp_fn; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t inp_data = NULL; gpgme_data_t out_data = NULL; gpgme_encrypt_flags_t flags = 0; @@ -1505,18 +2429,22 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign) flags |= GPGME_ENCRYPT_PREPARE; if (strstr (line, "--expect-sign")) flags |= GPGME_ENCRYPT_EXPECT_SIGN; - - inp_fd = assuan_get_input_fd (ctx); - out_fd = assuan_get_output_fd (ctx); - if (inp_fd != ASSUAN_INVALID_FD) + + inp_fd = server->input_fd; + inp_fn = server->input_filename; + out_fd = server->output_fd; + out_fn = server->output_filename; + if (inp_fd != ASSUAN_INVALID_FD || inp_fn) { - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; } - if (out_fd != ASSUAN_INVALID_FD) + if (out_fd != ASSUAN_INVALID_FD || out_fn) { - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) { gpgme_data_release (inp_data); @@ -1524,7 +2452,7 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign) } } - err = gt_sign_encrypt (server->gt, flags, inp_data, out_data, sign); + err = gt_sign_encrypt (server->gt, flags, inp_data, out_data, sign); gpgme_data_release (inp_data); gpgme_data_release (out_data); @@ -1535,6 +2463,14 @@ _cmd_sign_encrypt (assuan_context_t ctx, char *line, int sign) } +static const char hlp_encrypt[] = + "ENCRYPT [--always-trust] [--no-encrypt-to]\n" + " [--prepare] [--expect-sign]\n" + "\n" + "Encrypt the object set by the last INPUT command to\n" + "the keys specified by previous RECIPIENT commands. \n" + "Write the signed and encrypted message to the object\n" + "set by the last OUTPUT command."; static gpg_error_t cmd_encrypt (assuan_context_t ctx, char *line) { @@ -1542,6 +2478,15 @@ cmd_encrypt (assuan_context_t ctx, char *line) } +static const char hlp_sign_encrypt[] = + "SIGN_ENCRYPT [--always-trust] [--no-encrypt-to]\n" + " [--prepare] [--expect-sign]\n" + "\n" + "Sign the object set by the last INPUT command with the\n" + "keys specified by previous SIGNER commands and encrypt\n" + "it to the keys specified by previous RECIPIENT\n" + "commands. Write the signed and encrypted message to\n" + "the object set by the last OUTPUT command."; static gpg_error_t cmd_sign_encrypt (assuan_context_t ctx, char *line) { @@ -1549,13 +2494,24 @@ cmd_sign_encrypt (assuan_context_t ctx, char *line) } +static const char hlp_sign[] = + "SIGN [--clear|--detach]\n" + "\n" + "Sign the object set by the last INPUT command with the\n" + "keys specified by previous SIGNER commands. Write the\n" + "signed message to the object set by the last OUTPUT\n" + "command. With `--clear`, generate a clear text\n" + "signature. With `--detach`, generate a detached\n" + "signature."; static gpg_error_t cmd_sign (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t inp_fd; + char *inp_fn; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t inp_data; gpgme_data_t out_data; gpgme_sig_mode_t mode = GPGME_SIG_MODE_NORMAL; @@ -1565,17 +2521,21 @@ cmd_sign (assuan_context_t ctx, char *line) if (strstr (line, "--detach")) mode = GPGME_SIG_MODE_DETACH; - inp_fd = assuan_get_input_fd (ctx); - if (inp_fd == ASSUAN_INVALID_FD) + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; - out_fd = assuan_get_output_fd (ctx); - if (out_fd == ASSUAN_INVALID_FD) + out_fd = server->output_fd; + out_fn = server->output_filename; + if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; - - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) { gpgme_data_release (inp_data); @@ -1592,6 +2552,13 @@ cmd_sign (assuan_context_t ctx, char *line) } +static const char hlp_verify[] = + "VERIFY\n" + "\n" + "Verify signatures on the object set by the last INPUT\n" + "and MESSAGE commands. If the message was encrypted,\n" + "write the plaintext to the object set by the last\n" + "OUTPUT command."; static gpg_error_t cmd_verify (assuan_context_t ctx, char *line) { @@ -1600,31 +2567,40 @@ cmd_verify (assuan_context_t ctx, char *line) assuan_fd_t inp_fd; assuan_fd_t msg_fd; assuan_fd_t out_fd; + char *inp_fn; + char *msg_fn; + char *out_fn; gpgme_data_t inp_data; gpgme_data_t msg_data = NULL; gpgme_data_t out_data = NULL; - inp_fd = assuan_get_input_fd (ctx); - if (inp_fd == ASSUAN_INVALID_FD) + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; msg_fd = server->message_fd; - out_fd = assuan_get_output_fd (ctx); - - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + msg_fn = server->message_filename; + out_fd = server->output_fd; + out_fn = server->output_filename; + + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; - if (msg_fd != ASSUAN_INVALID_FD) + if (msg_fd != ASSUAN_INVALID_FD || msg_fn) { - err = server_data_obj (msg_fd, server->message_enc, &msg_data); + err = server_data_obj (msg_fd, msg_fn, 0, server->message_enc, &msg_data, + &server->message_stream); if (err) { gpgme_data_release (inp_data); return err; } } - if (out_fd != ASSUAN_INVALID_FD) + if (out_fd != ASSUAN_INVALID_FD || out_fn) { - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) { gpgme_data_release (inp_data); @@ -1647,11 +2623,17 @@ cmd_verify (assuan_context_t ctx, char *line) } +static const char hlp_import[] = + "IMPORT []\n" + "\n" + "With PATTERN, import the keys described by PATTERN.\n" + "Without, read a key (or keys) from the object set by the\n" + "last INPUT command."; static gpg_error_t cmd_import (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); - + if (line && *line) { char *fprs[2] = { line, NULL }; @@ -1662,18 +2644,21 @@ cmd_import (assuan_context_t ctx, char *line) { gpg_error_t err; assuan_fd_t inp_fd; + char *inp_fn; gpgme_data_t inp_data; - - inp_fd = assuan_get_input_fd (ctx); - if (inp_fd == ASSUAN_INVALID_FD) + + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; - - err = gt_import (server->gt, inp_data); - + + err = gt_import (server->gt, inp_data); + gpgme_data_release (inp_data); server_reset_fds (server); @@ -1682,29 +2667,38 @@ cmd_import (assuan_context_t ctx, char *line) } +static const char hlp_export[] = + "EXPORT [--extern] [--minimal] []\n" + "\n" + "Export the keys described by PATTERN. Write the\n" + "the output to the object set by the last OUTPUT command."; static gpg_error_t cmd_export (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t out_data; gpgme_export_mode_t mode = 0; const char *pattern[2]; - const char optstr[] = "--extern "; - out_fd = assuan_get_output_fd (ctx); - if (out_fd == ASSUAN_INVALID_FD) + out_fd = server->output_fd; + out_fn = server->output_filename; + if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) return err; - if (strncasecmp (line, optstr, strlen (optstr))) - { - mode |= GPGME_EXPORT_MODE_EXTERN; - line += strlen (optstr); - } + if (has_option (line, "--extern")) + mode |= GPGME_EXPORT_MODE_EXTERN; + if (has_option (line, "--minimal")) + mode |= GPGME_EXPORT_MODE_MINIMAL; + + line = skip_options (line); + pattern[0] = line; pattern[1] = NULL; @@ -1741,23 +2735,29 @@ cmd_genkey (assuan_context_t ctx, char *line) struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t inp_fd; + char *inp_fn; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t inp_data; gpgme_data_t out_data = NULL; gpgme_data_t parms_data = NULL; const char *parms; - inp_fd = assuan_get_input_fd (ctx); - if (inp_fd == ASSUAN_INVALID_FD) + inp_fd = server->input_fd; + inp_fn = server->input_filename; + if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; - out_fd = assuan_get_output_fd (ctx); - - err = server_data_obj (inp_fd, server->input_enc, &inp_data); + out_fd = server->output_fd; + out_fn = server->output_filename; + + err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, + &server->input_stream); if (err) return err; - if (out_fd != ASSUAN_INVALID_FD) + if (out_fd != ASSUAN_INVALID_FD || out_fn) { - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) { gpgme_data_release (inp_data); @@ -1808,7 +2808,7 @@ cmd_genkey (assuan_context_t ctx, char *line) if (parms_data) gpgme_data_release (parms_data); - return err; + return err; } @@ -1817,33 +2817,60 @@ cmd_delete (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); int allow_secret = 0; - const char optstr[] = "--allow-secret "; + const char optstr[] = "--allow-secret"; - if (strncasecmp (line, optstr, strlen (optstr))) + if (!strncasecmp (line, optstr, strlen (optstr))) { allow_secret = 1; line += strlen (optstr); + while (*line && !spacep (line)) + line++; } return gt_delete (server->gt, line, allow_secret); } +static const char hlp_keylist[] = + "KEYLIST [--secret-only] []\n" + "\n" + "List all certificates or only those specified by PATTERNS. Each\n" + "pattern shall be a percent-plus escaped certificate specification."; static gpg_error_t cmd_keylist (assuan_context_t ctx, char *line) { +#define MAX_CMD_KEYLIST_PATTERN 20 struct server *server = assuan_get_pointer (ctx); gpg_error_t err; int secret_only = 0; - const char *pattern[2]; - const char optstr[] = "--secret-only "; + int idx; + const char *pattern[MAX_CMD_KEYLIST_PATTERN+1]; + const char optstr[] = "--secret-only"; + char *p; - if (strncasecmp (line, optstr, strlen (optstr))) + if (!strncasecmp (line, optstr, strlen (optstr))) { secret_only = 1; line += strlen (optstr); + while (*line && !spacep (line)) + line++; } - pattern[0] = line; - pattern[1] = NULL; + + idx = 0; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + if (idx+1 == DIM (pattern)) + return gpg_error (GPG_ERR_TOO_MANY); + strcpy_escaped_plus (line, line); + pattern[idx++] = line; + } + } + pattern[idx] = NULL; err = gt_keylist_start (server->gt, pattern, secret_only); while (! err) @@ -1861,33 +2888,51 @@ cmd_keylist (assuan_context_t ctx, char *line) char buf[100]; /* FIXME: More data. */ snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr); - assuan_send_data (ctx, buf, strlen (buf)); + /* Write data and flush so that we see one D line for each + key. This does not change the semantics but is easier to + read by organic eyes. */ + if (!assuan_send_data (ctx, buf, strlen (buf))) + assuan_send_data (ctx, NULL, 0); gpgme_key_unref (key); } } - + server_reset_fds (server); return err; } +static const char hlp_getauditlog[] = + "GETAUDITLOG [--html] [--with-help]\n" + "\n" + "Call the function gpgme_op_getauditlog with the given flags. Write\n" + "the output to the object set by the last OUTPUT command."; static gpg_error_t cmd_getauditlog (assuan_context_t ctx, char *line) { struct server *server = assuan_get_pointer (ctx); gpg_error_t err; assuan_fd_t out_fd; + char *out_fn; gpgme_data_t out_data; + unsigned int flags = 0; - out_fd = assuan_get_output_fd (ctx); - if (out_fd == ASSUAN_INVALID_FD) + out_fd = server->output_fd; + out_fn = server->output_filename; + if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; - err = server_data_obj (out_fd, server->output_enc, &out_data); + err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data, + &server->output_stream); if (err) return err; - err = gt_getauditlog (server->gt, out_data, 0); + if (strstr (line, "--html")) + flags |= GPGME_AUDITLOG_HTML; + if (strstr (line, "--with-help")) + flags |= GPGME_AUDITLOG_WITH_HELP; + + err = gt_getauditlog (server->gt, out_data, flags); gpgme_data_release (out_data); server_reset_fds (server); @@ -1938,6 +2983,16 @@ cmd_vfs_create (assuan_context_t ctx, char *line) } +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + + return gt_passwd (server->gt, line); +} + + + static gpg_error_t cmd_result (assuan_context_t ctx, char *line) { @@ -1994,52 +3049,53 @@ register_commands (assuan_context_t ctx) assuan_handler_t handler; const char * const help; } table[] = { - // RESET, BYE are implicit. + /* RESET, BYE are implicit. */ { "VERSION", cmd_version, hlp_version }, - // TODO: Set engine info. - { "ENGINE", cmd_engine }, + /* TODO: Set engine info. */ + { "ENGINE", cmd_engine, hlp_engine }, { "PROTOCOL", cmd_protocol, hlp_protocol }, - { "SUB_PROTOCOL", cmd_sub_protocol }, - { "ARMOR", cmd_armor }, - { "TEXTMODE", cmd_textmode }, - { "INCLUDE_CERTS", cmd_include_certs }, - { "KEYLIST_MODE", cmd_keylist_mode }, - { "INPUT", NULL }, - { "OUTPUT", NULL }, - { "MESSAGE", cmd_message }, - { "RECIPIENT", cmd_recipient }, - { "SIGNER", cmd_signer }, - { "SIGNERS_CLEAR", cmd_signers_clear }, - // TODO: SIGNOTATION missing. - // TODO: Could add wait interface if we allow more than one context - // and add _START variants. - // TODO: Could add data interfaces if we allow multiple data objects. - { "DECRYPT", cmd_decrypt }, - { "DECRYPT_VERIFY", cmd_decrypt_verify }, - { "ENCRYPT", cmd_encrypt }, - { "ENCRYPT_SIGN", cmd_sign_encrypt }, - { "SIGN_ENCRYPT", cmd_sign_encrypt }, - { "SIGN", cmd_sign }, - { "VERIFY", cmd_verify }, - { "IMPORT", cmd_import }, - { "EXPORT", cmd_export }, + { "SUB_PROTOCOL", cmd_sub_protocol, hlp_sub_protocol }, + { "ARMOR", cmd_armor, hlp_armor }, + { "TEXTMODE", cmd_textmode, hlp_textmode }, + { "INCLUDE_CERTS", cmd_include_certs, hlp_include_certs }, + { "KEYLIST_MODE", cmd_keylist_mode, hlp_keylist_mode }, + { "INPUT", cmd_input, hlp_input }, + { "OUTPUT", cmd_output, hlp_output }, + { "MESSAGE", cmd_message, hlp_message }, + { "RECIPIENT", cmd_recipient, hlp_recipient }, + { "SIGNER", cmd_signer, hlp_signer }, + { "SIGNERS_CLEAR", cmd_signers_clear, hlp_signers_clear }, + /* TODO: SIGNOTATION missing. */ + /* TODO: Could add wait interface if we allow more than one context */ + /* and add _START variants. */ + /* TODO: Could add data interfaces if we allow multiple data objects. */ + { "DECRYPT", cmd_decrypt, hlp_decrypt }, + { "DECRYPT_VERIFY", cmd_decrypt_verify, hlp_decrypt_verify }, + { "ENCRYPT", cmd_encrypt, hlp_encrypt }, + { "ENCRYPT_SIGN", cmd_sign_encrypt, hlp_sign_encrypt }, + { "SIGN_ENCRYPT", cmd_sign_encrypt, hlp_sign_encrypt }, + { "SIGN", cmd_sign, hlp_sign }, + { "VERIFY", cmd_verify, hlp_verify }, + { "IMPORT", cmd_import, hlp_import }, + { "EXPORT", cmd_export, hlp_export }, { "GENKEY", cmd_genkey }, { "DELETE", cmd_delete }, - // TODO: EDIT, CARD_EDIT (with INQUIRE) - { "KEYLIST", cmd_keylist }, - { "LISTKEYS", cmd_keylist }, - // TODO: TRUSTLIST, TRUSTLIST_EXT - { "GETAUDITLOG", cmd_getauditlog }, - // TODO: ASSUAN + /* TODO: EDIT, CARD_EDIT (with INQUIRE) */ + { "KEYLIST", cmd_keylist, hlp_keylist }, + { "LISTKEYS", cmd_keylist, hlp_keylist }, + /* TODO: TRUSTLIST, TRUSTLIST_EXT */ + { "GETAUDITLOG", cmd_getauditlog, hlp_getauditlog }, + /* TODO: ASSUAN */ { "VFS_MOUNT", cmd_vfs_mount }, { "MOUNT", cmd_vfs_mount }, { "VFS_CREATE", cmd_vfs_create }, { "CREATE", cmd_vfs_create }, - // TODO: GPGCONF + /* TODO: GPGCONF */ { "RESULT", cmd_result }, { "STRERROR", cmd_strerror }, { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name }, { "HASH_ALGO_NAME", cmd_hash_algo_name }, + { "PASSWD", cmd_passwd, hlp_passwd }, { NULL } }; int idx; @@ -2050,7 +3106,7 @@ register_commands (assuan_context_t ctx) table[idx].help); if (err) return err; - } + } return 0; } @@ -2065,7 +3121,9 @@ gpgme_server (gpgme_tool_t gt) static const char hello[] = ("GPGME-Tool " VERSION " ready"); memset (&server, 0, sizeof (server)); - server.message_fd = -1; + server.input_fd = ASSUAN_INVALID_FD; + server.output_fd = ASSUAN_INVALID_FD; + server.message_fd = ASSUAN_INVALID_FD; server.input_enc = GPGME_DATA_ENCODING_NONE; server.output_enc = GPGME_DATA_ENCODING_NONE; server.message_enc = GPGME_DATA_ENCODING_NONE; @@ -2079,8 +3137,13 @@ gpgme_server (gpgme_tool_t gt) /* We use a pipe based server so that we can work from scripts. assuan_init_pipe_server will automagically detect when we are called with a socketpair and ignore FIELDES in this case. */ +#ifdef HAVE_W32CE_SYSTEM + filedes[0] = ASSUAN_STDIN; + filedes[1] = ASSUAN_STDOUT; +#else filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); +#endif err = assuan_new (&server.assuan_ctx); if (err) log_error (1, err, "can't create assuan context"); @@ -2096,8 +3159,6 @@ gpgme_server (gpgme_tool_t gt) assuan_set_hello_line (server.assuan_ctx, hello); assuan_register_reset_notify (server.assuan_ctx, reset_notify); - assuan_register_input_notify (server.assuan_ctx, input_notify); - assuan_register_output_notify (server.assuan_ctx, output_notify); #define DBG_ASSUAN 0 if (DBG_ASSUAN) @@ -2113,7 +3174,7 @@ gpgme_server (gpgme_tool_t gt) log_error (0, err, "assuan accept problem"); break; } - + err = assuan_process (server.assuan_ctx); if (err) log_error (0, err, "assuan processing failed"); @@ -2130,7 +3191,7 @@ const char *argp_program_version = VERSION; const char *argp_program_bug_address = "bug-gpgme@gnupg.org"; error_t argp_err_exit_status = 1; -static char doc[] = "GPGME Tool -- invoke GPGME operations"; +static char doc[] = "GPGME Tool -- Assuan server exposing GPGME operations"; static char args_doc[] = "COMMAND [OPTIONS...]"; static struct argp_option options[] = { @@ -2189,12 +3250,17 @@ main (int argc, char *argv[]) struct args args; struct gpgme_tool gt; +#ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); +#endif gpgme_check_version (NULL); +#ifdef LC_CTYPE gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); +#endif #ifdef LC_MESSAGES gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); #endif + args_init (&args); argp_parse (&argp, argc, argv, 0, 0, &args); @@ -2212,6 +3278,11 @@ main (int argc, char *argv[]) gpgme_release (gt.ctx); +#ifdef HAVE_W32CE_SYSTEM + /* Give the buggy ssh server time to flush the output buffers. */ + Sleep (300); +#endif + return 0; }