]> granicus.if.org Git - sudo/commitdiff
Redo utmp handling. If no getutent()/getutxent() is available,
authorTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 14 Mar 2011 14:20:47 +0000 (10:20 -0400)
committerTodd C. Miller <Todd.Miller@courtesan.com>
Mon, 14 Mar 2011 14:20:47 +0000 (10:20 -0400)
assume a ttyslot-based utmp.  If getttyent() is available, use
that directly instead of ttyslot() so we don't have to do the
stdin dup2 dance.

config.h.in
configure
configure.in
src/Makefile.in
src/exec_pty.c
src/sudo_exec.h
src/utmp.c

index 68066a8431eb08983223e220ee18a77f878dc94e..043894acd0c70fd9cb71ecc6a41758f96ab8a7e8 100644 (file)
    passwords) */
 #undef HAVE_GETSPWUID
 
+/* Define to 1 if you have the `getttyent' function. */
+#undef HAVE_GETTTYENT
+
 /* Define to 1 if you have the `getuserattr' function. */
 #undef HAVE_GETUSERATTR
 
 /* 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 <login_cap.h> header file. */
 #undef HAVE_LOGIN_CAP_H
 
 /* Define to 1 if the system has the type `struct in6_addr'. */
 #undef HAVE_STRUCT_IN6_ADDR
 
+/* Define to 1 if `ut_id' is a member of `struct utmpx'. */
+#undef HAVE_STRUCT_UTMPX_UT_ID
+
+/* Define to 1 if `ut_pid' is a member of `struct utmpx'. */
+#undef HAVE_STRUCT_UTMPX_UT_PID
+
+/* Define to 1 if `ut_tv' is a member of `struct utmpx'. */
+#undef HAVE_STRUCT_UTMPX_UT_TV
+
+/* Define to 1 if `ut_type' is a member of `struct utmpx'. */
+#undef HAVE_STRUCT_UTMPX_UT_TYPE
+
+/* Define to 1 if `ut_id' is a member of `struct utmp'. */
+#undef HAVE_STRUCT_UTMP_UT_ID
+
+/* Define to 1 if `ut_pid' is a member of `struct utmp'. */
+#undef HAVE_STRUCT_UTMP_UT_PID
+
+/* Define to 1 if `ut_tv' is a member of `struct utmp'. */
+#undef HAVE_STRUCT_UTMP_UT_TV
+
+/* Define to 1 if `ut_type' is a member of `struct utmp'. */
+#undef HAVE_STRUCT_UTMP_UT_TYPE
+
+/* Define to 1 if `ut_user' is a member of `struct utmp'. */
+#undef HAVE_STRUCT_UTMP_UT_USER
+
 /* Define to 1 if your struct stat has an st_mtim member */
 #undef HAVE_ST_MTIM
 
 /* Define to 1 if you have struct timespec in sys/time.h */
 #undef HAVE_TIMESPEC
 
+/* Define to 1 if you have the `ttyslot' function. */
+#undef HAVE_TTYSLOT
+
 /* Define to 1 if you have the `tzset' function. */
 #undef HAVE_TZSET
 
 /* Define to 1 if you have the <utime.h> header file. */
 #undef HAVE_UTIME_H
 
-/* Define to 1 if you have the <utmp.h> header file. */
-#undef HAVE_UTMP_H
+/* Define to 1 if you have the <utmpx.h> header file. */
+#undef HAVE_UTMPX_H
 
 /* Define to 1 if you have the `vasprintf' function. */
 #undef HAVE_VASPRINTF
index f75e285d4ef051dc0501f3ebaa86f2c810001d61..c5ba7cfd4ca9e55e29a877bc2adc9d123b0b71fe 100755 (executable)
--- a/configure
+++ b/configure
@@ -14232,7 +14232,7 @@ $as_echo "#define TIME_WITH_SYS_TIME 1" >>confdefs.h
 
 fi
 
-for ac_header in malloc.h paths.h utime.h netgroup.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h
+for ac_header in malloc.h paths.h utime.h netgroup.h utmpx.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.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"
@@ -15053,6 +15053,129 @@ $as_echo "#define HAVE_SA_LEN 1" >>confdefs.h
 fi
 
 
