1/*-
2 * Copyright (c) 1989 Jan-Simon Pendry
3 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine
4 * Copyright (c) 1989, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Jan-Simon Pendry at Imperial College, London.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its 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 THE REGENTS 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 THE REGENTS 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 "am.h"
36
37/*
38 * static copy of the options with
39 * which to play
40 */
41static struct am_opts fs_static;
42
43static char *opt_host = hostname;
44static char *opt_hostd = hostd;
45static char nullstr[] = "";
46static char *opt_key = nullstr;
47static char *opt_map = nullstr;
48static char *opt_path = nullstr;
49
50static char *vars[8];
51
52/*
53 * Length of longest option name
54 */
55#define	NLEN	16	/* conservative */
56#define S(x) (x) , (sizeof(x)-1)
57static struct opt {
58	char *name;		/* Name of the option */
59	int nlen;		/* Length of option name */
60	char **optp;		/* Pointer to option value string */
61	char **sel_p;		/* Pointer to selector value string */
62} opt_fields[] = {
63	/* Options in something corresponding to frequency of use */
64	{ S("opts"), &fs_static.opt_opts, 0 },
65	{ S("host"), 0, &opt_host },
66	{ S("hostd"), 0, &opt_hostd },
67	{ S("type"), &fs_static.opt_type, 0 },
68	{ S("rhost"), &fs_static.opt_rhost, 0 },
69	{ S("rfs"), &fs_static.opt_rfs, 0 },
70	{ S("fs"), &fs_static.opt_fs, 0 },
71	{ S("key"), 0, &opt_key },
72	{ S("map"), 0, &opt_map },
73	{ S("sublink"), &fs_static.opt_sublink, 0 },
74	{ S("arch"), 0, &arch },
75	{ S("dev"), &fs_static.opt_dev, 0 },
76	{ S("pref"), &fs_static.opt_pref, 0 },
77	{ S("path"), 0, &opt_path },
78	{ S("autodir"), 0, &auto_dir },
79	{ S("delay"), &fs_static.opt_delay, 0 },
80	{ S("domain"), 0, &hostdomain },
81	{ S("karch"), 0, &karch },
82	{ S("cluster"), 0, &cluster },
83	{ S("wire"), 0, &wire },
84	{ S("byte"), 0, &endian },
85	{ S("os"), 0, &op_sys },
86	{ S("remopts"), &fs_static.opt_remopts, 0 },
87	{ S("mount"), &fs_static.opt_mount, 0 },
88	{ S("unmount"), &fs_static.opt_unmount, 0 },
89	{ S("cache"), &fs_static.opt_cache, 0 },
90	{ S("user"), &fs_static.opt_user, 0 },
91	{ S("group"), &fs_static.opt_group, 0 },
92	{ S("var0"), &vars[0], 0 },
93	{ S("var1"), &vars[1], 0 },
94	{ S("var2"), &vars[2], 0 },
95	{ S("var3"), &vars[3], 0 },
96	{ S("var4"), &vars[4], 0 },
97	{ S("var5"), &vars[5], 0 },
98	{ S("var6"), &vars[6], 0 },
99	{ S("var7"), &vars[7], 0 },
100	{ 0, 0, 0, 0 },
101};
102
103typedef struct opt_apply opt_apply;
104struct opt_apply {
105	char **opt;
106	char *val;
107};
108
109/*
110 * Specially expand the remote host name first
111 */
112static opt_apply rhost_expansion[] = {
113	{ &fs_static.opt_rhost, "${host}" },
114	{ 0, 0 },
115};
116/*
117 * List of options which need to be expanded
118 * Note that this the order here _may_ be important.
119 */
120static opt_apply expansions[] = {
121/*	{ &fs_static.opt_dir, 0 },	*/
122	{ &fs_static.opt_sublink, 0 },
123	{ &fs_static.opt_rfs, "${path}" },
124	{ &fs_static.opt_fs, "${autodir}/${rhost}${rfs}" },
125	{ &fs_static.opt_opts, "rw" },
126	{ &fs_static.opt_remopts, "${opts}" },
127	{ &fs_static.opt_mount, 0 },
128	{ &fs_static.opt_unmount, 0 },
129	{ 0, 0 },
130};
131
132/*
133 * List of options which need to be free'ed before re-use
134 */
135static opt_apply to_free[] = {
136	{ &fs_static.fs_glob, 0 },
137	{ &fs_static.fs_local, 0 },
138	{ &fs_static.fs_mtab, 0 },
139/*	{ &fs_static.opt_dir, 0 },	*/
140	{ &fs_static.opt_sublink, 0 },
141	{ &fs_static.opt_rfs, 0 },
142	{ &fs_static.opt_fs, 0 },
143	{ &fs_static.opt_rhost, 0 },
144	{ &fs_static.opt_opts, 0 },
145	{ &fs_static.opt_remopts, 0 },
146	{ &fs_static.opt_mount, 0 },
147	{ &fs_static.opt_unmount, 0 },
148	{ &vars[0], 0 },
149	{ &vars[1], 0 },
150	{ &vars[2], 0 },
151	{ &vars[3], 0 },
152	{ &vars[4], 0 },
153	{ &vars[5], 0 },
154	{ &vars[6], 0 },
155	{ &vars[7], 0 },
156	{ 0, 0 },
157};
158
159/*
160 * Skip to next option in the string
161 */
162static char *
163opt(char **p)
164{
165	char *cp = *p;
166	char *dp = cp;
167	char *s = cp;
168
169top:
170	while (*cp && *cp != ';') {
171		if (*cp == '\"') {
172			/*
173			 * Skip past string
174			 */
175			cp++;
176			while (*cp && *cp != '\"')
177				*dp++ = *cp++;
178			if (*cp)
179				cp++;
180		} else {
181			*dp++ = *cp++;
182		}
183	}
184
185	/*
186	 * Skip past any remaining ';'s
187	 */
188	while (*cp == ';')
189		cp++;
190
191	/*
192	 * If we have a zero length string
193	 * and there are more fields, then
194	 * parse the next one.  This allows
195	 * sequences of empty fields.
196	 */
197	if (*cp && dp == s)
198		goto top;
199
200	*dp = '\0';
201
202	*p = cp;
203	return s;
204}
205
206static int
207eval_opts(char *opts, char *mapkey)
208{
209	/*
210	 * Fill in the global structure fs_static by
211	 * cracking the string opts.  opts may be
212	 * scribbled on at will.
213	 */
214	char *o = opts;
215	char *f;
216
217	/*
218	 * For each user-specified option
219	 */
220	while (*(f = opt(&o))) {
221		struct opt *op;
222		enum vs_opt { OldSyn, SelEQ, SelNE, VarAss } vs_opt;
223		char *eq = strchr(f, '=');
224		char *opt;
225		if (!eq || eq[1] == '\0' || eq == f) {
226			/*
227			 * No value, just continue
228			 */
229			plog(XLOG_USER, "key %s: No value component in \"%s\"", mapkey, f);
230			continue;
231		}
232
233		/*
234		 * Check what type of operation is happening
235		 * !=, =!  is SelNE
236		 * == is SelEQ
237		 * := is VarAss
238		 * = is OldSyn (either SelEQ or VarAss)
239		 */
240		if (eq[-1] == '!') {		/* != */
241			vs_opt = SelNE;
242			eq[-1] = '\0';
243			opt = eq + 1;
244		} else if (eq[-1] == ':') {	/* := */
245			vs_opt = VarAss;
246			eq[-1] = '\0';
247			opt = eq + 1;
248		} else if (eq[1] == '=') {	/* == */
249			vs_opt = SelEQ;
250			eq[0] = '\0';
251			opt = eq + 2;
252		} else if (eq[1] == '!') {	/* =! */
253			vs_opt = SelNE;
254			eq[0] = '\0';
255			opt = eq + 2;
256		} else {			/* = */
257			vs_opt = OldSyn;
258			eq[0] = '\0';
259			opt = eq + 1;
260		}
261
262		/*
263		 * For each recognised option
264		 */
265		for (op = opt_fields; op->name; op++) {
266			/*
267			 * Check whether they match
268			 */
269			if (FSTREQ(op->name, f)) {
270				switch (vs_opt) {
271#if 1	/* XXX ancient compat */
272				case OldSyn:
273					plog(XLOG_WARNING, "key %s: Old syntax selector found: %s=%s", mapkey, f, opt);
274					if (!op->sel_p) {
275						*op->optp = opt;
276						break;
277					}
278					/* fall through ... */
279#endif
280				case SelEQ:
281				case SelNE:
282					if (op->sel_p && (STREQ(*op->sel_p, opt) == (vs_opt == SelNE))) {
283						plog(XLOG_MAP, "key %s: map selector %s (=%s) did not %smatch %s",
284							mapkey,
285							op->name,
286							*op->sel_p,
287							vs_opt == SelNE ? "not " : "",
288							opt);
289						return 0;
290					}
291					break;
292
293				case VarAss:
294					if (op->sel_p) {
295						plog(XLOG_USER, "key %s: Can't assign to a selector (%s)", mapkey, op->name);
296						return 0;
297					}
298					*op->optp = opt;
299					break;
300				}
301				break;
302			}
303		}
304
305		if (!op->name)
306			plog(XLOG_USER, "key %s: Unrecognised key/option \"%s\"", mapkey, f);
307	}
308
309	return 1;
310}
311
312/*
313 * Free an option
314 */
315static void
316free_op(opt_apply *p, int b)
317{
318	if (*p->opt) {
319		free(*p->opt);
320		*p->opt = 0;
321	}
322}
323
324/*
325 * Normalize slashes in the string.
326 */
327void
328normalize_slash(char *p)
329{
330	char *f = strchr(p, '/');
331	char *f0 = f;
332	if (f) {
333		char *t = f;
334		do {
335			/* assert(*f == '/'); */
336			if (f == f0 && f[0] == '/' && f[1] == '/') {
337				/* copy double slash iff first */
338				*t++ = *f++;
339				*t++ = *f++;
340			} else {
341				/* copy a single / across */
342				*t++ = *f++;
343			}
344
345			/* assert(f[-1] == '/'); */
346			/* skip past more /'s */
347			while (*f == '/')
348				f++;
349
350			/* assert(*f != '/'); */
351			/* keep copying up to next / */
352			while (*f && *f != '/') {
353				*t++ = *f++;
354			}
355
356			/* assert(*f == 0 || *f == '/'); */
357
358		} while (*f);
359		*t = 0;			/* derived from fix by Steven Glassman */
360	}
361}
362
363/*
364 * Macro-expand an option.  Note that this does not
365 * handle recursive expansions.  They will go badly wrong.
366 * If sel is true then old expand selectors, otherwise
367 * don't expand selectors.
368 */
369static void
370expand_op(opt_apply *p, int sel_p)
371{
372/*
373 * The BUFSPACE macros checks that there is enough space
374 * left in the expansion buffer.  If there isn't then we
375 * give up completely.  This is done to avoid crashing the
376 * automounter itself (which would be a bad thing to do).
377 */
378#define BUFSPACE(ep, len) (((ep) + (len)) < expbuf+PATH_MAX)
379static char expand_error[] = "No space to expand \"%s\"";
380
381	char expbuf[PATH_MAX+1];
382	char nbuf[NLEN+1];
383	char *ep = expbuf;
384	char *cp = *p->opt;
385	char *dp;
386#ifdef DEBUG
387	char *cp_orig = *p->opt;
388#endif /* DEBUG */
389	struct opt *op;
390
391	while ((dp = strchr(cp, '$'))) {
392		char ch;
393		/*
394		 * First copy up to the $
395		 */
396		{ int len = dp - cp;
397		  if (BUFSPACE(ep, len)) {
398			strncpy(ep, cp, len);
399			ep += len;
400		  } else {
401			plog(XLOG_ERROR, expand_error, *p->opt);
402			goto out;
403		  }
404		}
405		cp = dp + 1;
406		ch = *cp++;
407		if (ch == '$') {
408			if (BUFSPACE(ep, 1)) {
409				*ep++ = '$';
410			} else {
411				plog(XLOG_ERROR, expand_error, *p->opt);
412				goto out;
413			}
414		} else if (ch == '{') {
415			/* Expansion... */
416			enum { E_All, E_Dir, E_File, E_Domain, E_Host } todo;
417			/*
418			 * Find closing brace
419			 */
420			char *br_p = strchr(cp, '}');
421			int len;
422			/*
423			 * Check we found it
424			 */
425			if (!br_p) {
426				/*
427				 * Just give up
428				 */
429				plog(XLOG_USER, "No closing '}' in \"%s\"", *p->opt);
430				goto out;
431			}
432			len = br_p - cp;
433			/*
434			 * Figure out which part of the variable to grab.
435			 */
436			if (*cp == '/') {
437				/*
438				 * Just take the last component
439				 */
440				todo = E_File;
441				cp++;
442				--len;
443			} else if (br_p[-1] == '/') {
444				/*
445				 * Take all but the last component
446				 */
447				todo = E_Dir;
448				--len;
449			} else if (*cp == '.') {
450				/*
451				 * Take domain name
452				 */
453				todo = E_Domain;
454				cp++;
455				--len;
456			} else if (br_p[-1] == '.') {
457				/*
458				 * Take host name
459				 */
460				todo = E_Host;
461				--len;
462			} else {
463				/*
464				 * Take the whole lot
465				 */
466				todo = E_All;
467			}
468			/*
469			 * Truncate if too long.  Since it won't
470			 * match anyway it doesn't matter that
471			 * it has been cut short.
472			 */
473			if (len > NLEN)
474				len = NLEN;
475			/*
476			 * Put the string into another buffer so
477			 * we can do comparisons.
478			 */
479			strncpy(nbuf, cp, len);
480			nbuf[len] = '\0';
481			/*
482			 * Advance cp
483			 */
484			cp = br_p + 1;
485			/*
486			 * Search the option array
487			 */
488			for (op = opt_fields; op->name; op++) {
489				/*
490				 * Check for match
491				 */
492				if (len == op->nlen && STREQ(op->name, nbuf)) {
493					char xbuf[NLEN+3];
494					char *val;
495					/*
496					 * Found expansion.  Copy
497					 * the correct value field.
498					 */
499					if (!(!op->sel_p == !sel_p)) {
500						/*
501						 * Copy the string across unexpanded
502						 */
503						snprintf(xbuf, sizeof(xbuf), "${%s%s%s}",
504							todo == E_File ? "/" :
505								todo == E_Domain ? "." : "",
506							nbuf,
507							todo == E_Dir ? "/" :
508								todo == E_Host ? "." : "");
509						val = xbuf;
510						/*
511						 * Make sure expansion doesn't
512						 * munge the value!
513						 */
514						todo = E_All;
515					} else if (op->sel_p) {
516						val = *op->sel_p;
517					} else {
518						val = *op->optp;
519					}
520					if (val) {
521						/*
522						 * Do expansion:
523						 * ${/var} means take just the last part
524						 * ${var/} means take all but the last part
525						 * ${.var} means take all but first part
526						 * ${var.} means take just the first part
527						 * ${var} means take the whole lot
528						 */
529						int vlen = strlen(val);
530						char *vptr = val;
531						switch (todo) {
532						case E_Dir:
533							vptr = strrchr(val, '/');
534							if (vptr)
535								vlen = vptr - val;
536							vptr = val;
537							break;
538						case E_File:
539							vptr = strrchr(val, '/');
540							if (vptr) {
541								vptr++;
542								vlen = strlen(vptr);
543							} else
544								vptr = val;
545							break;
546						case E_Domain:
547							vptr = strchr(val, '.');
548							if (vptr) {
549								vptr++;
550								vlen = strlen(vptr);
551							} else {
552								vptr = "";
553								vlen = 0;
554							}
555							break;
556						case E_Host:
557							vptr = strchr(val, '.');
558							if (vptr)
559								vlen = vptr - val;
560							vptr = val;
561							break;
562						case E_All:
563							break;
564						}
565#ifdef DEBUG
566					/*dlog("Expanding \"%s\" to \"%s\"", nbuf, val);*/
567#endif /* DEBUG */
568						if (BUFSPACE(ep, vlen)) {
569							strlcpy(ep, vptr, expbuf + sizeof expbuf - ep);
570							ep += strlen(ep);
571						} else {
572							plog(XLOG_ERROR, expand_error, *p->opt);
573							goto out;
574						}
575					}
576					/*
577					 * Done with this variable
578					 */
579					break;
580				}
581			}
582			/*
583			 * Check that the search was successful
584			 */
585			if (!op->name) {
586				/*
587				 * If it wasn't then scan the
588				 * environment for that name
589				 * and use any value found
590				 */
591				char *env = getenv(nbuf);
592				if (env) {
593					int vlen = strlen(env);
594
595					if (BUFSPACE(ep, vlen)) {
596						strlcpy(ep, env, expbuf + sizeof expbuf - ep);
597						ep += strlen(ep);
598					} else {
599						plog(XLOG_ERROR, expand_error, *p->opt);
600						goto out;
601					}
602#ifdef DEBUG
603					Debug(D_STR)
604					plog(XLOG_DEBUG, "Environment gave \"%s\" -> \"%s\"", nbuf, env);
605#endif /* DEBUG */
606				} else {
607					plog(XLOG_USER, "Unknown sequence \"${%s}\"", nbuf);
608				}
609			}
610		} else {
611			/*
612			 * Error, error
613			 */
614			plog(XLOG_USER, "Unknown $ sequence in \"%s\"", *p->opt);
615		}
616	}
617
618out:
619	/*
620	 * Handle common case - no expansion
621	 */
622	if (cp == *p->opt) {
623		*p->opt = strdup(cp);
624	} else {
625		/*
626		 * Finish off the expansion
627		 */
628		if (BUFSPACE(ep, strlen(cp))) {
629			strlcpy(ep, cp, expbuf + sizeof expbuf - ep);
630		} else {
631			plog(XLOG_ERROR, expand_error, *p->opt);
632		}
633
634		/*
635		 * Save the exansion
636		 */
637		*p->opt = strdup(expbuf);
638	}
639
640	normalize_slash(*p->opt);
641
642#ifdef DEBUG
643	Debug(D_STR) {
644		plog(XLOG_DEBUG, "Expansion of \"%s\"...", cp_orig);
645		plog(XLOG_DEBUG, "... is \"%s\"", *p->opt);
646	}
647#endif /* DEBUG */
648}
649
650/*
651 * Wrapper for expand_op
652 */
653static void
654expand_opts(opt_apply *p, int sel_p)
655{
656	if (*p->opt) {
657		expand_op(p, sel_p);
658	} else if (p->val) {
659		/*
660		 * Do double expansion, remembering
661		 * to free the string from the first
662		 * expansion...
663		 */
664		char *s = *p->opt = expand_key(p->val);
665		expand_op(p, sel_p);
666		free(s);
667	}
668}
669
670/*
671 * Apply a function to a list of options
672 */
673static void
674apply_opts(void (*op)(), opt_apply ppp[], int b)
675{
676	opt_apply *pp;
677	for (pp = ppp; pp->opt; pp++)
678		(*op)(pp, b);
679}
680
681/*
682 * Free the option table
683 */
684void
685free_opts(am_opts *fo)
686{
687	/*
688	 * Copy in the structure we are playing with
689	 */
690	fs_static = *fo;
691
692	/*
693	 * Free previously allocated memory
694	 */
695	apply_opts(free_op, to_free, FALSE);
696}
697
698/*
699 * Expand lookup key
700 */
701char *
702expand_key(char *key)
703{
704	opt_apply oa;
705
706	oa.opt = &key; oa.val = 0;
707	expand_opts(&oa, TRUE);
708
709	return key;
710}
711
712/*
713 * Remove trailing /'s from a string
714 * unless the string is a single / (Steven Glassman)
715 */
716void
717deslashify(char *s)
718{
719	if (s && *s) {
720		char *sl = s + strlen(s);
721		while (*--sl == '/' && sl > s)
722			*sl = '\0';
723	}
724}
725
726int
727eval_fs_opts(am_opts *fo, char *opts, char *g_opts, char *path,
728    char *key, char *map)
729{
730	int ok = TRUE;
731
732	free_opts(fo);
733
734	/*
735	 * Clear out the option table
736	 */
737	bzero(&fs_static, sizeof(fs_static));
738	bzero(vars, sizeof(vars));
739	bzero(fo, sizeof(*fo));
740
741	/*
742	 * Set key, map & path before expansion
743	 */
744	opt_key = key;
745	opt_map = map;
746	opt_path = path;
747
748	/*
749	 * Expand global options
750	 */
751	fs_static.fs_glob = expand_key(g_opts);
752
753	/*
754	 * Expand local options
755	 */
756	fs_static.fs_local = expand_key(opts);
757
758	/*
759	 * Expand default (global) options
760	 */
761	if (!eval_opts(fs_static.fs_glob, key))
762		ok = FALSE;
763
764	/*
765	 * Expand local options
766	 */
767	if (ok && !eval_opts(fs_static.fs_local, key))
768		ok = FALSE;
769
770	/*
771	 * Normalise remote host name.
772	 * 1.  Expand variables
773	 * 2.  Normalize relative to host tables
774	 * 3.  Strip local domains from the remote host
775	 *     name before using it in other expansions.
776	 *     This makes mount point names and other things
777	 *     much shorter, while allowing cross domain
778	 *     sharing of mount maps.
779	 */
780	apply_opts(expand_opts, rhost_expansion, FALSE);
781	if (ok && fs_static.opt_rhost && *fs_static.opt_rhost)
782		host_normalize(&fs_static.opt_rhost);
783
784	/*
785	 * Macro expand the options.
786	 * Do this regardless of whether we are accepting
787	 * this mount - otherwise nasty things happen
788	 * with memory allocation.
789	 */
790	apply_opts(expand_opts, expansions, FALSE);
791
792	/*
793	 * Strip trailing slashes from local pathname...
794	 */
795	deslashify(fs_static.opt_fs);
796
797	/*
798	 * ok... copy the data back out.
799	 */
800	*fo = fs_static;
801
802	/*
803	 * Clear defined options
804	 */
805	opt_key = opt_map = opt_path = nullstr;
806
807	return ok;
808}
809