diff options
Diffstat (limited to 'common/userids.c')
-rw-r--r-- | common/userids.c | 435 |
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; +} |