+if test $ac_cv_header_utmpx_h = "yes"; then
+    ac_fn_c_check_member "$LINENO" "struct utmpx" "ut_id" "ac_cv_member_struct_utmpx_ut_id" "
+       #include <sys/types.h>
+       #include <utmpx.h>
+
+"
+if test "x$ac_cv_member_struct_utmpx_ut_id" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMPX_UT_ID 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmpx" "ut_pid" "ac_cv_member_struct_utmpx_ut_pid" "
+       #include <sys/types.h>
+       #include <utmpx.h>
+
+"
+if test "x$ac_cv_member_struct_utmpx_ut_pid" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMPX_UT_PID 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmpx" "ut_tv" "ac_cv_member_struct_utmpx_ut_tv" "
+       #include <sys/types.h>
+       #include <utmpx.h>
+
+"
+if test "x$ac_cv_member_struct_utmpx_ut_tv" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMPX_UT_TV 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmpx" "ut_type" "ac_cv_member_struct_utmpx_ut_type" "
+       #include <sys/types.h>
+       #include <utmpx.h>
+
+"
+if test "x$ac_cv_member_struct_utmpx_ut_type" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMPX_UT_TYPE 1
+_ACEOF
+
+
+fi
+
+else
+    ac_fn_c_check_member "$LINENO" "struct utmp" "ut_id" "ac_cv_member_struct_utmp_ut_id" "
+       #include <sys/types.h>
+       #include <utmp.h>
+
+"
+if test "x$ac_cv_member_struct_utmp_ut_id" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMP_UT_ID 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmp" "ut_pid" "ac_cv_member_struct_utmp_ut_pid" "
+       #include <sys/types.h>
+       #include <utmp.h>
+
+"
+if test "x$ac_cv_member_struct_utmp_ut_pid" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMP_UT_PID 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmp" "ut_tv" "ac_cv_member_struct_utmp_ut_tv" "
+       #include <sys/types.h>
+       #include <utmp.h>
+
+"
+if test "x$ac_cv_member_struct_utmp_ut_tv" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMP_UT_TV 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmp" "ut_type" "ac_cv_member_struct_utmp_ut_type" "
+       #include <sys/types.h>
+       #include <utmp.h>
+
+"
+if test "x$ac_cv_member_struct_utmp_ut_type" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMP_UT_TYPE 1
+_ACEOF
+
+
+fi
+ac_fn_c_check_member "$LINENO" "struct utmp" "ut_user" "ac_cv_member_struct_utmp_ut_user" "
+       #include <sys/types.h>
+       #include <utmp.h>
+
+"
+if test "x$ac_cv_member_struct_utmp_ut_user" = x""yes; then :
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_STRUCT_UTMP_UT_USER 1
+_ACEOF
+
+
+fi
+
+fi
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking type of array argument to getgroups" >&5
 $as_echo_n "checking type of array argument to getgroups... " >&6; }
 if test "${ac_cv_type_getgroups+set}" = set; then :
 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
+    for ac_func in getttyent ttyslot
 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
+  as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+eval as_val=\$$as_ac_var
    if test "x$as_val" = x""yes; then :
   cat >>confdefs.h <<_ACEOF
-#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+#define `$as_echo "HAVE_$ac_func" | $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
 
index 6d85742d6ee218ac75a91d10fb23cb76526bf092..86f925b1c1228c94a543086885e2c6337cad9abb 100644 (file)
@@ -1900,7 +1900,7 @@ dnl
 AC_HEADER_STDC
 AC_HEADER_DIRENT
 AC_HEADER_TIME
-AC_CHECK_HEADERS(malloc.h paths.h utime.h netgroup.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h)
+AC_CHECK_HEADERS(malloc.h paths.h utime.h netgroup.h utmpx.h sys/sockio.h sys/bsdtypes.h sys/select.h sys/stropts.h sys/sysmacros.h)
 dnl
 dnl Check for large file support.  HP-UX 11.23 has a broken sys/type.h
 dnl when large files support is enabled so work around it.
@@ -1966,6 +1966,21 @@ SUDO_TYPE_DEV_T
 SUDO_TYPE_INO_T
 SUDO_UID_T_LEN
 SUDO_SOCK_SA_LEN
+dnl
+dnl Check for utmp/utmpx struct members.
+dnl
+if test $ac_cv_header_utmpx_h = "yes"; then
+    AC_CHECK_MEMBERS([struct utmpx.ut_id, struct utmpx.ut_pid, struct utmpx.ut_tv, struct utmpx.ut_type], [], [], [
+       #include <sys/types.h>
+       #include <utmpx.h>
+    ])
+else
+    AC_CHECK_MEMBERS([struct utmp.ut_id, struct utmp.ut_pid, struct utmp.ut_tv, struct utmp.ut_type, struct utmp.ut_user], [], [], [
+       #include <sys/types.h>
+       #include <utmp.h>
+    ])
+fi
+
 dnl
 dnl Function checks
 dnl
