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
42static pthread_mutex_t	statelock = PTHREAD_MUTEX_INITIALIZER;
43static nssad_state_t	state = {0};
44
45static void
46nssad_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
58static int
59nssad_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
72static int
73nssad_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
99static
100int
101update_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
120static
121int
122nssad_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
140static void
141nssad_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
152static nssad_cfg_t *
153nssad_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);
172errout:
173	nssad_cfg_destroy(cp);
174	return (NULL);
175}
176
177#define	hex_char(n)	"0123456789abcdef"[n & 0xf]
178
179int
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
211static
212nss_status_t
213map_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 */
223nss_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
252nss_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 */
276static
277nssad_cfg_t *
278get_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 */
383static
384nss_status_t
385ad_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
393retry:
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
413out:
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 */
422nss_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
449static
450void
451clean_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
465static
466void
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*/
485nss_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*/
495nss_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*/
503nss_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*/
511nss_status_t
512_nss_ad_getent(ad_backend_ptr be, void *a)
513{
514	return ((nss_status_t)NSS_UNAVAIL);
515}
516
517
518nss_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