1/*
2 * Copyright (c) 1995
3 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by Bill Paul.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <db.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <limits.h>
40#include <paths.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <unistd.h>
45#include <sys/stat.h>
46#include <sys/param.h>
47#include <rpcsvc/yp.h>
48#include "yp_extern.h"
49
50int ypdb_debug = 0;
51enum ypstat yp_errno = YP_TRUE;
52
53#define PERM_SECURE (S_IRUSR|S_IWUSR)
54HASHINFO openinfo = {
55	4096,		/* bsize */
56	32,		/* ffactor */
57	256,		/* nelem */
58	2048 * 512, 	/* cachesize */
59	NULL,		/* hash */
60	0,		/* lorder */
61};
62
63#ifdef DB_CACHE
64#include <sys/queue.h>
65
66#ifndef MAXDBS
67#define MAXDBS 20
68#endif
69
70static int numdbs = 0;
71
72struct dbent {
73	DB *dbp;
74	char *name;
75	char *key;
76	int size;
77	int flags;
78};
79
80static TAILQ_HEAD(circlehead, circleq_entry) qhead;
81
82struct circleq_entry {
83	struct dbent *dbptr;
84	TAILQ_ENTRY(circleq_entry) links;
85};
86
87/*
88 * Initialize the circular queue.
89 */
90void
91yp_init_dbs(void)
92{
93	TAILQ_INIT(&qhead);
94	return;
95}
96
97/*
98 * Dynamically allocate an entry for the circular queue.
99 * Return a NULL pointer on failure.
100 */
101static struct circleq_entry *
102yp_malloc_qent(void)
103{
104	register struct circleq_entry *q;
105
106	q = (struct circleq_entry *)malloc(sizeof(struct circleq_entry));
107	if (q == NULL) {
108		yp_error("failed to malloc() circleq entry");
109		return(NULL);
110	}
111	bzero((char *)q, sizeof(struct circleq_entry));
112	q->dbptr = (struct dbent *)malloc(sizeof(struct dbent));
113	if (q->dbptr == NULL) {
114		yp_error("failed to malloc() circleq entry");
115		free(q);
116		return(NULL);
117	}
118	bzero((char *)q->dbptr, sizeof(struct dbent));
119
120	return(q);
121}
122
123/*
124 * Free a previously allocated circular queue
125 * entry.
126 */
127static void
128yp_free_qent(struct circleq_entry *q)
129{
130	/*
131	 * First, close the database. In theory, this is also
132	 * supposed to free the resources allocated by the DB
133	 * package, including the memory pointed to by q->dbptr->key.
134	 * This means we don't have to free q->dbptr->key here.
135	 */
136	if (q->dbptr->dbp) {
137		(void)(q->dbptr->dbp->close)(q->dbptr->dbp);
138		q->dbptr->dbp = NULL;
139	}
140	/*
141	 * Then free the database name, which was strdup()'ed.
142	 */
143	free(q->dbptr->name);
144
145	/*
146	 * Free the rest of the dbent struct.
147	 */
148	free(q->dbptr);
149	q->dbptr = NULL;
150
151	/*
152	 * Free the circleq struct.
153	 */
154	free(q);
155	q = NULL;
156
157	return;
158}
159
160/*
161 * Zorch a single entry in the dbent queue and release
162 * all its resources. (This always removes the last entry
163 * in the queue.)
164 */
165static void
166yp_flush(void)
167{
168	register struct circleq_entry *qptr;
169
170	qptr = TAILQ_LAST(&qhead, circlehead);
171	TAILQ_REMOVE(&qhead, qptr, links);
172	yp_free_qent(qptr);
173	numdbs--;
174
175	return;
176}
177
178/*
179 * Close all databases, erase all database names and empty the queue.
180 */
181void
182yp_flush_all(void)
183{
184	register struct circleq_entry *qptr;
185
186	while (!TAILQ_EMPTY(&qhead)) {
187		qptr = TAILQ_FIRST(&qhead); /* save this */
188		TAILQ_REMOVE(&qhead, qptr, links);
189		yp_free_qent(qptr);
190	}
191	numdbs = 0;
192
193	return;
194}
195
196static char *inter_string = "YP_INTERDOMAIN";
197static char *secure_string = "YP_SECURE";
198static int inter_sz = sizeof("YP_INTERDOMAIN") - 1;
199static int secure_sz = sizeof("YP_SECURE") - 1;
200
201static int
202yp_setflags(DB *dbp)
203{
204	DBT key = { NULL, 0 }, data = { NULL, 0 };
205	int flags = 0;
206
207	key.data = inter_string;
208	key.size = inter_sz;
209
210	if (!(dbp->get)(dbp, &key, &data, 0))
211		flags |= YP_INTERDOMAIN;
212
213	key.data = secure_string;
214	key.size = secure_sz;
215
216	if (!(dbp->get)(dbp, &key, &data, 0))
217		flags |= YP_SECURE;
218
219	return(flags);
220}
221
222int
223yp_testflag(char *map, char *domain, int flag)
224{
225	char buf[MAXPATHLEN + 2];
226	register struct circleq_entry *qptr;
227
228	if (map == NULL || domain == NULL)
229		return(0);
230
231	strcpy(buf, domain);
232	strcat(buf, "/");
233	strcat(buf, map);
234
235	TAILQ_FOREACH(qptr, &qhead, links) {
236		if (!strcmp(qptr->dbptr->name, buf)) {
237			if (qptr->dbptr->flags & flag)
238				return(1);
239			else
240				return(0);
241		}
242	}
243
244	if (yp_open_db_cache(domain, map, NULL, 0) == NULL)
245		return(0);
246
247	if (TAILQ_FIRST(&qhead)->dbptr->flags & flag)
248		return(1);
249
250	return(0);
251}
252
253/*
254 * Add a DB handle and database name to the cache. We only maintain
255 * fixed number of entries in the cache, so if we're asked to store
256 * a new entry when all our slots are already filled, we have to kick
257 * out the entry in the last slot to make room.
258 */
259static int
260yp_cache_db(DB *dbp, char *name, int size)
261{
262	register struct circleq_entry *qptr;
263
264	if (numdbs == MAXDBS) {
265		if (ypdb_debug)
266			yp_error("queue overflow -- releasing last slot");
267		yp_flush();
268	}
269
270	/*
271	 * Allocate a new queue entry.
272	 */
273
274	if ((qptr = yp_malloc_qent()) == NULL) {
275		yp_error("failed to allocate a new cache entry");
276		return(1);
277	}
278
279	qptr->dbptr->dbp = dbp;
280	qptr->dbptr->name = strdup(name);
281	qptr->dbptr->size = size;
282	qptr->dbptr->key = NULL;
283
284	qptr->dbptr->flags = yp_setflags(dbp);
285
286	TAILQ_INSERT_HEAD(&qhead, qptr, links);
287	numdbs++;
288
289	return(0);
290}
291
292/*
293 * Search the list for a database matching 'name.' If we find it,
294 * move it to the head of the list and return its DB handle. If
295 * not, just fail: yp_open_db_cache() will subsequently try to open
296 * the database itself and call yp_cache_db() to add it to the
297 * list.
298 *
299 * The search works like this:
300 *
301 * - The caller specifies the name of a database to locate. We try to
302 *   find an entry in our queue with a matching name.
303 *
304 * - If the caller doesn't specify a key or size, we assume that the
305 *   first entry that we encounter with a matching name is returned.
306 *   This will result in matches regardless of the key/size values
307 *   stored in the queue entry.
308 *
309 * - If the caller also specifies a key and length, we check to see
310 *   if the key and length saved in the queue entry also matches.
311 *   This lets us return a DB handle that's already positioned at the
312 *   correct location within a database.
313 *
314 * - Once we have a match, it gets migrated to the top of the queue
315 *   so that it will be easier to find if another request for
316 *   the same database comes in later.
317 */
318static DB *
319yp_find_db(const char *name, const char *key, int size)
320{
321	register struct circleq_entry *qptr;
322
323	TAILQ_FOREACH(qptr, &qhead, links) {
324		if (!strcmp(qptr->dbptr->name, name)) {
325			if (size) {
326				if (size != qptr->dbptr->size ||
327				   strncmp(qptr->dbptr->key, key, size))
328					continue;
329			} else {
330				if (qptr->dbptr->size)
331					continue;
332			}
333			if (qptr != TAILQ_FIRST(&qhead)) {
334				TAILQ_REMOVE(&qhead, qptr, links);
335				TAILQ_INSERT_HEAD(&qhead, qptr, links);
336			}
337			return(qptr->dbptr->dbp);
338		}
339	}
340
341	return(NULL);
342}
343
344/*
345 * Open a DB database and cache the handle for later use. We first
346 * check the cache to see if the required database is already open.
347 * If so, we fetch the handle from the cache. If not, we try to open
348 * the database and save the handle in the cache for later use.
349 */
350DB *
351yp_open_db_cache(const char *domain, const char *map, const char *key,
352    const int size)
353{
354	DB *dbp = NULL;
355	char buf[MAXPATHLEN + 2];
356/*
357	snprintf(buf, sizeof(buf), "%s/%s", domain, map);
358*/
359	yp_errno = YP_TRUE;
360
361	strcpy(buf, domain);
362	strcat(buf, "/");
363	strcat(buf, map);
364
365	if ((dbp = yp_find_db(buf, key, size)) != NULL) {
366		return(dbp);
367	} else {
368		if ((dbp = yp_open_db(domain, map)) != NULL) {
369			if (yp_cache_db(dbp, buf, size)) {
370				(void)(dbp->close)(dbp);
371				yp_errno = YP_YPERR;
372				return(NULL);
373			}
374		}
375	}
376
377	return (dbp);
378}
379#endif
380
381/*
382 * Open a DB database.
383 */
384DB *
385yp_open_db(const char *domain, const char *map)
386{
387	DB *dbp = NULL;
388	char buf[MAXPATHLEN + 2];
389
390	yp_errno = YP_TRUE;
391
392	if (map[0] == '.' || strchr(map, '/')) {
393		yp_errno = YP_BADARGS;
394		return (NULL);
395	}
396
397#ifdef DB_CACHE
398	if (yp_validdomain(domain)) {
399		yp_errno = YP_NODOM;
400		return(NULL);
401	}
402#endif
403	snprintf(buf, sizeof(buf), "%s/%s/%s", yp_dir, domain, map);
404
405#ifdef DB_CACHE
406again:
407#endif
408	dbp = dbopen(buf,O_RDONLY, PERM_SECURE, DB_HASH, NULL);
409
410	if (dbp == NULL) {
411		switch (errno) {
412#ifdef DB_CACHE
413		case ENFILE:
414			/*
415			 * We ran out of file descriptors. Nuke an
416			 * open one and try again.
417			 */
418			yp_error("ran out of file descriptors");
419			yp_flush();
420			goto again;
421			break;
422#endif
423		case ENOENT:
424			yp_errno = YP_NOMAP;
425			break;
426		case EFTYPE:
427			yp_errno = YP_BADDB;
428			break;
429		default:
430			yp_errno = YP_YPERR;
431			break;
432		}
433	}
434
435	return (dbp);
436}
437
438/*
439 * Database access routines.
440 *
441 * - yp_get_record(): retrieve an arbitrary key/data pair given one key
442 *                 to match against.
443 *
444 * - yp_first_record(): retrieve first key/data base in a database.
445 *
446 * - yp_next_record(): retrieve key/data pair that sequentially follows
447 *                   the supplied key value in the database.
448 */
449
450#ifdef DB_CACHE
451int
452yp_get_record(DB *dbp, const DBT *key, DBT *data, int allow)
453#else
454int
455yp_get_record(const char *domain, const char *map,
456    const DBT *key, DBT *data, int allow)
457#endif
458{
459#ifndef DB_CACHE
460	DB *dbp;
461#endif
462	int rval = 0;
463#ifndef DB_CACHE
464	static unsigned char buf[YPMAXRECORD];
465#endif
466
467	if (ypdb_debug)
468		yp_error("looking up key [%.*s]",
469		    (int)key->size, (char *)key->data);
470
471	/*
472	 * Avoid passing back magic "YP_*" entries unless
473	 * the caller specifically requested them by setting
474	 * the 'allow' flag.
475	 */
476	if (!allow && !strncmp(key->data, "YP_", 3))
477		return(YP_NOKEY);
478
479#ifndef DB_CACHE
480	if ((dbp = yp_open_db(domain, map)) == NULL) {
481		return(yp_errno);
482	}
483#endif
484
485	if ((rval = (dbp->get)(dbp, key, data, 0)) != 0) {
486#ifdef DB_CACHE
487		TAILQ_FIRST(&qhead)->dbptr->size = 0;
488#else
489		(void)(dbp->close)(dbp);
490#endif
491		if (rval == 1)
492			return(YP_NOKEY);
493		else
494			return(YP_BADDB);
495	}
496
497	if (ypdb_debug)
498		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
499		    (int)key->size, (char *)key->data,
500		    (int)data->size, (char *)data->data);
501
502#ifdef DB_CACHE
503	if (TAILQ_FIRST(&qhead)->dbptr->size) {
504		TAILQ_FIRST(&qhead)->dbptr->key = "";
505		TAILQ_FIRST(&qhead)->dbptr->size = 0;
506	}
507#else
508	bcopy(data->data, &buf, data->size);
509	data->data = &buf;
510	(void)(dbp->close)(dbp);
511#endif
512
513	return(YP_TRUE);
514}
515
516int
517yp_first_record(const DB *dbp, DBT *key, DBT *data, int allow)
518{
519	int rval;
520#ifndef DB_CACHE
521	static unsigned char buf[YPMAXRECORD];
522#endif
523
524	if (ypdb_debug)
525		yp_error("retrieving first key in map");
526
527	if ((rval = (dbp->seq)(dbp,key,data,R_FIRST)) != 0) {
528#ifdef DB_CACHE
529		TAILQ_FIRST(&qhead)->dbptr->size = 0;
530#endif
531		if (rval == 1)
532			return(YP_NOKEY);
533		else
534			return(YP_BADDB);
535	}
536
537	/* Avoid passing back magic "YP_*" records. */
538	while (!strncmp(key->data, "YP_", 3) && !allow) {
539		if ((rval = (dbp->seq)(dbp,key,data,R_NEXT)) != 0) {
540#ifdef DB_CACHE
541			TAILQ_FIRST(&qhead)->dbptr->size = 0;
542#endif
543			if (rval == 1)
544				return(YP_NOKEY);
545			else
546				return(YP_BADDB);
547		}
548	}
549
550	if (ypdb_debug)
551		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
552		    (int)key->size, (char *)key->data,
553		    (int)data->size, (char *)data->data);
554
555#ifdef DB_CACHE
556	if (TAILQ_FIRST(&qhead)->dbptr->size) {
557		TAILQ_FIRST(&qhead)->dbptr->key = key->data;
558		TAILQ_FIRST(&qhead)->dbptr->size = key->size;
559	}
560#else
561	bcopy(data->data, &buf, data->size);
562	data->data = &buf;
563#endif
564
565	return(YP_TRUE);
566}
567
568int
569yp_next_record(const DB *dbp, DBT *key, DBT *data, int all, int allow)
570{
571	static DBT lkey = { NULL, 0 };
572	static DBT ldata = { NULL, 0 };
573	int rval;
574#ifndef DB_CACHE
575	static unsigned char keybuf[YPMAXRECORD];
576	static unsigned char datbuf[YPMAXRECORD];
577#endif
578
579	if (key == NULL || !key->size || key->data == NULL) {
580		rval = yp_first_record(dbp,key,data,allow);
581		if (rval == YP_NOKEY)
582			return(YP_NOMORE);
583		else {
584#ifdef DB_CACHE
585			TAILQ_FIRST(&qhead)->dbptr->key = key->data;
586			TAILQ_FIRST(&qhead)->dbptr->size = key->size;
587#endif
588			return(rval);
589		}
590	}
591
592	if (ypdb_debug)
593		yp_error("retrieving next key, previous was: [%.*s]",
594		    (int)key->size, (char *)key->data);
595
596	if (!all) {
597#ifdef DB_CACHE
598		if (TAILQ_FIRST(&qhead)->dbptr->key == NULL) {
599#endif
600			(dbp->seq)(dbp,&lkey,&ldata,R_FIRST);
601			while (key->size != lkey.size ||
602			    strncmp(key->data, lkey.data,
603			    (int)key->size))
604				if ((dbp->seq)(dbp,&lkey,&ldata,R_NEXT)) {
605#ifdef DB_CACHE
606					TAILQ_FIRST(&qhead)->dbptr->size = 0;
607#endif
608					return(YP_NOKEY);
609				}
610
611#ifdef DB_CACHE
612		}
613#endif
614	}
615
616	if ((dbp->seq)(dbp,key,data,R_NEXT)) {
617#ifdef DB_CACHE
618		TAILQ_FIRST(&qhead)->dbptr->size = 0;
619#endif
620		return(YP_NOMORE);
621	}
622
623	/* Avoid passing back magic "YP_*" records. */
624	while (!strncmp(key->data, "YP_", 3) && !allow)
625		if ((dbp->seq)(dbp,key,data,R_NEXT)) {
626#ifdef DB_CACHE
627		TAILQ_FIRST(&qhead)->dbptr->size = 0;
628#endif
629			return(YP_NOMORE);
630		}
631
632	if (ypdb_debug)
633		yp_error("result of lookup: key: [%.*s] data: [%.*s]",
634		    (int)key->size, (char *)key->data,
635		    (int)data->size, (char *)data->data);
636
637#ifdef DB_CACHE
638	if (TAILQ_FIRST(&qhead)->dbptr->size) {
639		TAILQ_FIRST(&qhead)->dbptr->key = key->data;
640		TAILQ_FIRST(&qhead)->dbptr->size = key->size;
641	}
642#else
643	bcopy(key->data, &keybuf, key->size);
644	lkey.data = &keybuf;
645	lkey.size = key->size;
646	bcopy(data->data, &datbuf, data->size);
647	data->data = &datbuf;
648#endif
649
650	return(YP_TRUE);
651}
652
653#ifdef DB_CACHE
654/*
655 * Database glue functions.
656 */
657
658static DB *yp_currmap_db = NULL;
659static int yp_allow_db = 0;
660
661ypstat
662yp_select_map(char *map, char *domain, keydat *key, int allow)
663{
664	if (key == NULL)
665		yp_currmap_db = yp_open_db_cache(domain, map, NULL, 0);
666	else
667		yp_currmap_db = yp_open_db_cache(domain, map,
668						 key->keydat_val,
669						 key->keydat_len);
670
671	yp_allow_db = allow;
672	return(yp_errno);
673}
674
675ypstat
676yp_getbykey(keydat *key, valdat *val)
677{
678	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
679	ypstat rval;
680
681	db_key.data = key->keydat_val;
682	db_key.size = key->keydat_len;
683
684	rval = yp_get_record(yp_currmap_db,
685				&db_key, &db_val, yp_allow_db);
686
687	if (rval == YP_TRUE) {
688		val->valdat_val = db_val.data;
689		val->valdat_len = db_val.size;
690	}
691
692	return(rval);
693}
694
695ypstat
696yp_firstbykey(keydat *key, valdat *val)
697{
698	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
699	ypstat rval;
700
701	rval = yp_first_record(yp_currmap_db, &db_key, &db_val, yp_allow_db);
702
703	if (rval == YP_TRUE) {
704		key->keydat_val = db_key.data;
705		key->keydat_len = db_key.size;
706		val->valdat_val = db_val.data;
707		val->valdat_len = db_val.size;
708	}
709
710	return(rval);
711}
712
713ypstat
714yp_nextbykey(keydat *key, valdat *val)
715{
716	DBT db_key = { NULL, 0 }, db_val = { NULL, 0 };
717	ypstat rval;
718
719	db_key.data = key->keydat_val;
720	db_key.size = key->keydat_len;
721
722	rval = yp_next_record(yp_currmap_db, &db_key, &db_val, 0, yp_allow_db);
723
724	if (rval == YP_TRUE) {
725		key->keydat_val = db_key.data;
726		key->keydat_len = db_key.size;
727		val->valdat_val = db_val.data;
728		val->valdat_len = db_val.size;
729	}
730
731	return(rval);
732}
733#endif
734