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