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.
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.
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]
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
27 * Processes name2sid & sid2name batched lookups for a given user or
28 * computer from an AD Directory server using GSSAPI authentication
38 #include <sasl/sasl.h>
48 #include <sys/u8_textprep.h>
49 #include "libadutils.h"
50 #include "nldaputils.h"
53 /* Attribute names and filter format strings */
54 #define SAN "sAMAccountName"
55 #define OBJSID "objectSid"
56 #define OBJCLASS "objectClass"
57 #define UIDNUMBER "uidNumber"
58 #define GIDNUMBER "gidNumber"
59 #define UIDNUMBERFILTER "(&(objectclass=user)(uidNumber=%u))"
60 #define GIDNUMBERFILTER "(&(objectclass=group)(gidNumber=%u))"
61 #define SANFILTER "(sAMAccountName=%s)"
62 #define OBJSIDFILTER "(objectSid=%s)"
64 void idmap_ldap_res_search_cb(LDAP
*ld
, LDAPMessage
**res
, int rc
,
68 * A place to put the results of a batched (async) query
70 * There is one of these for every query added to a batch object
71 * (idmap_query_state, see below).
73 typedef struct idmap_q
{
75 * data used for validating search result entries for name->SID
78 char *ecanonname
; /* expected canon name */
79 char *edomain
; /* expected domain name */
80 idmap_id_type esidtype
; /* expected SID type */
82 char **canonname
; /* actual canon name */
83 char **domain
; /* name of domain of object */
84 char **sid
; /* stringified SID */
86 idmap_id_type
*sid_type
; /* user or group SID? */
87 char **unixname
; /* unixname for name mapping */
88 char **dn
; /* DN of entry */
89 char **attr
; /* Attr for name mapping */
90 char **value
; /* value for name mapping */
91 posix_id_t
*pid
; /* Posix ID found via IDMU */
94 adutils_result_t
*result
;
97 * The LDAP search entry result is placed here to be processed
98 * when the search done result is received.
100 LDAPMessage
*search_res
; /* The LDAP search result */
103 /* Batch context structure; typedef is in header file */
104 struct idmap_query_state
{
105 adutils_query_state_t
*qs
;
106 int qsize
; /* Queue size */
107 uint32_t qcount
; /* Number of queued requests */
108 const char *ad_unixuser_attr
;
109 const char *ad_unixgroup_attr
;
110 int directory_based_mapping
; /* enum */
111 char *default_domain
;
112 idmap_q_t queries
[1]; /* array of query results */
115 static pthread_t reaperid
= 0;
118 * Keep connection management simple for now, extend or replace later
119 * with updated libsldap code.
121 #define ADREAPERSLEEP 60
124 * Idle connection reaping side of connection management
126 * Every minute wake up and look for connections that have been idle for
127 * five minutes or more and close them.
136 ts
.tv_sec
= ADREAPERSLEEP
;
141 * nanosleep(3RT) is thead-safe (no SIGALRM) and more
142 * portable than usleep(3C)
144 (void) nanosleep(&ts
, NULL
);
145 adutils_reap_idle_connections();
150 * Take ad_host_config_t information, create a ad_host_t,
151 * populate it and add it to the list of hosts.
155 idmap_add_ds(adutils_ad_t
*ad
, const char *host
, int port
)
159 if (adutils_add_ds(ad
, host
, port
) == ADUTILS_SUCCESS
)
162 /* Start reaper if it doesn't exist */
163 if (ret
== 0 && reaperid
== 0)
164 (void) pthread_create(&reaperid
, NULL
,
165 (void *(*)(void *))adreaper
, (void *)NULL
);
171 map_adrc2idmaprc(adutils_rc adrc
)
174 case ADUTILS_SUCCESS
:
175 return (IDMAP_SUCCESS
);
176 case ADUTILS_ERR_NOTFOUND
:
177 return (IDMAP_ERR_NOTFOUND
);
178 case ADUTILS_ERR_MEMORY
:
179 return (IDMAP_ERR_MEMORY
);
180 case ADUTILS_ERR_DOMAIN
:
181 return (IDMAP_ERR_DOMAIN
);
182 case ADUTILS_ERR_OTHER
:
183 return (IDMAP_ERR_OTHER
);
184 case ADUTILS_ERR_RETRIABLE_NET_ERR
:
185 return (IDMAP_ERR_RETRIABLE_NET_ERR
);
187 return (IDMAP_ERR_INTERNAL
);
193 idmap_lookup_batch_start(adutils_ad_t
*ad
, int nqueries
,
194 int directory_based_mapping
, const char *default_domain
,
195 idmap_query_state_t
**state
)
197 idmap_query_state_t
*new_state
;
204 new_state
= calloc(1, sizeof (idmap_query_state_t
) +
205 (nqueries
- 1) * sizeof (idmap_q_t
));
206 if (new_state
== NULL
)
207 return (IDMAP_ERR_MEMORY
);
209 if ((rc
= adutils_lookup_batch_start(ad
, nqueries
,
210 idmap_ldap_res_search_cb
, new_state
, &new_state
->qs
))
211 != ADUTILS_SUCCESS
) {
212 idmap_lookup_release_batch(&new_state
);
213 return (map_adrc2idmaprc(rc
));
216 new_state
->default_domain
= strdup(default_domain
);
217 if (new_state
->default_domain
== NULL
) {
218 idmap_lookup_release_batch(&new_state
);
219 return (IDMAP_ERR_MEMORY
);
222 new_state
->directory_based_mapping
= directory_based_mapping
;
223 new_state
->qsize
= nqueries
;
225 return (IDMAP_SUCCESS
);
229 * Set unixuser_attr and unixgroup_attr for AD-based name mapping
232 idmap_lookup_batch_set_unixattr(idmap_query_state_t
*state
,
233 const char *unixuser_attr
, const char *unixgroup_attr
)
235 state
->ad_unixuser_attr
= unixuser_attr
;
236 state
->ad_unixgroup_attr
= unixgroup_attr
;
240 * Take parsed attribute values from a search result entry and check if
241 * it is the result that was desired and, if so, set the result fields
242 * of the given idmap_q_t.
244 * Except for dn and attr, all strings are consumed, either by transferring
245 * them over into the request results (where the caller will eventually free
246 * them) or by freeing them here. Note that this aligns with the "const"
247 * declarations below.
268 if ((domain
= adutils_dn2dns(dn
)) == NULL
)
271 if (q
->ecanonname
!= NULL
&& san
!= NULL
) {
272 /* Check that this is the canonname that we were looking for */
273 if (u8_strcmp(q
->ecanonname
, san
, 0,
274 U8_STRCMP_CI_LOWER
, /* no normalization, for now */
275 U8_UNICODE_LATEST
, &err1
) != 0 || err1
!= 0)
279 if (q
->edomain
!= NULL
) {
280 /* Check that this is the domain that we were looking for */
281 if (!domain_eq(q
->edomain
, domain
))
285 /* Copy the DN and attr and value */
289 if (q
->attr
!= NULL
&& attr
!= NULL
)
290 *q
->attr
= strdup(attr
);
292 if (q
->value
!= NULL
&& value
!= NULL
) {
305 *q
->sid_type
= sid_type
;
307 *q
->unixname
= unixname
;
310 if (q
->domain
!= NULL
) {
314 if (q
->canonname
!= NULL
) {
316 * The caller may be replacing the given winname by its
317 * canonical name and therefore free any old name before
318 * overwriting the field by the canonical name.
325 if (q
->pid
!= NULL
&& pid
!= IDMAP_SENTINEL_PID
) {
329 q
->ad_rc
= ADUTILS_SUCCESS
;
332 /* Free unused attribute values */
340 #define BVAL_CASEEQ(bv, str) \
341 (((*(bv))->bv_len == (sizeof (str) - 1)) && \
342 strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
345 * Extract the class of the result entry. Returns 1 on success, 0 on
350 idmap_bv_objclass2sidtype(BerValue
**bvalues
, int *sid_type
)
354 *sid_type
= IDMAP_SID
;
359 * We consider Computer to be a subclass of User, so we can just
360 * ignore Computer entries and pay attention to the accompanying
363 for (cbval
= bvalues
; *cbval
!= NULL
; cbval
++) {
364 if (BVAL_CASEEQ(cbval
, "group")) {
365 *sid_type
= IDMAP_GSID
;
367 } else if (BVAL_CASEEQ(cbval
, "user")) {
368 *sid_type
= IDMAP_USID
;
372 * "else if (*sid_type = IDMAP_USID)" then this is a
373 * new sub-class of user -- what to do with it??
381 * Handle a given search result entry
385 idmap_extract_object(idmap_query_state_t
*state
, idmap_q_t
*q
,
386 LDAPMessage
*res
, LDAP
*ld
)
389 const char *attr
= NULL
;
391 char *unix_name
= NULL
;
398 posix_id_t pid
= IDMAP_SENTINEL_PID
;
400 assert(q
->rc
!= NULL
);
401 assert(q
->domain
== NULL
|| *q
->domain
== NULL
);
403 if ((dn
= ldap_get_dn(ld
, res
)) == NULL
)
406 bvalues
= ldap_get_values_len(ld
, res
, OBJCLASS
);
407 if (bvalues
== NULL
) {
409 * Didn't find objectclass. Something's wrong with our
412 idmapdlog(LOG_ERR
, "%s has no %s", dn
, OBJCLASS
);
415 ok
= idmap_bv_objclass2sidtype(bvalues
, &sid_type
);
416 ldap_value_free_len(bvalues
);
419 * Didn't understand objectclass. Something's wrong with our
422 idmapdlog(LOG_ERR
, "%s has unexpected %s", dn
, OBJCLASS
);
426 if (state
->directory_based_mapping
== DIRECTORY_MAPPING_IDMU
&&
428 if (sid_type
== IDMAP_USID
)
430 else if (sid_type
== IDMAP_GSID
)
433 bvalues
= ldap_get_values_len(ld
, res
, attr
);
434 if (bvalues
!= NULL
) {
435 value
= adutils_bv_str(bvalues
[0]);
436 if (!adutils_bv_uint(bvalues
[0], &pid
)) {
438 "%s has Invalid %s value \"%s\"",
441 ldap_value_free_len(bvalues
);
446 if (state
->directory_based_mapping
== DIRECTORY_MAPPING_NAME
&&
447 q
->unixname
!= NULL
) {
449 * If the caller has requested unixname then determine the
450 * AD attribute name that will have the unixname, and retrieve
453 idmap_id_type esidtype
;
455 * Determine the target type.
457 * If the caller specified one, use that. Otherwise, give the
458 * same type that as we found for the Windows user.
460 esidtype
= q
->esidtype
;
461 if (esidtype
== IDMAP_SID
)
464 if (esidtype
== IDMAP_USID
)
465 attr
= state
->ad_unixuser_attr
;
466 else if (esidtype
== IDMAP_GSID
)
467 attr
= state
->ad_unixgroup_attr
;
470 bvalues
= ldap_get_values_len(ld
, res
, attr
);
471 if (bvalues
!= NULL
) {
472 unix_name
= adutils_bv_str(bvalues
[0]);
473 ldap_value_free_len(bvalues
);
474 value
= strdup(unix_name
);
479 bvalues
= ldap_get_values_len(ld
, res
, SAN
);
480 if (bvalues
!= NULL
) {
481 san
= adutils_bv_str(bvalues
[0]);
482 ldap_value_free_len(bvalues
);
485 if (q
->sid
!= NULL
) {
486 bvalues
= ldap_get_values_len(ld
, res
, OBJSID
);
487 if (bvalues
!= NULL
) {
488 sid
= adutils_bv_objsid2sidstr(bvalues
[0], &rid
);
489 ldap_value_free_len(bvalues
);
493 idmap_setqresults(q
, san
, dn
,
503 idmap_ldap_res_search_cb(LDAP
*ld
, LDAPMessage
**res
, int rc
, int qid
,
506 idmap_query_state_t
*state
= (idmap_query_state_t
*)argp
;
507 idmap_q_t
*q
= &(state
->queries
[qid
]);
510 case LDAP_RES_SEARCH_RESULT
:
511 if (q
->search_res
!= NULL
) {
512 idmap_extract_object(state
, q
, q
->search_res
, ld
);
513 (void) ldap_msgfree(q
->search_res
);
514 q
->search_res
= NULL
;
516 q
->ad_rc
= ADUTILS_ERR_NOTFOUND
;
518 case LDAP_RES_SEARCH_ENTRY
:
519 if (q
->search_res
== NULL
) {
520 q
->search_res
= *res
;
531 idmap_cleanup_batch(idmap_query_state_t
*batch
)
535 for (i
= 0; i
< batch
->qcount
; i
++) {
536 if (batch
->queries
[i
].ecanonname
!= NULL
)
537 free(batch
->queries
[i
].ecanonname
);
538 batch
->queries
[i
].ecanonname
= NULL
;
539 if (batch
->queries
[i
].edomain
!= NULL
)
540 free(batch
->queries
[i
].edomain
);
541 batch
->queries
[i
].edomain
= NULL
;
546 * This routine frees the idmap_query_state_t structure
549 idmap_lookup_release_batch(idmap_query_state_t
**state
)
551 if (state
== NULL
|| *state
== NULL
)
553 adutils_lookup_batch_release(&(*state
)->qs
);
554 idmap_cleanup_batch(*state
);
555 free((*state
)->default_domain
);
561 idmap_lookup_batch_end(idmap_query_state_t
**state
)
565 idmap_query_state_t
*id_qs
= *state
;
567 ad_rc
= adutils_lookup_batch_end(&id_qs
->qs
);
570 * Map adutils rc to idmap_retcode in each
571 * query because consumers in dbutils.c
572 * expects idmap_retcode.
574 for (i
= 0; i
< id_qs
->qcount
; i
++) {
575 *id_qs
->queries
[i
].rc
=
576 map_adrc2idmaprc(id_qs
->queries
[i
].ad_rc
);
578 idmap_lookup_release_batch(state
);
579 return (map_adrc2idmaprc(ad_rc
));
583 * Send one prepared search, queue up msgid, process what results are
588 idmap_batch_add1(idmap_query_state_t
*state
, const char *filter
,
589 char *ecanonname
, char *edomain
, idmap_id_type esidtype
,
590 char **dn
, char **attr
, char **value
,
591 char **canonname
, char **dname
,
592 char **sid
, rid_t
*rid
, idmap_id_type
*sid_type
, char **unixname
,
599 char *attrs
[20]; /* Plenty */
601 qid
= atomic_inc_32_nv(&state
->qcount
) - 1;
602 q
= &(state
->queries
[qid
]);
604 assert(qid
< state
->qsize
);
607 * Remember the expected canonname, domainname and unix type
608 * so we can check the results * against it
610 q
->ecanonname
= ecanonname
;
611 q
->edomain
= edomain
;
612 q
->esidtype
= esidtype
;
614 /* Remember where to put the results */
615 q
->canonname
= canonname
;
619 q
->sid_type
= sid_type
;
621 q
->unixname
= unixname
;
627 /* Add attributes that are not always needed */
631 attrs
[i
++] = OBJCLASS
;
633 if (unixname
!= NULL
) {
634 /* Add unixuser/unixgroup attribute names to the attrs list */
635 if (esidtype
!= IDMAP_GSID
&&
636 state
->ad_unixuser_attr
!= NULL
)
637 attrs
[i
++] = (char *)state
->ad_unixuser_attr
;
638 if (esidtype
!= IDMAP_USID
&&
639 state
->ad_unixgroup_attr
!= NULL
)
640 attrs
[i
++] = (char *)state
->ad_unixgroup_attr
;
644 if (esidtype
!= IDMAP_GSID
)
645 attrs
[i
++] = UIDNUMBER
;
646 if (esidtype
!= IDMAP_USID
)
647 attrs
[i
++] = GIDNUMBER
;
653 * Provide sane defaults for the results in case we never hear
654 * back from the DS before closing the connection.
656 * In particular we default the result to indicate a retriable
657 * error. The first complete matching result entry will cause
658 * this to be set to IDMAP_SUCCESS, and the end of the results
659 * for this search will cause this to indicate "not found" if no
660 * result entries arrived or no complete ones matched the lookup
663 *rc
= IDMAP_ERR_RETRIABLE_NET_ERR
;
664 if (sid_type
!= NULL
)
665 *sid_type
= IDMAP_SID
;
680 * Don't set *canonname to NULL because it may be pointing to the
681 * given winname. Later on if we get a canonical name from AD the
682 * old name if any will be freed before assigning the new name.
686 * Invoke the mother of all APIs i.e. the adutils API
688 ad_rc
= adutils_lookup_batch_add(state
->qs
, filter
,
689 (const char **)attrs
,
690 edomain
, &q
->result
, &q
->ad_rc
);
691 return (map_adrc2idmaprc(ad_rc
));
695 idmap_name2sid_batch_add1(idmap_query_state_t
*state
,
696 const char *name
, const char *dname
, idmap_id_type esidtype
,
697 char **dn
, char **attr
, char **value
,
698 char **canonname
, char **sid
, rid_t
*rid
,
699 idmap_id_type
*sid_type
, char **unixname
,
700 posix_id_t
*pid
, idmap_retcode
*rc
)
702 idmap_retcode retcode
;
703 char *filter
, *s_name
;
704 char *ecanonname
, *edomain
; /* expected canonname */
707 * Strategy: search the global catalog for user/group by
708 * sAMAccountName = user/groupname with "" as the base DN and by
709 * userPrincipalName = user/groupname@domain. The result
710 * entries will be checked to conform to the name and domain
711 * name given here. The DN, sAMAccountName, userPrincipalName,
712 * objectSid and objectClass of the result entries are all we
713 * need to figure out which entries match the lookup, the SID of
714 * the user/group and whether it is a user or a group.
717 if ((ecanonname
= strdup(name
)) == NULL
)
718 return (IDMAP_ERR_MEMORY
);
720 if (dname
== NULL
|| *dname
== '\0') {
721 /* 'name' not qualified and dname not given */
722 dname
= state
->default_domain
;
723 edomain
= strdup(dname
);
724 if (edomain
== NULL
) {
726 return (IDMAP_ERR_MEMORY
);
729 if ((edomain
= strdup(dname
)) == NULL
) {
731 return (IDMAP_ERR_MEMORY
);
735 if (!adutils_lookup_check_domain(state
->qs
, dname
)) {
738 return (IDMAP_ERR_DOMAIN_NOTFOUND
);
741 s_name
= sanitize_for_ldap_filter(name
);
742 if (s_name
== NULL
) {
745 return (IDMAP_ERR_MEMORY
);
748 /* Assemble filter */
749 (void) asprintf(&filter
, SANFILTER
, s_name
);
752 if (filter
== NULL
) {
755 return (IDMAP_ERR_MEMORY
);
758 retcode
= idmap_batch_add1(state
, filter
, ecanonname
, edomain
,
759 esidtype
, dn
, attr
, value
, canonname
, NULL
, sid
, rid
, sid_type
,
768 idmap_sid2name_batch_add1(idmap_query_state_t
*state
,
769 const char *sid
, const rid_t
*rid
, idmap_id_type esidtype
,
770 char **dn
, char **attr
, char **value
,
771 char **name
, char **dname
, idmap_id_type
*sid_type
,
772 char **unixname
, posix_id_t
*pid
, idmap_retcode
*rc
)
774 idmap_retcode retcode
;
777 char cbinsid
[ADUTILS_MAXHEXBINSID
+ 1];
780 * Strategy: search [the global catalog] for user/group by
781 * objectSid = SID with empty base DN. The DN, sAMAccountName
782 * and objectClass of the result are all we need to figure out
783 * the name of the SID and whether it is a user, a group or a
787 if (!adutils_lookup_check_sid_prefix(state
->qs
, sid
))
788 return (IDMAP_ERR_DOMAIN_NOTFOUND
);
790 ret
= adutils_txtsid2hexbinsid(sid
, rid
, &cbinsid
[0], sizeof (cbinsid
));
792 return (IDMAP_ERR_SID
);
794 /* Assemble filter */
795 (void) asprintf(&filter
, OBJSIDFILTER
, cbinsid
);
797 return (IDMAP_ERR_MEMORY
);
799 retcode
= idmap_batch_add1(state
, filter
, NULL
, NULL
, esidtype
,
800 dn
, attr
, value
, name
, dname
, NULL
, NULL
, sid_type
, unixname
,
809 idmap_unixname2sid_batch_add1(idmap_query_state_t
*state
,
810 const char *unixname
, int is_user
, int is_wuser
,
811 char **dn
, char **attr
, char **value
,
812 char **sid
, rid_t
*rid
, char **name
,
813 char **dname
, idmap_id_type
*sid_type
, idmap_retcode
*rc
)
815 idmap_retcode retcode
;
816 char *filter
, *s_unixname
;
817 const char *attrname
;
819 /* Get unixuser or unixgroup AD attribute name */
820 attrname
= (is_user
) ?
821 state
->ad_unixuser_attr
: state
->ad_unixgroup_attr
;
822 if (attrname
== NULL
)
823 return (IDMAP_ERR_NOTFOUND
);
825 s_unixname
= sanitize_for_ldap_filter(unixname
);
826 if (s_unixname
== NULL
)
827 return (IDMAP_ERR_MEMORY
);
829 /* Assemble filter */
830 (void) asprintf(&filter
, "(&(objectclass=%s)(%s=%s))",
831 is_wuser
? "user" : "group", attrname
, s_unixname
);
832 if (s_unixname
!= unixname
)
834 if (filter
== NULL
) {
835 return (IDMAP_ERR_MEMORY
);
838 retcode
= idmap_batch_add1(state
, filter
, NULL
, NULL
,
839 IDMAP_POSIXID
, dn
, NULL
, NULL
, name
, dname
, sid
, rid
, sid_type
,
842 if (retcode
== IDMAP_SUCCESS
&& attr
!= NULL
) {
843 if ((*attr
= strdup(attrname
)) == NULL
)
844 retcode
= IDMAP_ERR_MEMORY
;
847 if (retcode
== IDMAP_SUCCESS
&& value
!= NULL
) {
848 if ((*value
= strdup(unixname
)) == NULL
)
849 retcode
= IDMAP_ERR_MEMORY
;
858 idmap_pid2sid_batch_add1(idmap_query_state_t
*state
,
859 posix_id_t pid
, int is_user
,
860 char **dn
, char **attr
, char **value
,
861 char **sid
, rid_t
*rid
, char **name
,
862 char **dname
, idmap_id_type
*sid_type
, idmap_retcode
*rc
)
864 idmap_retcode retcode
;
866 const char *attrname
;
868 /* Assemble filter */
870 (void) asprintf(&filter
, UIDNUMBERFILTER
, pid
);
871 attrname
= UIDNUMBER
;
873 (void) asprintf(&filter
, GIDNUMBERFILTER
, pid
);
874 attrname
= GIDNUMBER
;
877 return (IDMAP_ERR_MEMORY
);
879 retcode
= idmap_batch_add1(state
, filter
, NULL
, NULL
,
880 IDMAP_POSIXID
, dn
, NULL
, NULL
, name
, dname
, sid
, rid
, sid_type
,
883 if (retcode
== IDMAP_SUCCESS
&& attr
!= NULL
) {
884 if ((*attr
= strdup(attrname
)) == NULL
)
885 retcode
= IDMAP_ERR_MEMORY
;
888 if (retcode
== IDMAP_SUCCESS
&& value
!= NULL
) {
889 (void) asprintf(value
, "%u", pid
);
891 retcode
= IDMAP_ERR_MEMORY
;