]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
su: use lib/pty-session.c code for --pty
authorKarel Zak <kzak@redhat.com>
Thu, 4 Jul 2019 09:59:23 +0000 (11:59 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 8 Oct 2019 11:11:54 +0000 (13:11 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
login-utils/Makemodule.am
login-utils/su-common.c

index aafbea307778fd3d808083503e1d6967fd3214eb..c29dbb0afe1297a749ce474a2543452b307b52ae 100644 (file)
@@ -146,7 +146,9 @@ su_LDADD = $(LDADD) libcommon.la -lpam
 if HAVE_LINUXPAM
 su_LDADD += -lpam_misc
 endif
-if HAVE_UTIL
+if HAVE_PTY
+su_SOURCES += lib/pty-session.c \
+             include/pty-session.h
 su_LDADD += -lutil
 endif
 endif # BUILD_SU
@@ -165,7 +167,9 @@ runuser_LDADD = $(LDADD) libcommon.la -lpam
 if HAVE_LINUXPAM
 runuser_LDADD += -lpam_misc
 endif
-if HAVE_UTIL
+if HAVE_PTY
+runuser_SOURCES += lib/pty-session.c \
+             include/pty-session.h
 runuser_LDADD += -lutil
 endif
 endif # BUILD_RUNUSER
index 4d91b22e44b28e4d63a76a33e3e3ed329c09e77b..d4a995f43637e06550803e0dbe119bcb726a5bfe 100644 (file)
 #include <syslog.h>
 #include <utmpx.h>
 
-#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) && defined(HAVE_SYS_SIGNALFD_H)
+#ifdef HAVE_PTY
 # include <pty.h>
 # include <poll.h>
 # include <sys/signalfd.h>
-# include "all-io.h"
+# include "pty-session.h"
 # define USE_PTY
 #endif
 
@@ -136,15 +136,8 @@ struct su_context {
        char            **env_whitelist_vals;
 
        struct sigaction oldact[SIGNALS_IDX_COUNT];     /* original sigactions indexed by SIG*_IDX */
-
 #ifdef USE_PTY
-       struct termios  stdin_attrs;            /* stdin and slave terminal runtime attributes */
-       int             pty_master;
-       int             pty_slave;
-       int             pty_sigfd;              /* signalfd() */
-       int             poll_timeout;
-       struct winsize  win;                    /* terminal window size */
-       sigset_t        oldsig;                 /* original signal mask */
+       struct ul_pty   *pty;                   /* pseudo terminal handler (for --pty) */
 #endif
        unsigned int runuser :1,                /* flase=su, true=runuser */
                     runuser_uopt :1,           /* runuser -u specified */
@@ -156,7 +149,7 @@ struct su_context {
                     suppress_pam_info:1,       /* don't print PAM info messages (Last login, etc.). */
                     pam_has_session :1,        /* PAM session opened */
                     pam_has_cred :1,           /* PAM cred established */
-                    pty :1,                    /* create pseudo-terminal */
+                    force_pty :1,              /* create pseudo-terminal */
                     restricted :1;             /* false for root user */
 };
 
@@ -213,7 +206,7 @@ static int wait_for_child(struct su_context *su)
                                DBG(SIG, ul_debug(" session resumed -- continue"));
 #ifdef USE_PTY
                                /* Let's go back to pty_proxy_master() */
-                               if (su->pty_sigfd != -1) {
+                               if (su->force_pty && ul_pty_is_running(su->pty)) {
                                        DBG(SIG, ul_debug(" leaving on child SIGSTOP"));
                                        return 0;
                                }
@@ -235,6 +228,11 @@ static int wait_for_child(struct su_context *su)
                DBG(SIG, ul_debug("child %d is dead", su->child));
                su->child = (pid_t) -1; /* Don't use the PID anymore! */
                su->childstatus = status;
+#ifdef USE_PTY
+               /* inform pty suff that we have no child anymore */
+               if (su->force_pty)
+                       ul_pty_set_child(su->pty, (pid_t) -1);
+#endif
        } else if (caught_signal)
                status = caught_signal + 128;
        else
@@ -244,345 +242,12 @@ static int wait_for_child(struct su_context *su)
        return status;
 }
 
-
 #ifdef USE_PTY
-static void pty_init_slave(struct su_context *su)
-{
-       DBG(PTY, ul_debug("initialize slave"));
-
-       ioctl(su->pty_slave, TIOCSCTTY, 1);
-       close(su->pty_master);
-
-       dup2(su->pty_slave, STDIN_FILENO);
-       dup2(su->pty_slave, STDOUT_FILENO);
-       dup2(su->pty_slave, STDERR_FILENO);
-
-       close(su->pty_slave);
-       close(su->pty_sigfd);
-
-       su->pty_slave = -1;
-       su->pty_master = -1;
-       su->pty_sigfd = -1;
-
-       sigprocmask(SIG_SETMASK, &su->oldsig, NULL);
-
-       DBG(PTY, ul_debug("... initialize slave done"));
-}
-
-static void pty_create(struct su_context *su)
-{
-       struct termios slave_attrs;
-       int rc;
-
-       if (su->isterm) {
-               DBG(PTY, ul_debug("create for terminal"));
-
-               /* original setting of the current terminal */
-               if (tcgetattr(STDIN_FILENO, &su->stdin_attrs) != 0)
-                       err(EXIT_FAILURE, _("failed to get terminal attributes"));
-               ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
-               /* create master+slave */
-               rc = openpty(&su->pty_master, &su->pty_slave, NULL, &su->stdin_attrs, &su->win);
-
-               /* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */
-               slave_attrs = su->stdin_attrs;
-               cfmakeraw(&slave_attrs);
-               slave_attrs.c_lflag &= ~ECHO;
-               tcsetattr(STDIN_FILENO, TCSANOW, &slave_attrs);
-       } else {
-               DBG(PTY, ul_debug("create for non-terminal"));
-               rc = openpty(&su->pty_master, &su->pty_slave, NULL, NULL, NULL);
-
-               if (!rc) {
-                       tcgetattr(su->pty_slave, &slave_attrs);
-                       slave_attrs.c_lflag &= ~ECHO;
-                       tcsetattr(su->pty_slave, TCSANOW, &slave_attrs);
-               }
-       }
-
-       if (rc < 0)
-               err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
-
-       DBG(PTY, ul_debug("pty setup done [master=%d, slave=%d]", su->pty_master, su->pty_slave));
-}
-
-static void pty_cleanup(struct su_context *su)
+static void wait_for_child_cb(void *data)
 {
-       struct termios rtt;
-
-       if (su->pty_master == -1 || !su->isterm)
-               return;
-
-       DBG(PTY, ul_debug("cleanup"));
-       rtt = su->stdin_attrs;
-       tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt);
-}
-
-static int write_output(char *obuf, ssize_t bytes)
-{
-       DBG(PTY, ul_debug(" writing output"));
-
-       if (write_all(STDOUT_FILENO, obuf, bytes)) {
-               DBG(PTY, ul_debug("  writing output *failed*"));
-               warn(_("write failed"));
-               return -errno;
-       }
-
-       return 0;
+       wait_for_child((struct su_context *) data);
 }
-
-static int write_to_child(struct su_context *su,
-                         char *buf, size_t bufsz)
-{
-       return write_all(su->pty_master, buf, bufsz);
-}
-
-/*
- * The su(1) is usually faster than shell, so it's a good idea to wait until
- * the previous message has been already read by shell from slave before we
- * write to master. This is necessary especially for EOF situation when we can
- * send EOF to master before shell is fully initialized, to workaround this
- * problem we wait until slave is empty. For example:
- *
- *   echo "date" | su
- *
- * Unfortunately, the child (usually shell) can ignore stdin at all, so we
- * don't wait forever to avoid dead locks...
- *
- * Note that su --pty is primarily designed for interactive sessions as it
- * maintains master+slave tty stuff within the session. Use pipe to write to
- * su(1) and assume non-interactive (tee-like) behavior is NOT well
- * supported.
- */
-static void write_eof_to_child(struct su_context *su)
-{
-       unsigned int tries = 0;
-       struct pollfd fds[] = {
-                  { .fd = su->pty_slave, .events = POLLIN }
-       };
-       char c = DEF_EOF;
-
-       DBG(PTY, ul_debug(" waiting for empty slave"));
-       while (poll(fds, 1, 10) == 1 && tries < 8) {
-               DBG(PTY, ul_debug("   slave is not empty"));
-               xusleep(250000);
-               tries++;
-       }
-       if (tries < 8)
-               DBG(PTY, ul_debug("   slave is empty now"));
-
-       DBG(PTY, ul_debug(" sending EOF to master"));
-       write_to_child(su, &c, sizeof(char));
-}
-
-static int pty_handle_io(struct su_context *su, int fd, int *eof)
-{
-       char buf[BUFSIZ];
-       ssize_t bytes;
-
-       DBG(PTY, ul_debug("%d FD active", fd));
-       *eof = 0;
-
-       /* read from active FD */
-       bytes = read(fd, buf, sizeof(buf));
-       if (bytes < 0) {
-               if (errno == EAGAIN || errno == EINTR)
-                       return 0;
-               return -errno;
-       }
-
-       if (bytes == 0) {
-               *eof = 1;
-               return 0;
-       }
-
-       /* from stdin (user) to command */
-       if (fd == STDIN_FILENO) {
-               DBG(PTY, ul_debug(" stdin --> master %zd bytes", bytes));
-
-               if (write_to_child(su, buf, bytes)) {
-                       warn(_("write failed"));
-                       return -errno;
-               }
-               /* without sync write_output() will write both input &
-                * shell output that looks like double echoing */
-               fdatasync(su->pty_master);
-
-       /* from command (master) to stdout */
-       } else if (fd == su->pty_master) {
-               DBG(PTY, ul_debug(" master --> stdout %zd bytes", bytes));
-               write_output(buf, bytes);
-       }
-
-       return 0;
-}
-
-static int pty_handle_signal(struct su_context *su, int fd)
-{
-       struct signalfd_siginfo info;
-       ssize_t bytes;
-
-       DBG(SIG, ul_debug("signal FD %d active", fd));
-
-       bytes = read(fd, &info, sizeof(info));
-       if (bytes != sizeof(info)) {
-               if (bytes < 0 && (errno == EAGAIN || errno == EINTR))
-                       return 0;
-               return -errno;
-       }
-
-       switch (info.ssi_signo) {
-       case SIGCHLD:
-               DBG(SIG, ul_debug(" get signal SIGCHLD"));
-
-               /* The child terminated or stopped. Note that we ignore SIGCONT
-                * here, because stop/cont semantic is handled by wait_for_child() */
-               if (info.ssi_code == CLD_EXITED
-                   || info.ssi_code == CLD_KILLED
-                   || info.ssi_code == CLD_DUMPED
-                   || info.ssi_status == SIGSTOP)
-                       wait_for_child(su);
-               /* The child is dead, force poll() timeout. */
-               if (su->child == (pid_t) -1)
-                       su->poll_timeout = 10;
-               return 0;
-       case SIGWINCH:
-               DBG(SIG, ul_debug(" get signal SIGWINCH"));
-               if (su->isterm) {
-                       ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
-                       ioctl(su->pty_slave, TIOCSWINSZ, (char *)&su->win);
-               }
-               break;
-       case SIGTERM:
-               /* fallthrough */
-       case SIGINT:
-               /* fallthrough */
-       case SIGQUIT:
-               DBG(SIG, ul_debug(" get signal SIG{TERM,INT,QUIT}"));
-               caught_signal = info.ssi_signo;
-                /* Child termination is going to generate SIGCHILD (see above) */
-                kill(su->child, SIGTERM);
-               break;
-       default:
-               abort();
-       }
-
-       return 0;
-}
-
-static void pty_proxy_master(struct su_context *su)
-{
-       sigset_t ourset;
-       int rc = 0, ret, eof = 0;
-       enum {
-               POLLFD_SIGNAL = 0,
-               POLLFD_MASTER,
-               POLLFD_STDIN
-
-       };
-       struct pollfd pfd[] = {
-               [POLLFD_SIGNAL] = { .fd = -1,             .events = POLLIN | POLLERR | POLLHUP },
-               [POLLFD_MASTER] = { .fd = su->pty_master, .events = POLLIN | POLLERR | POLLHUP },
-               [POLLFD_STDIN]  = { .fd = STDIN_FILENO,   .events = POLLIN | POLLERR | POLLHUP }
-       };
-
-       /* for PTY mode we use signalfd
-        *
-        * TODO: script(1) initializes this FD before fork, good or bad idea?
-        */
-       sigfillset(&ourset);
-       if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
-               warn(_("cannot block signals"));
-               caught_signal = true;
-               return;
-       }
-
-       sigemptyset(&ourset);
-       sigaddset(&ourset, SIGCHLD);
-       sigaddset(&ourset, SIGWINCH);
-       sigaddset(&ourset, SIGALRM);
-       sigaddset(&ourset, SIGTERM);
-       sigaddset(&ourset, SIGINT);
-       sigaddset(&ourset, SIGQUIT);
-
-       if ((su->pty_sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) {
-               warn(("cannot create signal file descriptor"));
-               caught_signal = true;
-               return;
-       }
-
-       pfd[POLLFD_SIGNAL].fd = su->pty_sigfd;
-       su->poll_timeout = -1;
-
-       while (!caught_signal) {
-               size_t i;
-               int errsv;
-
-               DBG(PTY, ul_debug("calling poll()"));
-
-               /* wait for input or signal */
-               ret = poll(pfd, ARRAY_SIZE(pfd), su->poll_timeout);
-               errsv = errno;
-               DBG(PTY, ul_debug("poll() rc=%d", ret));
-
-               if (ret < 0) {
-                       if (errsv == EAGAIN)
-                               continue;
-                       warn(_("poll failed"));
-                       break;
-               }
-               if (ret == 0) {
-                       DBG(PTY, ul_debug("leaving poll() loop [timeout=%d]", su->poll_timeout));
-                       break;
-               }
-
-               for (i = 0; i < ARRAY_SIZE(pfd); i++) {
-                       rc = 0;
-
-                       if (pfd[i].revents == 0)
-                               continue;
-
-                       DBG(PTY, ul_debug(" active pfd[%s].fd=%d %s %s %s",
-                                               i == POLLFD_STDIN  ? "stdin" :
-                                               i == POLLFD_MASTER ? "master" :
-                                               i == POLLFD_SIGNAL ? "signal" : "???",
-                                               pfd[i].fd,
-                                               pfd[i].revents & POLLIN  ? "POLLIN" : "",
-                                               pfd[i].revents & POLLHUP ? "POLLHUP" : "",
-                                               pfd[i].revents & POLLERR ? "POLLERR" : ""));
-                       switch (i) {
-                       case POLLFD_STDIN:
-                       case POLLFD_MASTER:
-                               /* data */
-                               if (pfd[i].revents & POLLIN)
-                                       rc = pty_handle_io(su, pfd[i].fd, &eof);
-                               /* EOF maybe detected by two ways:
-                                *      A) poll() return POLLHUP event after close()
-                                *      B) read() returns 0 (no data) */
-                               if ((pfd[i].revents & POLLHUP) || eof) {
-                                       DBG(PTY, ul_debug(" ignore FD"));
-                                       pfd[i].fd = -1;
-                                       if (i == POLLFD_STDIN) {
-                                               write_eof_to_child(su);
-                                               DBG(PTY, ul_debug("  ignore STDIN"));
-                                       }
-                               }
-                               continue;
-                       case POLLFD_SIGNAL:
-                               rc = pty_handle_signal(su, pfd[i].fd);
-                               break;
-                       }
-                       if (rc)
-                               break;
-               }
-       }
-
-       close(su->pty_sigfd);
-       su->pty_sigfd = -1;
-       DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
-}
-#endif /* USE_PTY */
-
+#endif
 
 /* Log the fact that someone has run su to the user given by PW;
    if SUCCESSFUL is true, they gave the correct password, etc.  */
@@ -830,11 +495,23 @@ static void create_watching_parent(struct su_context *su)
 
        DBG(MISC, ul_debug("forking..."));
 #ifdef USE_PTY
-       /* no-op, just save original signal mask to oldsig */
-       sigprocmask(SIG_BLOCK, NULL, &su->oldsig);
+       if (su->force_pty) {
+               struct ul_pty_callbacks *cb;
+
+               /* no-op, just save original signal mask to pty */
+               sigprocmask(SIG_BLOCK, NULL, ul_pty_get_orig_sigset(su->pty));
+
+               /* set callbacks */
+               ul_pty_set_callback_data(su->pty, (void *) su);
 
-       if (su->pty)
-               pty_create(su);
+               cb = ul_pty_get_callbacks(su->pty);
+               cb->child_wait    = wait_for_child_cb;
+               cb->child_sigstop = wait_for_child_cb;
+
+               /* create pty */
+               if (ul_pty_setup(su->pty))
+                       err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
+       }
 #endif
        fflush(stdout);                 /* ??? */
 
@@ -842,8 +519,8 @@ static void create_watching_parent(struct su_context *su)
        case -1: /* error */
                supam_cleanup(su, PAM_ABORT);
 #ifdef USE_PTY
-               if (su->pty)
-                       pty_cleanup(su);
+               if (su->force_pty)
+                       ul_pty_cleanup(su->pty);
 #endif
                err(EXIT_FAILURE, _("cannot create child process"));
                break;
@@ -866,9 +543,17 @@ static void create_watching_parent(struct su_context *su)
        if (chdir("/") != 0)
                warn(_("cannot change directory to %s"), "/");
 #ifdef USE_PTY
-       if (su->pty)
-               pty_proxy_master(su);
-       else
+       if (su->force_pty) {
+               ul_pty_set_child(su->pty, su->child);
+
+               if (ul_pty_proxy_master(su->pty) != 0)
+                       caught_signal = true;
+
+               /* ul_pty_proxy_master() keeps classic signal handler are out of game */
+               caught_signal = ul_pty_get_delivered_signal(su->pty);
+
+               ul_pty_cleanup(su->pty);
+       } else
 #endif
                parent_setup_signals(su);
 
@@ -924,10 +609,6 @@ static void create_watching_parent(struct su_context *su)
                kill(getpid(), caught_signal);
        }
 
