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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28/*	  All Rights Reserved  	*/
29
30#pragma ident	"%Z%%M%	%I%	%E% SMI"
31
32#include "mt.h"
33#include <stdio.h>
34#include <string.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <fcntl.h>
38#include <ulimit.h>
39#include <wait.h>
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <stropts.h>
43#include <ctype.h>
44#include <sys/conf.h>
45#include <errno.h>
46#include <signal.h>
47#include "sac.h"
48
49#define	COMMENT	'#'
50#define	NOWAIT	0
51#define	WAIT	1
52
53static char	*eatwhite(char *);
54static int	doassign(char *);
55static int	dopush(int, char *);
56static int	dopop(int, char *);
57static int	dorun(char *, int);
58
59/*
60 * doconfig - the configuration script interpreter, if all is ok,
61 *	      return 0.  If there is a "system" error, return -1.
62 *	      If there is an error performing a command, or there
63 *	      is a syntax error, return the line number in error.
64 *
65 *	args:	fd - file descriptor to push and pop from
66 *		script - name of the configuration script
67 *		rflag - restriction flag to determine what "commands"
68 *			can be run
69 */
70
71int
72doconfig(int fd, char *script, long rflag)
73{
74	int line;		/* line counter */
75	struct stat statbuf;	/* place for stat */
76	FILE *fp;		/* file pointer for config script */
77	char buf[BUFSIZ + 1];	/* scratch buffer */
78	char *bp;		/* scratch pointer */
79	char *p;		/* scratch pointer */
80
81	/* if the script does not exist, then there is nothing to do */
82	if (stat(script, &statbuf) < 0)
83		return (0);
84
85	fp = fopen(script, "rF");
86	if (fp == NULL)
87		return (-1);
88
89	line = 0;
90	while (fgets(buf, BUFSIZ, fp)) {
91		line++;
92		p = strchr(buf, '\n');
93		/* if no \n, then line is too long */
94		if (p == NULL) {
95			(void) fclose(fp);
96			return (line);
97		}
98		*p = '\0';
99
100		/* remove comments */
101		p = strchr(buf, COMMENT);
102		if (p)
103			*p = '\0';
104
105		/* remove leading whitespace */
106		bp = eatwhite(buf);
107		/* see if anything is left */
108		if (*bp == '\0')
109			continue;
110
111		/* remove trailing whitespace */
112		p = &buf[strlen(buf) - 1];
113		while (*p && isspace(*p))
114			*p-- = '\0';
115
116		/* get the command */
117		p = bp;
118		while (*p && !isspace(*p))
119			p++;
120		if (*p)
121			*p++ = '\0';
122		/* skip any whitespace here too (between command and args) */
123		p = eatwhite(p);
124
125		if (strcmp(bp, "assign") == 0) {
126			if ((rflag & NOASSIGN) || doassign(p)) {
127				(void) fclose(fp);
128				return (line);
129			}
130		} else if (strcmp(bp, "push") == 0) {
131			if (dopush(fd, p)) {
132				(void) fclose(fp);
133				return (line);
134			}
135		} else if (strcmp(bp, "pop") == 0) {
136			if (dopop(fd, p)) {
137				(void) fclose(fp);
138				return (line);
139			}
140		} else if (strcmp(bp, "run") == 0) {
141			if ((rflag & NORUN) || dorun(p, NOWAIT)) {
142				(void) fclose(fp);
143				return (line);
144			}
145		} else if (strcmp(bp, "runwait") == 0) {
146			if ((rflag & NORUN) || dorun(p, WAIT)) {
147				(void) fclose(fp);
148				return (line);
149			}
150		} else {
151			/* unknown command */
152			(void) fclose(fp);
153			return (line);
154		}
155	}
156	if (!feof(fp)) {
157		(void) fclose(fp);
158		return (-1);
159	}
160	(void) fclose(fp);
161	return (0);
162}
163
164
165/*
166 * doassign - handle an `assign' command
167 *
168 *	args:	p - assignment string
169 */
170
171
172static int
173doassign(char *p)
174{
175	char *var;		/* environment variable to be assigned */
176	char val[BUFSIZ];	/* and the value to be assigned to it */
177	char scratch[BUFSIZ];	/* scratch buffer */
178	char delim;		/* delimiter char seen (for quoted strings ) */
179	char *tp;		/* scratch pointer */
180
181	if (*p == '\0')
182		return (-1);
183	var = p;
184	/* skip first token, but stop if we see a '=' */
185	while (*p && !isspace(*p) && (*p != '='))
186		p++;
187
188	/* if we found end of string, it's an error */
189	if (*p == '\0')
190		return (-1);
191
192	/* if we found a space, look for the '=', otherwise it's an error */
193	if (isspace(*p)) {
194		*p++ = '\0';
195		while (*p && isspace(*p))
196			p++;
197		if (*p == '\0')
198			return (-1);
199		if (*p == '=')
200			p++;
201		else
202			return (-1);
203	} else {
204		/* skip over '=' */
205		*p = '\0';
206		p++;
207	}
208
209	/* skip over any whitespace */
210	p = eatwhite(p);
211	if (*p == '\'' || *p == '"') {
212		/* handle quoted values */
213		delim = *p++;
214		tp = val;
215		for (;;) {
216			if (*p == '\0') {
217				return (-1);
218			} else if (*p == delim) {
219				if (*(p - 1) != '\\')
220					break;
221				else
222					*(tp - 1) = *p++;
223			} else
224				*tp++ = *p++;
225		}
226		*tp = '\0';
227		/*
228		 * these assignments make the comment below true
229		 * (values of tp and p
230		 */
231		tp = ++p;
232		p = val;
233	} else {
234		tp = p;
235		/* look for end of token */
236		while (*tp && !isspace(*tp))
237			tp++;
238	}
239
240/*
241 * at this point, p points to the value, and tp points to the
242 * end of the token.  check to make sure there is no garbage on
243 * the end of the line
244 */
245
246	if (*tp)
247		return (-1);
248	(void) snprintf(scratch, sizeof (scratch), "%s=%s", var, p);
249	/* note: need to malloc fresh space so putenv works */
250	tp = malloc(strlen(scratch) + 1);
251	if (tp == NULL)
252		return (-1);
253	(void) strcpy(tp, scratch);
254	if (putenv(tp))
255		return (-1);
256	return (0);
257}
258
259
260/*
261 * dopush - handle a `push' command
262 *
263 *	args:	fd - file descriptor to push on
264 *		p - list of modules to push
265 */
266
267
268static int
269dopush(int fd, char *p)
270{
271	char *tp;	/* scratch pointer */
272	int i;		/* scratch variable */
273	int npush;	/* count # of modules pushed */
274
275	if (*p == '\0')
276		return (-1);
277	npush = 0;
278	for (;;) {
279		if (*p == '\0')		/* found end of line */
280			return (0);
281		p = eatwhite(p);
282		if (*p == '\0')
283			return (-1);
284		tp = p;
285		while (*tp && !isspace(*tp) && (*tp != ','))
286			tp++;
287		if (*tp)
288			*tp++ = '\0';
289		if (ioctl(fd, I_PUSH, p) < 0) {
290
291/*
292 * try to pop all that we've done, if pop fails it doesn't matter because
293 * nothing can be done anyhow
294 */
295
296			for (i = 0; i < npush; ++i)
297				(void) ioctl(fd, I_POP, 0);
298			return (-1);
299		}
300		/* count the number of modules we've pushed */
301		npush++;
302		p = tp;
303	}
304}
305
306
307/*
308 * dopop - handle a `pop' command
309 *
310 *	args:	fd - file descriptor to pop from
311 *		p - name of module to pop to or ALL (null means pop top only)
312 */
313
314
315static int
316dopop(int fd, char *p)
317{
318	char *modp;		/* module name from argument to pop */
319	char buf[FMNAMESZ + 1];	/* scratch buffer */
320
321	if (*p == '\0') {
322		/* just a pop with no args */
323		if (ioctl(fd, I_POP, 0) < 0)
324			return (-1);
325		return (0);
326	}
327
328	/* skip any whitespace in between */
329	p = eatwhite(p);
330	modp = p;
331	/* find end of module name */
332	while (*p && !isspace(*p))
333		p++;
334
335	if (*p)		/* if not end of line, extra junk on line */
336		return (-1);
337	if (strcmp(modp, "ALL") == 0) {
338		/* it's the magic name, pop them all */
339		while (ioctl(fd, I_POP, 0) == 0)
340			;
341		/* After all popped, we'll get an EINVAL, which is expected */
342		if (errno != EINVAL)
343			return (-1);
344		return (0);
345	}
346	/* check to see if the named module is on the stream */
347	if (ioctl(fd, I_FIND, modp) != 1)
348		return (-1);
349
350	/* pop them until the right one is on top */
351	for (;;) {
352		if (ioctl(fd, I_LOOK, buf) < 0)
353			return (-1);
354		if (strcmp(modp, buf) == 0)
355			/* we're done */
356			return (0);
357		if (ioctl(fd, I_POP, 0) < 0)
358			return (-1);
359	}
360	/* NOTREACHED */
361}
362
363
364/*
365 * dorun - handle a `run' command
366 *
367 *	args:	p - command line to run
368 *		waitflag - flag indicating whether a wait should be done
369 */
370
371
372static int
373dorun(char *p, int waitflg)
374{
375	char *tp;		/* scratch pointer */
376	char *ep;		/* scratch pointer (end of token) */
377	char savech;		/* hold area */
378	int status;		/* return status from wait */
379	pid_t pid;		/* pid of child proc */
380	pid_t rpid;		/* returned pid from wait */
381	void (*func)();		/* return from signal */
382
383	if (*p == '\0')
384		return (-1);
385
386	/*
387	 * get first token
388	 */
389
390	for (tp = p; *tp && !isspace(*tp); ++tp)
391		;
392	savech = '\0';
393	if (*tp) {
394		savech = *tp;
395		*tp = '\0';
396	}
397
398	/*
399	 * look for built-in's
400	 */
401
402	if (strcmp(p, "cd") == 0) {
403		*tp = savech;
404		tp = eatwhite(tp);
405		if (*tp == '\0')
406			/* if nothing there, try to cd to $HOME */
407			tp = getenv("HOME");
408		if (chdir(tp) < 0)
409			return (-1);
410	} else if (strcmp(p, "ulimit") == 0) {
411		*tp = savech;
412		tp = eatwhite(tp);
413		/* must have an argument */
414		if (*tp == '\0')
415			return (-1);
416		/* make sure nothing appears on line after arg */
417		for (ep = tp; *ep && !isspace(*ep); ++ep)
418			;
419		ep = eatwhite(ep);
420		if (*ep)
421			return (-1);
422		if (!isdigit(*tp))
423			return (-1);
424
425		if (ulimit(2, atoi(tp)) < 0)
426			return (-1);
427	} else if (strcmp(p, "umask") == 0) {
428		*tp = savech;
429		tp = eatwhite(tp);
430		/* must have an argument */
431		if (*tp == '\0')
432			return (-1);
433		/* make sure nothing appears on line after arg */
434		for (ep = tp; *ep && !isspace(*ep); ++ep)
435			;
436		ep = eatwhite(ep);
437		if (*ep)
438			return (-1);
439		if (!isdigit(*tp))
440			return (-1);
441		(void) umask(strtol(tp, NULL, 8));
442	} else {
443		/* not a built-in */
444		*tp = savech;
445		func = signal(SIGCLD, SIG_DFL);
446		if ((pid = fork()) < 0) {
447			(void) signal(SIGCLD, func);
448			return (-1);
449		}
450		if (pid) {
451			if (waitflg == WAIT) {
452				status = 0;
453				rpid = -1;
454				while (rpid != pid)
455					rpid = wait(&status);
456				if (status) {
457					/* child failed */
458					(void) signal(SIGCLD, func);
459					return (-1);
460				}
461			}
462			(void) signal(SIGCLD, func);
463		} else {
464			/* set IFS for security */
465			(void) putenv("IFS=\" \"");
466			/*
467			 * need to close all files to prevent unauthorized
468			 * access in the children.  Setup stdin, stdout,
469			 * and stderr to /dev/null.
470			 */
471			closefrom(0);
472			/* stdin */
473			if (open("/dev/null", O_RDWR) != 0)
474				return (-1);
475			/* stdout */
476			if (dup(0) != 1)
477				return (-1);
478			/* stderr */
479			if (dup(0) != 2)
480				return (-1);
481			(void) execl("/usr/bin/sh", "sh", "-c", p, NULL);
482			/*
483			 * if we get here, there is a problem - remember that
484			 * this is the child
485			 */
486			exit(1);
487		}
488	}
489	return (0);
490}
491
492
493/*
494 * eatwhite - swallow any leading whitespace, return pointer to first
495 *	      non-white space character or to terminating null character
496 *	      if nothing else is there
497 *
498 *	args:	p - string to parse
499 */
500
501static char *
502eatwhite(char *p)
503{
504	while (*p && isspace(*p))
505		p++;
506	return (p);
507}
508