]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - term-utils/agetty.c
agetty: Fix input of non-ASCII characters in get_logname()
[thirdparty/util-linux.git] / term-utils / agetty.c
index 93a3843c776e9963d831d34a2b48870dfdd674cb..0ef8ba36d87e414750e2b28eeadfb921c0c40ae7 100644 (file)
@@ -35,6 +35,7 @@
 #include <netdb.h>
 #include <ifaddrs.h>
 #include <net/if.h>
+#include <sys/utsname.h>
 
 #include "strutils.h"
 #include "all-io.h"
 /*
  * Things you may want to modify.
  *
- * If ISSUE is not defined, agetty will never display the contents of the
- * /etc/issue file. You will not want to spit out large "issue" files at the
- * wrong baud rate. Relevant for System V only.
+ * If ISSUE_SUPPORT is not defined, agetty will never display the contents of
+ * the /etc/issue file. You will not want to spit out large "issue" files at
+ * the wrong baud rate. Relevant for System V only.
  *
  * You may disagree with the default line-editing etc. characters defined
  * below. Note, however, that DEL cannot be used for interrupt generation
 
 /* Displayed before the login prompt. */
 #ifdef SYSV_STYLE
-#  define ISSUE _PATH_ISSUE
-#  include <sys/utsname.h>
+#  define ISSUE_SUPPORT
+#  if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT)
+#    include <dirent.h>
+#    include "fileutils.h"
+#    define ISSUEDIR_SUPPORT
+#    define ISSUEDIR_EXT       ".issue"
+#    define ISSUEDIR_EXTSIZ    (sizeof(ISSUEDIR_EXT) - 1)
+#  endif
 #endif
 
 /* Login prompt. */
 # define AGETTY_RELOAD_FDNONE  -2                      /* uninitialized fd */
 static int inotify_fd = AGETTY_RELOAD_FDNONE;
 static int netlink_fd = AGETTY_RELOAD_FDNONE;
+static uint32_t netlink_groups;
+#endif
+
+struct issue {
+       FILE *output;
+       char *mem;
+       size_t mem_sz;
+
+#ifdef AGETTY_RELOAD
+       char *mem_old;
 #endif
+       unsigned int do_tcsetattr : 1,
+                    do_tcrestore : 1;
+};
 
 /*
  * When multiple baud rates are specified on the command line, the first one
@@ -169,7 +189,7 @@ struct options {
        char *vcline;                   /* line of virtual console */
        char *term;                     /* terminal type */
        char *initstring;               /* modem init string */
-       char *issue;                    /* alternative issue file */
+       char *issue;                    /* alternative issue file or directory */
        char *erasechars;               /* string with erase chars */
        char *killchars;                /* string with kill chars */
        char *osrelease;                /* /etc/os-release data */
@@ -188,12 +208,12 @@ enum {
 };
 
 #define        F_PARSE         (1<<0)  /* process modem status messages */
-#define        F_ISSUE         (1<<1)  /* display /etc/issue */
+#define        F_ISSUE         (1<<1)  /* display /etc/issue or /etc/issue.d */
 #define        F_RTSCTS        (1<<2)  /* enable RTS/CTS flow control */
 
 #define F_INITSTRING    (1<<4) /* initstring is set */
 #define F_WAITCRLF     (1<<5)  /* wait for CR or LF */
-#define F_CUSTISSUE    (1<<6)  /* give alternative issue file */
+
 #define F_NOPROMPT     (1<<7)  /* do not ask for login name! */
 #define F_LCUC         (1<<8)  /* support for *LCUC stty modes */
 #define F_KEEPSPEED    (1<<9)  /* follow baud rate from kernel */
@@ -296,19 +316,21 @@ static void parse_speeds(struct options *op, char *arg);
 static void update_utmp(struct options *op);
 static void open_tty(char *tty, struct termios *tp, struct options *op);
 static void termio_init(struct options *op, struct termios *tp);
-static void reset_vc (const struct options *op, struct termios *tp);
+static void reset_vc(const struct options *op, struct termios *tp, int canon);
 static void auto_baud(struct termios *tp);