-#ifdef USE_PTY
-       if (su->pty)
-               pty_cleanup(su);
-#endif
        DBG(MISC, ul_debug("exiting [rc=%d]", status));
        exit(status);
 }
@@ -1276,12 +957,7 @@ int su_main(int argc, char **argv, int mode)
                .conv                   = { supam_conv, NULL },
                .runuser                = (mode == RUNUSER_MODE ? 1 : 0),
                .change_environment     = 1,
-               .new_user               = DEFAULT_USER,
-#ifdef USE_PTY
-               .pty_master             = -1,
-               .pty_slave              = -1,
-               .pty_sigfd              = -1,
-#endif
+               .new_user               = DEFAULT_USER
        }, *su = &_su;
 
        int optc;
@@ -1371,7 +1047,7 @@ int su_main(int argc, char **argv, int mode)
 
                case 'P':
 #ifdef USE_PTY
-                       su->pty = 1;
+                       su->force_pty = 1;
 #else
                        errx(EXIT_FAILURE, _("--pty is not supported for your system"));
 #endif
@@ -1494,17 +1170,27 @@ int su_main(int argc, char **argv, int mode)
 
        supam_open_session(su);
 
+#ifdef USE_PTY
+       if (su->force_pty) {
+               ON_DBG(PTY, ul_pty_init_debug(0xffff));
+
+               su->pty = ul_new_pty(su->isterm);
+               if (!su->pty)
+                       err(EXIT_FAILURE, _("failed to allocate pty handler"));
+       }
+#endif
        create_watching_parent(su);
        /* Now we're in the child.  */
 
        change_identity(su->pwd);
-       if (!su->same_session || su->pty) {
+       if (!su->same_session) {
+               /* note that on --pty we call setsid() in ul_pty_init_slave() */
                DBG(MISC, ul_debug("call setsid()"));
                setsid();
        }
 #ifdef USE_PTY
-       if (su->pty)
-               pty_init_slave(su);
+       if (su->force_pty)
+               ul_pty_init_slave(su->pty);
 #endif
        /* Set environment after pam_open_session, which may put KRB5CCNAME
           into the pam_env, etc.  */