#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <termio.h>
#include <string.h>
#include <signal.h>
#include <poll.h>
#include <unistd.h>
#include "sys/stropts.h"
#include <sys/resource.h>
#include "sac.h"
#include "ttymon.h"
#include "tmstruct.h"
#include "tmextern.h"
#ifdef SYS_NAME
#include <sys/utsname.h>
#endif
static void openline(struct pmtab *, struct Gdef *);
static void invoke_service(struct pmtab *);
static char *do_autobaud(struct pmtab *, struct Gdef *);
static struct Gdef *next_speed(struct Gdef *);
static int check_hup(int);
void
tmchild(struct pmtab *pmtab)
{
struct Gdef *speedef;
char *auto_speed = "";
struct sigaction sigact;
#ifdef DEBUG
debug("in tmchild");
#endif
peek_ptr = NULL;
if (pmtab->p_status != GETTY) {
child_sigcatch();
(void) close(PCpipe[0]);
if (ioctl(PCpipe[1], I_SETSIG, S_HANGUP) == -1) {
log("I_SETSIG failed: %s", strerror(errno));
exit(1);
}
if (check_hup(PCpipe[1])) {
#ifdef DEBUG
debug("PCpipe hungup, tmchild exiting");
#endif
exit(1);
}
if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
if (pmtab->p_fd > 0) {
(void) close(pmtab->p_fd);
pmtab->p_fd = 0;
}
}
(void) setsid();
}
speedef = get_speed(pmtab);
openline(pmtab, speedef);
if (pmtab->p_ttyflags & (C_FLAG|B_FLAG)) {
if (pmtab->p_fd >= 0) {
if ((pmtab->p_modules != NULL) &&
(*(pmtab->p_modules) != '\0')) {
if (push_linedisc(pmtab->p_fd,
pmtab->p_modules, pmtab->p_device) == -1) {
(void) close(pmtab->p_fd);
return;
}
}
}
}
if ((pmtab->p_ttyflags & C_FLAG) &&
(State != PM_DISABLED) &&
(!(pmtab->p_flags & X_FLAG))) {
if (set_termio(0, speedef->g_fflags, NULL, FALSE,
CANON) == -1) {
log("set final termio failed");
exit(1);
}
invoke_service(pmtab);
exit(1);
}
if (speedef->g_autobaud & A_FLAG) {
auto_speed = do_autobaud(pmtab, speedef);
}
if (set_termio(0, speedef->g_fflags, NULL, FALSE, CANON) == -1) {
log("set final termio failed");
exit(1);
}
if ((pmtab->p_ttyflags & (R_FLAG|A_FLAG)) ||
(pmtab->p_status == GETTY) || (pmtab->p_timeout > 0)) {
write_prompt(1, pmtab, TRUE, TRUE);
if (pmtab->p_timeout) {
sigact.sa_flags = 0;
sigact.sa_handler = timedout;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaction(SIGALRM, &sigact, NULL);
(void) alarm((unsigned)pmtab->p_timeout);
}
} else if ((pmtab->p_ttyflags & (B_FLAG)))
write_prompt(pmtab->p_fd, pmtab, TRUE, TRUE);
for (;;) {
switch (poll_data()) {
case GOODNAME:
#ifdef DEBUG
debug("got GOODNAME");
#endif
if (pmtab->p_timeout) {
(void) alarm((unsigned)0);
sigact.sa_flags = 0;
sigact.sa_handler = SIG_DFL;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaction(SIGALRM, &sigact, NULL);
}
if ((State == PM_DISABLED) ||
(pmtab->p_flags & X_FLAG)) {
write_prompt(1, pmtab, TRUE, FALSE);
break;
}
if (set_termio(0, speedef->g_fflags, auto_speed,
FALSE, CANON) == -1) {
log("set final termio failed");
exit(1);
}
invoke_service(pmtab);
exit(1);
case BADSPEED:
speedef = next_speed(speedef);
#ifdef DEBUG
debug("BADSPEED: setup next speed");
#endif
if (speedef->g_autobaud & A_FLAG) {
if (auto_termio(0) == -1) {
exit(1);
}
auto_speed = do_autobaud(pmtab, speedef);
} else {
auto_speed = NULL;
if (set_termio(0, speedef->g_iflags, NULL,
FALSE, CANON) != 0) {
log("Warning -- speed of <%s> may "
"be not supported by the system",
speedef->g_id);
}
}
write_prompt(1, pmtab, TRUE, TRUE);
break;
case NONAME:
#ifdef DEBUG
debug("got NONAME");
#endif
write_prompt(1, pmtab, FALSE, FALSE);
break;
}
peek_ptr = NULL;
if (pmtab->p_timeout) {
sigact.sa_flags = 0;
sigact.sa_handler = timedout;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaction(SIGALRM, &sigact, NULL);
(void) alarm((unsigned)pmtab->p_timeout);
}
}
}
static void
openline(struct pmtab *pmtab, struct Gdef *speedef)
{
char buffer[5];
int rtn = 0;
int line_count;
#ifdef DEBUG
debug("in openline");
#endif
if (pmtab->p_status != GETTY) {
(void) close(0);
if ((pmtab->p_fd = open(pmtab->p_device, O_RDWR)) != 0) {
log("open \"%s\" failed: %s", pmtab->p_device,
strerror(errno));
exit(1);
}
}
(void) close(1);
(void) close(2);
(void) dup(0);
(void) dup(0);
if (pmtab->p_ttyflags & R_FLAG) {
if (pmtab->p_count) {
if (peek_ptr != NULL)
if ((peek_ptr->buf[0]&0x7F) == '\n' ||
(peek_ptr->buf[0]&0x7F) == '\r')
pmtab->p_count--;
if (set_termio(0, speedef->g_fflags, NULL, TRUE,
CANON) == -1) {
log("set final termio failed");
exit(1);
}
for (line_count = 0; line_count < pmtab->p_count; ) {
if (read(0, buffer, 1) < 0 ||
*buffer == '\0' ||
*buffer == '\004') {
(void) close(0);
exit(0);
}
if (*buffer == '\n')
line_count++;
}
} else {
if (peek_ptr == NULL) {
if (set_termio(0, NULL, NULL, TRUE,
RAW) == -1) {
log("set termio RAW failed");
exit(1);
}
rtn = read(0, buffer, 1);
} else
*buffer = (peek_ptr->buf[0]&0x7F);
if (rtn < 0 || *buffer == '\004') {
(void) close(0);
exit(0);
}
}
peek_ptr = NULL;
if (!(pmtab->p_ttyflags & A_FLAG)) {
if (set_termio(0, speedef->g_fflags, NULL, TRUE,
CANON) == -1) {
log("set final termio failed");
exit(1);
}
}
}
if (pmtab->p_ttyflags & B_FLAG) {
if (tm_lock(0) != 0) {
exit(0);
}
(void) fchown(0, ROOTUID, Tty_gid);
(void) fchmod(0, 0620);
}
}
void
write_prompt(int fd, struct pmtab *pmtab, int flush, int clear)
{
#ifdef DEBUG
debug("in write_prompt");
#endif
if (flush)
flush_input(fd);
if (clear) {
(void) write(fd, "\r\n", 2);
}
#ifdef SYS_NAME
sys_name(fd);
#endif
if ((State == PM_DISABLED) || (pmtab->p_flags & X_FLAG))
(void) write(fd, pmtab->p_dmsg,
(unsigned)strlen(pmtab->p_dmsg));
else
(void) write(fd, pmtab->p_prompt,
(unsigned)strlen(pmtab->p_prompt));
}
void
timedout(int signal __unused)
{
exit(1);
}
#ifdef SYS_NAME
void
sys_name(int fd)
{
char *ptr, buffer[BUFSIZ];
FILE *fp;
#if 0
struct utsname utsname;
if (uname(&utsname) != FAILURE) {
(void) sprintf(buffer, "%.9s\r\n", utsname.nodename);
(void) write(fd, buffer, strlen(buffer));
}
#endif
if ((fp = fopen(ISSUEFILE, "r")) != NULL) {
while ((ptr = fgets(buffer, sizeof (buffer), fp)) != NULL) {
(void) write(fd, ptr, strlen(ptr));
}
(void) fclose(fp);
}
}
#endif
static char *
do_autobaud(struct pmtab *pmtab, struct Gdef *speedef)
{
int done = FALSE;
char *auto_speed;
#ifdef DEBUG
debug("in do_autobaud");
#endif
while (!done) {
if ((auto_speed = autobaud(0, pmtab->p_timeout)) == NULL) {
speedef = next_speed(speedef);
if (speedef->g_autobaud & A_FLAG) {
continue;
} else {
if (set_termio(0, speedef->g_iflags, NULL,
TRUE, CANON) != 0) {
exit(1);
}
done = TRUE;
}
} else {
if (set_termio(0, speedef->g_fflags, auto_speed,
TRUE, CANON) != 0) {
exit(1);
}
done = TRUE;
}
}
#ifdef DEBUG
debug("autobaud done");
#endif
return (auto_speed);
}
static struct Gdef *
next_speed(struct Gdef *speedef)
{
struct Gdef *sp;
if (strcmp(speedef->g_nextid, speedef->g_id) == 0)
return (speedef);
if ((sp = find_def(speedef->g_nextid)) == NULL) {
log("%s's next speed-label (%s) is bad.", speedef->g_id,
speedef->g_nextid);
if ((sp = find_def(speedef->g_id)) == NULL) {
log("unable to find (%s) again", speedef->g_id);
exit(1);
}
}
return (sp);
}
static void
inform_parent(int fd)
{
pid_t pid;
pid = getpid();
(void) write(fd, &pid, sizeof (pid));
}
static char pbuf[BUFSIZ];
static char hbuf[BUFSIZ];
static char tbuf[BUFSIZ];
static void
invoke_service(struct pmtab *pmtab)
{
char *argvp[MAXARGS];
int cnt = 0;
int i;
struct sigaction sigact;
#ifdef DEBUG
debug("in invoke_service");
#endif
if (tcgetsid(0) != getsid(getpid())) {
cons_printf("Warning -- ttymon cannot allocate controlling "
"tty on \"%s\",\n", pmtab->p_device);
cons_printf("\tThere may be another session active on this "
"port.\n");
if (strcmp("/dev/console", pmtab->p_device) != 0) {
(void) fprintf(stderr, "Warning -- ttymon cannot "
"allocate controlling tty on \"%s\",\n",
pmtab->p_device);
(void) fprintf(stderr, "\tthere may be another session "
"active on this port.\n");
}
}
if (pmtab->p_status != GETTY) {
inform_parent(PCpipe[1]);
sigact.sa_flags = 0;
sigact.sa_handler = SIG_DFL;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaction(SIGPOLL, &sigact, NULL);
}
if (pmtab->p_flags & U_FLAG) {
if (account(pmtab->p_device) != 0) {
log("invoke_service: account failed");
exit(1);
}
}
mkargv(pmtab->p_server, &argvp[0], &cnt, MAXARGS-1);
if (!(pmtab->p_ttyflags & C_FLAG)) {
(void) sprintf(pbuf, "TTYPROMPT=%s", pmtab->p_prompt);
if (putenv(pbuf)) {
log("cannot expand service <%s> environment", argvp[0]);
exit(1);
}
}
if (pmtab->p_status != GETTY) {
(void) sprintf(hbuf, "HOME=%s", pmtab->p_dir);
if (putenv(hbuf)) {
log("cannot expand service <%s> environment", argvp[0]);
exit(1);
}
#ifdef DEBUG
debug("about to run config script");
#endif
if ((i = doconfig(0, pmtab->p_tag, 0)) != 0) {
if (i < 0) {
log("doconfig failed, system error");
} else {
log("doconfig failed on line %d of script %s",
i, pmtab->p_tag);
}
exit(1);
}
}
if (setgid(pmtab->p_gid)) {
log("cannot set group id to %ld: %s", pmtab->p_gid,
strerror(errno));
exit(1);
}
if (setuid(pmtab->p_uid)) {
log("cannot set user id to %ld: %s", pmtab->p_uid,
strerror(errno));
exit(1);
}
if (chdir(pmtab->p_dir)) {
log("cannot chdir to %s: %s", pmtab->p_dir, strerror(errno));
exit(1);
}
if (pmtab->p_uid != ROOTUID) {
(void) fchown(0, pmtab->p_uid, Tty_gid);
(void) fchmod(0, 0620);
}
if (pmtab->p_status != GETTY) {
sigact.sa_flags = 0;
sigact.sa_handler = SIG_DFL;
(void) sigemptyset(&sigact.sa_mask);
(void) sigaction(SIGINT, &sigact, NULL);
if (setrlimit(RLIMIT_NOFILE, &Rlimit) == -1) {
log("setrlimit failed: %s", strerror(errno));
exit(1);
}
log("Starting service (%s) on %s", argvp[0], pmtab->p_device);
}
if (pmtab->p_termtype != (char *)NULL) {
(void) sprintf(tbuf, "TERM=%s", pmtab->p_termtype);
if (putenv(tbuf)) {
log("cannot expand service <%s> environment", argvp[0]);
exit(1);
}
}
(void) sigaction(SIGINT, &Sigint, NULL);
(void) sigaction(SIGALRM, &Sigalrm, NULL);
(void) sigaction(SIGPOLL, &Sigpoll, NULL);
(void) sigaction(SIGQUIT, &Sigquit, NULL);
(void) sigaction(SIGCLD, &Sigcld, NULL);
(void) sigaction(SIGTERM, &Sigterm, NULL);
#ifdef DEBUG
(void) sigaction(SIGUSR1, &Sigusr1, NULL);
(void) sigaction(SIGUSR2, &Sigusr2, NULL);
#endif
(void) sigprocmask(SIG_SETMASK, &Origmask, NULL);
(void) execve(argvp[0], argvp, environ);
log("tmchild: exec service failed: %s", strerror(errno));
exit(1);
}
static int
check_hup(int fd)
{
int ret;
struct pollfd pfd[1];
pfd[0].fd = fd;
pfd[0].events = POLLHUP;
for (;;) {
ret = poll(pfd, 1, 0);
if (ret < 0) {
if (errno == EINTR)
continue;
log("check_hup: poll failed: %s", strerror(errno));
exit(1);
} else if (ret > 0) {
if (pfd[0].revents & POLLHUP) {
return (1);
}
}
return (0);
}
}
void
sigpoll(int s __unused)
{
#ifdef DEBUG
debug("tmchild got SIGPOLL, exiting");
#endif
exit(1);
}