-static void output_special_char (unsigned char c, struct options *op,
+static void list_speeds(void);
+static void output_special_char (struct issue *ie, unsigned char c, struct options *op,
                struct termios *tp, FILE *fp);
-static void do_prompt(struct options *op, struct termios *tp);
+static void do_prompt(struct issue *ie, struct options *op, struct termios *tp);
 static void next_speed(struct options *op, struct termios *tp);
-static char *get_logname(struct options *op,
+static char *get_logname(struct issue *ie, struct options *op,
                         struct termios *tp, struct chardata *cp);
 static void termio_final(struct options *op,
                         struct termios *tp, struct chardata *cp);
 static int caps_lock(char *s);
 static speed_t bcode(char *s);
-static void usage(FILE * out) __attribute__((__noreturn__));
+static void usage(void) __attribute__((__noreturn__));
+static void exit_slowly(int code) __attribute__((__noreturn__));
 static void log_err(const char *, ...) __attribute__((__noreturn__))
                               __attribute__((__format__(printf, 1, 2)));
 static void log_warn (const char *, ...)
@@ -317,6 +339,8 @@ static ssize_t append(char *dest, size_t len, const char  *sep, const char *src)
 static void check_username (const char* nm);
 static void login_options_to_argv(char *argv[], int *argc, char *str, char *username);
 static void reload_agettys(void);
+static void print_issue_file(struct issue *ie, struct options *op, struct termios *tp);
+static void eval_issue_file(struct issue *ie, struct options *op, struct termios *tp);
 
 /* Fake hostname for ut_host specified on command line. */
 static char *fakehost;
@@ -340,8 +364,10 @@ int main(int argc, char **argv)
        struct options options = {
                .flags  =  F_ISSUE,             /* show /etc/issue (SYSV_STYLE) */
                .login  =  _PATH_LOGIN,         /* default login program */
-               .tty    = "tty1",               /* default tty line */
-               .issue  =  ISSUE                /* default issue file */
+               .tty    = "tty1"                /* default tty line */
+       };
+       struct issue issue = {
+               .mem = NULL,
        };
        char *login_argv[LOGIN_ARGV_MAX + 1];
        int login_argc = 0;
@@ -452,16 +478,20 @@ int main(int argc, char **argv)
                username = options.autolog;
        }
 
-       if ((options.flags & F_NOPROMPT) == 0) {
+       if (options.flags & F_NOPROMPT) {       /* --skip-login */
+               eval_issue_file(&issue, &options, &termios);
+               print_issue_file(&issue, &options, &termios);
+       } else {                                /* regular (auto)login */
                if (options.autolog) {
                        /* Autologin prompt */
-                       do_prompt(&options, &termios);
+                       eval_issue_file(&issue, &options, &termios);
+                       do_prompt(&issue, &options, &termios);
                        printf(_("%s%s (automatic login)\n"), LOGIN, options.autolog);
                } else {
                        /* Read the login name. */
                        debug("reading login name\n");
                        while ((username =
-                               get_logname(&options, &termios, &chardata)) == NULL)
+                               get_logname(&issue, &options, &termios, &chardata)) == NULL)
                                if ((options.flags & F_VCONSOLE) == 0 && options.numspeed)
                                        next_speed(&options, &termios);
                }
@@ -471,13 +501,14 @@ int main(int argc, char **argv)
        if (options.timeout)
                alarm(0);
 
-       if ((options.flags & F_VCONSOLE) == 0) {
-               /* Finalize the termios settings. */
+       /* Finalize the termios settings. */
+       if ((options.flags & F_VCONSOLE) == 0)
                termio_final(&options, &termios, &chardata);
+       else
+               reset_vc(&options, &termios, 1);
 
-               /* Now the newline character should be properly written. */
-               write_all(STDOUT_FILENO, "\r\n", 2);
-       }
+       /* Now the newline character should be properly written. */
+       write_all(STDOUT_FILENO, "\r\n", 2);
 
        sigaction(SIGQUIT, &sa_quit, NULL);
        sigaction(SIGINT, &sa_int, NULL);
@@ -613,6 +644,53 @@ static void login_options_to_argv(char *argv[], int *argc,
        *argc = i;
 }
 
+static void output_version(void)
+{
+       static const char *features[] = {
+#ifdef DEBUGGING
+               "debug",
+#endif
+#ifdef CRTSCTS
+               "flow control",
+#endif
+#ifdef KDGKBLED
+               "hints",
+#endif
+#ifdef ISSUE_SUPPORT
+               "issue",
+#endif
+#ifdef ISSUEDIR_SUPPORT
+               "issue.d",
+#endif
+#ifdef KDGKBMODE
+               "keyboard mode",
+#endif
+#ifdef USE_PLYMOUTH_SUPPORT
+               "plymouth",
+#endif
+#ifdef AGETTY_RELOAD
+               "reload",
+#endif
+#ifdef USE_SYSLOG
+               "syslog",
+#endif
+#ifdef HAVE_WIDECHAR
+               "widechar",
+#endif
+               NULL
+       };
+       unsigned int i;
+
+       printf( _("%s from %s"), program_invocation_short_name, PACKAGE_STRING);
+       fputs(" (", stdout);
+       for (i = 0; features[i]; i++) {
+               if (0 < i)
+                       fputs(", ", stdout);
+               printf("%s", features[i]);
+       }
+       fputs(")\n", stdout);
+}
+
 #define is_speed(str) (strlen((str)) == strspn((str), "0123456789,"))
 
 /* Parse command-line arguments. */
@@ -629,6 +707,7 @@ static void parse_args(int argc, char **argv, struct options *op)
                ERASE_CHARS_OPTION,
                KILL_CHARS_OPTION,
                RELOAD_OPTION,
+               LIST_SPEEDS_OPTION,
        };
        const struct option longopts[] = {
                {  "8bits",          no_argument,        NULL,  '8'  },
@@ -646,6 +725,7 @@ static void parse_args(int argc, char **argv, struct options *op)
                {  "login-program",  required_argument,  NULL,  'l'  },
                {  "local-line",     optional_argument,  NULL,  'L'  },
                {  "extract-baud",   no_argument,        NULL,  'm'  },
+               {  "list-speeds",    no_argument,        NULL,  LIST_SPEEDS_OPTION },
                {  "skip-login",     no_argument,        NULL,  'n'  },
                {  "nonewline",      no_argument,        NULL,  'N'  },
                {  "login-options",  required_argument,  NULL,  'o'  },
@@ -691,7 +771,6 @@ static void parse_args(int argc, char **argv, struct options *op)
                        op->flags |= F_REMOTE;
                        break;
                case 'f':
-                       op->flags |= F_CUSTISSUE;
                        op->issue = optarg;
                        break;
                case 'h':
@@ -781,13 +860,16 @@ static void parse_args(int argc, char **argv, struct options *op)
                case RELOAD_OPTION:
                        reload_agettys();
                        exit(EXIT_SUCCESS);
+               case LIST_SPEEDS_OPTION:
+                       list_speeds();
+                       exit(EXIT_SUCCESS);
                case VERSION_OPTION:
-                       printf(UTIL_LINUX_VERSION);
+                       output_version();
                        exit(EXIT_SUCCESS);
                case HELP_OPTION:
-                       usage(stdout);
+                       usage();
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
@@ -795,7 +877,7 @@ static void parse_args(int argc, char **argv, struct options *op)
 
        if (argc < optind + 1) {
                log_warn(_("not enough arguments"));
-               usage(stderr);
+               errx(EXIT_FAILURE, _("not enough arguments"));
        }
 
        /* Accept "tty", "baudrate tty", and "tty baudrate". */
@@ -803,8 +885,8 @@ static void parse_args(int argc, char **argv, struct options *op)
                /* Assume BSD style speed. */
                parse_speeds(op, argv[optind++]);
                if (argc < optind + 1) {
-                       warn(_("not enough arguments"));
-                       usage(stderr);
+                       log_warn(_("not enough arguments"));
+                       errx(EXIT_FAILURE, _("not enough arguments"));
                }
                op->tty = argv[optind++];
        } else {
@@ -826,45 +908,6 @@ static void parse_args(int argc, char **argv, struct options *op)
        if (argc > optind && argv[optind])
                op->term = argv[optind];
 
-#ifdef DO_DEVFS_FIDDLING
-       /*
-        * Some devfs junk, following Goswin Brederlow:
-        *   turn ttyS<n> into tts/<n>
-        *   turn tty<n> into vc/<n>
-        * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=72241
-        */
-       if (op->tty && strlen(op->tty) < 90) {
-               char dev_name[100];
-               struct stat st;
-
-               if (strncmp(op->tty, "ttyS", 4) == 0) {
-                       strcpy(dev_name, "/dev/");
-                       strcat(dev_name, op->tty);
-                       if (stat(dev_name, &st) < 0) {
-                               strcpy(dev_name, "/dev/tts/");
-                               strcat(dev_name, op->tty + 4);
-                               if (stat(dev_name, &st) == 0) {
-                                       op->tty = strdup(dev_name + 5);
-                                       if (!op->tty)
-                                               log_err(_("failed to allocate memory: %m"));
-                               }
-                       }
-               } else if (strncmp(op->tty, "tty", 3) == 0) {
-                       strcpy(dev_name, "/dev/");
-                       strncat(dev_name, op->tty, 90);
-                       if (stat(dev_name, &st) < 0) {
-                               strcpy(dev_name, "/dev/vc/");
-                               strcat(dev_name, op->tty + 3);
-                               if (stat(dev_name, &st) == 0) {
-                                       op->tty = strdup(dev_name + 5);
-                                       if (!op->tty)
-                                               log_err(_("failed to allocate memory: %m"));
-                               }
-                       }
-               }
-       }
-#endif                         /* DO_DEVFS_FIDDLING */
-
        debug("exiting parseargs\n");
 }
 
