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 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 *
25 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
26 */
27
28 #include <malloc.h>
29 #include <synch.h>
30 #include <syslog.h>
31 #include <rpcsvc/ypclnt.h>
32 #include <rpcsvc/yp_prot.h>
33 #include <pthread.h>
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <signal.h>
38 #include <sys/stat.h>
39 #include <assert.h>
40 #include "ad_common.h"
41
42 static pthread_mutex_t statelock = PTHREAD_MUTEX_INITIALIZER;
43 static nssad_state_t state = {0};
44
45 static void
nssad_cfg_free_props(nssad_prop_t * props)46 nssad_cfg_free_props(nssad_prop_t *props)
47 {
48 if (props->domain_name != NULL) {
49 free(props->domain_name);
50 props->domain_name = NULL;
51 }
52 if (props->domain_controller != NULL) {
53 free(props->domain_controller);
54 props->domain_controller = NULL;
55 }
56 }
57
58 static int
nssad_cfg_discover_props(const char * domain,ad_disc_t ad_ctx,nssad_prop_t * props)59 nssad_cfg_discover_props(const char *domain, ad_disc_t ad_ctx,
60 nssad_prop_t *props)
61 {
62 ad_disc_refresh(ad_ctx);
63 if (ad_disc_set_DomainName(ad_ctx, domain) != 0)
64 return (-1);
65 if (props->domain_controller == NULL)
66 props->domain_controller =
67 ad_disc_get_DomainController(ad_ctx, AD_DISC_PREFER_SITE,
68 NULL);
69 return (0);
70 }
71
72 static int
nssad_cfg_reload_ad(nssad_prop_t * props,adutils_ad_t ** ad)73 nssad_cfg_reload_ad(nssad_prop_t *props, adutils_ad_t **ad)
74 {
75 int i;
76 adutils_ad_t *new;
77
78 if (props->domain_controller == NULL ||
79 props->domain_controller[0].host[0] == '\0')
80 return (0);
81 if (adutils_ad_alloc(&new, props->domain_name,
82 ADUTILS_AD_DATA) != ADUTILS_SUCCESS)
83 return (-1);
84 for (i = 0; props->domain_controller[i].host[0] != '\0'; i++) {
85 if (adutils_add_ds(new,
86 props->domain_controller[i].host,
87 props->domain_controller[i].port) != ADUTILS_SUCCESS) {
88 adutils_ad_free(&new);
89 return (-1);
90 }
91 }
92
93 if (*ad != NULL)
94 adutils_ad_free(ad);
95 *ad = new;
96 return (0);
97 }
98
99 static
100 int
update_dirs(ad_disc_ds_t ** value,ad_disc_ds_t ** new)101 update_dirs(ad_disc_ds_t **value, ad_disc_ds_t **new)
102 {
103 if (*value == *new)
104 return (0);
105
106 if (*value != NULL && *new != NULL &&
107 ad_disc_compare_ds(*value, *new) == 0) {
108 free(*new);
109 *new = NULL;
110 return (0);
111 }
112
113 if (*value)
114 free(*value);
115 *value = *new;
116 *new = NULL;
117 return (1);
118 }
119
120 static
121 int
nssad_cfg_refresh(nssad_cfg_t * cp)122 nssad_cfg_refresh(nssad_cfg_t *cp)
123 {
124 nssad_prop_t props;
125
126 (void) ad_disc_SubnetChanged(cp->ad_ctx);
127 (void) memset(&props, 0, sizeof (props));
128 if (nssad_cfg_discover_props(cp->props.domain_name, cp->ad_ctx,
129 &props) < 0)
130 return (-1);
131 if (update_dirs(&cp->props.domain_controller,
132 &props.domain_controller)) {
133 if (cp->props.domain_controller != NULL &&
134 cp->props.domain_controller[0].host[0] != '\0')
135 (void) nssad_cfg_reload_ad(&cp->props, &cp->ad);
136 }
137 return (0);
138 }
139
140 static void
nssad_cfg_destroy(nssad_cfg_t * cp)141 nssad_cfg_destroy(nssad_cfg_t *cp)
142 {
143 if (cp != NULL) {
144 (void) pthread_rwlock_destroy(&cp->lock);
145 ad_disc_fini(cp->ad_ctx);
146 nssad_cfg_free_props(&cp->props);
147 adutils_ad_free(&cp->ad);
148 free(cp);
149 }
150 }
151
152 static nssad_cfg_t *
nssad_cfg_create(const char * domain)153 nssad_cfg_create(const char *domain)
154 {
155 nssad_cfg_t *cp;
156
157 if ((cp = calloc(1, sizeof (*cp))) == NULL)
158 return (NULL);
159 if (pthread_rwlock_init(&cp->lock, NULL) != 0) {
160 free(cp);
161 return (NULL);
162 }
163 if ((cp->ad_ctx = ad_disc_init()) == NULL)
164 goto errout;
165 if ((cp->props.domain_name = strdup(domain)) == NULL)
166 goto errout;
167 if (nssad_cfg_discover_props(domain, cp->ad_ctx, &cp->props) < 0)
168 goto errout;
169 if (nssad_cfg_reload_ad(&cp->props, &cp->ad) < 0)
170 goto errout;
171 return (cp);
172 errout:
173 nssad_cfg_destroy(cp);
174 return (NULL);
175 }
176
177 #define hex_char(n) "0123456789abcdef"[n & 0xf]
178
179 int
_ldap_filter_name(char * filter_name,const char * name,int filter_name_size)180 _ldap_filter_name(char *filter_name, const char *name, int filter_name_size)
181 {
182 char *end = filter_name + filter_name_size;
183 char c;
184
185 for (; *name; name++) {
186 c = *name;
187 switch (c) {
188 case '*':
189 case '(':
190 case ')':
191 case '\\':
192 if (end <= filter_name + 3)
193 return (-1);
194 *filter_name++ = '\\';
195 *filter_name++ = hex_char(c >> 4);
196 *filter_name++ = hex_char(c & 0xf);
197 break;
198 default:
199 if (end <= filter_name + 1)
200 return (-1);
201 *filter_name++ = c;
202 break;
203 }
204 }
205 if (end <= filter_name)
206 return (-1);
207 *filter_name = '\0';
208 return (0);
209 }
210
211 static
212 nss_status_t
map_adrc2nssrc(adutils_rc adrc)213 map_adrc2nssrc(adutils_rc adrc)
214 {
215 if (adrc == ADUTILS_SUCCESS)
216 return ((nss_status_t)NSS_SUCCESS);
217 if (adrc == ADUTILS_ERR_NOTFOUND)
218 errno = 0;
219 return ((nss_status_t)NSS_NOTFOUND);
220 }
221
222 /* ARGSUSED */
223 nss_status_t
_nss_ad_marshall_data(ad_backend_ptr be,nss_XbyY_args_t * argp)224 _nss_ad_marshall_data(ad_backend_ptr be, nss_XbyY_args_t *argp)
225 {
226 int stat;
227
228 if (argp->buf.result == NULL) {
229 /*
230 * This suggests that the process (e.g. nscd) expects
231 * nssad to return the data in native file format in
232 * argp->buf.buffer i.e. no need to marshall the data.
233 */
234 argp->returnval = argp->buf.buffer;
235 argp->returnlen = strlen(argp->buf.buffer);
236 return ((nss_status_t)NSS_STR_PARSE_SUCCESS);
237 }
238
239 if (argp->str2ent == NULL)
240 return ((nss_status_t)NSS_STR_PARSE_PARSE);
241
242 stat = (*argp->str2ent)(be->buffer, be->buflen,
243 argp->buf.result, argp->buf.buffer, argp->buf.buflen);
244
245 if (stat == NSS_STR_PARSE_SUCCESS) {
246 argp->returnval = argp->buf.result;
247 argp->returnlen = 1; /* irrelevant */
248 }
249 return ((nss_status_t)stat);
250 }
251
252 nss_status_t
_nss_ad_sanitize_status(ad_backend_ptr be,nss_XbyY_args_t * argp,nss_status_t stat)253 _nss_ad_sanitize_status(ad_backend_ptr be, nss_XbyY_args_t *argp,
254 nss_status_t stat)
255 {
256 if (be->buffer != NULL) {
257 free(be->buffer);
258 be->buffer = NULL;
259 be->buflen = 0;
260 be->db_type = NSS_AD_DB_NONE;
261 }
262
263 if (stat == NSS_STR_PARSE_SUCCESS) {
264 return ((nss_status_t)NSS_SUCCESS);
265 } else if (stat == NSS_STR_PARSE_PARSE) {
266 argp->returnval = 0;
267 return ((nss_status_t)NSS_NOTFOUND);
268 } else if (stat == NSS_STR_PARSE_ERANGE) {
269 argp->erange = 1;
270 return ((nss_status_t)NSS_NOTFOUND);
271 }
272 return ((nss_status_t)NSS_UNAVAIL);
273 }
274
275 /* ARGSUSED */
276 static
277 nssad_cfg_t *
get_cfg(const char * domain)278 get_cfg(const char *domain)
279 {
280 nssad_cfg_t *cp, *lru, *prev;
281
282 /*
283 * Note about the queue:
284 *
285 * The queue is used to hold our per domain
286 * configs. The queue is limited to CFG_QUEUE_MAX_SIZE.
287 * If the queue increases beyond that point we toss
288 * out the LRU entry. The entries are inserted into
289 * the queue at state.qtail and the LRU entry is
290 * removed from state.qhead. state.qnext points
291 * from the qtail to the qhead. Everytime a config
292 * is accessed it is moved to qtail.
293 */
294
295 (void) pthread_mutex_lock(&statelock);
296
297 for (cp = state.qtail, prev = NULL; cp != NULL;
298 prev = cp, cp = cp->qnext) {
299 if (cp->props.domain_name == NULL ||
300 strcasecmp(cp->props.domain_name, domain) != 0)
301 continue;
302
303 /* Found config for the given domain. */
304
305 if (state.qtail != cp) {
306 /*
307 * Move the entry to the tail of the queue.
308 * This way the LRU entry can be found at
309 * the head of the queue.
310 */
311 prev->qnext = cp->qnext;
312 if (state.qhead == cp)
313 state.qhead = prev;
314 cp->qnext = state.qtail;
315 state.qtail = cp;
316 }
317
318 if (ad_disc_get_TTL(cp->ad_ctx) == 0) {
319 /*
320 * If there are expired items in the
321 * config, grab the write lock and
322 * refresh the config.
323 */
324 (void) pthread_rwlock_wrlock(&cp->lock);
325 if (nssad_cfg_refresh(cp) < 0) {
326 (void) pthread_rwlock_unlock(&cp->lock);
327 (void) pthread_mutex_unlock(&statelock);
328 return (NULL);
329 }
330 (void) pthread_rwlock_unlock(&cp->lock);
331 }
332
333 /* Return the config found */
334 (void) pthread_rwlock_rdlock(&cp->lock);
335 (void) pthread_mutex_unlock(&statelock);
336 return (cp);
337 }
338
339 /* Create new config entry for the domain */
340 if ((cp = nssad_cfg_create(domain)) == NULL) {
341 (void) pthread_mutex_unlock(&statelock);
342 return (NULL);
343 }
344
345 /* Add it to the queue */
346 state.qcount++;
347 if (state.qtail == NULL) {
348 state.qtail = state.qhead = cp;
349 (void) pthread_rwlock_rdlock(&cp->lock);
350 (void) pthread_mutex_unlock(&statelock);
351 return (cp);
352 }
353 cp->qnext = state.qtail;
354 state.qtail = cp;
355
356 /* If the queue has exceeded its size, remove the LRU entry */
357 if (state.qcount >= CFG_QUEUE_MAX_SIZE) {
358 /* Detach the lru entry and destroy */
359 lru = state.qhead;
360 if (pthread_rwlock_trywrlock(&lru->lock) == 0) {
361 for (prev = state.qtail; prev != NULL;
362 prev = prev->qnext) {
363 if (prev->qnext != lru)
364 continue;
365 state.qhead = prev;
366 prev->qnext = NULL;
367 state.qcount--;
368 (void) pthread_rwlock_unlock(&lru->lock);
369 nssad_cfg_destroy(lru);
370 break;
371 }
372 (void) assert(prev != NULL);
373 }
374 }
375
376 (void) pthread_rwlock_rdlock(&cp->lock);
377 (void) pthread_mutex_unlock(&statelock);
378 return (cp);
379 }
380
381
382 /* ARGSUSED */
383 static
384 nss_status_t
ad_lookup(const char * filter,const char ** attrs,const char * domain,adutils_result_t ** result)385 ad_lookup(const char *filter, const char **attrs,
386 const char *domain, adutils_result_t **result)
387 {
388 int retries = 0;
389 adutils_rc rc, brc;
390 adutils_query_state_t *qs;
391 nssad_cfg_t *cp;
392
393 retry:
394 if ((cp = get_cfg(domain)) == NULL)
395 return ((nss_status_t)NSS_NOTFOUND);
396
397 rc = adutils_lookup_batch_start(cp->ad, 1, NULL, NULL, &qs);
398 (void) pthread_rwlock_unlock(&cp->lock);
399 if (rc != ADUTILS_SUCCESS)
400 goto out;
401
402 rc = adutils_lookup_batch_add(qs, filter, attrs, domain, result, &brc);
403 if (rc != ADUTILS_SUCCESS) {
404 adutils_lookup_batch_release(&qs);
405 goto out;
406 }
407
408 rc = adutils_lookup_batch_end(&qs);
409 if (rc != ADUTILS_SUCCESS)
410 goto out;
411 rc = brc;
412
413 out:
414 if (rc == ADUTILS_ERR_RETRIABLE_NET_ERR &&
415 retries++ < ADUTILS_DEF_NUM_RETRIES)
416 goto retry;
417 return (map_adrc2nssrc(rc));
418 }
419
420
421 /* ARGSUSED */
422 nss_status_t
_nss_ad_lookup(ad_backend_ptr be,nss_XbyY_args_t * argp,const char * database,const char * searchfilter,const char * dname,int * try_idmap)423 _nss_ad_lookup(ad_backend_ptr be, nss_XbyY_args_t *argp,
424 const char *database, const char *searchfilter,
425 const char *dname, int *try_idmap)
426 {
427 nss_status_t stat;
428
429 *try_idmap = 0;
430
431 /* Clear up results if any */
432 (void) adutils_freeresult(&be->result);
433
434 /* Lookup AD */
435 stat = ad_lookup(searchfilter, be->attrs, dname, &be->result);
436 if (stat != NSS_SUCCESS) {
437 argp->returnval = 0;
438 *try_idmap = 1;
439 return (stat);
440 }
441
442 /* Map AD object(s) to string in native file format */
443 stat = be->adobj2str(be, argp);
444 if (stat == NSS_STR_PARSE_SUCCESS)
445 stat = _nss_ad_marshall_data(be, argp);
446 return (_nss_ad_sanitize_status(be, argp, stat));
447 }
448
449 static
450 void
clean_state()451 clean_state()
452 {
453 nssad_cfg_t *cp, *curr;
454
455 (void) pthread_mutex_lock(&statelock);
456 for (cp = state.qtail; cp != NULL; ) {
457 curr = cp;
458 cp = cp->qnext;
459 nssad_cfg_destroy(curr);
460 }
461 (void) memset(&state, 0, sizeof (state));
462 (void) pthread_mutex_unlock(&statelock);
463 }
464
465 static
466 void
_clean_ad_backend(ad_backend_ptr be)467 _clean_ad_backend(ad_backend_ptr be)
468 {
469 if (be->tablename != NULL)
470 free(be->tablename);
471 if (be->buffer != NULL) {
472 free(be->buffer);
473 be->buffer = NULL;
474 }
475 free(be);
476 }
477
478
479 /*
480 * _nss_ad_destr frees allocated memory before exiting this nsswitch shared
481 * backend library. This function is called before returning control back to
482 * nsswitch.
483 */
484 /*ARGSUSED*/
485 nss_status_t
_nss_ad_destr(ad_backend_ptr be,void * a)486 _nss_ad_destr(ad_backend_ptr be, void *a)
487 {
488 (void) _clean_ad_backend(be);
489 clean_state();
490 return ((nss_status_t)NSS_SUCCESS);
491 }
492
493
494 /*ARGSUSED*/
495 nss_status_t
_nss_ad_setent(ad_backend_ptr be,void * a)496 _nss_ad_setent(ad_backend_ptr be, void *a)
497 {
498 return ((nss_status_t)NSS_UNAVAIL);
499 }
500
501
502 /*ARGSUSED*/
503 nss_status_t
_nss_ad_endent(ad_backend_ptr be,void * a)504 _nss_ad_endent(ad_backend_ptr be, void *a)
505 {
506 return ((nss_status_t)NSS_UNAVAIL);
507 }
508
509
510 /*ARGSUSED*/
511 nss_status_t
_nss_ad_getent(ad_backend_ptr be,void * a)512 _nss_ad_getent(ad_backend_ptr be, void *a)
513 {
514 return ((nss_status_t)NSS_UNAVAIL);
515 }
516
517
518 nss_backend_t *
_nss_ad_constr(ad_backend_op_t ops[],int nops,char * tablename,const char ** attrs,fnf adobj2str)519 _nss_ad_constr(ad_backend_op_t ops[], int nops, char *tablename,
520 const char **attrs, fnf adobj2str)
521 {
522 ad_backend_ptr be;
523
524 if ((be = (ad_backend_ptr) calloc(1, sizeof (*be))) == NULL)
525 return (NULL);
526 if ((be->tablename = (char *)strdup(tablename)) == NULL) {
527 free(be);
528 return (NULL);
529 }
530 be->ops = ops;
531 be->nops = (nss_dbop_t)nops;
532 be->attrs = attrs;
533 be->adobj2str = adobj2str;
534 (void) memset(&state, 0, sizeof (state));
535 return ((nss_backend_t *)be);
536 }
537