summaryrefslogtreecommitdiffstats
path: root/common/userids.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/userids.c')
-rw-r--r--common/userids.c435
1 files changed, 435 insertions, 0 deletions
diff --git a/common/userids.c b/common/userids.c
new file mode 100644
index 0000000..00f26b7
--- /dev/null
+++ b/common/userids.c
@@ -0,0 +1,435 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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.
+ */
+
+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;
+ 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;
+ break;
+
+ case '@': /* Part of an email address. */
+ mode = KEYDB_SEARCH_MODE_MAILSUB;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '=': /* Exact compare. */
+ mode = KEYDB_SEARCH_MODE_EXACT;
+ s++;
+ desc->u.name = s;
+ break;
+
+ case '*': /* Case insensitive substring search. */
+ mode = KEYDB_SEARCH_MODE_SUBSTR;
+ s++;
+ desc->u.name = s;
+ 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;
+ 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;
+ 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;
+ 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 = -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;
+ 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)
+ {
+ 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);
+ for (; i < 20; 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;
+
+ 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;
+ }
+ mode = KEYDB_SEARCH_MODE_FPR16;
+ }
+ else if ((hexlength == 40
+ && (s[hexlength] == 0
+ || (s[hexlength] == '!' && s[hexlength + 1] == 0)))
+ || (!hexprefix && hexlength == 41 && *s == '0'))
+ {
+ /* SHA1/RMD160 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;
+ }
+ mode = KEYDB_SEARCH_MODE_FPR20;
+ }
+ 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)
+ mode = KEYDB_SEARCH_MODE_FPR20;
+ }
+ 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
+ */
+ 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)
+ mode = KEYDB_SEARCH_MODE_FPR20;
+ }
+ }
+ if (!mode) /* Default to substring search. */
+ {
+ desc->u.name = s;
+ 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;
+}