xref: /illumos-gate/usr/src/cmd/sh/main.c (revision 03831d35)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 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 
31 
32 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 /*
35  * UNIX shell
36  */
37 
38 #include	"defs.h"
39 #include	"sym.h"
40 #include	"timeout.h"
41 #include	<stdio.h>
42 #include	<sys/types.h>
43 #include	<sys/stat.h>
44 #include	<sys/wait.h>
45 #include	"dup.h"
46 #include	"sh_policy.h"
47 
48 #ifdef RES
49 #include	<sgtty.h>
50 #endif
51 
52 pid_t mypid, mypgid, mysid;
53 
54 static BOOL	beenhere = FALSE;
55 unsigned char	tmpout[TMPOUTSZ];
56 struct fileblk	stdfile;
57 struct fileblk *standin = &stdfile;
58 int mailchk = 0;
59 
60 static unsigned char	*mailp;
61 static long	*mod_time = 0;
62 static BOOL login_shell = FALSE;
63 
64 #if vax
65 char **execargs = (char **)(0x7ffffffc);
66 #endif
67 
68 #if pdp11
69 char **execargs = (char **)(-2);
70 #endif
71 
72 
73 static void	exfile();
74 extern unsigned char 	*simple();
75 static void Ldup(int, int);
76 void settmp(void);
77 void chkmail(void);
78 void setmail(unsigned char *);
79 
80 int
81 main(int c, char *v[], char *e[])
82 {
83 	int		rflag = ttyflg;
84 	int		rsflag = 1;	/* local restricted flag */
85 	unsigned char	*flagc = flagadr;
86 	struct namnod	*n;
87 
88 	mypid = getpid();
89 	mypgid = getpgid(mypid);
90 	mysid = getsid(mypid);
91 
92 	/*
93 	 * Do locale processing only if /usr is mounted.
94 	 */
95 	localedir_exists = (access(localedir, F_OK) == 0);
96 
97 	/*
98 	 * initialize storage allocation
99 	 */
100 
101 	if (stakbot == 0) {
102 	addblok((unsigned)0);
103 	}
104 
105 	/*
106 	 * If the first character of the last path element of v[0] is "-"
107 	 * (ex. -sh, or /bin/-sh), this is a login shell
108 	 */
109 	if (*simple(v[0]) == '-') {
110 		signal(SIGXCPU, SIG_DFL);
111 		signal(SIGXFSZ, SIG_DFL);
112 
113 		/*
114 		 * As the previous comment states, this is a login shell.
115 		 * Therefore, we set the login_shell flag to explicitly
116 		 * indicate this condition.
117 		 */
118 		login_shell = TRUE;
119 	}
120 
121 	stdsigs();
122 
123 	/*
124 	 * set names from userenv
125 	 */
126 
127 	setup_env();
128 
129 	/*
130 	 * LC_MESSAGES is set here so that early error messages will
131 	 * come out in the right style.
132 	 * Note that LC_CTYPE is done later on and is *not*
133 	 * taken from the previous environ
134 	 */
135 
136 	/*
137 	 * Do locale processing only if /usr is mounted.
138 	 */
139 	if (localedir_exists)
140 		(void) setlocale(LC_ALL, "");
141 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
142 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
143 #endif
144 	(void) textdomain(TEXT_DOMAIN);
145 
146 	/*
147 	 * This is a profile shell if the simple name of argv[0] is
148 	 * pfsh or -pfsh
149 	 */
150 	if (c > 0 && (eq("pfsh", simple(*v)) || eq("-pfsh", simple(*v)))) {
151 		flags |= pfshflg;
152 		secpolicy_init();
153 	}
154 
155 	/*
156 	 * 'rsflag' is zero if SHELL variable is
157 	 *  set in environment and
158 	 *  the simple file part of the value.
159 	 *  is rsh
160 	 */
161 	if (n = findnam("SHELL")) {
162 		if (eq("rsh", simple(n->namval)))
163 			rsflag = 0;
164 	}
165 
166 	/*
167 	 * a shell is also restricted if the simple name of argv(0) is
168 	 * rsh or -rsh in its simple name
169 	 */
170 
171 #ifndef RES
172 
173 	if (c > 0 && (eq("rsh", simple(*v)) || eq("-rsh", simple(*v))))
174 		rflag = 0;
175 
176 #endif
177 
178 	if (eq("jsh", simple(*v)) || eq("-jsh", simple(*v)))
179 		flags |= monitorflg;
180 
181 	hcreate();
182 	set_dotpath();
183 
184 
185 	/*
186 	 * look for options
187 	 * dolc is $#
188 	 */
189 	dolc = options(c, v);
190 
191 	if (dolc < 2) {
192 		flags |= stdflg;
193 		{
194 
195 			while (*flagc)
196 				flagc++;
197 			*flagc++ = STDFLG;
198 			*flagc = 0;
199 		}
200 	}
201 	if ((flags & stdflg) == 0)
202 		dolc--;
203 
204 	if ((flags & privflg) == 0) {
205 		uid_t euid;
206 		gid_t egid;
207 		uid_t ruid;
208 		gid_t rgid;
209 
210 		/*
211 		 * Determine all of the user's id #'s for this process and
212 		 * then decide if this shell is being entered as a result
213 		 * of a fork/exec.
214 		 * If the effective uid/gid do NOT match and the euid/egid
215 		 * is < 100 and the egid is NOT 1, reset the uid and gid to
216 		 * the user originally calling this process.
217 		 */
218 		euid = geteuid();
219 		ruid = getuid();
220 		egid = getegid();
221 		rgid = getgid();
222 		if ((euid != ruid) && (euid < 100))
223 			setuid(ruid);   /* reset the uid to the orig user */
224 		if ((egid != rgid) && ((egid < 100) && (egid != 1)))
225 			setgid(rgid);   /* reset the gid to the orig user */
226 	}
227 
228 	dolv = (unsigned char **)v + c - dolc;
229 	dolc--;
230 
231 	/*
232 	 * return here for shell file execution
233 	 * but not for parenthesis subshells
234 	 */
235 	if (setjmp(subshell)) {
236 		freejobs();
237 		flags |= subsh;
238 	}
239 
240 	/*
241 	 * number of positional parameters
242 	 */
243 	replace(&cmdadr, dolv[0]);	/* cmdadr is $0 */
244 
245 	/*
246 	 * set pidname '$$'
247 	 */
248 	assnum(&pidadr, (long)mypid);
249 
250 	/*
251 	 * set up temp file names
252 	 */
253 	settmp();
254 
255 	/*
256 	 * default internal field separators
257 	 * Do not allow importing of IFS from parent shell.
258 	 * setup_env() may have set anything from parent shell to IFS.
259 	 * Always set the default ifs to IFS.
260 	 */
261 	assign(&ifsnod, (unsigned char *)sptbnl);
262 
263 	dfault(&mchknod, MAILCHECK);
264 	mailchk = stoi(mchknod.namval);
265 
266 	/* initialize OPTIND for getopt */
267 
268 	n = lookup("OPTIND");
269 	assign(n, (unsigned char *)"1");
270 	/*
271 	 * make sure that option parsing starts
272 	 * at first character
273 	 */
274 	_sp = 1;
275 
276 	/* initialize multibyte information */
277 	setwidth();
278 
279 	if ((beenhere++) == FALSE)	/* ? profile */
280 	{
281 		if ((login_shell == TRUE) && (flags & privflg) == 0) {
282 
283 			/* system profile */
284 
285 #ifndef RES
286 
287 			if ((input = pathopen(nullstr, sysprofile)) >= 0)
288 				exfile(rflag);		/* file exists */
289 
290 #endif
291 			/* user profile */
292 
293 			if ((input = pathopen(homenod.namval, profile)) >= 0) {
294 				exfile(rflag);
295 				flags &= ~ttyflg;
296 			}
297 		}
298 		if (rsflag == 0 || rflag == 0) {
299 			if ((flags & rshflg) == 0) {
300 				while (*flagc)
301 					flagc++;
302 				*flagc++ = 'r';
303 				*flagc = '\0';
304 			}
305 			flags |= rshflg;
306 		}
307 
308 		/*
309 		 * open input file if specified
310 		 */
311 		if (comdiv) {
312 			estabf(comdiv);
313 			input = -1;
314 		}
315 		else
316 		{
317 			if (flags & stdflg) {
318 				input = 0;
319 			} else {
320 			/*
321 			 * If the command file specified by 'cmdadr'
322 			 * doesn't exist, chkopen() will fail calling
323 			 * exitsh(). If this is a login shell and
324 			 * the $HOME/.profile file does not exist, the
325 			 * above statement "flags &= ~ttyflg" does not
326 			 * get executed and this makes exitsh() call
327 			 * longjmp() instead of exiting. longjmp() will
328 			 * return to the location specified by the last
329 			 * active jmpbuffer, which is the one set up in
330 			 * the function exfile() called after the system
331 			 * profile file is executed (see lines above).
332 			 * This would cause an infinite loop, because
333 			 * chkopen() will continue to fail and exitsh()
334 			 * to call longjmp(). To make exitsh() exit instead
335 			 * of calling longjmp(), we then set the flag forcexit
336 			 * at this stage.
337 			 */
338 
339 				flags |= forcexit;
340 				input = chkopen(cmdadr, 0);
341 				flags &= ~forcexit;
342 			}
343 
344 #ifdef ACCT
345 			if (input != 0)
346 				preacct(cmdadr);
347 #endif
348 			comdiv--;
349 		}
350 	}
351 #ifdef pdp11
352 	else
353 		*execargs = (char *)dolv;	/* for `ps' cmd */
354 #endif
355 
356 
357 	exfile(0);
358 	done(0);
359 }
360 
361 static void
362 exfile(int prof)
363 {
364 	time_t	mailtime = 0;	/* Must not be a register variable */
365 	time_t 	curtime = 0;
366 
367 	/*
368 	 * move input
369 	 */
370 	if (input > 0) {
371 		Ldup(input, INIO);
372 		input = INIO;
373 	}
374 
375 
376 	setmode(prof);
377 
378 	if (setjmp(errshell) && prof) {
379 		close(input);
380 		(void) endjobs(0);
381 		return;
382 	}
383 	/*
384 	 * error return here
385 	 */
386 
387 	loopcnt = peekc = peekn = 0;
388 	fndef = 0;
389 	nohash = 0;
390 	iopend = 0;
391 
392 	if (input >= 0)
393 		initf(input);
394 	/*
395 	 * command loop
396 	 */
397 	for (;;) {
398 		tdystak(0);
399 		stakchk();	/* may reduce sbrk */
400 		exitset();
401 
402 		if ((flags & prompt) && standin->fstak == 0 && !eof) {
403 
404 			if (mailp) {
405 				time(&curtime);
406 
407 				if ((curtime - mailtime) >= mailchk) {
408 					chkmail();
409 					mailtime = curtime;
410 				}
411 			}
412 
413 			/* necessary to print jobs in a timely manner */
414 			if (trapnote & TRAPSET)
415 				chktrap();
416 
417 			prs(ps1nod.namval);
418 
419 #ifdef TIME_OUT
420 			alarm(TIMEOUT);
421 #endif
422 
423 		}
424 
425 		trapnote = 0;
426 		peekc = readwc();
427 		if (eof) {
428 			if (endjobs(JOB_STOPPED))
429 				return;
430 			eof = 0;
431 		}
432 
433 #ifdef TIME_OUT
434 		alarm(0);
435 #endif
436 
437 		{
438 			struct trenod *t;
439 			t = cmd(NL, MTFLG);
440 			if (t == NULL && flags & ttyflg)
441 				freejobs();
442 			else
443 				execute(t, 0, eflag);
444 		}
445 
446 		eof |= (flags & oneflg);
447 
448 	}
449 }
450 
451 void
452 chkpr(void)
453 {
454 	if ((flags & prompt) && standin->fstak == 0)
455 		prs(ps2nod.namval);
456 }
457 
458 void
459 settmp(void)
460 {
461 	int len;
462 	serial = 0;
463 	if ((len = snprintf((char *)tmpout, TMPOUTSZ, "/tmp/sh%u", mypid)) >=
464 	    TMPOUTSZ) {
465 		/*
466 		 * TMPOUTSZ should be big enough, but if it isn't,
467 		 * we'll at least try to create tmp files with
468 		 * a truncated tmpfile name at tmpout.
469 		 */
470 		tmpout_offset = TMPOUTSZ - 1;
471 	} else {
472 		tmpout_offset = len;
473 	}
474 }
475 
476 static void
477 Ldup(int fa, int fb)
478 {
479 #ifdef RES
480 
481 	dup(fa | DUPFLG, fb);
482 	close(fa);
483 	ioctl(fb, FIOCLEX, 0);
484 
485 #else
486 
487 	if (fa >= 0) {
488 		if (fa != fb) {
489 			close(fb);
490 			fcntl(fa, 0, fb); /* normal dup */
491 			close(fa);
492 		}
493 		fcntl(fb, 2, 1);	/* autoclose for fb */
494 	}
495 
496 #endif
497 }
498 
499 void
500 chkmail(void)
501 {
502 	unsigned char 	*s = mailp;
503 	unsigned char	*save;
504 
505 	long	*ptr = mod_time;
506 	unsigned char	*start;
507 	BOOL	flg;
508 	struct stat	statb;
509 
510 	while (*s) {
511 		start = s;
512 		save = 0;
513 		flg = 0;
514 
515 		while (*s) {
516 			if (*s != COLON) {
517 				if (*s == '%' && save == 0)
518 					save = s;
519 
520 				s++;
521 			} else {
522 				flg = 1;
523 				*s = 0;
524 			}
525 		}
526 
527 		if (save)
528 			*save = 0;
529 
530 		if (*start && stat((const char *)start, &statb) >= 0) {
531 			if (statb.st_size && *ptr &&
532 			    statb.st_mtime != *ptr) {
533 				if (save) {
534 					prs(save+1);
535 					newline();
536 				}
537 				else
538 					prs(mailmsg);
539 			}
540 			*ptr = statb.st_mtime;
541 		} else if (*ptr == 0)
542 			*ptr = 1;
543 
544 		if (save)
545 			*save = '%';
546 
547 		if (flg)
548 			*s++ = COLON;
549 
550 		ptr++;
551 	}
552 }
553 
554 void
555 setmail(unsigned char *mailpath)
556 {
557 	unsigned char	*s = mailpath;
558 	int 		cnt = 1;
559 
560 	long	*ptr;
561 
562 	free(mod_time);
563 	if (mailp = mailpath) {
564 		while (*s) {
565 			if (*s == COLON)
566 				cnt += 1;
567 
568 			s++;
569 		}
570 
571 		ptr = mod_time = (long *)alloc(sizeof (long) * cnt);
572 
573 		while (cnt) {
574 			*ptr = 0;
575 			ptr++;
576 			cnt--;
577 		}
578 	}
579 }
580 
581 void
582 setwidth()
583 {
584 	unsigned char *name = lookup("LC_CTYPE")->namval;
585 	if (!name || !*name)
586 		name = lookup("LANG")->namval;
587 	/*
588 	 * Do locale processing only if /usr is mounted.
589 	 */
590 	if (localedir_exists) {
591 		if (!name || !*name)
592 			(void) setlocale(LC_CTYPE, "C");
593 		else
594 			(void) setlocale(LC_CTYPE, (const char *)name);
595 	}
596 }
597 
598 void
599 setmode(int prof)
600 {
601 	/*
602 	 * decide whether interactive
603 	 */
604 
605 	if ((flags & intflg) ||
606 	    ((flags&oneflg) == 0 &&
607 	    isatty(output) &&
608 	    isatty(input)))
609 
610 	{
611 		dfault(&ps1nod, (geteuid() ? stdprompt : supprompt));
612 		dfault(&ps2nod, readmsg);
613 		flags |= ttyflg | prompt;
614 		if (mailpnod.namflg != N_DEFAULT)
615 			setmail(mailpnod.namval);
616 		else
617 			setmail(mailnod.namval);
618 		startjobs();
619 	}
620 	else
621 	{
622 		flags |= prof;
623 		flags &= ~prompt;
624 	}
625 }
626 
627 /*
628  * A generic call back routine to output error messages from the
629  * policy backing functions called by pfsh.
630  *
631  * msg must contain '\n' if a new line is to be printed.
632  */
633 void
634 secpolicy_print(int level, const char *msg)
635 {
636 	switch (level) {
637 	case SECPOLICY_WARN:
638 	default:
639 		prs(msg);	/* prs() does gettext() */
640 		return;
641 	case SECPOLICY_ERROR:
642 		error(msg);
643 		break;
644 	}
645 }
646