1
0
Fork 0
mirror of git://git.gnupg.org/gnupg.git synced 2025-07-03 22:56:33 +02:00

dirmngr: Rewrite the LDAP wrapper tool

* dirmngr/ldap-misc.c: New.
* dirmngr/ldap-misc.h: New.
* dirmngr/ks-engine-ldap.c: Include ldap-misc.h.
(ldap_err_to_gpg_err, ldap_to_gpg_err): Move to ldap-misc.c.
* dirmngr/ldap-wrapper.c (ldap_wrapper): Print list of args in debug
mode.
* dirmngr/server.c (lookup_cert_by_pattern): Handle GPG_ERR_NOT_FOUND
the saqme as GPG_ERR_NO_DATA.
* dirmngr/ldap.c (run_ldap_wrapper): Add args tls_mode and ntds.
Remove arg url.  Adjust for changes in dirmngr_ldap.
(url_fetch_ldap): Remove args host and port.  Parse the URL and use
these values to call run_ldap_wrapper.
(attr_fetch_ldap): Pass tls flags to run_ldap_wrapper.
(rfc2254_need_escape, rfc2254_escape): New.
(extfilt_need_escape, extfilt_escape): New.
(parse_one_pattern): Rename to ...
(make_one_filter): this.  Change for new dirmngr_ldap calling
convention.  Make issuer DN searching partly work.
(escape4url, make_url): Remove.
(start_cert_fetch_ldap): Change for new dirmngr_ldap calling
convention.
* dirmngr/dirmngr_ldap.c: Major rewrite.

* dirmngr/t-ldap-misc.c: New.
* dirmngr/t-support.h (DIM, DIMof): New.
* dirmngr/Makefile.am (dirmngr_ldap_SOURCES): Add ldap-misc.c
(module_tests) [USE_LDAP]: Add t-ldap-misc.
(t_ldap_parse_uri_SOURCES): Ditto.
(t_ldap_misc_SOURCES): New.
--

This rewrite allows to properly handle TLS and avoids some code
duplication.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2021-06-08 08:46:59 +02:00
parent 2f98d8a0f9
commit 39815c023f
No known key found for this signature in database
GPG key ID: E3FDFF218E45B72B
12 changed files with 1272 additions and 707 deletions

View file

