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, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22
23/*
24 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 * Copyright (c) 2016 by Delphix. All rights reserved.
27 */
28
29/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
30/*	  All Rights Reserved   */
31
32/*
33 * Portions of this source code were derived from Berkeley
34 * under license from the Regents of the University of
35 * California.
36 */
37
38#pragma ident	"%Z%%M%	%I%	%E% SMI"
39
40#include "mt.h"
41#include <stdlib.h>
42#include <unistd.h>
43#include "../rpc/rpc_mt.h"
44#include <rpc/rpc.h>
45#include <sys/types.h>
46#include "yp_b.h"
47#include <rpcsvc/yp_prot.h>
48#include <rpcsvc/ypclnt.h>
49#include <malloc.h>
50#include <string.h>
51#include <sys/time.h>
52
53extern int __yp_dobind_cflookup(char *, struct dom_binding **, int);
54extern int __yp_dobind_rsvdport_cflookup(char *, struct dom_binding **, int);
55
56static int domatch(char *, char *, char *, int, struct dom_binding *,
57    struct timeval *, char **, int *);
58int yp_match_rsvdport();
59int yp_match_rsvdport_cflookup();
60
61struct cache {
62	struct cache *next;
63	unsigned int birth;
64	char *domain;
65	char *map;
66	char *key;
67	int  keylen;
68	char *val;
69	int  vallen;
70};
71
72static mutex_t		cache_lock = DEFAULTMUTEX;
73static int		generation;	/* Incremented when we add to cache */
74static struct cache	*head;
75
76#define	CACHESZ 16
77#define	CACHETO 600
78
79static void
80freenode(struct cache *n)
81{
82	if (n->val != 0)
83		free(n->val);
84	if (n->key != 0)
85		free(n->key);
86	if (n->map != 0)
87		free(n->map);
88	if (n->domain != 0)
89		free(n->domain);
90	free(n);
91}
92
93/*
94 * Attempt to Add item to cache
95 */
96static struct cache *
97makenode(char *domain, char *map, int keylen, int vallen)
98{
99	struct cache *n;
100
101	/* Do not cache 'passwd' values i.e. passwd.byname or passwd.byuid. */
102	if (strncmp(map, "passwd", 6) == 0)
103		return (0);
104
105	if ((n = calloc(1, sizeof (*n))) == 0)
106		return (0);
107	if (((n->domain = strdup(domain)) == 0) ||
108	    ((n->map = strdup(map)) == 0) ||
109	    ((n->key = malloc(keylen)) == 0) ||
110	    ((n->val = malloc(vallen)) == 0)) {
111		freenode(n);
112		return (0);
113	}
114	return (n);
115}
116
117/*
118 * Look for a matching result in the per-process cache.
119 * Upon finding a match set the passed in 'val' and 'vallen'
120 * parameters and return 1.  Otherwise return 0.
121 */
122static int
123in_cache(char *domain, char *map, char *key, int keylen, char **val,
124								int *vallen)
125{
126	struct cache *c, **pp;
127	int cnt;
128	struct timeval now;
129	struct timezone tz;
130
131	/* The 'passwd' data is not cached. */
132	if (strncmp(map, "passwd", 6) == 0)
133		return (0);
134
135	/*
136	 * Assumes that caller (yp_match) has locked the cache
137	 */
138	for (pp = &head, cnt = 0;  (c = *pp) != 0;  pp = &c->next, cnt++) {
139		if ((c->keylen == keylen) &&
140		    (memcmp(key, c->key, (size_t)keylen) == 0) &&
141		    (strcmp(map, c->map) == 0) &&
142		    (strcmp(domain, c->domain) == 0)) {
143			/* cache hit */
144			(void) gettimeofday(&now, &tz);
145			if ((now.tv_sec - c->birth) > CACHETO) {
146				/* rats.  it is too old to use */
147				*pp = c->next;
148				freenode(c);
149				break;
150			} else {
151				*val = c->val;
152				*vallen = c->vallen;
153
154				/* Ersatz LRU:  Move this entry to the front */
155				*pp = c->next;
156				c->next = head;
157				head = c;
158				return (1);
159			}
160		}
161		if (cnt >= CACHESZ) {
162			*pp = c->next;
163			freenode(c);
164			break;
165		}
166	}
167	return (0);
168}
169
170/*
171 * Requests the yp server associated with a given domain to attempt to match
172 * the passed key datum in the named map, and to return the associated value
173 * datum. This part does parameter checking, and implements the "infinite"
174 * (until success) sleep loop if 'hardlookup' parameter is set.
175 */
176int
177__yp_match_cflookup(char *domain, char *map, char *key, int keylen, char **val,
178						int *vallen, int hardlookup)
179{
180	size_t domlen;
181	size_t maplen;
182	int reason;
183	struct dom_binding *pdomb;
184	int savesize;
185	struct timeval now;
186	struct timezone tz;
187	char *my_val;
188	int  my_vallen;
189	int  found_it;
190	int  cachegen;
191
192	if ((map == NULL) || (domain == NULL))
193		return (YPERR_BADARGS);
194
195	domlen = strlen(domain);
196	maplen = strlen(map);
197
198	if ((domlen == 0) || (domlen > YPMAXDOMAIN) ||
199	    (maplen == 0) || (maplen > YPMAXMAP) ||
200	    (key == NULL) || (keylen == 0))
201		return (YPERR_BADARGS);
202
203	(void) mutex_lock(&cache_lock);
204	found_it = in_cache(domain, map, key, keylen, &my_val, &my_vallen);
205	cachegen = generation;
206
207	if (found_it) {
208		/* NB: Copy two extra bytes; see below */
209		savesize = my_vallen + 2;
210		if ((*val = malloc((size_t)savesize)) == 0) {
211			(void) mutex_unlock(&cache_lock);
212			return (YPERR_RESRC);
213		}
214		(void) memcpy(*val, my_val, (size_t)savesize);
215		*vallen = my_vallen;
216		(void) mutex_unlock(&cache_lock);
217		return (0);	/* Success */
218	}
219	(void) mutex_unlock(&cache_lock);
220
221	for (;;) {
222
223		if (reason = __yp_dobind_cflookup(domain, &pdomb, hardlookup))
224			return (reason);
225
226		if (pdomb->dom_binding->ypbind_hi_vers >= YPVERS) {
227
228			reason = domatch(domain, map, key, keylen, pdomb,
229			    &_ypserv_timeout, val, vallen);
230
231			__yp_rel_binding(pdomb);
232			if (reason == YPERR_RPC || reason == YPERR_YPSERV ||
233			    reason == YPERR_BUSY /* as if */) {
234				yp_unbind(domain);
235				if (hardlookup)
236					(void) sleep(_ypsleeptime); /* retry */
237				else
238					return (reason);
239			} else
240				break;
241		} else {
242			__yp_rel_binding(pdomb);
243			return (YPERR_VERS);
244		}
245	}
246
247	/* add to our cache */
248	if (reason == 0) {
249		(void) mutex_lock(&cache_lock);
250		/*
251		 * Check whether some other annoying thread did the same
252		 * thing in parallel with us.  I hate it when that happens...
253		 */
254		if (generation != cachegen &&
255		    in_cache(domain, map, key, keylen, &my_val, &my_vallen)) {
256			/*
257			 * Could get cute and update the birth time, but it's
258			 *   not worth the bother.
259			 * It looks strange that we return one val[] array
260			 *   to the caller and have a different copy of the
261			 *   val[] array in the cache (presumably with the
262			 *   same contents), but it should work just fine.
263			 * So, do absolutely nothing...
264			 */
265			/* EMPTY */
266		} else {
267			struct cache	*c;
268			/*
269			 * NB: allocate and copy extract two bytes of the
270			 * value;  these are mandatory CR and NULL bytes.
271			 */
272			savesize = *vallen + 2;
273			c = makenode(domain, map, keylen, savesize);
274			if (c != 0) {
275				(void) gettimeofday(&now, &tz);
276				c->birth = now.tv_sec;
277				c->keylen = keylen;
278				c->vallen = *vallen;
279				(void) memcpy(c->key, key, (size_t)keylen);
280				(void) memcpy(c->val, *val, (size_t)savesize);
281
282				c->next = head;
283				head = c;
284				++generation;
285			}
286		}
287		(void) mutex_unlock(&cache_lock);
288	} else if (reason == YPERR_MAP && geteuid() == 0) {
289		/*
290		 * Lookup could be for a secure map; fail over to retry
291		 * from a reserved port. Only useful to try this if we're
292		 * the super user.
293		 */
294		int rsvdreason;
295		rsvdreason = yp_match_rsvdport(domain, map, key, keylen, val,
296						vallen);
297		if (rsvdreason == 0)
298			reason = rsvdreason;
299	}
300	return (reason);
301}
302
303int
304yp_match(
305	char *domain,
306	char *map,
307	char *key,
308	int  keylen,
309	char **val,		/* returns value array */
310	int  *vallen)		/* returns bytes in val */
311
312{
313	/* the traditional yp_match loops forever thus hardlookup is set */
314	return (__yp_match_cflookup(domain, map, key, keylen, val, vallen, 1));
315}
316
317extern void
318__empty_yp_cache(void)
319{
320	struct cache *p, *n;
321
322	/* Copy the cache pointer and make it ZERO */
323	(void) mutex_lock(&cache_lock);
324	p = head;
325	head = 0;
326	(void) mutex_unlock(&cache_lock);
327
328	if (p == 0)
329		return;
330
331	/* Empty the cache */
332	n = p->next;
333	while (p) {
334		freenode(p);
335		p = n;
336		if (p)
337			n = p->next;
338	}
339}
340
341/*
342 * Requests the yp server associated with a given domain to attempt to match
343 * the passed key datum in the named map, and to return the associated value
344 * datum. This part does parameter checking, and implements the "infinite"
345 * (until success) sleep loop.
346 *
347 * XXX special version for handling C2 (passwd.adjunct) lookups when we need
348 * a reserved port.
349 * Only difference against yp_match is that this function uses
350 * __yp_dobind_rsvdport().
351 *
352 * Only called from NIS switch backend.
353 */
354int
355__yp_match_rsvdport_cflookup(
356	char *domain,
357	char *map,
358	char *key,
359	int  keylen,
360	char **val,		/* returns value array */
361	int  *vallen,		/* returns bytes in val */
362	int  hardlookup)	/* retry until we can an answer */
363{
364	size_t domlen;
365	size_t maplen;
366	int reason;
367	struct dom_binding *pdomb;
368	int savesize;
369	struct timeval now;
370	struct timezone tz;
371	char *my_val;
372	int  my_vallen;
373	int  found_it;
374	int  cachegen;
375
376	if ((map == NULL) || (domain == NULL))
377		return (YPERR_BADARGS);
378
379	domlen = strlen(domain);
380	maplen = strlen(map);
381
382	if ((domlen == 0) || (domlen > YPMAXDOMAIN) ||
383	    (maplen == 0) || (maplen > YPMAXMAP) ||
384	    (key == NULL) || (keylen == 0))
385		return (YPERR_BADARGS);
386
387	(void) mutex_lock(&cache_lock);
388	found_it = in_cache(domain, map, key, keylen, &my_val, &my_vallen);
389	cachegen = generation;
390	if (found_it) {
391		/* NB: Copy two extra bytes; see below */
392		savesize = my_vallen + 2;
393		if ((*val = malloc((size_t)savesize)) == 0) {
394			(void) mutex_unlock(&cache_lock);
395			return (YPERR_RESRC);
396		}
397		(void) memcpy(*val, my_val, (size_t)savesize);
398		*vallen = my_vallen;
399		(void) mutex_unlock(&cache_lock);
400		return (0);	/* Success */
401	}
402	(void) mutex_unlock(&cache_lock);
403
404	for (;;) {
405
406		if (reason = __yp_dobind_rsvdport_cflookup(domain, &pdomb,
407							hardlookup))
408			return (reason);
409
410		if (pdomb->dom_binding->ypbind_hi_vers >= YPVERS) {
411
412			reason = domatch(domain, map, key, keylen,
413				pdomb, &_ypserv_timeout, val, vallen);
414
415			/*
416			 * Have to free the binding since the reserved
417			 * port bindings are not cached.
418			 */
419			__yp_rel_binding(pdomb);
420			free_dom_binding(pdomb);
421
422			if (reason == YPERR_RPC || reason == YPERR_YPSERV ||
423			    reason == YPERR_BUSY /* as if */) {
424				yp_unbind(domain);
425				if (hardlookup)
426					(void) sleep(_ypsleeptime); /* retry */
427				else
428					return (reason);
429			} else
430				break;
431		} else {
432			/*
433			 * Have to free the binding since the reserved
434			 * port bindings are not cached.
435			 */
436			__yp_rel_binding(pdomb);
437			free_dom_binding(pdomb);
438			return (YPERR_VERS);
439		}
440	}
441
442	/* add to our cache */
443	if (reason == 0) {
444		(void) mutex_lock(&cache_lock);
445		/*
446		 * Check whether some other annoying thread did the same
447		 * thing in parallel with us.  I hate it when that happens...
448		 */
449		if (generation != cachegen &&
450		    in_cache(domain, map, key, keylen, &my_val, &my_vallen)) {
451			/*
452			 * Could get cute and update the birth time, but it's
453			 *   not worth the bother.
454			 * It looks strange that we return one val[] array
455			 *   to the caller and have a different copy of the
456			 *   val[] array in the cache (presumably with the
457			 *   same contents), but it should work just fine.
458			 * So, do absolutely nothing...
459			 */
460			/* EMPTY */
461		} else {
462			struct cache	*c;
463			/*
464			 * NB: allocate and copy extract two bytes of the
465			 * value;  these are mandatory CR and NULL bytes.
466			 */
467			savesize = *vallen + 2;
468			c = makenode(domain, map, keylen, savesize);
469			if (c != 0) {
470				(void) gettimeofday(&now, &tz);
471				c->birth = now.tv_sec;
472				c->keylen = keylen;
473				c->vallen = *vallen;
474				(void) memcpy(c->key, key, (size_t)keylen);
475				(void) memcpy(c->val, *val, (size_t)savesize);
476
477				c->next = head;
478				head = c;
479				++generation;
480			}
481		}
482		(void) mutex_unlock(&cache_lock);
483	}
484	return (reason);
485}
486
487
488int
489yp_match_rsvdport(
490	char *domain,
491	char *map,
492	char *key,
493	int  keylen,
494	char **val,		/* returns value array */
495	int  *vallen)		/* returns bytes in val */
496{
497	/* traditional yp_match retries forever so set hardlookup */
498	return (__yp_match_rsvdport_cflookup(domain, map, key, keylen, val,
499					vallen, 1));
500}
501
502
503/*
504 * This talks v3 protocol to ypserv
505 */
506static int
507domatch(char *domain, char *map, char *key, int  keylen,
508    struct dom_binding *pdomb, struct timeval *timeoutp, char **val,
509    int  *vallen)
510{
511	struct ypreq_key req;
512	struct ypresp_val resp;
513	unsigned int retval = 0;
514
515	req.domain = domain;
516	req.map = map;
517	req.keydat.dptr = key;
518	req.keydat.dsize = keylen;
519
520	resp.valdat.dptr = NULL;
521	resp.valdat.dsize = 0;
522	(void) memset((char *)&resp, 0, sizeof (struct ypresp_val));
523
524	/*
525	 * Do the match request.  If the rpc call failed, return with status
526	 * from this point.
527	 */
528
529	switch (clnt_call(pdomb->dom_client, YPPROC_MATCH,
530			(xdrproc_t)xdr_ypreq_key, (char *)&req,
531			(xdrproc_t)xdr_ypresp_val, (char *)&resp,
532			*timeoutp)) {
533	case RPC_SUCCESS:
534		break;
535	case RPC_TIMEDOUT:
536		return (YPERR_YPSERV);
537	default:
538		return (YPERR_RPC);
539	}
540
541	/* See if the request succeeded */
542
543	if (resp.status != YP_TRUE) {
544		retval = ypprot_err(resp.status);
545	}
546
547	/* Get some memory which the user can get rid of as they likes */
548
549	if (!retval && ((*val = malloc((size_t)
550	    resp.valdat.dsize + 2)) == NULL)) {
551		retval = YPERR_RESRC;
552	}
553
554	/* Copy the returned value byte string into the new memory */
555
556	if (!retval) {
557		*vallen = (int)resp.valdat.dsize;
558		(void) memcpy(*val, resp.valdat.dptr,
559		    (size_t)resp.valdat.dsize);
560		(*val)[resp.valdat.dsize] = '\n';
561		(*val)[resp.valdat.dsize + 1] = '\0';
562	}
563
564	CLNT_FREERES(pdomb->dom_client,
565		(xdrproc_t)xdr_ypresp_val, (char *)&resp);
566	return (retval);
567}
568