diff --git a/Makefile.in b/Makefile.in
index 46f9617..e216252 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -116,7 +116,7 @@ SRCS = aix.c alias.c alloc.c audit.c boottime.c bsm_audit.c check.c \
        sudo_edit.c sudo_nss.c term.c testsudoers.c tgetpass.c toke.c toke.l \
        toke_util.c tsgetgrpw.c utimes.c vasgroups.c visudo.c zero_bytes.c \
        redblack.c selinux.c sesh.c sudoreplay.c getdate.c getdate.y getline.c \
-       timestr.c ttyname.c $(AUTH_SRCS)
+       timestr.c ttyname.c utmp.c $(AUTH_SRCS)
 
 AUTH_SRCS = auth/afs.c auth/aix_auth.c auth/bsdauth.c auth/dce.c auth/fwtk.c \
 	    auth/kerb4.c auth/kerb5.c auth/pam.c auth/passwd.c auth/rfc1938.c \
@@ -139,7 +139,7 @@ COMMON_OBJS = alias.o alloc.o defaults.o error.o gram.o \
 SUDO_OBJS = $(AUTH_OBJS) @SUDO_OBJS@ audit.o boottime.o check.o env.o \
 	    exec.o gettime.o goodpath.o fileops.o find_path.o \
 	    interfaces.o lbuf.o logging.o parse.o parse_args.o set_perms.o \
-	    sudo.o sudo_edit.o sudo_nss.o tgetpass.o ttyname.o
+	    sudo.o sudo_edit.o sudo_nss.o tgetpass.o ttyname.o utmp.o
 
 VISUDO_OBJS = visudo.o fileops.o gettime.o goodpath.o find_path.o
 
@@ -469,6 +469,8 @@ uncompr.o: $(srcdir)/zlib/zlib.h zlib/zconf.h
 	$(CC) -c -I. -I$(srcdir)/zlib $(CFLAGS) $(srcdir)/zlib/uncompr.c
 zutil.o: $(srcdir)/zlib/zutil.h $(srcdir)/zlib/zlib.h zlib/zconf.h
 	$(CC) -c -I. -I$(srcdir)/zlib $(CFLAGS) $(srcdir)/zlib/zutil.c
+utmp.o: $(srcdir)/utmp.c $(SUDODEP)
+	$(CC) -c $(CPPFLAGS) $(CFLAGS) $(DEFS) $(srcdir)/utmp.c
 
 @DEV@varsub: $(srcdir)/configure.in
 @DEV@	printf 's#@%s@#1#\ns#@%s@#1#\ns#@%s@#1#\ns#@%s@#/etc#g\ns#@%s@#/usr/local#g\ns#@%s@#4#g\ns#@%s@#1m#g\n' SEMAN BAMAN LCMAN sysconfdir prefix mansectform mansectsu > $@; sed -n '/Begin initial values for man page substitution/,/End initial values for man page substitution/{;p;}' $(srcdir)/configure.in | sed -e '/^#/d' -e 's/^/s#@/' -e 's/=[\\"]*/@#/' -e 's/[\\"]*$$/#g/' >> $@
diff --git a/aclocal.m4 b/aclocal.m4
index 255b91f..ab8c93f 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -80,6 +80,24 @@ fi
 ])dnl
 
 dnl
