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