1/*	$OpenBSD: mapc.c,v 1.24 2021/10/21 10:55:56 deraadt Exp $	*/
2
3/*-
4 * Copyright (c) 1989 Jan-Simon Pendry
5 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
6 * Copyright (c) 1989, 1993
7 *	The Regents of the University of California.  All rights reserved.
8 *
9 * This code is derived from software contributed to Berkeley by
10 * Jan-Simon Pendry at Imperial College, London.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37/*
38 * Mount map cache
39 */
40
41#include "am.h"
42#include <regex.h>
43
44/*
45 * Hash table size
46 */
47#define	NKVHASH	(1 << 2)		/* Power of two */
48
49/*
50 * Wildcard key
51 */
52static char wildcard[] = "*";
53
54/*
55 * Map cache types
56 * default, none, incremental, all, regexp
57 * MAPC_RE implies MAPC_ALL and must be numerically
58 * greater.
59 */
60#define	MAPC_DFLT	0x000
61#define	MAPC_NONE	0x001
62#define	MAPC_INC	0x002
63#define	MAPC_ROOT	0x004
64#define	MAPC_ALL	0x010
65#define MAPC_RE		0x020
66#define	MAPC_ISRE(m) ((m)->alloc == MAPC_RE)
67#define	MAPC_CACHE_MASK	0x0ff
68#define	MAPC_SYNC	0x100
69
70static struct opt_tab mapc_opt[] = {
71	{ "all", MAPC_ALL },
72	{ "default", MAPC_DFLT },
73	{ "inc", MAPC_INC },
74	{ "mapdefault", MAPC_DFLT },
75	{ "none", MAPC_NONE },
76	{ "re", MAPC_RE },
77	{ "regexp", MAPC_RE },
78	{ "sync", MAPC_SYNC },
79	{ 0, 0 }
80};
81
82/*
83 * Lookup recursion
84 */
85#define	MREC_FULL	2
86#define	MREC_PART	1
87#define	MREC_NONE	0
88
89/*
90 * Cache map operations
91 */
92typedef void	add_fn(mnt_map *, char *, char *);
93typedef int	init_fn(char *, time_t *);
94typedef int	search_fn(mnt_map *, char *, char *, char **, time_t *);
95typedef int	reload_fn(mnt_map *, char *, add_fn *);
96typedef int	mtime_fn(char *, time_t *);
97
98static void	mapc_sync(mnt_map *);
99
100/*
101 * Map type
102 */
103typedef struct map_type map_type;
104struct map_type {
105	char *name;			/* Name of this map type */
106	init_fn *init;			/* Initialisation */
107	reload_fn *reload;		/* Reload or fill */
108	search_fn *search;		/* Search for new entry */
109	mtime_fn *mtime;		/* Find modify time */
110	int def_alloc;			/* Default allocation mode */
111};
112
113/*
114 * Key-value pair
115 */
116typedef struct kv kv;
117struct kv {
118	kv *next;
119	char *key;
120	char *val;
121};
122
123struct mnt_map {
124	qelem hdr;
125	int refc;			/* Reference count */
126	short flags;			/* Allocation flags */
127	short alloc;			/* Allocation mode */
128	time_t modify;			/* Modify time of map */
129	char *map_name;			/* Name of this map */
130	char *wildcard;			/* Wildcard value */
131	reload_fn *reload;		/* Function to be used for reloads */
132	search_fn *search;		/* Function to be used for searching */
133	mtime_fn *mtime;		/* Modify time function */
134	kv *kvhash[NKVHASH];		/* Cached data */
135};
136
137/*
138 * Map for root node
139 */
140static mnt_map *root_map;
141
142/*
143 * List of known maps
144 */
145extern qelem map_list_head;
146qelem map_list_head = { &map_list_head, &map_list_head };
147
148/*
149 * Configuration
150 */
151
152/* ROOT MAP */
153static int	root_init(char *, time_t *);
154
155/* FILE MAPS */
156extern int	file_init(char *, time_t *);
157extern int	file_reload(mnt_map *, char *, add_fn *);
158extern int	file_search(mnt_map *, char *, char *, char **, time_t *);
159extern int	file_mtime(char *, time_t *);
160
161/* Network Information Service (NIS) MAPS */
162extern int	nis_init(char *, time_t *);
163extern int	nis_reload(mnt_map *, char *, add_fn *);
164extern int	nis_search(mnt_map *, char *, char *, char **, time_t *);
165#define nis_mtime nis_init
166
167/* NDBM MAPS */
168#ifdef HAS_NDBM_MAPS
169extern int	ndbm_init(char *, time_t *);
170extern int	ndbm_search(mnt_map *, char *, charo *, char **, time_t *);
171#define ndbm_mtime ndbm_init
172#endif /* HAS_NDBM_MAPS */
173
174/* PASSWD MAPS */
175extern int	passwd_init(char *, time_t *);
176extern int	passwd_search(mnt_map *, char *, char *, char **, time_t *);
177
178/* UNION MAPS */
179extern int	union_init(char *, time_t *);
180extern int	union_search(mnt_map *, char *, char *, char **, time_t *);
181extern int	union_reload(mnt_map *, char *, add_fn *);
182
183/* ERROR MAP */
184static int	error_init(char *, time_t *);
185static int	error_reload(mnt_map *, char *, add_fn *);
186static int	error_search(mnt_map *, char *, char *, char **, time_t *);
187static int	error_mtime(char *, time_t *);
188
189static map_type maptypes[] = {
190	{ "root", root_init, error_reload, error_search, error_mtime, MAPC_ROOT },
191
192	{ "passwd", passwd_init, error_reload, passwd_search, error_mtime, MAPC_INC },
193
194	{ "union", union_init, union_reload, union_search, error_mtime, MAPC_ALL },
195
196	{ "nis", nis_init, nis_reload, nis_search, nis_mtime, MAPC_INC },
197
198#ifdef HAS_NDBM_MAPS
199	{ "ndbm", ndbm_init, error_reload, ndbm_search, ndbm_mtime, MAPC_INC },
200#endif
201
202	{ "file", file_init, file_reload, file_search, file_mtime, MAPC_ALL },
203
204	{ "error", error_init, error_reload, error_search, error_mtime, MAPC_NONE },
205};
206
207/*
208 * Hash function
209 */
210static unsigned int
211kvhash_of(char *key)
212{
213	unsigned int i, j;
214
215	for (i = 0; (j = *key++); i += j)
216		;
217
218	return i % NKVHASH;
219}
220
221void
222mapc_showtypes(FILE *fp)
223{
224	map_type *mt;
225	char *sep = "";
226
227	for (mt = maptypes; mt < maptypes+sizeof(maptypes)/sizeof(maptypes[0]); mt++) {
228		fprintf(fp, "%s%s", sep, mt->name);
229		sep = ", ";
230	}
231}
232
233/*
234 * Add key and val to the map m.
235 * key and val are assumed to be safe copies
236 */
237void
238mapc_add_kv(mnt_map *m, char *key, char *val)
239{
240	kv **h;
241	kv *n;
242	int hash = kvhash_of(key);
243
244#ifdef DEBUG
245	dlog("add_kv: %s -> %s", key, val);
246#endif
247
248	if (MAPC_ISRE(m)) {
249		char keyb[PATH_MAX];
250		regex_t *re;
251		int err;
252
253		/*
254		 * Make sure the string is bound to the start and end
255		 */
256		snprintf(keyb, sizeof(keyb), "^%s$", key);
257		re = malloc(sizeof(*re));
258		if (re == NULL) {
259			plog(XLOG_USER, "error allocating RE \"%s\"", keyb);
260			return;
261		}
262		err = regcomp(re, keyb, 0);
263		if (err) {
264			char errbuf[100];
265
266			regerror(err, re, errbuf, sizeof errbuf);
267			free(re);
268			plog(XLOG_USER, "error compiling RE \"%s\": %s",
269			    keyb, errbuf);
270			return;
271		}
272
273		free(key);
274		key = (char *)re;
275	}
276
277	h = &m->kvhash[hash];
278	n = ALLOC(kv);
279	n->key = key;
280	n->val = val;
281	n->next = *h;
282	*h = n;
283}
284
285static void
286mapc_repl_kv(mnt_map *m, char *key, char *val)
287{
288	kv *k;
289
290	/*
291	 * Compute the hash table offset
292	 */
293	k = m->kvhash[kvhash_of(key)];
294
295	/*
296	 * Scan the linked list for the key
297	 */
298	while (k && !FSTREQ(k->key, key))
299		k = k->next;
300
301	if (k) {
302		free(k->val);
303		k->val = val;
304	} else {
305		mapc_add_kv(m, key, val);
306	}
307
308}
309
310/*
311 * Search a map for a key.
312 * Calls map specific search routine.
313 * While map is out of date, keep re-syncing.
314 */
315static int search_map(mnt_map *m, char *key, char **valp)
316{
317	int rc;
318
319	do {
320		rc = (*m->search)(m, m->map_name, key, valp, &m->modify);
321		if (rc < 0) {
322			plog(XLOG_MAP, "Re-synchronizing cache for map %s", m->map_name);
323			mapc_sync(m);
324		}
325	} while (rc < 0);
326
327	return rc;
328}
329
330/*
331 * Do a wildcard lookup in the map and
332 * save the result.
333 */
334static void
335mapc_find_wildcard(mnt_map *m)
336{
337	/*
338	 * Attempt to find the wildcard entry
339	 */
340	int rc = search_map(m, wildcard, &m->wildcard);
341
342	if (rc != 0)
343		m->wildcard = 0;
344}
345
346/*
347 * Make a duplicate reference to an existing map
348 */
349#define mapc_dup(m) ((m)->refc++, (m))
350
351/*
352 * Do a map reload
353 */
354static int
355mapc_reload_map(mnt_map *m)
356{
357	int error;
358#ifdef DEBUG
359	dlog("calling map reload on %s", m->map_name);
360#endif
361	error = (*m->reload)(m, m->map_name, mapc_add_kv);
362	if (error)
363		return error;
364	m->wildcard = 0;
365#ifdef DEBUG
366	dlog("calling mapc_search for wildcard");
367#endif
368	error = mapc_search(m, wildcard, &m->wildcard);
369	if (error)
370		m->wildcard = 0;
371	return 0;
372}
373
374/*
375 * Create a new map
376 */
377static mnt_map *
378mapc_create(char *map, char *opt)
379{
380	mnt_map *m = ALLOC(mnt_map);
381	map_type *mt;
382	time_t modify;
383	int alloc = 0;
384
385	(void) cmdoption(opt, mapc_opt, &alloc);
386
387	for (mt = maptypes; mt < maptypes+sizeof(maptypes)/sizeof(maptypes[0]); mt++)
388		if ((*mt->init)(map, &modify) == 0)
389			break;
390	/* assert: mt in maptypes */
391
392	m->flags = alloc & ~MAPC_CACHE_MASK;
393	alloc &= MAPC_CACHE_MASK;
394
395	if (alloc == MAPC_DFLT)
396		alloc = mt->def_alloc;
397	switch (alloc) {
398	default:
399		plog(XLOG_USER, "Ambiguous map cache type \"%s\"; using \"inc\"", opt);
400		alloc = MAPC_INC;
401		/* fallthrough... */
402	case MAPC_NONE:
403	case MAPC_INC:
404	case MAPC_ROOT:
405		break;
406	case MAPC_ALL:
407		/*
408		 * If there is no support for reload and it was requested
409		 * then back off to incremental instead.
410		 */
411		if (mt->reload == error_reload) {
412			plog(XLOG_WARNING, "Map type \"%s\" does not support cache type \"all\"; using \"inc\"", mt->name);
413			alloc = MAPC_INC;
414		}
415		break;
416	case MAPC_RE:
417		if (mt->reload == error_reload) {
418			plog(XLOG_WARNING, "Map type \"%s\" does not support cache type \"re\"", mt->name);
419			mt = &maptypes[sizeof(maptypes)/sizeof(maptypes[0]) - 1];
420			/* assert: mt->name == "error" */
421		}
422		break;
423	}
424
425#ifdef DEBUG
426	dlog("Map for %s coming from maptype %s", map, mt->name);
427#endif
428
429	m->alloc = alloc;
430	m->reload = mt->reload;
431	m->modify = modify;
432	m->search = alloc >= MAPC_ALL ? error_search : mt->search;
433	m->mtime = mt->mtime;
434	bzero(m->kvhash, sizeof(m->kvhash));
435	m->map_name = strdup(map);
436	m->refc = 1;
437	m->wildcard = 0;
438
439	/*
440	 * synchronize cache with reality
441	 */
442	mapc_sync(m);
443
444	return m;
445}
446
447/*
448 * Free the cached data in a map
449 */
450static void
451mapc_clear(mnt_map *m)
452{
453	int i;
454
455	/*
456	 * For each of the hash slots, chain
457	 * along free'ing the data.
458	 */
459	for (i = 0; i < NKVHASH; i++) {
460		kv *k = m->kvhash[i];
461		while (k) {
462			kv *n = k->next;
463			free(k->key);
464			free(k->val);
465			free(k);
466			k = n;
467		}
468	}
469	/*
470	 * Zero the hash slots
471	 */
472	bzero(m->kvhash, sizeof(m->kvhash));
473	/*
474	 * Free the wildcard if it exists
475	 */
476	if (m->wildcard) {
477		free(m->wildcard);
478		m->wildcard = 0;
479	}
480}
481
482/*
483 * Find a map, or create one if it does not exist
484 */
485mnt_map *
486mapc_find(char *map, char *opt)
487{
488	mnt_map *m;
489
490	/*
491	 * Search the list of known maps to see if
492	 * it has already been loaded.  If it is found
493	 * then return a duplicate reference to it.
494	 * Otherwise make a new map as required and
495	 * add it to the list of maps
496	 */
497	ITER(m, mnt_map, &map_list_head)
498		if (STREQ(m->map_name, map))
499			return mapc_dup(m);
500
501	m = mapc_create(map, opt);
502	ins_que(&m->hdr, &map_list_head);
503	return m;
504}
505
506/*
507 * Free a map.
508 */
509void
510mapc_free(void *arg)
511{
512	mnt_map *m = arg;
513	/*
514	 * Decrement the reference count.
515	 * If the reference count hits zero
516	 * then throw the map away.
517	 */
518	if (m && --m->refc == 0) {
519		mapc_clear(m);
520		free(m->map_name);
521		rem_que(&m->hdr);
522		free(m);
523	}
524}
525
526/*
527 * Search the map for the key.
528 * Put a safe copy in *pval or return
529 * an error code
530 */
531int
532mapc_meta_search(mnt_map *m, char *key, char **pval, int recurse)
533{
534	int error = 0;
535	kv *k = 0;
536
537	/*
538	 * Firewall
539	 */
540	if (!m) {
541		plog(XLOG_ERROR, "Null map request for %s", key);
542		return ENOENT;
543	}
544
545	if (m->flags & MAPC_SYNC) {
546		/*
547		 * Get modify time...
548		 */
549		time_t t;
550		error = (*m->mtime)(m->map_name, &t);
551		if (error || t > m->modify) {
552			m->modify = t;
553			plog(XLOG_INFO, "Map %s is out of date", m->map_name);
554			mapc_sync(m);
555		}
556	}
557
558	if (!MAPC_ISRE(m)) {
559		/*
560		 * Compute the hash table offset
561		 */
562		k = m->kvhash[kvhash_of(key)];
563
564		/*
565		 * Scan the linked list for the key
566		 */
567		while (k && !FSTREQ(k->key, key)) k = k->next;
568
569	}
570	else if (recurse == MREC_FULL) {
571		/*
572		 * Try for an RE match against the entire map.
573		 * Note that this will be done in a "random"
574		 * order.
575		 */
576
577		int i;
578
579		for (i = 0; i < NKVHASH; i++) {
580			k = m->kvhash[i];
581			while (k) {
582				if (regexec((regex_t *)k->key, key,
583				    0, NULL, 0) == 0)
584					break;
585				k = k->next;
586			}
587			if (k)
588				break;
589		}
590	}
591
592	/*
593	 * If found then take a copy
594	 */
595	if (k) {
596		if (k->val)
597			*pval = strdup(k->val);
598		else
599			error = ENOENT;
600	} else if (m->alloc >= MAPC_ALL) {
601		/*
602		 * If the entire map is cached then this
603		 * key does not exist.
604		 */
605		error = ENOENT;
606	} else {
607		/*
608		 * Otherwise search the map.  If we are
609		 * in incremental mode then add the key
610		 * to the cache.
611		 */
612		error = search_map(m, key, pval);
613		if (!error && m->alloc == MAPC_INC)
614			mapc_add_kv(m, strdup(key), strdup(*pval));
615	}
616
617	/*
618	 * If an error, and a wildcard exists,
619	 * and the key is not internal then
620	 * return a copy of the wildcard.
621	 */
622	if (error > 0) {
623		if (recurse == MREC_FULL && !MAPC_ISRE(m)) {
624			char wildname[PATH_MAX];
625			char *subp;
626			if (*key == '/')
627				return error;
628			/*
629			 * Keep chopping sub-directories from the RHS
630			 * and replacing with "/ *" and repeat the lookup.
631			 * For example:
632			 * "src/gnu/gcc" -> "src / gnu / *" -> "src / *"
633			 */
634			strlcpy(wildname, key, sizeof wildname);
635			while (error && (subp = strrchr(wildname, '/'))) {
636				strlcpy(subp, "/*", 3);
637#ifdef DEBUG
638				dlog("mapc recurses on %s", wildname);
639#endif
640				error = mapc_meta_search(m, wildname, pval, MREC_PART);
641				if (error)
642					*subp = 0;
643			}
644			if (error > 0 && m->wildcard) {
645				*pval = strdup(m->wildcard);
646				error = 0;
647			}
648		}
649	}
650
651	return error;
652}
653
654int
655mapc_search(mnt_map *m, char *key, char **pval)
656{
657	return mapc_meta_search(m, key, pval, MREC_FULL);
658}
659
660/*
661 * Get map cache in sync with physical representation
662 */
663static void
664mapc_sync(mnt_map *m)
665{
666	if (m->alloc != MAPC_ROOT) {
667		mapc_clear(m);
668
669		if (m->alloc >= MAPC_ALL)
670			if (mapc_reload_map(m))
671				m->alloc = MAPC_INC;
672		/*
673		 * Attempt to find the wildcard entry
674		 */
675		if (m->alloc < MAPC_ALL)
676			mapc_find_wildcard(m);
677	}
678}
679
680/*
681 * Reload all the maps
682 * Called when Amd gets hit by a SIGHUP.
683 */
684void mapc_reload(void)
685{
686	mnt_map *m;
687
688	/*
689	 * For all the maps,
690	 * Throw away the existing information.
691	 * Do a reload
692	 * Find the wildcard
693	 */
694	ITER(m, mnt_map, &map_list_head)
695		mapc_sync(m);
696}
697
698/*
699 * Root map.
700 * The root map is used to bootstrap amd.
701 * All the require top-level mounts are added
702 * into the root map and then the map is iterated
703 * and a lookup is done on all the mount points.
704 * This causes the top level mounts to be automounted.
705 */
706
707static int
708root_init(char *map, time_t *tp)
709{
710	*tp = clocktime();
711	return strcmp(map, ROOT_MAP) == 0 ? 0 : ENOENT;
712}
713
714/*
715 * Add a new entry to the root map
716 *
717 * dir - directory (key)
718 * opts - mount options
719 * map - map name
720 */
721void
722root_newmap(char *dir, char *opts, char *map)
723{
724	char str[PATH_MAX];
725
726	/*
727	 * First make sure we have a root map to talk about...
728	 */
729	if (!root_map)
730		root_map = mapc_find(ROOT_MAP, "mapdefault");
731
732	/*
733	 * Then add the entry...
734	 */
735	dir = strdup(dir);
736	if (map)
737		snprintf(str, sizeof(str), "cache:=mapdefault;type:=toplvl;fs:=\"%s\";%s",
738			map, opts ? opts : "");
739	else
740		strlcpy(str, opts, sizeof str);
741	mapc_repl_kv(root_map, dir, strdup(str));
742}
743
744int
745mapc_keyiter(mnt_map *m, void (*fn)(char *,void *), void *arg)
746{
747	int i;
748	int c = 0;
749
750	for (i = 0; i < NKVHASH; i++) {
751		kv *k = m->kvhash[i];
752		while (k) {
753			(*fn)(k->key, arg);
754			k = k->next;
755			c++;
756		}
757	}
758
759	return c;
760}
761
762/*
763 * Iterate over the root map
764 * and call (*fn)() on the key
765 * of all the nodes.
766 * Finally throw away the root map.
767 */
768int
769root_keyiter(void (*fn)(char *,void *), void *arg)
770{
771	if (root_map) {
772		int c = mapc_keyiter(root_map, fn, arg);
773#ifdef notdef
774		mapc_free(root_map);
775		root_map = 0;
776#endif
777		return c;
778	}
779	return 0;
780}
781
782/*
783 * Error map
784 */
785static int
786error_init(char *map, time_t *tp)
787{
788	plog(XLOG_USER, "No source data for map %s", map);
789	*tp = 0;
790	return 0;
791}
792
793static int
794error_search(mnt_map *m, char *map, char *key, char **pval, time_t *tp)
795{
796	return ENOENT;
797}
798
799static int
800error_reload(mnt_map *m, char *map, add_fn *fn)
801{
802	return ENOENT;
803}
804
805static int
806error_mtime(char *map, time_t *tp)
807{
808	*tp = 0;
809	return 0;
810}
811