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