diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index e37a0a847..ea0da6c9d 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -1,839 +1,839 @@ /* dirmngr-ldap.c - The LDAP helper for dirmngr. * Copyright (C) 2004, 2021 g10 Code GmbH * Copyright (C) 2010 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #include #ifdef HAVE_W32_SYSTEM # include # include # include # include # include "ldap-url.h" #else /* For OpenLDAP, to enable the API that we're using. */ # define LDAP_DEPRECATED 1 # include #endif #include #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/util.h" #include "../common/init.h" #include "ldap-misc.h" /* There is no need for the npth_unprotect and leave functions here; * thus we redefine them to nops. We keep them in the code just for * the case we ever want to reuse parts of the code in npth programs. */ static void npth_unprotect (void) { } static void npth_protect (void) { } #ifdef HAVE_W32_SYSTEM typedef LDAP_TIMEVAL my_ldap_timeval_t; #else typedef struct timeval my_ldap_timeval_t; #endif #define DEFAULT_LDAP_TIMEOUT 15 /* Arbitrary long timeout. */ /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oTimeout = 500, oMulti, oProxy, oHost, oPort, oUser, oPass, oEnvPass, oBase, oAttr, oStartTLS, oLdapTLS, oNtds, oOnlySearchTimeout, oLogWithPID }; /* The list of options as used by the argparse.c code. */ static ARGPARSE_OPTS opts[] = { { oVerbose, "verbose", 0, "verbose" }, { oQuiet, "quiet", 0, "be somewhat more quiet" }, { oTimeout, "timeout", 1, "|N|set LDAP timeout to N seconds"}, { oMulti, "multi", 0, "return all values in" " a record oriented format"}, { oProxy, "proxy", 2, "|NAME|ignore host part and connect through NAME"}, { oStartTLS, "starttls", 0, "use STARTLS for the conenction"}, { oLdapTLS, "ldaptls", 0, "use a TLS for the connection"}, { oNtds, "ntds", 0, "authenticate using AD"}, { oHost, "host", 2, "|NAME|connect to host NAME"}, { oPort, "port", 1, "|N|connect to port N"}, { oUser, "user", 2, "|NAME|use NAME for authentication"}, { oPass, "pass", 2, "|PASS|use password PASS" " for authentication"}, { oEnvPass, "env-pass", 0, "take password from $DIRMNGR_LDAP_PASS"}, { oBase, "base", 2, "|DN|Start query at DN"}, { oAttr, "attr", 2, "|STRING|return the attribute STRING"}, { oOnlySearchTimeout, "only-search-timeout", 0, "@"}, { oLogWithPID,"log-with-pid", 0, "@"}, ARGPARSE_end () }; /* A structure with module options. */ static struct { int quiet; int verbose; my_ldap_timeval_t timeout;/* Timeout for the LDAP search functions. */ unsigned int alarm_timeout; /* And for the alarm based timeout. */ int multi; int starttls; int ldaptls; int ntds; estream_t outstream; /* Send output to this stream. */ /* Note that we can't use const for the strings because ldap_* are not defined that way. */ char *proxy; /* Host and Port override. */ char *user; /* Authentication user. */ char *pass; /* Authentication password. */ char *host; /* Override host. */ int port; /* Override port. */ char *base; /* Override DN. */ char *attr; /* Override attribute. */ } opt; /* Prototypes. */ #ifndef HAVE_W32_SYSTEM static void catch_alarm (int dummy); #endif static gpg_error_t connect_ldap (LDAP **r_ld); static gpg_error_t process_filter (LDAP *ld, const char *string); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "dirmngr_ldap (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = "Please report bugs to <@EMAIL@>.\n"; break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = "Usage: dirmngr_ldap [options] filters (-h for help)\n"; break; case 41: p = ("Syntax: dirmngr_ldap [options] filters\n" "Internal LDAP helper for Dirmngr\n" "Interface and options may change without notice\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv) { ARGPARSE_ARGS pargs; int any_err = 0; char *p; int only_search_timeout = 0; char *malloced_buffer1 = NULL; LDAP *ld; early_system_init (); set_strusage (my_strusage); log_set_prefix ("dirmngr_ldap", GPGRT_LOG_WITH_PREFIX); init_common_subsystems (&argc, &argv); es_set_binary (es_stdout); opt.outstream = es_stdout; /* LDAP defaults */ opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT; opt.timeout.tv_usec = 0; opt.alarm_timeout = 0; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gnupg_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oQuiet: opt.quiet++; break; case oTimeout: opt.timeout.tv_sec = pargs.r.ret_int; opt.timeout.tv_usec = 0; opt.alarm_timeout = pargs.r.ret_int; break; case oOnlySearchTimeout: only_search_timeout = 1; break; case oStartTLS: opt.starttls = 1; opt.ldaptls = 0; break; case oLdapTLS: opt.starttls = 0; opt.ldaptls = 1; break; case oNtds: opt.ntds = 1; break; case oMulti: opt.multi = 1; break; case oUser: opt.user = pargs.r.ret_str; break; case oPass: opt.pass = pargs.r.ret_str; break; case oEnvPass: opt.pass = getenv ("DIRMNGR_LDAP_PASS"); break; case oProxy: opt.proxy = pargs.r.ret_str; break; case oHost: opt.host = pargs.r.ret_str; break; case oPort: opt.port = pargs.r.ret_int; break; case oBase: opt.base = pargs.r.ret_str; break; case oAttr: opt.attr = pargs.r.ret_str; break; case oLogWithPID: { unsigned int oldflags; log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_WITH_PID); } break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (only_search_timeout) opt.alarm_timeout = 0; if (opt.proxy) { malloced_buffer1 = xtrystrdup (opt.proxy); if (!malloced_buffer1) { log_error ("error copying string: %s\n", strerror (errno)); return 1; } opt.host = malloced_buffer1; p = strchr (opt.host, ':'); if (p) { *p++ = 0; opt.port = atoi (p); } if (!opt.port) opt.port = 389; /* make sure ports gets overridden. */ } if (opt.port < 0 || opt.port > 65535) log_error ("invalid port number %d\n", opt.port); if (!opt.port) opt.port = opt.ldaptls? 636 : 389; #ifndef HAVE_W32_SYSTEM if (!opt.host) opt.host = "localhost"; #endif if (log_get_errorcount (0)) exit (2); if (opt.alarm_timeout) { #ifndef HAVE_W32_SYSTEM # if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) struct sigaction act; act.sa_handler = catch_alarm; sigemptyset (&act.sa_mask); act.sa_flags = 0; if (sigaction (SIGALRM,&act,NULL)) # else if (signal (SIGALRM, catch_alarm) == SIG_ERR) # endif log_fatal ("unable to register timeout handler\n"); #endif } if (connect_ldap (&ld)) any_err = 1; else { if (!argc) { if (process_filter (ld, "(objectClass=*)")) any_err = 1; } else { for (; argc; argc--, argv++) if (process_filter (ld, *argv)) any_err = 1; } ldap_unbind (ld); } xfree (malloced_buffer1); return any_err; } #ifndef HAVE_W32_SYSTEM static void catch_alarm (int dummy) { (void)dummy; _exit (10); } #endif #ifdef HAVE_W32_SYSTEM static DWORD CALLBACK alarm_thread (void *arg) { HANDLE timer = arg; WaitForSingleObject (timer, INFINITE); _exit (10); return 0; } #endif static void set_timeout (void) { if (opt.alarm_timeout) { #ifdef HAVE_W32_SYSTEM static HANDLE timer; LARGE_INTEGER due_time; /* A negative value is a relative time. */ due_time.QuadPart = (unsigned long long)-10000000 * opt.alarm_timeout; if (!timer) { SECURITY_ATTRIBUTES sec_attr; DWORD tid; memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; /* Create a manual resetable timer. */ timer = CreateWaitableTimer (NULL, TRUE, NULL); /* Initially set the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); if (CreateThread (&sec_attr, 0, alarm_thread, timer, 0, &tid)) log_error ("failed to create alarm thread\n"); } else /* Retrigger the timer. */ SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); #else alarm (opt.alarm_timeout); #endif } } /* Connect to the ldap server. On success the connection handle is * stored at R_LD. */ static gpg_error_t connect_ldap (LDAP **r_ld) { gpg_error_t err = 0; int lerr; LDAP *ld = NULL; #ifndef HAVE_W32_SYSTEM char *tmpstr; #endif *r_ld = NULL; if (opt.starttls || opt.ldaptls) { #ifndef HAVE_LDAP_START_TLS_S log_error ("ldap: can't connect to the server: no TLS support."); err = GPG_ERR_LDAP_NOT_SUPPORTED; goto leave; #endif } set_timeout (); #ifdef HAVE_W32_SYSTEM npth_unprotect (); ld = ldap_sslinit (opt.host, opt.port, opt.ldaptls); npth_protect (); if (!ld) { lerr = LdapGetLastError (); err = ldap_err_to_gpg_err (lerr); log_error ("error initializing LDAP '%s:%d': %s\n", opt.host, opt.port, ldap_err2string (lerr)); goto leave; } #else /* Unix */ tmpstr = xtryasprintf ("%s://%s:%d", opt.ldaptls? "ldaps" : "ldap", opt.host, opt.port); if (!tmpstr) { err = gpg_error_from_syserror (); goto leave; } npth_unprotect (); lerr = ldap_initialize (&ld, tmpstr); npth_protect (); if (lerr || !ld) { err = ldap_err_to_gpg_err (lerr); log_error ("error initializing LDAP '%s': %s\n", tmpstr, ldap_err2string (lerr)); xfree (tmpstr); goto leave; } xfree (tmpstr); #endif /* Unix */ if (opt.verbose) log_info ("LDAP connected to '%s:%d'%s\n", opt.host, opt.port, opt.starttls? " using STARTTLS" : opt.ldaptls? " using LDAP-over-TLS" : ""); #ifdef HAVE_LDAP_SET_OPTION { int ver = LDAP_VERSION3; lerr = ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &ver); if (lerr != LDAP_SUCCESS) { log_error ("unable to go to LDAP 3: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto leave; } } #endif #ifdef HAVE_LDAP_START_TLS_S if (opt.starttls) { #ifndef HAVE_W32_SYSTEM int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */ lerr = ldap_set_option (ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert); if (lerr) { log_error ("ldap: error setting an TLS option: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto leave; } #else /* On Windows, the certificates are checked by default. If the option to disable checking mentioned above is ever implemented, the way to do that on Windows is to install a callback routine using ldap_set_option (.., LDAP_OPT_SERVER_CERTIFICATE, ..); */ #endif npth_unprotect (); lerr = ldap_start_tls_s (ld, #ifdef HAVE_W32_SYSTEM /* ServerReturnValue, result */ NULL, NULL, #endif /* ServerControls, ClientControls */ NULL, NULL); npth_protect (); if (lerr) { log_error ("ldap: error switching to STARTTLS mode: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto leave; } } #endif if (opt.ntds) { if (opt.verbose) log_info ("binding to current user via AD\n"); #ifdef HAVE_W32_SYSTEM npth_unprotect (); lerr = ldap_bind_s (ld, NULL, NULL, LDAP_AUTH_NEGOTIATE); npth_protect (); if (lerr != LDAP_SUCCESS) { log_error ("error binding to LDAP via AD: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto leave; } #else /* Unix */ err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; #endif /* Unix */ } else if (opt.user) { if (opt.verbose) log_info ("LDAP bind to '%s', password '%s'\n", opt.user, opt.pass ? ">not_shown<" : ">none<"); npth_unprotect (); lerr = ldap_simple_bind_s (ld, opt.user, opt.pass); npth_protect (); if (lerr != LDAP_SUCCESS) { log_error ("error binding to LDAP: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto leave; } } else { /* By default we don't bind as there is usually no need to. */ } leave: if (err) { if (ld) ldap_unbind (ld); } else *r_ld = ld; return err; } /* Helper for fetch_ldap(). */ static int print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr) { LDAPMessage *item; int any = 0; for (npth_unprotect (), item = ldap_first_entry (ld, msg), npth_protect (); item; npth_unprotect (), item = ldap_next_entry (ld, item), npth_protect ()) { BerElement *berctx; char *attr; if (opt.verbose > 1) log_info ("scanning result for attribute '%s'\n", want_attr? want_attr : "[all]"); if (opt.multi) { /* Write item marker. */ if (es_fwrite ("I\0\0\0\0", 5, 1, opt.outstream) != 1) { log_error ("error writing to stdout: %s\n", strerror (errno)); return -1; } } for (npth_unprotect (), attr = ldap_first_attribute (ld, item, &berctx), npth_protect (); attr; npth_unprotect (), attr = ldap_next_attribute (ld, item, berctx), npth_protect ()) { struct berval **values; int idx; if (opt.verbose > 1) log_info (" available attribute '%s'\n", attr); set_timeout (); /* I case we want only one attribute we do a case insensitive compare without the optional extension (i.e. ";binary"). Case insensitive is not really correct but the best we can do. */ if (want_attr) { char *cp1, *cp2; int cmpres; cp1 = strchr (want_attr, ';'); if (cp1) *cp1 = 0; cp2 = strchr (attr, ';'); if (cp2) *cp2 = 0; cmpres = ascii_strcasecmp (want_attr, attr); if (cp1) *cp1 = ';'; if (cp2) *cp2 = ';'; if (cmpres) { ldap_memfree (attr); continue; /* Not found: Try next attribute. */ } } npth_unprotect (); values = ldap_get_values_len (ld, item, attr); npth_protect (); if (!values) { if (opt.verbose) log_info ("attribute '%s' not found\n", attr); ldap_memfree (attr); continue; } if (opt.verbose) { log_info ("found attribute '%s'\n", attr); if (opt.verbose > 1) for (idx=0; values[idx]; idx++) log_info (" length[%d]=%d\n", idx, (int)values[0]->bv_len); } if (opt.multi) { /* Write attribute marker. */ unsigned char tmp[5]; size_t n = strlen (attr); tmp[0] = 'A'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); if (es_fwrite (tmp, 5, 1, opt.outstream) != 1 || es_fwrite (attr, n, 1, opt.outstream) != 1) { log_error ("error writing to stdout: %s\n", strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } for (idx=0; values[idx]; idx++) { if (opt.multi) { /* Write value marker. */ unsigned char tmp[5]; size_t n = values[0]->bv_len; tmp[0] = 'V'; tmp[1] = (n >> 24); tmp[2] = (n >> 16); tmp[3] = (n >> 8); tmp[4] = (n); if (es_fwrite (tmp, 5, 1, opt.outstream) != 1) { log_error ("error writing to stdout: %s\n", strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } } if (es_fwrite (values[0]->bv_val, values[0]->bv_len, 1, opt.outstream) != 1) { log_error ("error writing to stdout: %s\n", strerror (errno)); ldap_value_free_len (values); ldap_memfree (attr); ber_free (berctx, 0); return -1; } any = 1; if (!opt.multi) break; /* Print only the first value. */ } ldap_value_free_len (values); ldap_memfree (attr); if (want_attr || !opt.multi) break; /* We only want to return the first attribute. */ } ber_free (berctx, 0); } if (opt.verbose > 1 && any) log_info ("result has been printed\n"); return any?0:-1; } /* Fetch data from the server at LD using FILTER. */ static int -fetch_ldap (LDAP *ld, const char *base, int scope, const char *filter) +fetch_ldap (LDAP *ld, char *base, int scope, char *filter) { gpg_error_t err; int lerr; LDAPMessage *msg; char *attrs[2]; if (filter && !*filter) filter = NULL; if (opt.verbose) { log_info ("fetching using"); if (base) log_printf (" base '%s'", base); if (filter) log_printf (" filter '%s'", filter); log_printf ("\n"); } attrs[0] = opt.attr; attrs[1] = NULL; set_timeout (); npth_unprotect (); lerr = ldap_search_st (ld, base, scope, filter, attrs, 0, &opt.timeout, &msg); npth_protect (); if (lerr == LDAP_SIZELIMIT_EXCEEDED && opt.multi) { if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, opt.outstream) != 1) { log_error ("error writing to stdout: %s\n", strerror (errno)); return -1; } } else if (lerr) { log_error ("searching '%s' failed: %s\n", filter, ldap_err2string (lerr)); if (lerr != LDAP_NO_SUCH_OBJECT) { /* FIXME: Need deinit (ld)? */ /* Hmmm: Do we need to released MSG in case of an error? */ return -1; } } err = print_ldap_entries (ld, msg, opt.multi? NULL:opt.attr); ldap_msgfree (msg); return err; } /* Main processing. Take the filter and run the LDAP query. The * result is printed to stdout, errors are logged to the log stream. * To allow searching with a different base it is possible to extend * the filer. For example: * * ^CN=foo, OU=My Users&(objectClasses=*) * * Uses "CN=foo, OU=My Users" as base DN and "(objectClasses=*)" as * filter. If the base prefix includes an ampersand, it needs to be * doubled. The usual escaping rules for DNs (for the base) and * filters apply. If no scope is given (see ldap_parse_extfilter for * the syntax) subtree scope is used. */ static gpg_error_t process_filter (LDAP *ld, const char *string) { gpg_error_t err; char *base, *filter; int scope = -1; err = ldap_parse_extfilter (string, 0, &base, &scope, &filter); if (!err) err = fetch_ldap (ld, base? base : opt.base, scope == -1? LDAP_SCOPE_SUBTREE : scope, filter); xfree (base); xfree (filter); return err; } diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index afc7f56ed..6bf19cb1f 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -1,2198 +1,2203 @@ /* ks-engine-ldap.c - talk to a LDAP keyserver * Copyright (C) 2001, 2002, 2004, 2005, 2006 * 2007 Free Software Foundation, Inc. * Copyright (C) 2015, 2020 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 . */ #include #include #include #include #include #ifdef HAVE_GETOPT_H # include #endif #include #include #include "dirmngr.h" #include "misc.h" #include "../common/userids.h" #include "../common/mbox-util.h" #include "ks-engine.h" #include "ldap-misc.h" #include "ldap-parse-uri.h" #include "ldapserver.h" /* Flags with infos from the connected server. */ #define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */ #define SERVERINFO_PGPKEYV2 2 /* Needs "pgpeyV2" instead of "pgpKey" */ #define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */ #define SERVERINFO_NTDS 8 /* Server is an Active Directory. */ #ifndef HAVE_TIMEGM time_t timegm(struct tm *tm); #endif static time_t ldap2epochtime (const char *timestr) { struct tm pgptime; time_t answer; memset (&pgptime, 0, sizeof(pgptime)); /* YYYYMMDDHHmmssZ */ sscanf (timestr, "%4d%2d%2d%2d%2d%2d", &pgptime.tm_year, &pgptime.tm_mon, &pgptime.tm_mday, &pgptime.tm_hour, &pgptime.tm_min, &pgptime.tm_sec); pgptime.tm_year -= 1900; pgptime.tm_isdst = -1; pgptime.tm_mon--; /* mktime() takes the timezone into account, so we use timegm() */ answer = timegm (&pgptime); return answer; } /* Caller must free the result. */ static char * tm2ldaptime (struct tm *tm) { struct tm tmp = *tm; char buf[16]; /* YYYYMMDDHHmmssZ */ tmp.tm_year += 1900; tmp.tm_mon ++; snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ", tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tmp.tm_hour, tmp.tm_min, tmp.tm_sec); return xstrdup (buf); } #if 0 /* Caller must free */ static char * epoch2ldaptime (time_t stamp) { struct tm tm; if (gmtime_r (&stamp, &tm)) return tm2ldaptime (&tm); else return xstrdup ("INVALID TIME"); } #endif static void my_ldap_value_free (char **vals) { if (vals) ldap_value_free (vals); } /* Print a help output for the schemata supported by this module. */ gpg_error_t ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri) { const char data[] = "Handler for LDAP URLs:\n" " ldap://HOST:PORT/[BASEDN]????[bindname=BINDNAME,password=PASSWORD]\n" "\n" "Note: basedn, bindname and password need to be percent escaped. In\n" "particular, spaces need to be replaced with %20 and commas with %2c.\n" "Thus bindname will typically be of the form:\n" "\n" " uid=user%2cou=PGP%20Users%2cdc=EXAMPLE%2cdc=ORG\n" "\n" "The ldaps:// and ldapi:// schemes are also supported. If ldaps is used\n" "then the server's certificate will be checked. If it is not valid, any\n" "operation will be aborted. Note that ldaps means LDAP with STARTTLS\n" "\n" "As an alternative to an URL a string in this form may be used:\n" "\n" " HOST:PORT:BINDNAME:PASSWORD:BASEDN:FLAGS:\n" "\n" "The use of the percent sign or a colon in one of the string values is\n" "currently not supported.\n" "\n" "Supported methods: search, get, put\n"; gpg_error_t err; if(!uri) err = ks_print_help (ctrl, " ldap"); else if (!strcmp (uri->scheme, "ldap") || !strcmp (uri->scheme, "ldaps") || !strcmp (uri->scheme, "ldapi") || uri->opaque) err = ks_print_help (ctrl, data); else err = 0; return err; } /* Convert a keyspec to a filter. Return an error if the keyspec is bad or is not supported. The filter is escaped and returned in *filter. It is the caller's responsibility to free *filter. *filter is only set if this function returns success (i.e., 0). */ static gpg_error_t keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact, unsigned int serverinfo) { /* Remove search type indicator and adjust PATTERN accordingly. Note: don't include a preceding 0x when searching by keyid. */ /* XXX: Should we include disabled / revoke options? */ KEYDB_SEARCH_DESC desc; char *f = NULL; char *freeme = NULL; char *p; gpg_error_t err = classify_user_id (keyspec, &desc, 1); if (err) return err; switch (desc.mode) { case KEYDB_SEARCH_MODE_EXACT: f = xasprintf ("(pgpUserID=%s)", (freeme = ldap_escape_filter (desc.u.name))); break; case KEYDB_SEARCH_MODE_SUBSTR: if (! only_exact) f = xasprintf ("(pgpUserID=*%s*)", (freeme = ldap_escape_filter (desc.u.name))); break; case KEYDB_SEARCH_MODE_MAIL: freeme = ldap_escape_filter (desc.u.name); if (!freeme) break; if (*freeme == '<' && freeme[1] && freeme[2]) { /* Strip angle brackets. Note that it is does not * matter whether we work on the plan or LDAP escaped * version of the mailbox. */ p = freeme + 1; if (p[strlen(p)-1] == '>') p[strlen(p)-1] = 0; } else p = freeme; if ((serverinfo & SERVERINFO_SCHEMAV2)) f = xasprintf ("(&(gpgMailbox=%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))", p); else if (!only_exact) f = xasprintf ("(pgpUserID=*<%s>*)", p); break; case KEYDB_SEARCH_MODE_MAILSUB: if (! only_exact) f = xasprintf ("(pgpUserID=*<*%s*>*)", (freeme = ldap_escape_filter (desc.u.name))); break; case KEYDB_SEARCH_MODE_MAILEND: if (! only_exact) f = xasprintf ("(pgpUserID=*<*%s>*)", (freeme = ldap_escape_filter (desc.u.name))); break; case KEYDB_SEARCH_MODE_SHORT_KID: f = xasprintf ("(pgpKeyID=%08lX)", (ulong) desc.u.kid[1]); break; case KEYDB_SEARCH_MODE_LONG_KID: f = xasprintf ("(pgpCertID=%08lX%08lX)", (ulong) desc.u.kid[0], (ulong) desc.u.kid[1]); break; case KEYDB_SEARCH_MODE_FPR16: case KEYDB_SEARCH_MODE_FPR20: case KEYDB_SEARCH_MODE_FPR: if ((serverinfo & SERVERINFO_SCHEMAV2)) { freeme = bin2hex (desc.u.fpr, 20, NULL); if (!freeme) return gpg_error_from_syserror (); f = xasprintf ("(|(gpgFingerprint=%s)(gpgSubFingerprint=%s))", freeme, freeme); /* FIXME: For an exact search and in case of a match on * gpgSubFingerprint we need to check that there is only one * matching value. */ } break; case KEYDB_SEARCH_MODE_ISSUER: case KEYDB_SEARCH_MODE_ISSUER_SN: case KEYDB_SEARCH_MODE_SN: case KEYDB_SEARCH_MODE_SUBJECT: case KEYDB_SEARCH_MODE_KEYGRIP: case KEYDB_SEARCH_MODE_WORDS: case KEYDB_SEARCH_MODE_FIRST: case KEYDB_SEARCH_MODE_NEXT: default: break; } xfree (freeme); if (! f) { log_error ("Unsupported search mode.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } *filter = f; return 0; } /* Connect to an LDAP server and interrogate it. - uri describes the server to connect to and various options including whether to use TLS and the username and password (see ldap_parse_uri for a description of the various fields). This function returns: - The ldap connection handle in *LDAP_CONNP. - The base DN for the PGP key space by querying the pgpBaseKeySpaceDN attribute (This is normally 'ou=PGP Keys,dc=EXAMPLE,dc=ORG'). - The attribute to lookup to find the pgp key. This is either 'pgpKey' or 'pgpKeyV2'. - Whether this is a real ldap server. (It's unclear what this exactly means.) The values are returned in the passed variables. If you pass NULL, then the value won't be returned. It is the caller's responsibility to release *LDAP_CONNP with ldap_unbind and xfree *BASEDNP. If this function successfully interrogated the server, it returns 0. If there was an LDAP error, it returns the LDAP error code. If an error occurred, *basednp, etc., are undefined (and don't need to be freed.) R_SERVERINFO receives information about the server. If no LDAP error occurred, you still need to check that *basednp is valid. If it is NULL, then the server does not appear to be an OpenPGP Keyserver. */ static gpg_error_t my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, char **r_basedn, char **r_host, int *r_use_tls, unsigned int *r_serverinfo) { gpg_error_t err = 0; int lerr; ldap_server_t server = NULL; LDAP *ldap_conn = NULL; char *basedn = NULL; char *host = NULL; /* Host to use. */ int port; /* Port to use. */ int use_tls; /* 1 = starttls, 2 = ldap-over-tls */ int use_ntds; /* Use Active Directory authentication. */ const char *bindname; const char *password; const char *basedn_arg; #ifndef HAVE_W32_SYSTEM char *tmpstr; #endif if (r_basedn) *r_basedn = NULL; if (r_host) *r_host = NULL; if (r_use_tls) *r_use_tls = 0; *r_serverinfo = 0; if (uri->opaque) { server = ldapserver_parse_one (uri->path, NULL, 0); if (!server) return gpg_error (GPG_ERR_LDAP_OTHER); host = server->host; port = server->port; bindname = server->user; password = bindname? server->pass : NULL; basedn_arg = server->base; use_tls = server->starttls? 1 : server->ldap_over_tls? 2 : 0; use_ntds = server->ntds; } else { host = uri->host; port = uri->port; bindname = uri->auth; password = bindname? uri_query_value (uri, "password") : NULL; basedn_arg = uri->path; use_tls = uri->use_tls ? 1 : 0; use_ntds = uri->ad_current; } if (!port) port = use_tls == 2? 636 : 389; if (host) { host = xtrystrdup (host); if (!host) { err = gpg_error_from_syserror (); goto out; } } if (opt.verbose) log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s'\n", host, port, basedn_arg ? basedn_arg : "", bindname ? bindname : "", password ? "*****" : "", use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain", use_ntds ? ",ntds":""); /* If the uri specifies a secure connection and we don't support TLS, then fail; don't silently revert to an insecure connection. */ if (use_tls) { #ifndef HAVE_LDAP_START_TLS_S log_error ("ldap: can't connect to the server: no TLS support."); err = GPG_ERR_LDAP_NOT_SUPPORTED; goto out; #endif } #ifdef HAVE_W32_SYSTEM /* Note that host==NULL uses the default domain controller. */ npth_unprotect (); ldap_conn = ldap_sslinit (host, port, (use_tls == 2)); npth_protect (); if (!ldap_conn) { lerr = LdapGetLastError (); err = ldap_err_to_gpg_err (lerr); log_error ("error initializing LDAP '%s:%d': %s\n", host, port, ldap_err2string (lerr)); goto out; } #else /* Unix */ tmpstr = xtryasprintf ("%s://%s:%d", use_tls == 2? "ldaps" : "ldap", host, port); if (!tmpstr) { err = gpg_error_from_syserror (); goto out; } npth_unprotect (); lerr = ldap_initialize (&ldap_conn, tmpstr); npth_protect (); if (lerr != LDAP_SUCCESS || !ldap_conn) { err = ldap_err_to_gpg_err (lerr); log_error ("error initializing LDAP '%s': %s\n", tmpstr, ldap_err2string (lerr)); xfree (tmpstr); goto out; } xfree (tmpstr); #endif /* Unix */ #ifdef HAVE_LDAP_SET_OPTION { int ver = LDAP_VERSION3; lerr = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver); if (lerr != LDAP_SUCCESS) { log_error ("ks-ldap: unable to go to LDAP 3: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } } if (opt.ldaptimeout) { int ver = opt.ldaptimeout; lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver); if (lerr != LDAP_SUCCESS) { log_error ("ks-ldap: unable to set LDAP timelimit to %us: %s\n", opt.ldaptimeout, ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } if (opt.verbose) log_info ("ldap timeout set to %us\n", opt.ldaptimeout); } #endif #ifdef HAVE_LDAP_START_TLS_S if (use_tls == 1) { #ifndef HAVE_W32_SYSTEM int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */ lerr = ldap_set_option (ldap_conn, LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert); if (lerr) { log_error ("ldap: error setting an TLS option: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } #else /* On Windows, the certificates are checked by default. If the option to disable checking mentioned above is ever implemented, the way to do that on Windows is to install a callback routine using ldap_set_option (.., LDAP_OPT_SERVER_CERTIFICATE, ..); */ #endif npth_unprotect (); lerr = ldap_start_tls_s (ldap_conn, #ifdef HAVE_W32_SYSTEM /* ServerReturnValue, result */ NULL, NULL, #endif /* ServerControls, ClientControls */ NULL, NULL); npth_protect (); if (lerr) { log_error ("ldap: error switching to STARTTLS mode: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } } #endif if (use_ntds) { #ifdef HAVE_W32_SYSTEM npth_unprotect (); lerr = ldap_bind_s (ldap_conn, NULL, NULL, LDAP_AUTH_NEGOTIATE); npth_protect (); if (lerr != LDAP_SUCCESS) { log_error ("error binding to LDAP via AD: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } #else log_error ("ldap: no Active Directory support but 'ntds' requested\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto out; #endif } else if (bindname) { + npth_unprotect (); - lerr = ldap_simple_bind_s (ldap_conn, bindname, password); + /* Older Windows header dont have the const for the last two args. + * Thus we need to cast to avoid warnings. */ + lerr = ldap_simple_bind_s (ldap_conn, + (char * const)bindname, + (char * const)password); npth_protect (); if (lerr != LDAP_SUCCESS) { log_error ("error binding to LDAP: %s\n", ldap_err2string (lerr)); err = ldap_err_to_gpg_err (lerr); goto out; } } else { /* By default we don't bind as there is usually no need to. */ } if (basedn_arg && *basedn_arg) { /* User specified base DN. In this case we know the server is a * real LDAP server. */ basedn = xtrystrdup (basedn_arg); if (!basedn) { err = gpg_error_from_syserror (); goto out; } *r_serverinfo |= SERVERINFO_REALLDAP; } else { /* Look for namingContexts. */ LDAPMessage *res = NULL; char *attr[] = { "namingContexts", NULL }; npth_unprotect (); lerr = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE, "(objectClass=*)", attr, 0, &res); npth_protect (); if (lerr == LDAP_SUCCESS) { char **context; npth_unprotect (); context = ldap_get_values (ldap_conn, res, "namingContexts"); npth_protect (); if (context) { /* We found some, so try each namingContext as the * search base and look for pgpBaseKeySpaceDN. Because * we found this, we know we're talking to a regular-ish * LDAP server and not an LDAP keyserver. */ int i; char *attr2[] = { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL }; *r_serverinfo |= SERVERINFO_REALLDAP; for (i = 0; context[i] && !basedn; i++) { char **vals; LDAPMessage *si_res; int is_gnupg = 0; { char *object = xasprintf ("cn=pgpServerInfo,%s", context[i]); npth_unprotect (); lerr = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE, "(objectClass=*)", attr2, 0, &si_res); npth_protect (); xfree (object); } if (lerr == LDAP_SUCCESS) { vals = ldap_get_values (ldap_conn, si_res, "pgpBaseKeySpaceDN"); if (vals && vals[0]) { basedn = xtrystrdup (vals[0]); } my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, si_res, "pgpSoftware"); if (vals && vals[0]) { if (opt.debug) log_debug ("Server: \t%s\n", vals[0]); if (!ascii_strcasecmp (vals[0], "GnuPG")) is_gnupg = 1; } my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, si_res, "pgpVersion"); if (vals && vals[0]) { if (opt.debug) log_debug ("Version:\t%s\n", vals[0]); if (is_gnupg) { char *fields[2]; int nfields; nfields = split_fields (vals[0], fields, DIM(fields)); if (nfields > 0 && atoi(fields[0]) > 1) *r_serverinfo |= SERVERINFO_SCHEMAV2; if (nfields > 1 && !ascii_strcasecmp (fields[1], "ntds")) *r_serverinfo |= SERVERINFO_NTDS; } } my_ldap_value_free (vals); } /* From man ldap_search_s: "res parameter of ldap_search_ext_s() and ldap_search_s() should be freed with ldap_msgfree() regardless of return value of these functions. */ ldap_msgfree (si_res); } ldap_value_free (context); } } else /* ldap_search failed. */ { /* We don't have an answer yet, which means the server might be a PGP.com keyserver. */ char **vals; LDAPMessage *si_res = NULL; char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL }; npth_unprotect (); lerr = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE, "(objectClass=*)", attr2, 0, &si_res); npth_protect (); if (lerr == LDAP_SUCCESS) { /* For the PGP LDAP keyserver, this is always * "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be * in the future. */ vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN"); if (vals && vals[0]) { basedn = xtrystrdup (vals[0]); } my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, si_res, "software"); if (vals && vals[0]) { if (opt.debug) log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]); } my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, si_res, "version"); if (vals && vals[0]) { if (opt.debug) log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]); /* If the version is high enough, use the new pgpKeyV2 attribute. This design is iffy at best, but it matches how PGP does it. I figure the NAI folks assumed that there would never be an LDAP keyserver vendor with a different numbering scheme. */ if (atoi (vals[0]) > 1) *r_serverinfo |= SERVERINFO_PGPKEYV2; } my_ldap_value_free (vals); } ldap_msgfree (si_res); } /* From man ldap_search_s: "res parameter of ldap_search_ext_s() and ldap_search_s() should be freed with ldap_msgfree() regardless of return value of these functions. */ ldap_msgfree (res); } out: if (!err && opt.debug) { log_debug ("ldap_conn: %p\n", ldap_conn); log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP) ? "LDAP" : "PGP.com keyserver") ); log_debug ("basedn: %s\n", basedn); log_debug ("pgpkeyattr: %s\n", (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); } ldapserver_list_free (server); if (err) { xfree (basedn); if (ldap_conn) ldap_unbind (ldap_conn); } else { if (r_basedn) *r_basedn = basedn; else xfree (basedn); if (r_host) *r_host = host; else xfree (host); *ldap_connp = ldap_conn; } return err; } /* Extract keys from an LDAP reply and write them out to the output stream OUTPUT in a format GnuPG can import (either the OpenPGP binary format or armored format). */ static void extract_keys (estream_t output, LDAP *ldap_conn, const char *certid, LDAPMessage *message) { char **vals; es_fprintf (output, "INFO %s BEGIN\n", certid); /* Note: ldap_get_values returns a NULL terminated array of strings. */ vals = ldap_get_values (ldap_conn, message, "gpgfingerprint"); if (vals && vals[0] && vals[0][0]) es_fprintf (output, "pub:%s:", vals[0]); else es_fprintf (output, "pub:%s:", certid); my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, message, "pgpkeytype"); if (vals && vals[0]) { if (strcmp (vals[0], "RSA") == 0) es_fprintf (output, "1"); else if (strcmp (vals[0],"DSS/DH") == 0) es_fprintf (output, "17"); } my_ldap_value_free (vals); es_fprintf (output, ":"); vals = ldap_get_values (ldap_conn, message, "pgpkeysize"); if (vals && vals[0]) { int v = atoi (vals[0]); if (v > 0) es_fprintf (output, "%d", v); } my_ldap_value_free (vals); es_fprintf (output, ":"); vals = ldap_get_values (ldap_conn, message, "pgpkeycreatetime"); if (vals && vals[0]) { if (strlen (vals[0]) == 15) es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0])); } my_ldap_value_free (vals); es_fprintf (output, ":"); vals = ldap_get_values (ldap_conn, message, "pgpkeyexpiretime"); if (vals && vals[0]) { if (strlen (vals[0]) == 15) es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0])); } my_ldap_value_free (vals); es_fprintf (output, ":"); vals = ldap_get_values (ldap_conn, message, "pgprevoked"); if (vals && vals[0]) { if (atoi (vals[0]) == 1) es_fprintf (output, "r"); } my_ldap_value_free (vals); es_fprintf (output, "\n"); vals = ldap_get_values (ldap_conn, message, "pgpuserid"); if (vals && vals[0]) { int i; for (i = 0; vals[i]; i++) es_fprintf (output, "uid:%s\n", vals[i]); } my_ldap_value_free (vals); es_fprintf (output, "INFO %s END\n", certid); } /* Get the key described key the KEYSPEC string from the keyserver identified by URI. On success R_FP has an open stream to read the data. */ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) { gpg_error_t err = 0; int ldap_err; unsigned int serverinfo; char *host = NULL; int use_tls; char *filter = NULL; LDAP *ldap_conn = NULL; char *basedn = NULL; estream_t fp = NULL; LDAPMessage *message = NULL; (void) ctrl; if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("LDAP access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } /* Make sure we are talking to an OpenPGP LDAP server. */ err = my_ldap_connect (uri, &ldap_conn, &basedn, &host, &use_tls, &serverinfo); if (err || !basedn) { if (!err) err = gpg_error (GPG_ERR_GENERAL); goto out; } /* Now that we have information about the server we can construct a * query best suited for the capabilities of the server. */ err = keyspec_to_ldap_filter (keyspec, &filter, 1, serverinfo); if (err) goto out; if (opt.debug) log_debug ("ks-ldap: using filter: %s\n", filter); { /* The ordering is significant. Specifically, "pgpcertid" needs to be the second item in the list, since everything after it may be discarded if we aren't in verbose mode. */ char *attrs[] = { "dummy", "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", "gpgfingerprint", NULL }; /* 1 if we want just attribute types; 0 if we want both attribute * types and values. */ int attrsonly = 0; int count; /* Replace "dummy". */ attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; npth_unprotect (); ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, filter, attrs, attrsonly, &message); npth_protect (); if (ldap_err) { err = ldap_err_to_gpg_err (ldap_err); log_error ("ks-ldap: LDAP search error: %s\n", ldap_err2string (ldap_err)); goto out; } count = ldap_count_entries (ldap_conn, message); if (count < 1) { log_info ("ks-ldap: key %s not found on keyserver\n", keyspec); if (count == -1) err = ldap_to_gpg_err (ldap_conn); else err = gpg_error (GPG_ERR_NO_DATA); goto out; } { /* There may be more than one unique result for a given keyID, so we should fetch them all (test this by fetching short key id 0xDEADBEEF). */ /* The set of entries that we've seen. */ strlist_t seen = NULL; LDAPMessage *each; int anykey = 0; for (npth_unprotect (), each = ldap_first_entry (ldap_conn, message), npth_protect (); each; npth_unprotect (), each = ldap_next_entry (ldap_conn, each), npth_protect ()) { char **vals; char **certid; /* Use the long keyid to remove duplicates. The LDAP server returns the same keyid more than once if there are multiple user IDs on the key. Note that this does NOT mean that a keyid that exists multiple times on the keyserver will not be fetched. It means that each KEY, no matter how many user IDs share its keyid, will be fetched only once. If a keyid that belongs to more than one key is fetched, the server quite properly responds with all matching keys. -ds */ certid = ldap_get_values (ldap_conn, each, "pgpcertid"); if (certid && certid[0]) { if (! strlist_find (seen, certid[0])) { /* It's not a duplicate, add it */ add_to_strlist (&seen, certid[0]); if (! fp) fp = es_fopenmem(0, "rw"); extract_keys (fp, ldap_conn, certid[0], each); vals = ldap_get_values (ldap_conn, each, attrs[0]); if (! vals) { err = ldap_to_gpg_err (ldap_conn); log_error("ks-ldap: unable to retrieve key %s " "from keyserver\n", certid[0]); goto out; } else { /* We should strip the new lines. */ es_fprintf (fp, "KEY 0x%s BEGIN\n", certid[0]); es_fputs (vals[0], fp); es_fprintf (fp, "\nKEY 0x%s END\n", certid[0]); ldap_value_free (vals); anykey = 1; } } } my_ldap_value_free (certid); } free_strlist (seen); if (! fp) err = gpg_error (GPG_ERR_NO_DATA); if (!err && anykey) err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s", use_tls? "ldaps" : "ldap", host? host:""); } } out: if (message) ldap_msgfree (message); if (err) { if (fp) es_fclose (fp); } else { if (fp) es_fseek (fp, 0, SEEK_SET); *r_fp = fp; } xfree (basedn); xfree (host); if (ldap_conn) ldap_unbind (ldap_conn); xfree (filter); return err; } /* Search the keyserver identified by URI for keys matching PATTERN. On success R_FP has an open stream to read the data. */ gpg_error_t ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, estream_t *r_fp) { gpg_error_t err; int ldap_err; unsigned int serverinfo; char *filter = NULL; LDAP *ldap_conn = NULL; char *basedn = NULL; estream_t fp = NULL; (void) ctrl; if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("LDAP access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } /* Make sure we are talking to an OpenPGP LDAP server. */ err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) err = GPG_ERR_GENERAL; goto out; } /* Now that we have information about the server we can construct a * query best suited for the capabilities of the server. */ err = keyspec_to_ldap_filter (pattern, &filter, 0, serverinfo); if (err) { log_error ("Bad search pattern: '%s'\n", pattern); goto out; } /* Even if we have no results, we want to return a stream. */ fp = es_fopenmem(0, "rw"); if (!fp) { err = gpg_error_from_syserror (); goto out; } { char **vals; LDAPMessage *res, *each; int count = 0; strlist_t dupelist = NULL; /* The maximum size of the search, including the optional stuff and the trailing \0 */ char *attrs[] = { "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled", "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp", "pgpkeysize", "pgpkeytype", "gpgfingerprint", NULL }; if (opt.debug) log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter); npth_unprotect (); ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res); npth_protect (); xfree (filter); filter = NULL; if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED) { err = ldap_err_to_gpg_err (ldap_err); log_error ("SEARCH %s FAILED %d\n", pattern, err); log_error ("ks-ldap: LDAP search error: %s\n", ldap_err2string (err)); goto out; } /* The LDAP server doesn't return a real count of unique keys, so we can't use ldap_count_entries here. */ for (npth_unprotect (), each = ldap_first_entry (ldap_conn, res), npth_protect (); each; npth_unprotect (), each = ldap_next_entry (ldap_conn, each), npth_protect ()) { char **certid = ldap_get_values (ldap_conn, each, "pgpcertid"); if (certid && certid[0] && ! strlist_find (dupelist, certid[0])) { add_to_strlist (&dupelist, certid[0]); count++; } my_ldap_value_free (certid); } if (ldap_err == LDAP_SIZELIMIT_EXCEEDED) { if (count == 1) log_error ("ks-ldap: search results exceeded server limit." " First 1 result shown.\n"); else log_error ("ks-ldap: search results exceeded server limit." " First %d results shown.\n", count); } free_strlist (dupelist); dupelist = NULL; if (count < 1) es_fputs ("info:1:0\n", fp); else { es_fprintf (fp, "info:1:%d\n", count); for (each = ldap_first_entry (ldap_conn, res); each; each = ldap_next_entry (ldap_conn, each)) { char **certid; LDAPMessage *uids; certid = ldap_get_values (ldap_conn, each, "pgpcertid"); if (!certid || !certid[0]) { my_ldap_value_free (certid); continue; } /* Have we seen this certid before? */ if (! strlist_find (dupelist, certid[0])) { add_to_strlist (&dupelist, certid[0]); vals = ldap_get_values (ldap_conn, each, "gpgfingerprint"); if (vals && vals[0] && vals[0][0]) es_fprintf (fp, "pub:%s:", vals[0]); else es_fprintf (fp, "pub:%s:", certid[0]); my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, each, "pgpkeytype"); if (vals && vals[0]) { /* The LDAP server doesn't exactly handle this well. */ if (strcasecmp (vals[0], "RSA") == 0) es_fputs ("1", fp); else if (strcasecmp (vals[0], "DSS/DH") == 0) es_fputs ("17", fp); } my_ldap_value_free (vals); es_fputc (':', fp); vals = ldap_get_values (ldap_conn, each, "pgpkeysize"); if (vals && vals[0]) { /* Not sure why, but some keys are listed with a key size of 0. Treat that like an unknown. */ if (atoi (vals[0]) > 0) es_fprintf (fp, "%d", atoi (vals[0])); } my_ldap_value_free (vals); es_fputc (':', fp); /* YYYYMMDDHHmmssZ */ vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime"); if(vals && vals[0] && strlen (vals[0]) == 15) { es_fprintf (fp, "%u", (unsigned int) ldap2epochtime(vals[0])); } my_ldap_value_free (vals); es_fputc (':', fp); vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime"); if (vals && vals[0] && strlen (vals[0]) == 15) { es_fprintf (fp, "%u", (unsigned int) ldap2epochtime (vals[0])); } my_ldap_value_free (vals); es_fputc (':', fp); vals = ldap_get_values (ldap_conn, each, "pgprevoked"); if (vals && vals[0]) { if (atoi (vals[0]) == 1) es_fprintf (fp, "r"); } my_ldap_value_free (vals); vals = ldap_get_values (ldap_conn, each, "pgpdisabled"); if (vals && vals[0]) { if (atoi (vals[0]) ==1) es_fprintf (fp, "d"); } my_ldap_value_free (vals); #if 0 /* This is not yet specified in the keyserver protocol, but may be someday. */ es_fputc (':', fp); vals = ldap_get_values (ldap_conn, each, "modifytimestamp"); if(vals && vals[0] strlen (vals[0]) == 15) { es_fprintf (fp, "%u", (unsigned int) ldap2epochtime (vals[0])); } my_ldap_value_free (vals); #endif es_fprintf (fp, "\n"); /* Now print all the uids that have this certid */ for (uids = ldap_first_entry (ldap_conn, res); uids; uids = ldap_next_entry (ldap_conn, uids)) { vals = ldap_get_values (ldap_conn, uids, "pgpcertid"); if (!vals || !vals[0]) { my_ldap_value_free (vals); continue; } if (!ascii_strcasecmp (certid[0], vals[0])) { char **uidvals; es_fprintf (fp, "uid:"); uidvals = ldap_get_values (ldap_conn, uids, "pgpuserid"); if (uidvals) { /* Need to percent escape any colons */ char *quoted = try_percent_escape (uidvals[0], NULL); if (quoted) es_fputs (quoted, fp); xfree (quoted); } my_ldap_value_free (uidvals); es_fprintf (fp, "\n"); } ldap_value_free(vals); } } my_ldap_value_free (certid); } } ldap_msgfree (res); free_strlist (dupelist); } if (opt.debug) log_debug ("SEARCH %s END\n", pattern); out: if (err) { es_fclose (fp); } else { /* Return the read stream. */ if (fp) es_fseek (fp, 0, SEEK_SET); *r_fp = fp; } xfree (basedn); if (ldap_conn) ldap_unbind (ldap_conn); xfree (filter); return err; } /* A modlist describes a set of changes to an LDAP entry. (An entry consists of 1 or more attributes. Attributes are pairs. Note: an attribute may be multi-valued in which case multiple values are associated with a single name.) A modlist is a NULL terminated array of struct LDAPMod's. Thus, if we have: LDAPMod **modlist; Then: modlist[i] Is the ith modification. Each LDAPMod describes a change to a single attribute. Further, there is one modification for each attribute that we want to change. The attribute's new value is stored in LDAPMod.mod_values. If the attribute is multi-valued, we still only use a single LDAPMod structure: mod_values is a NULL-terminated array of strings. To delete an attribute from an entry, we set mod_values to NULL. Thus, if: modlist[i]->mod_values == NULL then we remove the attribute. (Using LDAP_MOD_DELETE doesn't work here as we don't know if the attribute in question exists or not.) Note: this function does NOT copy or free ATTR. It does copy VALUE. */ static void modlist_add (LDAPMod ***modlistp, char *attr, const char *value) { LDAPMod **modlist = *modlistp; LDAPMod **m; int nummods = 0; /* Search modlist for the attribute we're playing with. If modlist is NULL, then the list is empty. Recall: modlist is a NULL terminated array. */ for (m = modlist; m && *m; m++, nummods ++) { /* The attribute is already on the list. */ char **ptr; int numvalues = 0; if (strcasecmp ((*m)->mod_type, attr) != 0) continue; /* We have this attribute already, so when the REPLACE happens, the server attributes will be replaced anyway. */ if (! value) return; /* Attributes can be multi-valued. See if the value is already present. mod_values is a NULL terminated array of pointers. Note: mod_values can be NULL. */ for (ptr = (*m)->mod_values; ptr && *ptr; ptr++) { if (strcmp (*ptr, value) == 0) /* Duplicate value, we're done. */ return; numvalues ++; } /* Append the value. */ ptr = xrealloc ((*m)->mod_values, sizeof (char *) * (numvalues + 2)); (*m)->mod_values = ptr; ptr[numvalues] = xstrdup (value); ptr[numvalues + 1] = NULL; return; } /* We didn't find the attr, so make one and add it to the end */ /* Like attribute values, the list of attributes is NULL terminated array of pointers. */ modlist = xrealloc (modlist, sizeof (LDAPMod *) * (nummods + 2)); *modlistp = modlist; modlist[nummods] = xmalloc (sizeof (LDAPMod)); modlist[nummods]->mod_op = LDAP_MOD_REPLACE; modlist[nummods]->mod_type = attr; if (value) { modlist[nummods]->mod_values = xmalloc (sizeof(char *) * 2); modlist[nummods]->mod_values[0] = xstrdup (value); modlist[nummods]->mod_values[1] = NULL; } else modlist[nummods]->mod_values = NULL; modlist[nummods + 1] = NULL; return; } /* Look up the value of an attribute in the specified modlist. If the attribute is not on the mod list, returns NULL. The result is a NULL-terminated array of strings. Don't change it. */ static char ** modlist_lookup (LDAPMod **modlist, const char *attr) { LDAPMod **m; for (m = modlist; m && *m; m++) { if (strcasecmp ((*m)->mod_type, attr) != 0) continue; return (*m)->mod_values; } return NULL; } /* Dump a modlist to a file. This is useful for debugging. */ static estream_t modlist_dump (LDAPMod **modlist, estream_t output) GPGRT_ATTR_USED; static estream_t modlist_dump (LDAPMod **modlist, estream_t output) { LDAPMod **m; int opened = 0; if (! output) { output = es_fopenmem (0, "rw"); if (!output) return NULL; opened = 1; } for (m = modlist; m && *m; m++) { es_fprintf (output, " %s:", (*m)->mod_type); if (! (*m)->mod_values) es_fprintf(output, " delete.\n"); else { char **ptr; int i; int multi = 0; if ((*m)->mod_values[0] && (*m)->mod_values[1]) /* Have at least 2. */ multi = 1; if (multi) es_fprintf (output, "\n"); for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++) { /* Assuming terminals are about 80 characters wide, display at most about 10 lines of debugging output. If we do trim the buffer, append '...' to the end. */ const int max_len = 10 * 70; size_t value_len = strlen (*ptr); int elide = value_len > max_len; if (multi) es_fprintf (output, " %d. ", i); es_fprintf (output, "`%.*s", max_len, *ptr); if (elide) es_fprintf (output, "...' (%zd bytes elided)", value_len - max_len); else es_fprintf (output, "'"); es_fprintf (output, "\n"); } } } if (opened) es_fseek (output, 0, SEEK_SET); return output; } /* Free all of the memory allocated by the mod list. This assumes that the attribute names don't have to be freed, but the attributes values do. (Which is what modlist_add does.) */ static void modlist_free (LDAPMod **modlist) { LDAPMod **ml; if (! modlist) return; /* Unwind and free the whole modlist structure */ /* The modlist is a NULL terminated array of pointers. */ for (ml = modlist; *ml; ml++) { LDAPMod *mod = *ml; char **ptr; /* The list of values is a NULL termianted array of pointers. If the list is NULL, there are no values. */ if (mod->mod_values) { for (ptr = mod->mod_values; *ptr; ptr++) xfree (*ptr); xfree (mod->mod_values); } xfree (mod); } xfree (modlist); } /* Append two onto the end of one. Two is not freed, but its pointers are now part of one. Make sure you don't free them both! As long as you don't add anything to ONE, TWO is still valid. After that all bets are off. */ static void modlists_join (LDAPMod ***one, LDAPMod **two) { int i, one_count = 0, two_count = 0; LDAPMod **grow; if (!*two) /* two is empty. Nothing to do. */ return; if (!*one) /* one is empty. Just set it equal to *two. */ { *one = two; return; } for (grow = *one; *grow; grow++) one_count ++; for (grow = two; *grow; grow++) two_count ++; grow = xrealloc (*one, sizeof(LDAPMod *) * (one_count + two_count + 1)); for (i = 0; i < two_count; i++) grow[one_count + i] = two[i]; grow[one_count + i] = NULL; *one = grow; } /* Given a string, unescape C escapes. In particular, \xXX. This modifies the string in place. */ static void uncescape (char *str) { size_t r = 0; size_t w = 0; char *first = strchr (str, '\\'); if (! first) /* No backslashes => no escaping. We're done. */ return; /* Start at the first '\\'. */ r = w = (uintptr_t) first - (uintptr_t) str; while (str[r]) { /* XXX: What to do about bad escapes? XXX: hextobyte already checks the string thus the hexdigitp could be removed. */ if (str[r] == '\\' && str[r + 1] == 'x' && str[r+2] && str[r+3] && hexdigitp (str + r + 2) && hexdigitp (str + r + 3)) { int x = hextobyte (&str[r + 2]); log_assert (0 <= x && x <= 0xff); str[w] = x; /* We consumed 4 characters and wrote 1. */ r += 4; w ++; } else str[w ++] = str[r ++]; } str[w] = '\0'; } /* Given one line from an info block (`gpg --list-{keys,sigs} --with-colons KEYID'), pull it apart and fill in the modlist with the relevant (for the LDAP schema) attributes. EXTRACT_STATE should initally be set to 0 by the caller. SCHEMAV2 is set if the server supports the version 2 schema. */ static void extract_attributes (LDAPMod ***modlist, int *extract_state, char *line, int schemav2) { int field_count; char **fields; char *keyid; int is_pub, is_sub, is_uid, is_sig; /* Remove trailing whitespace */ trim_trailing_spaces (line); fields = strsplit (line, ':', '\0', &field_count); if (field_count == 1) /* We only have a single field. There is definitely nothing to do. */ goto out; if (field_count < 7) goto out; is_pub = !ascii_strcasecmp ("pub", fields[0]); is_sub = !ascii_strcasecmp ("sub", fields[0]); is_uid = !ascii_strcasecmp ("uid", fields[0]); is_sig = !ascii_strcasecmp ("sig", fields[0]); if (!ascii_strcasecmp ("fpr", fields[0])) { /* Special treatment for a fingerprint. */ if (!(*extract_state & 1)) goto out; /* Stray fingerprint line - ignore. */ *extract_state &= ~1; if (field_count >= 10 && schemav2) { if ((*extract_state & 2)) modlist_add (modlist, "gpgFingerprint", fields[9]); else modlist_add (modlist, "gpgSubFingerprint", fields[9]); } goto out; } *extract_state &= ~(1|2); if (is_pub) *extract_state |= (1|2); else if (is_sub) *extract_state |= 1; if (!is_pub && !is_sub && !is_uid && !is_sig) goto out; /* Not a relevant line. */ keyid = fields[4]; if (is_uid && strlen (keyid) == 0) ; /* The uid record type can have an empty keyid. */ else if (strlen (keyid) == 16 && strspn (keyid, "0123456789aAbBcCdDeEfF") == 16) ; /* Otherwise, we expect exactly 16 hex characters. */ else { log_error ("malformed record!\n"); goto out; } if (is_pub) { int disabled = 0; int revoked = 0; char *flags; for (flags = fields[1]; *flags; flags ++) switch (*flags) { case 'r': case 'R': revoked = 1; break; case 'd': case 'D': disabled = 1; break; } /* Note: we always create the pgpDisabled and pgpRevoked attributes, regardless of whether the key is disabled/revoked or not. This is because a very common search is like "(&(pgpUserID=*isabella*)(pgpDisabled=0))" */ if (is_pub) { modlist_add (modlist,"pgpDisabled", disabled ? "1" : "0"); modlist_add (modlist,"pgpRevoked", revoked ? "1" : "0"); } } if (is_pub || is_sub) { char padded[6]; int val; val = atoi (fields[2]); if (val < 99999 && val > 0) { /* We zero pad this on the left to make PGP happy. */ snprintf (padded, sizeof padded, "%05u", val); modlist_add (modlist, "pgpKeySize", padded); } } if (is_pub) { char *algo = fields[3]; int val = atoi (algo); switch (val) { case 1: algo = "RSA"; break; case 17: algo = "DSS/DH"; break; default: algo = NULL; break; } if (algo) modlist_add (modlist, "pgpKeyType", algo); } if (is_pub || is_sub || is_sig) { if (is_pub) { modlist_add (modlist, "pgpCertID", keyid); /* Long keyid(!) */ modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid */ } if (is_sub) modlist_add (modlist, "pgpSubKeyID", keyid); /* Long keyid(!) */ } if (is_pub) { char *create_time = fields[5]; if (strlen (create_time) == 0) create_time = NULL; else { char *create_time_orig = create_time; struct tm tm; time_t t; char *end; memset (&tm, 0, sizeof (tm)); /* parse_timestamp handles both seconds fromt he epoch and ISO 8601 format. We also need to handle YYYY-MM-DD format (as generated by gpg1 --with-colons --list-key). Check that first and then if it fails, then try parse_timestamp. */ if (!isodate_human_to_tm (create_time, &tm)) create_time = tm2ldaptime (&tm); else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1 && *end == '\0') { if (!gnupg_gmtime (&t, &tm)) create_time = NULL; else create_time = tm2ldaptime (&tm); } else create_time = NULL; if (! create_time) /* Failed to parse string. */ log_error ("Failed to parse creation time ('%s')", create_time_orig); } if (create_time) { modlist_add (modlist, "pgpKeyCreateTime", create_time); xfree (create_time); } } if (is_pub) { char *expire_time = fields[6]; if (strlen (expire_time) == 0) expire_time = NULL; else { char *expire_time_orig = expire_time; struct tm tm; time_t t; char *end; memset (&tm, 0, sizeof (tm)); /* parse_timestamp handles both seconds fromt he epoch and ISO 8601 format. We also need to handle YYYY-MM-DD format (as generated by gpg1 --with-colons --list-key). Check that first and then if it fails, then try parse_timestamp. */ if (!isodate_human_to_tm (expire_time, &tm)) expire_time = tm2ldaptime (&tm); else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1 && *end == '\0') { if (!gnupg_gmtime (&t, &tm)) expire_time = NULL; else expire_time = tm2ldaptime (&tm); } else expire_time = NULL; if (! expire_time) /* Failed to parse string. */ log_error ("Failed to parse creation time ('%s')", expire_time_orig); } if (expire_time) { modlist_add (modlist, "pgpKeyExpireTime", expire_time); xfree (expire_time); } } if (is_uid && field_count >= 10) { char *uid = fields[9]; char *mbox; uncescape (uid); modlist_add (modlist, "pgpUserID", uid); if (schemav2 && (mbox = mailbox_from_userid (uid))) { modlist_add (modlist, "gpgMailbox", mbox); xfree (mbox); } } out: xfree (fields); } /* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to the keyserver identified by URI. See server.c:cmd_ks_put for the format of the data and metadata. */ gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, void *data, size_t datalen, void *info, size_t infolen) { gpg_error_t err = 0; int ldap_err; unsigned int serverinfo; LDAP *ldap_conn = NULL; char *basedn = NULL; LDAPMod **modlist = NULL; LDAPMod **addlist = NULL; char *data_armored = NULL; int extract_state; /* The last byte of the info block. */ const char *infoend = (const char *) info + infolen - 1; /* Enable this code to dump the modlist to /tmp/modlist.txt. */ #if 0 # warning Disable debug code before checking in. const int dump_modlist = 1; #else const int dump_modlist = 0; #endif estream_t dump = NULL; /* Elide a warning. */ (void) ctrl; if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("LDAP access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo); if (err || !basedn) { if (!err) err = GPG_ERR_GENERAL; goto out; } if (!(serverinfo & SERVERINFO_REALLDAP)) { /* We appear to have a PGP.com Keyserver, which can unpack the * key on its own (not just a dump LDAP server). This will * rarely be the case these days. */ LDAPMod mod; LDAPMod *attrs[2]; char *key[2]; char *dn; key[0] = data; key[1] = NULL; memset (&mod, 0, sizeof (mod)); mod.mod_op = LDAP_MOD_ADD; mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"; mod.mod_values = key; attrs[0] = &mod; attrs[1] = NULL; dn = xtryasprintf ("pgpCertid=virtual,%s", basedn); if (!dn) { err = gpg_error_from_syserror (); goto out; } ldap_err = ldap_add_s (ldap_conn, dn, attrs); xfree (dn); if (ldap_err != LDAP_SUCCESS) { err = ldap_err_to_gpg_err (err); goto out; } goto out; } modlist = xtrymalloc (sizeof (LDAPMod *)); if (!modlist) { err = gpg_error_from_syserror (); goto out; } *modlist = NULL; if (dump_modlist) { dump = es_fopen("/tmp/modlist.txt", "w"); if (! dump) log_error ("failed to open /tmp/modlist.txt: %s\n", gpg_strerror (gpg_error_from_syserror ())); if (dump) { es_fprintf(dump, "data (%zd bytes)\n", datalen); es_fprintf(dump, "info (%zd bytes): '\n", infolen); es_fwrite(info, infolen, 1, dump); es_fprintf(dump, "'\n"); } } /* Start by nulling out all attributes. We try and do a modify operation first, so this ensures that we don't leave old attributes lying around. */ modlist_add (&modlist, "pgpDisabled", NULL); modlist_add (&modlist, "pgpKeyID", NULL); modlist_add (&modlist, "pgpKeyType", NULL); modlist_add (&modlist, "pgpUserID", NULL); modlist_add (&modlist, "pgpKeyCreateTime", NULL); modlist_add (&modlist, "pgpRevoked", NULL); modlist_add (&modlist, "pgpSubKeyID", NULL); modlist_add (&modlist, "pgpKeySize", NULL); modlist_add (&modlist, "pgpKeyExpireTime", NULL); modlist_add (&modlist, "pgpCertID", NULL); if ((serverinfo & SERVERINFO_SCHEMAV2)) { modlist_add (&modlist, "gpgFingerprint", NULL); modlist_add (&modlist, "gpgSubFingerprint", NULL); modlist_add (&modlist, "gpgMailbox", NULL); } /* Assemble the INFO stuff into LDAP attributes */ extract_state = 0; while (infolen > 0) { char *temp = NULL; char *newline = memchr (info, '\n', infolen); if (! newline) /* The last line is not \n terminated! Make a copy so we can add a NUL terminator. */ { temp = xmalloc (infolen + 1); memcpy (temp, info, infolen); info = temp; newline = (char *) info + infolen; } *newline = '\0'; extract_attributes (&addlist, &extract_state, info, (serverinfo & SERVERINFO_SCHEMAV2)); infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1); info = newline + 1; /* Sanity check. */ if (! temp) log_assert ((char *) info + infolen - 1 == infoend); else { log_assert (infolen == -1); xfree (temp); } } modlist_add (&addlist, "objectClass", "pgpKeyInfo"); err = armor_data (&data_armored, data, datalen); if (err) goto out; modlist_add (&addlist, (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey", data_armored); /* Now append addlist onto modlist. */ modlists_join (&modlist, addlist); if (dump) { estream_t input = modlist_dump (modlist, NULL); if (input) { copy_stream (input, dump); es_fclose (input); } } /* Going on the assumption that modify operations are more frequent than adds, we try a modify first. If it's not there, we just turn around and send an add command for the same key. Otherwise, the modify brings the server copy into compliance with our copy. Note that unlike the LDAP keyserver (and really, any other keyserver) this does NOT merge signatures, but replaces the whole key. This should make some people very happy. */ { char **attrval; char *dn; if ((serverinfo & SERVERINFO_NTDS)) { /* The modern way using a CN RDN with the fingerprint. This * has the advantage that we won't have duplicate 64 bit * keyids in the store. In particular NTDS requires the * DN to be unique. */ attrval = modlist_lookup (addlist, "gpgFingerprint"); /* We should have exactly one value. */ if (!attrval || !(attrval[0] && !attrval[1])) { log_error ("ks-ldap: bad gpgFingerprint provided\n"); err = GPG_ERR_GENERAL; goto out; } dn = xtryasprintf ("CN=%s,%s", attrval[0], basedn); } else /* The old style way. */ { attrval = modlist_lookup (addlist, "pgpCertID"); /* We should have exactly one value. */ if (!attrval || !(attrval[0] && !attrval[1])) { log_error ("ks-ldap: bad pgpCertID provided\n"); err = GPG_ERR_GENERAL; goto out; } dn = xtryasprintf ("pgpCertID=%s,%s", attrval[0], basedn); } if (!dn) { err = gpg_error_from_syserror (); goto out; } if (opt.debug) log_debug ("ks-ldap: using DN: %s\n", dn); npth_unprotect (); err = ldap_modify_s (ldap_conn, dn, modlist); if (err == LDAP_NO_SUCH_OBJECT) err = ldap_add_s (ldap_conn, dn, addlist); npth_protect (); xfree (dn); if (err != LDAP_SUCCESS) { log_error ("ks-ldap: error adding key to keyserver: %s\n", ldap_err2string (err)); err = ldap_err_to_gpg_err (err); } } out: if (dump) es_fclose (dump); if (ldap_conn) ldap_unbind (ldap_conn); xfree (basedn); modlist_free (modlist); xfree (addlist); xfree (data_armored); return err; } diff --git a/dirmngr/ldap-url.c b/dirmngr/ldap-url.c index 8308514d6..0cb5d4a27 100644 --- a/dirmngr/ldap-url.c +++ b/dirmngr/ldap-url.c @@ -1,935 +1,954 @@ /* The following code comes from the OpenLDAP project. The references to the COPYRIGHT file below refer to the corresponding file in the OpenLDAP distribution, which is reproduced here in full: Copyright 1998-2004 The OpenLDAP Foundation All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted only as authorized by the OpenLDAP Public License. A copy of this license is available in the file LICENSE in the top-level directory of the distribution or, alternatively, at . OpenLDAP is a registered trademark of the OpenLDAP Foundation. Individual files and/or contributed packages may be copyright by other parties and subject to additional restrictions. This work is derived from the University of Michigan LDAP v3.3 distribution. Information concerning this software is available at . This work also contains materials derived from public sources. Additional information about OpenLDAP can be obtained at . --- Portions Copyright 1998-2004 Kurt D. Zeilenga. Portions Copyright 1998-2004 Net Boolean Incorporated. Portions Copyright 2001-2004 IBM Corporation. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted only as authorized by the OpenLDAP Public License. --- Portions Copyright 1999-2003 Howard Y.H. Chu. Portions Copyright 1999-2003 Symas Corporation. Portions Copyright 1998-2003 Hallvard B. Furuseth. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that this notice is preserved. The names of the copyright holders may not be used to endorse or promote products derived from this software without their specific prior written permission. This software is provided `'as is'' without express or implied warranty. --- Portions Copyright (c) 1992-1996 Regents of the University of Michigan. All rights reserved. Redistribution and use in source and binary forms are permitted provided that this notice is preserved and that due credit is given to the University of Michigan at Ann Arbor. The name of the University may not be used to endorse or promote products derived from this software without specific prior written permission. This software is provided `'as is'' without express or implied warranty. */ #include #include #include #include #include #include #include #include #include "ldap-url.h" #define LDAP_P(protos) protos #define LDAP_URL_URLCOLON "URL:" #define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1) #define LDAP_URL_PREFIX "ldap://" #define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1) #define LDAPS_URL_PREFIX "ldaps://" #define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1) #define LDAPI_URL_PREFIX "ldapi://" #define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1) #define LDAP_VFREE(v) { int _i; for (_i = 0; (v)[_i]; _i++) free((v)[_i]); } #define LDAP_FREE free #define LDAP_STRDUP strdup #define LDAP_CALLOC calloc #define LDAP_MALLOC malloc #define LDAP_REALLOC realloc #define ldap_utf8_strchr strchr #define ldap_utf8_strtok(n,d) strtok (n,d) #define Debug(a,b,c,d,e) void ldap_pvt_hex_unescape( char *s ); #ifndef LDAP_SCOPE_DEFAULT # define LDAP_SCOPE_DEFAULT -1 #endif +#if __GNUC__ +# define MY_GCC_VERSION (__GNUC__ * 10000 \ + + __GNUC_MINOR__ * 100 \ + + __GNUC_PATCHLEVEL__) +#else +# define MY_GCC_VERSION 0 +#endif + + +/* Avoid warnings about strncpy usage. */ +#if MY_GCC_VERSION >= 80000 +# pragma GCC diagnostic ignored "-Wstringop-truncation" +# pragma GCC diagnostic ignored "-Wstringop-overflow" +#elif defined __clang__ +# pragma clang diagnostic ignored "-Wstringop-truncation" +# pragma clang diagnostic ignored "-Wstringop-overflow" +#endif + + /* $OpenLDAP: pkg/ldap/libraries/libldap/charray.c,v 1.9.2.2 2003/03/03 17:10:04 kurt Exp $ */ /* * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. * COPYING RESTRICTIONS APPLY, see COPYRIGHT file */ /* charray.c - routines for dealing with char * arrays */ int ldap_charray_add( char ***a, char *s ) { int n; if ( *a == NULL ) { *a = (char **) LDAP_MALLOC( 2 * sizeof(char *) ); n = 0; if( *a == NULL ) { return -1; } } else { char **new; for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { ; /* NULL */ } new = (char **) LDAP_REALLOC( (char *) *a, (n + 2) * sizeof(char *) ); if( new == NULL ) { /* caller is required to call ldap_charray_free(*a) */ return -1; } *a = new; } (*a)[n] = LDAP_STRDUP(s); if( (*a)[n] == NULL ) { return 1; } (*a)[++n] = NULL; return 0; } int ldap_charray_merge( char ***a, char **s ) { int i, n, nn; char **aa; for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { ; /* NULL */ } for ( nn = 0; s[nn] != NULL; nn++ ) { ; /* NULL */ } aa = (char **) LDAP_REALLOC( (char *) *a, (n + nn + 1) * sizeof(char *) ); if( aa == NULL ) { return -1; } *a = aa; for ( i = 0; i < nn; i++ ) { (*a)[n + i] = LDAP_STRDUP(s[i]); if( (*a)[n + i] == NULL ) { for( --i ; i >= 0 ; i-- ) { LDAP_FREE( (*a)[n + i] ); (*a)[n + i] = NULL; } return -1; } } (*a)[n + nn] = NULL; return 0; } void ldap_charray_free( char **a ) { char **p; if ( a == NULL ) { return; } for ( p = a; *p != NULL; p++ ) { if ( *p != NULL ) { LDAP_FREE( *p ); } } LDAP_FREE( (char *) a ); } int ldap_charray_inlist( char **a, char *s ) { int i; if( a == NULL ) return 0; for ( i=0; a[i] != NULL; i++ ) { if ( strcasecmp( s, a[i] ) == 0 ) { return 1; } } return 0; } char ** ldap_charray_dup( char **a ) { int i; char **new; for ( i = 0; a[i] != NULL; i++ ) ; /* NULL */ new = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); if( new == NULL ) { return NULL; } for ( i = 0; a[i] != NULL; i++ ) { new[i] = LDAP_STRDUP( a[i] ); if( new[i] == NULL ) { for( --i ; i >= 0 ; i-- ) { LDAP_FREE( new[i] ); } LDAP_FREE( new ); return NULL; } } new[i] = NULL; return( new ); } char ** ldap_str2charray( const char *str_in, const char *brkstr ) { char **res; char *str, *s; int i; /* protect the input string from strtok */ str = LDAP_STRDUP( str_in ); if( str == NULL ) { return NULL; } i = 1; for ( s = str; *s; s++ ) { if ( ldap_utf8_strchr( brkstr, *s ) != NULL ) { i++; } } res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); if( res == NULL ) { LDAP_FREE( str ); return NULL; } i = 0; for ( s = ldap_utf8_strtok( str, brkstr); s != NULL; s = ldap_utf8_strtok( NULL, brkstr) ) { res[i] = LDAP_STRDUP( s ); if(res[i] == NULL) { for( --i ; i >= 0 ; i-- ) { LDAP_FREE( res[i] ); } LDAP_FREE( res ); LDAP_FREE( str ); return NULL; } i++; } res[i] = NULL; LDAP_FREE( str ); return( res ); } char * ldap_charray2str( char **a, const char *sep ) { char *s, **v, *p; int len; int slen; if( sep == NULL ) sep = " "; slen = strlen( sep ); len = 0; for ( v = a; *v != NULL; v++ ) { len += strlen( *v ) + slen; } if ( len == 0 ) { return NULL; } /* trim extra sep len */ len -= slen; s = LDAP_MALLOC ( len + 1 ); if ( s == NULL ) { return NULL; } p = s; for ( v = a; *v != NULL; v++ ) { if ( v != a ) { strncpy( p, sep, slen ); p += slen; } len = strlen( *v ); strncpy( p, *v, len ); p += len; } *p = '\0'; return s; } /* $OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.64.2.5 2003/03/03 17:10:05 kurt Exp $ */ /* * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. * COPYING RESTRICTIONS APPLY, see COPYRIGHT file */ /* Portions * Copyright (c) 1996 Regents of the University of Michigan. * All rights reserved. * * LIBLDAP url.c -- LDAP URL (RFC 2255) related routines * * LDAP URLs look like this: * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] * * where: * attributes is a comma separated list * scope is one of these three strings: base one sub (default=base) * filter is an string-represented filter as in RFC 2254 * * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension * * We also tolerate URLs that look like: and */ /* local functions */ static const char* skip_url_prefix LDAP_P(( const char *url, int *enclosedp, const char **scheme )); int ldap_is_ldap_url( LDAP_CONST char *url ) { int enclosed; const char * scheme; if( url == NULL ) { return 0; } if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { return 0; } return 1; } static const char* skip_url_prefix( const char *url, int *enclosedp, const char **scheme ) { /* * return non-zero if this looks like a LDAP URL; zero if not * if non-zero returned, *urlp will be moved past "ldap://" part of URL */ const char *p; if ( url == NULL ) { return( NULL ); } p = url; /* skip leading '<' (if any) */ if ( *p == '<' ) { *enclosedp = 1; ++p; } else { *enclosedp = 0; } /* skip leading "URL:" (if any) */ if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { p += LDAP_URL_URLCOLON_LEN; } /* check for "ldap://" prefix */ if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldap://" prefix and return success */ p += LDAP_URL_PREFIX_LEN; *scheme = "ldap"; return( p ); } /* check for "ldaps://" prefix */ if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldaps://" prefix and return success */ p += LDAPS_URL_PREFIX_LEN; *scheme = "ldaps"; return( p ); } /* check for "ldapi://" prefix */ if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { /* skip over "ldapi://" prefix and return success */ p += LDAPI_URL_PREFIX_LEN; *scheme = "ldapi"; return( p ); } #ifdef LDAP_CONNECTIONLESS /* check for "cldap://" prefix */ if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { /* skip over "cldap://" prefix and return success */ p += LDAPC_URL_PREFIX_LEN; *scheme = "cldap"; return( p ); } #endif return( NULL ); } static int str2scope( const char *p ) { if ( strcasecmp( p, "one" ) == 0 ) { return LDAP_SCOPE_ONELEVEL; } else if ( strcasecmp( p, "onetree" ) == 0 ) { return LDAP_SCOPE_ONELEVEL; } else if ( strcasecmp( p, "base" ) == 0 ) { return LDAP_SCOPE_BASE; } else if ( strcasecmp( p, "sub" ) == 0 ) { return LDAP_SCOPE_SUBTREE; } else if ( strcasecmp( p, "subtree" ) == 0 ) { return LDAP_SCOPE_SUBTREE; } return( -1 ); } int ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) { /* * Pick apart the pieces of an LDAP URL. */ LDAPURLDesc *ludp; char *p, *q, *r; int i, enclosed; const char *scheme = NULL; const char *url_tmp; char *url; if( url_in == NULL || ludpp == NULL ) { return LDAP_URL_ERR_PARAM; } #ifndef LDAP_INT_IN_KERNEL /* Global options may not be created yet * We can't test if the global options are initialized * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate * the options and cause infinite recursion */ #ifdef NEW_LOGGING LDAP_LOG ( OPERATION, ENTRY, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); #else Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); #endif #endif *ludpp = NULL; /* pessimistic */ url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); if ( url_tmp == NULL ) { return LDAP_URL_ERR_BADSCHEME; } assert( scheme ); /* make working copy of the remainder of the URL */ url = LDAP_STRDUP( url_tmp ); if ( url == NULL ) { return LDAP_URL_ERR_MEM; } if ( enclosed ) { p = &url[strlen(url)-1]; if( *p != '>' ) { LDAP_FREE( url ); return LDAP_URL_ERR_BADENCLOSURE; } *p = '\0'; } /* allocate return struct */ ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); if ( ludp == NULL ) { LDAP_FREE( url ); return LDAP_URL_ERR_MEM; } ludp->lud_next = NULL; ludp->lud_host = NULL; ludp->lud_port = 0; ludp->lud_dn = NULL; ludp->lud_attrs = NULL; ludp->lud_filter = NULL; ludp->lud_scope = LDAP_SCOPE_DEFAULT; ludp->lud_filter = NULL; ludp->lud_exts = NULL; ludp->lud_scheme = LDAP_STRDUP( scheme ); if ( ludp->lud_scheme == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } /* scan forward for '/' that marks end of hostport and begin. of dn */ p = strchr( url, '/' ); if( p != NULL ) { /* terminate hostport; point to start of dn */ *p++ = '\0'; } /* IPv6 syntax with [ip address]:port */ if ( *url == '[' ) { r = strchr( url, ']' ); if ( r == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } *r++ = '\0'; q = strchr( r, ':' ); } else { q = strchr( url, ':' ); } if ( q != NULL ) { *q++ = '\0'; ldap_pvt_hex_unescape( q ); if( *q == '\0' ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } ludp->lud_port = atoi( q ); } ldap_pvt_hex_unescape( url ); /* If [ip address]:port syntax, url is [ip and we skip the [ */ ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) ); if( ludp->lud_host == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } /* * Kludge. ldap://111.222.333.444:389??cn=abc,o=company * * On early Novell releases, search references/referrals were returned * in this format, i.e., the dn was kind of in the scope position, * but the required slash is missing. The whole thing is illegal syntax, * but we need to account for it. Fortunately it can't be confused with * anything real. */ if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) { q++; /* ? immediately followed by question */ if( *q == '?') { q++; if( *q != '\0' ) { /* parse dn part */ ldap_pvt_hex_unescape( q ); ludp->lud_dn = LDAP_STRDUP( q ); } else { ludp->lud_dn = LDAP_STRDUP( "" ); } if( ludp->lud_dn == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } } } if( p == NULL ) { LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of dn */ q = strchr( p, '?' ); if( q != NULL ) { /* terminate dn part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse dn part */ ldap_pvt_hex_unescape( p ); ludp->lud_dn = LDAP_STRDUP( p ); } else { ludp->lud_dn = LDAP_STRDUP( "" ); } if( ludp->lud_dn == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } if( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of attributes */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate attributes part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse attributes */ ldap_pvt_hex_unescape( p ); ludp->lud_attrs = ldap_str2charray( p, "," ); if( ludp->lud_attrs == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADATTRS; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of scope */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate the scope part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse the scope */ ldap_pvt_hex_unescape( p ); ludp->lud_scope = str2scope( p ); if( ludp->lud_scope == -1 ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADSCOPE; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of filter */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* terminate the filter part */ *q++ = '\0'; } if( *p != '\0' ) { /* parse the filter */ ldap_pvt_hex_unescape( p ); if( ! *p ) { /* missing filter */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADFILTER; } LDAP_FREE( ludp->lud_filter ); ludp->lud_filter = LDAP_STRDUP( p ); if( ludp->lud_filter == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_MEM; } } if ( q == NULL ) { /* no more */ LDAP_FREE( url ); *ludpp = ludp; return LDAP_URL_SUCCESS; } /* scan forward for '?' that may marks end of extensions */ p = q; q = strchr( p, '?' ); if( q != NULL ) { /* extra '?' */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADURL; } /* parse the extensions */ ludp->lud_exts = ldap_str2charray( p, "," ); if( ludp->lud_exts == NULL ) { LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADEXTS; } for( i=0; ludp->lud_exts[i] != NULL; i++ ) { ldap_pvt_hex_unescape( ludp->lud_exts[i] ); if( *ludp->lud_exts[i] == '!' ) { /* count the number of critical extensions */ ludp->lud_crit_exts++; } } if( i == 0 ) { /* must have 1 or more */ LDAP_FREE( url ); ldap_free_urldesc( ludp ); return LDAP_URL_ERR_BADEXTS; } /* no more */ *ludpp = ludp; LDAP_FREE( url ); return LDAP_URL_SUCCESS; } int ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) { int rc = ldap_url_parse_ext( url_in, ludpp ); if( rc != LDAP_URL_SUCCESS ) { return rc; } if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) { (*ludpp)->lud_scope = LDAP_SCOPE_BASE; } if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') { LDAP_FREE( (*ludpp)->lud_host ); (*ludpp)->lud_host = NULL; } if ((*ludpp)->lud_port == 0) { if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) { (*ludpp)->lud_port = LDAP_PORT; #ifdef LDAP_CONNECTIONLESS } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) { (*ludpp)->lud_port = LDAP_PORT; #endif } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) { (*ludpp)->lud_port = LDAPS_PORT; } } return rc; } void ldap_free_urldesc( LDAPURLDesc *ludp ) { if ( ludp == NULL ) { return; } if ( ludp->lud_scheme != NULL ) { LDAP_FREE( ludp->lud_scheme ); } if ( ludp->lud_host != NULL ) { LDAP_FREE( ludp->lud_host ); } if ( ludp->lud_dn != NULL ) { LDAP_FREE( ludp->lud_dn ); } if ( ludp->lud_filter != NULL ) { LDAP_FREE( ludp->lud_filter); } if ( ludp->lud_attrs != NULL ) { LDAP_VFREE( ludp->lud_attrs ); } if ( ludp->lud_exts != NULL ) { LDAP_VFREE( ludp->lud_exts ); } LDAP_FREE( ludp ); } static int ldap_int_unhex( int c ) { return( c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c - 'a' + 10 ); } void ldap_pvt_hex_unescape( char *s ) { /* * Remove URL hex escapes from s... done in place. The basic concept for * this routine is borrowed from the WWW library HTUnEscape() routine. */ char *p; for ( p = s; *s != '\0'; ++s ) { if ( *s == '%' ) { if ( *++s == '\0' ) { break; } *p = ldap_int_unhex( *s ) << 4; if ( *++s == '\0' ) { break; } *p++ += ldap_int_unhex( *s ); } else { *p++ = *s; } } *p = '\0'; }