+dnl check for utmp file
+dnl
+AC_DEFUN(SUDO_PATH_UTMP, [AC_MSG_CHECKING([for utmp file path])
+found=no
+for p in "/var/run/utmp" "/var/adm/utmp" "/etc/utmp"; do
+    if test -r "$p"; then
+	found=yes
+	AC_MSG_RESULT([$p])
+	SUDO_DEFINE_UNQUOTED(_PATH_UTMP, "$p")
+	break
+    fi
+done
+if test X"$found" != X"yes"; then
+    AC_MSG_RESULT([not found])
+fi
+])dnl
+
+dnl
 dnl Where the log file goes, use /var/log if it exists, else /{var,usr}/adm
 dnl
 AC_DEFUN([SUDO_LOGFILE], [AC_MSG_CHECKING(for log file location)
diff --git a/config.h.in b/config.h.in
index fd67c4c..e562b8e 100644
--- a/config.h.in
+++ b/config.h.in
@@ -187,6 +187,9 @@
 /* Define to 1 if you have the `gettimeofday' function. */
 #undef HAVE_GETTIMEOFDAY
 
+/* Define to 1 if you have the `getttyent' function. */
+#undef HAVE_GETTTYENT
+
 /* Define to 1 if you have the `getuserattr' function. */
 #undef HAVE_GETUSERATTR
 
@@ -514,6 +517,33 @@
 /* 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
 
@@ -571,6 +601,9 @@
 /* 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
 
@@ -589,6 +622,9 @@
 /* Define to 1 if you have the <utime.h> header file. */
 #undef HAVE_UTIME_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
 
diff --git a/configure b/configure
index d4e7d58..d16493b 100755
--- a/configure
+++ b/configure
@@ -14110,7 +14110,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"
@@ -14979,6 +14979,131 @@ _ACEOF
 
 ;;
 esac
+
+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 :
@@ -15158,7 +15283,7 @@ LIBS=$ac_save_LIBS
 for ac_func in strchr strrchr memchr memcpy memset sysconf tzset \
 	       strftime setrlimit initgroups getgroups fstat gettimeofday \
 	       regcomp setlocale nl_langinfo getaddrinfo getsid setenv \
-	       mbr_check_membership setrlimit64
+	       mbr_check_membership 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"
@@ -15263,7 +15388,22 @@ fi
 done
 
 
-for ac_func in sysctl getutxid getutid
+utmp_style=LEGACY
+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"
+eval as_val=\$$as_ac_var
+   if test "x$as_val" = x""yes; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+ utmp_style=POSIX; break
+fi
+done
+
+if test "$utmp_style" = "LEGACY"; then
+    for ac_func in getttyent ttyslot
 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"
@@ -15276,6 +15416,7 @@ _ACEOF
 fi
 done
 
+fi
 
 for ac_func in openpty
 do :
@@ -18659,6 +18800,28 @@ if test -n "$blibpath"; then
     fi
 fi
 
+if test "$utmp_style" = "LEGACY"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for utmp file path" >&5
+$as_echo_n "checking for utmp file path... " >&6; }
+found=no
+for p in "/var/run/utmp" "/var/adm/utmp" "/etc/utmp"; do
+    if test -r "$p"; then
+	found=yes
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $p" >&5
+$as_echo "$p" >&6; }
+	cat >>confdefs.h <<EOF
+#define _PATH_UTMP "$p"
+EOF
+
+	break
+    fi
+done
+if test X"$found" != X"yes"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: not found" >&5
+$as_echo "not found" >&6; }
+fi
+
+fi
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for log file location" >&5
 $as_echo_n "checking for log file location... " >&6; }
 if test -n "$with_logpath"; then
diff --git a/configure.in b/configure.in
index 157da03..40fc14b 100644
--- a/configure.in
+++ b/configure.in
@@ -1912,7 +1912,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.
@@ -1986,13 +1986,28 @@ case "$DEFS" in
     *)			AC_TYPE_SIGNAL;;
 esac
 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
 AC_FUNC_GETGROUPS
 AC_CHECK_FUNCS(strchr strrchr memchr memcpy memset sysconf tzset \
 	       strftime setrlimit initgroups getgroups fstat gettimeofday \
 	       regcomp setlocale nl_langinfo getaddrinfo getsid setenv \
-	       mbr_check_membership setrlimit64)
+	       mbr_check_membership setrlimit64 sysctl)
 AC_CHECK_FUNCS(getline, [], [
     AC_LIBOBJ(getline)
     AC_CHECK_FUNCS(fgetln)
@@ -2002,7 +2017,11 @@ AC_CHECK_FUNCS(setsid, [], [
     AC_FUNC_SETPGRP
 ])
 
-AC_CHECK_FUNCS(sysctl getutxid getutid, [break])
+utmp_style=LEGACY
+AC_CHECK_FUNCS(getutxid getutid, [utmp_style=POSIX; break])
+if test "$utmp_style" = "LEGACY"; then
+    AC_CHECK_FUNCS(getttyent ttyslot, [break])
+fi
 
 AC_CHECK_FUNCS(openpty, [AC_CHECK_HEADERS(util.h pty.h, [break])], [
     AC_CHECK_LIB(util, openpty, [
@@ -2759,6 +2778,9 @@ fi
 dnl
 dnl Check for log file, timestamp and iolog locations
 dnl
+if test "$utmp_style" = "LEGACY"; then
+    SUDO_PATH_UTMP
+fi
 SUDO_LOGFILE
 SUDO_TIMEDIR
 SUDO_IO_LOGDIR
diff --git a/exec.c b/exec.c
index 19dbad2..9875f79 100644
--- a/exec.c
+++ b/exec.c
@@ -208,7 +208,7 @@ restore_signals()
  * we fact that we have two different controlling terminals to deal with.
  */
 int
-sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode)
+sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode, tty)
     const char *path;
     char *argv[];
     char *envp[];
@@ -216,6 +216,7 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode)
     struct command_status *cstat;
     int dowait;
     int bgmode;
+    const char *tty;
 {
     int maxfd, n, nready, sv[2];
     int rbac_enabled = 0;
@@ -243,7 +244,7 @@ sudo_execve(path, argv, envp, uid, cstat, dowait, bgmode)
     log_io = def_log_output || def_log_input || def_use_pty;
     if (log_io) {
 	if (!bgmode)
-	    pty_setup(uid);
+	    pty_setup(uid, tty);
 	io_log_open();
 	dowait = TRUE;
     }
diff --git a/exec_pty.c b/exec_pty.c
index ec54a1f..958e369 100644
--- a/exec_pty.c
+++ b/exec_pty.c
@@ -116,14 +116,17 @@ static int safe_close __P((int fd));
  * and slavename globals.
  */
 void
-pty_setup(uid)
-    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) {
 	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.
+	 */
+	utmp_login(tty, slavename, io_fds[SFD_SLAVE]);
     }
 }
 
@@ -540,6 +543,7 @@ pty_close(cstat)
 	    }
 	}
     }
