mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
dirmngr: New command AD_QUERY.
* dirmngr/dirmngr.h: Include name-value.h (struct server_control_s): Add rootdse and rootdse_tried. * dirmngr/dirmngr.c (dirmngr_deinit_default_ctrl): Release them. * dirmngr/ks-engine.h (KS_GET_FLAG_ROOTDSE): Add two new flags. * dirmngr/ks-engine-ldap.c: Include ks-action.h (SERVERINFO_GENERIC): New. (struct ks_engine_ldap_local_s): Add scope. (ks_ldap_new_state): Set a default scope. (ks_ldap_clear_state): Ditto. (my_ldap_connect): Add flag generic. (return_all_attributes): New. (fetch_rootdse): New. (basedn_from_rootdse): New. (ks_ldap_get): Move some code out to ... (ks_ldap_prepare_my_state): New. (ks_ldap_query): New. * dirmngr/ks-action.c (ks_action_parse_uri): Factored out from server.c (ks_action_query): New. * dirmngr/server.c (make_keyserver_item): Factored most code out to ks_action_parse_uri. (cmd_ad_query): New. -- This command allows to query the Windows Active directory.
This commit is contained in:
parent
f5347fbc25
commit
625aeb65b0
@ -1705,6 +1705,8 @@ dirmngr_deinit_default_ctrl (ctrl_t ctrl)
|
|||||||
|
|
||||||
xfree (ctrl->http_proxy);
|
xfree (ctrl->http_proxy);
|
||||||
ctrl->http_proxy = NULL;
|
ctrl->http_proxy = NULL;
|
||||||
|
nvc_release (ctrl->rootdse);
|
||||||
|
ctrl->rootdse = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "../common/sysutils.h" /* (gnupg_fd_t) */
|
#include "../common/sysutils.h" /* (gnupg_fd_t) */
|
||||||
#include "../common/asshelp.h" /* (assuan_context_t) */
|
#include "../common/asshelp.h" /* (assuan_context_t) */
|
||||||
#include "../common/i18n.h"
|
#include "../common/i18n.h"
|
||||||
|
#include "../common/name-value.h"
|
||||||
#include "dirmngr-status.h"
|
#include "dirmngr-status.h"
|
||||||
#include "http.h" /* (parsed_uri_t) */
|
#include "http.h" /* (parsed_uri_t) */
|
||||||
|
|
||||||
@ -220,9 +221,12 @@ struct server_control_s
|
|||||||
int audit_events; /* Send audit events to client. */
|
int audit_events; /* Send audit events to client. */
|
||||||
char *http_proxy; /* The used http_proxy or NULL. */
|
char *http_proxy; /* The used http_proxy or NULL. */
|
||||||
|
|
||||||
|
nvc_t rootdse; /* Container wit the rootDSE properties. */
|
||||||
|
|
||||||
unsigned int timeout; /* Timeout for connect calls in ms. */
|
unsigned int timeout; /* Timeout for connect calls in ms. */
|
||||||
|
|
||||||
unsigned int http_no_crl:1; /* Do not check CRLs for https. */
|
unsigned int http_no_crl:1; /* Do not check CRLs for https. */
|
||||||
|
unsigned int rootdse_tried:1;/* Already tried to get the rootDSE. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -241,6 +245,8 @@ void ks_hkp_reload (void);
|
|||||||
void ks_hkp_init (void);
|
void ks_hkp_init (void);
|
||||||
|
|
||||||
/*-- server.c --*/
|
/*-- server.c --*/
|
||||||
|
void release_uri_item_list (uri_item_t list);
|
||||||
|
|
||||||
ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl);
|
ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl);
|
||||||
ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer);
|
ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer);
|
||||||
ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer);
|
ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer);
|
||||||
|
@ -823,7 +823,7 @@ fetch_ldap (LDAP *ld, const char *base, int scope, const char *filter)
|
|||||||
/* Main processing. Take the filter and run the LDAP query. The
|
/* Main processing. Take the filter and run the LDAP query. The
|
||||||
* result is printed to stdout, errors are logged to the log stream.
|
* result is printed to stdout, errors are logged to the log stream.
|
||||||
* To allow searching with a different base it is possible to extend
|
* To allow searching with a different base it is possible to extend
|
||||||
* the filer. For example:
|
* the filter. For example:
|
||||||
*
|
*
|
||||||
* ^CN=foo, OU=My Users&(objectClasses=*)
|
* ^CN=foo, OU=My Users&(objectClasses=*)
|
||||||
*
|
*
|
||||||
|
@ -34,6 +34,100 @@
|
|||||||
# include "ldap-parse-uri.h"
|
# include "ldap-parse-uri.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* Parse an URI and store it in a new parsed URI item object which is
|
||||||
|
* returned at R_PARSEDURI (with its next set to NULL). On error an
|
||||||
|
* error code is returned an NULL stored at R_PARSEDITEM. */
|
||||||
|
gpg_error_t
|
||||||
|
ks_action_parse_uri (const char *uri, uri_item_t *r_parseduri)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
uri_item_t item;
|
||||||
|
char *tmpstr = NULL;
|
||||||
|
#if USE_LDAP
|
||||||
|
const char *s;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
*r_parseduri = NULL;
|
||||||
|
|
||||||
|
if (!uri)
|
||||||
|
return gpg_error (GPG_ERR_INV_URI);
|
||||||
|
|
||||||
|
item = xtrymalloc (sizeof *item + strlen (uri));
|
||||||
|
if (!item)
|
||||||
|
return gpg_error_from_syserror ();
|
||||||
|
|
||||||
|
item->next = NULL;
|
||||||
|
item->parsed_uri = NULL;
|
||||||
|
strcpy (item->uri, uri);
|
||||||
|
|
||||||
|
#if USE_LDAP
|
||||||
|
if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/'))
|
||||||
|
{
|
||||||
|
/* Special ldap scheme given. This differs from a valid ldap
|
||||||
|
* scheme in that no double slash follows. We use
|
||||||
|
* http_parse_uri to put it as opaque value into parsed_uri. */
|
||||||
|
tmpstr = strconcat ("opaque:", uri+5, NULL);
|
||||||
|
if (!tmpstr)
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
else
|
||||||
|
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
||||||
|
}
|
||||||
|
else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/'))
|
||||||
|
{
|
||||||
|
/* No valid scheme given. We use http_parse_uri to put the
|
||||||
|
* string as opaque value into parsed_uri. */
|
||||||
|
tmpstr = strconcat ("opaque:", uri, NULL);
|
||||||
|
if (!tmpstr)
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
else
|
||||||
|
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
||||||
|
}
|
||||||
|
else if (ldap_uri_p (uri))
|
||||||
|
{
|
||||||
|
int fixup = 0;
|
||||||
|
/* Fixme: We should get rid of that parser and replace it with
|
||||||
|
* our generic (http) URI parser. */
|
||||||
|
|
||||||
|
/* If no port has been specified and the scheme ist ldaps we use
|
||||||
|
* our idea of the default port because the standard LDAP URL
|
||||||
|
* parser would use 636 here. This is because we redefined
|
||||||
|
* ldaps to mean starttls. */
|
||||||
|
#ifdef HAVE_W32_SYSTEM
|
||||||
|
if (!strcmp (uri, "ldap:///"))
|
||||||
|
fixup = 1;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK))
|
||||||
|
{
|
||||||
|
if (!item->parsed_uri->port
|
||||||
|
&& !strcmp (item->parsed_uri->scheme, "ldaps"))
|
||||||
|
fixup = 2;
|
||||||
|
http_release_parsed_uri (item->parsed_uri);
|
||||||
|
item->parsed_uri = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ldap_parse_uri (&item->parsed_uri, uri);
|
||||||
|
if (!err && fixup == 1)
|
||||||
|
item->parsed_uri->ad_current = 1;
|
||||||
|
else if (!err && fixup == 2)
|
||||||
|
item->parsed_uri->port = 389;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif /* USE_LDAP */
|
||||||
|
{
|
||||||
|
err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK);
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree (tmpstr);
|
||||||
|
if (err)
|
||||||
|
xfree (item);
|
||||||
|
else
|
||||||
|
*r_parseduri = item;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Called by the engine's help functions to print the actual help. */
|
/* Called by the engine's help functions to print the actual help. */
|
||||||
gpg_error_t
|
gpg_error_t
|
||||||
ks_print_help (ctrl_t ctrl, const char *text)
|
ks_print_help (ctrl_t ctrl, const char *text)
|
||||||
@ -270,7 +364,7 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers,
|
|||||||
|| strcmp (uri->parsed_uri->scheme, "https") == 0);
|
|| strcmp (uri->parsed_uri->scheme, "https") == 0);
|
||||||
int is_ldap = 0;
|
int is_ldap = 0;
|
||||||
|
|
||||||
if ((ks_get_flags & KS_GET_FLAG_ONLY_LDAP))
|
if ((ks_get_flags & (KS_GET_FLAG_ONLY_LDAP|KS_GET_FLAG_ONLY_AD)))
|
||||||
is_hkp_s = is_http_s = 0;
|
is_hkp_s = is_http_s = 0;
|
||||||
|
|
||||||
#if USE_LDAP
|
#if USE_LDAP
|
||||||
@ -448,3 +542,52 @@ ks_action_put (ctrl_t ctrl, uri_item_t keyservers,
|
|||||||
err = first_err;
|
err = first_err;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Query the default LDAP server or the one given by URL using
|
||||||
|
* the filter expression FILTER. Write the result to OUTFP. */
|
||||||
|
gpg_error_t
|
||||||
|
ks_action_query (ctrl_t ctrl, const char *url, unsigned int ks_get_flags,
|
||||||
|
const char *filter, char **attrs, estream_t outfp)
|
||||||
|
{
|
||||||
|
#if USE_LDAP
|
||||||
|
gpg_error_t err;
|
||||||
|
estream_t infp = NULL;
|
||||||
|
uri_item_t puri; /* The broken down URI (only one item used). */
|
||||||
|
|
||||||
|
if (!url && (ks_get_flags & KS_GET_FLAG_ROOTDSE))
|
||||||
|
url = "ldap://";
|
||||||
|
|
||||||
|
err = ks_action_parse_uri (url, &puri);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if ((ks_get_flags & KS_GET_FLAG_ROOTDSE))
|
||||||
|
{
|
||||||
|
/* Reset authentication for a serverless connection. */
|
||||||
|
puri->parsed_uri->ad_current = 0;
|
||||||
|
puri->parsed_uri->auth = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp (puri->parsed_uri->scheme, "ldap")
|
||||||
|
|| !strcmp (puri->parsed_uri->scheme, "ldaps")
|
||||||
|
|| !strcmp (puri->parsed_uri->scheme, "ldapi")
|
||||||
|
|| puri->parsed_uri->opaque)
|
||||||
|
{
|
||||||
|
err = ks_ldap_query (ctrl, puri->parsed_uri, ks_get_flags, filter,
|
||||||
|
attrs, &infp);
|
||||||
|
if (!err)
|
||||||
|
err = copy_stream (infp, outfp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
err = gpg_error (GPG_ERR_CONFIGURATION); /* No LDAP server known. */
|
||||||
|
|
||||||
|
es_fclose (infp);
|
||||||
|
release_uri_item_list (puri);
|
||||||
|
return err;
|
||||||
|
|
||||||
|
#else /* !USE_LDAP */
|
||||||
|
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#ifndef DIRMNGR_KS_ACTION_H
|
#ifndef DIRMNGR_KS_ACTION_H
|
||||||
#define DIRMNGR_KS_ACTION_H 1
|
#define DIRMNGR_KS_ACTION_H 1
|
||||||
|
|
||||||
|
gpg_error_t ks_action_parse_uri (const char *uri, uri_item_t *r_parseduri);
|
||||||
gpg_error_t ks_action_help (ctrl_t ctrl, const char *url);
|
gpg_error_t ks_action_help (ctrl_t ctrl, const char *url);
|
||||||
gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers);
|
gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers);
|
||||||
gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers,
|
gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers,
|
||||||
@ -32,6 +33,9 @@ gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp);
|
|||||||
gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers,
|
gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers,
|
||||||
void *data, size_t datalen,
|
void *data, size_t datalen,
|
||||||
void *info, size_t infolen);
|
void *info, size_t infolen);
|
||||||
|
gpg_error_t ks_action_query (ctrl_t ctrl, const char *ldapserver,
|
||||||
|
unsigned int ks_get_flags,
|
||||||
|
const char *filter, char **attr, estream_t outfp);
|
||||||
|
|
||||||
|
|
||||||
#endif /*DIRMNGR_KS_ACTION_H*/
|
#endif /*DIRMNGR_KS_ACTION_H*/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* ks-engine-ldap.c - talk to a LDAP keyserver
|
/* ks-engine-ldap.c - talk to a LDAP keyserver
|
||||||
* Copyright (C) 2001, 2002, 2004, 2005, 2006
|
* Copyright (C) 2001, 2002, 2004, 2005, 2006
|
||||||
* 2007 Free Software Foundation, Inc.
|
* 2007 Free Software Foundation, Inc.
|
||||||
* Copyright (C) 2015, 2020 g10 Code GmbH
|
* Copyright (C) 2015, 2020, 2023 g10 Code GmbH
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
@ -32,6 +32,7 @@
|
|||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "../common/userids.h"
|
#include "../common/userids.h"
|
||||||
#include "../common/mbox-util.h"
|
#include "../common/mbox-util.h"
|
||||||
|
#include "ks-action.h"
|
||||||
#include "ks-engine.h"
|
#include "ks-engine.h"
|
||||||
#include "ldap-misc.h"
|
#include "ldap-misc.h"
|
||||||
#include "ldap-parse-uri.h"
|
#include "ldap-parse-uri.h"
|
||||||
@ -43,6 +44,7 @@
|
|||||||
#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/
|
#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/
|
||||||
#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */
|
#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */
|
||||||
#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */
|
#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */
|
||||||
|
#define SERVERINFO_GENERIC 16 /* Connected in genric mode. */
|
||||||
|
|
||||||
|
|
||||||
/* The page size requested from the server. */
|
/* The page size requested from the server. */
|
||||||
@ -61,6 +63,7 @@ struct ks_engine_ldap_local_s
|
|||||||
LDAPMessage *message;
|
LDAPMessage *message;
|
||||||
LDAPMessage *msg_iter; /* Iterator for message. */
|
LDAPMessage *msg_iter; /* Iterator for message. */
|
||||||
unsigned int serverinfo;
|
unsigned int serverinfo;
|
||||||
|
int scope;
|
||||||
char *basedn;
|
char *basedn;
|
||||||
char *keyspec;
|
char *keyspec;
|
||||||
char *filter;
|
char *filter;
|
||||||
@ -192,7 +195,12 @@ ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri)
|
|||||||
static struct ks_engine_ldap_local_s *
|
static struct ks_engine_ldap_local_s *
|
||||||
ks_ldap_new_state (void)
|
ks_ldap_new_state (void)
|
||||||
{
|
{
|
||||||
return xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s));
|
struct ks_engine_ldap_local_s *state;
|
||||||
|
|
||||||
|
state = xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s));
|
||||||
|
if (state)
|
||||||
|
state->scope = LDAP_SCOPE_SUBTREE;
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -217,6 +225,7 @@ ks_ldap_clear_state (struct ks_engine_ldap_local_s *state)
|
|||||||
}
|
}
|
||||||
state->serverinfo = 0;
|
state->serverinfo = 0;
|
||||||
xfree (state->basedn);
|
xfree (state->basedn);
|
||||||
|
state->scope = LDAP_SCOPE_SUBTREE;
|
||||||
state->basedn = NULL;
|
state->basedn = NULL;
|
||||||
xfree (state->keyspec);
|
xfree (state->keyspec);
|
||||||
state->keyspec = NULL;
|
state->keyspec = NULL;
|
||||||
@ -240,6 +249,45 @@ ks_ldap_free_state (struct ks_engine_ldap_local_s *state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Helper for ks_ldap_get and ks_ldap_query. On return first_mode and
|
||||||
|
* next_mode are set accordingly. */
|
||||||
|
static gpg_error_t
|
||||||
|
ks_ldap_prepare_my_state (ctrl_t ctrl, unsigned int ks_get_flags,
|
||||||
|
int *first_mode, int *next_mode)
|
||||||
|
{
|
||||||
|
*first_mode = *next_mode = 0;
|
||||||
|
|
||||||
|
if ((ks_get_flags & KS_GET_FLAG_FIRST))
|
||||||
|
{
|
||||||
|
if (ctrl->ks_get_state)
|
||||||
|
ks_ldap_clear_state (ctrl->ks_get_state);
|
||||||
|
else if (!(ctrl->ks_get_state = ks_ldap_new_state ()))
|
||||||
|
return gpg_error_from_syserror ();
|
||||||
|
*first_mode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ks_get_flags & KS_GET_FLAG_NEXT))
|
||||||
|
{
|
||||||
|
if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn
|
||||||
|
|| !ctrl->ks_get_state->message)
|
||||||
|
{
|
||||||
|
log_error ("ks-ldap: --next requested but no state\n");
|
||||||
|
return gpg_error (GPG_ERR_INV_STATE);
|
||||||
|
}
|
||||||
|
*next_mode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do not keep an old state around if not needed. */
|
||||||
|
if (!(*first_mode || *next_mode))
|
||||||
|
{
|
||||||
|
ks_ldap_free_state (ctrl->ks_get_state);
|
||||||
|
ctrl->ks_get_state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Convert a keyspec to a filter. Return an error if the keyspec is
|
/* 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
|
bad or is not supported. The filter is escaped and returned in
|
||||||
@ -437,7 +485,9 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search,
|
|||||||
*
|
*
|
||||||
* URI describes the server to connect to and various options
|
* URI describes the server to connect to and various options
|
||||||
* including whether to use TLS and the username and password (see
|
* including whether to use TLS and the username and password (see
|
||||||
* ldap_parse_uri for a description of the various fields).
|
* ldap_parse_uri for a description of the various fields). Be
|
||||||
|
* default a PGP keyserver is assumed; if GENERIC is true a generic
|
||||||
|
* ldap conenction is instead established.
|
||||||
*
|
*
|
||||||
* Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set
|
* Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set
|
||||||
* to the base DN for the PGP key space, several flags will be stored
|
* to the base DN for the PGP key space, several flags will be stored
|
||||||
@ -450,7 +500,7 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search,
|
|||||||
* If it is NULL, then the server does not appear to be an OpenPGP
|
* If it is NULL, then the server does not appear to be an OpenPGP
|
||||||
* keyserver. */
|
* keyserver. */
|
||||||
static gpg_error_t
|
static gpg_error_t
|
||||||
my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
my_ldap_connect (parsed_uri_t uri, unsigned int generic, LDAP **ldap_connp,
|
||||||
char **r_basedn, char **r_host, int *r_use_tls,
|
char **r_basedn, char **r_host, int *r_use_tls,
|
||||||
unsigned int *r_serverinfo)
|
unsigned int *r_serverinfo)
|
||||||
{
|
{
|
||||||
@ -519,15 +569,15 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opt.verbose)
|
if (opt.verbose)
|
||||||
log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'\n",
|
log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'%s\n",
|
||||||
host, port,
|
host, port,
|
||||||
basedn_arg ? basedn_arg : "",
|
basedn_arg ? basedn_arg : "",
|
||||||
bindname ? bindname : "",
|
bindname ? bindname : "",
|
||||||
password ? "*****" : "",
|
password ? "*****" : "",
|
||||||
use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain",
|
use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain",
|
||||||
use_ntds ? ",ntds":"",
|
use_ntds ? ",ntds":"",
|
||||||
use_areconly? ",areconly":"");
|
use_areconly? ",areconly":"",
|
||||||
|
generic? " (generic)":"");
|
||||||
|
|
||||||
/* If the uri specifies a secure connection and we don't support
|
/* If the uri specifies a secure connection and we don't support
|
||||||
TLS, then fail; don't silently revert to an insecure
|
TLS, then fail; don't silently revert to an insecure
|
||||||
@ -535,7 +585,7 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
|||||||
if (use_tls)
|
if (use_tls)
|
||||||
{
|
{
|
||||||
#ifndef HAVE_LDAP_START_TLS_S
|
#ifndef HAVE_LDAP_START_TLS_S
|
||||||
log_error ("ldap: can't connect to the server: no TLS support.");
|
log_error ("ks-ldap: can't connect to the server: no TLS support.");
|
||||||
err = GPG_ERR_LDAP_NOT_SUPPORTED;
|
err = GPG_ERR_LDAP_NOT_SUPPORTED;
|
||||||
goto out;
|
goto out;
|
||||||
#endif
|
#endif
|
||||||
@ -607,6 +657,8 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
|||||||
{
|
{
|
||||||
int ver = opt.ldaptimeout;
|
int ver = opt.ldaptimeout;
|
||||||
|
|
||||||
|
/* fixme: also use LDAP_OPT_SEND_TIMEOUT? */
|
||||||
|
|
||||||
lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver);
|
lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver);
|
||||||
if (lerr != LDAP_SUCCESS)
|
if (lerr != LDAP_SUCCESS)
|
||||||
{
|
{
|
||||||
@ -699,7 +751,21 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
|||||||
/* By default we don't bind as there is usually no need to. */
|
/* By default we don't bind as there is usually no need to. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (generic)
|
||||||
|
{
|
||||||
|
/* Generic use of this function for arbitrary LDAP servers. */
|
||||||
|
*r_serverinfo |= SERVERINFO_GENERIC;
|
||||||
if (basedn_arg && *basedn_arg)
|
if (basedn_arg && *basedn_arg)
|
||||||
|
{
|
||||||
|
basedn = xtrystrdup (basedn_arg);
|
||||||
|
if (!basedn)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (basedn_arg && *basedn_arg)
|
||||||
{
|
{
|
||||||
/* User specified base DN. In this case we know the server is a
|
/* User specified base DN. In this case we know the server is a
|
||||||
* real LDAP server. */
|
* real LDAP server. */
|
||||||
@ -819,9 +885,13 @@ my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp,
|
|||||||
if (!err && opt.debug)
|
if (!err && opt.debug)
|
||||||
{
|
{
|
||||||
log_debug ("ldap_conn: %p\n", ldap_conn);
|
log_debug ("ldap_conn: %p\n", ldap_conn);
|
||||||
log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP)
|
log_debug ("server_type: %s\n",
|
||||||
|
((*r_serverinfo & SERVERINFO_GENERIC)
|
||||||
|
? "Generic" :
|
||||||
|
(*r_serverinfo & SERVERINFO_REALLDAP)
|
||||||
? "LDAP" : "PGP.com keyserver") );
|
? "LDAP" : "PGP.com keyserver") );
|
||||||
log_debug ("basedn: %s\n", basedn);
|
log_debug ("basedn: %s\n", basedn);
|
||||||
|
if (!(*r_serverinfo & SERVERINFO_GENERIC))
|
||||||
log_debug ("pgpkeyattr: %s\n",
|
log_debug ("pgpkeyattr: %s\n",
|
||||||
(*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
|
(*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
|
||||||
}
|
}
|
||||||
@ -1028,11 +1098,132 @@ return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Helper for ks_ldap_get. Note that KEYSPEC is only used for
|
/* Helper for ks_ldap_query. Returns 0 if an attr was fetched and
|
||||||
* diagnostics. */
|
* printed to FP. The error code GPG_ERR_NO_DATA is returned if no
|
||||||
|
* data was printed. Note that FP is updated by this function. */
|
||||||
|
static gpg_error_t
|
||||||
|
return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp)
|
||||||
|
{
|
||||||
|
gpg_error_t err = 0;
|
||||||
|
BerElement *berctx = NULL;
|
||||||
|
char *attr = NULL;
|
||||||
|
const char *attrprefix;
|
||||||
|
struct berval **values = NULL;
|
||||||
|
int idx;
|
||||||
|
int any = 0;
|
||||||
|
const char *s;
|
||||||
|
const char *val;
|
||||||
|
size_t len;
|
||||||
|
char *mydn;
|
||||||
|
|
||||||
|
mydn = ldap_get_dn (ld, msg);
|
||||||
|
if (!*fp)
|
||||||
|
{
|
||||||
|
*fp = es_fopenmem(0, "rw");
|
||||||
|
if (!*fp)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always print the DN - note that by using only unbkown attributes
|
||||||
|
* it is pissible to list just the DNs with out addiional
|
||||||
|
* linefeeds. */
|
||||||
|
es_fprintf (*fp, "Dn: %s\n", mydn? mydn : "[oops DN missing]");
|
||||||
|
|
||||||
|
for (npth_unprotect (), attr = ldap_first_attribute (ld, msg, &berctx),
|
||||||
|
npth_protect ();
|
||||||
|
attr;
|
||||||
|
npth_unprotect (), attr = ldap_next_attribute (ld, msg, berctx),
|
||||||
|
npth_protect ())
|
||||||
|
{
|
||||||
|
npth_unprotect ();
|
||||||
|
values = ldap_get_values_len (ld, msg, attr);
|
||||||
|
npth_protect ();
|
||||||
|
|
||||||
|
if (!values)
|
||||||
|
{
|
||||||
|
if (opt.verbose)
|
||||||
|
log_info ("attribute '%s' not found\n", attr);
|
||||||
|
ldap_memfree (attr);
|
||||||
|
attr = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
any = 1;
|
||||||
|
|
||||||
|
if (opt.verbose > 1)
|
||||||
|
{
|
||||||
|
log_info ("found attribute '%s'\n", attr);
|
||||||
|
for (idx=0; values[idx]; idx++)
|
||||||
|
log_info (" length[%d]=%d\n",
|
||||||
|
idx, (int)values[0]->bv_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ascii_strcasecmp (attr, "Dn"))
|
||||||
|
attrprefix = "X-";
|
||||||
|
else if (*attr == '#')
|
||||||
|
attrprefix = "X-hash-";
|
||||||
|
else if (*attr == ' ')
|
||||||
|
attrprefix = "X-blank-";
|
||||||
|
else
|
||||||
|
attrprefix = "";
|
||||||
|
/* FIXME: We should remap all invalid chars in ATTR. */
|
||||||
|
|
||||||
|
for (idx=0; values[idx]; idx++)
|
||||||
|
{
|
||||||
|
es_fprintf (*fp, "%s%s: ", attrprefix, attr);
|
||||||
|
val = values[idx]->bv_val;
|
||||||
|
len = values[idx]->bv_len;
|
||||||
|
while (len && (s = memchr (val, '\n', len)))
|
||||||
|
{
|
||||||
|
s++; /* We als want to print the LF. */
|
||||||
|
if (es_fwrite (val, s - val, 1, *fp) != 1)
|
||||||
|
goto fwrite_failed;
|
||||||
|
len -= (s-val);
|
||||||
|
val = s;
|
||||||
|
if (len && es_fwrite (" ", 1, 1, *fp) != 1)
|
||||||
|
goto fwrite_failed;
|
||||||
|
}
|
||||||
|
if (len && es_fwrite (val, len, 1, *fp) != 1)
|
||||||
|
goto fwrite_failed;
|
||||||
|
if (es_fwrite ("\n", 1, 1, *fp) != 1) /* Final LF. */
|
||||||
|
goto fwrite_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ldap_value_free_len (values);
|
||||||
|
values = NULL;
|
||||||
|
ldap_memfree (attr);
|
||||||
|
attr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* One final linefeed to prettify the output. */
|
||||||
|
if (any && es_fwrite ("\n", 1, 1, *fp) != 1)
|
||||||
|
goto fwrite_failed;
|
||||||
|
|
||||||
|
|
||||||
|
leave:
|
||||||
|
if (values)
|
||||||
|
ldap_value_free_len (values);
|
||||||
|
ldap_memfree (attr);
|
||||||
|
if (mydn)
|
||||||
|
ldap_memfree (mydn);
|
||||||
|
ber_free (berctx, 0);
|
||||||
|
return err;
|
||||||
|
|
||||||
|
fwrite_failed:
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
log_error ("error writing to stdout: %s\n", gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Helper for ks_ldap_get and ks_ldap_query. Note that KEYSPEC is
|
||||||
|
* only used for diagnostics. */
|
||||||
static gpg_error_t
|
static gpg_error_t
|
||||||
search_and_parse (ctrl_t ctrl, const char *keyspec,
|
search_and_parse (ctrl_t ctrl, const char *keyspec,
|
||||||
LDAP *ldap_conn, char *basedn, char *filter,
|
LDAP *ldap_conn, char *basedn, int scope, char *filter,
|
||||||
char **attrs, LDAPMessage **r_message)
|
char **attrs, LDAPMessage **r_message)
|
||||||
{
|
{
|
||||||
gpg_error_t err = 0;
|
gpg_error_t err = 0;
|
||||||
@ -1065,7 +1256,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec,
|
|||||||
}
|
}
|
||||||
|
|
||||||
npth_unprotect ();
|
npth_unprotect ();
|
||||||
l_err = ldap_search_ext_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE,
|
l_err = ldap_search_ext_s (ldap_conn, basedn, scope,
|
||||||
filter, attrs, 0,
|
filter, attrs, 0,
|
||||||
srvctrls[0]? srvctrls : NULL, NULL, NULL, 0,
|
srvctrls[0]? srvctrls : NULL, NULL, NULL, 0,
|
||||||
r_message);
|
r_message);
|
||||||
@ -1130,7 +1321,7 @@ search_and_parse (ctrl_t ctrl, const char *keyspec,
|
|||||||
if (count < 1)
|
if (count < 1)
|
||||||
{
|
{
|
||||||
if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1)
|
if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1)
|
||||||
log_info ("ks-ldap: key %s not found on keyserver\n", keyspec);
|
log_info ("ks-ldap: '%s' not found on LDAP server\n", keyspec);
|
||||||
|
|
||||||
if (count == -1)
|
if (count == -1)
|
||||||
err = ldap_to_gpg_err (ldap_conn);
|
err = ldap_to_gpg_err (ldap_conn);
|
||||||
@ -1150,6 +1341,77 @@ search_and_parse (ctrl_t ctrl, const char *keyspec,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Fetch all entries from the RootDSE and return them as a name value
|
||||||
|
* object. */
|
||||||
|
static nvc_t
|
||||||
|
fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
estream_t infp = NULL;
|
||||||
|
uri_item_t puri; /* The broken down URI (only one item used). */
|
||||||
|
nvc_t nvc = NULL;
|
||||||
|
|
||||||
|
/* FIXME: We need the unparsed URI here - use uri_item_t instead
|
||||||
|
* of fix the parser to fill in original */
|
||||||
|
err = ks_action_parse_uri (uri && uri->original? uri->original : "ldap://",
|
||||||
|
&puri);
|
||||||
|
if (err)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Reset authentication for a serverless. */
|
||||||
|
puri->parsed_uri->ad_current = 0;
|
||||||
|
puri->parsed_uri->auth = NULL;
|
||||||
|
|
||||||
|
if (!strcmp (puri->parsed_uri->scheme, "ldap")
|
||||||
|
|| !strcmp (puri->parsed_uri->scheme, "ldaps")
|
||||||
|
|| !strcmp (puri->parsed_uri->scheme, "ldapi")
|
||||||
|
|| puri->parsed_uri->opaque)
|
||||||
|
{
|
||||||
|
err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE,
|
||||||
|
"^&base&(objectclass=*)", NULL, &infp);
|
||||||
|
if (err)
|
||||||
|
log_error ("ldap: reading the rootDES failed: %s\n",
|
||||||
|
gpg_strerror (err));
|
||||||
|
else if ((err = nvc_parse (&nvc, NULL, infp)))
|
||||||
|
log_error ("parsing the rootDES failed: %s\n", gpg_strerror (err));
|
||||||
|
}
|
||||||
|
|
||||||
|
es_fclose (infp);
|
||||||
|
release_uri_item_list (puri);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
nvc_release (nvc);
|
||||||
|
nvc = NULL;
|
||||||
|
}
|
||||||
|
return nvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Return the baseDN for URI which might have already been cached for
|
||||||
|
* this session. */
|
||||||
|
static char *
|
||||||
|
basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri)
|
||||||
|
{
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
if (!ctrl->rootdse && !ctrl->rootdse_tried)
|
||||||
|
{
|
||||||
|
ctrl->rootdse = fetch_rootdse (ctrl, uri);
|
||||||
|
ctrl->rootdse_tried = 1;
|
||||||
|
if (ctrl->rootdse)
|
||||||
|
{
|
||||||
|
log_debug ("Dump of all rootDSE attributes:\n");
|
||||||
|
nvc_write (ctrl->rootdse, log_get_stream ());
|
||||||
|
log_debug ("End of dump\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = nvc_get_string (ctrl->rootdse, "defaultNamingContext:");
|
||||||
|
return s? xtrystrdup (s): NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Get the key described key the KEYSPEC string from the keyserver
|
/* 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
|
* identified by URI. On success R_FP has an open stream to read the
|
||||||
* data. KS_GET_FLAGS conveys flags from the client. */
|
* data. KS_GET_FLAGS conveys flags from the client. */
|
||||||
@ -1157,13 +1419,14 @@ gpg_error_t
|
|||||||
ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
||||||
unsigned int ks_get_flags, estream_t *r_fp)
|
unsigned int ks_get_flags, estream_t *r_fp)
|
||||||
{
|
{
|
||||||
gpg_error_t err = 0;
|
gpg_error_t err;
|
||||||
unsigned int serverinfo;
|
unsigned int serverinfo;
|
||||||
char *host = NULL;
|
char *host = NULL;
|
||||||
int use_tls;
|
int use_tls;
|
||||||
char *filter = NULL;
|
char *filter = NULL;
|
||||||
LDAP *ldap_conn = NULL;
|
LDAP *ldap_conn = NULL;
|
||||||
char *basedn = NULL;
|
char *basedn = NULL;
|
||||||
|
int scope = LDAP_SCOPE_SUBTREE;
|
||||||
estream_t fp = NULL;
|
estream_t fp = NULL;
|
||||||
LDAPMessage *message = NULL;
|
LDAPMessage *message = NULL;
|
||||||
LDAPMessage *msg;
|
LDAPMessage *msg;
|
||||||
@ -1184,41 +1447,14 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
(void) ctrl;
|
|
||||||
|
|
||||||
if (dirmngr_use_tor ())
|
if (dirmngr_use_tor ())
|
||||||
{
|
{
|
||||||
return no_ldap_due_to_tor (ctrl);
|
return no_ldap_due_to_tor (ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure we got a state. */
|
err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
|
||||||
if ((ks_get_flags & KS_GET_FLAG_FIRST))
|
if (err)
|
||||||
{
|
return err;
|
||||||
if (ctrl->ks_get_state)
|
|
||||||
ks_ldap_clear_state (ctrl->ks_get_state);
|
|
||||||
else if (!(ctrl->ks_get_state = ks_ldap_new_state ()))
|
|
||||||
return gpg_error_from_syserror ();
|
|
||||||
first_mode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ks_get_flags & KS_GET_FLAG_NEXT))
|
|
||||||
{
|
|
||||||
if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn
|
|
||||||
|| !ctrl->ks_get_state->message)
|
|
||||||
{
|
|
||||||
log_error ("ks_ldap: --next requested but no state\n");
|
|
||||||
return gpg_error (GPG_ERR_INV_STATE);
|
|
||||||
}
|
|
||||||
next_mode = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do not keep an old state around if not needed. */
|
|
||||||
if (!(first_mode || next_mode))
|
|
||||||
{
|
|
||||||
ks_ldap_free_state (ctrl->ks_get_state);
|
|
||||||
ctrl->ks_get_state = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (next_mode)
|
if (next_mode)
|
||||||
{
|
{
|
||||||
@ -1236,6 +1472,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
|||||||
err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
|
err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
|
||||||
ctrl->ks_get_state->ldap_conn,
|
ctrl->ks_get_state->ldap_conn,
|
||||||
ctrl->ks_get_state->basedn,
|
ctrl->ks_get_state->basedn,
|
||||||
|
ctrl->ks_get_state->scope,
|
||||||
ctrl->ks_get_state->filter,
|
ctrl->ks_get_state->filter,
|
||||||
attrs,
|
attrs,
|
||||||
&ctrl->ks_get_state->message);
|
&ctrl->ks_get_state->message);
|
||||||
@ -1284,7 +1521,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
|||||||
else /* Not in --next mode. */
|
else /* Not in --next mode. */
|
||||||
{
|
{
|
||||||
/* Make sure we are talking to an OpenPGP LDAP server. */
|
/* Make sure we are talking to an OpenPGP LDAP server. */
|
||||||
err = my_ldap_connect (uri, &ldap_conn,
|
err = my_ldap_connect (uri, 0, &ldap_conn,
|
||||||
&basedn, &host, &use_tls, &serverinfo);
|
&basedn, &host, &use_tls, &serverinfo);
|
||||||
if (err || !basedn)
|
if (err || !basedn)
|
||||||
{
|
{
|
||||||
@ -1311,8 +1548,8 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
|||||||
/* Replace "dummy". */
|
/* Replace "dummy". */
|
||||||
attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
|
attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
|
||||||
|
|
||||||
err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, filter, attrs,
|
err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, scope,
|
||||||
&message);
|
filter, attrs, &message);
|
||||||
if (err)
|
if (err)
|
||||||
goto leave;
|
goto leave;
|
||||||
|
|
||||||
@ -1363,6 +1600,7 @@ ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
|
|||||||
ctrl->ks_get_state->message = message;
|
ctrl->ks_get_state->message = message;
|
||||||
message = NULL;
|
message = NULL;
|
||||||
ctrl->ks_get_state->serverinfo = serverinfo;
|
ctrl->ks_get_state->serverinfo = serverinfo;
|
||||||
|
ctrl->ks_get_state->scope = scope;
|
||||||
ctrl->ks_get_state->basedn = basedn;
|
ctrl->ks_get_state->basedn = basedn;
|
||||||
basedn = NULL;
|
basedn = NULL;
|
||||||
ctrl->ks_get_state->keyspec = keyspec? xtrystrdup (keyspec) : NULL;
|
ctrl->ks_get_state->keyspec = keyspec? xtrystrdup (keyspec) : NULL;
|
||||||
@ -1423,7 +1661,7 @@ ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure we are talking to an OpenPGP LDAP server. */
|
/* Make sure we are talking to an OpenPGP LDAP server. */
|
||||||
err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
|
err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
|
||||||
if (err || !basedn)
|
if (err || !basedn)
|
||||||
{
|
{
|
||||||
if (!err)
|
if (!err)
|
||||||
@ -2312,7 +2550,7 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
|
|||||||
return no_ldap_due_to_tor (ctrl);
|
return no_ldap_due_to_tor (ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = my_ldap_connect (uri, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
|
err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
|
||||||
if (err || !basedn)
|
if (err || !basedn)
|
||||||
{
|
{
|
||||||
if (!err)
|
if (!err)
|
||||||
@ -2538,3 +2776,224 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
|
|||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Get the data described by FILTER_ARG from URI. On success R_FP has
|
||||||
|
* an open stream to read the data. KS_GET_FLAGS conveys flags from
|
||||||
|
* the client. ATTRS is a NULL terminated list of attributes to
|
||||||
|
* return or NULL for all. */
|
||||||
|
gpg_error_t
|
||||||
|
ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags,
|
||||||
|
const char *filter_arg, char **attrs, estream_t *r_fp)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
unsigned int serverinfo;
|
||||||
|
char *host = NULL;
|
||||||
|
int use_tls;
|
||||||
|
LDAP *ldap_conn = NULL;
|
||||||
|
char *basedn = NULL;
|
||||||
|
estream_t fp = NULL;
|
||||||
|
char *filter = NULL;
|
||||||
|
int scope = LDAP_SCOPE_SUBTREE;
|
||||||
|
LDAPMessage *message = NULL;
|
||||||
|
LDAPMessage *msg;
|
||||||
|
int anydata = 0;
|
||||||
|
int first_mode = 0;
|
||||||
|
int next_mode = 0;
|
||||||
|
int get_first;
|
||||||
|
|
||||||
|
if (dirmngr_use_tor ())
|
||||||
|
return no_ldap_due_to_tor (ctrl);
|
||||||
|
|
||||||
|
if ((!filter_arg || !*filter_arg) && (ks_get_flags & KS_GET_FLAG_ROOTDSE))
|
||||||
|
filter_arg = "^&base&(objectclass=*)";
|
||||||
|
|
||||||
|
err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
|
||||||
|
if (!next_mode) /* (In --next mode the filter is ignored.) */
|
||||||
|
{
|
||||||
|
if (!filter_arg || !*filter_arg)
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_LDAP_FILTER);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
err = ldap_parse_extfilter (filter_arg, 0, &basedn, &scope, &filter);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (next_mode)
|
||||||
|
{
|
||||||
|
next_again:
|
||||||
|
if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages)
|
||||||
|
{
|
||||||
|
/* Get the next page of results. */
|
||||||
|
if (ctrl->ks_get_state->message)
|
||||||
|
{
|
||||||
|
ldap_msgfree (ctrl->ks_get_state->message);
|
||||||
|
ctrl->ks_get_state->message = NULL;
|
||||||
|
}
|
||||||
|
err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
|
||||||
|
ctrl->ks_get_state->ldap_conn,
|
||||||
|
ctrl->ks_get_state->basedn,
|
||||||
|
ctrl->ks_get_state->scope,
|
||||||
|
ctrl->ks_get_state->filter,
|
||||||
|
attrs,
|
||||||
|
&ctrl->ks_get_state->message);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message;
|
||||||
|
get_first = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
get_first = 0;
|
||||||
|
|
||||||
|
while (ctrl->ks_get_state->msg_iter)
|
||||||
|
{
|
||||||
|
npth_unprotect ();
|
||||||
|
ctrl->ks_get_state->msg_iter
|
||||||
|
= get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn,
|
||||||
|
ctrl->ks_get_state->msg_iter)
|
||||||
|
/* */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn,
|
||||||
|
ctrl->ks_get_state->msg_iter);
|
||||||
|
npth_protect ();
|
||||||
|
get_first = 0;
|
||||||
|
if (ctrl->ks_get_state->msg_iter)
|
||||||
|
{
|
||||||
|
err = return_all_attributes (ctrl->ks_get_state->ldap_conn,
|
||||||
|
ctrl->ks_get_state->msg_iter,
|
||||||
|
&fp);
|
||||||
|
if (!err)
|
||||||
|
break; /* Found. */
|
||||||
|
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
|
||||||
|
err = 0; /* Skip empty attributes. */
|
||||||
|
else
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctrl->ks_get_state->msg_iter || !fp)
|
||||||
|
{
|
||||||
|
ctrl->ks_get_state->msg_iter = NULL;
|
||||||
|
if (ctrl->ks_get_state->more_pages)
|
||||||
|
goto next_again;
|
||||||
|
err = gpg_error (GPG_ERR_NO_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else /* Not in --next mode. */
|
||||||
|
{
|
||||||
|
/* Connect to the LDAP server in generic mode. */
|
||||||
|
char *tmpbasedn;
|
||||||
|
|
||||||
|
err = my_ldap_connect (uri, 1 /*generic*/, &ldap_conn,
|
||||||
|
&tmpbasedn, &host, &use_tls, &serverinfo);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
if (basedn)
|
||||||
|
xfree (tmpbasedn); /* Extended syntax overrides. */
|
||||||
|
else if (tmpbasedn)
|
||||||
|
basedn = tmpbasedn;
|
||||||
|
else if (!(ks_get_flags & KS_GET_FLAG_ROOTDSE))
|
||||||
|
{
|
||||||
|
/* No BaseDN known - get one. */
|
||||||
|
basedn = basedn_from_rootdse (ctrl, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt.debug)
|
||||||
|
{
|
||||||
|
log_debug ("ks-ldap: using basedn: %s\n", basedn);
|
||||||
|
log_debug ("ks-ldap: using filter: %s\n", filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = search_and_parse (ctrl, filter, ldap_conn, basedn, scope, filter,
|
||||||
|
attrs, &message);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
|
||||||
|
|
||||||
|
for (npth_unprotect (),
|
||||||
|
msg = ldap_first_entry (ldap_conn, message),
|
||||||
|
npth_protect ();
|
||||||
|
msg;
|
||||||
|
npth_unprotect (),
|
||||||
|
msg = ldap_next_entry (ldap_conn, msg),
|
||||||
|
npth_protect ())
|
||||||
|
{
|
||||||
|
err = return_all_attributes (ldap_conn, msg, &fp);
|
||||||
|
if (!err)
|
||||||
|
{
|
||||||
|
anydata = 1;
|
||||||
|
if (first_mode)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
|
||||||
|
err = 0; /* Skip empty/duplicate attributes. */
|
||||||
|
else
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl->ks_get_state) /* Save the iterator. */
|
||||||
|
ctrl->ks_get_state->msg_iter = msg;
|
||||||
|
|
||||||
|
if (!fp) /* Nothing was found. */
|
||||||
|
err = gpg_error (GPG_ERR_NO_DATA);
|
||||||
|
|
||||||
|
if (!err && anydata)
|
||||||
|
err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
|
||||||
|
use_tls? "ldaps" : "ldap",
|
||||||
|
host? host:"");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
leave:
|
||||||
|
/* Store our state if needed. */
|
||||||
|
if (!err && (ks_get_flags & KS_GET_FLAG_FIRST))
|
||||||
|
{
|
||||||
|
log_assert (!ctrl->ks_get_state->ldap_conn);
|
||||||
|
ctrl->ks_get_state->ldap_conn = ldap_conn;
|
||||||
|
ldap_conn = NULL;
|
||||||
|
log_assert (!ctrl->ks_get_state->message);
|
||||||
|
ctrl->ks_get_state->message = message;
|
||||||
|
message = NULL;
|
||||||
|
ctrl->ks_get_state->serverinfo = serverinfo;
|
||||||
|
ctrl->ks_get_state->scope = scope;
|
||||||
|
ctrl->ks_get_state->basedn = basedn;
|
||||||
|
basedn = NULL;
|
||||||
|
ctrl->ks_get_state->keyspec = filter? xtrystrdup (filter) : NULL;
|
||||||
|
ctrl->ks_get_state->filter = filter;
|
||||||
|
filter = NULL;
|
||||||
|
}
|
||||||
|
if ((ks_get_flags & KS_GET_FLAG_NEXT))
|
||||||
|
{
|
||||||
|
/* Keep the state in --next mode even with errors. */
|
||||||
|
ldap_conn = NULL;
|
||||||
|
message = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message)
|
||||||
|
ldap_msgfree (message);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
es_fclose (fp);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fp)
|
||||||
|
es_fseek (fp, 0, SEEK_SET);
|
||||||
|
*r_fp = fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree (basedn);
|
||||||
|
xfree (host);
|
||||||
|
|
||||||
|
if (ldap_conn)
|
||||||
|
ldap_unbind (ldap_conn);
|
||||||
|
|
||||||
|
xfree (filter);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
#define KS_GET_FLAG_ONLY_LDAP 1
|
#define KS_GET_FLAG_ONLY_LDAP 1
|
||||||
#define KS_GET_FLAG_FIRST 2
|
#define KS_GET_FLAG_FIRST 2
|
||||||
#define KS_GET_FLAG_NEXT 4
|
#define KS_GET_FLAG_NEXT 4
|
||||||
|
#define KS_GET_FLAG_ONLY_AD 8 /* Do this only if we have an AD. */
|
||||||
|
#define KS_GET_FLAG_ROOTDSE 16 /* Get the rootDSE. */
|
||||||
|
|
||||||
|
|
||||||
/*-- ks-action.c --*/
|
/*-- ks-action.c --*/
|
||||||
@ -78,6 +80,9 @@ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri,
|
|||||||
gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
|
gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
|
||||||
void *data, size_t datalen,
|
void *data, size_t datalen,
|
||||||
void *info, size_t infolen);
|
void *info, size_t infolen);
|
||||||
|
gpg_error_t ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri,
|
||||||
|
unsigned int ks_get_flags,
|
||||||
|
const char *filter, char **attrs, estream_t *r_fp);
|
||||||
|
|
||||||
|
|
||||||
#endif /*DIRMNGR_KS_ENGINE_H*/
|
#endif /*DIRMNGR_KS_ENGINE_H*/
|
||||||
|
165
dirmngr/server.c
165
dirmngr/server.c
@ -146,7 +146,7 @@ get_ldapservers_from_ctrl (ctrl_t ctrl)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Release an uri_item_t list. */
|
/* Release an uri_item_t list. */
|
||||||
static void
|
void
|
||||||
release_uri_item_list (uri_item_t list)
|
release_uri_item_list (uri_item_t list)
|
||||||
{
|
{
|
||||||
while (list)
|
while (list)
|
||||||
@ -2147,15 +2147,6 @@ cmd_validate (assuan_context_t ctx, char *line)
|
|||||||
static gpg_error_t
|
static gpg_error_t
|
||||||
make_keyserver_item (const char *uri, uri_item_t *r_item)
|
make_keyserver_item (const char *uri, uri_item_t *r_item)
|
||||||
{
|
{
|
||||||
gpg_error_t err;
|
|
||||||
uri_item_t item;
|
|
||||||
char *tmpstr = NULL;
|
|
||||||
#if USE_LDAP
|
|
||||||
const char *s;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
*r_item = NULL;
|
|
||||||
|
|
||||||
/* We used to have DNS CNAME redirection from the URLs below to
|
/* We used to have DNS CNAME redirection from the URLs below to
|
||||||
* sks-keyserver. pools. The idea was to allow for a quick way to
|
* sks-keyserver. pools. The idea was to allow for a quick way to
|
||||||
* switch to a different set of pools. The problem with that
|
* switch to a different set of pools. The problem with that
|
||||||
@ -2187,78 +2178,7 @@ make_keyserver_item (const char *uri, uri_item_t *r_item)
|
|||||||
else if (!strcmp (uri, "http://http-keys.gnupg.net"))
|
else if (!strcmp (uri, "http://http-keys.gnupg.net"))
|
||||||
uri = "hkp://keyserver.ubuntu.com:80";
|
uri = "hkp://keyserver.ubuntu.com:80";
|
||||||
|
|
||||||
item = xtrymalloc (sizeof *item + strlen (uri));
|
return ks_action_parse_uri (uri, r_item);
|
||||||
if (!item)
|
|
||||||
return gpg_error_from_syserror ();
|
|
||||||
|
|
||||||
item->next = NULL;
|
|
||||||
item->parsed_uri = NULL;
|
|
||||||
strcpy (item->uri, uri);
|
|
||||||
|
|
||||||
#if USE_LDAP
|
|
||||||
if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/'))
|
|
||||||
{
|
|
||||||
/* Special ldap scheme given. This differs from a valid ldap
|
|
||||||
* scheme in that no double slash follows.. Use http_parse_uri
|
|
||||||
* to put it as opaque value into parsed_uri. */
|
|
||||||
tmpstr = strconcat ("opaque:", uri+5, NULL);
|
|
||||||
if (!tmpstr)
|
|
||||||
err = gpg_error_from_syserror ();
|
|
||||||
else
|
|
||||||
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
|
||||||
}
|
|
||||||
else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/'))
|
|
||||||
{
|
|
||||||
/* No valid scheme given. Use http_parse_uri to put the string
|
|
||||||
* as opaque value into parsed_uri. */
|
|
||||||
tmpstr = strconcat ("opaque:", uri, NULL);
|
|
||||||
if (!tmpstr)
|
|
||||||
err = gpg_error_from_syserror ();
|
|
||||||
else
|
|
||||||
err = http_parse_uri (&item->parsed_uri, tmpstr, 0);
|
|
||||||
}
|
|
||||||
else if (ldap_uri_p (uri))
|
|
||||||
{
|
|
||||||
int fixup = 0;
|
|
||||||
/* Fixme: We should get rid of that parser and replace it with
|
|
||||||
* our generic (http) URI parser. */
|
|
||||||
|
|
||||||
/* If no port has been specified and the scheme ist ldaps we use
|
|
||||||
* our idea of the default port because the standard LDAP URL
|
|
||||||
* parser would use 636 here. This is because we redefined
|
|
||||||
* ldaps to mean starttls. */
|
|
||||||
#ifdef HAVE_W32_SYSTEM
|
|
||||||
if (!strcmp (uri, "ldap:///"))
|
|
||||||
fixup = 1;
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK))
|
|
||||||
{
|
|
||||||
if (!item->parsed_uri->port
|
|
||||||
&& !strcmp (item->parsed_uri->scheme, "ldaps"))
|
|
||||||
fixup = 2;
|
|
||||||
http_release_parsed_uri (item->parsed_uri);
|
|
||||||
item->parsed_uri = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ldap_parse_uri (&item->parsed_uri, uri);
|
|
||||||
if (!err && fixup == 1)
|
|
||||||
item->parsed_uri->ad_current = 1;
|
|
||||||
else if (!err && fixup == 2)
|
|
||||||
item->parsed_uri->port = 389;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
#endif /* USE_LDAP */
|
|
||||||
{
|
|
||||||
err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK);
|
|
||||||
}
|
|
||||||
|
|
||||||
xfree (tmpstr);
|
|
||||||
if (err)
|
|
||||||
xfree (item);
|
|
||||||
else
|
|
||||||
*r_item = item;
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2768,6 +2688,86 @@ cmd_ks_put (assuan_context_t ctx, char *line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static const char hlp_ad_query[] =
|
||||||
|
"AD_QUERY [--first|--next] [--] <filter_expression> \n"
|
||||||
|
"\n"
|
||||||
|
"Query properties from a Windows Active Directory.\n"
|
||||||
|
"Our extended filter syntax may be used for the filter\n"
|
||||||
|
"expression; see gnupg/dirmngr/ldap-misc.c. There are\n"
|
||||||
|
"a couple of other options available:\n\n"
|
||||||
|
" --rootdse - Query the root using serverless binding,\n"
|
||||||
|
" --attr=<attribs> - Comma delimited list of attributes\n"
|
||||||
|
" to return.\n"
|
||||||
|
;
|
||||||
|
static gpg_error_t
|
||||||
|
cmd_ad_query (assuan_context_t ctx, char *line)
|
||||||
|
{
|
||||||
|
ctrl_t ctrl = assuan_get_pointer (ctx);
|
||||||
|
gpg_error_t err;
|
||||||
|
unsigned int flags = 0;
|
||||||
|
const char *filter;
|
||||||
|
estream_t outfp = NULL;
|
||||||
|
char *p;
|
||||||
|
char **opt_attr = NULL;
|
||||||
|
|
||||||
|
/* No options for now. */
|
||||||
|
if (has_option (line, "--first"))
|
||||||
|
flags |= KS_GET_FLAG_FIRST;
|
||||||
|
if (has_option (line, "--next"))
|
||||||
|
flags |= KS_GET_FLAG_NEXT;
|
||||||
|
if (has_option (line, "--rootdse"))
|
||||||
|
flags |= KS_GET_FLAG_ROOTDSE;
|
||||||
|
err = get_option_value (line, "--attr", &p);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
if (p)
|
||||||
|
{
|
||||||
|
opt_attr = strtokenize (p, ",");
|
||||||
|
if (!opt_attr)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
xfree (p);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
xfree (p);
|
||||||
|
}
|
||||||
|
line = skip_options (line);
|
||||||
|
filter = line;
|
||||||
|
|
||||||
|
if ((flags & KS_GET_FLAG_NEXT))
|
||||||
|
{
|
||||||
|
if (*filter || (flags & ~KS_GET_FLAG_NEXT))
|
||||||
|
{
|
||||||
|
err = PARM_ERROR ("No filter or other options allowed with --next");
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Setup an output stream and perform the get. */
|
||||||
|
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
|
||||||
|
if (!outfp)
|
||||||
|
{
|
||||||
|
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl->server_local->inhibit_data_logging = 1;
|
||||||
|
ctrl->server_local->inhibit_data_logging_now = 0;
|
||||||
|
ctrl->server_local->inhibit_data_logging_count = 0;
|
||||||
|
|
||||||
|
err = ks_action_query (ctrl,
|
||||||
|
(flags & KS_GET_FLAG_ROOTDSE)? NULL : "ldap:///",
|
||||||
|
flags, filter, opt_attr, outfp);
|
||||||
|
|
||||||
|
leave:
|
||||||
|
es_fclose (outfp);
|
||||||
|
xfree (opt_attr);
|
||||||
|
ctrl->server_local->inhibit_data_logging = 0;
|
||||||
|
return leave_cmd (ctx, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static const char hlp_loadswdb[] =
|
static const char hlp_loadswdb[] =
|
||||||
"LOADSWDB [--force]\n"
|
"LOADSWDB [--force]\n"
|
||||||
@ -2973,6 +2973,7 @@ register_commands (assuan_context_t ctx)
|
|||||||
{ "KS_GET", cmd_ks_get, hlp_ks_get },
|
{ "KS_GET", cmd_ks_get, hlp_ks_get },
|
||||||
{ "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
|
{ "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
|
||||||
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
|
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
|
||||||
|
{ "AD_QUERY", cmd_ad_query, hlp_ad_query },
|
||||||
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
||||||
{ "LOADSWDB", cmd_loadswdb, hlp_loadswdb },
|
{ "LOADSWDB", cmd_loadswdb, hlp_loadswdb },
|
||||||
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
|
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user