2015-03-19 11:02:46 +01:00
|
|
|
|
/* ks-engine-ldap.c - talk to a LDAP keyserver
|
|
|
|
|
* Copyright (C) 2001, 2002, 2004, 2005, 2006
|
|
|
|
|
* 2007 Free Software Foundation, Inc.
|
2020-12-14 19:28:25 +01:00
|
|
|
|
* Copyright (C) 2015, 2020 g10 Code GmbH
|
2015-03-19 11:02:46 +01:00
|
|
|
|
*
|
|
|
|
|
* 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
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2015-03-19 11:02:46 +01:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#ifdef HAVE_GETOPT_H
|
|
|
|
|
# include <getopt.h>
|
|
|
|
|
#endif
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
# include <winsock2.h>
|
|
|
|
|
# include <winldap.h>
|
|
|
|
|
#else
|
|
|
|
|
# ifdef NEED_LBER_H
|
|
|
|
|
# include <lber.h>
|
|
|
|
|
# endif
|
|
|
|
|
/* For OpenLDAP, to enable the API that we're using. */
|
|
|
|
|
# define LDAP_DEPRECATED 1
|
|
|
|
|
# include <ldap.h>
|
|
|
|
|
#endif
|
2020-12-18 11:56:15 +01:00
|
|
|
|
#include <npth.h>
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
#include "dirmngr.h"
|
|
|
|
|
#include "misc.h"
|
2017-03-07 20:21:23 +09:00
|
|
|
|
#include "../common/userids.h"
|
2020-12-15 08:55:36 +01:00
|
|
|
|
#include "../common/mbox-util.h"
|
2015-03-19 11:02:46 +01:00
|
|
|
|
#include "ks-engine.h"
|
|
|
|
|
#include "ldap-parse-uri.h"
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
#ifndef HAVE_TIMEGM
|
|
|
|
|
time_t timegm(struct tm *tm);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* Convert an LDAP error to a GPG error. */
|
|
|
|
|
static int
|
|
|
|
|
ldap_err_to_gpg_err (int code)
|
|
|
|
|
{
|
|
|
|
|
gpg_err_code_t ec;
|
|
|
|
|
|
|
|
|
|
switch (code)
|
|
|
|
|
{
|
|
|
|
|
#ifdef LDAP_X_CONNECTING
|
|
|
|
|
case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break;
|
|
|
|
|
case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break;
|
|
|
|
|
case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break;
|
|
|
|
|
case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break;
|
|
|
|
|
case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break;
|
|
|
|
|
case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break;
|
|
|
|
|
case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break;
|
|
|
|
|
case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break;
|
|
|
|
|
case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break;
|
|
|
|
|
case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break;
|
|
|
|
|
case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break;
|
|
|
|
|
case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break;
|
|
|
|
|
case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break;
|
|
|
|
|
case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break;
|
|
|
|
|
case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break;
|
|
|
|
|
case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break;
|
|
|
|
|
|
|
|
|
|
case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break;
|
|
|
|
|
|
|
|
|
|
case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break;
|
|
|
|
|
case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break;
|
|
|
|
|
case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break;
|
|
|
|
|
case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break;
|
|
|
|
|
case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break;
|
|
|
|
|
case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break;
|
|
|
|
|
case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break;
|
|
|
|
|
case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break;
|
|
|
|
|
case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break;
|
|
|
|
|
case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_ADMINLIMIT_EXCEEDED
|
|
|
|
|
case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION
|
|
|
|
|
case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
|
|
|
|
|
ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break;
|
|
|
|
|
case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break;
|
|
|
|
|
case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break;
|
|
|
|
|
case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break;
|
|
|
|
|
case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break;
|
|
|
|
|
case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_TYPE_OR_VALUE_EXISTS
|
|
|
|
|
case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break;
|
|
|
|
|
case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break;
|
|
|
|
|
case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break;
|
|
|
|
|
case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break;
|
|
|
|
|
case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break;
|
|
|
|
|
case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_X_PROXY_AUTHZ_FAILURE
|
|
|
|
|
case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break;
|
|
|
|
|
case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_INSUFFICIENT_ACCESS
|
|
|
|
|
case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break;
|
|
|
|
|
case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break;
|
|
|
|
|
case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break;
|
|
|
|
|
case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break;
|
|
|
|
|
case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break;
|
|
|
|
|
case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break;
|
|
|
|
|
case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break;
|
|
|
|
|
case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break;
|
|
|
|
|
case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break;
|
|
|
|
|
case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break;
|
|
|
|
|
case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break;
|
|
|
|
|
case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_VLV_ERROR
|
|
|
|
|
case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break;
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_CUP_RESOURCES_EXHAUSTED
|
|
|
|
|
case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break;
|
|
|
|
|
case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break;
|
|
|
|
|
case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break;
|
|
|
|
|
case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break;
|
|
|
|
|
case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_CANCELLED
|
|
|
|
|
case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_NO_SUCH_OPERATION
|
|
|
|
|
case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_TOO_LATE
|
|
|
|
|
case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_CANNOT_CANCEL
|
|
|
|
|
case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_ASSERTION_FAILED
|
|
|
|
|
case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED
|
|
|
|
|
case LDAP_PROXIED_AUTHORIZATION_DENIED:
|
|
|
|
|
ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR)
|
|
|
|
|
if (LDAP_E_ERROR (code))
|
|
|
|
|
ec = GPG_ERR_LDAP_E_GENERAL;
|
|
|
|
|
else if (LDAP_X_ERROR (code))
|
|
|
|
|
ec = GPG_ERR_LDAP_X_GENERAL;
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
ec = GPG_ERR_LDAP_GENERAL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Retrieve an LDAP error and return it's GPG equivalent. */
|
|
|
|
|
static int
|
|
|
|
|
ldap_to_gpg_err (LDAP *ld)
|
|
|
|
|
{
|
|
|
|
|
#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0)
|
|
|
|
|
return ldap_err_to_gpg_err (err);
|
|
|
|
|
else
|
|
|
|
|
return GPG_ERR_GENERAL;
|
|
|
|
|
#elif defined(HAVE_LDAP_LD_ERRNO)
|
|
|
|
|
return ldap_err_to_gpg_err (ld->ld_errno);
|
|
|
|
|
#else
|
|
|
|
|
/* We should never get here since the LDAP library should always
|
|
|
|
|
have either ldap_get_option or ld_errno, but just in case... */
|
2015-03-25 19:33:59 +01:00
|
|
|
|
return GPG_ERR_INTERNAL;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
#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 ++;
|
|
|
|
|
|
2015-03-25 19:33:59 +01:00
|
|
|
|
snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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
|
2021-05-19 17:18:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
my_ldap_value_free (char **vals)
|
|
|
|
|
{
|
|
|
|
|
if (vals)
|
|
|
|
|
ldap_value_free (vals);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* Print a help output for the schemata supported by this module. */
|
|
|
|
|
gpg_error_t
|
|
|
|
|
ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri)
|
|
|
|
|
{
|
2017-01-23 16:32:44 +01:00
|
|
|
|
const char data[] =
|
2015-03-19 11:02:46 +01:00
|
|
|
|
"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"
|
|
|
|
|
"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.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Supported methods: search, get, put\n";
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
|
2015-04-12 01:11:07 +02:00
|
|
|
|
if(!uri)
|
|
|
|
|
err = ks_print_help (ctrl, " ldap");
|
2019-11-26 13:09:35 +01:00
|
|
|
|
else if (uri->is_ldap)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
err = ks_print_help (ctrl, data);
|
|
|
|
|
else
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2019-11-26 13:09:35 +01:00
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* 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
|
2020-12-17 10:20:28 +01:00
|
|
|
|
keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact,
|
|
|
|
|
unsigned int serverinfo)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
/* 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;
|
2015-03-25 19:39:27 +01:00
|
|
|
|
char *freeme = NULL;
|
2020-12-17 18:18:52 +01:00
|
|
|
|
char *p;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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)",
|
2015-03-25 19:39:27 +01:00
|
|
|
|
(freeme = ldap_escape_filter (desc.u.name)));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KEYDB_SEARCH_MODE_SUBSTR:
|
|
|
|
|
if (! only_exact)
|
|
|
|
|
f = xasprintf ("(pgpUserID=*%s*)",
|
2015-03-25 19:39:27 +01:00
|
|
|
|
(freeme = ldap_escape_filter (desc.u.name)));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KEYDB_SEARCH_MODE_MAIL:
|
2020-12-17 18:18:52 +01:00
|
|
|
|
freeme = ldap_escape_filter (desc.u.name);
|
|
|
|
|
if (!freeme)
|
2020-12-17 10:20:28 +01:00
|
|
|
|
break;
|
2020-12-17 18:18:52 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2020-12-17 10:20:28 +01:00
|
|
|
|
else
|
2020-12-17 18:18:52 +01:00
|
|
|
|
p = freeme;
|
|
|
|
|
if ((serverinfo & SERVERINFO_SCHEMAV2))
|
2021-05-17 15:35:27 +02:00
|
|
|
|
f = xasprintf ("(&(gpgMailbox=%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
|
|
|
|
|
p);
|
2020-12-17 18:18:52 +01:00
|
|
|
|
else if (!only_exact)
|
|
|
|
|
f = xasprintf ("(pgpUserID=*<%s>*)", p);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KEYDB_SEARCH_MODE_MAILSUB:
|
|
|
|
|
if (! only_exact)
|
|
|
|
|
f = xasprintf ("(pgpUserID=*<*%s*>*)",
|
2015-03-25 19:39:27 +01:00
|
|
|
|
(freeme = ldap_escape_filter (desc.u.name)));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case KEYDB_SEARCH_MODE_MAILEND:
|
|
|
|
|
if (! only_exact)
|
|
|
|
|
f = xasprintf ("(pgpUserID=*<*%s>*)",
|
2015-03-25 19:39:27 +01:00
|
|
|
|
(freeme = ldap_escape_filter (desc.u.name)));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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_FPR:
|
2020-12-17 10:20:28 +01:00
|
|
|
|
if ((serverinfo & SERVERINFO_SCHEMAV2))
|
|
|
|
|
{
|
|
|
|
|
freeme = bin2hex (desc.u.fpr, desc.fprlen, 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;
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-25 19:39:27 +01:00
|
|
|
|
xfree (freeme);
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (! f)
|
|
|
|
|
{
|
|
|
|
|
log_error ("Unsupported search mode.\n");
|
|
|
|
|
return gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*filter = f;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2015-03-25 19:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* 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
|
2020-12-14 19:28:25 +01:00
|
|
|
|
*BASEDNP.
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
If this function successfully interrogated the server, it returns
|
|
|
|
|
0. If there was an LDAP error, it returns the LDAP error code. If
|
2015-11-16 12:41:46 +01:00
|
|
|
|
an error occurred, *basednp, etc., are undefined (and don't need to
|
2015-03-19 11:02:46 +01:00
|
|
|
|
be freed.)
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
R_SERVERINFO receives information about the server.
|
|
|
|
|
|
2015-11-16 12:41:46 +01:00
|
|
|
|
If no LDAP error occurred, you still need to check that *basednp is
|
2015-03-19 11:02:46 +01:00
|
|
|
|
valid. If it is NULL, then the server does not appear to be an
|
2020-12-14 19:28:25 +01:00
|
|
|
|
OpenPGP Keyserver. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
static int
|
2015-04-10 10:59:28 +02:00
|
|
|
|
my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
2020-12-14 19:28:25 +01:00
|
|
|
|
char **basednp, unsigned int *r_serverinfo)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
int err = 0;
|
|
|
|
|
LDAP *ldap_conn = NULL;
|
|
|
|
|
char *user = uri->auth;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
struct uri_tuple_s *password_param;
|
|
|
|
|
char *password;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *basedn = NULL;
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
*r_serverinfo = 0;
|
|
|
|
|
|
|
|
|
|
password_param = uri_query_lookup (uri, "password");
|
|
|
|
|
password = password_param ? password_param->value : NULL;
|
|
|
|
|
|
|
|
|
|
if (opt.debug)
|
2020-12-17 16:09:31 +01:00
|
|
|
|
log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s%s)\n",
|
2020-12-14 19:28:25 +01:00
|
|
|
|
uri->host, uri->port,
|
2020-12-17 16:09:31 +01:00
|
|
|
|
uri->path ? uri->path : "",
|
|
|
|
|
uri->auth ? "bindname=" : "",
|
|
|
|
|
uri->auth ? uri->auth : "",
|
2020-12-14 19:28:25 +01:00
|
|
|
|
uri->auth && password ? "," : "",
|
|
|
|
|
password ? "password=" : "",
|
2020-12-17 16:09:31 +01:00
|
|
|
|
password ? ">not shown<": "",
|
|
|
|
|
uri->ad_current? " auth=>current_user<":"");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* If the uri specifies a secure connection and we don't support
|
|
|
|
|
TLS, then fail; don't silently revert to an insecure
|
|
|
|
|
connection. */
|
|
|
|
|
if (uri->use_tls)
|
|
|
|
|
{
|
|
|
|
|
#ifndef HAVE_LDAP_START_TLS_S
|
|
|
|
|
log_error ("Can't use LDAP to connect to the server: no TLS support.");
|
|
|
|
|
err = GPG_ERR_LDAP_NOT_SUPPORTED;
|
|
|
|
|
goto out;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-17 17:31:36 +01:00
|
|
|
|
ldap_conn = ldap_init (uri->host, uri->port);
|
2020-12-17 16:09:31 +01:00
|
|
|
|
if (!ldap_conn)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2015-03-25 19:33:59 +01:00
|
|
|
|
err = gpg_err_code_from_syserror ();
|
2021-02-17 17:31:36 +01:00
|
|
|
|
log_error ("error initializing LDAP for (%s://%s:%d)\n",
|
|
|
|
|
uri->scheme, uri->host, uri->port);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_LDAP_SET_OPTION
|
|
|
|
|
{
|
|
|
|
|
int ver = LDAP_VERSION3;
|
|
|
|
|
|
|
|
|
|
err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
|
|
|
|
|
if (err != LDAP_SUCCESS)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
ldap_err2string (err));
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* XXX: It would be nice to have an option to provide the server's
|
|
|
|
|
certificate. */
|
|
|
|
|
#if 0
|
|
|
|
|
#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION)
|
|
|
|
|
err = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("unable to set ca-cert-file to '%s': %s\n",
|
|
|
|
|
ca_cert_file, ldap_err2string (err));
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-09-29 14:17:24 +02:00
|
|
|
|
#ifdef HAVE_LDAP_START_TLS_S
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (uri->use_tls)
|
|
|
|
|
{
|
|
|
|
|
/* XXX: We need an option to determine whether to abort if the
|
|
|
|
|
certificate is bad or not. Right now we conservatively
|
|
|
|
|
default to checking the certificate and aborting. */
|
2016-09-30 10:57:32 +02:00
|
|
|
|
#ifndef HAVE_W32_SYSTEM
|
2015-03-25 19:33:59 +01:00
|
|
|
|
int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
err = ldap_set_option (ldap_conn,
|
|
|
|
|
LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2020-12-17 16:09:31 +01:00
|
|
|
|
log_error ("error setting TLS option on LDAP connection\n");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
2016-09-30 10:57:32 +02:00
|
|
|
|
#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
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2016-09-30 10:57:32 +02:00
|
|
|
|
err = ldap_start_tls_s (ldap_conn,
|
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
|
|
|
|
/* ServerReturnValue, result */
|
|
|
|
|
NULL, NULL,
|
|
|
|
|
#endif
|
|
|
|
|
/* ServerControls, ClientControls */
|
|
|
|
|
NULL, NULL);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (err)
|
|
|
|
|
{
|
2020-12-17 16:09:31 +01:00
|
|
|
|
log_error ("error connecting to LDAP server with TLS\n");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-12-17 16:09:31 +01:00
|
|
|
|
if (uri->ad_current)
|
|
|
|
|
{
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("LDAP bind to current user via AD\n");
|
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2020-12-17 16:09:31 +01:00
|
|
|
|
err = ldap_bind_s (ldap_conn, NULL, NULL, LDAP_AUTH_NEGOTIATE);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2020-12-17 16:09:31 +01:00
|
|
|
|
if (err != LDAP_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
log_error ("error binding to LDAP via AD: %s\n",
|
|
|
|
|
ldap_err2string (err));
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2021-02-17 17:31:36 +01:00
|
|
|
|
#else
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
goto out;
|
|
|
|
|
#endif
|
2020-12-17 16:09:31 +01:00
|
|
|
|
}
|
|
|
|
|
else if (uri->auth)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("LDAP bind to %s, password %s\n",
|
|
|
|
|
user, password ? ">not shown<" : ">none<");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
err = ldap_simple_bind_s (ldap_conn, user, password);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (err != LDAP_SUCCESS)
|
|
|
|
|
{
|
2020-12-17 16:09:31 +01:00
|
|
|
|
log_error ("error binding to LDAP: %s\n", ldap_err2string (err));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-17 16:09:31 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* By default we don't bind as there is usually no need to. */
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
if (uri->path && *uri->path)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
/* User specified base DN. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
basedn = xstrdup (uri->path);
|
|
|
|
|
|
|
|
|
|
/* If the user specifies a base DN, then we know the server is a
|
2020-12-14 19:28:25 +01:00
|
|
|
|
* real LDAP server. */
|
|
|
|
|
*r_serverinfo |= SERVERINFO_REALLDAP;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
2020-12-14 19:28:25 +01:00
|
|
|
|
{ /* Look for namingContexts. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
LDAPMessage *res = NULL;
|
|
|
|
|
char *attr[] = { "namingContexts", NULL };
|
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
|
|
|
|
|
"(objectClass=*)", attr, 0, &res);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (err == LDAP_SUCCESS)
|
|
|
|
|
{
|
2020-12-18 11:56:15 +01:00
|
|
|
|
char **context;
|
|
|
|
|
|
|
|
|
|
npth_unprotect ();
|
|
|
|
|
context = ldap_get_values (ldap_conn, res, "namingContexts");
|
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (context)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
/* 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. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
int i;
|
|
|
|
|
char *attr2[] =
|
|
|
|
|
{ "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
*r_serverinfo |= SERVERINFO_REALLDAP;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
for (i = 0; context[i] && ! basedn; i++)
|
|
|
|
|
{
|
|
|
|
|
char **vals;
|
|
|
|
|
LDAPMessage *si_res;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
int is_gnupg = 0;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2015-03-25 19:33:59 +01:00
|
|
|
|
{
|
|
|
|
|
char *object = xasprintf ("cn=pgpServerInfo,%s",
|
|
|
|
|
context[i]);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-25 19:33:59 +01:00
|
|
|
|
err = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
|
|
|
|
|
"(objectClass=*)", attr2, 0, &si_res);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-25 19:33:59 +01:00
|
|
|
|
xfree (object);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
if (err == LDAP_SUCCESS)
|
|
|
|
|
{
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res,
|
|
|
|
|
"pgpBaseKeySpaceDN");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2015-03-25 19:33:59 +01:00
|
|
|
|
basedn = xtrystrdup (vals[0]);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res,
|
|
|
|
|
"pgpSoftware");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("Server: \t%s\n", vals[0]);
|
|
|
|
|
if (!ascii_strcasecmp (vals[0], "GnuPG"))
|
|
|
|
|
is_gnupg = 1;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res,
|
|
|
|
|
"pgpVersion");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("Version:\t%s\n", vals[0]);
|
|
|
|
|
if (is_gnupg)
|
|
|
|
|
{
|
|
|
|
|
const 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;
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
|
{
|
|
|
|
|
/* We don't have an answer yet, which means the server might
|
2020-12-14 19:28:25 +01:00
|
|
|
|
be a PGP.com keyserver. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char **vals;
|
|
|
|
|
LDAPMessage *si_res = NULL;
|
|
|
|
|
|
|
|
|
|
char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL };
|
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
err = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
|
|
|
|
|
"(objectClass=*)", attr2, 0, &si_res);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (err == LDAP_SUCCESS)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
/* For the PGP LDAP keyserver, this is always
|
|
|
|
|
* "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
|
|
|
|
|
* in the future. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2015-03-25 19:33:59 +01:00
|
|
|
|
basedn = xtrystrdup (vals[0]);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res, "software");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, si_res, "version");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* 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)
|
2020-12-14 19:28:25 +01:00
|
|
|
|
*r_serverinfo |= SERVERINFO_PGPKEYV2;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (!err && opt.debug)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
log_debug ("ldap_conn: %p\n", ldap_conn);
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP)
|
|
|
|
|
? "LDAP" : "PGP.com keyserver") );
|
2015-03-19 11:02:46 +01:00
|
|
|
|
log_debug ("basedn: %s\n", basedn);
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_debug ("pgpkeyattr: %s\n",
|
|
|
|
|
(*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
xfree (basedn);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (basednp)
|
|
|
|
|
*basednp = basedn;
|
|
|
|
|
else
|
|
|
|
|
xfree (basedn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
if (ldap_conn)
|
|
|
|
|
ldap_unbind (ldap_conn);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
*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);
|
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
/* Note: ldap_get_values returns a NULL terminated array of
|
2015-03-19 11:02:46 +01:00
|
|
|
|
strings. */
|
2021-05-19 17:18:15 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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");
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
es_fprintf (output, ":");
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, message, "pgprevoked");
|
|
|
|
|
if (vals && vals[0])
|
|
|
|
|
{
|
|
|
|
|
if (atoi (vals[0]) == 1)
|
|
|
|
|
es_fprintf (output, "r");
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
unsigned int serverinfo;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *filter = NULL;
|
|
|
|
|
LDAP *ldap_conn = NULL;
|
|
|
|
|
char *basedn = NULL;
|
|
|
|
|
estream_t fp = NULL;
|
|
|
|
|
LDAPMessage *message = NULL;
|
|
|
|
|
|
|
|
|
|
(void) ctrl;
|
|
|
|
|
|
2017-02-01 17:54:14 +01:00
|
|
|
|
if (dirmngr_use_tor ())
|
2015-09-18 16:17:11 +02:00
|
|
|
|
{
|
2015-10-21 18:14:24 +02:00
|
|
|
|
/* For now we do not support LDAP over Tor. */
|
|
|
|
|
log_error (_("LDAP access not possible due to Tor mode\n"));
|
2015-09-18 16:17:11 +02:00
|
|
|
|
return gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
/* Make sure we are talking to an OpenPGP LDAP server. */
|
2020-12-14 19:28:25 +01:00
|
|
|
|
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (ldap_err || !basedn)
|
|
|
|
|
{
|
|
|
|
|
if (ldap_err)
|
|
|
|
|
err = ldap_err_to_gpg_err (ldap_err);
|
|
|
|
|
else
|
2020-12-17 10:20:28 +01:00
|
|
|
|
err = gpg_error (GPG_ERR_GENERAL);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 10:20:28 +01:00
|
|
|
|
/* 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;
|
|
|
|
|
|
2020-12-17 18:18:52 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("ks-ldap: using filter: %s\n", filter);
|
2020-12-17 10:20:28 +01:00
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
/* The ordering is significant. Specifically, "pgpcertid" needs
|
|
|
|
|
to be the second item in the list, since everything after it
|
2021-04-13 14:25:16 +02:00
|
|
|
|
may be discarded if we aren't in verbose mode. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *attrs[] =
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
"dummy",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
"pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
|
|
|
|
|
"pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype",
|
2021-05-19 17:18:15 +02:00
|
|
|
|
"gpgfingerprint",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
/* 1 if we want just attribute types; 0 if we want both attribute
|
2020-12-14 19:28:25 +01:00
|
|
|
|
* types and values. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
int attrsonly = 0;
|
|
|
|
|
int count;
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
/* Replace "dummy". */
|
|
|
|
|
attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
|
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE,
|
|
|
|
|
filter, attrs, attrsonly, &message);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (ldap_err)
|
|
|
|
|
{
|
|
|
|
|
err = ldap_err_to_gpg_err (ldap_err);
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: LDAP search error: %s\n",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
ldap_err2string (ldap_err));
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count = ldap_count_entries (ldap_conn, message);
|
|
|
|
|
if (count < 1)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_info ("ks-ldap: key %s not found on keyserver\n", keyspec);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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;
|
2021-04-13 14:25:16 +02:00
|
|
|
|
int anykey = 0;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
for (npth_unprotect (),
|
|
|
|
|
each = ldap_first_entry (ldap_conn, message),
|
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
each;
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect (),
|
|
|
|
|
each = ldap_next_entry (ldap_conn, each),
|
|
|
|
|
npth_protect ())
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
vals = ldap_get_values (ldap_conn, each, attrs[0]);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (! vals)
|
|
|
|
|
{
|
|
|
|
|
err = ldap_to_gpg_err (ldap_conn);
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error("ks-ldap: unable to retrieve key %s "
|
2015-03-19 11:02:46 +01:00
|
|
|
|
"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);
|
2021-04-13 14:25:16 +02:00
|
|
|
|
anykey = 1;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (certid);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free_strlist (seen);
|
|
|
|
|
|
|
|
|
|
if (! fp)
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_DATA);
|
2021-04-13 14:25:16 +02:00
|
|
|
|
|
|
|
|
|
if (!err && anykey)
|
|
|
|
|
err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
|
|
|
|
|
uri->scheme, uri->host? uri->host:"");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (ldap_conn)
|
|
|
|
|
ldap_unbind (ldap_conn);
|
|
|
|
|
|
|
|
|
|
xfree (filter);
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
/* 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;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
unsigned int serverinfo;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *filter = NULL;
|
|
|
|
|
LDAP *ldap_conn = NULL;
|
|
|
|
|
char *basedn = NULL;
|
|
|
|
|
estream_t fp = NULL;
|
|
|
|
|
|
|
|
|
|
(void) ctrl;
|
|
|
|
|
|
2017-02-01 17:54:14 +01:00
|
|
|
|
if (dirmngr_use_tor ())
|
2015-09-18 16:17:11 +02:00
|
|
|
|
{
|
2015-10-21 18:14:24 +02:00
|
|
|
|
/* For now we do not support LDAP over Tor. */
|
|
|
|
|
log_error (_("LDAP access not possible due to Tor mode\n"));
|
2015-09-18 16:17:11 +02:00
|
|
|
|
return gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
/* Make sure we are talking to an OpenPGP LDAP server. */
|
2020-12-14 19:28:25 +01:00
|
|
|
|
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (ldap_err || !basedn)
|
|
|
|
|
{
|
|
|
|
|
if (ldap_err)
|
|
|
|
|
err = ldap_err_to_gpg_err (ldap_err);
|
|
|
|
|
else
|
|
|
|
|
err = GPG_ERR_GENERAL;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 10:20:28 +01:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
/* Even if we have no results, we want to return a stream. */
|
|
|
|
|
fp = es_fopenmem(0, "rw");
|
2015-03-25 19:39:27 +01:00
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
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",
|
2021-05-19 17:18:15 +02:00
|
|
|
|
"pgpkeysize", "pgpkeytype", "gpgfingerprint",
|
|
|
|
|
NULL
|
2015-03-19 11:02:46 +01:00
|
|
|
|
};
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
ldap_err = ldap_search_s (ldap_conn, basedn,
|
|
|
|
|
LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
xfree (filter);
|
|
|
|
|
filter = NULL;
|
|
|
|
|
|
2015-03-25 19:39:27 +01:00
|
|
|
|
if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
err = ldap_err_to_gpg_err (ldap_err);
|
|
|
|
|
|
|
|
|
|
log_error ("SEARCH %s FAILED %d\n", pattern, err);
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: LDAP search error: %s\n",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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. */
|
2020-12-18 11:56:15 +01:00
|
|
|
|
for (npth_unprotect (),
|
|
|
|
|
each = ldap_first_entry (ldap_conn, res),
|
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
each;
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect (),
|
|
|
|
|
each = ldap_next_entry (ldap_conn, each),
|
|
|
|
|
npth_protect ())
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
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++;
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (certid);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-03-25 19:39:27 +01:00
|
|
|
|
if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
if (count == 1)
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: search results exceeded server limit."
|
2015-03-19 11:02:46 +01:00
|
|
|
|
" First 1 result shown.\n");
|
|
|
|
|
else
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: search results exceeded server limit."
|
2015-03-19 11:02:46 +01:00
|
|
|
|
" 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");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (!certid || !certid[0])
|
|
|
|
|
{
|
|
|
|
|
my_ldap_value_free (certid);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* Have we seen this certid before? */
|
|
|
|
|
if (! strlist_find (dupelist, certid[0]))
|
|
|
|
|
{
|
|
|
|
|
add_to_strlist (&dupelist, certid[0]);
|
|
|
|
|
|
2021-05-19 17:18:15 +02:00
|
|
|
|
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);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2015-03-31 12:26:59 +02:00
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgpkeytype");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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);
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
es_fputc (':', fp);
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgpkeysize");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
es_fputc (':', fp);
|
|
|
|
|
|
|
|
|
|
/* YYYYMMDDHHmmssZ */
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if(vals && vals[0] && strlen (vals[0]) == 15)
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
es_fprintf (fp, "%u",
|
|
|
|
|
(unsigned int) ldap2epochtime(vals[0]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
es_fputc (':', fp);
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0] && strlen (vals[0]) == 15)
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
es_fprintf (fp, "%u",
|
|
|
|
|
(unsigned int) ldap2epochtime (vals[0]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
es_fputc (':', fp);
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgprevoked");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
if (atoi (vals[0]) == 1)
|
|
|
|
|
es_fprintf (fp, "r");
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "pgpdisabled");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (vals && vals[0])
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
if (atoi (vals[0]) ==1)
|
|
|
|
|
es_fprintf (fp, "d");
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
#if 0
|
2015-03-31 12:26:59 +02:00
|
|
|
|
/* This is not yet specified in the keyserver
|
|
|
|
|
protocol, but may be someday. */
|
|
|
|
|
es_fputc (':', fp);
|
|
|
|
|
|
|
|
|
|
vals = ldap_get_values (ldap_conn, each, "modifytimestamp");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if(vals && vals[0] strlen (vals[0]) == 15)
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
es_fprintf (fp, "%u",
|
|
|
|
|
(unsigned int) ldap2epochtime (vals[0]));
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (vals);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
#endif
|
|
|
|
|
|
2015-03-31 12:26:59 +02:00
|
|
|
|
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");
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (!vals || !vals[0])
|
|
|
|
|
{
|
|
|
|
|
my_ldap_value_free (vals);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
2021-05-19 17:18:15 +02:00
|
|
|
|
if (!ascii_strcasecmp (certid[0], vals[0]))
|
2015-03-31 12:26:59 +02:00
|
|
|
|
{
|
|
|
|
|
char **uidvals;
|
|
|
|
|
|
|
|
|
|
es_fprintf (fp, "uid:");
|
|
|
|
|
|
|
|
|
|
uidvals = ldap_get_values (ldap_conn,
|
|
|
|
|
uids, "pgpuserid");
|
|
|
|
|
if (uidvals)
|
|
|
|
|
{
|
2021-05-19 17:18:15 +02:00
|
|
|
|
/* Need to percent escape any colons */
|
|
|
|
|
char *quoted = try_percent_escape (uidvals[0],
|
|
|
|
|
NULL);
|
|
|
|
|
if (quoted)
|
|
|
|
|
es_fputs (quoted, fp);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
xfree (quoted);
|
|
|
|
|
}
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (uidvals);
|
2015-03-31 12:26:59 +02:00
|
|
|
|
|
|
|
|
|
es_fprintf (fp, "\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ldap_value_free(vals);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-19 17:18:15 +02:00
|
|
|
|
my_ldap_value_free (certid);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ldap_msgfree (res);
|
|
|
|
|
free_strlist (dupelist);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("SEARCH %s END\n", pattern);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2021-05-19 17:18:15 +02:00
|
|
|
|
es_fclose (fp);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
2015-03-25 19:39:27 +01:00
|
|
|
|
|
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* A modlist describes a set of changes to an LDAP entry. (An entry
|
|
|
|
|
consists of 1 or more attributes. Attributes are <name, value>
|
|
|
|
|
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)
|
2015-07-26 12:50:16 +02:00
|
|
|
|
GPGRT_ATTR_USED;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
static estream_t
|
|
|
|
|
modlist_dump (LDAPMod **modlist, estream_t output)
|
|
|
|
|
{
|
|
|
|
|
LDAPMod **m;
|
|
|
|
|
|
|
|
|
|
int opened = 0;
|
2015-03-25 19:39:27 +01:00
|
|
|
|
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (! output)
|
|
|
|
|
{
|
|
|
|
|
output = es_fopenmem (0, "rw");
|
2015-03-25 19:39:27 +01:00
|
|
|
|
if (!output)
|
|
|
|
|
return NULL;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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 ++)
|
|
|
|
|
{
|
2015-03-31 14:23:13 +02:00
|
|
|
|
/* Assuming terminals are about 80 characters wide,
|
2017-02-20 16:19:50 -05:00
|
|
|
|
display at most about 10 lines of debugging
|
2015-03-31 14:23:13 +02:00
|
|
|
|
output. If we do trim the buffer, append '...' to
|
|
|
|
|
the end. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
const int max_len = 10 * 70;
|
|
|
|
|
size_t value_len = strlen (*ptr);
|
2015-03-31 14:23:13 +02:00
|
|
|
|
int elide = value_len > max_len;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
if (multi)
|
|
|
|
|
es_fprintf (output, " %d. ", i);
|
2015-03-31 14:23:13 +02:00
|
|
|
|
es_fprintf (output, "`%.*s", max_len, *ptr);
|
|
|
|
|
if (elide)
|
|
|
|
|
es_fprintf (output, "...' (%zd bytes elided)",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
value_len - max_len);
|
2015-03-31 14:23:13 +02:00
|
|
|
|
else
|
|
|
|
|
es_fprintf (output, "'");
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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++)
|
2015-03-25 19:33:59 +01:00
|
|
|
|
xfree (*ptr);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2015-03-25 19:33:59 +01:00
|
|
|
|
xfree (mod->mod_values);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
2015-03-25 19:33:59 +01:00
|
|
|
|
xfree (mod);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2015-03-25 19:33:59 +01:00
|
|
|
|
xfree (modlist);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2015-03-25 19:39:27 +01:00
|
|
|
|
size_t r = 0;
|
|
|
|
|
size_t w = 0;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
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])
|
|
|
|
|
{
|
2015-03-25 19:39:27 +01:00
|
|
|
|
/* XXX: What to do about bad escapes?
|
|
|
|
|
XXX: hextobyte already checks the string thus the hexdigitp
|
|
|
|
|
could be removed. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (str[r] == '\\' && str[r + 1] == 'x'
|
2015-03-25 19:39:27 +01:00
|
|
|
|
&& str[r+2] && str[r+3]
|
|
|
|
|
&& hexdigitp (str + r + 2)
|
|
|
|
|
&& hexdigitp (str + r + 3))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
int x = hextobyte (&str[r + 2]);
|
|
|
|
|
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
|
2020-12-15 08:55:36 +01:00
|
|
|
|
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. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
static void
|
2020-12-15 08:55:36 +01:00
|
|
|
|
extract_attributes (LDAPMod ***modlist, int *extract_state,
|
|
|
|
|
char *line, int schemav2)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
int field_count;
|
|
|
|
|
char **fields;
|
|
|
|
|
char *keyid;
|
|
|
|
|
int is_pub, is_sub, is_uid, is_sig;
|
|
|
|
|
|
|
|
|
|
/* Remove trailing whitespace */
|
2015-03-25 19:39:27 +01:00
|
|
|
|
trim_trailing_spaces (line);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
fields = strsplit (line, ':', '\0', &field_count);
|
|
|
|
|
if (field_count == 1)
|
2015-11-16 12:41:46 +01:00
|
|
|
|
/* We only have a single field. There is definitely nothing to
|
2015-03-19 11:02:46 +01:00
|
|
|
|
do. */
|
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
|
|
if (field_count < 7)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2020-12-15 08:55:36 +01:00
|
|
|
|
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;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
if (!is_pub && !is_sub && !is_uid && !is_sig)
|
2020-12-15 08:55:36 +01:00
|
|
|
|
goto out; /* Not a relevant line. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
keyid = fields[4];
|
|
|
|
|
|
|
|
|
|
if (is_uid && strlen (keyid) == 0)
|
2020-12-15 08:55:36 +01:00
|
|
|
|
; /* The uid record type can have an empty keyid. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
else if (strlen (keyid) == 16
|
|
|
|
|
&& strspn (keyid, "0123456789aAbBcCdDeEfF") == 16)
|
2020-12-15 08:55:36 +01:00
|
|
|
|
; /* Otherwise, we expect exactly 16 hex characters. */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
log_error ("malformed record!\n");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_pub)
|
|
|
|
|
{
|
2015-03-25 19:33:59 +01:00
|
|
|
|
int disabled = 0;
|
|
|
|
|
int revoked = 0;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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)
|
|
|
|
|
{
|
2018-10-25 17:21:52 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
2018-10-25 17:21:52 +02:00
|
|
|
|
modlist_add (modlist, "pgpKeyType", algo);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_pub || is_sub || is_sig)
|
|
|
|
|
{
|
|
|
|
|
if (is_pub)
|
|
|
|
|
{
|
2020-12-15 08:55:36 +01:00
|
|
|
|
modlist_add (modlist, "pgpCertID", keyid); /* Long keyid(!) */
|
|
|
|
|
modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_sub)
|
2020-12-15 08:55:36 +01:00
|
|
|
|
modlist_add (modlist, "pgpSubKeyID", keyid); /* Long keyid(!) */
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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. */
|
|
|
|
|
|
2015-04-10 13:05:38 +02:00
|
|
|
|
if (!isodate_human_to_tm (create_time, &tm))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
create_time = tm2ldaptime (&tm);
|
|
|
|
|
else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1
|
|
|
|
|
&& *end == '\0')
|
|
|
|
|
{
|
2015-04-10 13:05:38 +02:00
|
|
|
|
|
|
|
|
|
if (!gnupg_gmtime (&t, &tm))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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. */
|
|
|
|
|
|
2015-04-10 13:05:38 +02:00
|
|
|
|
if (!isodate_human_to_tm (expire_time, &tm))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
expire_time = tm2ldaptime (&tm);
|
|
|
|
|
else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1
|
|
|
|
|
&& *end == '\0')
|
|
|
|
|
{
|
2015-04-10 13:05:38 +02:00
|
|
|
|
if (!gnupg_gmtime (&t, &tm))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 08:55:36 +01:00
|
|
|
|
if (is_uid && field_count >= 10)
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
|
|
|
|
char *uid = fields[9];
|
2020-12-15 08:55:36 +01:00
|
|
|
|
char *mbox;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-15 08:55:36 +01:00
|
|
|
|
uncescape (uid);
|
|
|
|
|
modlist_add (modlist, "pgpUserID", uid);
|
|
|
|
|
if (schemav2 && (mbox = mailbox_from_userid (uid, 0)))
|
|
|
|
|
{
|
|
|
|
|
modlist_add (modlist, "gpgMailbox", mbox);
|
|
|
|
|
xfree (mbox);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
2020-12-15 08:55:36 +01:00
|
|
|
|
xfree (fields);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
unsigned int serverinfo;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
LDAP *ldap_conn = NULL;
|
|
|
|
|
char *basedn = NULL;
|
|
|
|
|
LDAPMod **modlist = NULL;
|
|
|
|
|
LDAPMod **addlist = NULL;
|
|
|
|
|
char *data_armored = NULL;
|
2020-12-15 08:55:36 +01:00
|
|
|
|
int extract_state;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* 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. */
|
2020-12-17 10:20:28 +01:00
|
|
|
|
#if 0
|
2015-03-19 11:02:46 +01:00
|
|
|
|
# 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;
|
|
|
|
|
|
2017-02-01 17:54:14 +01:00
|
|
|
|
if (dirmngr_use_tor ())
|
2015-09-18 16:17:11 +02:00
|
|
|
|
{
|
2015-10-21 18:14:24 +02:00
|
|
|
|
/* For now we do not support LDAP over Tor. */
|
|
|
|
|
log_error (_("LDAP access not possible due to Tor mode\n"));
|
2015-09-18 16:17:11 +02:00
|
|
|
|
return gpg_error (GPG_ERR_NOT_SUPPORTED);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
if (ldap_err || !basedn)
|
|
|
|
|
{
|
|
|
|
|
if (ldap_err)
|
|
|
|
|
err = ldap_err_to_gpg_err (ldap_err);
|
|
|
|
|
else
|
|
|
|
|
err = GPG_ERR_GENERAL;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (!(serverinfo & SERVERINFO_REALLDAP))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
/* 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];
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *dn;
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
key[0] = data;
|
|
|
|
|
key[1] = NULL;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
memset (&mod, 0, sizeof (mod));
|
|
|
|
|
mod.mod_op = LDAP_MOD_ADD;
|
2020-12-14 19:28:25 +01:00
|
|
|
|
mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey";
|
2015-03-19 11:02:46 +01:00
|
|
|
|
mod.mod_values = key;
|
|
|
|
|
attrs[0] = &mod;
|
|
|
|
|
attrs[1] = NULL;
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
dn = xtryasprintf ("pgpCertid=virtual,%s", basedn);
|
|
|
|
|
if (!dn)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
modlist = xtrymalloc (sizeof (LDAPMod *));
|
|
|
|
|
if (!modlist)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
*modlist = NULL;
|
|
|
|
|
|
|
|
|
|
if (dump_modlist)
|
|
|
|
|
{
|
|
|
|
|
dump = es_fopen("/tmp/modlist.txt", "w");
|
|
|
|
|
if (! dump)
|
|
|
|
|
log_error ("Failed to open /tmp/modlist.txt: %s\n",
|
|
|
|
|
strerror (errno));
|
|
|
|
|
|
|
|
|
|
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);
|
2020-12-15 08:55:36 +01:00
|
|
|
|
if ((serverinfo & SERVERINFO_SCHEMAV2))
|
|
|
|
|
{
|
|
|
|
|
modlist_add (&modlist, "gpgFingerprint", NULL);
|
|
|
|
|
modlist_add (&modlist, "gpgSubFingerprint", NULL);
|
|
|
|
|
modlist_add (&modlist, "gpgMailbox", NULL);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* Assemble the INFO stuff into LDAP attributes */
|
2020-12-15 08:55:36 +01:00
|
|
|
|
extract_state = 0;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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. */
|
|
|
|
|
{
|
2015-03-31 14:48:31 +02:00
|
|
|
|
temp = xmalloc (infolen + 1);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
memcpy (temp, info, infolen);
|
|
|
|
|
info = temp;
|
|
|
|
|
newline = (char *) info + infolen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*newline = '\0';
|
|
|
|
|
|
2020-12-15 08:55:36 +01:00
|
|
|
|
extract_attributes (&addlist, &extract_state, info,
|
|
|
|
|
(serverinfo & SERVERINFO_SCHEMAV2));
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1);
|
|
|
|
|
info = newline + 1;
|
|
|
|
|
|
|
|
|
|
/* Sanity check. */
|
|
|
|
|
if (! temp)
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_assert ((char *) info + infolen - 1 == infoend);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
else
|
2015-03-31 14:48:31 +02:00
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_assert (infolen == -1);
|
2015-03-31 14:48:31 +02:00
|
|
|
|
xfree (temp);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modlist_add (&addlist, "objectClass", "pgpKeyInfo");
|
|
|
|
|
|
|
|
|
|
err = armor_data (&data_armored, data, datalen);
|
|
|
|
|
if (err)
|
|
|
|
|
goto out;
|
|
|
|
|
|
2020-12-14 19:28:25 +01:00
|
|
|
|
modlist_add (&addlist,
|
|
|
|
|
(serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey",
|
|
|
|
|
data_armored);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
/* Now append addlist onto modlist. */
|
|
|
|
|
modlists_join (&modlist, addlist);
|
|
|
|
|
|
|
|
|
|
if (dump)
|
|
|
|
|
{
|
|
|
|
|
estream_t input = modlist_dump (modlist, NULL);
|
2015-03-25 19:39:27 +01:00
|
|
|
|
if (input)
|
|
|
|
|
{
|
|
|
|
|
copy_stream (input, dump);
|
|
|
|
|
es_fclose (input);
|
|
|
|
|
}
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
{
|
2020-12-15 08:55:36 +01:00
|
|
|
|
char **attrval;
|
2015-03-19 11:02:46 +01:00
|
|
|
|
char *dn;
|
|
|
|
|
|
2020-12-15 08:55:36 +01:00
|
|
|
|
if ((serverinfo & SERVERINFO_NTDS))
|
2015-03-19 11:02:46 +01:00
|
|
|
|
{
|
2020-12-15 08:55:36 +01:00
|
|
|
|
/* 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);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
}
|
2020-12-14 19:28:25 +01:00
|
|
|
|
if (!dn)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
log_debug ("ks-ldap: using DN: %s\n", dn);
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_unprotect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
err = ldap_modify_s (ldap_conn, dn, modlist);
|
|
|
|
|
if (err == LDAP_NO_SUCH_OBJECT)
|
|
|
|
|
err = ldap_add_s (ldap_conn, dn, addlist);
|
2020-12-18 11:56:15 +01:00
|
|
|
|
npth_protect ();
|
2015-03-19 11:02:46 +01:00
|
|
|
|
|
|
|
|
|
xfree (dn);
|
|
|
|
|
|
|
|
|
|
if (err != LDAP_SUCCESS)
|
|
|
|
|
{
|
2020-12-14 19:28:25 +01:00
|
|
|
|
log_error ("ks-ldap: error adding key to keyserver: %s\n",
|
2015-03-19 11:02:46 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|