@@ -1979,20 +1994,7 @@ AC_CHECK_FUNCS(getline, [], [
 ])
 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)
-       ])
-    ])
+    AC_CHECK_FUNCS(getttyent ttyslot, [break])
 fi
 
 AC_CHECK_FUNCS(openpty, [AC_CHECK_HEADERS(util.h pty.h, [break])], [
index 3400579201d0117f0ebad98ed39f2710ec5a903e..9b4a3b49dc4372b185c13762238ccff648cd2e90 100644 (file)
@@ -72,7 +72,7 @@ PROGS = @PROGS@
 
 OBJS = conversation.o error.o exec.o exec_pty.o get_pty.o net_ifs.o \
        load_plugins.o parse_args.o sudo.o sudo_edit.o tgetpass.o \
-       ttysize.o @SUDO_OBJS@
+       ttysize.o utmp.o @SUDO_OBJS@
 
 LIBOBJDIR = $(top_builddir)/@ac_config_libobj_dir@/
 
index e02f72f26b7e69c1242859088ab33ff50d16d23e..6c7a3d6c698c25799b1090b4ae433e5d230826a3 100644 (file)
@@ -118,7 +118,7 @@ cleanup(int gotsignal)
 #ifdef HAVE_SELINUX
     selinux_restore_tty();
 #endif
-    utmp_remove(slavename);
+    utmp_logout(slavename);
 }
 
 /*
@@ -131,24 +131,13 @@ 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);
+       utmp_login(tty, slavename, io_fds[SFD_SLAVE]);
     }
 }
 
@@ -667,7 +656,7 @@ pty_close(struct command_status *cstat)
            }
        }
     }
-    utmp_remove(slavename);
+    utmp_logout(slavename);
 }
 
 /*
index 24bb5521cb389df8ffc642bdc36ad05aeee39925..1484ae1b68939fcce9fb63c3ed9cdad7936c7b04 100644 (file)
@@ -43,7 +43,7 @@ void terminate_child(pid_t pid, int use_pgrp);
 extern int signal_pipe[2];
 
 /* utmp.c */
-int utmp_clone(const char *from_line, const char *to_line);
-int utmp_remove(const char *line);
+int utmp_login(const char *from_line, const char *to_line, int ttyfd);
+int utmp_logout(const char *line);
 
 #endif /* _SUDO_EXEC_H */
index f7a55de363e9456526514979d6efed34b3e8b293..f557703cea5519428fa885318a98457cbb99c565 100644 (file)
 #if TIME_WITH_SYS_TIME
 # include <time.h>
 #endif
-#if defined(HAVE_GETUTXID)
+#ifdef HAVE_UTMPX_H
 # include <utmpx.h>
-#elif defined(HAVE_GETUTID)
-# include <utmp.h>
-#elif defined(HAVE_UTIL_H)
-# include <util.h>
+#else
 # include <utmp.h>
+#endif /* HAVE_UTMPX_H */
+#ifdef HAVE_GETTTYENT
+# include <ttyent.h>
 #endif
+#include <fcntl.h>
 
 #include "sudo.h"
 #include "sudo_exec.h"
 
+/*
+ * Simplify handling of utmp vs. utmpx
+ */
+#if !defined(HAVE_GETUTXID) && defined(HAVE_GETUTID)
+# define getutxline(u) getutline(u)
+# define pututxline(u) pututline(u)
+# define setutxent     setutent(u)
+# define endutxent     endutent(u)
+#endif /* !HAVE_GETUTXID && HAVE_GETUTID */
+
+#ifdef HAVE_GETUTXID
+typedef struct utmpx sudo_utmp_t;
+#else
+typedef struct utmp sudo_utmp_t;
+/* Older systems have ut_name, not us_user */
+# if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
+#  define ut_user ut_name
+# endif
+#endif
+
 #if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
 /*
- * Create ut_id from tty line and the id from the entry we are cloning.
+ * Create ut_id from the new ut_line and the old ut_id.
  */
 static void
-utmp_setid(const char *line, const char *old_id, char *new_id, size_t idsize)
+utmp_setid(sudo_utmp_t *old, sudo_utmp_t *new)
 {
+    const char *line = new->ut_line;
     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;
+    if (strncmp(line, "tty", 3) == 0) {
+       idlen = MIN(sizeof(old->ut_id), 3);
+       if (strncmp(old->ut_id, "tty", idlen) != 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;
+    if (idlen > sizeof(new->ut_id)) {
+       line += idlen - sizeof(new->ut_id);
+       idlen = sizeof(new->ut_id);
     }
-    strncpy(new_id, line, idlen);
+    strncpy(new->ut_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
+ * Store time in utmp structure.
  */
-static int
-utmp_doclone(const char *from_line, const char *to_line)
+static void
+utmp_settime(sudo_utmp_t *ut)
 {
-    int rval = FALSE;
-#ifdef HAVE_GETUTXID
-    struct utmpx *ut_old, ut_new;
+    struct timeval tv;
 
-    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);
+    gettimeofday(&tv, NULL);
+
+#if defined(HAVE_STRUCT_UTMP_UT_TV) || defined(HAVE_STRUCT_UTMPX_UT_TV)
+    ut->ut_tv.tv_sec = tv.tv_sec;
+    ut->ut_tv.tv_usec = tv.tv_usec;
+#else
+    ut->ut_time = tv.tv_sec;
+#endif
+}
+
+/*
+ * Fill in a utmp entry, using an old entry as a template if there is one.
+ */
+static void
+utmp_fill(const char *line, sudo_utmp_t *ut_old, sudo_utmp_t *ut_new)
+{
+    if (ut_old == NULL) {
+       memset(ut_new, 0, sizeof(*ut_new));
+       strncpy(ut_new->ut_user, user_details.username, sizeof(ut_new->ut_user));
+    } else if (ut_old != ut_new) {
+       memcpy(ut_new, ut_old, sizeof(*ut_new));
     }
+    strncpy(ut_new->ut_line, line, sizeof(ut_new->ut_line));
+#if defined(HAVE_STRUCT_UTMPX_UT_ID) || defined(HAVE_STRUCT_UTMP_UT_ID)
+    utmp_setid(ut_old, ut_new);
+#endif
+#if defined(HAVE_STRUCT_UTMPX_UT_PID) || defined(HAVE_STRUCT_UTMP_UT_PID)
+    ut_new->ut_pid = getpid();
+#endif
+    utmp_settime(ut_new);
+#if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
+    ut_new->ut_type = USER_PROCESS;
 #endif
-    return rval;
 }
 
+/*
+ * There are two basic utmp file types:
+ *
+ *  POSIX:  sequential access with new entries appended to the end.
+ *         Manipulated via {get,put}utent()/{get,put}getutxent().
+ *
+ *  Legacy: sparse file indexed by ttyslot() * sizeof(struct utmp)
+ */
+#if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
 int
-utmp_clone(const char *from_line, const char *to_line)
+utmp_login(const char *from_line, const char *to_line, int ttyfd)
 {
-    /* 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;
+    sudo_utmp_t utbuf, *ut_old = NULL;
+    int rval = FALSE;
+
+    /* Strip off /dev/ prefix from line as needed. */
     if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
        to_line += sizeof(_PATH_DEV) - 1;
-   
-    return utmp_doclone(from_line, to_line);
+    setutxent();
+    if (from_line != NULL) {
+       if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+           from_line += sizeof(_PATH_DEV) - 1;
+
+       /* Lookup old line. */
+       memset(&utbuf, 0, sizeof(utbuf));
+       strncpy(utbuf.ut_line, from_line, sizeof(utbuf.ut_line));
+       ut_old = getutxline(&utbuf);
+    }
+    utmp_fill(to_line, ut_old, &utbuf);
+    if (pututxline(&utbuf) != NULL)
+       rval = TRUE;
+    endutxent();
+
+    return rval;
 }
 
-/*
- * Remove (zero out) the utmp entry for a line.
- */
-static int
-utmp_doremove(const char *line)
+int
+utmp_logout(const char *line)
 {
     int rval = FALSE;
-#ifdef HAVE_GETUTXID
-    struct utmpx *ut, key;
+    sudo_utmp_t *ut, utbuf;
+
+    /* Strip off /dev/ prefix from line as needed. */
+    if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+       line += sizeof(_PATH_DEV) - 1;
    
-    memset(&key, 0, sizeof(key));
-    strncpy(key.ut_line, line, sizeof(key.ut_line));
-    setutxent();
-    if ((ut = getutxid(&key)) != NULL) {
+    memset(&utbuf, 0, sizeof(utbuf));
+    strncpy(utbuf.ut_line, line, sizeof(utbuf.ut_line));
+    if ((ut = getutxline(&utbuf)) != NULL) {
+       memset(ut->ut_user, 0, sizeof(ut->ut_user));
+# if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
        ut->ut_type = DEAD_PROCESS;
-       (void)gettimeofday(&ut->ut_tv, NULL);
+# endif
+       utmp_settime(ut);
        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)
+    return rval;
+}
+
+#else /* !HAVE_GETUTXID && !HAVE_GETUTID */
+
+/*
+ * Find the slot for the specified line (tty name and file descriptor).
+ * Returns a slot suitable for seeking into utmp on success or <= 0 on error.
+ * If getttyent() is available we can use that to compute the slot.
+ */
+# ifdef HAVE_GETTTYENT
+static int
+utmp_slot(const char *line, int ttyfd)
+{
+    int slot = 1;
+    struct ttyent *tty;
+
+    setttyent();
+    while ((tty = getttyent()) != NULL) {
+       if (strcmp(line, tty->ty_name) == 0)
+           break;
+       slot++;
+    }
+    endttyent();
+    return tty ? slot : 0;
+}
+# else
+static int
+utmp_slot(const char *line, int ttyfd)
+{
+    int sfd, slot;
+
+    /*
+     * Temporarily point stdin to the tty since ttyslot()
+     * doesn't take an argument.
+     */
+    if ((sfd = dup(STDIN_FILENO)) == -1)
+       error(1, "Can't save stdin");
+    if (dup2(ttyfd, STDIN_FILENO) == -1)
+       error(1, "Can't dup2 stdin");
+    slot = ttyslot();
+    if (dup2(sfd, STDIN_FILENO) == -1)
+       error(1, "Can't restore stdin");
+    close(sfd);
+
+    return slot;
+}
+# endif /* HAVE_GETTTYENT */
+
+int
+utmp_login(const char *from_line, const char *to_line, int ttyfd)
+{
+    sudo_utmp_t utbuf, *ut_old = NULL;
+    int slot, rval = FALSE;
+    FILE *fp;
+
+    /* Find slot for new entry. */
+    slot = utmp_slot(to_line, ttyfd);
+    if (slot <= 0)
+       goto done;
+
+    if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
+       goto done;
+
+    /* Strip off /dev/ prefix from line as needed. */
+    if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+       to_line += sizeof(_PATH_DEV) - 1;
+    if (from_line != NULL) {
+       if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
+           from_line += sizeof(_PATH_DEV) - 1;
+
+       /* Lookup old line. */
+       while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
+# ifdef HAVE_STRUCT_UTMP_UT_ID
+           if (utbuf.ut_type != LOGIN_PROCESS && utbuf.ut_type != USER_PROCESS)
+               continue;
+# endif
+           if (utbuf.ut_user[0] &&
+               !strncmp(utbuf.ut_line, from_line, sizeof(utbuf.ut_line))) {
+               ut_old = &utbuf;
+               break;
+           }
+       }
+    }
+    utmp_fill(to_line, ut_old, &utbuf);
+    if (fseek(fp, slot * (long)sizeof(utbuf), SEEK_SET) == 0) {
+       if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
            rval = TRUE;
     }
-    endutent();
-#elif HAVE_LOGIN
-    if (logout(line) != 0)
-       rval = TRUE;
-#endif /* HAVE_GETUTXID */
+    fclose(fp);
+
+done:
     return rval;
 }
 
 int
-utmp_remove(const char *line)
+utmp_logout(const char *line)
 {
-    /* Strip off /dev/ prefix from to/from line as needed. */
+    sudo_utmp_t utbuf;
+    int rval = FALSE;
+    FILE *fp;
+
+    if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
+       return rval;
+
+    /* Strip off /dev/ prefix from line as needed. */
     if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
        line += sizeof(_PATH_DEV) - 1;
+   
+    while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
+       if (!strncmp(utbuf.ut_line, line, sizeof(utbuf.ut_line))) {
+           memset(utbuf.ut_user, 0, sizeof(utbuf.ut_user));
+# if defined(HAVE_STRUCT_UTMP_UT_TYPE)
+           utbuf.ut_type = DEAD_PROCESS;
+# endif
+           utmp_settime(&utbuf);
+           /* Back up and overwrite record. */
+           if (fseek(fp, 0L - (long)sizeof(utbuf), SEEK_CUR) == 0) {
+               if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
+                   rval = TRUE;
+           }
+           break;
+       }
+    }
+    fclose(fp);
 
-    return utmp_doremove(line);
+    return rval;
 }
+#endif /* HAVE_GETUTXID || HAVE_GETUTID */