diff options
Diffstat (limited to 'src/gpgme-tool.c')
-rw-r--r-- | src/gpgme-tool.c | 660 |
1 files changed, 544 insertions, 116 deletions
diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 9591d15..978c387 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -1,18 +1,19 @@ /* gpgme-tool.c - Assuan server exposing GnuPG Made Easy operations. - Copyright (C) 2009, 2010 g10 Code GmbH + Copyright (C) 2009, 2010, 2012, 2013 g10 Code GmbH + Copyright (C) 2001, 2003, 2009, 2011 Free Software Foundation, Inc. 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. - + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + 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 <http://www.gnu.org/licenses/>. */ @@ -40,10 +41,10 @@ #include "gpgme.h" /* GCC attributes. */ -#if __GNUC__ >= 4 +#if __GNUC__ >= 4 # define GT_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a))) #else -# define GT_GCC_A_SENTINEL(a) +# define GT_GCC_A_SENTINEL(a) #endif #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ) @@ -158,9 +159,9 @@ struct argp | ARGP_HELP_DOC | ARGP_HELP_BUG_ADDR) -void argp_error (const struct argp_state *state, +void argp_error (const struct argp_state *state, const char *fmt, ...) GT_GCC_A_PRINTF(2, 3); - + char * @@ -374,10 +375,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 @@ -453,7 +454,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); @@ -474,6 +475,148 @@ argp_parse (const struct argp *argp, int argc, #endif +/* MEMBUF */ + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out-of-core checks. */ + +/* The definition of the structure is private, we only need it here, + so it can be allocated on the stack. */ +struct private_membuf_s +{ + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + +typedef struct private_membuf_s membuf_t; + +/* Return the current length of the membuf. */ +#define get_membuf_len(a) ((a)->len) +#define is_membuf_ready(a) ((a)->buf || (a)->out_of_core) +#define MEMBUF_ZERO { 0, 0, NULL, 0} + + +static void +init_membuf (membuf_t *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = malloc (initiallen); + if (!mb->buf) + mb->out_of_core = errno; +} + + +/* Shift the the content of the membuf MB by AMOUNT bytes. The next + operation will then behave as if AMOUNT bytes had not been put into + the buffer. If AMOUNT is greater than the actual accumulated + bytes, the membuf is basically reset to its initial state. */ +#if 0 /* Not yet used. */ +static void +clear_membuf (membuf_t *mb, size_t amount) +{ + /* No need to clear if we are already out of core. */ + if (mb->out_of_core) + return; + if (amount >= mb->len) + mb->len = 0; + else + { + mb->len -= amount; + memmove (mb->buf, mb->buf+amount, mb->len); + } +} +#endif /* unused */ + +static void +put_membuf (membuf_t *mb, const void *buf, size_t len) +{ + if (mb->out_of_core || !len) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = realloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = errno ? errno : ENOMEM; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + + +#if 0 /* Not yet used. */ +static void +put_membuf_str (membuf_t *mb, const char *string) +{ + put_membuf (mb, string, strlen (string)); +} +#endif /* unused */ + + +static void * +get_membuf (membuf_t *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + if (mb->buf) + { + free (mb->buf); + mb->buf = NULL; + } + gpg_err_set_errno (mb->out_of_core); + return NULL; + } + + p = mb->buf; + if (len) + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = ENOMEM; /* hack to make sure it won't get reused. */ + return p; +} + + +/* Peek at the membuf MB. On success a pointer to the buffer is + returned which is valid until the next operation on MB. If LEN is + not NULL the current LEN of the buffer is stored there. On error + NULL is returned and ERRNO is set. */ +#if 0 /* Not yet used. */ +static const void * +peek_membuf (membuf_t *mb, size_t *len) +{ + const char *p; + + if (mb->out_of_core) + { + gpg_err_set_errno (mb->out_of_core); + return NULL; + } + + p = mb->buf; + if (len) + *len = mb->len; + return p; +} +#endif /* unused */ + + + /* SUPPORT. */ FILE *log_stream; char *program_name = "gpgme-tool"; @@ -481,7 +624,7 @@ char *program_name = "gpgme-tool"; #define spacep(p) (*(p) == ' ' || *(p) == '\t') -void log_error (int status, gpg_error_t errnum, +void log_error (int status, gpg_error_t errnum, const char *fmt, ...) GT_GCC_A_PRINTF(3,4); @@ -502,8 +645,11 @@ log_error (int status, gpg_error_t errnum, const char *fmt, ...) vfprintf (log_stream, fmt, ap); va_end (ap); if (errnum) - fprintf (log_stream, ": %s <%s>", gpg_strerror (errnum), - gpg_strsource (errnum)); + { + fprintf (log_stream, ": %s", gpg_strerror (errnum)); + if (gpg_err_source (errnum) != GPG_ERR_SOURCE_GPGME) + fprintf (log_stream, " <%s>", gpg_strsource (errnum)); + } fprintf (log_stream, "\n"); if (status) exit (status); @@ -518,7 +664,7 @@ strcpy_escaped_plus (char *d, const char *s) while (*s) { if (*s == '%' && s[1] && s[2]) - { + { s++; *d++ = xtoi_2 (s); s += 2; @@ -528,7 +674,7 @@ strcpy_escaped_plus (char *d, const char *s) else *d++ = *s++; } - *d = 0; + *d = 0; } @@ -565,6 +711,12 @@ skip_options (char *line) typedef gpg_error_t (*result_xml_write_cb_t) (void *hook, const void *buf, size_t len); +static char xml_preamble1[] = "<?xml version=\"1.0\" " + "encoding=\"UTF-8\" standalone=\"yes\"?>\n"; +static const char xml_preamble2[] = "<gpgme>\n"; +static const char xml_end[] = "</gpgme>\n"; + + struct result_xml_state { int indent; @@ -630,7 +782,7 @@ result_xml_tag_start (struct result_xml_state *state, char *name, ...) state->had_data[state->next_tag] = 0; state->indent += 2; state->next_tag++; - + while (1) { attr = va_arg (ap, char *); @@ -651,12 +803,56 @@ result_xml_tag_start (struct result_xml_state *state, char *name, ...) return 0; } +/* Return a constant string with an XML entity for C. */ +static const char * +result_xml_escape_replacement(char c) +{ + switch (c) + { + case '<': + return "<"; + case '>': + return ">"; + case '&': + return "&"; + default: + return NULL; + } +} + +/* Escape DATA by replacing certain characters with their XML + entities. The result is stored in a newly allocated buffer which + address will be stored at BUF. Returns 0 on success. */ +static gpg_error_t +result_xml_escape (const char *data, char **buf) +{ + int data_len, i; + const char *r; + membuf_t mb; + + init_membuf (&mb, 128); + data_len = strlen (data); + for (i = 0; i < data_len; i++) + { + r = result_xml_escape_replacement (data[i]); + if (r) + put_membuf (&mb, r, strlen (r)); + else + put_membuf (&mb, data+i, 1); + } + put_membuf (&mb, "", 1); + *buf = get_membuf (&mb, NULL); + return *buf? 0 : gpg_error_from_syserror (); +} + gpg_error_t -result_xml_tag_data (struct result_xml_state *state, char *data) +result_xml_tag_data (struct result_xml_state *state, const char *data) { + gpg_error_t err; result_xml_write_cb_t cb = state->cb; void *hook = state->hook; + char *buf = NULL; if (state->had_data[state->next_tag - 1]) { @@ -668,7 +864,13 @@ result_xml_tag_data (struct result_xml_state *state, char *data) (*cb) (hook, ">", 1); state->had_data[state->next_tag - 1] = 2; - (*cb) (hook, data, strlen (data)); + err = result_xml_escape (data, &buf); + if (err) + return err; + + (*cb) (hook, buf, strlen (buf)); + + free (buf); return 0; } @@ -704,11 +906,11 @@ result_xml_tag_end (struct result_xml_state *state) 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>", + 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); @@ -720,7 +922,7 @@ result_add_error (struct result_xml_state *state, char *name, gpg_error_t err) 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); @@ -736,7 +938,7 @@ result_add_pubkey_algo (struct result_xml_state *state, 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); @@ -751,7 +953,7 @@ result_add_hash_algo (struct result_xml_state *state, 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); @@ -761,7 +963,7 @@ result_add_keyid (struct result_xml_state *state, char *name, char *keyid) 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); @@ -785,7 +987,7 @@ result_add_timestamp (struct result_xml_state *state, char *name, 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]; @@ -813,9 +1015,63 @@ result_add_sig_mode (struct result_xml_state *state, char *name, gpg_error_t +result_add_protocol (struct result_xml_state *state, char *name, + gpgme_protocol_t protocol) +{ + const char *str; + char code[20]; + + snprintf (code, sizeof (code) - 1, "%i", protocol); + str = gpgme_get_protocol_name(protocol); + if (!str) + str = "invalid"; + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_data (state, str); + result_xml_tag_end (state); + return 0; +} + + +gpg_error_t +result_add_validity (struct result_xml_state *state, char *name, + gpgme_validity_t validity) +{ + const char *str; + char code[20]; + + snprintf (code, sizeof (code) - 1, "%i", validity); + switch (validity) + { + case GPGME_VALIDITY_UNDEFINED: + str ="undefined"; + break; + case GPGME_VALIDITY_NEVER: + str ="never"; + break; + case GPGME_VALIDITY_MARGINAL: + str ="marginal"; + break; + case GPGME_VALIDITY_FULL: + str ="full"; + break; + case GPGME_VALIDITY_ULTIMATE: + str ="ultimate"; + break; + default: + str ="unknown"; + } + + result_xml_tag_start (state, name, "value", code, NULL); + result_xml_tag_data (state, str); + 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); @@ -828,7 +1084,9 @@ result_add_value (struct result_xml_state *state, gpg_error_t result_add_string (struct result_xml_state *state, char *name, char *str) -{ +{ + if (!str) + str = ""; result_xml_tag_start (state, name, NULL); result_xml_tag_data (state, str); result_xml_tag_end (state); @@ -854,7 +1112,7 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent, if (inv_recp) { result_xml_tag_start (&state, "invalid-recipients", NULL); - + while (inv_recp) { result_xml_tag_start (&state, "invalid-key", NULL); @@ -867,7 +1125,7 @@ result_encrypt_to_xml (gpgme_ctx_t ctx, int indent, result_xml_tag_end (&state); } result_xml_tag_end (&state); - + return 0; } @@ -920,7 +1178,7 @@ result_decrypt_to_xml (gpgme_ctx_t ctx, int indent, result_xml_tag_end (&state); } result_xml_tag_end (&state); - + return 0; } @@ -944,7 +1202,7 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent, if (inv_key) { result_xml_tag_start (&state, "invalid-signers", NULL); - + while (inv_key) { result_xml_tag_start (&state, "invalid-key", NULL); @@ -980,7 +1238,7 @@ result_sign_to_xml (gpgme_ctx_t ctx, int indent, } result_xml_tag_end (&state); - + return 0; } @@ -1014,7 +1272,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent, while (sig) { result_xml_tag_start (&state, "signature", NULL); - + /* FIXME: Could be done better. */ result_add_value (&state, "summary", sig->summary); if (sig->fpr) @@ -1032,7 +1290,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent, 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; } @@ -1040,7 +1298,7 @@ result_verify_to_xml (gpgme_ctx_t ctx, int indent, } result_xml_tag_end (&state); - + return 0; } @@ -1078,7 +1336,7 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent, if (stat) { result_xml_tag_start (&state, "imports", NULL); - + while (stat) { result_xml_tag_start (&state, "import-status", NULL); @@ -1096,7 +1354,7 @@ result_import_to_xml (gpgme_ctx_t ctx, int indent, } result_xml_tag_end (&state); - + return 0; } @@ -1120,7 +1378,7 @@ result_genkey_to_xml (gpgme_ctx_t ctx, int indent, result_add_fpr (&state, "fpr", res->fpr); result_xml_tag_end (&state); - + return 0; } @@ -1141,7 +1399,7 @@ result_keylist_to_xml (gpgme_ctx_t ctx, int indent, result_add_value (&state, "truncated", res->truncated); result_xml_tag_end (&state); - + return 0; } @@ -1162,7 +1420,7 @@ result_vfs_mount_to_xml (gpgme_ctx_t ctx, int indent, result_add_string (&state, "mount-dir", res->mount_dir); result_xml_tag_end (&state); - + return 0; } @@ -1209,8 +1467,12 @@ typedef struct gpgme_tool *gpgme_tool_t; /* Forward declaration. */ -void gt_write_status (gpgme_tool_t gt, +void gt_write_status (gpgme_tool_t gt, status_t status, ...) GT_GCC_A_SENTINEL(0); +static gpg_error_t +server_passphrase_cb (void *opaque, const char *uid_hint, const char *info, + int was_bad, int fd); + void _gt_progress_cb (void *opaque, const char *what, @@ -1240,9 +1502,10 @@ _gt_gpgme_new (gpgme_tool_t gt, gpgme_ctx_t *ctx) void gt_init (gpgme_tool_t gt) { - memset (gt, '\0', sizeof (*gt)); gpg_error_t err; + memset (gt, '\0', sizeof (*gt)); + err = _gt_gpgme_new (gt, >->ctx); if (err) log_error (1, err, "can't create gpgme context"); @@ -1281,7 +1544,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); @@ -1343,10 +1606,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; } @@ -1359,7 +1622,7 @@ gt_recipients_add (gpgme_tool_t gt, const char *pattern) gpgme_key_t key; if (gt->recipients_nr >= MAX_RECIPIENTS) - return gpg_error_from_errno (ENOMEM); + return gpg_error (GPG_ERR_ENOMEM); if (gpgme_get_protocol (gt->ctx) == GPGME_PROTOCOL_UISERVER) err = gpgme_key_from_uid (&key, pattern); @@ -1390,7 +1653,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; @@ -1482,7 +1745,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) { @@ -1522,6 +1785,19 @@ gt_get_sub_protocol (gpgme_tool_t gt) gpg_error_t +gt_set_pinentry_mode (gpgme_tool_t gt, gpgme_pinentry_mode_t mode, void *opaque) +{ + gpg_error_t err; + + gpgme_set_passphrase_cb (gt->ctx, NULL, NULL); + err = gpgme_set_pinentry_mode (gt->ctx, mode); + if (!err && mode == GPGME_PINENTRY_MODE_LOOPBACK) + gpgme_set_passphrase_cb (gt->ctx, server_passphrase_cb, opaque); + return err; +} + + +gpg_error_t gt_set_armor (gpgme_tool_t gt, int armor) { gpgme_set_armor (gt->ctx, armor); @@ -1572,7 +1848,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) @@ -1693,18 +1969,18 @@ gt_import_keys (gpgme_tool_t gt, char *fpr[]) 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); @@ -1716,7 +1992,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]); @@ -1786,7 +2062,7 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags) } -static const char hlp_passwd[] = +static const char hlp_passwd[] = "PASSWD <user-id>\n" "\n" "Ask the backend to change the passphrase for the key\n" @@ -1820,10 +2096,6 @@ gt_passwd (gpgme_tool_t gt, char *fpr) gpg_error_t gt_result (gpgme_tool_t gt, unsigned int flags) { - static const char xml_preamble1[] = "<?xml version=\"1.0\" " - "encoding=\"UTF-8\" standalone=\"yes\"?>\n"; - static const char xml_preamble2[] = "<gpgme>\n"; - static const char xml_end[] = "</gpgme>\n"; int indent = 2; gt_write_data (gt, xml_preamble1, sizeof (xml_preamble1)); @@ -1900,7 +2172,45 @@ server_write_data (void *hook, const void *buf, size_t len) } +static gpg_error_t +server_passphrase_cb (void *opaque, const char *uid_hint, const char *info, + int was_bad, int fd) +{ + struct server *server = opaque; + gpg_error_t err; + unsigned char *buf = NULL; + size_t buflen = 0; + + if (server && server->assuan_ctx) + { + if (uid_hint) + assuan_write_status (server->assuan_ctx, "USERID_HINT", uid_hint); + if (info) + assuan_write_status (server->assuan_ctx, "NEED_PASSPHRASE", info); + + err = assuan_inquire (server->assuan_ctx, "PASSPHRASE", + &buf, &buflen, 100); + } + else + err = gpg_error (GPG_ERR_NO_PASSPHRASE); + + if (!err) + { + /* We take care to always send a LF. */ + if (gpgme_io_writen (fd, buf, buflen)) + err = gpg_error_from_syserror (); + else if (!memchr (buf, '\n', buflen) && gpgme_io_writen (fd, "\n", 1)) + err = gpg_error_from_syserror (); + } + free (buf); + return err; +} + +/* 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) @@ -1922,7 +2232,7 @@ server_parse_fd (assuan_context_t ctx, char *line, assuan_fd_t *rfd, else return assuan_command_parse_fd (ctx, line, rfd); } - + static gpgme_data_encoding_t server_data_encoding (const char *line) @@ -1974,8 +2284,24 @@ 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->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. */ @@ -2033,7 +2359,7 @@ reset_notify (assuan_context_t ctx, char *line) } -static const char hlp_version[] = +static const char hlp_version[] = "VERSION [<string>]\n" "\n" "Call the function gpgme_check_version."; @@ -2065,7 +2391,7 @@ cmd_engine (assuan_context_t ctx, char *line) } -static const char hlp_protocol[] = +static const char hlp_protocol[] = "PROTOCOL [<name>]\n" "\n" "With NAME, set the protocol. Without, return the current\n" @@ -2097,6 +2423,39 @@ cmd_sub_protocol (assuan_context_t ctx, char *line) } +static const char hlp_pinentry_mode[] = + "PINENTRY_MODE <name>\n" + "\n" + "Set the pinentry mode to NAME. Allowedvalues for NAME are:\n" + " default - reset to the default of the engine,\n" + " ask - force the use of the pinentry,\n" + " cancel - emulate use of pinentry's cancel button,\n" + " error - return a pinentry error,\n" + " loopback - redirect pinentry queries to the caller.\n" + "Note that only recent versions of GPG support changing the pinentry mode."; +static gpg_error_t +cmd_pinentry_mode (assuan_context_t ctx, char *line) +{ + struct server *server = assuan_get_pointer (ctx); + gpgme_pinentry_mode_t mode; + + if (!line || !*line || !strcmp (line, "default")) + mode = GPGME_PINENTRY_MODE_DEFAULT; + else if (!strcmp (line, "ask")) + mode = GPGME_PINENTRY_MODE_ASK; + else if (!strcmp (line, "cancel")) + mode = GPGME_PINENTRY_MODE_CANCEL; + else if (!strcmp (line, "error")) + mode = GPGME_PINENTRY_MODE_ERROR; + else if (!strcmp (line, "loopback")) + mode = GPGME_PINENTRY_MODE_LOOPBACK; + else + return gpg_error (GPG_ERR_INV_VALUE); + + return gt_set_pinentry_mode (server->gt, mode, server); +} + + static const char hlp_armor[] = "ARMOR [true|false]\n" "\n" @@ -2109,11 +2468,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 @@ -2137,7 +2496,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 @@ -2160,12 +2519,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 @@ -2186,7 +2545,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")) @@ -2199,7 +2558,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 @@ -2331,15 +2690,15 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify) gpgme_data_t inp_data; gpgme_data_t out_data; - inp_fd = assuan_get_input_fd (ctx); + 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); + 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, inp_fn, 0, server->input_enc, &inp_data, &server->input_stream); if (err) @@ -2352,7 +2711,7 @@ _cmd_decrypt_verify (assuan_context_t ctx, char *line, int verify) 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); @@ -2410,10 +2769,10 @@ _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); + + inp_fd = server->input_fd; inp_fn = server->input_filename; - out_fd = assuan_get_output_fd (ctx); + out_fd = server->output_fd; out_fn = server->output_filename; if (inp_fd != ASSUAN_INVALID_FD || inp_fn) { @@ -2433,7 +2792,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); @@ -2502,15 +2861,15 @@ cmd_sign (assuan_context_t ctx, char *line) if (strstr (line, "--detach")) mode = GPGME_SIG_MODE_DETACH; - inp_fd = assuan_get_input_fd (ctx); + 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); + 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, inp_fn, 0, server->input_enc, &inp_data, &server->input_stream); if (err) @@ -2555,13 +2914,13 @@ cmd_verify (assuan_context_t ctx, char *line) gpgme_data_t msg_data = NULL; gpgme_data_t out_data = NULL; - inp_fd = assuan_get_input_fd (ctx); + 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; msg_fn = server->message_filename; - out_fd = assuan_get_output_fd (ctx); + out_fd = server->output_fd; out_fn = server->output_filename; err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data, @@ -2614,7 +2973,7 @@ 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 }; @@ -2627,8 +2986,8 @@ cmd_import (assuan_context_t ctx, char *line) assuan_fd_t inp_fd; char *inp_fn; gpgme_data_t inp_data; - - inp_fd = assuan_get_input_fd (ctx); + + inp_fd = server->input_fd; inp_fn = server->input_filename; if (inp_fd == ASSUAN_INVALID_FD && !inp_fn) return GPG_ERR_ASS_NO_INPUT; @@ -2637,9 +2996,9 @@ cmd_import (assuan_context_t ctx, char *line) &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); @@ -2648,7 +3007,7 @@ cmd_import (assuan_context_t ctx, char *line) } -static const char hlp_export[] = +static const char hlp_export[] = "EXPORT [--extern] [--minimal] [<pattern>]\n" "\n" "Export the keys described by PATTERN. Write the\n" @@ -2664,7 +3023,7 @@ cmd_export (assuan_context_t ctx, char *line) gpgme_export_mode_t mode = 0; const char *pattern[2]; - out_fd = assuan_get_output_fd (ctx); + out_fd = server->output_fd; out_fn = server->output_filename; if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; @@ -2724,13 +3083,13 @@ cmd_genkey (assuan_context_t ctx, char *line) gpgme_data_t parms_data = NULL; const char *parms; - inp_fd = assuan_get_input_fd (ctx); + 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); + 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) @@ -2789,7 +3148,7 @@ cmd_genkey (assuan_context_t ctx, char *line) if (parms_data) gpgme_data_release (parms_data); - return err; + return err; } @@ -2811,7 +3170,7 @@ cmd_delete (assuan_context_t ctx, char *line) } -static const char hlp_keylist[] = +static const char hlp_keylist[] = "KEYLIST [--secret-only] [<patterns>]\n" "\n" "List all certificates or only those specified by PATTERNS. Each\n" @@ -2821,9 +3180,11 @@ cmd_keylist (assuan_context_t ctx, char *line) { #define MAX_CMD_KEYLIST_PATTERN 20 struct server *server = assuan_get_pointer (ctx); + gpgme_tool_t gt = server->gt; + struct result_xml_state state; gpg_error_t err; int secret_only = 0; - int idx; + int idx, indent=2; const char *pattern[MAX_CMD_KEYLIST_PATTERN+1]; const char optstr[] = "--secret-only"; char *p; @@ -2853,10 +3214,19 @@ cmd_keylist (assuan_context_t ctx, char *line) } pattern[idx] = NULL; + 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); + result_init (&state, indent, (result_xml_write_cb_t) gt_write_data, gt); + result_xml_tag_start (&state, "keylist", NULL); + err = gt_keylist_start (server->gt, pattern, secret_only); while (! err) { gpgme_key_t key; + gpgme_subkey_t subkey; + gpgme_user_id_t uid; err = gt_keylist_next (server->gt, &key); if (gpg_err_code (err) == GPG_ERR_EOF) @@ -2866,25 +3236,62 @@ cmd_keylist (assuan_context_t ctx, char *line) } else if (! err) { - char buf[100]; - /* FIXME: More data. */ - snprintf (buf, sizeof (buf), "key:%s\n", key->subkeys->fpr); - /* 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); + result_xml_tag_start (&state, "key", NULL); + result_add_value (&state, "revoked", key->revoked); + result_add_value (&state, "expired", key->expired); + result_add_value (&state, "disabled", key->disabled); + result_add_value (&state, "invalid", key->invalid); + result_add_value (&state, "can-encrypt", key->can_encrypt); + result_add_value (&state, "can-sign", key->can_sign); + result_add_value (&state, "can-certify", key->can_certify); + result_add_value (&state, "can-authenticate", key->can_authenticate); + result_add_value (&state, "is-qualified", key->is_qualified); + result_add_value (&state, "secret", key->secret); + result_add_protocol (&state, "protocol", key->protocol); + result_xml_tag_start (&state, "issuer", NULL); + result_add_string (&state, "serial", key->issuer_serial); + result_add_string (&state, "name", key->issuer_name); + result_xml_tag_end (&state); /* issuer */ + result_add_string (&state, "chain-id", key->chain_id); + result_add_validity (&state, "owner-trust", key->owner_trust); + result_xml_tag_start (&state, "subkeys", NULL); + subkey = key->subkeys; + while (subkey) { + result_xml_tag_start (&state, "subkey", NULL); + /* FIXME: more data */ + result_add_fpr (&state, "fpr", subkey->fpr); + result_xml_tag_end (&state); /* subkey */ + subkey = subkey->next; + } + result_xml_tag_end (&state); /* subkeys */ + result_xml_tag_start (&state, "uids", NULL); + uid = key->uids; + while (uid) { + result_xml_tag_start (&state, "uid", NULL); + /* FIXME: more data */ + result_add_string (&state, "uid", uid->uid); + result_add_string (&state, "name", uid->name); + result_add_string (&state, "email", uid->email); + result_add_string (&state, "comment", uid->comment); + result_xml_tag_end (&state); /* uid */ + uid = uid->next; + } + result_xml_tag_end (&state); /* uids */ + result_xml_tag_end (&state); /* key */ gpgme_key_unref (key); } } - + + result_xml_tag_end (&state); /* keylist */ + gt_write_data (gt, xml_end, sizeof (xml_end)); + server_reset_fds (server); return err; } -static const char hlp_getauditlog[] = +static const char hlp_getauditlog[] = "GETAUDITLOG [--html] [--with-help]\n" "\n" "Call the function gpgme_op_getauditlog with the given flags. Write\n" @@ -2899,7 +3306,7 @@ cmd_getauditlog (assuan_context_t ctx, char *line) gpgme_data_t out_data; unsigned int flags = 0; - out_fd = assuan_get_output_fd (ctx); + out_fd = server->output_fd; out_fn = server->output_filename; if (out_fd == ASSUAN_INVALID_FD && !out_fn) return GPG_ERR_ASS_NO_OUTPUT; @@ -3036,6 +3443,7 @@ register_commands (assuan_context_t ctx) { "ENGINE", cmd_engine, hlp_engine }, { "PROTOCOL", cmd_protocol, hlp_protocol }, { "SUB_PROTOCOL", cmd_sub_protocol, hlp_sub_protocol }, + { "PINENTRY_MODE", cmd_pinentry_mode, hlp_pinentry_mode }, { "ARMOR", cmd_armor, hlp_armor }, { "TEXTMODE", cmd_textmode, hlp_textmode }, { "INCLUDE_CERTS", cmd_include_certs, hlp_include_certs }, @@ -3087,12 +3495,11 @@ register_commands (assuan_context_t ctx) table[idx].help); if (err) return err; - } + } return 0; } -/* TODO: password callback can do INQUIRE. */ void gpgme_server (gpgme_tool_t gt) { @@ -3102,6 +3509,8 @@ gpgme_server (gpgme_tool_t gt) static const char hello[] = ("GPGME-Tool " VERSION " ready"); memset (&server, 0, sizeof (server)); + 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; @@ -3153,7 +3562,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"); @@ -3175,6 +3584,7 @@ static char args_doc[] = "COMMAND [OPTIONS...]"; static struct argp_option options[] = { { "server", 's', 0, 0, "Server mode" }, + { "gpg-binary", 501, "FILE", 0, "Use FILE for the GPG backend" }, { 0 } }; @@ -3184,6 +3594,7 @@ static struct argp argp = { options, parse_options, args_doc, doc }; struct args { enum { CMD_DEFAULT, CMD_SERVER } cmd; + const char *gpg_binary; }; void @@ -3204,6 +3615,10 @@ parse_options (int key, char *arg, struct argp_state *state) case 's': args->cmd = CMD_SERVER; break; + + case 501: + args->gpg_binary = arg; + break; #if 0 case ARGP_KEY_ARG: if (state->arg_num >= 2) @@ -3228,6 +3643,7 @@ main (int argc, char *argv[]) { struct args args; struct gpgme_tool gt; + gpg_error_t err; #ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); @@ -3245,6 +3661,18 @@ main (int argc, char *argv[]) argp_parse (&argp, argc, argv, 0, 0, &args); log_init (); + if (args.gpg_binary) + { + if (access (args.gpg_binary, X_OK)) + err = gpg_error_from_syserror (); + else + err = gpgme_set_engine_info (GPGME_PROTOCOL_OpenPGP, + args.gpg_binary, NULL); + if (err) + log_error (1, err, "error witching OpenPGP engine to '%s'", + args.gpg_binary); + } + gt_init (>); switch (args.cmd) |