+    utmp_logout(slavename);
 }
 
 
diff --git a/pathnames.h.in b/pathnames.h.in
index 82969e8..b2c9e11 100644
--- a/pathnames.h.in
+++ b/pathnames.h.in
@@ -130,6 +130,10 @@
 #undef _PATH_MAILDIR
 #endif /* _PATH_MAILDIR */
 
+#ifndef _PATH_UTMP
+#undef _PATH_UTMP
+#endif /* _PATH_UTMP */
+
 #ifndef _PATH_SUDO_SESH
 #undef _PATH_SUDO_SESH
 #endif /* _PATH_SUDO_SESH */
diff --git a/sudo.c b/sudo.c
index 6f54129..f19e3a1 100644
--- a/sudo.c
+++ b/sudo.c
@@ -943,7 +943,7 @@ run_command(path, argv, envp, uid, dowait)
     cstat.val = 0;
 
     sudo_execve(path, argv, envp, uid, &cstat, dowait,
-	ISSET(sudo_mode, MODE_BACKGROUND));
+	ISSET(sudo_mode, MODE_BACKGROUND), user_tty);
 
     switch (cstat.type) {
     case CMD_ERRNO:
diff --git a/sudo.h b/sudo.h
index 4991952..d81c93d 100644
--- a/sudo.h
+++ b/sudo.h
@@ -236,7 +236,7 @@ void validate_env_vars	__P((struct list_member *));
 
 /* exec.c */
 int sudo_execve __P((const char *path, char *argv[], char *envp[], uid_t uid,
-    struct command_status *cstat, int dowait, int bgmode));
+    struct command_status *cstat, int dowait, int bgmode, const char *tty));
 void save_signals __P((void));
 void restore_signals __P((void));
 
diff --git a/sudo_exec.h b/sudo_exec.h
index ea43d55..871d6e1 100644
--- a/sudo_exec.h
+++ b/sudo_exec.h
@@ -39,7 +39,11 @@ int suspend_parent __P((int signo));
 void fd_set_iobs __P((fd_set *fdsr, fd_set *fdsw));
 RETSIGTYPE handler __P((int s));
 void pty_close __P((struct command_status *cstat));
-void pty_setup __P((uid_t uid));
+void pty_setup(uid_t uid, const char *tty);
 extern int signal_pipe[2];
 
+/* utmp.c */
+int utmp_login(const char *from_line, const char *to_line, int ttyfd);
+int utmp_logout(const char *line);
+
 #endif /* _SUDO_EXEC_H */
diff --git a/utmp.c b/utmp.c
new file mode 100644
index 0000000..853ce5a
--- /dev/null
+++ b/utmp.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2011 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <stdio.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif /* STDC_HEADERS */
+#ifdef HAVE_STRING_H
+# if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#ifdef HAVE_UTMPX_H
+# include <utmpx.h>
+#else
+# include <utmp.h>
+#endif /* HAVE_UTMPX_H */
+#ifdef HAVE_GETTTYENT
+# include <ttyent.h>
+#endif
+#include <fcntl.h>
+
+#include "pwd.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 the new ut_line and the old ut_id.
+ */
+static void
+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) {
+	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 > sizeof(new->ut_id)) {
+	line += idlen - sizeof(new->ut_id);
+	idlen = sizeof(new->ut_id);
+    }
+    strncpy(new->ut_id, line, idlen);
+}
+#endif /* HAVE_GETUTXID || HAVE_GETUTID */
+
+/*
+ * Store time in utmp structure.
+ */
+static void
+utmp_settime(sudo_utmp_t *ut)
+{
+    struct timeval tv;
+
+    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_name), 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
+}
+
+/*
+ * 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_login(const char *from_line, const char *to_line, int ttyfd)
+{
+    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;
+    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;
+}
+
+int
+utmp_logout(const char *line)
+{
+    int rval = FALSE;
+    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(&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;
+# endif
+	utmp_settime(ut);
+	if (pututxline(ut) != NULL)
+	    rval = TRUE;
+    }
+    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;
+
+    /* 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;
+
+    /* 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;
+
+    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;
+    }
+    fclose(fp);
+
+done:
+    return rval;
+}
+
+int
+utmp_logout(const char *line)
+{
+    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 rval;
+}
+#endif /* HAVE_GETUTXID || HAVE_GETUTID */