@ -1,12 +1,12 @@
/* ldap.c - LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 g10 Code GmbH
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010, 2021 g10 Code GmbH
*
* This file is part of DirMngr.
* This file is part of GnuPG.
*
* DirMngr is free software; you can redistribute it and/or modify
* 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 2 of the License, or
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
@ -15,8 +15,8 @@
* 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
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
@ -36,6 +36,7 @@
#include "ldapserver.h"
#include "misc.h"
#include "ldap-wrapper.h"
#include "ldap-url.h"
#include "../common/host2net.h"
@ -116,14 +117,15 @@ static gpg_error_t
run_ldap_wrapper (ctrl_t ctrl,
int ignore_timeout,
int multi_mode,
int tls_mode,
int ntds,
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,
const char *base, const char *filter, const char *attr,
ksba_reader_t *reader)
{
const char *argv[40];
const char *argv[50];
int argc;
char portbuf[30], timeoutbuf[30];
@ -131,7 +133,7 @@ run_ldap_wrapper (ctrl_t ctrl,
*reader = NULL;
argc = 0;
if (pass) /* Note, that the password must be the first item. */
if (pass && *pass) /* Note, that the password must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
@ -145,9 +147,18 @@ run_ldap_wrapper (ctrl_t ctrl,
argv[argc++] = "--log-with-pid";
if (multi_mode)
argv[argc++] = "--multi";
if (tls_mode == 1)
argv[argc++] = "--starttls";
else if (tls_mode)
argv[argc++] = "--ldaptls";
if (ntds)
argv[argc++] = "--ntds";
if (opt.ldaptimeout)
{
sprintf (timeoutbuf, "%u", opt.ldaptimeout);
snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
if (ignore_timeout)
@ -158,7 +169,7 @@ run_ldap_wrapper (ctrl_t ctrl,
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host)
if (host && *host)
{
argv[argc++] = "--host";
argv[argc++] = host;
@ -169,27 +180,24 @@ run_ldap_wrapper (ctrl_t ctrl,
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user)
if (user && *user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
if (dn)
if (base && *base)
{
argv[argc++] = "--dn";
argv[argc++] = dn;
}
if (filter)
{
argv[argc++] = "--filter";
argv[argc++] = filter;
argv[argc++] = "--base";
argv[argc++] = base;
}
if (attr)
{
argv[argc++] = "--attr";
argv[argc++] = attr;
}
argv[argc++] = url? url : "ldap://";
if (filter)
argv[argc++] = filter;
argv[argc] = NULL;
return ldap_wrapper (ctrl, reader, argv);
@ -202,19 +210,48 @@ run_ldap_wrapper (ctrl_t ctrl,
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)
url_fetch_ldap (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
{
gpg_error_t err;
LDAPURLDesc *ludp = NULL;
int tls_mode;
if (!ldap_is_ldap_url (url))
{
log_error (_("'%s' is not an LDAP URL\n"), url);
return gpg_error (GPG_ERR_INV_URI);
}
if (ldap_url_parse (url, &ludp))
{
log_error (_("'%s' is an invalid LDAP URL\n"), url);
return gpg_error (GPG_ERR_INV_URI);
}
if (ludp->lud_filter && ludp->lud_filter[0] != '(')
{
log_error (_("'%s' is an invalid LDAP URL\n"), url);
err = gpg_error (GPG_ERR_BAD_URI);
goto leave;
}
if (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))
tls_mode = 2; /* LDAP-over-TLS here becuase we get it from certs. */
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
1, /* Ignore explicit timeout because CRLs
might be very large. */
0,
0, /* No Multi-mode. */
tls_mode,
0, /* No AD authentication. */
opt.ldap_proxy,
host, port,
NULL, NULL,
NULL, NULL, NULL, url,
ludp->lud_host, ludp->lud_port,
NULL, NULL, /* user, password */
ludp->lud_dn, /* Base DN */
ludp->lud_filter,
ludp->lud_attrs? ludp->lud_attrs[0] : NULL,
reader);
/* FIXME: This option might be used for DoS attacks. Because it
@ -223,17 +260,8 @@ url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port,
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 (ludp->lud_host)
add_server_to_servers (ludp->lud_host, ludp->lud_port);
}
/* If the lookup failed and we are not only using the proxy, we try
@ -252,19 +280,32 @@ url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port,
{
ldap_server_t server = iter.server;
if (server->starttls)
tls_mode = 1;
else if (server->ldap_over_tls)
tls_mode = 2;
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
0,
0,
0, /* No Multi-mode */
tls_mode,
server->ntds,
NULL,
server->host, server->port,
NULL, NULL,
NULL, NULL, NULL, url,
server->user, server->pass,
server->base,
ludp->lud_filter,
ludp->lud_attrs? ludp->lud_attrs[0] : NULL,
reader);
if (!err)
break;
}
}
leave:
ldap_free_urldesc (ludp);
return err;
}
@ -281,20 +322,32 @@ attr_fetch_ldap (ctrl_t ctrl,
*reader = NULL;
/* FIXME; we might want to look at the Base SN to try matching
/* FIXME; we might want to look at the Base DN 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;
int tls_mode;
if (server->starttls)
tls_mode = 1;
else if (server->ldap_over_tls)
tls_mode = 2;
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
0,
0,
tls_mode,
server->ntds,
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn, "objectClass=*", attr, NULL,
dn,
"(objectClass=*)",
attr,
reader);
if (!err)
break; /* Probably found a result. Ready. */
@ -302,46 +355,144 @@ attr_fetch_ldap (ctrl_t ctrl,
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)
/* Return true if VALUE needs escaping. */
static int
rfc2254_need_escape (const char *value)
{
strlist_t result = NULL;
char *p;
/* NUL needs to be escaped as well but we can represent that in
* VALUE, so no need for it. */
return !!strpbrk (value, "*()\\");
}
/* Escape VALUE using RFC-2254 rules. Returns NULL on error. */
static char *
rfc2254_escape (const char *value)
{
const char *s;
char *buffer, *p;
size_t length = 0;
for (s=value; *s; s++)
switch (*s)
{
case '*':
case '(':
case ')':
case '\\': length += 3; break;
default: length++; break;
}
buffer = xtrymalloc (length+1);
if (!buffer)
return NULL;
p = buffer;
for (s=value; *s; s++)
switch (*s)
{
case '*': p = stpcpy (p, "\\2a"); break;
case '(': p = stpcpy (p, "\\28"); break;
case ')': p = stpcpy (p, "\\29"); break;
case '\\': p = stpcpy (p, "\\5c"); break;
default: *p++ = *s; break;
}
*p = 0;
return buffer;
}
/* Return true if VALUE needs escaping. */
static int
extfilt_need_escape (const char *value)
{
/* NUL needs to be escaped as well but we can represent that in
* VALUE, so no need for it. */
return !!strchr (value, '&');
}
/* Escape VALUE using our extended filter rules from dirmngr_ldap.c.
* Returns NULL on error. */
static char *
extfilt_escape (const char *value)
{
const char *s;
char *buffer, *p;
size_t length = 0;
for (s=value; *s; s++)
{
length++;
if (*s == '&')
length++;
}
buffer = xtrymalloc (length+1);
if (!buffer)
return NULL;
p = buffer;
for (s=value; *s; s++)
{
*p++ = *s;
if (*s == '&')
*p++ = '&';
}
*p = 0;
return buffer;
}
/* Parse PATTERN and return a new filter expression for an LDAP query.
* The extended filter syntax as known by dirmngr_ldap.c is used.
* Caller must release the returned value. R_RESULT is set to NULL on
* error.
*
* Supported patterns:
*
* | Ok | gpg style user id type |
* |-----+------------------------------------------------------|
* | no | KeyID |
* | no | Fingerprint |
* | no | OpenPGP userid |
* | yes | Email address Indicated by a left angle bracket. |
* | no | Exact word match in user id or subj. name |
* | yes | Subj. DN indicated by a leading slash |
* | no | Issuer DN |
* | no | Serial number + subj. DN |
* | yes | Substring match indicated by a leading '*; (default) |
*/
static gpg_error_t
make_one_filter (const char *pattern, char **r_result)
{
gpg_error_t err = 0;
char *pattern_buffer = NULL;
char *result = NULL;
size_t n;
*r_result = NULL;
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. */
if (rfc2254_need_escape (pattern)
&& !(pattern = pattern_buffer = rfc2254_escape (pattern)))
{
xfree (result);
result = NULL;
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("(mail=", pattern, ")", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
n = strlen (result);
if (result[n-2] == '>') /* Strip trailing '>' */
{
result[n-2] = ')';
result[n-1] = 0;
}
break;
}
@ -349,120 +500,86 @@ parse_one_pattern (const char *pattern)
pattern++;
if (*pattern)
{
result = xmalloc (sizeof *result + strlen (pattern));
result->next = NULL;
result->flags = 1; /* Base spec. */
strcpy (result->d, pattern);
/* We need just the BaseDN. This assumes that the Subject
* is correcly stored in the DT. This is however not always
* the case and the actual DN is different ffrom the
* subject. In this case we won't find anything. */
if (extfilt_need_escape (pattern)
&& !(pattern = pattern_buffer = extfilt_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("^", pattern, "&base&", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
break;
case '#': /* Issuer DN. */
case '#': /* Issuer DN - Not yet working. */
pattern++;
if (*pattern == '/') /* Just issuer DN. */
{
pattern++;
if (extfilt_need_escape (pattern)
&& !(pattern = pattern_buffer = extfilt_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("^", pattern, "&base&", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
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);
}
}
if (*pattern)
{
if (rfc2254_need_escape (pattern)
&& !(pattern = pattern_buffer = rfc2254_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("(|(sn=*", pattern,
"*)(|(cn=*", pattern,
"*)(mail=*", pattern,
"*)))", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
break;
}
return result;
}
if (!result)
err = gpg_error (GPG_ERR_INV_USER_ID);
/* Take the string STRING and escape it according to the URL rules.
Retun 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 ","
/* 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 ();
leave:
xfree (pattern_buffer);
if (err)
xfree (result);
else
err = 0;
xfree (u_dn);
xfree (u_filter);
*r_result = result;
return err;
}
/* 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
@ -478,7 +595,7 @@ start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
if (!*r_context)
return gpg_error_from_errno (errno);
/* FIXME; we might want to look at the Base SN to try matching
/* FIXME; we might want to look at the Base DN to try matching
servers first. */
err = gpg_error (GPG_ERR_CONFIGURATION);
@ -490,10 +607,12 @@ start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
err = run_ldap_wrapper (ctrl,
0,
1, /* --multi (record format) */
0, /* No TLS */
0, /* No AD authentication. */
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn, "objectClass=*", "cACertificate", NULL,
dn, "objectClass=*", "cACertificate",
&(*r_context)->reader);
if (!err)
break; /* Probably found a result. */
@ -521,11 +640,12 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
int port;
char *user = NULL;
char *pass = NULL;
const char *base;
char *base = NULL;
char *argv[50];
int argc = 0;
int argc_malloced = 0;
char portbuf[30], timeoutbuf[30];
int starttls, ldaptls, ntds;
*r_context = NULL;
@ -554,17 +674,24 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
err = gpg_error_from_syserror ();
goto leave;
}
base = server->base;
if (server->base && !(base = xtrystrdup (server->base)))
{
err = gpg_error_from_syserror ();
goto leave;
}
starttls = server->starttls;
ldaptls = server->ldap_over_tls;
ntds = server->ntds;
}
else /* Use a default server. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
{
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
if (!base)
base = "";
if (pass) /* Note: Must be the first item. */
if (pass && *pass) /* Note: Must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
@ -577,18 +704,27 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
argv[argc++] = "--log-with-pid";
argv[argc++] = "--multi";
if (starttls)
argv[argc++] = "--starttls";
else if (ldaptls)
argv[argc++] = "--ldaptls";
if (ntds)
argv[argc++] = "--ntds";
if (opt.ldaptimeout)
{
snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
}
if (opt.ldap_proxy)
if (proxy && *proxy)
{
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host)
if (host && *host)
{
argv[argc++] = "--host";
argv[argc++] = host;
@ -599,56 +735,49 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user)
if (user && *user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
if (base && *base)
{
argv[argc++] = "--base";
argv[argc++] = base;
}
/* 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);
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
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;
if (*patterns->d)
{
err = make_one_filter (patterns->d, &argv[argc]);
if (err)
goto leave;
argc++;
}
}
argv[argc] = NULL;
*r_context = xtrycalloc (1, sizeof **r_context);
if (!*r_context)
{
err = gpg_error_from_errno (errno);
err = gpg_error_from_syserror ();
goto leave;
}
err = ldap_wrapper (ctrl, &(*r_context)->reader, (const char**)argv);
if (err)
{
xfree (*r_context);
@ -660,6 +789,7 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
xfree (argv[argc_malloced]);
xfree (proxy);
xfree (host);
xfree (base);
xfree (user);
xfree (pass);
return err;