diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index 7da3d8b6e..c8091f6f6 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -1,589 +1,589 @@ /* crlfetch.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2006, 2007 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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 2 of the License, or * (at your option) any later version. * * DirMngr 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 "crlfetch.h" #include "dirmngr.h" #include "misc.h" #include "http.h" #include "ks-engine.h" /* For ks_http_fetch. */ #if USE_LDAP # include "ldap-wrapper.h" #endif /* For detecting armored CRLs received via HTTP (yes, such CRLS really exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June 2008) we need a context in the reader callback. */ struct reader_cb_context_s { estream_t fp; /* The stream used with the ksba reader. */ int checked:1; /* PEM/binary detection ahs been done. */ int is_pem:1; /* The file stream is PEM encoded. */ struct b64state b64state; /* The state used for Base64 decoding. */ }; /* We need to associate a reader object with the reader callback context. This table is used for it. */ struct file_reader_map_s { ksba_reader_t reader; struct reader_cb_context_s *cb_ctx; }; #define MAX_FILE_READER 50 static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; /* Associate FP with READER. If the table is full wait until another thread has removed an entry. */ static void register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) { int i; for (;;) { for (i=0; i < MAX_FILE_READER; i++) if (!file_reader_map[i].reader) { file_reader_map[i].reader = reader; file_reader_map[i].cb_ctx = cb_ctx; return; } log_info (_("reader to file mapping table full - waiting\n")); npth_sleep (2); } } /* Scan the table for an entry matching READER, remove that entry and return the associated file pointer. */ static struct reader_cb_context_s * get_file_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx = NULL; int i; for (i=0; i < MAX_FILE_READER; i++) if (file_reader_map[i].reader == reader) { cb_ctx = file_reader_map[i].cb_ctx; file_reader_map[i].reader = NULL; file_reader_map[i].cb_ctx = NULL; break; } return cb_ctx; } static int my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) { struct reader_cb_context_s *cb_ctx = opaque; int result; result = es_read (cb_ctx->fp, buffer, nbytes, nread); if (result) return result; /* Fixme we should check whether the semantics of es_read are okay and well defined. I have some doubts. */ if (nbytes && !*nread && es_feof (cb_ctx->fp)) return gpg_error (GPG_ERR_EOF); if (!nread && es_ferror (cb_ctx->fp)) return gpg_error (GPG_ERR_EIO); if (!cb_ctx->checked && *nread) { int c = *(unsigned char *)buffer; cb_ctx->checked = 1; if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ && (c & 0x1f) == 16 /* sequence */ && (c & 0x20) /* is constructed */ ) ; /* Binary data. */ else { cb_ctx->is_pem = 1; b64dec_start (&cb_ctx->b64state, ""); } } if (cb_ctx->is_pem && *nread) { size_t nread2; if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) { /* EOF from decoder. */ *nread = 0; result = gpg_error (GPG_ERR_EOF); } else *nread = nread2; } return result; } /* Fetch CRL from URL and return the entire CRL using new ksba reader object in READER. Note that this reader object should be closed only using ldap_close_reader. */ gpg_error_t crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) { gpg_error_t err; parsed_uri_t uri; estream_t httpfp = NULL; *reader = NULL; if (!url) return gpg_error (GPG_ERR_INV_ARG); err = http_parse_uri (&uri, url, 0); http_release_parsed_uri (uri); if (!err) /* Yes, our HTTP code groks that. */ { if (opt.disable_http) { log_error (_("CRL access not possible due to disabled %s\n"), "HTTP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else { /* Note that we also allow root certificates loaded from * "/etc/gnupg/trusted-certs/". We also do not consult the * CRL for the TLS connection - that may lead to a loop. * Due to cacert.org redirecting their https URL to http we * also allow such a downgrade. */ err = ks_http_fetch (ctrl, url, (KS_HTTP_FETCH_TRUST_CFG | KS_HTTP_FETCH_NO_CRL | KS_HTTP_FETCH_ALLOW_DOWNGRADE ), &httpfp); } if (err) log_error (_("error retrieving '%s': %s\n"), url, gpg_strerror (err)); else { struct reader_cb_context_s *cb_ctx; cb_ctx = xtrycalloc (1, sizeof *cb_ctx); if (!cb_ctx) err = gpg_error_from_syserror (); else if (!(err = ksba_reader_new (reader))) { cb_ctx->fp = httpfp; err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); if (!err) { /* The ksba reader misses a user pointer thus we * need to come up with our own way of associating a * file pointer (or well the callback context) with * the reader. It is only required when closing the * reader thus there is no performance issue doing * it this way. FIXME: We now have a close * notification which might be used here. */ register_file_reader (*reader, cb_ctx); httpfp = NULL; } } if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (*reader); *reader = NULL; xfree (cb_ctx); } } } else /* Let the LDAP code parse other schemes. */ { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("CRL access not possible due to Tor mode\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else { # if USE_LDAP err = url_fetch_ldap (ctrl, url, NULL, 0, reader); # else /*!USE_LDAP*/ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); # endif /*!USE_LDAP*/ } } es_fclose (httpfp); return err; } /* Fetch CRL for ISSUER using a default server. Return the entire CRL as a newly opened stream returned in R_FP. */ gpg_error_t crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader) { if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("CRL access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", reader); #else (void)ctrl; (void)issuer; (void)reader; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch a CA certificate for DN using the default server. This * function only initiates the fetch; fetch_next_cert must be used to * actually read the certificate; end_cert_fetch to end the * operation. */ gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) { if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("CRL access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP - return start_default_fetch_ldap (ctrl, context, dn, "cACertificate"); + return start_cacert_fetch_ldap (ctrl, context, dn); #else (void)ctrl; (void)context; (void)dn; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server) { if (dirmngr_use_tor ()) { /* For now we do not support LDAP over Tor. */ log_error (_("CRL access not possible due to Tor mode\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (opt.disable_ldap) { log_error (_("certificate search not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return start_cert_fetch_ldap (ctrl, context, patterns, server); #else (void)ctrl; (void)context; (void)patterns; (void)server; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t fetch_next_cert (cert_fetch_context_t context, unsigned char **value, size_t * valuelen) { #if USE_LDAP return fetch_next_cert_ldap (context, value, valuelen); #else (void)context; (void)value; (void)valuelen; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch the next data from CONTEXT, assuming it is a certificate and return * it as a cert object in R_CERT. */ gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) { gpg_error_t err; unsigned char *value; size_t valuelen; ksba_cert_t cert; *r_cert = NULL; #if USE_LDAP err = fetch_next_cert_ldap (context, &value, &valuelen); if (!err && !value) err = gpg_error (GPG_ERR_BUG); #else (void)context; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif if (err) return err; err = ksba_cert_new (&cert); if (err) { xfree (value); return err; } err = ksba_cert_init_from_mem (cert, value, valuelen); xfree (value); if (err) { ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } void end_cert_fetch (cert_fetch_context_t context) { #if USE_LDAP end_cert_fetch_ldap (context); #else (void)context; #endif } /* Read a certificate from an HTTP URL and return it as an estream * memory buffer at R_FP. */ static gpg_error_t read_cert_via_http (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; estream_t fp = NULL; estream_t httpfp = NULL; size_t nread, nwritten; char buffer[1024]; if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_TRUST_CFG, &httpfp))) goto leave; /* We now read the data from the web server into a memory buffer. * To DOS we limit the certificate length to 32k. */ fp = es_fopenmem (32*1024, "rw"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } for (;;) { if (es_read (httpfp, buffer, sizeof buffer, &nread)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", es_fname_get (httpfp), gpg_strerror (err)); goto leave; } if (!nread) break; /* Ready. */ if (es_write (fp, buffer, nread, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (fp), gpg_strerror (err)); goto leave; } else if (nread != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing '%s': %s\n", es_fname_get (fp), "short write"); goto leave; } } es_rewind (fp); *r_fp = fp; fp = NULL; leave: es_fclose (httpfp); es_fclose (fp); return err; } /* Lookup a cert by it's URL. */ gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, unsigned char **value, size_t *valuelen) { const unsigned char *cert_image = NULL; size_t cert_image_n; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; gpg_error_t err; *value = NULL; *valuelen = 0; err = ksba_cert_new (&cert); if (err) goto leave; if (url && (!strncmp (url, "http:", 5) || !strncmp (url, "https:", 6))) { estream_t stream; void *der; size_t derlen; err = read_cert_via_http (ctrl, url, &stream); if (err) goto leave; if (es_fclose_snatch (stream, &der, &derlen)) { err = gpg_error_from_syserror (); goto leave; } err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (err) goto leave; } else /* Assume LDAP. */ { #if USE_LDAP err = url_fetch_ldap (ctrl, url, NULL, 0, &reader); #else (void)ctrl; (void)url; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif /*USE_LDAP*/ if (err) goto leave; err = ksba_cert_read_der (cert, reader); if (err) goto leave; } cert_image = ksba_cert_get_image (cert, &cert_image_n); if (!cert_image || !cert_image_n) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } *value = xtrymalloc (cert_image_n); if (!*value) { err = gpg_error_from_syserror (); goto leave; } memcpy (*value, cert_image, cert_image_n); *valuelen = cert_image_n; leave: ksba_cert_release (cert); #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ return err; } /* This function is to be used to close the reader object. In addition to running ksba_reader_release it also releases the LDAP or HTTP contexts associated with that reader. */ void crl_close_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx; if (!reader) return; /* Check whether this is a HTTP one. */ cb_ctx = get_file_reader (reader); if (cb_ctx) { /* This is an HTTP context. */ if (cb_ctx->fp) es_fclose (cb_ctx->fp); /* Release the base64 decoder state. */ if (cb_ctx->is_pem) b64dec_finish (&cb_ctx->b64state); /* Release the callback context. */ xfree (cb_ctx); } else /* This is an ldap wrapper context (Currently not used). */ { #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ } /* Now get rid of the reader object. */ ksba_reader_release (reader); } diff --git a/dirmngr/crlfetch.h b/dirmngr/crlfetch.h index cf4a3c0aa..3822adb54 100644 --- a/dirmngr/crlfetch.h +++ b/dirmngr/crlfetch.h @@ -1,88 +1,88 @@ /* crlfetch.h - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * * This file is part of DirMngr. * * DirMngr 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 2 of the License, or * (at your option) any later version. * * DirMngr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef CRLFETCH_H #define CRLFETCH_H #include "dirmngr.h" struct cert_fetch_context_s; typedef struct cert_fetch_context_s *cert_fetch_context_t; /* Fetch CRL from URL. */ gpg_error_t crl_fetch (ctrl_t ctrl, const char* url, ksba_reader_t *reader); /* Fetch CRL for ISSUER using default server. */ gpg_error_t crl_fetch_default (ctrl_t ctrl, const char* issuer, ksba_reader_t *reader); /* Fetch cert for DN. */ gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn); /* Query the server for certs matching patterns. */ gpg_error_t start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server); gpg_error_t fetch_next_cert(cert_fetch_context_t context, unsigned char **value, size_t *valuelen); gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert); void end_cert_fetch (cert_fetch_context_t context); /* Lookup a cert by it's URL. */ gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, unsigned char **value, size_t *valuelen); /* Close a reader object. */ void crl_close_reader (ksba_reader_t reader); /*-- ldap.c --*/ gpg_error_t url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port, ksba_reader_t *reader); gpg_error_t attr_fetch_ldap (ctrl_t ctrl, const char *dn, const char *attr, ksba_reader_t *reader); -gpg_error_t start_default_fetch_ldap (ctrl_t ctrl, - cert_fetch_context_t *context, - const char *dn, const char *attr); +gpg_error_t start_cacert_fetch_ldap (ctrl_t ctrl, + cert_fetch_context_t *context, + const char *dn); gpg_error_t start_cert_fetch_ldap( ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server ); gpg_error_t fetch_next_cert_ldap (cert_fetch_context_t context, unsigned char **value, size_t *valuelen ); void end_cert_fetch_ldap (cert_fetch_context_t context); #endif /* CRLFETCH_H */ diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index a9913cbe7..ffe54bade 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -1,881 +1,881 @@ /* ldap.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr 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 2 of the License, or * (at your option) any later version. * * DirMngr 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include "dirmngr.h" #include "../common/exechelp.h" #include "crlfetch.h" #include "ldapserver.h" #include "misc.h" #include "ldap-wrapper.h" #include "../common/host2net.h" #define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "01234567890" \ "$-_.+!*'()," #define USERCERTIFICATE "userCertificate" #define CACERTIFICATE "caCertificate" #define X509CACERT "x509caCert" #define USERSMIMECERTIFICATE "userSMIMECertificate" /* Definition for the context of the cert fetch functions. */ struct cert_fetch_context_s { ksba_reader_t reader; /* The reader used (shallow copy). */ unsigned char *tmpbuf; /* Helper buffer. */ size_t tmpbufsize; /* Allocated size of tmpbuf. */ int truncated; /* Flag to indicate a truncated output. */ }; /* Add HOST and PORT to our list of LDAP servers. Fixme: We should better use an extra list of servers. */ static void add_server_to_servers (const char *host, int port) { ldap_server_t server; ldap_server_t last = NULL; const char *s; if (!port) port = 389; for (server=opt.ldapservers; server; server = server->next) { if (!strcmp (server->host, host) && server->port == port) return; /* already in list... */ last = server; } /* We assume that the host names are all supplied by our configuration files and thus are sane. To keep this assumption we must reject all invalid host names. */ for (s=host; *s; s++) if (!strchr ("abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "01234567890.-", *s)) { log_error (_("invalid char 0x%02x in host name - not added\n"), *s); return; } log_info (_("adding '%s:%d' to the ldap server list\n"), host, port); server = xtrycalloc (1, sizeof *s); if (!server) log_error (_("malloc failed: %s\n"), strerror (errno)); else { server->host = xstrdup (host); server->port = port; if (last) last->next = server; else opt.ldapservers = server; } } /* Perform an LDAP query. Returns an gpg error code or 0 on success. The function returns a new reader object at READER. */ static gpg_error_t run_ldap_wrapper (ctrl_t ctrl, int ignore_timeout, int multi_mode, const char *proxy, const char *host, int port, const char *user, const char *pass, const char *dn, const char *filter, const char *attr, const char *url, ksba_reader_t *reader) { const char *argv[40]; int argc; char portbuf[30], timeoutbuf[30]; *reader = NULL; argc = 0; if (pass) /* Note, that the password must be the first item. */ { argv[argc++] = "--pass"; argv[argc++] = pass; } if (DBG_LOOKUP) argv[argc++] = "-vv"; else if (DBG_EXTPROG) argv[argc++] = "-v"; argv[argc++] = "--log-with-pid"; if (multi_mode) argv[argc++] = "--multi"; if (opt.ldaptimeout) { sprintf (timeoutbuf, "%u", opt.ldaptimeout); argv[argc++] = "--timeout"; argv[argc++] = timeoutbuf; if (ignore_timeout) argv[argc++] = "--only-search-timeout"; } if (proxy) { argv[argc++] = "--proxy"; argv[argc++] = proxy; } if (host) { argv[argc++] = "--host"; argv[argc++] = host; } if (port) { sprintf (portbuf, "%d", port); argv[argc++] = "--port"; argv[argc++] = portbuf; } if (user) { argv[argc++] = "--user"; argv[argc++] = user; } if (dn) { argv[argc++] = "--dn"; argv[argc++] = dn; } if (filter) { argv[argc++] = "--filter"; argv[argc++] = filter; } if (attr) { argv[argc++] = "--attr"; argv[argc++] = attr; } argv[argc++] = url? url : "ldap://"; argv[argc] = NULL; return ldap_wrapper (ctrl, reader, argv); } /* Perform a LDAP query using a given URL. On success a new ksba reader is returned. If HOST or PORT are not 0, they are used to override the values from the URL. */ gpg_error_t url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port, ksba_reader_t *reader) { gpg_error_t err; err = run_ldap_wrapper (ctrl, 1, /* Ignore explicit timeout because CRLs might be very large. */ 0, opt.ldap_proxy, host, port, NULL, NULL, NULL, NULL, NULL, url, reader); /* FIXME: This option might be used for DoS attacks. Because it will enlarge the list of servers to consult without a limit and all LDAP queries w/o a host are will then try each host in turn. */ if (!err && opt.add_new_ldapservers && !opt.ldap_proxy) { if (host) add_server_to_servers (host, port); else if (url) { char *tmp = host_and_port_from_url (url, &port); if (tmp) { add_server_to_servers (tmp, port); xfree (tmp); } } } /* If the lookup failed and we are not only using the proxy, we try again using our default list of servers. */ if (err && !(opt.ldap_proxy && opt.only_ldap_proxy)) { struct ldapserver_iter iter; if (DBG_LOOKUP) log_debug ("no hostname in URL or query failed; " "trying all default hostnames\n"); for (ldapserver_iter_begin (&iter, ctrl); err && ! ldapserver_iter_end_p (&iter); ldapserver_iter_next (&iter)) { ldap_server_t server = iter.server; err = run_ldap_wrapper (ctrl, 0, 0, NULL, server->host, server->port, NULL, NULL, NULL, NULL, NULL, url, reader); if (!err) break; } } return err; } /* Perform an LDAP query on all configured servers. On error the error code of the last try is returned. */ gpg_error_t attr_fetch_ldap (ctrl_t ctrl, const char *dn, const char *attr, ksba_reader_t *reader) { gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION); struct ldapserver_iter iter; *reader = NULL; /* FIXME; we might want to look at the Base SN to try matching servers first. */ for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter); ldapserver_iter_next (&iter)) { ldap_server_t server = iter.server; err = run_ldap_wrapper (ctrl, 0, 0, opt.ldap_proxy, server->host, server->port, server->user, server->pass, dn, "objectClass=*", attr, NULL, reader); if (!err) break; /* Probably found a result. Ready. */ } return err; } /* Parse PATTERN and return a new strlist to be used for the actual LDAP query. Bit 0 of the flags field is set if that pattern is actually a base specification. Caller must release the returned strlist. NULL is returned on error. * Possible patterns: * * KeyID * Fingerprint * OpenPGP userid * x Email address Indicated by a left angle bracket. * Exact word match in user id or subj. name * x Subj. DN indicated bu a leading slash * Issuer DN * Serial number + subj. DN * x Substring match indicated by a leading '*; is also the default. */ strlist_t parse_one_pattern (const char *pattern) { strlist_t result = NULL; char *p; switch (*pattern) { case '<': /* Email. */ { pattern++; result = xmalloc (sizeof *result + 5 + strlen (pattern)); result->next = NULL; result->flags = 0; p = stpcpy (stpcpy (result->d, "mail="), pattern); if (p[-1] == '>') *--p = 0; if (!*result->d) /* Error. */ { xfree (result); result = NULL; } break; } case '/': /* Subject DN. */ pattern++; if (*pattern) { result = xmalloc (sizeof *result + strlen (pattern)); result->next = NULL; result->flags = 1; /* Base spec. */ strcpy (result->d, pattern); } break; case '#': /* Issuer DN. */ pattern++; if (*pattern == '/') /* Just issuer DN. */ { pattern++; } else /* Serial number + issuer DN */ { } break; case '*': pattern++; /* fall through */ default: /* Take as substring match. */ { const char format[] = "(|(sn=*%s*)(|(cn=*%s*)(mail=*%s*)))"; if (*pattern) { result = xmalloc (sizeof *result + strlen (format) + 3 * strlen (pattern)); result->next = NULL; result->flags = 0; sprintf (result->d, format, pattern, pattern, pattern); } } break; } return result; } /* Take the string STRING and escape it according to the URL rules. Return a newly allocated string. */ static char * escape4url (const char *string) { const char *s; char *buf, *p; size_t n; if (!string) string = ""; for (s=string,n=0; *s; s++) if (strchr (UNENCODED_URL_CHARS, *s)) n++; else n += 3; buf = malloc (n+1); if (!buf) return NULL; for (s=string,p=buf; *s; s++) if (strchr (UNENCODED_URL_CHARS, *s)) *p++ = *s; else { sprintf (p, "%%%02X", *(const unsigned char *)s); p += 3; } *p = 0; return buf; } /* Create a LDAP URL from DN and FILTER and return it in URL. We don't need the host and port because this will be specified using the override options. */ static gpg_error_t make_url (char **url, const char *dn, const char *filter) { gpg_error_t err; char *u_dn, *u_filter; char const attrs[] = (USERCERTIFICATE "," /* In 2005 wk mentioned in the changelog that * work on the userSMIMECertificate has * started but it seems that no further * progress was made or the whole thing was * simply forgotten. */ /* USERSMIMECERTIFICATE "," */ CACERTIFICATE "," X509CACERT ); *url = NULL; u_dn = escape4url (dn); if (!u_dn) return gpg_error_from_errno (errno); u_filter = escape4url (filter); if (!u_filter) { err = gpg_error_from_errno (errno); xfree (u_dn); return err; } *url = strconcat ("ldap:///", u_dn, "?", attrs, "?sub?", u_filter, NULL); if (!*url) err = gpg_error_from_syserror (); else err = 0; xfree (u_dn); xfree (u_filter); return err; } -/* Prepare an LDAP query to return the attribute ATTR for the DN. All - configured default servers are queried until one responds. This - function returns an error code or 0 and a CONTEXT on success. */ +/* Prepare an LDAP query to return the cACertificate attribute for DN. + * All configured default servers are queried until one responds. + * This function returns an error code or 0 and stored a newly + * allocated contect object at CONTEXT on success. */ gpg_error_t -start_default_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context, - const char *dn, const char *attr) +start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, + const char *dn) { gpg_error_t err; struct ldapserver_iter iter; - *context = xtrycalloc (1, sizeof **context); - if (!*context) + *r_context = xtrycalloc (1, sizeof **r_context); + if (!*r_context) return gpg_error_from_errno (errno); /* FIXME; we might want to look at the Base SN to try matching servers first. */ err = gpg_error (GPG_ERR_CONFIGURATION); for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter); ldapserver_iter_next (&iter)) { ldap_server_t server = iter.server; err = run_ldap_wrapper (ctrl, 0, - 1, + 1, /* --multi (record format) */ opt.ldap_proxy, server->host, server->port, server->user, server->pass, - dn, "objectClass=*", attr, NULL, - &(*context)->reader); + dn, "objectClass=*", "cACertificate", NULL, + &(*r_context)->reader); if (!err) break; /* Probably found a result. */ } if (err) { - xfree (*context); - *context = NULL; + xfree (*r_context); + *r_context = NULL; } return err; } -/* Prepare an LDAP query to return certificates matching PATTERNS using - the SERVER. This function returns an error code or 0 and a CONTEXT - on success. */ +/* Prepare an LDAP query to return certificates matching PATTERNS + * using the SERVER. This function returns an error code or 0 and + * stores a newly allocated object at R_CONTEXT on success. */ gpg_error_t -start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context, +start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, strlist_t patterns, const ldap_server_t server) { gpg_error_t err; char *proxy = NULL; char *host = NULL; int port; char *user = NULL; char *pass = NULL; const char *base; char *argv[50]; int argc = 0; int argc_malloced = 0; char portbuf[30], timeoutbuf[30]; int use_ldaps = 0; - *context = NULL; + *r_context = NULL; if (opt.ldap_proxy && !(proxy = xtrystrdup (opt.ldap_proxy))) { err = gpg_error_from_syserror (); goto leave; } if (server) { if (server->host && !(host = xtrystrdup (server->host))) { err = gpg_error_from_syserror (); goto leave; } port = server->port; if (server->user && !(user = xtrystrdup (server->user))) { err = gpg_error_from_syserror (); goto leave; } if (server->pass && !(pass = xtrystrdup (server->pass))) { err = gpg_error_from_syserror (); goto leave; } base = server->base; use_ldaps = server->use_ldaps; } else /* Use a default server. */ return gpg_error (GPG_ERR_NOT_IMPLEMENTED); if (!base) base = ""; if (pass) /* Note: Must be the first item. */ { argv[argc++] = "--pass"; argv[argc++] = pass; } if (DBG_LOOKUP) argv[argc++] = "-vv"; else if (DBG_EXTPROG) argv[argc++] = "-v"; argv[argc++] = "--log-with-pid"; argv[argc++] = "--multi"; if (opt.ldaptimeout) { snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout); argv[argc++] = "--timeout"; argv[argc++] = timeoutbuf; } if (opt.ldap_proxy) { argv[argc++] = "--proxy"; argv[argc++] = proxy; } if (use_ldaps) argv[argc++] = "--tls"; if (host) { argv[argc++] = "--host"; argv[argc++] = host; } if (port) { snprintf (portbuf, sizeof portbuf, "%d", port); argv[argc++] = "--port"; argv[argc++] = portbuf; } if (user) { argv[argc++] = "--user"; argv[argc++] = user; } /* All entries in argv from this index on are malloc'ed. */ argc_malloced = argc; for (; patterns; patterns = patterns->next) { strlist_t sl; char *url; if (argc >= DIM (argv) - 1) { /* Too many patterns. It does not make sense to allow an arbitrary number of patters because the length of the command line is limited anyway. */ /* fixme: cleanup. */ return gpg_error (GPG_ERR_RESOURCE_LIMIT); } sl = parse_one_pattern (patterns->d); if (!sl) { log_error (_("start_cert_fetch: invalid pattern '%s'\n"), patterns->d); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if ((sl->flags & 1)) err = make_url (&url, sl->d, "objectClass=*"); else err = make_url (&url, base, sl->d); free_strlist (sl); if (err) goto leave; argv[argc++] = url; } argv[argc] = NULL; - *context = xtrycalloc (1, sizeof **context); - if (!*context) + *r_context = xtrycalloc (1, sizeof **r_context); + if (!*r_context) { err = gpg_error_from_errno (errno); goto leave; } - err = ldap_wrapper (ctrl, &(*context)->reader, (const char**)argv); + err = ldap_wrapper (ctrl, &(*r_context)->reader, (const char**)argv); if (err) { - xfree (*context); - *context = NULL; + xfree (*r_context); + *r_context = NULL; } leave: for (; argc_malloced < argc; argc_malloced++) xfree (argv[argc_malloced]); xfree (proxy); xfree (host); xfree (user); xfree (pass); return err; } /* Read a fixed amount of data from READER into BUFFER. */ static gpg_error_t read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) { gpg_error_t err; size_t nread; while (count) { err = ksba_reader_read (reader, buffer, count, &nread); if (err) return err; buffer += nread; count -= nread; } return 0; } /* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF if no (more) certificates are available or any other error code. GPG_ERR_TRUNCATED may be returned to indicate that the result has been truncated. */ gpg_error_t fetch_next_cert_ldap (cert_fetch_context_t context, unsigned char **value, size_t *valuelen) { gpg_error_t err; unsigned char hdr[5]; char *p, *pend; unsigned long n; int okay = 0; /* int is_cms = 0; */ *value = NULL; *valuelen = 0; err = 0; while (!err) { err = read_buffer (context->reader, hdr, 5); if (err) break; n = buf32_to_ulong (hdr+1); if (*hdr == 'V' && okay) { -#if 0 /* That code is not yet ready. */ - +#if 0 /* That code to extra a cert from a CMS object is not yet ready. */ if (is_cms) { /* The certificate needs to be parsed from CMS data. */ ksba_cms_t cms; ksba_stop_reason_t stopreason; int i; err = ksba_cms_new (&cms); if (err) goto leave; err = ksba_cms_set_reader_writer (cms, context->reader, NULL); if (err) { log_error ("ksba_cms_set_reader_writer failed: %s\n", gpg_strerror (err)); goto leave; } do { err = ksba_cms_parse (cms, &stopreason); if (err) { log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (err)); goto leave; } if (stopreason == KSBA_SR_BEGIN_DATA) log_error ("userSMIMECertificate is not " "a certs-only message\n"); } while (stopreason != KSBA_SR_READY); for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) { check_and_store (ctrl, stats, cert, 0); ksba_cert_release (cert); cert = NULL; } if (!i) log_error ("no certificate found\n"); else any = 1; } else -#endif +#endif /* End unfinished code to extract from a CMS object. */ { *value = xtrymalloc (n); if (!*value) return gpg_error_from_errno (errno); *valuelen = n; err = read_buffer (context->reader, *value, n); break; /* Ready or error. */ } } else if (!n && *hdr == 'A') okay = 0; else if (n) { if (n > context->tmpbufsize) { xfree (context->tmpbuf); context->tmpbufsize = 0; context->tmpbuf = xtrymalloc (n+1); if (!context->tmpbuf) return gpg_error_from_errno (errno); context->tmpbufsize = n; } err = read_buffer (context->reader, context->tmpbuf, n); if (err) break; if (*hdr == 'A') { p = context->tmpbuf; p[n] = 0; /*(we allocated one extra byte for this.)*/ /* fixme: is_cms = 0; */ if ( (pend = strchr (p, ';')) ) *pend = 0; /* Strip off the extension. */ if (!ascii_strcasecmp (p, USERCERTIFICATE)) { if (DBG_LOOKUP) log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", USERCERTIFICATE); okay = 1; } else if (!ascii_strcasecmp (p, CACERTIFICATE)) { if (DBG_LOOKUP) log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", CACERTIFICATE); okay = 1; } else if (!ascii_strcasecmp (p, X509CACERT)) { if (DBG_LOOKUP) log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", CACERTIFICATE); okay = 1; } /* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */ /* { */ /* if (DBG_LOOKUP) */ /* log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", */ /* USERSMIMECERTIFICATE); */ /* okay = 1; */ /* is_cms = 1; */ /* } */ else { if (DBG_LOOKUP) log_debug ("fetch_next_cert_ldap: got attribute '%s'" " - ignored\n", p); okay = 0; } } else if (*hdr == 'E') { p = context->tmpbuf; p[n] = 0; /*(we allocated one extra byte for this.)*/ if (!strcmp (p, "truncated")) { context->truncated = 1; log_info (_("ldap_search hit the size limit of" " the server\n")); } } } } if (err) { xfree (*value); *value = NULL; *valuelen = 0; if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated) { context->truncated = 0; /* So that the next call would return EOF. */ err = gpg_error (GPG_ERR_TRUNCATED); } } return err; } void end_cert_fetch_ldap (cert_fetch_context_t context) { if (context) { ksba_reader_t reader = context->reader; xfree (context->tmpbuf); xfree (context); ldap_wrapper_release_context (reader); ksba_reader_release (reader); } }