diff --git a/common/userids.c b/common/userids.c index 0f03896ee..9d866d583 100644 --- a/common/userids.c +++ b/common/userids.c @@ -1,490 +1,500 @@ /* userids.c - Utility functions for user ids. * Copyright (C) 2001, 2003, 2004, 2006, * 2009 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * This file 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 . */ #include #include #include #include #include "util.h" #include "userids.h" /* Parse the user-id NAME and build a search description for it. * Returns 0 on success or an error code. DESC may be NULL to merely * check the validity of a user-id. * * Some used rules: * - If the username starts with 8,9,16 or 17 hex-digits (the first one * must be in the range 0..9), this is considered a keyid; depending * on the length a short or complete one. * - If the username starts with 32,33,40 or 41 hex-digits (the first one * must be in the range 0..9), this is considered a fingerprint. * - If the username starts with a left angle, we assume it is a complete * email address and look only at this part. * - If the username starts with a colon we assume it is a unified * key specfification. * - If the username starts with a '.', we assume it is the ending * part of an email address * - If the username starts with an '@', we assume it is a part of an * email address * - If the userid start with an '=' an exact compare is done. * - If the userid starts with a '*' a case insensitive substring search is * done (This is the default). * - If the userid starts with a '+' we will compare individual words * and a match requires that all the words are in the userid. * Words are delimited by white space or "()<>[]{}.@-+_,;/&!" * (note that you can't search for these characters). Compare * is not case sensitive. * - If the userid starts with a '&' a 40 hex digits keygrip is expected. * - If the userid starts with a '^' followed by 40 hex digits it describes * a Unique-Blob-ID (UBID) which is the hash of keyblob or certificate as * stored in the database. This is used in the IPC of the keyboxd. */ gpg_error_t classify_user_id (const char *name, KEYDB_SEARCH_DESC *desc, int openpgp_hack) { const char *s; char *s2 = NULL; int rc = 0; int hexprefix = 0; int hexlength; int mode = 0; KEYDB_SEARCH_DESC dummy_desc; if (!desc) desc = &dummy_desc; /* Clear the structure so that the mode field is set to zero unless we set it to the correct value right at the end of this function. */ memset (desc, 0, sizeof *desc); /* Skip leading and trailing spaces. */ for(s = name; *s && spacep (s); s++ ) ; if (*s && spacep (s + strlen(s) - 1)) { s2 = xtrystrdup (s); if (!s2) { rc = gpg_error_from_syserror (); goto out; } trim_trailing_spaces (s2); s = s2; } switch (*s) { case 0: /* Empty string is an error. */ rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; case '.': /* An email address, compare from end. Note that this has not yet been implemented in the search code. */ mode = KEYDB_SEARCH_MODE_MAILEND; s++; desc->u.name = s; + desc->name_used = 1; break; case '<': /* An email address. */ mode = KEYDB_SEARCH_MODE_MAIL; /* FIXME: The keyring code in g10 assumes that the mail name is prefixed with an '<'. However the keybox code used for sm/ assumes it has been removed. For now we use this simple hack to overcome the problem. */ if (!openpgp_hack) s++; desc->u.name = s; + desc->name_used = 1; break; case '@': /* Part of an email address. */ mode = KEYDB_SEARCH_MODE_MAILSUB; s++; desc->u.name = s; + desc->name_used = 1; break; case '=': /* Exact compare. */ mode = KEYDB_SEARCH_MODE_EXACT; s++; desc->u.name = s; + desc->name_used = 1; break; case '*': /* Case insensitive substring search. */ mode = KEYDB_SEARCH_MODE_SUBSTR; s++; desc->u.name = s; + desc->name_used = 1; break; case '+': /* Compare individual words. Note that this has not yet been implemented in the search code. */ mode = KEYDB_SEARCH_MODE_WORDS; s++; desc->u.name = s; + desc->name_used = 1; break; case '/': /* Subject's DN. */ s++; if (!*s || spacep (s)) /* No DN or prefixed with a space. */ { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.name = s; + desc->name_used = 1; mode = KEYDB_SEARCH_MODE_SUBJECT; break; case '#': /* S/N with optional issuer id or just issuer id. */ { const char *si; s++; if ( *s == '/') { /* "#/" indicates an issuer's DN. */ s++; if (!*s || spacep (s)) /* No DN or prefixed with a space. */ { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.name = s; + desc->name_used = 1; mode = KEYDB_SEARCH_MODE_ISSUER; } else { /* Serialnumber + optional issuer ID. */ for (si=s; *si && *si != '/'; si++) { /* Check for an invalid digit in the serial number. */ if (!strchr("01234567890abcdefABCDEF", *si)) { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } } desc->sn = (const unsigned char*)s; desc->snlen = si - s; desc->snhex = 1; if (!*si) mode = KEYDB_SEARCH_MODE_SN; else { s = si+1; if (!*s || spacep (s)) /* No DN or prefixed with a space. */ { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.name = s; + desc->name_used = 1; mode = KEYDB_SEARCH_MODE_ISSUER_SN; } } } break; case ':': /* Unified fingerprint. */ { const char *se, *si; int i; se = strchr (++s,':'); if (!se) { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } for (i=0,si=s; si < se; si++, i++ ) { if (!strchr("01234567890abcdefABCDEF", *si)) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid digit. */ goto out; } } if (i != 32 && i != 40 && i != 64) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid length of fpr. */ goto out; } for (i=0,si=s; si < se; i++, si +=2) desc->u.fpr[i] = hextobyte(si); desc->fprlen = i; for (; i < 32; i++) desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR; } break; case '&': /* Keygrip*/ { if (hex2bin (s+1, desc->u.grip, 20) < 0) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid. */ goto out; } mode = KEYDB_SEARCH_MODE_KEYGRIP; } break; case '^': /* UBID */ { if (hex2bin (s+1, desc->u.ubid, UBID_LEN) < 0) { rc = gpg_error (GPG_ERR_INV_USER_ID); /* Invalid. */ goto out; } mode = KEYDB_SEARCH_MODE_UBID; } break; default: if (s[0] == '0' && s[1] == 'x') { hexprefix = 1; s += 2; } hexlength = strspn(s, "0123456789abcdefABCDEF"); if (hexlength >= 8 && s[hexlength] =='!') { desc->exact = 1; hexlength++; /* Just for the following check. */ } /* Check if a hexadecimal number is terminated by EOS or blank. */ if (hexlength && s[hexlength] && !spacep (s+hexlength)) { if (hexprefix) /* A "0x" prefix without a correct termination is an error. */ { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } /* The first characters looked like a hex number, but the entire string is not. */ hexlength = 0; } if (desc->exact) hexlength--; /* Remove the bang. */ if ((hexlength == 8 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 9 && *s == '0')) { /* Short keyid. */ if (hexlength == 9) s++; desc->u.kid[1] = strtoul( s, NULL, 16 ); mode = KEYDB_SEARCH_MODE_SHORT_KID; } else if ((hexlength == 16 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 17 && *s == '0')) { /* Long keyid. */ char buf[9]; if (hexlength == 17) s++; mem2str (buf, s, 9); desc->u.kid[0] = strtoul (buf, NULL, 16); desc->u.kid[1] = strtoul (s+8, NULL, 16); mode = KEYDB_SEARCH_MODE_LONG_KID; } else if ((hexlength == 32 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 33 && *s == '0')) { /* MD5 fingerprint. */ int i; if (hexlength == 33) s++; memset (desc->u.fpr+16, 0, 4); for (i=0; i < 16; i++, s+=2) { int c = hextobyte(s); if (c == -1) { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.fpr[i] = c; } desc->fprlen = 16; for (; i < 32; i++) desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR; } else if ((hexlength == 40 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 41 && *s == '0')) { /* SHA1 fingerprint. */ int i; if (hexlength == 41) s++; for (i=0; i < 20; i++, s+=2) { int c = hextobyte(s); if (c == -1) { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.fpr[i] = c; } desc->fprlen = 20; for (; i < 32; i++) desc->u.fpr[i]= 0; mode = KEYDB_SEARCH_MODE_FPR; } else if ((hexlength == 64 && (s[hexlength] == 0 || (s[hexlength] == '!' && s[hexlength + 1] == 0))) || (!hexprefix && hexlength == 65 && *s == '0')) { /* SHA256 fingerprint. */ int i; if (hexlength == 65) s++; for (i=0; i < 32; i++, s+=2) { int c = hextobyte(s); if (c == -1) { rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } desc->u.fpr[i] = c; } desc->fprlen = 32; mode = KEYDB_SEARCH_MODE_FPR; } else if (!hexprefix) { /* The fingerprint of an X.509 listing is often delimited by * colons, so we try to single this case out. Note that the * OpenPGP bang suffix is not supported here. */ desc->exact = 0; mode = 0; hexlength = strspn (s, ":0123456789abcdefABCDEF"); if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) { int i; for (i=0; i < 20; i++, s += 3) { int c = hextobyte(s); if (c == -1 || (i < 19 && s[2] != ':')) break; desc->u.fpr[i] = c; } if (i == 20) { desc->fprlen = 20; mode = KEYDB_SEARCH_MODE_FPR; } for (; i < 32; i++) desc->u.fpr[i]= 0; } if (!mode) { /* Still not found. Now check for a space separated * OpenPGP v4 fingerprint like: * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 * or * 8061 5870 F5BA D690 3336 86D0 F2AD 85AC 1E42 B367 * FIXME: Support OpenPGP v5 fingerprint */ hexlength = strspn (s, " 0123456789abcdefABCDEF"); if (s[hexlength] && s[hexlength] != ' ') hexlength = 0; /* Followed by non-space. */ while (hexlength && s[hexlength-1] == ' ') hexlength--; /* Trim trailing spaces. */ if ((hexlength == 49 || hexlength == 50) && (!s[hexlength] || s[hexlength] == ' ')) { int i, c; for (i=0; i < 20; i++) { if (i && !(i % 2)) { if (*s != ' ') break; s++; /* Skip the double space in the middle but don't require it to help copying fingerprints from sources which fold multiple space to one. */ if (i == 10 && *s == ' ') s++; } c = hextobyte(s); if (c == -1) break; desc->u.fpr[i] = c; s += 2; } if (i == 20) { desc->fprlen = 20; mode = KEYDB_SEARCH_MODE_FPR; } for (; i < 32; i++) desc->u.fpr[i]= 0; } } if (!mode) /* Default to substring search. */ { desc->u.name = s; + desc->name_used = 1; mode = KEYDB_SEARCH_MODE_SUBSTR; } } else { /* Hex number with a prefix but with a wrong length. */ rc = gpg_error (GPG_ERR_INV_USER_ID); goto out; } } desc->mode = mode; out: xfree (s2); return rc; } diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index 0b76cde31..1949e6278 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -1,999 +1,1073 @@ /* 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. */ + * 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; } /* 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) { - KEYBOX_SEARCH_DESC *desc; 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 (!desc) + { + 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]; + { + 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. */ - ctrl->server_local->multi_search_desc - [ctrl->server_local->multi_search_desc_len++] - = ctrl->server_local->search_desc; + /* 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 aagain with more pattern. */ + /* 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 tru but we better handle + /* 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/keybox-search-desc.h b/kbx/keybox-search-desc.h index 9a0df2846..f312da99b 100644 --- a/kbx/keybox-search-desc.h +++ b/kbx/keybox-search-desc.h @@ -1,98 +1,99 @@ /* keybox-search-desc.h - Keybox serch description * Copyright (C) 2001 Free Software Foundation, Inc. * * 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 . */ /* This file is a temporary kludge until we can come up with solution to share this description between keybox and the application specific keydb */ #ifndef KEYBOX_SEARCH_DESC_H #define KEYBOX_SEARCH_DESC_H 1 typedef enum { KEYDB_SEARCH_MODE_NONE, KEYDB_SEARCH_MODE_EXACT, KEYDB_SEARCH_MODE_SUBSTR, KEYDB_SEARCH_MODE_MAIL, KEYDB_SEARCH_MODE_MAILSUB, KEYDB_SEARCH_MODE_MAILEND, KEYDB_SEARCH_MODE_WORDS, KEYDB_SEARCH_MODE_SHORT_KID, KEYDB_SEARCH_MODE_LONG_KID, KEYDB_SEARCH_MODE_FPR, /* (Length of fpr in .fprlen) */ KEYDB_SEARCH_MODE_ISSUER, KEYDB_SEARCH_MODE_ISSUER_SN, KEYDB_SEARCH_MODE_SN, KEYDB_SEARCH_MODE_SUBJECT, KEYDB_SEARCH_MODE_KEYGRIP, KEYDB_SEARCH_MODE_UBID, KEYDB_SEARCH_MODE_FIRST, KEYDB_SEARCH_MODE_NEXT } KeydbSearchMode; /* Identifiers for the public key types we use in GnuPG. */ enum pubkey_types { PUBKEY_TYPE_UNKNOWN = 0, PUBKEY_TYPE_OPGP = 1, PUBKEY_TYPE_X509 = 2 }; /* Forward declaration. See g10/packet.h. */ struct gpg_pkt_user_id_s; typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t; /* A search descriptor. */ struct keydb_search_desc { KeydbSearchMode mode; /* Callback used to filter results. The first parameter is SKIPFUNCVALUE. The second is the keyid. The third is the 1-based index of the UID packet that matched the search criteria (or 0, if none). Return non-zero if the result should be skipped. */ int (*skipfnc)(void *, u32 *, int); void *skipfncvalue; const unsigned char *sn; unsigned short snlen; union { const char *name; unsigned char fpr[32]; u32 kid[2]; /* Note that this is in native endianness. */ unsigned char grip[KEYGRIP_LEN]; unsigned char ubid[UBID_LEN]; } u; + byte name_used;/* The union uses NAME. */ byte snhex; /* SN above is a hexstring and not binary. */ byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */ int exact; /* Use exactly this key ('!' suffix in gpg). */ }; struct keydb_search_desc; typedef struct keydb_search_desc KEYDB_SEARCH_DESC; typedef struct keydb_search_desc KEYBOX_SEARCH_DESC; #endif /*KEYBOX_SEARCH_DESC_H*/