freebsd-skq/libexec/getty/main.c
imp e1f9385d4b Report once that the device isn't there, but keep trying. Don't
filter the errno values.  They don't make as much sense as they used
to given how we do devices in /dev.
2004-09-29 20:55:14 +00:00

817 lines
18 KiB
C

/*-
* Copyright (c) 1980, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1980, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
#if 0
static char sccsid[] = "@(#)from: main.c 8.1 (Berkeley) 6/20/93";
#endif
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/ttydefaults.h>
#include <sys/utsname.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <libutil.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include "extern.h"
#include "gettytab.h"
#include "pathnames.h"
/*
* Set the amount of running time that getty should accumulate
* before deciding that something is wrong and exit.
*/
#define GETTY_TIMEOUT 60 /* seconds */
#undef CTRL
#define CTRL(x) (x&037)
/* defines for auto detection of incoming PPP calls (->PAP/CHAP) */
#define PPP_FRAME 0x7e /* PPP Framing character */
#define PPP_STATION 0xff /* "All Station" character */
#define PPP_ESCAPE 0x7d /* Escape Character */
#define PPP_CONTROL 0x03 /* PPP Control Field */
#define PPP_CONTROL_ESCAPED 0x23 /* PPP Control Field, escaped */
#define PPP_LCP_HI 0xc0 /* LCP protocol - high byte */
#define PPP_LCP_LOW 0x21 /* LCP protocol - low byte */
/* original mode; flags've been reset using values from <sys/ttydefaults.h> */
struct termios omode;
/* current mode */
struct termios tmode;
int crmod, digit, lower, upper;
char hostname[MAXHOSTNAMELEN];
char name[MAXLOGNAME*3];
char dev[] = _PATH_DEV;
char ttyn[32];
#define OBUFSIZ 128
#define TABBUFSIZ 512
char defent[TABBUFSIZ];
char tabent[TABBUFSIZ];
const char *tname;
char *env[128];
char partab[] = {
0001,0201,0201,0001,0201,0001,0001,0201,
0202,0004,0003,0205,0005,0206,0201,0001,
0201,0001,0001,0201,0001,0201,0201,0001,
0001,0201,0201,0001,0201,0001,0001,0201,
0200,0000,0000,0200,0000,0200,0200,0000,
0000,0200,0200,0000,0200,0000,0000,0200,
0000,0200,0200,0000,0200,0000,0000,0200,
0200,0000,0000,0200,0000,0200,0200,0000,
0200,0000,0000,0200,0000,0200,0200,0000,
0000,0200,0200,0000,0200,0000,0000,0200,
0000,0200,0200,0000,0200,0000,0000,0200,
0200,0000,0000,0200,0000,0200,0200,0000,
0000,0200,0200,0000,0200,0000,0000,0200,
0200,0000,0000,0200,0000,0200,0200,0000,
0200,0000,0000,0200,0000,0200,0200,0000,
0000,0200,0200,0000,0200,0000,0000,0201
};
#define ERASE tmode.c_cc[VERASE]
#define KILL tmode.c_cc[VKILL]
#define EOT tmode.c_cc[VEOF]
#define puts Gputs
static void defttymode(void);
static void dingdong(int);
static void dogettytab(void);
static int getname(void);
static void interrupt(int);
static void oflush(void);
static void prompt(void);
static void putchr(int);
static void putf(const char *);
static void putpad(const char *);
static void puts(const char *);
static void timeoverrun(int);
static char *getline(int);
static void setttymode(int);
static int opentty(const char *, int);
jmp_buf timeout;
static void
dingdong(int signo __unused)
{
alarm(0);
longjmp(timeout, 1);
}
jmp_buf intrupt;
static void
interrupt(int signo __unused)
{
longjmp(intrupt, 1);
}
/*
* Action to take when getty is running too long.
*/
static void
timeoverrun(int signo __unused)
{
syslog(LOG_ERR, "getty exiting due to excessive running time");
exit(1);
}
int
main(int argc, char *argv[])
{
extern char **environ;
int first_sleep = 1, first_time = 1;
struct rlimit limit;
int rval;
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
openlog("getty", LOG_ODELAY|LOG_CONS|LOG_PID, LOG_AUTH);
gethostname(hostname, sizeof(hostname) - 1);
hostname[sizeof(hostname) - 1] = '\0';
if (hostname[0] == '\0')
strcpy(hostname, "Amnesiac");
/*
* Limit running time to deal with broken or dead lines.
*/
(void)signal(SIGXCPU, timeoverrun);
limit.rlim_max = RLIM_INFINITY;
limit.rlim_cur = GETTY_TIMEOUT;
(void)setrlimit(RLIMIT_CPU, &limit);
gettable("default", defent);
gendefaults();
tname = "default";
if (argc > 1)
tname = argv[1];
/*
* The following is a work around for vhangup interactions
* which cause great problems getting window systems started.
* If the tty line is "-", we do the old style getty presuming
* that the file descriptors are already set up for us.
* J. Gettys - MIT Project Athena.
*/
if (argc <= 2 || strcmp(argv[2], "-") == 0)
strcpy(ttyn, ttyname(STDIN_FILENO));
else {
strcpy(ttyn, dev);
strncat(ttyn, argv[2], sizeof(ttyn)-sizeof(dev));
if (strcmp(argv[0], "+") != 0) {
chown(ttyn, 0, 0);
chmod(ttyn, 0600);
revoke(ttyn);
/*
* Do the first scan through gettytab.
* Terminal mode parameters will be wrong until
* defttymode() called, but they're irrelevant for
* the initial setup of the terminal device.
*/
dogettytab();
/*
* Init or answer modem sequence has been specified.
*/
if (IC || AC) {
if (!opentty(ttyn, O_RDWR|O_NONBLOCK))
exit(1);
defttymode();
setttymode(1);
}
if (IC) {
if (getty_chat(IC, CT, DC) > 0) {
syslog(LOG_ERR, "modem init problem on %s", ttyn);
(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
exit(1);
}
}
if (AC) {
int i, rfds;
struct timeval to;
rfds = 1 << 0; /* FD_SET */
to.tv_sec = RT;
to.tv_usec = 0;
i = select(32, (fd_set*)&rfds, (fd_set*)NULL,
(fd_set*)NULL, RT ? &to : NULL);
if (i < 0) {
syslog(LOG_ERR, "select %s: %m", ttyn);
} else if (i == 0) {
syslog(LOG_NOTICE, "recycle tty %s", ttyn);
(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
exit(0); /* recycle for init */
}
i = getty_chat(AC, CT, DC);
if (i > 0) {
syslog(LOG_ERR, "modem answer problem on %s", ttyn);
(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
exit(1);
}
} else { /* maybe blocking open */
if (!opentty(ttyn, O_RDWR | (NC ? O_NONBLOCK : 0 )))
exit(1);
}
}
}
defttymode();
for (;;) {
/*
* if a delay was specified then sleep for that
* number of seconds before writing the initial prompt
*/
if (first_sleep && DE) {
sleep(DE);
/* remove any noise */
(void)tcflush(STDIN_FILENO, TCIOFLUSH);
}
first_sleep = 0;
setttymode(0);
if (AB) {
tname = autobaud();
dogettytab();
continue;
}
if (PS) {
tname = portselector();
dogettytab();
continue;
}
if (CL && *CL)
putpad(CL);
edithost(HE);
/* if this is the first time through this, and an
issue file has been given, then send it */
if (first_time && IF) {
int fd;
if ((fd = open(IF, O_RDONLY)) != -1) {
char * cp;
while ((cp = getline(fd)) != NULL) {
putf(cp);
}
close(fd);
}
}
first_time = 0;
if (IM && *IM && !(PL && PP))
putf(IM);
if (setjmp(timeout)) {
cfsetispeed(&tmode, B0);
cfsetospeed(&tmode, B0);
(void)tcsetattr(STDIN_FILENO, TCSANOW, &tmode);
exit(1);
}
if (TO) {
signal(SIGALRM, dingdong);
alarm(TO);
}
rval = 0;
if (AL) {
const char *p = AL;
char *q = name;
while (*p && q < &name[sizeof name - 1]) {
if (isupper(*p))
upper = 1;
else if (islower(*p))
lower = 1;
else if (isdigit(*p))
digit = 1;
*q++ = *p++;
}
} else if (!(PL && PP))
rval = getname();
if (rval == 2 || (PL && PP)) {
oflush();
alarm(0);
limit.rlim_max = RLIM_INFINITY;
limit.rlim_cur = RLIM_INFINITY;
(void)setrlimit(RLIMIT_CPU, &limit);
execle(PP, "ppplogin", ttyn, (char *) 0, env);
syslog(LOG_ERR, "%s: %m", PP);
exit(1);
} else if (rval || AL) {
int i;
oflush();
alarm(0);
signal(SIGALRM, SIG_DFL);
if (name[0] == '\0')
continue;
if (name[0] == '-') {
puts("user names may not start with '-'.");
continue;
}
if (!(upper || lower || digit)) {
if (AL) {
syslog(LOG_ERR,
"invalid auto-login name: %s", AL);
exit(1);
} else
continue;
}
set_flags(2);
if (crmod) {
tmode.c_iflag |= ICRNL;
tmode.c_oflag |= ONLCR;
}
#if REALLY_OLD_TTYS
if (upper || UC)
tmode.sg_flags |= LCASE;
if (lower || LC)
tmode.sg_flags &= ~LCASE;
#endif
if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
syslog(LOG_ERR, "tcsetattr %s: %m", ttyn);
exit(1);
}
signal(SIGINT, SIG_DFL);
for (i = 0; environ[i] != (char *)0; i++)
env[i] = environ[i];
makeenv(&env[i]);
limit.rlim_max = RLIM_INFINITY;
limit.rlim_cur = RLIM_INFINITY;
(void)setrlimit(RLIMIT_CPU, &limit);
execle(LO, "login", AL ? "-fp" : "-p", name,
(char *) 0, env);
syslog(LOG_ERR, "%s: %m", LO);
exit(1);
}
alarm(0);
signal(SIGALRM, SIG_DFL);
signal(SIGINT, SIG_IGN);
if (NX && *NX) {
tname = NX;
dogettytab();
}
}
}
static int
opentty(const char *tty, int flags)
{
int i;
int failopenlogged = 0;
while ((i = open(tty, flags)) == -1)
{
if (!failopenlogged) {
syslog(LOG_ERR, "open %s: %m", tty);
failopenlogged = 1;
}
sleep(60);
}
if (login_tty(i) < 0) {
if (daemon(0,0) < 0) {
syslog(LOG_ERR,"daemon: %m");
close(i);
return 0;
}
if (login_tty(i) < 0) {
syslog(LOG_ERR, "login_tty %s: %m", tty);
close(i);
return 0;
}
}
return 1;
}
static void
defttymode()
{
/* Start with default tty settings. */
if (tcgetattr(STDIN_FILENO, &tmode) < 0) {
syslog(LOG_ERR, "tcgetattr %s: %m", ttyn);
exit(1);
}
omode = tmode; /* fill c_cc for dogettytab() */
dogettytab();
/*
* Don't rely on the driver too much, and initialize crucial
* things according to <sys/ttydefaults.h>. Avoid clobbering
* the c_cc[] settings however, the console drivers might wish
* to leave their idea of the preferred VERASE key value
* there.
*/
tmode.c_iflag = TTYDEF_IFLAG;
tmode.c_oflag = TTYDEF_OFLAG;
tmode.c_lflag = TTYDEF_LFLAG;
tmode.c_cflag = TTYDEF_CFLAG;
if (NC)
tmode.c_cflag |= CLOCAL;
omode = tmode;
}
static void
setttymode(int raw)
{
int off = 0;
(void)tcflush(STDIN_FILENO, TCIOFLUSH); /* clear out the crap */
ioctl(STDIN_FILENO, FIONBIO, &off); /* turn off non-blocking mode */
ioctl(STDIN_FILENO, FIOASYNC, &off); /* ditto for async mode */
if (IS)
cfsetispeed(&tmode, speed(IS));
else if (SP)
cfsetispeed(&tmode, speed(SP));
if (OS)
cfsetospeed(&tmode, speed(OS));
else if (SP)
cfsetospeed(&tmode, speed(SP));
set_flags(0);
setchars();
if (raw)
cfmakeraw(&tmode);
if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
syslog(LOG_ERR, "tcsetattr %s: %m", ttyn);
exit(1);
}
}
static int
getname(void)
{
int c;
char *np;
unsigned char cs;
int ppp_state = 0;
int ppp_connection = 0;
/*
* Interrupt may happen if we use CBREAK mode
*/
if (setjmp(intrupt)) {
signal(SIGINT, SIG_IGN);
return (0);
}
signal(SIGINT, interrupt);
set_flags(1);
prompt();
oflush();
if (PF > 0) {
sleep(PF);
PF = 0;
}
if (tcsetattr(STDIN_FILENO, TCSANOW, &tmode) < 0) {
syslog(LOG_ERR, "%s: %m", ttyn);
exit(1);
}
crmod = digit = lower = upper = 0;
np = name;
for (;;) {
oflush();
if (read(STDIN_FILENO, &cs, 1) <= 0)
exit(0);
if ((c = cs&0177) == 0)
return (0);
/* PPP detection state machine..
Look for sequences:
PPP_FRAME, PPP_STATION, PPP_ESCAPE, PPP_CONTROL_ESCAPED or
PPP_FRAME, PPP_STATION, PPP_CONTROL (deviant from RFC)
See RFC1662.
Derived from code from Michael Hancock, <michaelh@cet.co.jp>
and Erik 'PPP' Olson, <eriko@wrq.com>
*/
if (PP && (cs == PPP_FRAME)) {
ppp_state = 1;
} else if (ppp_state == 1 && cs == PPP_STATION) {
ppp_state = 2;
} else if (ppp_state == 2 && cs == PPP_ESCAPE) {
ppp_state = 3;
} else if ((ppp_state == 2 && cs == PPP_CONTROL)
|| (ppp_state == 3 && cs == PPP_CONTROL_ESCAPED)) {
ppp_state = 4;
} else if (ppp_state == 4 && cs == PPP_LCP_HI) {
ppp_state = 5;
} else if (ppp_state == 5 && cs == PPP_LCP_LOW) {
ppp_connection = 1;
break;
} else {
ppp_state = 0;
}
if (c == EOT || c == CTRL('d'))
exit(0);
if (c == '\r' || c == '\n' || np >= &name[sizeof name-1]) {
putf("\r\n");
break;
}
if (islower(c))
lower = 1;
else if (isupper(c))
upper = 1;
else if (c == ERASE || c == '\b' || c == 0177) {
if (np > name) {
np--;
if (cfgetospeed(&tmode) >= 1200)
puts("\b \b");
else
putchr(cs);
}
continue;
} else if (c == KILL || c == CTRL('u')) {
putchr('\r');
if (cfgetospeed(&tmode) < 1200)
putchr('\n');
/* this is the way they do it down under ... */
else if (np > name)
puts(" \r");
prompt();
digit = lower = upper = 0;
np = name;
continue;
} else if (isdigit(c))
digit = 1;
if (IG && (c <= ' ' || c > 0176))
continue;
*np++ = c;
putchr(cs);
}
signal(SIGINT, SIG_IGN);
*np = 0;
if (c == '\r')
crmod = 1;
if ((upper && !lower && !LC) || UC)
for (np = name; *np; np++)
if (isupper(*np))
*np = tolower(*np);
return (1 + ppp_connection);
}
static void
putpad(const char *s)
{
int pad = 0;
speed_t ospeed = cfgetospeed(&tmode);
if (isdigit(*s)) {
while (isdigit(*s)) {
pad *= 10;
pad += *s++ - '0';
}
pad *= 10;
if (*s == '.' && isdigit(s[1])) {
pad += s[1] - '0';
s += 2;
}
}
puts(s);
/*
* If no delay needed, or output speed is
* not comprehensible, then don't try to delay.
*/
if (pad == 0 || ospeed <= 0)
return;
/*
* Round up by a half a character frame, and then do the delay.
* Too bad there are no user program accessible programmed delays.
* Transmitting pad characters slows many terminals down and also
* loads the system.
*/
pad = (pad * ospeed + 50000) / 100000;
while (pad--)
putchr(*PC);
}
static void
puts(const char *s)
{
while (*s)
putchr(*s++);
}
char outbuf[OBUFSIZ];
int obufcnt = 0;
static void
putchr(int cc)
{
char c;
c = cc;
if (!NP) {
c |= partab[c&0177] & 0200;
if (OP)
c ^= 0200;
}
if (!UB) {
outbuf[obufcnt++] = c;
if (obufcnt >= OBUFSIZ)
oflush();
} else
write(STDOUT_FILENO, &c, 1);
}
static void
oflush(void)
{
if (obufcnt)
write(STDOUT_FILENO, outbuf, obufcnt);
obufcnt = 0;
}
static void
prompt(void)
{
putf(LM);
if (CO)
putchr('\n');
}
static char *
getline(int fd)
{
int i = 0;
static char linebuf[512];
/*
* This is certainly slow, but it avoids having to include
* stdio.h unnecessarily. Issue files should be small anyway.
*/
while (i < (sizeof linebuf - 3) && read(fd, linebuf+i, 1)==1) {
if (linebuf[i] == '\n') {
/* Don't rely on newline mode, assume raw */
linebuf[i++] = '\r';
linebuf[i++] = '\n';
linebuf[i] = '\0';
return linebuf;
}
++i;
}
linebuf[i] = '\0';
return i ? linebuf : 0;
}
static void
putf(const char *cp)
{
extern char editedhost[];
time_t t;
char *slash, db[100];
static struct utsname kerninfo;
if (!*kerninfo.sysname)
uname(&kerninfo);
while (*cp) {
if (*cp != '%') {
putchr(*cp++);
continue;
}
switch (*++cp) {
case 't':
slash = strrchr(ttyn, '/');
if (slash == (char *) 0)
puts(ttyn);
else
puts(&slash[1]);
break;
case 'h':
puts(editedhost);
break;
case 'd': {
t = (time_t)0;
(void)time(&t);
if (Lo)
(void)setlocale(LC_TIME, Lo);
(void)strftime(db, sizeof(db), DF, localtime(&t));
puts(db);
break;
case 's':
puts(kerninfo.sysname);
break;
case 'm':
puts(kerninfo.machine);
break;
case 'r':
puts(kerninfo.release);
break;
case 'v':
puts(kerninfo.version);
break;
}
case '%':
putchr('%');
break;
}
cp++;
}
}
/*
* Read a gettytab database entry and perform necessary quirks.
*/
static void
dogettytab()
{
/* Read the database entry. */
gettable(tname, tabent);
/*
* Avoid inheriting the parity values from the default entry
* if any of them is set in the current entry.
* Mixing different parity settings is unreasonable.
*/
if (OPset || EPset || APset || NPset)
OPset = EPset = APset = NPset = 1;
/* Fill in default values for unset capabilities. */
setdefaults();
}