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