From 1496bfed6c92873798d6f44ece6a387ba273965e Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Tue, 8 Mar 2011 15:37:40 -0500 Subject: [PATCH] Add support for adding a utmp entry when allocating a new pty. Requires the BSD login(3) or SYSV/POSIX getutent()/getutxent(). Currently only creates a new entry if the existing tty has a utmp entry. --- config.h.in | 6 ++ configure | 100 ++++++++++++++++++++++++++- configure.in | 20 +++++- src/exec.c | 2 +- src/exec_pty.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++- src/sudo_exec.h | 2 +- 6 files changed, 302 insertions(+), 8 deletions(-) diff --git a/config.h.in b/config.h.in index 3050e0f5c..0b81e44da 100644 --- a/config.h.in +++ b/config.h.in @@ -313,6 +313,9 @@ /* Define to 1 if you have the `lockf' function. */ #undef HAVE_LOCKF +/* Define to 1 if you have the `login' function. */ +#undef HAVE_LOGIN + /* Define to 1 if you have the header file. */ #undef HAVE_LOGIN_CAP_H @@ -552,6 +555,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_UTIME_H +/* Define to 1 if you have the header file. */ +#undef HAVE_UTMP_H + /* Define to 1 if you have the `vasprintf' function. */ #undef HAVE_VASPRINTF diff --git a/configure b/configure index 56e8aaf1b..a001a5d4a 100755 --- a/configure +++ b/configure @@ -15039,7 +15039,7 @@ LIBS=$ac_save_LIBS for ac_func in strrchr sysconf tzset strftime initgroups getgroups fstat \ regcomp setlocale nl_langinfo getaddrinfo mbr_check_membership \ - setrlimit64 + setrlimit64 sysctl do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15083,7 +15083,7 @@ done fi done -for ac_func in sysctl getutxid getutid +for ac_func in getutxid getutid do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -15092,10 +15092,104 @@ eval as_val=\$$as_ac_var cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF - break + utmp=POSIX; break fi done +if test "${utmp-NONE}" = "NONE"; then + for ac_func in login +do : + ac_fn_c_check_func "$LINENO" "login" "ac_cv_func_login" +if test "x$ac_cv_func_login" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LOGIN 1 +_ACEOF + + UTMP=BSD + for ac_header in util.h utmp.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +eval as_val=\$$as_ac_Header + if test "x$as_val" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + break +fi + +done + + +else + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for login in -lutil" >&5 +$as_echo_n "checking for login in -lutil... " >&6; } +if test "${ac_cv_lib_util_login+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lutil $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char login (); +int +main () +{ +return login (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_util_login=yes +else + ac_cv_lib_util_login=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_util_login" >&5 +$as_echo "$ac_cv_lib_util_login" >&6; } +if test "x$ac_cv_lib_util_login" = x""yes; then : + + UTMP=BSD + for ac_header in util.h utmp.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +eval as_val=\$$as_ac_Header + if test "x$as_val" = x""yes; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + break +fi + +done + + case "$SUDO_LIBS" in + *-lutil*) ;; + *) SUDO_LIBS="${SUDO_LIBS} -lutil";; + esac + $as_echo "#define HAVE_LOGIN 1" >>confdefs.h + + +fi + + +fi +done + +fi for ac_func in openpty do : diff --git a/configure.in b/configure.in index da618b9d8..1a447beaa 100644 --- a/configure.in +++ b/configure.in @@ -1968,12 +1968,28 @@ dnl AC_FUNC_GETGROUPS AC_CHECK_FUNCS(strrchr sysconf tzset strftime initgroups getgroups fstat \ regcomp setlocale nl_langinfo getaddrinfo mbr_check_membership \ - setrlimit64) + setrlimit64 sysctl) AC_CHECK_FUNCS(getline, [], [ AC_LIBOBJ(getline) AC_CHECK_FUNCS(fgetln) ]) -AC_CHECK_FUNCS(sysctl getutxid getutid, [break]) +AC_CHECK_FUNCS(getutxid getutid, [utmp=POSIX; break]) +if test "${utmp-NONE}" = "NONE"; then + AC_CHECK_FUNCS(login, [ + UTMP=BSD + AC_CHECK_HEADERS(util.h utmp.h, [break]) + ], [ + AC_CHECK_LIB(util, login, [ + UTMP=BSD + AC_CHECK_HEADERS(util.h utmp.h, [break]) + case "$SUDO_LIBS" in + *-lutil*) ;; + *) SUDO_LIBS="${SUDO_LIBS} -lutil";; + esac + AC_DEFINE(HAVE_LOGIN) + ]) + ]) +fi AC_CHECK_FUNCS(openpty, [AC_CHECK_HEADERS(util.h pty.h, [break])], [ AC_CHECK_LIB(util, openpty, [ diff --git a/src/exec.c b/src/exec.c index a477f65d8..139d04a00 100644 --- a/src/exec.c +++ b/src/exec.c @@ -234,7 +234,7 @@ sudo_execve(struct command_details *details, char *argv[], char *envp[], log_io = TRUE; if (!ISSET(details->flags, CD_BACKGROUND)) { sudo_debug(8, "allocate pty for I/O logging"); - pty_setup(details->euid); + pty_setup(details->euid, user_details.tty); } } diff --git a/src/exec_pty.c b/src/exec_pty.c index a132924d2..be45d4bb1 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -52,6 +52,14 @@ #if TIME_WITH_SYS_TIME # include #endif +#if defined(HAVE_GETUTXID) +# include +#elif defined(HAVE_GETUTID) +# include +#elif defined(HAVE_UTIL_H) +# include +# include +#endif #include #include #include @@ -109,6 +117,159 @@ static void sync_ttysize(int src, int dst); static void deliver_signal(pid_t pid, int signo); static int safe_close(int fd); +#if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID) +/* + * Create ut_id from tty line and the id from the entry we are cloning. + */ +static void +utmp_setid(const char *line, const char *old_id, char *new_id, size_t idsize) +{ + size_t idlen; + + /* Skip over "tty" in the id if old entry did too. */ + if (strncmp(line, "tty", 3) == 0 && + strncmp(old_id, "tty", idsize < 3 ? idsize : 3) != 0) + line += 3; + + /* Store as much as will fit, skipping parts of the beginning as needed. */ + idlen = strlen(line); + if (idlen > idsize) { + line += (idlen - idsize); + idlen = idsize; + } + strncpy(new_id, line, idlen); +} +#endif /* HAVE_GETUTXID || HAVE_GETUTID */ + +/* + * Clone a utmp entry, updating the line, id, pid and time. + * XXX - if no existing entry, make a new one + */ +static int +utmp_doclone(const char *from_line, const char *to_line) +{ + int rval = FALSE; +#ifdef HAVE_GETUTXID + struct utmpx *ut_old, ut_new; + + memset(&ut_new, 0, sizeof(ut_new)); + strncpy(ut_new.ut_line, from_line, sizeof(ut_new.ut_line)); + setutxent(); + if ((ut_old = getutxid(&ut_new)) != NULL) { + if (ut_old != &ut_new) + memcpy(&ut_new, ut_old, sizeof(ut_new)); + strncpy(ut_new.ut_line, to_line, sizeof(ut_new.ut_line)); + utmp_setid(to_line, ut_old->ut_id, ut_new.ut_id, sizeof(ut_new.ut_id)); + ut_new.ut_pid = getpid(); + gettimeofday(&ut_new.ut_tv, NULL); + ut_new.ut_type = USER_PROCESS; + + if (pututxline(&ut_new) != NULL) + rval = TRUE; + } + endutxent(); +#elif HAVE_GETUTID + struct utmp *ut_old, ut_new; + + memset(&ut_new, 0, sizeof(ut_new)); + strncpy(ut_new.ut_line, from_line, sizeof(ut_new.ut_line)); + setutent(); + if ((ut_old = getutid(&ut_new)) != NULL) { + if (ut_old != &ut_new) + memcpy(&ut_new, ut_old, sizeof(ut_new)); + strncpy(ut_new.ut_line, to_line, sizeof(ut_new.ut_line)); + utmp_setid(to_line, ut_old->ut_id, ut_new.ut_id, sizeof(ut_new.ut_id)); + ut_new.ut_pid = getpid(); + ut_new.ut_time = time(NULL); + ut_new.ut_type = USER_PROCESS; + + if (pututline(&ut_new) != NULL) + rval = TRUE; + } + endutent(); +#elif HAVE_LOGIN + FILE *fp; + struct utmp ut; + + /* Find existing entry, update line and add as new. */ + if ((fp = fopen(_PATH_UTMP, "r")) != NULL) { + while (fread(&ut, sizeof(ut), 1, fp) == 1) { + if (ut.ut_name[0] && + strncmp(ut.ut_line, from_line, sizeof(ut.ut_line)) == 0) { + strncpy(ut.ut_line, to_line, sizeof(ut.ut_line)); + login(&ut); + rval = TRUE; + break; + } + } + fclose(fp); + } +#endif + return rval; +} + +static int +utmp_clone(const char *from_line, const char *to_line) +{ + /* Strip off /dev/ prefix from to/from line as needed. */ + if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) + from_line += sizeof(_PATH_DEV) - 1; + if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) + to_line += sizeof(_PATH_DEV) - 1; + + return utmp_doclone(from_line, to_line); +} + +/* + * Remove (zero out) the utmp entry for a line. + */ +static int +utmp_doremove(const char *line) +{ + int rval = FALSE; +#ifdef HAVE_GETUTXID + struct utmpx *ut, key; + + memset(&key, 0, sizeof(key)); + strncpy(key.ut_line, line, sizeof(key.ut_line)); + setutxent(); + if ((ut = getutxid(&key)) != NULL) { + ut->ut_type = DEAD_PROCESS; + (void)gettimeofday(&ut->ut_tv, NULL); + if (pututxline(ut) != NULL) + rval = TRUE; + } + endutxent(); +#elif HAVE_GETUTID + struct utmp *ut, key; + + memset(&key, 0, sizeof(key)); + strncpy(key.ut_line, line, sizeof(key.ut_line)); + setutent(); + if ((ut = getutid(&key)) != NULL) { + ut->ut_type = DEAD_PROCESS; + ut->ut_time = time(NULL); + if (pututline(ut) != NULL) + rval = TRUE; + } + endutent(); +#elif HAVE_LOGIN + if (logout(line) != 0) + rval = TRUE; +#endif /* HAVE_GETUTXID */ + return rval; +} + +static int +utmp_remove(const char *line) +{ + /* Strip off /dev/ prefix from to/from line as needed. */ + if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) + line += sizeof(_PATH_DEV) - 1; + + return utmp_doremove(line); +} + /* * Cleanup hook for error()/errorx() */ @@ -120,6 +281,7 @@ cleanup(int gotsignal) #ifdef HAVE_SELINUX selinux_restore_tty(); #endif + utmp_remove(slavename); } /* @@ -128,13 +290,28 @@ cleanup(int gotsignal) * and slavename globals. */ void -pty_setup(uid_t uid) +pty_setup(uid_t uid, const char *tty) { io_fds[SFD_USERTTY] = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); if (io_fds[SFD_USERTTY] != -1) { + int sfd; + if (!get_pty(&io_fds[SFD_MASTER], &io_fds[SFD_SLAVE], slavename, sizeof(slavename), uid)) error(1, "Can't get pty"); + /* + * Add entry to utmp/utmpx. + * Temporarily point stdin to the pty slave for the benefit of + * legacy utmp handling that uses ttyslot(). + */ + if ((sfd = dup(STDIN_FILENO)) == -1) + error(1, "Can't save stdin"); + if (dup2(io_fds[SFD_SLAVE], STDIN_FILENO) == -1) + error(1, "Can't dup2 stdin"); + utmp_clone(tty, slavename); + if (dup2(sfd, STDIN_FILENO) == -1) + error(1, "Can't restore stdin"); + close(sfd); } } @@ -654,6 +831,7 @@ pty_close(struct command_status *cstat) } } } + utmp_remove(slavename); } /* diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 4a7d34222..7bc3d52ec 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -39,7 +39,7 @@ int suspend_parent(int signo); void fd_set_iobs(fd_set *fdsr, fd_set *fdsw); void handler(int s); void pty_close(struct command_status *cstat); -void pty_setup(uid_t uid); +void pty_setup(uid_t uid, const char *tty); void terminate_child(pid_t pid, int use_pgrp); extern int signal_pipe[2];