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 2008 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 	adutils_set_log(-1, TRUE, FALSE);
162 	if ((cp->ad_ctx = ad_disc_init()) == NULL)
163 		goto errout;
164 	if ((cp->props.domain_name = strdup(domain)) == NULL)
165 		goto errout;
166 	if (nssad_cfg_discover_props(domain, cp->ad_ctx, &cp->props) < 0)
167 		goto errout;
168 	if (nssad_cfg_reload_ad(&cp->props, &cp->ad) < 0)
169 		goto errout;
170 	return (cp);
171 errout:
172 	nssad_cfg_destroy(cp);
173 	return (NULL);
174 }
175 
176 #define	hex_char(n)	"0123456789abcdef"[n & 0xf]
177 
178 int
179 _ldap_filter_name(char *filter_name, const char *name, int filter_name_size)
180 {
181 	char *end = filter_name + filter_name_size;
182 	char c;
183 
184 	for (; *name; name++) {
185 		c = *name;
186 		switch (c) {
187 			case '*':
188 			case '(':
189 			case ')':
190 			case '\\':
191 				if (end <= filter_name + 3)
192 					return (-1);
193 				*filter_name++ = '\\';
194 				*filter_name++ = hex_char(c >> 4);
195 				*filter_name++ = hex_char(c & 0xf);
196 				break;
197 			default:
198 				if (end <= filter_name + 1)
199 					return (-1);
200 				*filter_name++ = c;
201 				break;
202 		}
203 	}
204 	if (end <= filter_name)
205 		return (-1);
206 	*filter_name = '\0';
207 	return (0);
208 }
209 
210 static
211 nss_status_t
212 map_adrc2nssrc(adutils_rc adrc)
213 {
214 	if (adrc == ADUTILS_SUCCESS)
215 		return ((nss_status_t)NSS_SUCCESS);
216 	if (adrc == ADUTILS_ERR_NOTFOUND)
217 		errno = 0;
218 	return ((nss_status_t)NSS_NOTFOUND);
219 }
220 
221 /* ARGSUSED */
222 nss_status_t
223 _nss_ad_marshall_data(ad_backend_ptr be, nss_XbyY_args_t *argp)
224 {
225 	int	stat;
226 
227 	if (argp->buf.result == NULL) {
228 		/*
229 		 * This suggests that the process (e.g. nscd) expects
230 		 * nssad to return the data in native file format in
231 		 * argp->buf.buffer i.e. no need to marshall the data.
232 		 */
233 		argp->returnval = argp->buf.buffer;
234 		argp->returnlen = strlen(argp->buf.buffer);
235 		return ((nss_status_t)NSS_STR_PARSE_SUCCESS);
236 	}
237 
238 	if (argp->str2ent == NULL)
239 		return ((nss_status_t)NSS_STR_PARSE_PARSE);
240 
241 	stat = (*argp->str2ent)(be->buffer, be->buflen,
242 	    argp->buf.result, argp->buf.buffer, argp->buf.buflen);
243 
244 	if (stat == NSS_STR_PARSE_SUCCESS) {
245 		argp->returnval = argp->buf.result;
246 		argp->returnlen = 1; /* irrelevant */
247 	}
248 	return ((nss_status_t)stat);
249 }
250 
251 nss_status_t
252 _nss_ad_sanitize_status(ad_backend_ptr be, nss_XbyY_args_t *argp,
253 		nss_status_t stat)
254 {
255 	if (be->buffer != NULL) {
256 		free(be->buffer);
257 		be->buffer = NULL;
258 		be->buflen = 0;
259 		be->db_type = NSS_AD_DB_NONE;
260 	}
261 
262 	if (stat == NSS_STR_PARSE_SUCCESS) {
263 		return ((nss_status_t)NSS_SUCCESS);
264 	} else if (stat == NSS_STR_PARSE_PARSE) {
265 		argp->returnval = 0;
266 		return ((nss_status_t)NSS_NOTFOUND);
267 	} else if (stat == NSS_STR_PARSE_ERANGE) {
268 		argp->erange = 1;
269 		return ((nss_status_t)NSS_NOTFOUND);
270 	}
271 	return ((nss_status_t)NSS_UNAVAIL);
272 }
273 
274 /* ARGSUSED */
275 static
276 nssad_cfg_t *
277 get_cfg(const char *domain)
278 {
279 	nssad_cfg_t	*cp, *lru, *prev;
280 
281 	/*
282 	 * Note about the queue:
283 	 *
284 	 * The queue is used to hold our per domain
285 	 * configs. The queue is limited to CFG_QUEUE_MAX_SIZE.
286 	 * If the queue increases beyond that point we toss
287 	 * out the LRU entry. The entries are inserted into
288 	 * the queue at state.qtail and the LRU entry is
289 	 * removed from state.qhead. state.qnext points
290 	 * from the qtail to the qhead. Everytime a config
291 	 * is accessed it is moved to qtail.
292 	 */
293 
294 	(void) pthread_mutex_lock(&statelock);
295 
296 	for (cp = state.qtail, prev = NULL; cp != NULL;
297 	    prev = cp, cp = cp->qnext) {
298 		if (cp->props.domain_name == NULL ||
299 		    strcasecmp(cp->props.domain_name, domain) != 0)
300 			continue;
301 
302 		/* Found config for the given domain. */
303 
304 		if (state.qtail != cp) {
305 			/*
306 			 * Move the entry to the tail of the queue.
307 			 * This way the LRU entry can be found at
308 			 * the head of the queue.
309 			 */
310 			prev->qnext = cp->qnext;
311 			if (state.qhead == cp)
312 				state.qhead = prev;
313 			cp->qnext = state.qtail;
314 			state.qtail = cp;
315 		}
316 
317 		if (ad_disc_get_TTL(cp->ad_ctx) == 0) {
318 			/*
319 			 * If there are expired items in the
320 			 * config, grab the write lock and
321 			 * refresh the config.
322 			 */
323 			(void) pthread_rwlock_wrlock(&cp->lock);
324 			if (nssad_cfg_refresh(cp) < 0) {
325 				(void) pthread_rwlock_unlock(&cp->lock);
326 				(void) pthread_mutex_unlock(&statelock);
327 				return (NULL);
328 			}
329 			(void) pthread_rwlock_unlock(&cp->lock);
330 		}
331 
332 		/* Return the config found */
333 		(void) pthread_rwlock_rdlock(&cp->lock);
334 		(void) pthread_mutex_unlock(&statelock);
335 		return (cp);
336 	}
337 
338 	/* Create new config entry for the domain */
339 	if ((cp = nssad_cfg_create(domain)) == NULL) {
340 		(void) pthread_mutex_unlock(&statelock);
341 		return (NULL);
342 	}
343 
344 	/* Add it to the queue */
345 	state.qcount++;
346 	if (state.qtail == NULL) {
347 		state.qtail = state.qhead = cp;
348 		(void) pthread_rwlock_rdlock(&cp->lock);
349 		(void) pthread_mutex_unlock(&statelock);
350 		return (cp);
351 	}
352 	cp->qnext = state.qtail;
353 	state.qtail = cp;
354 
355 	/* If the queue has exceeded its size, remove the LRU entry */
356 	if (state.qcount >= CFG_QUEUE_MAX_SIZE) {
357 		/* Detach the lru entry and destroy */
358 		lru = state.qhead;
359 		if (pthread_rwlock_trywrlock(&lru->lock) == 0) {
360 			for (prev = state.qtail; prev != NULL;
361 			    prev = prev->qnext) {
362 				if (prev->qnext != lru)
363 					continue;
364 				state.qhead = prev;
365 				prev->qnext = NULL;
366 				state.qcount--;
367 				(void) pthread_rwlock_unlock(&lru->lock);
368 				nssad_cfg_destroy(lru);
369 				break;
370 			}
371 			(void) assert(prev != NULL);
372 		}
373 	}
374 
375 	(void) pthread_rwlock_rdlock(&cp->lock);
376 	(void) pthread_mutex_unlock(&statelock);
377 	return (cp);
378 }
379 
380 
381 /* ARGSUSED */
382 static
383 nss_status_t
384 ad_lookup(const char *filter, const char **attrs,
385 	const char *domain, adutils_result_t **result)
386 {
387 	int			retries = 0;
388 	adutils_rc		rc, brc;
389 	adutils_query_state_t	*qs;
390 	nssad_cfg_t		*cp;
391 
392 retry:
393 	if ((cp = get_cfg(domain)) == NULL)
394 		return ((nss_status_t)NSS_NOTFOUND);
395 
396 	rc = adutils_lookup_batch_start(cp->ad, 1, NULL, NULL, &qs);
397 	(void) pthread_rwlock_unlock(&cp->lock);
398 	if (rc != ADUTILS_SUCCESS)
399 		goto out;
400 
401 	rc = adutils_lookup_batch_add(qs, filter, attrs, domain, result, &brc);
402 	if (rc != ADUTILS_SUCCESS) {
403 		adutils_lookup_batch_release(&qs);
404 		goto out;
405 	}
406 
407 	rc = adutils_lookup_batch_end(&qs);
408 	if (rc != ADUTILS_SUCCESS)
409 		goto out;
410 	rc = brc;
411 
412 out:
413 	if (rc == ADUTILS_ERR_RETRIABLE_NET_ERR &&
414 	    retries++ < ADUTILS_DEF_NUM_RETRIES)
415 		goto retry;
416 	return (map_adrc2nssrc(rc));
417 }
418 
419 
420 /* ARGSUSED */
421 nss_status_t
422 _nss_ad_lookup(ad_backend_ptr be, nss_XbyY_args_t *argp,
423 		const char *database, const char *searchfilter,
424 		const char *dname, int *try_idmap)
425 {
426 	nss_status_t	stat;
427 
428 	*try_idmap = 0;
429 
430 	/* Clear up results if any */
431 	(void) adutils_freeresult(&be->result);
432 
433 	/* Lookup AD */
434 	stat = ad_lookup(searchfilter, be->attrs, dname, &be->result);
435 	if (stat != NSS_SUCCESS) {
436 		argp->returnval = 0;
437 		*try_idmap = 1;
438 		return (stat);
439 	}
440 
441 	/* Map AD object(s) to string in native file format */
442 	stat = be->adobj2str(be, argp);
443 	if (stat == NSS_STR_PARSE_SUCCESS)
444 		stat = _nss_ad_marshall_data(be, argp);
445 	return (_nss_ad_sanitize_status(be, argp, stat));
446 }
447 
448 static
449 void
450 clean_state()
451 {
452 	nssad_cfg_t	*cp, *curr;
453 
454 	(void) pthread_mutex_lock(&statelock);
455 	for (cp = state.qtail; cp != NULL; ) {
456 		curr = cp;
457 		cp = cp->qnext;
458 		nssad_cfg_destroy(curr);
459 	}
460 	(void) memset(&state, 0, sizeof (state));
461 	(void) pthread_mutex_unlock(&statelock);
462 }
463 
464 static
465 void
466 _clean_ad_backend(ad_backend_ptr be)
467 {
468 	if (be->tablename != NULL)
469 		free(be->tablename);
470 	if (be->buffer != NULL) {
471 		free(be->buffer);
472 		be->buffer = NULL;
473 	}
474 	free(be);
475 }
476 
477 
478 /*
479  * _nss_ad_destr frees allocated memory before exiting this nsswitch shared
480  * backend library. This function is called before returning control back to
481  * nsswitch.
482  */
483 /*ARGSUSED*/
484 nss_status_t
485 _nss_ad_destr(ad_backend_ptr be, void *a)
486 {
487 	(void) _clean_ad_backend(be);
488 	clean_state();
489 	return ((nss_status_t)NSS_SUCCESS);
490 }
491 
492 
493 /*ARGSUSED*/
494 nss_status_t
495 _nss_ad_setent(ad_backend_ptr be, void *a)
496 {
497 	return ((nss_status_t)NSS_UNAVAIL);
498 }
499 
500 
501 /*ARGSUSED*/
502 nss_status_t
503 _nss_ad_endent(ad_backend_ptr be, void *a)
504 {
505 	return ((nss_status_t)NSS_UNAVAIL);
506 }
507 
508 
509 /*ARGSUSED*/
510 nss_status_t
511 _nss_ad_getent(ad_backend_ptr be, void *a)
512 {
513 	return ((nss_status_t)NSS_UNAVAIL);
514 }
515 
516 
517 nss_backend_t *
518 _nss_ad_constr(ad_backend_op_t ops[], int nops, char *tablename,
519 		const char **attrs, fnf adobj2str)
520 {
521 	ad_backend_ptr	be;
522 
523 	if ((be = (ad_backend_ptr) calloc(1, sizeof (*be))) == NULL)
524 		return (NULL);
525 	if ((be->tablename = (char *)strdup(tablename)) == NULL) {
526 		free(be);
527 		return (NULL);
528 	}
529 	be->ops = ops;
530 	be->nops = (nss_dbop_t)nops;
531 	be->attrs = attrs;
532 	be->adobj2str = adobj2str;
533 	(void) memset(&state, 0, sizeof (state));
534 	return ((nss_backend_t *)be);
535 }
536