@@ -934,7 +977,7 @@ static void update_utmp(struct options *op)
                memset(&ut, 0, sizeof(ut));
                if (vcline && *vcline)
                        /* Standard virtual console devices */
-                       strncpy(ut.ut_id, vcline, sizeof(ut.ut_id));
+                       str2memcpy(ut.ut_id, vcline, sizeof(ut.ut_id));
                else {
                        size_t len = strlen(line);
                        char * ptr;
@@ -942,14 +985,14 @@ static void update_utmp(struct options *op)
                                ptr = line + len - sizeof(ut.ut_id);
                        else
                                ptr = line;
-                       strncpy(ut.ut_id, ptr, sizeof(ut.ut_id));
+                       str2memcpy(ut.ut_id, ptr, sizeof(ut.ut_id));
                }
        }
 
-       strncpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
-       strncpy(ut.ut_line, line, sizeof(ut.ut_line));
+       str2memcpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
+       str2memcpy(ut.ut_line, line, sizeof(ut.ut_line));
        if (fakehost)
-               strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
+               str2memcpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
        time(&t);
        ut.ut_tv.tv_sec = t;
        ut.ut_type = LOGIN_PROCESS;
@@ -1208,7 +1251,7 @@ static void termio_init(struct options *op, struct termios *tp)
                setlocale(LC_CTYPE, "POSIX");
                op->flags &= ~F_UTF8;
 #endif
-               reset_vc(op, tp);
+               reset_vc(op, tp, 0);
 
                if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
                        op->flags |= F_EIGHTBITS;
@@ -1241,13 +1284,22 @@ static void termio_init(struct options *op, struct termios *tp)
         * later on.
         */
 
+       /* The default is set c_iflag in termio_final() according to chardata.
+        * Unfortunately, the chardata are not set according to the serial line
+        * if --autolog is enabled. In this case we do not read from the line
+        * at all. The best what we can do in this case is to keep c_iflag
+        * unmodified for --autolog.
+        */
+       if (!op->autolog) {
 #ifdef IUTF8
-       tp->c_iflag = tp->c_iflag & IUTF8;
-       if (tp->c_iflag & IUTF8)
-               op->flags |= F_UTF8;
+               tp->c_iflag = tp->c_iflag & IUTF8;
+               if (tp->c_iflag & IUTF8)
+                       op->flags |= F_UTF8;
 #else
-       tp->c_iflag = 0;
+               tp->c_iflag = 0;
 #endif
+       }
+
        tp->c_lflag = 0;
        tp->c_oflag &= OPOST | ONLCR;
 
