#include <config.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#if defined(HAVE_UTMPS_H)
# include <utmps.h>
#elif defined(HAVE_UTMPX_H)
# include <utmpx.h>
#else
# include <utmp.h>
#endif
#ifdef HAVE_GETTTYENT
# include <ttyent.h>
#endif
#include <fcntl.h>
#include <signal.h>
#include <sudo.h>
#include <sudo_exec.h>
#if __GNUC_PREREQ__(8, 0)
# pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
#if defined(HAVE_GETUTSID)
# define sudo_getutline(u) GETUTSLINE(u)
# define sudo_pututline(u) PUTUTSLINE(u)
# define sudo_setutent() SETUTSENT()
# define sudo_endutent() ENDUTSENT()
#elif defined(HAVE_GETUTXID)
# define sudo_getutline(u) getutxline(u)
# define sudo_pututline(u) pututxline(u)
# define sudo_setutent() setutxent()
# define sudo_endutent() endutxent()
#elif defined(HAVE_GETUTID)
# define sudo_getutline(u) getutline(u)
# define sudo_pututline(u) pututline(u)
# define sudo_setutent() setutent()
# define sudo_endutent() endutent()
#endif
#if defined(HAVE_GETUTSID)
typedef struct utmps sudo_utmp_t;
#elif defined(HAVE_GETUTXID)
typedef struct utmpx sudo_utmp_t;
#else
typedef struct utmp sudo_utmp_t;
# if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
# define ut_user ut_name
# endif
#endif
#if defined(HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION)
# undef __e_termination
# define __e_termination e_termination
# undef __e_exit
# define __e_exit e_exit
#endif
#if defined(HAVE_STRUCT_UTMP_UT_ID)
static void
utmp_setid(sudo_utmp_t *old, sudo_utmp_t *new)
{
const char *line = new->ut_line;
size_t linelen = strnlen(new->ut_line, sizeof(new->ut_line));
debug_decl(utmp_setid, SUDO_DEBUG_UTMP);
if (old != NULL) {
if (strncmp(line, "tty", 3) == 0) {
const size_t idlen = MIN(sizeof(old->ut_id), 3);
if (strncmp(old->ut_id, "tty", idlen) != 0) {
line += 3;
linelen -= 3;
}
}
}
if (linelen > sizeof(new->ut_id)) {
line += linelen - sizeof(new->ut_id);
linelen = sizeof(new->ut_id);
}
strncpy(new->ut_id, line, linelen);
debug_return;
}
#endif
static void
utmp_settime(sudo_utmp_t *ut)
{
struct timeval tv;
debug_decl(utmp_settime, SUDO_DEBUG_UTMP);
if (gettimeofday(&tv, NULL) == 0) {
#if defined(HAVE_STRUCT_UTMP_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
}
debug_return;
}
static void
utmp_fill(const char *line, const char *user, sudo_utmp_t *ut_old,
sudo_utmp_t *ut_new)
{
debug_decl(utmp_file, SUDO_DEBUG_UTMP);
if (ut_old == NULL) {
memset(ut_new, 0, sizeof(*ut_new));
} else if (ut_old != ut_new) {
memcpy(ut_new, ut_old, sizeof(*ut_new));
}
strncpy(ut_new->ut_user, user, sizeof(ut_new->ut_user));
strncpy(ut_new->ut_line, line, sizeof(ut_new->ut_line));
#if defined(HAVE_STRUCT_UTMP_UT_ID)
utmp_setid(ut_old, ut_new);
#endif
#if defined(HAVE_STRUCT_UTMP_UT_PID)
ut_new->ut_pid = getpid();
#endif
utmp_settime(ut_new);
#if defined(HAVE_STRUCT_UTMP_UT_TYPE)
ut_new->ut_type = USER_PROCESS;
#endif
debug_return;
}
#if defined(HAVE_GETUTSID) || defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
bool
utmp_login(const char *from_line, const char *to_line, int ttyfd,
const char *user)
{
sudo_utmp_t utbuf, *ut_old = NULL;
bool ret = false;
debug_decl(utmp_login, SUDO_DEBUG_UTMP);
if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
to_line += sizeof(_PATH_DEV) - 1;
sudo_setutent();
if (from_line != NULL) {
if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
from_line += sizeof(_PATH_DEV) - 1;
memset(&utbuf, 0, sizeof(utbuf));
strncpy(utbuf.ut_line, from_line, sizeof(utbuf.ut_line));
ut_old = sudo_getutline(&utbuf);
sudo_setutent();
}
utmp_fill(to_line, user, ut_old, &utbuf);
if (sudo_pututline(&utbuf) != NULL)
ret = true;
sudo_endutent();
debug_return_bool(ret);
}
bool
utmp_logout(const char *line, int status)
{
bool ret = false;
sudo_utmp_t *ut, utbuf;
debug_decl(utmp_logout, SUDO_DEBUG_UTMP);
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 = sudo_getutline(&utbuf)) != NULL) {
memset(ut->ut_user, 0, sizeof(ut->ut_user));
# if defined(HAVE_STRUCT_UTMP_UT_TYPE)
ut->ut_type = DEAD_PROCESS;
# endif
# if defined(HAVE_STRUCT_UTMP_UT_EXIT)
ut->ut_exit.__e_termination = WIFSIGNALED(status) ? WTERMSIG(status) : 0;
ut->ut_exit.__e_exit = WIFEXITED(status) ? WEXITSTATUS(status) : 0;
# endif
utmp_settime(ut);
if (sudo_pututline(ut) != NULL)
ret = true;
}
debug_return_bool(ret);
}
#else
# ifdef HAVE_GETTTYENT
static int
utmp_slot(const char *line, int ttyfd)
{
int slot = 1;
struct ttyent *tty;
debug_decl(utmp_slot, SUDO_DEBUG_UTMP);
setttyent();
while ((tty = getttyent()) != NULL) {
if (strcmp(line, tty->ty_name) == 0)
break;
slot++;
}
endttyent();
debug_return_int(tty ? slot : 0);
}
# elif defined(HAVE_TTYSLOT)
static int
utmp_slot(const char *line, int ttyfd)
{
int sfd, slot;
debug_decl(utmp_slot, SUDO_DEBUG_UTMP);
if ((sfd = dup(STDIN_FILENO)) == -1)
sudo_fatal("%s", U_("unable to save stdin"));
if (dup2(ttyfd, STDIN_FILENO) == -1)
sudo_fatal("%s", U_("unable to dup2 stdin"));
slot = ttyslot();
if (dup2(sfd, STDIN_FILENO) == -1)
sudo_fatal("%s", U_("unable to restore stdin"));
close(sfd);
debug_return_int(slot);
}
# else
static int
utmp_slot(const char *line, int ttyfd)
{
return -1;
}
# endif
bool
utmp_login(const char *from_line, const char *to_line, int ttyfd,
const char *user)
{
sudo_utmp_t utbuf, *ut_old = NULL;
bool ret = false;
int slot;
FILE *fp;
debug_decl(utmp_login, SUDO_DEBUG_UTMP);
if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
to_line += sizeof(_PATH_DEV) - 1;
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;
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, user, ut_old, &utbuf);
if (fseeko(fp, slot * (off_t)sizeof(utbuf), SEEK_SET) == 0) {
if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
ret = true;
}
fclose(fp);
done:
debug_return_bool(ret);
}
bool
utmp_logout(const char *line, int status)
{
sudo_utmp_t utbuf;
bool ret = false;
FILE *fp;
debug_decl(utmp_logout, SUDO_DEBUG_UTMP);
if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
debug_return_int(ret);
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);
if (fseeko(fp, (off_t)0 - (off_t)sizeof(utbuf), SEEK_CUR) == 0) {
if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
ret = true;
}
break;
}
}
fclose(fp);
debug_return_bool(ret);
}
#endif