xref: /illumos-gate/usr/src/lib/nsswitch/ad/common/ad_common.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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
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
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
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
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
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
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 *
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
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
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
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
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 *
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
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
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
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
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
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
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
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
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 *
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