@@ -1309,7 +1361,7 @@ static void termio_init(struct options *op, struct termios *tp)
 }
 
 /* Reset virtual console on stdin to its defaults */
-static void reset_vc(const struct options *op, struct termios *tp)
+static void reset_vc(const struct options *op, struct termios *tp, int canon)
 {
        int fl = 0;
 
@@ -1318,6 +1370,15 @@ static void reset_vc(const struct options *op, struct termios *tp)
 
        reset_virtual_console(tp, fl);
 
+#ifdef AGETTY_RELOAD
+       /*
+        * Discard all the flags that makes the line go canonical with echoing.
+        * We need to know when the user starts typing.
+        */
+       if (canon == 0)
+               tp->c_lflag = 0;
+#endif
+
        if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp))
                log_warn(_("setting terminal attributes failed: %m"));
 
@@ -1424,6 +1485,7 @@ static char *xgetdomainname(void)
 #endif
 }
 
+
 static char *read_os_release(struct options *op, const char *varname)
 {
        int fd = -1;
@@ -1476,6 +1538,11 @@ static char *read_os_release(struct options *op, const char *varname)
                        continue;
                }
                p += varsz;
+               p += strspn(p, " \t\n\r");
+
+               if (*p != '=')
+                       continue;
+
                p += strspn(p, " \t\n\r=\"");
                eol = p + strcspn(p, "\n\r");
                *eol = '\0';
@@ -1517,7 +1584,7 @@ static void open_netlink(void)
        if (sock >= 0) {
                addr.nl_family = AF_NETLINK;
                addr.nl_pid = getpid();
-               addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+               addr.nl_groups = netlink_groups;
                if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
                        close(sock);
                else
@@ -1525,7 +1592,7 @@ static void open_netlink(void)
        }
 }
 
-static int process_netlink_msg(int *changed)
+static int process_netlink_msg(int *triggered)
 {
        char buf[4096];
        struct sockaddr_nl snl;
@@ -1565,7 +1632,7 @@ static int process_netlink_msg(int *changed)
                        return 0;
                }
 
-               *changed = 1;
+               *triggered = 1;
                break;
        }
 
@@ -1574,31 +1641,15 @@ static int process_netlink_msg(int *changed)
 
 static int process_netlink(void)
 {
-       int changed = 0;
-       while (process_netlink_msg(&changed));
-       return changed;
+       int triggered = 0;
+       while (process_netlink_msg(&triggered));
+       return triggered;
 }
 
