*
* Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
+//config:config GETTY
+//config: bool "getty (10 kb)"
+//config: default y
+//config: select FEATURE_SYSLOG
+//config: help
+//config: getty lets you log in on a tty. It is normally invoked by init.
+//config:
+//config: Note that you can save a few bytes by disabling it and
+//config: using login applet directly.
+//config: If you need to reset tty attributes before calling login,
+//config: this script approximates getty:
+//config:
+//config: exec </dev/$1 >/dev/$1 2>&1 || exit 1
+//config: reset
+//config: stty sane; stty ispeed 38400; stty ospeed 38400
+//config: printf "%s login: " "`hostname`"
+//config: read -r login
+//config: exec /bin/login "$login"
+
+//applet:IF_GETTY(APPLET(getty, BB_DIR_SBIN, BB_SUID_DROP))
+
+//kbuild:lib-$(CONFIG_GETTY) += getty.o
#include "libbb.h"
#include <syslog.h>
*/
#define ISSUE "/etc/issue"
-/* Some shorthands for control characters */
-#define CTL(x) ((x) ^ 0100) /* Assumes ASCII dialect */
-#define BS CTL('H') /* back space */
-#define DEL CTL('?') /* delete */
-
-/* Defaults for line-editing etc. characters; you may want to change this */
-#define DEF_INTR CTL('C') /* default interrupt character */
-#define DEF_QUIT CTL('\\') /* default quit char */
-#define DEF_KILL CTL('U') /* default kill char */
-#define DEF_EOF CTL('D') /* default EOF char */
-#define DEF_EOL '\n'
-#define DEF_SWITCH 0 /* default switch char (none) */
+/* Macro to build Ctrl-LETTER. Assumes ASCII dialect */
+#define CTL(x) ((x) ^ 0100)
/*
* When multiple baud rates are specified on the command line,
//usage: "\n"
//usage: "\nBAUD_RATE of 0 leaves it unchanged"
-static const char opt_string[] ALIGN1 = "I:LH:f:hil:mt:wn";
+#define OPT_STR "I:LH:f:hil:mt:+wn"
#define F_INITSTRING (1 << 0) /* -I */
#define F_LOCAL (1 << 1) /* -L */
#define F_FAKEHOST (1 << 2) /* -H */
/* note: arg "0" turns into speed B0 */
G.numspeed++;
if (G.numspeed > MAX_SPEED)
- bb_error_msg_and_die("too many alternate speeds");
+ bb_simple_error_msg_and_die("too many alternate speeds");
}
debug("exiting parse_speeds\n");
}
char *ts;
int flags;
- opt_complementary = "-2:t+"; /* at least 2 args; -t N */
- flags = getopt32(argv, opt_string,
+ flags = getopt32(argv, "^" OPT_STR "\0" "-2"/* at least 2 args*/,
&G.initstring, &G.fakehost, &G.issue,
&G.login, &G.timeout
);
* Make sure it is open for read/write.
*/
if ((fcntl(0, F_GETFL) & (O_RDWR|O_RDONLY|O_WRONLY)) != O_RDWR)
- bb_error_msg_and_die("stdin is not open for read/write");
+ bb_simple_error_msg_and_die("stdin is not open for read/write");
/* Try to get real tty name instead of "-" */
n = xmalloc_ttyname(0);
static void set_tty_attrs(void)
{
if (tcsetattr_stdin_TCSANOW(&G.tty_attrs) < 0)
- bb_perror_msg_and_die("tcsetattr");
+ bb_simple_perror_msg_and_die("tcsetattr");
}
/* We manipulate tty_attrs this way:
#ifdef CMSPAR
| CMSPAR /* mark or space parity */
#endif
+#ifdef CBAUD
| CBAUD /* (output) baud rate */
+#endif
#ifdef CBAUDEX
| CBAUDEX /* (output) baud rate */
#endif
/* non-raw output; add CR to each NL */
G.tty_attrs.c_oflag = OPOST | ONLCR;
- G.tty_attrs.c_cc[VMIN] = 1; /* block reads if < 1 char is available */
- G.tty_attrs.c_cc[VTIME] = 0; /* no timeout (reads block forever) */
+ /* reads will block only if < 1 char is available */
+ G.tty_attrs.c_cc[VMIN] = 1;
+ /* no timeout (reads block forever) */
+ G.tty_attrs.c_cc[VTIME] = 0;
#ifdef __linux__
G.tty_attrs.c_line = 0;
#endif
* observed to improve backspacing through Unicode chars
*/
- /* line buffered input (NL or EOL or EOF chars end a line);
- * recognize INT/QUIT/SUSP chars;
- * echo input chars;
- * echo BS-SP-BS on erase character;
- * echo kill char specially, not as ^c (ECHOKE controls how exactly);
- * erase all input via BS-SP-BS on kill char (else go to next line)
+ /* ICANON line buffered input (NL or EOL or EOF chars end a line);
+ * ISIG recognize INT/QUIT/SUSP chars;
+ * ECHO echo input chars;
+ * ECHOE echo BS-SP-BS on erase character;
+ * ECHOK echo kill char specially, not as ^c (ECHOKE controls how exactly);
+ * ECHOKE erase all input via BS-SP-BS on kill char (else go to next line)
+ * ECHOCTL Echo ctrl chars as ^c (else echo verbatim:
+ * e.g. up arrow emits "ESC-something" and thus moves cursor up!)
*/
- G.tty_attrs.c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE;
+ G.tty_attrs.c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
/* Other bits in c_lflag:
* XCASE Map uppercase to \lowercase [tried, doesn't work]
* ECHONL Echo NL even if ECHO is not set
- * ECHOCTL Echo ctrl chars as ^c (else don't echo) - maybe set this?
* ECHOPRT On erase, echo erased chars
* [qwe<BS><BS><BS> input looks like "qwe\ewq/" on screen]
* NOFLSH Don't flush input buffer after interrupt or quit chars
* (why "stty sane" unsets this bit?)
*/
- G.tty_attrs.c_cc[VINTR] = DEF_INTR;
- G.tty_attrs.c_cc[VQUIT] = DEF_QUIT;
- G.tty_attrs.c_cc[VEOF] = DEF_EOF;
- G.tty_attrs.c_cc[VEOL] = DEF_EOL;
+ G.tty_attrs.c_cc[VINTR] = CTL('C');
+ G.tty_attrs.c_cc[VQUIT] = CTL('\\');
+ G.tty_attrs.c_cc[VEOF] = CTL('D');
+ G.tty_attrs.c_cc[VEOL] = '\n';
#ifdef VSWTC
- G.tty_attrs.c_cc[VSWTC] = DEF_SWITCH;
+ G.tty_attrs.c_cc[VSWTC] = 0;
#endif
#ifdef VSWTCH
- G.tty_attrs.c_cc[VSWTCH] = DEF_SWITCH;
+ G.tty_attrs.c_cc[VSWTCH] = 0;
#endif
- G.tty_attrs.c_cc[VKILL] = DEF_KILL;
+ G.tty_attrs.c_cc[VKILL] = CTL('U');
/* Other control chars:
* VEOL2
* VERASE, VWERASE - (word) erase. we may set VERASE in get_logname
*/
set_tty_attrs();
+
+ /* Now the newline character should be properly written */
+ full_write(STDOUT_FILENO, "\n", 1);
}
/* extract baud rate from modem status message */
tcflush(STDIN_FILENO, TCIFLUSH);
/* Prompt for and read a login name */
- G.line_buf[0] = '\0';
- while (!G.line_buf[0]) {
+ do {
/* Write issue file and prompt */
#ifdef ISSUE
if (!(option_mask32 & F_NOISSUE))
#endif
print_login_prompt();
- /* Read name, watch for break, parity, erase, kill, end-of-line */
+ /* Read name, watch for break, erase, kill, end-of-line */
bp = G.line_buf;
- G.eol = '\0';
while (1) {
/* Do not report trivial EINTR/EIO errors */
errno = EINTR; /* make read of 0 bytes be silent too */
if (read(STDIN_FILENO, &c, 1) < 1) {
+ finalize_tty_attrs();
if (errno == EINTR || errno == EIO)
exit(EXIT_SUCCESS);
- bb_perror_msg_and_die(bb_msg_read_error);
+ bb_simple_perror_msg_and_die(bb_msg_read_error);
}
- /* BREAK. If we have speeds to try,
- * return NULL (will switch speeds and return here) */
- if (c == '\0' && G.numspeed > 1)
- return NULL;
-
- /* Do erase, kill and end-of-line processing */
switch (c) {
case '\r':
case '\n':
*bp = '\0';
G.eol = c;
goto got_logname;
- case BS:
- case DEL:
+ case CTL('H'):
+ case 0x7f:
G.tty_attrs.c_cc[VERASE] = c;
if (bp > G.line_buf) {
full_write(STDOUT_FILENO, "\010 \010", 3);
bp--;
}
break;
+ case CTL('C'):
case CTL('D'):
+ finalize_tty_attrs();
exit(EXIT_SUCCESS);
+ case '\0':
+ /* BREAK. If we have speeds to try,
+ * return NULL (will switch speeds and return here) */
+ if (G.numspeed > 1)
+ return NULL;
+ /* fall through and ignore it */
default:
if ((unsigned char)c < ' ') {
/* ignore garbage characters */
}
} /* end of get char loop */
got_logname: ;
- } /* while logname is empty */
+ } while (G.line_buf[0] == '\0'); /* while logname is empty */
return G.line_buf;
}
_exit(EXIT_SUCCESS);
}
+static void sleep10(void)
+{
+ sleep(10);
+}
+
int getty_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int getty_main(int argc UNUSED_PARAM, char **argv)
{
* a session leader - which is quite possible for getty!
*/
pid = getpid();
- if (getsid(0) != pid)
- bb_perror_msg_and_die("setsid");
+ if (getsid(0) != pid) {
+ //for debugging:
+ //bb_perror_msg_and_die("setsid failed:"
+ // " pid %d ppid %d"
+ // " sid %d pgid %d",
+ // pid, getppid(),
+ // getsid(0), getpgid(0));
+ bb_simple_perror_msg_and_die("setsid");
+ /*
+ * When we can end up here?
+ * Example: setsid() fails when run alone in interactive shell:
+ * # getty 115200 /dev/tty2
+ * because shell's child (getty) is put in a new process group.
+ * But doesn't fail if shell is not interactive
+ * (and therefore doesn't create process groups for pipes),
+ * or if getty is not the first process in the process group:
+ * # true | getty 115200 /dev/tty2
+ */
+ }
/* Looks like we are already a session leader.
* In this case (setsid failed) we may still have ctty,
* and it may be different from tty we need to control!
* If we still have ctty, on Linux ioctl(TIOCSCTTY)
- * (which we are going to call a bit later) always fails.
- * Try to drop ctty now to prevent that.
+ * (which we are going to use a bit later) always fails -
+ * even if we try to take ctty which is already ours!
+ * Try to drop old ctty now to prevent that.
+ * Use O_NONBLOCK: old ctty may be a serial line.
*/
- fd = open("/dev/tty", O_RDWR);
+ fd = open("/dev/tty", O_RDWR | O_NONBLOCK);
if (fd >= 0) {
+ /* TIOCNOTTY sends SIGHUP to the foreground
+ * process group - which may include us!
+ * Make sure to not die on it:
+ */
+ sighandler_t old = signal(SIGHUP, SIG_IGN);
ioctl(fd, TIOCNOTTY);
close(fd);
+ signal(SIGHUP, old);
}
}
close(n--);
/* Logging. We want special flavor of error_msg_and_die */
- die_sleep = 10;
+ die_func = sleep10;
msg_eol = "\r\n";
/* most likely will internally use fd #3 in CLOEXEC mode: */
openlog(applet_name, LOG_PID, LOG_AUTH);
tsid = tcgetsid(STDIN_FILENO);
if (tsid < 0 || pid != tsid) {
if (ioctl(STDIN_FILENO, TIOCSCTTY, /*force:*/ (long)1) < 0)
- bb_perror_msg_and_die("TIOCSCTTY");
+ bb_simple_perror_msg_and_die("TIOCSCTTY");
}
#ifdef __linux__
/* Make ourself a foreground process group within our session */
if (tcsetpgrp(STDIN_FILENO, pid) < 0)
- bb_perror_msg_and_die("tcsetpgrp");
+ bb_simple_perror_msg_and_die("tcsetpgrp");
#endif
/*
* 5 seconds seems to be a good value.
*/
if (tcgetattr(STDIN_FILENO, &G.tty_attrs) < 0)
- bb_perror_msg_and_die("tcgetattr");
+ bb_simple_perror_msg_and_die("tcgetattr");
/* Update the utmp file. This tty is ours now! */
update_utmp(pid, LOGIN_PROCESS, G.tty_name, "LOGIN", G.fakehost);
finalize_tty_attrs();
- /* Now the newline character should be properly written */
- full_write(STDOUT_FILENO, "\n", 1);
-
/* Let the login program take care of password validation */
/* We use PATH because we trust that root doesn't set "bad" PATH,
* and getty is not suid-root applet */
/* With -n, logname == NULL, and login will ask for username instead */
- BB_EXECLP(G.login, G.login, "--", logname, NULL);
+ BB_EXECLP(G.login, G.login, "--", logname, (char *)0);
bb_error_msg_and_die("can't execute '%s'", G.login);
}