xref: /illumos-gate/usr/src/cmd/ttymon/tmchild.c (revision 02ac56e010f18fc0c5aafe47377586d8ba8c897c)
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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved	*/
28 
29 #include	<stdio.h>
30 #include	<stdlib.h>
31 #include	<fcntl.h>
32 #include	<errno.h>
33 #include	<sys/types.h>
34 #include	<termio.h>
35 #include	<string.h>
36 #include	<signal.h>
37 #include	<poll.h>
38 #include	<unistd.h>
39 #include	"sys/stropts.h"
40 #include	<sys/resource.h>
41 #include	"sac.h"
42 #include	"ttymon.h"
43 #include	"tmstruct.h"
44 #include	"tmextern.h"
45 #ifdef	SYS_NAME
46 #include	<sys/utsname.h>
47 #endif
48 
49 static void openline(struct pmtab *, struct Gdef *);
50 static void invoke_service(struct pmtab *);
51 static char	*do_autobaud(struct pmtab *, struct Gdef *);
52 static	struct	Gdef	*next_speed(struct Gdef *);
53 static int check_hup(int);
54 
55 /*
56  * tmchild	- process that handles peeking data, determine baud rate
57  *		  and invoke service on each individual port.
58  *
59  */
60 void
61 tmchild(struct pmtab *pmtab)
62 {
63 	struct Gdef *speedef;
64 	char	*auto_speed = "";
65 	struct	sigaction sigact;
66 
67 #ifdef	DEBUG
68 	debug("in tmchild");
69 #endif
70 	peek_ptr = NULL;
71 	if (pmtab->p_status != GETTY) {
72 		child_sigcatch();
73 		(void) close(PCpipe[0]); /* close parent end of the pipe */
74 		if (ioctl(PCpipe[1], I_SETSIG, S_HANGUP) == -1) {
75 			log("I_SETSIG failed: %s", strerror(errno));
76 			exit(1);
77 		}
78 		/*
79 		 * the following check is to make sure no hangup
80 		 * happens before registering for SIGPOLL
81 		 */
82 		if (check_hup(PCpipe[1])) {
83 #ifdef	DEBUG
84 			debug("PCpipe hungup, tmchild exiting");
85 #endif
86 			exit(1);
87 		}
88 
89 		if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
90 			if (pmtab->p_fd > 0) {
91 				(void) close(pmtab->p_fd);
92 				pmtab->p_fd = 0;
93 			}
94 		}
95 
96 		/*
97 		 * become the session leader so that a controlling tty
98 		 * will be allocated.
99 		 */
100 		(void) setsid();
101 	}
102 	speedef = get_speed(pmtab);
103 	openline(pmtab, speedef);
104 	if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
105 		if (pmtab->p_fd >= 0) {
106 			if ((pmtab->p_modules != NULL) &&
107 			    (*(pmtab->p_modules) != '\0')) {
108 				if (push_linedisc(pmtab->p_fd,
109 				    pmtab->p_modules, pmtab->p_device) == -1) {
110 					(void) close(pmtab->p_fd);
111 					return;
112 				}
113 			}
114 		}
115 	}
116 	if ((pmtab->p_ttyflags & C_FLAG) &&
117 	    (State != PM_DISABLED) &&
118 	    (!(pmtab->p_flags & X_FLAG))) {
119 		/*
120 		 * if "c" flag is set, and the port is not disabled
121 		 * invoke service immediately
122 		 */
123 		if (set_termio(0, speedef->g_fflags, NULL, FALSE,
124 		    CANON) == -1) {
125 			log("set final termio failed");
126 			exit(1);
127 		}
128 		invoke_service(pmtab);
129 		exit(1);	/*NOTREACHED*/
130 	}
131 	if (speedef->g_autobaud & A_FLAG) {
132 		auto_speed = do_autobaud(pmtab, speedef);
133 	}
134 	if (set_termio(0, speedef->g_fflags, NULL, FALSE, CANON) == -1) {
135 		log("set final termio failed");
136 		exit(1);
137 	}
138 	if ((pmtab->p_ttyflags & (R_FLAG|A_FLAG)) ||
139 	    (pmtab->p_status == GETTY) || (pmtab->p_timeout > 0)) {
140 		write_prompt(1, pmtab, TRUE, TRUE);
141 		if (pmtab->p_timeout) {
142 			sigact.sa_flags = 0;
143 			sigact.sa_handler = timedout;
144 			(void) sigemptyset(&sigact.sa_mask);
145 			(void) sigaction(SIGALRM, &sigact, NULL);
146 			(void) alarm((unsigned)pmtab->p_timeout);
147 		}
148 	} else if ((pmtab->p_ttyflags & (B_FLAG)))
149 		write_prompt(pmtab->p_fd, pmtab, TRUE, TRUE);
150 
151 
152 	/* Loop until user is successful in invoking service. */
153 	for (;;) {
154 
155 		/* Peek the user's typed response and respond appropriately. */
156 		switch (poll_data()) {
157 		case GOODNAME:
158 #ifdef	DEBUG
159 			debug("got GOODNAME");
160 #endif
161 			if (pmtab->p_timeout) {
162 				(void) alarm((unsigned)0);
163 				sigact.sa_flags = 0;
164 				sigact.sa_handler = SIG_DFL;
165 				(void) sigemptyset(&sigact.sa_mask);
166 				(void) sigaction(SIGALRM, &sigact, NULL);
167 			}
168 			if ((State == PM_DISABLED) ||
169 			    (pmtab->p_flags & X_FLAG)) {
170 				write_prompt(1, pmtab, TRUE, FALSE);
171 				break;
172 			}
173 			if (set_termio(0, speedef->g_fflags, auto_speed,
174 			    FALSE, CANON) == -1) {
175 				log("set final termio failed");
176 				exit(1);
177 			}
178 			invoke_service(pmtab);
179 			exit(1);	/*NOTREACHED*/
180 
181 		case BADSPEED:
182 			/* wrong speed! try next speed in the list. */
183 			speedef = next_speed(speedef);
184 #ifdef	DEBUG
185 			debug("BADSPEED: setup next speed");
186 #endif
187 			if (speedef->g_autobaud & A_FLAG) {
188 				if (auto_termio(0) == -1) {
189 					exit(1);
190 				}
191 				auto_speed = do_autobaud(pmtab, speedef);
192 			} else {
193 				auto_speed = NULL;
194 				/*
195 				 * this reset may fail if the speed is not
196 				 * supported by the system
197 				 * we just cycle through it to the next one
198 				 */
199 				if (set_termio(0, speedef->g_iflags, NULL,
200 				    FALSE, CANON) != 0) {
201 					log("Warning -- speed of <%s> may "
202 					    "be not supported by the system",
203 					    speedef->g_id);
204 				}
205 			}
206 			write_prompt(1, pmtab, TRUE, TRUE);
207 			break;
208 
209 		case NONAME:
210 #ifdef	DEBUG
211 			debug("got NONAME");
212 #endif
213 			write_prompt(1, pmtab, FALSE, FALSE);
214 			break;
215 
216 		}  /* end switch */
217 
218 		peek_ptr = NULL;
219 		if (pmtab->p_timeout) {
220 			sigact.sa_flags = 0;
221 			sigact.sa_handler = timedout;
222 			(void) sigemptyset(&sigact.sa_mask);
223 			(void) sigaction(SIGALRM, &sigact, NULL);
224 			(void) alarm((unsigned)pmtab->p_timeout);
225 		}
226 	} /* end for loop */
227 }
228 
229 static void
230 openline(struct pmtab *pmtab, struct Gdef *speedef)
231 {
232 	char	 buffer[5];
233 	int	 rtn = 0;
234 	int	 line_count;
235 
236 #ifdef	DEBUG
237 	debug("in openline");
238 #endif
239 	if (pmtab->p_status != GETTY) {
240 		(void) close(0);
241 		/* open should return fd 0, if not, then close it */
242 		if ((pmtab->p_fd = open(pmtab->p_device, O_RDWR)) != 0) {
243 			log("open \"%s\" failed: %s", pmtab->p_device,
244 			    strerror(errno));
245 			exit(1);
246 		}
247 	}
248 	(void) close(1);
249 	(void) close(2);
250 	(void) dup(0);
251 	(void) dup(0);
252 
253 	if (pmtab->p_ttyflags & R_FLAG) { /* wait_read is needed */
254 		if (pmtab->p_count) {
255 			if (peek_ptr != NULL)
256 				if ((peek_ptr->buf[0]&0x7F) == '\n' ||
257 				    (peek_ptr->buf[0]&0x7F) == '\r')
258 					pmtab->p_count--;
259 
260 			/*
261 			 * - wait for "p_count" lines
262 			 * - datakit switch does not
263 			 *   know you are a host or a terminal
264 			 * - so it send you several lines of msg
265 			 * - we need to swallow that msg
266 			 * - we assume the baud rate is correct
267 			 * - if it is not, '\n' will not look like '\n'
268 			 * and we will wait forever here
269 			 */
270 			if (set_termio(0, speedef->g_fflags, NULL, TRUE,
271 			    CANON) == -1) {
272 				log("set final termio failed");
273 				exit(1);
274 			}
275 			for (line_count = 0; line_count < pmtab->p_count; ) {
276 				if (read(0, buffer, 1) < 0 ||
277 				    *buffer == '\0' ||
278 				    *buffer == '\004') {
279 					(void) close(0);
280 					exit(0);
281 				}
282 				if (*buffer == '\n')
283 					line_count++;
284 			}
285 		} else { /* wait for 1 char */
286 			if (peek_ptr == NULL) {
287 				if (set_termio(0, NULL, NULL, TRUE,
288 				    RAW) == -1) {
289 					log("set termio RAW failed");
290 					exit(1);
291 				}
292 				rtn = read(0, buffer, 1);
293 			} else
294 				*buffer = (peek_ptr->buf[0]&0x7F);
295 
296 			/*
297 			 * NOTE: Cu on a direct line when ~. is encountered will
298 			 * send EOTs to the other side.  EOT=\004
299 			 */
300 			if (rtn < 0 || *buffer == '\004') {
301 				(void) close(0);
302 				exit(0);
303 			}
304 		}
305 		peek_ptr = NULL;
306 		if (!(pmtab->p_ttyflags & A_FLAG)) { /* autobaud not enabled */
307 			if (set_termio(0, speedef->g_fflags, NULL, TRUE,
308 			    CANON) == -1) {
309 				log("set final termio failed");
310 				exit(1);
311 			}
312 		}
313 	}
314 	if (pmtab->p_ttyflags & B_FLAG) { /* port is bi-directional */
315 		/* set advisory lock on the line */
316 		if (tm_lock(0) != 0) {
317 			/*
318 			 * device is locked
319 			 * child exits and let the parent wait for
320 			 * the lock to go away
321 			 */
322 			exit(0);
323 		}
324 		/* change ownership back to root */
325 		(void) fchown(0, ROOTUID, Tty_gid);
326 		(void) fchmod(0, 0620);
327 	}
328 }
329 
330 /*
331  *	write_prompt	- write the msg to fd
332  *			- if flush is set, flush input queue
333  *			- if clear is set, write a new line
334  */
335 void
336 write_prompt(int fd, struct pmtab *pmtab, int flush, int clear)
337 {
338 
339 #ifdef DEBUG
340 	debug("in write_prompt");
341 #endif
342 	if (flush)
343 		flush_input(fd);
344 	if (clear) {
345 		(void) write(fd, "\r\n", 2);
346 	}
347 #ifdef SYS_NAME
348 	sys_name(fd);
349 #endif
350 	/* Print prompt/disable message. */
351 	if ((State == PM_DISABLED) || (pmtab->p_flags & X_FLAG))
352 		(void) write(fd, pmtab->p_dmsg,
353 		    (unsigned)strlen(pmtab->p_dmsg));
354 	else
355 		(void) write(fd, pmtab->p_prompt,
356 		    (unsigned)strlen(pmtab->p_prompt));
357 }
358 
359 /*
360  *	timedout	- input period timed out
361  */
362 void
363 timedout(int signal __unused)
364 {
365 	exit(1);
366 }
367 
368 #ifdef SYS_NAME
369 /*
370  * void sys_name() - generate a msg with system id
371  *		   - print out /etc/issue file if it exists
372  */
373 void
374 sys_name(int fd)
375 {
376 	char	*ptr, buffer[BUFSIZ];
377 	FILE	*fp;
378 
379 #if 0	/* 1111333 - don't print node name, we already do this elsewhere */
380 	struct	utsname utsname;
381 
382 	if (uname(&utsname) != FAILURE) {
383 		(void) sprintf(buffer, "%.9s\r\n", utsname.nodename);
384 		(void) write(fd, buffer, strlen(buffer));
385 	}
386 #endif
387 
388 	if ((fp = fopen(ISSUEFILE, "r")) != NULL) {
389 		while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) {
390 			(void) write(fd, ptr, strlen(ptr));
391 		}
392 		(void) fclose(fp);
393 	}
394 }
395 #endif
396 
397 
398 /*
399  *	do_autobaud	- do autobaud
400  *			- if it succeed, set the new speed and return
401  *			- if it failed, it will get the nextlabel
402  *			- if next entry is also autobaud,
403  *			  it will loop back to do autobaud again
404  *			- otherwise, it will set new termio and return
405  */
406 static	char	*
407 do_autobaud(struct pmtab *pmtab, struct Gdef *speedef)
408 {
409 	int	done = FALSE;
410 	char	*auto_speed;
411 
412 #ifdef	DEBUG
413 	debug("in do_autobaud");
414 #endif
415 	while (!done) {
416 		if ((auto_speed = autobaud(0, pmtab->p_timeout)) == NULL) {
417 			speedef = next_speed(speedef);
418 			if (speedef->g_autobaud & A_FLAG) {
419 				continue;
420 			} else {
421 				if (set_termio(0, speedef->g_iflags, NULL,
422 				    TRUE, CANON) != 0) {
423 					exit(1);
424 				}
425 				done = TRUE;
426 			}
427 		} else {
428 			if (set_termio(0, speedef->g_fflags, auto_speed,
429 			    TRUE, CANON) != 0) {
430 				exit(1);
431 			}
432 			done = TRUE;
433 		}
434 	}
435 #ifdef	DEBUG
436 	debug("autobaud done");
437 #endif
438 	return (auto_speed);
439 }
440 
441 /*
442  *	next_speed(speedef)
443  *	- find the next entry according to nextlabel. If "nextlabel"
444  *	  is not valid, go back to the old ttylabel.
445  */
446 
447 static	struct	Gdef *
448 next_speed(struct Gdef *speedef)
449 {
450 	struct	Gdef *sp;
451 
452 	if (strcmp(speedef->g_nextid, speedef->g_id) == 0)
453 		return (speedef);
454 	if ((sp = find_def(speedef->g_nextid)) == NULL) {
455 		log("%s's next speed-label (%s) is bad.", speedef->g_id,
456 		    speedef->g_nextid);
457 
458 		/* go back to the original entry. */
459 		if ((sp = find_def(speedef->g_id)) == NULL) {
460 			/* if failed, complain and quit. */
461 			log("unable to find (%s) again", speedef->g_id);
462 			exit(1);
463 		}
464 	}
465 	return (sp);
466 }
467 
468 /*
469  * inform_parent()	- inform ttymon that tmchild is going to exec service
470  */
471 static	void
472 inform_parent(int fd)
473 {
474 	pid_t	pid;
475 
476 	pid = getpid();
477 	(void) write(fd, &pid, sizeof (pid));
478 }
479 
480 static	char	 pbuf[BUFSIZ];	/* static buf for TTYPROMPT	*/
481 static	char	 hbuf[BUFSIZ];	/* static buf for HOME		*/
482 static	char	 tbuf[BUFSIZ];	/* static buf for TERM		*/
483 
484 /*
485  * void invoke_service	- invoke the service
486  */
487 
488 static	void
489 invoke_service(struct pmtab *pmtab)
490 {
491 	char	 *argvp[MAXARGS];		/* service cmd args */
492 	int	 cnt = 0;			/* arg counter */
493 	int	 i;
494 	struct	 sigaction	sigact;
495 
496 #ifdef	DEBUG
497 	debug("in invoke_service");
498 #endif
499 
500 	if (tcgetsid(0) != getsid(getpid())) {
501 		cons_printf("Warning -- ttymon cannot allocate controlling "
502 		    "tty on \"%s\",\n", pmtab->p_device);
503 		cons_printf("\tThere may be another session active on this "
504 		    "port.\n");
505 
506 		if (strcmp("/dev/console", pmtab->p_device) != 0) {
507 			/*
508 			 * if not on console, write to stderr to warn the user
509 			 * also.
510 			 */
511 			(void) fprintf(stderr, "Warning -- ttymon cannot "
512 			    "allocate controlling tty on \"%s\",\n",
513 			    pmtab->p_device);
514 			(void) fprintf(stderr, "\tthere may be another session "
515 			    "active on this port.\n");
516 		}
517 	}
518 
519 	if (pmtab->p_status != GETTY) {
520 		inform_parent(PCpipe[1]);
521 		sigact.sa_flags = 0;
522 		sigact.sa_handler = SIG_DFL;
523 		(void) sigemptyset(&sigact.sa_mask);
524 		(void) sigaction(SIGPOLL, &sigact, NULL);
525 	}
526 
527 	if (pmtab->p_flags & U_FLAG) {
528 		if (account(pmtab->p_device) != 0) {
529 			log("invoke_service: account failed");
530 			exit(1);
531 		}
532 	}
533 
534 	/* parse command line */
535 	mkargv(pmtab->p_server, &argvp[0], &cnt, MAXARGS-1);
536 
537 	if (!(pmtab->p_ttyflags & C_FLAG)) {
538 		(void) sprintf(pbuf, "TTYPROMPT=%s", pmtab->p_prompt);
539 		if (putenv(pbuf)) {
540 			log("cannot expand service <%s> environment", argvp[0]);
541 			exit(1);
542 		}
543 	}
544 	if (pmtab->p_status != GETTY) {
545 		(void) sprintf(hbuf, "HOME=%s", pmtab->p_dir);
546 		if (putenv(hbuf)) {
547 			log("cannot expand service <%s> environment", argvp[0]);
548 			exit(1);
549 		}
550 #ifdef	DEBUG
551 		debug("about to run config script");
552 #endif
553 		if ((i = doconfig(0, pmtab->p_tag, 0)) != 0) {
554 			if (i < 0) {
555 				log("doconfig failed, system error");
556 			} else {
557 				log("doconfig failed on line %d of script %s",
558 				    i, pmtab->p_tag);
559 			}
560 			exit(1);
561 		}
562 	}
563 
564 	if (setgid(pmtab->p_gid)) {
565 		log("cannot set group id to %ld: %s", pmtab->p_gid,
566 		    strerror(errno));
567 		exit(1);
568 	}
569 
570 	if (setuid(pmtab->p_uid)) {
571 		log("cannot set user id to %ld: %s", pmtab->p_uid,
572 		    strerror(errno));
573 		exit(1);
574 	}
575 
576 	if (chdir(pmtab->p_dir)) {
577 		log("cannot chdir to %s: %s", pmtab->p_dir, strerror(errno));
578 		exit(1);
579 	}
580 
581 	if (pmtab->p_uid != ROOTUID) {
582 		/* change ownership and mode of device */
583 		(void) fchown(0, pmtab->p_uid, Tty_gid);
584 		(void) fchmod(0, 0620);
585 	}
586 
587 
588 	if (pmtab->p_status != GETTY) {
589 		sigact.sa_flags = 0;
590 		sigact.sa_handler = SIG_DFL;
591 		(void) sigemptyset(&sigact.sa_mask);
592 		(void) sigaction(SIGINT, &sigact, NULL);
593 		if (setrlimit(RLIMIT_NOFILE, &Rlimit) == -1) {
594 			log("setrlimit failed: %s", strerror(errno));
595 			exit(1);
596 		}
597 		/* invoke the service */
598 		log("Starting service (%s) on %s", argvp[0], pmtab->p_device);
599 	}
600 
601 	if (pmtab->p_termtype != (char *)NULL) {
602 		(void) sprintf(tbuf, "TERM=%s", pmtab->p_termtype);
603 		if (putenv(tbuf)) {
604 			log("cannot expand service <%s> environment", argvp[0]);
605 			exit(1);
606 		}
607 	}
608 	/* restore signal handlers and mask */
609 	(void) sigaction(SIGINT, &Sigint, NULL);
610 	(void) sigaction(SIGALRM, &Sigalrm, NULL);
611 	(void) sigaction(SIGPOLL, &Sigpoll, NULL);
612 	(void) sigaction(SIGQUIT, &Sigquit, NULL);
613 	(void) sigaction(SIGCLD, &Sigcld, NULL);
614 	(void) sigaction(SIGTERM, &Sigterm, NULL);
615 #ifdef	DEBUG
616 	(void) sigaction(SIGUSR1, &Sigusr1, NULL);
617 	(void) sigaction(SIGUSR2, &Sigusr2, NULL);
618 #endif
619 	(void) sigprocmask(SIG_SETMASK, &Origmask, NULL);
620 	(void) execve(argvp[0], argvp, environ);
621 
622 	/* exec returns only on failure! */
623 	log("tmchild: exec service failed: %s", strerror(errno));
624 	exit(1);
625 }
626 
627 /*
628  *	check_hup(fd)	- do a poll on fd to check if it is in hangup state
629  *			- return 1 if hangup, otherwise return 0
630  */
631 
632 static	int
633 check_hup(int fd)
634 {
635 	int	ret;
636 	struct	pollfd	pfd[1];
637 
638 	pfd[0].fd = fd;
639 	pfd[0].events = POLLHUP;
640 	for (;;) {
641 		ret = poll(pfd, 1, 0);
642 		if (ret < 0) {
643 			if (errno == EINTR)
644 				continue;
645 			log("check_hup: poll failed: %s", strerror(errno));
646 			exit(1);
647 		} else if (ret > 0) {
648 			if (pfd[0].revents & POLLHUP) {
649 				return (1);
650 			}
651 		}
652 		return (0);
653 	}
654 }
655 
656 /*
657  * sigpoll()	- SIGPOLL handle for tmchild
658  *		- when SIGPOLL is received by tmchild,
659  *		  the pipe between ttymon and tmchild is broken.
660  *		  Something must happen to ttymon.
661  */
662 void
663 sigpoll(int s __unused)
664 {
665 #ifdef	DEBUG
666 	debug("tmchild got SIGPOLL, exiting");
667 #endif
668 	exit(1);
669 }
670