1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 *
25 * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
26 */
27
28/*
29 * Processes name2sid & sid2name batched lookups for a given user or
30 * computer from an AD Directory server using GSSAPI authentication
31 */
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <alloca.h>
36#include <string.h>
37#include <strings.h>
38#include <lber.h>
39#include <ldap.h>
40#include <sasl/sasl.h>
41#include <string.h>
42#include <ctype.h>
43#include <pthread.h>
44#include <synch.h>
45#include <atomic.h>
46#include <errno.h>
47#include <assert.h>
48#include <limits.h>
49#include <time.h>
50#include <sys/u8_textprep.h>
51#include "libadutils.h"
52#include "nldaputils.h"
53#include "idmapd.h"
54
55/* Attribute names and filter format strings */
56#define	SAN		"sAMAccountName"
57#define	OBJSID		"objectSid"
58#define	OBJCLASS	"objectClass"
59#define	UIDNUMBER	"uidNumber"
60#define	GIDNUMBER	"gidNumber"
61#define	UIDNUMBERFILTER	"(&(objectclass=user)(uidNumber=%u))"
62#define	GIDNUMBERFILTER	"(&(objectclass=group)(gidNumber=%u))"
63#define	SANFILTER	"(sAMAccountName=%s)"
64#define	OBJSIDFILTER	"(|(objectSid=%s)(sIDHistory=%s))"
65
66void	idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc,
67		int qid, void *argp);
68
69/*
70 * A place to put the results of a batched (async) query
71 *
72 * There is one of these for every query added to a batch object
73 * (idmap_query_state, see below).
74 */
75typedef struct idmap_q {
76	/*
77	 * data used for validating search result entries for name->SID
78	 * lookups
79	 */
80	char			*ecanonname;	/* expected canon name */
81	char			*edomain;	/* expected domain name */
82	idmap_id_type		esidtype;	/* expected SID type */
83	/* results */
84	char			**canonname;	/* actual canon name */
85	char			**domain;	/* name of domain of object */
86	char			**sid;		/* stringified SID */
87	rid_t			*rid;		/* RID */
88	idmap_id_type		*sid_type;	/* user or group SID? */
89	char			**unixname;	/* unixname for name mapping */
90	char			**dn;		/* DN of entry */
91	char			**attr;		/* Attr for name mapping */
92	char			**value;	/* value for name mapping */
93	posix_id_t		*pid;		/* Posix ID found via IDMU */
94	idmap_retcode		*rc;
95	adutils_rc		ad_rc;
96	adutils_result_t	*result;
97
98	/*
99	 * The LDAP search entry result is placed here to be processed
100	 * when the search done result is received.
101	 */
102	LDAPMessage		*search_res;	/* The LDAP search result */
103} idmap_q_t;
104
105/* Batch context structure; typedef is in header file */
106struct idmap_query_state {
107	adutils_query_state_t	*qs;
108	int			qsize;		/* Queue size */
109	uint32_t		qcount;		/* Number of queued requests */
110	const char		*ad_unixuser_attr;
111	const char		*ad_unixgroup_attr;
112	int			directory_based_mapping;	/* enum */
113	char			*default_domain;
114	idmap_q_t		queries[1];	/* array of query results */
115};
116
117static pthread_t	reaperid = 0;
118
119/*
120 * Keep connection management simple for now, extend or replace later
121 * with updated libsldap code.
122 */
123#define	ADREAPERSLEEP	60
124
125/*
126 * Idle connection reaping side of connection management
127 *
128 * Every minute wake up and look for connections that have been idle for
129 * five minutes or more and close them.
130 */
131/*ARGSUSED*/
132static
133void
134adreaper(void *arg)
135{
136	timespec_t	ts;
137
138	ts.tv_sec = ADREAPERSLEEP;
139	ts.tv_nsec = 0;
140
141	for (;;) {
142		/*
143		 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
144		 * portable than usleep(3C)
145		 */
146		(void) nanosleep(&ts, NULL);
147		adutils_reap_idle_connections();
148	}
149}
150
151/*
152 * Take ad_host_config_t information, create a ad_host_t,
153 * populate it and add it to the list of hosts.
154 */
155
156int
157idmap_add_ds(adutils_ad_t *ad, const char *host, int port)
158{
159	int	ret = -1;
160
161	if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS)
162		ret = 0;
163
164	/* Start reaper if it doesn't exist */
165	if (ret == 0 && reaperid == 0)
166		(void) pthread_create(&reaperid, NULL,
167		    (void *(*)(void *))adreaper, (void *)NULL);
168	return (ret);
169}
170
171static
172idmap_retcode
173map_adrc2idmaprc(adutils_rc adrc)
174{
175	switch (adrc) {
176	case ADUTILS_SUCCESS:
177		return (IDMAP_SUCCESS);
178	case ADUTILS_ERR_NOTFOUND:
179		return (IDMAP_ERR_NOTFOUND);
180	case ADUTILS_ERR_MEMORY:
181		return (IDMAP_ERR_MEMORY);
182	case ADUTILS_ERR_DOMAIN:
183		return (IDMAP_ERR_DOMAIN);
184	case ADUTILS_ERR_OTHER:
185		return (IDMAP_ERR_OTHER);
186	case ADUTILS_ERR_RETRIABLE_NET_ERR:
187		return (IDMAP_ERR_RETRIABLE_NET_ERR);
188	default:
189		return (IDMAP_ERR_INTERNAL);
190	}
191	/* NOTREACHED */
192}
193
194idmap_retcode
195idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries,
196	int directory_based_mapping, const char *default_domain,
197	idmap_query_state_t **state)
198{
199	idmap_query_state_t	*new_state;
200	adutils_rc		rc;
201
202	*state = NULL;
203
204	assert(ad != NULL);
205
206	new_state = calloc(1, sizeof (idmap_query_state_t) +
207	    (nqueries - 1) * sizeof (idmap_q_t));
208	if (new_state == NULL)
209		return (IDMAP_ERR_MEMORY);
210
211	if ((rc = adutils_lookup_batch_start(ad, nqueries,
212	    idmap_ldap_res_search_cb, new_state, &new_state->qs))
213	    != ADUTILS_SUCCESS) {
214		idmap_lookup_release_batch(&new_state);
215		return (map_adrc2idmaprc(rc));
216	}
217
218	new_state->default_domain = strdup(default_domain);
219	if (new_state->default_domain == NULL) {
220		idmap_lookup_release_batch(&new_state);
221		return (IDMAP_ERR_MEMORY);
222	}
223
224	new_state->directory_based_mapping = directory_based_mapping;
225	new_state->qsize = nqueries;
226	*state = new_state;
227	return (IDMAP_SUCCESS);
228}
229
230/*
231 * Set unixuser_attr and unixgroup_attr for AD-based name mapping
232 */
233void
234idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
235		const char *unixuser_attr, const char *unixgroup_attr)
236{
237	state->ad_unixuser_attr = unixuser_attr;
238	state->ad_unixgroup_attr = unixgroup_attr;
239}
240
241/*
242 * Take parsed attribute values from a search result entry and check if
243 * it is the result that was desired and, if so, set the result fields
244 * of the given idmap_q_t.
245 *
246 * Except for dn and attr, all strings are consumed, either by transferring
247 * them over into the request results (where the caller will eventually free
248 * them) or by freeing them here.  Note that this aligns with the "const"
249 * declarations below.
250 */
251static
252void
253idmap_setqresults(
254    idmap_q_t *q,
255    char *san,
256    const char *dn,
257    const char *attr,
258    char *value,
259    char *sid,
260    rid_t rid,
261    int sid_type,
262    char *unixname,
263    posix_id_t pid)
264{
265	char *domain;
266	int err1;
267
268	assert(dn != NULL);
269
270	if ((domain = adutils_dn2dns(dn)) == NULL)
271		goto out;
272
273	if (q->ecanonname != NULL && san != NULL) {
274		/* Check that this is the canonname that we were looking for */
275		if (u8_strcmp(q->ecanonname, san, 0,
276		    U8_STRCMP_CI_LOWER, /* no normalization, for now */
277		    U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
278			goto out;
279	}
280
281	if (q->edomain != NULL) {
282		/* Check that this is the domain that we were looking for */
283		if (!domain_eq(q->edomain, domain))
284			goto out;
285	}
286
287	/* Copy the DN and attr and value */
288	if (q->dn != NULL)
289		*q->dn = strdup(dn);
290
291	if (q->attr != NULL && attr != NULL)
292		*q->attr = strdup(attr);
293
294	if (q->value != NULL && value != NULL) {
295		*q->value = value;
296		value = NULL;
297	}
298
299	/* Set results */
300	if (q->sid) {
301		*q->sid = sid;
302		sid = NULL;
303	}
304	if (q->rid)
305		*q->rid = rid;
306	if (q->sid_type)
307		*q->sid_type = sid_type;
308	if (q->unixname) {
309		*q->unixname = unixname;
310		unixname = NULL;
311	}
312	if (q->domain != NULL) {
313		*q->domain = domain;
314		domain = NULL;
315	}
316	if (q->canonname != NULL) {
317		/*
318		 * The caller may be replacing the given winname by its
319		 * canonical name and therefore free any old name before
320		 * overwriting the field by the canonical name.
321		 */
322		free(*q->canonname);
323		*q->canonname = san;
324		san = NULL;
325	}
326
327	if (q->pid != NULL && pid != IDMAP_SENTINEL_PID) {
328		*q->pid = pid;
329	}
330
331	q->ad_rc = ADUTILS_SUCCESS;
332
333out:
334	/* Free unused attribute values */
335	free(san);
336	free(sid);
337	free(domain);
338	free(unixname);
339	free(value);
340}
341
342#define	BVAL_CASEEQ(bv, str) \
343		(((*(bv))->bv_len == (sizeof (str) - 1)) && \
344		    strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
345
346/*
347 * Extract the class of the result entry.  Returns 1 on success, 0 on
348 * failure.
349 */
350static
351int
352idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
353{
354	BerValue	**cbval;
355
356	*sid_type = IDMAP_SID;
357	if (bvalues == NULL)
358		return (0);
359
360	/*
361	 * We consider Computer to be a subclass of User, so we can just
362	 * ignore Computer entries and pay attention to the accompanying
363	 * User entries.
364	 */
365	for (cbval = bvalues; *cbval != NULL; cbval++) {
366		if (BVAL_CASEEQ(cbval, "group")) {
367			*sid_type = IDMAP_GSID;
368			break;
369		} else if (BVAL_CASEEQ(cbval, "user")) {
370			*sid_type = IDMAP_USID;
371			break;
372		}
373		/*
374		 * "else if (*sid_type = IDMAP_USID)" then this is a
375		 * new sub-class of user -- what to do with it??
376		 */
377	}
378
379	return (1);
380}
381
382/*
383 * Handle a given search result entry
384 */
385static
386void
387idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
388	LDAPMessage *res, LDAP *ld)
389{
390	BerValue		**bvalues;
391	const char		*attr = NULL;
392	char			*value = NULL;
393	char			*unix_name = NULL;
394	char			*dn;
395	char			*san = NULL;
396	char			*sid = NULL;
397	rid_t			rid = 0;
398	int			sid_type;
399	int			ok;
400	posix_id_t		pid = IDMAP_SENTINEL_PID;
401
402	assert(q->rc != NULL);
403	assert(q->domain == NULL || *q->domain == NULL);
404
405	if ((dn = ldap_get_dn(ld, res)) == NULL)
406		return;
407
408	bvalues = ldap_get_values_len(ld, res, OBJCLASS);
409	if (bvalues == NULL) {
410		/*
411		 * Didn't find objectclass. Something's wrong with our
412		 * AD data.
413		 */
414		idmapdlog(LOG_ERR, "%s has no %s", dn, OBJCLASS);
415		goto out;
416	}
417	ok = idmap_bv_objclass2sidtype(bvalues, &sid_type);
418	ldap_value_free_len(bvalues);
419	if (!ok) {
420		/*
421		 * Didn't understand objectclass. Something's wrong with our
422		 * AD data.
423		 */
424		idmapdlog(LOG_ERR, "%s has unexpected %s", dn, OBJCLASS);
425		goto out;
426	}
427
428	if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU &&
429	    q->pid != NULL) {
430		if (sid_type == IDMAP_USID)
431			attr = UIDNUMBER;
432		else if (sid_type == IDMAP_GSID)
433			attr = GIDNUMBER;
434		if (attr != NULL) {
435			bvalues = ldap_get_values_len(ld, res, attr);
436			if (bvalues != NULL) {
437				value = adutils_bv_str(bvalues[0]);
438				if (!adutils_bv_uint(bvalues[0], &pid)) {
439					idmapdlog(LOG_ERR,
440					    "%s has Invalid %s value \"%s\"",
441					    dn, attr, value);
442				}
443				ldap_value_free_len(bvalues);
444			}
445		}
446	}
447
448	if (state->directory_based_mapping == DIRECTORY_MAPPING_NAME &&
449	    q->unixname != NULL) {
450		/*
451		 * If the caller has requested unixname then determine the
452		 * AD attribute name that will have the unixname, and retrieve
453		 * its value.
454		 */
455		idmap_id_type esidtype;
456		/*
457		 * Determine the target type.
458		 *
459		 * If the caller specified one, use that.  Otherwise, give the
460		 * same type that as we found for the Windows user.
461		 */
462		esidtype = q->esidtype;
463		if (esidtype == IDMAP_SID)
464			esidtype = sid_type;
465
466		if (esidtype == IDMAP_USID)
467			attr = state->ad_unixuser_attr;
468		else if (esidtype == IDMAP_GSID)
469			attr = state->ad_unixgroup_attr;
470
471		if (attr != NULL) {
472			bvalues = ldap_get_values_len(ld, res, attr);
473			if (bvalues != NULL) {
474				unix_name = adutils_bv_str(bvalues[0]);
475				ldap_value_free_len(bvalues);
476				value = strdup(unix_name);
477			}
478		}
479	}
480
481	bvalues = ldap_get_values_len(ld, res, SAN);
482	if (bvalues != NULL) {
483		san = adutils_bv_str(bvalues[0]);
484		ldap_value_free_len(bvalues);
485	}
486
487	if (q->sid != NULL) {
488		bvalues = ldap_get_values_len(ld, res, OBJSID);
489		if (bvalues != NULL) {
490			sid = adutils_bv_objsid2sidstr(bvalues[0], &rid);
491			ldap_value_free_len(bvalues);
492		}
493	}
494
495	idmap_setqresults(q, san, dn,
496	    attr, value,
497	    sid, rid, sid_type,
498	    unix_name, pid);
499
500out:
501	ldap_memfree(dn);
502}
503
504void
505idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
506		void *argp)
507{
508	idmap_query_state_t	*state = (idmap_query_state_t *)argp;
509	idmap_q_t		*q = &(state->queries[qid]);
510
511	switch (rc) {
512	case LDAP_RES_SEARCH_RESULT:
513		if (q->search_res != NULL) {
514			idmap_extract_object(state, q, q->search_res, ld);
515			(void) ldap_msgfree(q->search_res);
516			q->search_res = NULL;
517		} else
518			q->ad_rc = ADUTILS_ERR_NOTFOUND;
519		break;
520	case LDAP_RES_SEARCH_ENTRY:
521		if (q->search_res == NULL) {
522			q->search_res = *res;
523			*res = NULL;
524		}
525		break;
526	default:
527		break;
528	}
529}
530
531static
532void
533idmap_cleanup_batch(idmap_query_state_t *batch)
534{
535	int i;
536
537	for (i = 0; i < batch->qcount; i++) {
538		if (batch->queries[i].ecanonname != NULL)
539			free(batch->queries[i].ecanonname);
540		batch->queries[i].ecanonname = NULL;
541		if (batch->queries[i].edomain != NULL)
542			free(batch->queries[i].edomain);
543		batch->queries[i].edomain = NULL;
544	}
545}
546
547/*
548 * This routine frees the idmap_query_state_t structure
549 */
550void
551idmap_lookup_release_batch(idmap_query_state_t **state)
552{
553	if (state == NULL || *state == NULL)
554		return;
555	adutils_lookup_batch_release(&(*state)->qs);
556	idmap_cleanup_batch(*state);
557	free((*state)->default_domain);
558	free(*state);
559	*state = NULL;
560}
561
562idmap_retcode
563idmap_lookup_batch_end(idmap_query_state_t **state)
564{
565	adutils_rc		ad_rc;
566	int			i;
567	idmap_query_state_t	*id_qs = *state;
568
569	ad_rc = adutils_lookup_batch_end(&id_qs->qs);
570
571	/*
572	 * Map adutils rc to idmap_retcode in each
573	 * query because consumers in dbutils.c
574	 * expects idmap_retcode.
575	 */
576	for (i = 0; i < id_qs->qcount; i++) {
577		*id_qs->queries[i].rc =
578		    map_adrc2idmaprc(id_qs->queries[i].ad_rc);
579	}
580	idmap_lookup_release_batch(state);
581	return (map_adrc2idmaprc(ad_rc));
582}
583
584/*
585 * Send one prepared search, queue up msgid, process what results are
586 * available
587 */
588static
589idmap_retcode
590idmap_batch_add1(idmap_query_state_t *state, const char *filter,
591	char *ecanonname, char *edomain, idmap_id_type esidtype,
592	char **dn, char **attr, char **value,
593	char **canonname, char **dname,
594	char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname,
595	posix_id_t *pid,
596	idmap_retcode *rc)
597{
598	adutils_rc	ad_rc;
599	int		qid, i;
600	idmap_q_t	*q;
601	char	*attrs[20];	/* Plenty */
602
603	qid = atomic_inc_32_nv(&state->qcount) - 1;
604	q = &(state->queries[qid]);
605
606	assert(qid < state->qsize);
607
608	/*
609	 * Remember the expected canonname, domainname and unix type
610	 * so we can check the results * against it
611	 */
612	q->ecanonname = ecanonname;
613	q->edomain = edomain;
614	q->esidtype = esidtype;
615
616	/* Remember where to put the results */
617	q->canonname = canonname;
618	q->sid = sid;
619	q->domain = dname;
620	q->rid = rid;
621	q->sid_type = sid_type;
622	q->rc = rc;
623	q->unixname = unixname;
624	q->dn = dn;
625	q->attr = attr;
626	q->value = value;
627	q->pid = pid;
628
629	/* Add attributes that are not always needed */
630	i = 0;
631	attrs[i++] = SAN;
632	attrs[i++] = OBJSID;
633	attrs[i++] = OBJCLASS;
634
635	if (unixname != NULL) {
636		/* Add unixuser/unixgroup attribute names to the attrs list */
637		if (esidtype != IDMAP_GSID &&
638		    state->ad_unixuser_attr != NULL)
639			attrs[i++] = (char *)state->ad_unixuser_attr;
640		if (esidtype != IDMAP_USID &&
641		    state->ad_unixgroup_attr != NULL)
642			attrs[i++] = (char *)state->ad_unixgroup_attr;
643	}
644
645	if (pid != NULL) {
646		if (esidtype != IDMAP_GSID)
647			attrs[i++] = UIDNUMBER;
648		if (esidtype != IDMAP_USID)
649			attrs[i++] = GIDNUMBER;
650	}
651
652	attrs[i] = NULL;
653
654	/*
655	 * Provide sane defaults for the results in case we never hear
656	 * back from the DS before closing the connection.
657	 *
658	 * In particular we default the result to indicate a retriable
659	 * error.  The first complete matching result entry will cause
660	 * this to be set to IDMAP_SUCCESS, and the end of the results
661	 * for this search will cause this to indicate "not found" if no
662	 * result entries arrived or no complete ones matched the lookup
663	 * we were doing.
664	 */
665	*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
666	if (sid_type != NULL)
667		*sid_type = IDMAP_SID;
668	if (sid != NULL)
669		*sid = NULL;
670	if (dname != NULL)
671		*dname = NULL;
672	if (rid != NULL)
673		*rid = 0;
674	if (dn != NULL)
675		*dn = NULL;
676	if (attr != NULL)
677		*attr = NULL;
678	if (value != NULL)
679		*value = NULL;
680
681	/*
682	 * Don't set *canonname to NULL because it may be pointing to the
683	 * given winname. Later on if we get a canonical name from AD the
684	 * old name if any will be freed before assigning the new name.
685	 */
686
687	/*
688	 * Invoke the mother of all APIs i.e. the adutils API
689	 */
690	ad_rc = adutils_lookup_batch_add(state->qs, filter,
691	    (const char **)attrs,
692	    edomain, &q->result, &q->ad_rc);
693	return (map_adrc2idmaprc(ad_rc));
694}
695
696idmap_retcode
697idmap_name2sid_batch_add1(idmap_query_state_t *state,
698	const char *name, const char *dname, idmap_id_type esidtype,
699	char **dn, char **attr, char **value,
700	char **canonname, char **sid, rid_t *rid,
701	idmap_id_type *sid_type, char **unixname,
702	posix_id_t *pid, idmap_retcode *rc)
703{
704	idmap_retcode	retcode;
705	char		*filter, *s_name;
706	char		*ecanonname, *edomain; /* expected canonname */
707
708	/*
709	 * Strategy: search the global catalog for user/group by
710	 * sAMAccountName = user/groupname with "" as the base DN and by
711	 * userPrincipalName = user/groupname@domain.  The result
712	 * entries will be checked to conform to the name and domain
713	 * name given here.  The DN, sAMAccountName, userPrincipalName,
714	 * objectSid and objectClass of the result entries are all we
715	 * need to figure out which entries match the lookup, the SID of
716	 * the user/group and whether it is a user or a group.
717	 */
718
719	if ((ecanonname = strdup(name)) == NULL)
720		return (IDMAP_ERR_MEMORY);
721
722	if (dname == NULL || *dname == '\0') {
723		/* 'name' not qualified and dname not given */
724		dname = state->default_domain;
725		edomain = strdup(dname);
726		if (edomain == NULL) {
727			free(ecanonname);
728			return (IDMAP_ERR_MEMORY);
729		}
730	} else {
731		if ((edomain = strdup(dname)) == NULL) {
732			free(ecanonname);
733			return (IDMAP_ERR_MEMORY);
734		}
735	}
736
737	if (!adutils_lookup_check_domain(state->qs, dname)) {
738		free(ecanonname);
739		free(edomain);
740		return (IDMAP_ERR_DOMAIN_NOTFOUND);
741	}
742
743	s_name = sanitize_for_ldap_filter(name);
744	if (s_name == NULL) {
745		free(ecanonname);
746		free(edomain);
747		return (IDMAP_ERR_MEMORY);
748	}
749
750	/* Assemble filter */
751	(void) asprintf(&filter, SANFILTER, s_name);
752	if (s_name != name)
753		free(s_name);
754	if (filter == NULL) {
755		free(ecanonname);
756		free(edomain);
757		return (IDMAP_ERR_MEMORY);
758	}
759
760	retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
761	    esidtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
762	    unixname, pid, rc);
763
764	free(filter);
765
766	return (retcode);
767}
768
769idmap_retcode
770idmap_sid2name_batch_add1(idmap_query_state_t *state,
771	const char *sid, const rid_t *rid, idmap_id_type esidtype,
772	char **dn, char **attr, char **value,
773	char **name, char **dname, idmap_id_type *sid_type,
774	char **unixname, posix_id_t *pid, idmap_retcode *rc)
775{
776	idmap_retcode	retcode;
777	int		ret;
778	char		*filter;
779	char		cbinsid[ADUTILS_MAXHEXBINSID + 1];
780
781	/*
782	 * Strategy: search [the global catalog] for user/group by
783	 * objectSid = SID with empty base DN.  The DN, sAMAccountName
784	 * and objectClass of the result are all we need to figure out
785	 * the name of the SID and whether it is a user, a group or a
786	 * computer.
787	 */
788
789	if (!adutils_lookup_check_sid_prefix(state->qs, sid))
790		return (IDMAP_ERR_DOMAIN_NOTFOUND);
791
792	ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
793	if (ret != 0)
794		return (IDMAP_ERR_SID);
795
796	/* Assemble filter */
797	(void) asprintf(&filter, OBJSIDFILTER, cbinsid, cbinsid);
798	if (filter == NULL)
799		return (IDMAP_ERR_MEMORY);
800
801	retcode = idmap_batch_add1(state, filter, NULL, NULL, esidtype,
802	    dn, attr, value, name, dname, NULL, NULL, sid_type, unixname,
803	    pid, rc);
804
805	free(filter);
806
807	return (retcode);
808}
809
810idmap_retcode
811idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
812	const char *unixname, int is_user, int is_wuser,
813	char **dn, char **attr, char **value,
814	char **sid, rid_t *rid, char **name,
815	char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
816{
817	idmap_retcode	retcode;
818	char		*filter, *s_unixname;
819	const char	*attrname;
820
821	/* Get unixuser or unixgroup AD attribute name */
822	attrname = (is_user) ?
823	    state->ad_unixuser_attr : state->ad_unixgroup_attr;
824	if (attrname == NULL)
825		return (IDMAP_ERR_NOTFOUND);
826
827	s_unixname = sanitize_for_ldap_filter(unixname);
828	if (s_unixname == NULL)
829		return (IDMAP_ERR_MEMORY);
830
831	/*  Assemble filter */
832	(void) asprintf(&filter, "(&(objectclass=%s)(%s=%s))",
833	    is_wuser ? "user" : "group", attrname, s_unixname);
834	if (s_unixname != unixname)
835		free(s_unixname);
836	if (filter == NULL) {
837		return (IDMAP_ERR_MEMORY);
838	}
839
840	retcode = idmap_batch_add1(state, filter, NULL, NULL,
841	    IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
842	    NULL, NULL, rc);
843
844	if (retcode == IDMAP_SUCCESS && attr != NULL) {
845		if ((*attr = strdup(attrname)) == NULL)
846			retcode = IDMAP_ERR_MEMORY;
847	}
848
849	if (retcode == IDMAP_SUCCESS && value != NULL) {
850		if ((*value = strdup(unixname)) == NULL)
851			retcode = IDMAP_ERR_MEMORY;
852	}
853
854	free(filter);
855
856	return (retcode);
857}
858
859idmap_retcode
860idmap_pid2sid_batch_add1(idmap_query_state_t *state,
861	posix_id_t pid, int is_user,
862	char **dn, char **attr, char **value,
863	char **sid, rid_t *rid, char **name,
864	char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
865{
866	idmap_retcode	retcode;
867	char		*filter;
868	const char	*attrname;
869
870	/*  Assemble filter */
871	if (is_user) {
872		(void) asprintf(&filter, UIDNUMBERFILTER, pid);
873		attrname = UIDNUMBER;
874	} else {
875		(void) asprintf(&filter, GIDNUMBERFILTER, pid);
876		attrname = GIDNUMBER;
877	}
878	if (filter == NULL)
879		return (IDMAP_ERR_MEMORY);
880
881	retcode = idmap_batch_add1(state, filter, NULL, NULL,
882	    IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
883	    NULL, NULL, rc);
884
885	if (retcode == IDMAP_SUCCESS && attr != NULL) {
886		if ((*attr = strdup(attrname)) == NULL)
887			retcode = IDMAP_ERR_MEMORY;
888	}
889
890	if (retcode == IDMAP_SUCCESS && value != NULL) {
891		(void) asprintf(value, "%u", pid);
892		if (*value == NULL)
893			retcode = IDMAP_ERR_MEMORY;
894	}
895
896	free(filter);
897
898	return (retcode);
899}
900