-static int wait_for_term_input(int fd, char *prebuf, size_t prebufsz, size_t *readsz)
+static int wait_for_term_input(int fd)
 {
        char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
-       struct termios orig, nonc;
        fd_set rfds;
-       int count;
-
-       /* Our aim here is to fall through if something fails
-         * and not be stuck waiting. On failure assume we have input */
-
-       if (tcgetattr(fd, &orig) != 0)
-               return 1;
-
-       memcpy(&nonc, &orig, sizeof (nonc));
-       nonc.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHOKE);
-       nonc.c_cc[VMIN] = 1;
-       nonc.c_cc[VTIME] = 0;
-
-       if (tcsetattr(fd, TCSANOW, &nonc) != 0)
-               return 1;
 
        if (inotify_fd == AGETTY_RELOAD_FDNONE) {
                /* make sure the reload trigger file exists */
@@ -1639,15 +1690,6 @@ static int wait_for_term_input(int fd, char *prebuf, size_t prebufsz, size_t *re
                        return 1;
 
                if (FD_ISSET(fd, &rfds)) {
-                       count = read(fd, buffer, sizeof (buffer));
-
-                       tcsetattr(fd, TCSANOW, &orig);
-
-                       if (count > 0 && prebuf && prebufsz) {
-                               memcpy(prebuf, buffer, min(sizeof(buffer), prebufsz));
-                               *readsz = count;
-                       }
-
                        return 1;
 
                } else if (netlink_fd >= 0 && FD_ISSET(netlink_fd, &rfds)) {
@@ -1659,72 +1701,251 @@ static int wait_for_term_input(int fd, char *prebuf, size_t prebufsz, size_t *re
                        while (read(inotify_fd, buffer, sizeof (buffer)) > 0);
                }
 
-               tcsetattr(fd, TCSANOW, &orig);
-
-               /* Need to reprompt */
                return 0;
        }
 }
 #endif  /* AGETTY_RELOAD */
-static void print_issue_file(struct options *op, struct termios *tp)
+
+#ifdef ISSUEDIR_SUPPORT
+static int issuedir_filter(const struct dirent *d)
 {
-#ifdef ISSUE
-       FILE *fd;
+       size_t namesz;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+       if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG &&
+           d->d_type != DT_LNK)
+               return 0;
 #endif
+       if (*d->d_name == '.')
+               return 0;
+
+       namesz = strlen(d->d_name);
+       if (!namesz || namesz < ISSUEDIR_EXTSIZ + 1 ||
+           strcmp(d->d_name + (namesz - ISSUEDIR_EXTSIZ), ISSUEDIR_EXT))
+               return 0;
+
+       /* Accept this */
+       return 1;
+}
+
+static FILE *issuedir_next_file(int dd, struct dirent **namelist, int nfiles, int *n)
+{
+       while (*n < nfiles) {
+               struct dirent *d = namelist[*n];
+               struct stat st;
+               FILE *f;
+
+               (*n)++;
+
+               if (fstatat(dd, d->d_name, &st, 0) ||
+                   !S_ISREG(st.st_mode))
+                       continue;
+
+               f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
+               if (f)
+                       return f;
+       }
+       return NULL;
+}
+
+#endif /* ISSUEDIR_SUPPORT */
+
+#ifndef ISSUE_SUPPORT
+static void print_issue_file(struct issue *ie __attribute__((__unused__)),
+                            struct options *op,
+                            struct termios *tp __attribute__((__unused__)))
+{
        if ((op->flags & F_NONL) == 0) {
                /* Issue not in use, start with a new line. */
                write_all(STDOUT_FILENO, "\r\n", 2);
        }
+}
+
+static void eval_issue_file(struct issue *ie __attribute__((__unused__)),
+                           struct options *op __attribute__((__unused__)),
+                           struct termios *tp __attribute__((__unused__)))
+{
+}
+#else /* ISSUE_SUPPORT */
+
+#ifdef AGETTY_RELOAD
+static int issue_is_changed(struct issue *ie)
+{
+       if (ie->mem_old && ie->mem
+           && strcmp(ie->mem_old, ie->mem) == 0) {
+               free(ie->mem_old);
+               ie->mem_old = ie->mem;
+               ie->mem = NULL;
+               return 0;
+       }
+
+       return 1;
+}
+#endif
+
+static void print_issue_file(struct issue *ie,
+                            struct options *op,
+                            struct termios *tp)
+{
+       int oflag = tp->c_oflag;            /* Save current setting. */
 
-#ifdef ISSUE
-       if ((op->flags & F_ISSUE) && (fd = fopen(op->issue, "r"))) {
-               int c, oflag = tp->c_oflag;         /* Save current setting. */
+       if ((op->flags & F_NONL) == 0) {
+               /* Issue not in use, start with a new line. */
+               write_all(STDOUT_FILENO, "\r\n", 2);
+       }
 
+       if (ie->do_tcsetattr) {
                if ((op->flags & F_VCONSOLE) == 0) {
                        /* Map new line in output to carriage return & new line. */
                        tp->c_oflag |= (ONLCR | OPOST);
                        tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
                }
+       }
 
-               while ((c = getc(fd)) != EOF) {
-                       if (c == '\\')
-                               output_special_char(getc(fd), op, tp, fd);
-                       else
-                               putchar(c);
+       if (ie->mem_sz)
+               write_all(STDOUT_FILENO, ie->mem, ie->mem_sz);
+
+       if (ie->do_tcrestore) {
+               /* Restore settings. */
+               tp->c_oflag = oflag;
+               /* Wait till output is gone. */
+               tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
+       }
+
+#ifdef AGETTY_RELOAD
+       free(ie->mem_old);
+       ie->mem_old = ie->mem;
+       ie->mem = NULL;
+       ie->mem_sz = 0;
+#else
+       free(ie->mem);
+       ie->mem = NULL;
+       ie->mem_sz = 0;
+#endif
+}
+
+static void eval_issue_file(struct issue *ie,
+                           struct options *op,
+                           struct termios *tp)
+{
+       const char *filename, *dirname = NULL;
+       FILE *f = NULL;
+#ifdef ISSUEDIR_SUPPORT
+       int dd = -1, nfiles = 0, i;
+       struct dirent **namelist = NULL;
+#endif
+#ifdef AGETTY_RELOAD
+       netlink_groups = 0;
+#endif
+
+       if (!(op->flags & F_ISSUE))
+               return;
+
+       /*
+        * The custom issue file or directory specified by: agetty -f <path>.
+        * Note that nothing is printed if the file/dir does not exist.
+        */
+       filename = op->issue;
+       if (filename) {
+               struct stat st;
+
+               if (stat(filename, &st) < 0)
+                       return;
+               if (S_ISDIR(st.st_mode)) {
+                       dirname = filename;
+                       filename = NULL;
                }
+       } else {
+               /* The default /etc/issue and optional /etc/issue.d directory
+                * as extension to the file. The /etc/issue.d directory is
+                * ignored if there is no /etc/issue file. The file may be
+                * empty or symlink.
+                */
+               if (access(_PATH_ISSUE, F_OK|R_OK) != 0)
+                       return;
+               filename  = _PATH_ISSUE;
+               dirname = _PATH_ISSUEDIR;
+       }
+
+       ie->output = open_memstream(&ie->mem, &ie->mem_sz);
+#ifdef ISSUEDIR_SUPPORT
+       if (dirname) {
+               dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+               if (dd >= 0)
+                       nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort);
+               if (nfiles <= 0)
+                       dirname = NULL;
+       }
+       i = 0;
+#endif
+       if (filename)
+               f = fopen(filename, "r");
+
+       if (f || dirname) {
+               int c;
+
+               ie->do_tcsetattr = 1;
+
+               do {
+#ifdef ISSUEDIR_SUPPORT
+                       if (!f && i < nfiles)
+                               f = issuedir_next_file(dd, namelist, nfiles, &i);
+#endif
+                       if (!f)
+                               break;
+                       while ((c = getc(f)) != EOF) {
+                               if (c == '\\')
+                                       output_special_char(ie, getc(f), op, tp, f);
+                               else
+                                       putc(c, ie->output);
+                       }
+                       fclose(f);
+                       f = NULL;
+               } while (dirname);
+
                fflush(stdout);
 
-               if ((op->flags & F_VCONSOLE) == 0) {
-                       /* Restore settings. */
-                       tp->c_oflag = oflag;
-                       /* Wait till output is gone. */
-                       tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
-               }
-               fclose(fd);
+               if ((op->flags & F_VCONSOLE) == 0)
+                       ie->do_tcrestore = 1;
        }
-#endif /* ISSUE */
+
+#ifdef ISSUEDIR_SUPPORT
+       for (i = 0; i < nfiles; i++)
+               free(namelist[i]);
+       free(namelist);
+       if (dd >= 0)
+               close(dd);
+#endif
+#ifdef AGETTY_RELOAD
+       if (netlink_groups != 0)
+               open_netlink();
+#endif
+       fclose(ie->output);
 }
+#endif /* ISSUE_SUPPORT */
 
 /* Show login prompt, optionally preceded by /etc/issue contents. */
-static void do_prompt(struct options *op, struct termios *tp)
+static void do_prompt(struct issue *ie, struct options *op, struct termios *tp)
 {
 #ifdef AGETTY_RELOAD
 again:
 #endif
-       print_issue_file(op, tp);
+       print_issue_file(ie, op, tp);
 
        if (op->flags & F_LOGINPAUSE) {
                puts(_("[press ENTER to login]"));
 #ifdef AGETTY_RELOAD
-               if (!wait_for_term_input(STDIN_FILENO, NULL, 0, NULL)) {
-                       /* reload issue */
-                       if (op->flags & F_VCONSOLE)
-                               termio_clear(STDOUT_FILENO);
-                       goto again;
+               /* reload issue */
+               if (!wait_for_term_input(STDIN_FILENO)) {
+                       eval_issue_file(ie, op, tp);
+                       if (issue_is_changed(ie)) {
+                               if (op->flags & F_VCONSOLE)
+                                       termio_clear(STDOUT_FILENO);
+                               goto again;
+                       }
                }
-#else
-               getc(stdin);
 #endif
+               getc(stdin);
        }
 #ifdef KDGKBLED
        if (!(op->flags & F_NOHINTS) && !op->autolog &&
@@ -1813,7 +2034,7 @@ static void next_speed(struct options *op, struct termios *tp)
 }
 
 /* Get user name, establish parity, speed, erase, kill & eol. */
-static char *get_logname(struct options *op, struct termios *tp, struct chardata *cp)
+static char *get_logname(struct issue *ie, struct options *op, struct termios *tp, struct chardata *cp)
 {
        static char logname[BUFSIZ];
        char *bp;
@@ -1842,29 +2063,27 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
        bp = logname;
        *bp = '\0';
 
+       eval_issue_file(ie, op, tp);
        while (*logname == '\0') {
-               char prebuf[LOGIN_NAME_MAX] = { 0 };
-               size_t presz = 0, precur = 0;
-
                /* Write issue file and prompt */
-               do_prompt(op, tp);
+               do_prompt(ie, op, tp);
 
+       no_reload:
 #ifdef AGETTY_RELOAD
-               /* If asked to reprompt *before* terminal input arrives, then do so.
-                *
-                * Note that wait_for_term_input() calls read() and the result
-                * is stored to the 'prebuf'. We need this to avoid data lost
-                * by terminal attributes reset (and return chars back to the
-                * terminal by TIOCSTI is fragile (chars reorder)).
-                *
-                * The data from 'prebuf' are not printed to the terminal yet
-                * (disabled ECHO in wait_for_term_input()).
-                */
-               if (!wait_for_term_input(STDIN_FILENO,
-                                        prebuf, sizeof(prebuf), &presz)) {
-
+               if (!wait_for_term_input(STDIN_FILENO)) {
+                       /* refresh prompt -- discard input data, clear terminal
+                        * and call do_prompt() again
+                        */
+                       if ((op->flags & F_VCONSOLE) == 0)
+                               sleep(1);
+                       eval_issue_file(ie, op, tp);
+                       if (!issue_is_changed(ie))
+                               goto no_reload;
+                       tcflush(STDIN_FILENO, TCIFLUSH);
                        if (op->flags & F_VCONSOLE)
                                termio_clear(STDOUT_FILENO);
+                       bp = logname;
+                       *bp = '\0';
                        continue;
                }
 #endif
@@ -1874,15 +2093,12 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
                while (cp->eol == '\0') {
 
                        char key;
-                       int force_echo = 0;
+                       ssize_t readres;
 
-                       /* use already read data from buffer */
-                       if (presz && precur < presz) {
-                               c = prebuf[precur++];
-                               force_echo = 1;
-
-                       /* read from terminal */
-                       } else if (read(STDIN_FILENO, &c, 1) < 1) {
+                       debug("read from FD\n");
+                       readres = read(STDIN_FILENO, &c, 1);
+                       if (readres < 0) {
+                               debug("read failed\n");
 
                                /* The terminal could be open with O_NONBLOCK when
                                 * -L (force CLOCAL) is specified...  */
@@ -1896,12 +2112,15 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
                                case ESRCH:
                                case EINVAL:
                                case ENOENT:
-                                       break;
+                                       exit_slowly(EXIT_SUCCESS);
                                default:
                                        log_err(_("%s: read: %m"), op->tty);
                                }
                        }
 
+                       if (readres == 0)
+                               c = 0;
+
                        /* Do parity bit handling. */
                        if (eightbit)
                                ascval = c;
@@ -1926,8 +2145,10 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
                        switch (key) {
                        case 0:
                                *bp = 0;
-                               if (op->numspeed > 1)
+                               if (op->numspeed > 1 && !(op->flags & F_VCONSOLE))
                                        return NULL;
+                               if (readres == 0)
+                                       exit_slowly(EXIT_SUCCESS);
                                break;
                        case CR:
                        case NL:
@@ -1954,15 +2175,16 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
                        case CTL('D'):
                                exit(EXIT_SUCCESS);
                        default:
-                               if (!isascii(ascval) || !isprint(ascval))
-                                       break;
                                if ((size_t)(bp - logname) >= sizeof(logname) - 1)
                                        log_err(_("%s: input overrun"), op->tty);
-                               if ((tp->c_lflag & ECHO) == 0 || force_echo)
+                               if ((tp->c_lflag & ECHO) == 0)
                                        write_all(1, &c, 1);    /* echo the character */
                                *bp++ = ascval;                 /* and store it */
                                break;
                        }
+                       /* Everything was erased. */
+                       if (bp == logname && cp->eol == '\0')
+                               goto no_reload;
                }
        }
 
@@ -2041,12 +2263,12 @@ static void termio_final(struct options *op, struct termios *tp, struct chardata
        case 1:
                /* odd parity */
                tp->c_cflag |= PARODD;
-               /* do not break */
+               /* fallthrough */
        case 2:
                /* even parity */
                tp->c_cflag |= PARENB;
                tp->c_iflag |= INPCK | ISTRIP;
-               /* do not break */
+               /* fallthrough */
        case (1 | 2):
                /* no parity bit */
                tp->c_cflag &= ~CSIZE;
@@ -2106,8 +2328,10 @@ static speed_t bcode(char *s)
        return 0;
 }
 
-static void __attribute__ ((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
+
        fputs(USAGE_HEADER, out);
        fprintf(out, _(" %1$s [options] <line> [<baud_rate>,...] [<termtype>]\n"
                       " %1$s [options] <baud_rate>,... <line> [<termtype>]\n"), program_invocation_short_name);
@@ -2148,11 +2372,20 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
        fputs(_("     --delay <number>       sleep seconds before prompt\n"), out);
        fputs(_("     --nice <number>        run login with this priority\n"), out);
        fputs(_("     --reload               reload prompts on running agetty instances\n"), out);
-       fputs(_("     --help                 display this help and exit\n"), out);
-       fputs(_("     --version              output version information and exit\n"), out);
-       fprintf(out, USAGE_MAN_TAIL("agetty(8)"));
+       fputs(_("     --list-speeds          display supported baud rates\n"), out);
+       printf( "     --help                 %s\n", USAGE_OPTSTR_HELP);
+       printf( "     --version              %s\n", USAGE_OPTSTR_VERSION);
+       printf(USAGE_MAN_TAIL("agetty(8)"));
+
+       exit(EXIT_SUCCESS);
+}
+
+static void list_speeds(void)
+{
+       const struct Speedtab *sp;
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       for (sp = speedtab; sp->speed; sp++)
+               printf("%10ld\n", sp->speed);
 }
 
 /*
@@ -2202,6 +2435,13 @@ static void dolog(int priority, const char *fmt, va_list ap)
 #endif                         /* USE_SYSLOG */
 }
 
+static void exit_slowly(int code)
+{
+       /* Be kind to init(8). */
+       sleep(10);
+       exit(code);
+}
+
 static void log_err(const char *fmt, ...)
 {
        va_list ap;
@@ -2210,9 +2450,7 @@ static void log_err(const char *fmt, ...)
        dolog(LOG_ERR, fmt, ap);
        va_end(ap);
 
-       /* Be kind to init(8). */
-       sleep(10);
-       exit(EXIT_FAILURE);
+       exit_slowly(EXIT_FAILURE);
 }
 
 static void log_warn(const char *fmt, ...)
@@ -2224,12 +2462,12 @@ static void log_warn(const char *fmt, ...)
        va_end(ap);
 }
 
-static void print_addr(sa_family_t family, void *addr)
+static void print_addr(struct issue *ie, sa_family_t family, void *addr)
 {
        char buff[INET6_ADDRSTRLEN + 1];
 
        inet_ntop(family, addr, buff, sizeof(buff));
-       printf("%s", buff);
+       fprintf(ie->output, "%s", buff);
 }
 
 /*
@@ -2237,7 +2475,8 @@ static void print_addr(sa_family_t family, void *addr)
  * specified then prints the "best" one (UP, RUNNING, non-LOOPBACK). If not
  * found the "best" interface then prints at least host IP.
  */
-static void output_iface_ip(struct ifaddrs *addrs,
+static void output_iface_ip(struct issue *ie,
+                           struct ifaddrs *addrs,
                            const char *iface,
                            sa_family_t family)
 {
@@ -2279,7 +2518,7 @@ static void output_iface_ip(struct ifaddrs *addrs,
                }
 
                if (addr) {
-                       print_addr(family, addr);
+                       print_addr(ie, family, addr);
                        return;
                }
        }
@@ -2304,7 +2543,7 @@ static void output_iface_ip(struct ifaddrs *addrs,
                        break;
                }
                if (addr)
-                       print_addr(family, addr);
+                       print_addr(ie, family, addr);
 
                freeaddrinfo(info);
        }
@@ -2338,8 +2577,11 @@ static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz)
        return buf;
 }
 
-static void output_special_char(unsigned char c, struct options *op,
-                               struct termios *tp, FILE *fp)
+static void output_special_char(struct issue *ie,
+                               unsigned char c,
+                               struct options *op,
+                               struct termios *tp,
+                               FILE *fp)
 {
        struct utsname uts;
 
@@ -2351,36 +2593,36 @@ static void output_special_char(unsigned char c, struct options *op,
                if (get_escape_argument(fp, escname, sizeof(escname))) {
                        const char *esc = color_sequence_from_colorname(escname);
                        if (esc)
-                               fputs(esc, stdout);
+                               fputs(esc, ie->output);
                } else
-                       fputs("\033", stdout);
+                       fputs("\033", ie->output);
                break;
        }
        case 's':
                uname(&uts);
-               printf("%s", uts.sysname);
+               fprintf(ie->output, "%s", uts.sysname);
                break;
        case 'n':
                uname(&uts);
-               printf("%s", uts.nodename);
+               fprintf(ie->output, "%s", uts.nodename);
                break;
        case 'r':
                uname(&uts);
-               printf("%s", uts.release);
+               fprintf(ie->output, "%s", uts.release);
                break;
        case 'v':
                uname(&uts);
-               printf("%s", uts.version);
+               fprintf(ie->output, "%s", uts.version);
                break;
        case 'm':
                uname(&uts);
-               printf("%s", uts.machine);
+               fprintf(ie->output, "%s", uts.machine);
                break;
        case 'o':
        {
                char *dom = xgetdomainname();
 
-               fputs(dom ? dom : "unknown_domain", stdout);
+               fputs(dom ? dom : "unknown_domain", ie->output);
                free(dom);
                break;
        }
@@ -2400,7 +2642,7 @@ static void output_special_char(unsigned char c, struct options *op,
                            (canon = strchr(info->ai_canonname, '.')))
                                dom = canon + 1;
                }
-               fputs(dom ? dom : "unknown_domain", stdout);
+               fputs(dom ? dom : "unknown_domain", ie->output);
                if (info)
                        freeaddrinfo(info);
                free(host);
@@ -2419,19 +2661,19 @@ static void output_special_char(unsigned char c, struct options *op,
                        break;
 
                if (c == 'd') /* ISO 8601 */
-                       printf("%s %s %d  %d",
+                       fprintf(ie->output, "%s %s %d  %d",
                                      nl_langinfo(ABDAY_1 + tm->tm_wday),
                                      nl_langinfo(ABMON_1 + tm->tm_mon),
                                      tm->tm_mday,
                                      tm->tm_year < 70 ? tm->tm_year + 2000 :
                                      tm->tm_year + 1900);
                else
-                       printf("%02d:%02d:%02d",
+                       fprintf(ie->output, "%02d:%02d:%02d",
                                      tm->tm_hour, tm->tm_min, tm->tm_sec);
                break;
        }
        case 'l':
-               printf ("%s", op->tty);
+               fprintf (ie->output, "%s", op->tty);
                break;
        case 'b':
        {
@@ -2440,7 +2682,7 @@ static void output_special_char(unsigned char c, struct options *op,
 
                for (i = 0; speedtab[i].speed; i++) {
                        if (speedtab[i].code == speed) {
-                               printf("%ld", speedtab[i].speed);
+                               fprintf(ie->output, "%ld", speedtab[i].speed);
                                break;
                        }
                }
@@ -2455,18 +2697,18 @@ static void output_special_char(unsigned char c, struct options *op,
                        var = read_os_release(op, varname);
                        if (var) {
                                if (strcmp(varname, "ANSI_COLOR") == 0)
-                                       printf("\033[%sm", var);
+                                       fprintf(ie->output, "\033[%sm", var);
                                else
-                                       fputs(var, stdout);
+                                       fputs(var, ie->output);
                        }
                /* \S */
                } else if ((var = read_os_release(op, "PRETTY_NAME"))) {
-                       fputs(var, stdout);
+                       fputs(var, ie->output);
 
                /* \S and PRETTY_NAME not found */
                } else {
                        uname(&uts);
-                       fputs(uts.sysname, stdout);
+                       fputs(uts.sysname, ie->output);
                }
 
                free(var);
@@ -2484,11 +2726,12 @@ static void output_special_char(unsigned char c, struct options *op,
                                users++;
                endutxent();
                if (c == 'U')
-                       printf(P_("%d user", "%d users", users), users);
+                       fprintf(ie->output, P_("%d user", "%d users", users), users);
                else
-                       printf ("%d ", users);
+                       fprintf (ie->output, "%d ", users);
                break;
        }
+#if defined(RTMGRP_IPV4_IFADDR) && defined(RTMGRP_IPV6_IFADDR)
        case '4':
        case '6':
        {
@@ -2496,23 +2739,25 @@ static void output_special_char(unsigned char c, struct options *op,
                struct ifaddrs *addrs = NULL;
                char iface[128];
 
-#ifdef AGETTY_RELOAD
-               open_netlink();
-#endif
-
                if (getifaddrs(&addrs))
                        break;
 
                if (get_escape_argument(fp, iface, sizeof(iface)))
-                       output_iface_ip(addrs, iface, family);
+                       output_iface_ip(ie, addrs, iface, family);
                else
-                       output_iface_ip(addrs, NULL, family);
+                       output_iface_ip(ie, addrs, NULL, family);
 
                freeifaddrs(addrs);
+
+               if (c == '4')
+                       netlink_groups |= RTMGRP_IPV4_IFADDR;
+               else
+                       netlink_groups |= RTMGRP_IPV6_IFADDR;
                break;
        }
+#endif
        default:
-               putchar(c);
+               putc(c, ie->output);
                break;
        }
 }