diff --git a/kbx/backend-support.c b/kbx/backend-support.c index 1821129ea..7ab63985c 100644 --- a/kbx/backend-support.c +++ b/kbx/backend-support.c @@ -1,359 +1,359 @@ /* backend-support.c - Supporting functions for the backend. * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include "keyboxd.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/tlv.h" #include "backend.h" #include "keybox-defs.h" /* Common definition part of all backend handle. All definitions of * this structure must start with these fields. */ struct backend_handle_s { enum database_types db_type; unsigned int backend_id; }; /* Return a string with the name of the database type T. */ const char * strdbtype (enum database_types t) { switch (t) { case DB_TYPE_NONE: return "none"; case DB_TYPE_CACHE:return "cache"; case DB_TYPE_KBX: return "keybox"; case DB_TYPE_SQLITE: return "sqlite"; } return "?"; } /* Return a new backend ID. Backend IDs are used to identify backends * without using the actual object. The number of backend resources * is limited because they are specified in the config file. Thus an * overflow check is not required. */ unsigned int be_new_backend_id (void) { static unsigned int last; return ++last; } /* Release the backend described by HD. This is a generic function * which dispatches to the the actual backend. */ void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd) { if (!hd) return; switch (hd->db_type) { case DB_TYPE_NONE: xfree (hd); break; case DB_TYPE_CACHE: be_cache_release_resource (ctrl, hd); break; case DB_TYPE_KBX: be_kbx_release_resource (ctrl, hd); break; case DB_TYPE_SQLITE: be_sqlite_release_resource (ctrl, hd); break; default: log_error ("%s: faulty backend handle of type %d given\n", __func__, hd->db_type); } } /* Release the request object REQ. */ void be_release_request (db_request_t req) { db_request_part_t part, partn; if (!req) return; for (part = req->part; part; part = partn) { partn = part->next; be_kbx_release_kbx_hd (part->kbx_hd); be_sqlite_release_local (part->besqlite); xfree (part); } } /* Given the backend handle BACKEND_HD and the REQUEST find or * allocate a request part for that backend and store it at R_PART. * On error R_PART is set to NULL and an error returned. */ gpg_error_t be_find_request_part (backend_handle_t backend_hd, db_request_t request, db_request_part_t *r_part) { gpg_error_t err; db_request_part_t part; for (part = request->part; part; part = part->next) if (part->backend_id == backend_hd->backend_id) break; if (!part) { part = xtrycalloc (1, sizeof *part); if (!part) return gpg_error_from_syserror (); part->backend_id = backend_hd->backend_id; if (backend_hd->db_type == DB_TYPE_KBX) { err = be_kbx_init_request_part (backend_hd, part); if (err) { xfree (part); return err; } } else if (backend_hd->db_type == DB_TYPE_SQLITE) { err = be_sqlite_init_local (backend_hd, part); if (err) { xfree (part); return err; } } part->next = request->part; request->part = part; } *r_part = part; return 0; } /* Return the public key (BUFFER,BUFLEN) which has the type * PUBKEY_TYPE to the caller. */ gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen, enum pubkey_types pubkey_type, const unsigned char *ubid, int is_ephemeral, int is_revoked, int uid_no, int pk_no) { gpg_error_t err; char hexubid[2*UBID_LEN+1]; bin2hex (ubid, UBID_LEN, hexubid); - err = status_printf (ctrl, "PUBKEY_INFO", "%d %s %c%c %d %d", - pubkey_type, hexubid, - is_ephemeral? 'e':'-', - is_revoked? 'r':'-', - uid_no, pk_no); + err = kbxd_status_printf (ctrl, "PUBKEY_INFO", "%d %s %c%c %d %d", + pubkey_type, hexubid, + is_ephemeral? 'e':'-', + is_revoked? 'r':'-', + uid_no, pk_no); if (err) goto leave; if (ctrl->no_data_return) err = 0; else - err = kbxd_write_data_line(ctrl, buffer, buflen); + err = kbxd_write_data_line (ctrl, buffer, buflen); leave: return err; } /* Return true if (BLOB/BLOBLEN) seems to be an X509 certificate. */ int be_is_x509_blob (const unsigned char *blob, size_t bloblen) { const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; /* An X.509 certificate can be identified by this DER encoding: * * 30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39 * ----------- +++++++++++ ----- ++++++++ -------------------------- * SEQUENCE SEQUENCE [0] INTEGER INTEGER * (tbs) (version) (s/n) * */ p = blob; n = bloblen; if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) return 0; /* Does not start with a sequence. */ if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) return 0; /* No TBS sequence. */ if (n < 7 || objlen < 7) return 0; /* Too short: [0], version and min. s/n required. */ if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_CONTEXT && tag == 0 && cons)) return 0; /* No context tag. */ if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons && objlen == 1 && n && (*p == 1 || *p == 2))) return 0; /* Unknown X.509 version. */ p++; /* Skip version number. */ n--; if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen)) return 0; /* Not a proper BER object. */ if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons)) return 0; /* No s/n. */ return 1; /* Looks like an X.509 certificate. */ } /* Return the public key type and the (primary) fingerprint for * (BLOB,BLOBLEN). r_UBID must point to a buffer of at least UBID_LEN * bytes, on success it receives the UBID (primary fingerprint * truncated 20 octets). R_PKTYPE receives the public key type. */ gpg_error_t be_ubid_from_blob (const void *blob, size_t bloblen, enum pubkey_types *r_pktype, char *r_ubid) { gpg_error_t err; if (be_is_x509_blob (blob, bloblen)) { /* Although libksba has a dedicated function to compute the * fingerprint we compute it here directly because we know that * we have the entire certificate here (we checked the start of * the blob and assume that the length is also okay). */ *r_pktype = PUBKEY_TYPE_X509; gcry_md_hash_buffer (GCRY_MD_SHA1, r_ubid, blob, bloblen); err = 0; } else { struct _keybox_openpgp_info info; err = _keybox_parse_openpgp (blob, bloblen, NULL, &info); if (err) { log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); } else { *r_pktype = PUBKEY_TYPE_OPGP; log_assert (info.primary.fprlen >= 20); memcpy (r_ubid, info.primary.fpr, UBID_LEN); _keybox_destroy_openpgp_info (&info); } } return err; } /* Return a certificates serial number in hex encoding. Caller must * free the returned string. NULL is returned on error but ERRNO * might not be set if the certificate and thus Libksba is broken. */ char * be_get_x509_serial (ksba_cert_t cert) { const char *p; unsigned long n; char *endp; p = (const char *)ksba_cert_get_serial (cert); if (!p) { log_debug ("oops: Libksba returned a certificate w/o a serial\n"); return NULL; } if (*p != '(') { log_debug ("oops: Libksba returned an invalid s-expression\n"); return NULL; } p++; n = strtoul (p, &endp, 10); p = endp; if (*p != ':') { log_debug ("oops: Libksba returned an invalid s-expression\n"); return NULL; } p++; return bin2hex (p, n, NULL); } /* Return the keygrip for the X.509 certificate CERT. The grip is * stored at KEYGRIP which must have been allocated by the caller * with a size of KEYGRIP_LEN. */ gpg_error_t be_get_x509_keygrip (ksba_cert_t cert, unsigned char *keygrip) { gpg_error_t err; size_t n; ksba_sexp_t p; gcry_sexp_t s_pkey; p = ksba_cert_get_public_key (cert); if (!p) return gpg_error (GPG_ERR_NO_PUBKEY); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { ksba_free (p); return gpg_error (GPG_ERR_NO_PUBKEY); } err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n); ksba_free (p); if (err) return err; if (!gcry_pk_get_keygrip (s_pkey, keygrip)) err = gpg_error (GPG_ERR_PUBKEY_ALGO); gcry_sexp_release (s_pkey); return err; } diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index fa04ccf64..990840980 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -1,1073 +1,1085 @@ /* kbxserver.c - Handle Assuan commands send to the keyboxd * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include #include #include #include #include #include "keyboxd.h" #include #include "../common/i18n.h" #include "../common/server-help.h" #include "../common/userids.h" #include "../common/asshelp.h" #include "../common/host2net.h" #include "frontend.h" #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ /**/: gpg_error (e)) /* Helper to provide packing memory for search descriptions. */ struct search_backing_store_s { unsigned char *sn; char *name; }; /* Control structure per connection. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at * SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* The pid of the client. */ pid_t client_pid; /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* The session id (a counter). */ unsigned int session_id; /* If this flag is set to true this process will be terminated after * the end of this session. */ int stopme; /* If the first both flags are set the assuan logging of data lines * is suppressed. The count variable is used to show the number of * non-logged bytes. */ size_t inhibit_data_logging_count; unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; /* This flag is set if the last search command was called with --more. */ unsigned int search_expecting_more : 1; /* This flag is set if the last search command was successful. */ unsigned int search_any_found : 1; /* The first is the current search description as parsed by the * cmd_search. If more than one pattern is required, cmd_search * also allocates and sets multi_search_desc and * multi_search_desc_len. If a search description has ever been * allocated the allocated size is stored at multi_search_desc_size. * multi_search_store is allocated at the same size as * multi_search_desc and used to provde backing store for the SN and * NAME elements of KEYBOX_SEARCH_DESC. */ KEYBOX_SEARCH_DESC search_desc; KEYBOX_SEARCH_DESC *multi_search_desc; struct search_backing_store_s *multi_search_store; unsigned int multi_search_desc_size; unsigned int multi_search_desc_len; /* If not NULL write output to this stream instead of using D lines. */ estream_t outstream; }; /* To keep track of all running sessions, we link all active server * contexts and anchor them at this variable. */ static struct server_local_s *session_list; /* Return the assuan contxt from the local server info in CTRL. */ static assuan_context_t get_assuan_ctx_from_ctrl (ctrl_t ctrl) { if (!ctrl || !ctrl->server_local) return NULL; return ctrl->server_local->assuan_ctx; } /* If OUTPUT has been used prepare the output FD for use. This needs * to be called by all functions which will in any way use * kbxd_write_data_line later. Whether the output goes to the output * stream is decided by this function. */ static gpg_error_t prepare_outstream (ctrl_t ctrl) { int fd; log_assert (ctrl && ctrl->server_local); if (ctrl->server_local->outstream) return 0; /* Already enabled. */ fd = translate_sys2libc_fd (assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1); if (fd == -1) return 0; /* No Output command active. */ ctrl->server_local->outstream = es_fdopen_nc (fd, "w"); if (!ctrl->server_local->outstream) return gpg_err_code_from_syserror (); return 0; } /* The usual writen function; here with diagnostic output. */ static gpg_error_t kbxd_writen (estream_t fp, const void *buffer, size_t length) { gpg_error_t err; size_t nwritten; if (es_write (fp, buffer, length, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } else if (length != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing OUTPUT: %s\n", "short write"); } else err = 0; return err; } +/* This status functions expects a printf style format string. */ +gpg_error_t +kbxd_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl); + + if (!ctx) /* Oops - no assuan context. */ + return gpg_error (GPG_ERR_NOT_PROCESSED); + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + /* A wrapper around assuan_send_data which makes debugging the output * in verbose mode easier. It also takes CTRL as argument. */ gpg_error_t kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) { const char *buffer = buffer_arg; assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl); gpg_error_t err; if (!ctx) /* Oops - no assuan context. */ return gpg_error (GPG_ERR_NOT_PROCESSED); /* Write toa file descriptor if enabled. */ if (ctrl && ctrl->server_local && ctrl->server_local->outstream) { unsigned char lenbuf[4]; ulongtobuf (lenbuf, size); err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4); if (!err) err = kbxd_writen (ctrl->server_local->outstream, buffer, size); if (!err && es_fflush (ctrl->server_local->outstream)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } goto leave; } /* If we do not want logging, enable it here. */ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) ctrl->server_local->inhibit_data_logging_now = 1; if (0 && opt.verbose && buffer && size) { /* Ease reading of output by limiting the line length. */ size_t n, nbytes; nbytes = size; do { n = nbytes > 64? 64 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ goto leave; } } leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { ctrl->server_local->inhibit_data_logging_count += size; ctrl->server_local->inhibit_data_logging_now = 0; } return err; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err && opt.verbose) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_search[] = "SEARCH [--no-data] [--openpgp|--x509] [[--more] PATTERN]\n" "\n" "Search for the keys identified by PATTERN. With --more more\n" "patterns to be used for the search are expected with the next\n" "command. With --no-data only the search status is returned but\n" "not the actual data. With --openpgp or --x509 only the respective\n" "keys are returned. See also \"NEXT\"."; static gpg_error_t cmd_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_more, opt_no_data, opt_openpgp, opt_x509; gpg_error_t err; unsigned int n, k; opt_no_data = has_option (line, "--no-data"); opt_more = has_option (line, "--more"); opt_openpgp = has_option (line, "--openpgp"); opt_x509 = has_option (line, "--x509"); line = skip_options (line); ctrl->server_local->search_any_found = 0; if (!*line) { if (opt_more) { err = set_error (GPG_ERR_INV_ARG, "--more but no pattern"); goto leave; } else if (!*line && ctrl->server_local->search_expecting_more) { /* It would be too surprising to first set a pattern but * finally add no pattern to search the entire DB. */ err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern"); goto leave; } else /* No pattern - return the first item. */ { memset (&ctrl->server_local->search_desc, 0, sizeof ctrl->server_local->search_desc); ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST; } } else { err = classify_user_id (line, &ctrl->server_local->search_desc, 1); if (err) goto leave; } if (opt_more || ctrl->server_local->search_expecting_more) { /* More pattern are expected - store the current one and return * success. */ KEYBOX_SEARCH_DESC *desc; struct search_backing_store_s *store; if (!ctrl->server_local->multi_search_desc_size) { n = 10; ctrl->server_local->multi_search_desc = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc); if (!ctrl->server_local->multi_search_desc) { err = gpg_error_from_syserror (); goto leave; } ctrl->server_local->multi_search_store = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_store); if (!ctrl->server_local->multi_search_store) { err = gpg_error_from_syserror (); xfree (ctrl->server_local->multi_search_desc); ctrl->server_local->multi_search_desc = NULL; goto leave; } ctrl->server_local->multi_search_desc_size = n; } if (ctrl->server_local->multi_search_desc_len == ctrl->server_local->multi_search_desc_size) { n = ctrl->server_local->multi_search_desc_size + 10; desc = xtrycalloc (n, sizeof *desc); if (!desc) { err = gpg_error_from_syserror (); goto leave; } store = xtrycalloc (n, sizeof *store); if (!store) { err = gpg_error_from_syserror (); xfree (desc); goto leave; } for (k=0; k < ctrl->server_local->multi_search_desc_size; k++) { desc[k] = ctrl->server_local->multi_search_desc[k]; store[k] = ctrl->server_local->multi_search_store[k]; } xfree (ctrl->server_local->multi_search_desc); xfree (ctrl->server_local->multi_search_store); ctrl->server_local->multi_search_desc = desc; ctrl->server_local->multi_search_store = store; ctrl->server_local->multi_search_desc_size = n; } /* Actually store. We need to fix up the const pointers by * copies from our backing store. */ desc = &(ctrl->server_local->multi_search_desc [ctrl->server_local->multi_search_desc_len]); store = &(ctrl->server_local->multi_search_store [ctrl->server_local->multi_search_desc_len]); *desc = ctrl->server_local->search_desc; if (ctrl->server_local->search_desc.sn) { xfree (store->sn); store->sn = xtrymalloc (ctrl->server_local->search_desc.snlen); if (!store->sn) { err = gpg_error_from_syserror (); goto leave; } memcpy (store->sn, ctrl->server_local->search_desc.sn, ctrl->server_local->search_desc.snlen); desc->sn = store->sn; } if (ctrl->server_local->search_desc.name_used) { xfree (store->name); store->name = xtrystrdup (ctrl->server_local->search_desc.u.name); if (!store->name) { err = gpg_error_from_syserror (); xfree (store->sn); store->sn = NULL; goto leave; } desc->u.name = store->name; } ctrl->server_local->multi_search_desc_len++; if (opt_more) { /* We need to be called again with more pattern. */ ctrl->server_local->search_expecting_more = 1; goto leave; } ctrl->server_local->search_expecting_more = 0; /* Continue with the actual search. */ } else ctrl->server_local->multi_search_desc_len = 0; ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; ctrl->filter_opgp = opt_openpgp; ctrl->filter_x509 = opt_x509; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 1); else err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1); if (err) goto leave; /* Set a flag for use by NEXT. */ ctrl->server_local->search_any_found = 1; leave: if (err) ctrl->server_local->multi_search_desc_len = 0; ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } static const char hlp_next[] = "NEXT [--no-data]\n" "\n" "Get the next search result from a previous search."; static gpg_error_t cmd_next (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_no_data; gpg_error_t err; opt_no_data = has_option (line, "--no-data"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_INV_ARG, "no args expected"); goto leave; } if (!ctrl->server_local->search_any_found) { err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH"); goto leave; } ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) { /* The next condition should never be true but we better handle * the first/next transition anyway. */ if (ctrl->server_local->multi_search_desc[0].mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 0); } else { /* We need to do the transition from first to next here. */ if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0); } if (err) goto leave; leave: ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } static const char hlp_store[] = "STORE [--update|--insert]\n" "\n" "Insert a key into the database. Whether to insert or update\n" "the key is decided by looking at the primary key's fingerprint.\n" "With option --update the key must already exist.\n" "With option --insert the key must not already exist.\n" "The actual key material is requested by this function using\n" " INQUIRE BLOB"; static gpg_error_t cmd_store (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_update, opt_insert; enum kbxd_store_modes mode; gpg_error_t err; unsigned char *value = NULL; size_t valuelen; opt_update = has_option (line, "--update"); opt_insert = has_option (line, "--insert"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_INV_ARG, "no args expected"); goto leave; } if (opt_update && !opt_insert) mode = KBXD_STORE_UPDATE; else if (!opt_update && opt_insert) mode = KBXD_STORE_INSERT; else mode = KBXD_STORE_AUTO; /* Ask for the key material. */ err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data received. */ { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = kbxd_store (ctrl, value, valuelen, mode); leave: xfree (value); return leave_cmd (ctx, err); } static const char hlp_delete[] = "DELETE \n" "\n" "Delete a key into the database. The UBID identifies the key.\n"; static gpg_error_t cmd_delete (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int n; unsigned char ubid[UBID_LEN]; line = skip_options (line); if (!*line) { err = set_error (GPG_ERR_INV_ARG, "UBID missing"); goto leave; } /* Skip an optional UBID identifier character. */ if (*line == '^' && line[1]) line++; if ((n=hex2bin (line, ubid, UBID_LEN)) < 0) { err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID"); goto leave; } if (line[n]) { err = set_error (GPG_ERR_INV_ARG, "garbage after UBID"); goto leave; } err = kbxd_delete (ctrl, ubid); leave: return leave_cmd (ctx, err); } static const char hlp_transaction[] = "TRANSACTION [begin|commit|rollback]\n" "\n" "For bulk import of data it is often useful to run everything\n" "in one transaction. This can be achieved with this command.\n" "If the last connection of client is closed before a commit\n" "or rollback an implicit rollback is done. With no argument\n" "the status of the current transaction is returned."; static gpg_error_t cmd_transaction (assuan_context_t ctx, char *line) { gpg_error_t err = 0; line = skip_options (line); if (!strcmp (line, "begin")) { /* Note that we delay the actual transaction until we have to * use SQL. */ if (opt.in_transaction) err = set_error (GPG_ERR_CONFLICT, "already in a transaction"); else { opt.in_transaction = 1; opt.transaction_pid = assuan_get_pid (ctx); } } else if (!strcmp (line, "commit")) { if (!opt.in_transaction) err = set_error (GPG_ERR_CONFLICT, "not in a transaction"); else if (opt.transaction_pid != assuan_get_pid (ctx)) err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction"); else err = kbxd_commit (); } else if (!strcmp (line, "rollback")) { if (!opt.in_transaction) err = set_error (GPG_ERR_CONFLICT, "not in a transaction"); else if (opt.transaction_pid != assuan_get_pid (ctx)) err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction"); else err = kbxd_rollback (); } else if (!*line) { if (opt.in_transaction && opt.transaction_pid == assuan_get_pid (ctx)) err = assuan_set_okay_line (ctx, opt.active_transaction? "active transaction" : "pending transaction"); else if (opt.in_transaction) err = assuan_set_okay_line (ctx, opt.active_transaction? "active transaction on other client" : "pending transaction on other client"); else err = set_error (GPG_ERR_FALSE, "no transaction"); } else { err = set_error (GPG_ERR_ASS_PARAMETER, "unknown transaction command"); } return leave_cmd (ctx, err); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "socket_name - Return the name of the socket.\n" "session_id - Return the current session_id.\n" "getenv NAME - Return value of envvar NAME\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char numbuf[50]; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_kbxd_socket_name (); if (!s) s = "[none]"; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "session_id")) { snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else err = assuan_send_data (ctx, s, strlen (s)); } } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killkeyboxd[] = "KILLKEYBOXD\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this keyboxd process.\n"; static gpg_error_t cmd_killkeyboxd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloadkeyboxd[] = "RELOADKEYBOXD\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadkeyboxd (assuan_context_t ctx, char *line) { (void)ctx; (void)line; kbxd_sighup_action (); return 0; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used."; /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SEARCH", cmd_search, hlp_search }, { "NEXT", cmd_next, hlp_next }, { "STORE", cmd_store, hlp_store }, { "DELETE", cmd_delete, hlp_delete }, { "TRANSACTION",cmd_transaction,hlp_transaction }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "OUTPUT", NULL, hlp_output }, { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd }, { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; (void)ctrl; return 0; } /* This function is called by our assuan log handler to test whether a * log message shall really be printed. The function must return * false to inhibit the logging of MSG. CAT gives the requested log * category. MSG might be NULL. */ int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)cat; (void)msg; if (!ctrl || !ctrl->server_local) return 1; /* Can't decide - allow logging. */ if (!ctrl->server_local->inhibit_data_logging) return 1; /* Not requested - allow logging. */ /* Disallow logging if *_now is true. */ return !ctrl->server_local->inhibit_data_logging_now; } /* Startup the server and run the main command loop. With FD = -1, * use stdin/stdout. SESSION_ID is either 0 or a unique number * identifying a session. */ void kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) { static const char hello[] = "Keyboxd " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), gpg_strerror (gpg_error_from_syserror ())); return; } ctrl->server_local->client_pid = ASSUAN_INVALID_PID; rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } if (fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, (ASSUAN_SOCKET_SERVER_ACCEPTED |ASSUAN_SOCKET_SERVER_FDPASSING)); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); kbxd_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), /*opt.config_filename? opt.config_filename :*/ "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); ctrl->server_local->session_id = session_id; /* Put the session int a list. */ ctrl->server_local->next_session = session_list; session_list = ctrl->server_local; - - /* The next call enable the use of status_printf. */ - set_assuan_context_func (get_assuan_ctx_from_ctrl); - for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif ctrl->server_local->client_pid = assuan_get_pid (ctx); rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } if (opt.in_transaction && opt.transaction_pid == ctrl->server_local->client_pid) { struct server_local_s *sl; pid_t thispid = ctrl->server_local->client_pid; int npids = 0; /* Only if this is the last connection rollback the transaction. */ for (sl = session_list; sl; sl = sl->next_session) if (sl->client_pid == thispid) npids++; if (npids == 1) kbxd_rollback (); } assuan_close_output_fd (ctx); - set_assuan_context_func (NULL); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) kbxd_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { if (session_list == ctrl->server_local) session_list = ctrl->server_local->next_session; else { struct server_local_s *sl; for (sl=session_list; sl->next_session; sl = sl->next_session) if (sl->next_session == ctrl->server_local) break; if (!sl->next_session) BUG (); sl->next_session = ctrl->server_local->next_session; } xfree (ctrl->server_local->multi_search_desc); if (ctrl->server_local->multi_search_store) { size_t nn; for (nn=0; nn < ctrl->server_local->multi_search_desc_size; nn++) { xfree (ctrl->server_local->multi_search_store[nn].sn); xfree (ctrl->server_local->multi_search_store[nn].name); } xfree (ctrl->server_local->multi_search_store); } xfree (ctrl->server_local); ctrl->server_local = NULL; } } diff --git a/kbx/keyboxd.h b/kbx/keyboxd.h index 3a80f9b64..3fea6addd 100644 --- a/kbx/keyboxd.h +++ b/kbx/keyboxd.h @@ -1,178 +1,180 @@ /* keyboxd.h - Global definitions for keyboxd * Copyright (C) 2018 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef KEYBOXD_H #define KEYBOXD_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_KEYBOX #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ /* A large struct name "opt" to keep global flags */ EXTERN_UNLESS_MAIN_MODULE struct { unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ int verbose; /* Verbosity level */ int quiet; /* Be as quiet as possible */ int dry_run; /* Don't change any persistent data */ /* True if we are running detached from the tty. */ int running_detached; /* * Global state variables. */ /* Whether a global transaction has been requested along with the * caller's pid and whether a transaction is active. */ pid_t transaction_pid; unsigned int in_transaction : 1; unsigned int active_transaction : 1; } opt; /* Bit values for the --debug option. */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ #define DBG_CLOCK_VALUE 4096 /* debug timings (required build option). */ #define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ /* Test macros for the debug option. */ #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) /* Declaration of a database request object. This is used for all * database operation (search, insert, update, delete). */ struct db_request_s; typedef struct db_request_s *db_request_t; /* Forward reference for local definitions in command.c. */ struct server_local_s; #if SIZEOF_UNSIGNED_LONG == 8 # define SERVER_CONTROL_MAGIC 0x6b6579626f786420 #else # define SERVER_CONTROL_MAGIC 0x6b627864 #endif /* Collection of data per session (aka connection). */ struct server_control_s { unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */ int refcount; /* Count additional references to this object. */ /* Private data used to fire up the connection thread. We use this * structure do avoid an extra allocation for only a few bytes while * spawning a new connection thread. */ struct { gnupg_fd_t fd; } thread_startup; /* Private data of the server (kbxserver.c). */ struct server_local_s *server_local; /* Environment settings for the connection. */ char *lc_messages; /* Miscellaneous info on the connection. */ unsigned long client_pid; int client_uid; /* The database request object used with a connection. It is * auto-created as needed. */ db_request_t db_req; /* Flags for the current request. */ /* If the any of the filter flags are set a search returns only * results with a blob type matching one of these filter flags. */ unsigned int filter_opgp : 1; unsigned int filter_x509 : 1; /* Used by SEARCH and NEXT. */ unsigned int no_data_return : 1; }; /* This is a special version of the usual _() gettext macro. It * assumes a server connection control variable with the name "ctrl" * and uses that to translate a string according to the locale set for * the connection. The macro LunderscoreIMPL is used by i18n to * actually define the inline function when needed. */ #if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) #define L_(a) keyboxd_Lunderscore (ctrl, (a)) #define LunderscorePROTO \ static inline const char *keyboxd_Lunderscore (ctrl_t ctrl, \ const char *string) \ GNUPG_GCC_ATTR_FORMAT_ARG(2); #define LunderscoreIMPL \ static inline const char * \ keyboxd_Lunderscore (ctrl_t ctrl, const char *string) \ { \ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ /* */: gettext (string); \ } #else #define L_(a) (a) #endif enum kbxd_store_modes { KBXD_STORE_AUTO = 0, /* Update or insert. */ KBXD_STORE_INSERT, /* Allow only inserts. */ KBXD_STORE_UPDATE /* Allow only updates. */ }; /*-- keyboxd.c --*/ void kbxd_exit (int rc) GPGRT_ATTR_NORETURN; void kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl); const char *get_kbxd_socket_name (void); int get_kbxd_active_connection_count (void); void kbxd_sighup_action (void); /*-- kbxserver.c --*/ +gpg_error_t kbxd_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...); gpg_error_t kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size); void kbxd_start_command_handler (ctrl_t, gnupg_fd_t, unsigned int); #endif /*KEYBOXD_H*/