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