Files
sudo/src/utmp.c
MertsA 0a506b3801 Rewind utmp file pointer after searching for entry (#108)
getutline() advances the file pointer until it matches or reaches EOF. pututline() starts from the current position in utmp. This rewinds the file pointer to the beginning to avoid allocating additional spurious utmp entries.
2021-07-26 09:07:10 -06:00

391 lines
10 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2011-2018 Todd C. Miller <Todd.Miller@sudo.ws>
*
* 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.
*/
/*
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
*/
#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 /* HAVE_UTMPS_H */
#ifdef HAVE_GETTTYENT
# include <ttyent.h>
#endif
#include <fcntl.h>
#include <signal.h>
#include "sudo.h"
#include "sudo_exec.h"
/*
* GCC 8 warns about strncpy() where the size field is the size of the buffer.
* However, strings in utmp may not be NUL terminated so this usage is correct.
*/
#if __GNUC_PREREQ__(8, 0)
# pragma GCC diagnostic ignored "-Wstringop-truncation"
#endif
/*
* Simplify handling of different utmp types.
*/
#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;
/* Older systems have ut_name, not ut_user */
# if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
# define ut_user ut_name
# endif
#endif
/* HP-UX has __e_termination and __e_exit, others may lack the __ */
#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)
/*
* 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;
debug_decl(utmp_setid, SUDO_DEBUG_UTMP);
/* Skip over "tty" in the id if old entry did too. */
if (old != NULL) {
/* cppcheck-suppress uninitdata */
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. */
/* cppcheck-suppress uninitdata */
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);
debug_return;
}
#endif /* HAVE_STRUCT_UTMP_UT_ID */
/*
* Store time in utmp structure.
*/
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;
}
/*
* Fill in a utmp entry, using an old entry as a template if there is one.
*/
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));
if (user == NULL) {
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));
}
if (user != NULL)
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;
}
/*
* There are two basic utmp file types:
*
* POSIX: sequential access with new entries appended to the end.
* Manipulated via {get,put}[sx]?utent()
*
* Legacy: sparse file indexed by ttyslot() * sizeof(struct utmp)
*/
#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);
/* 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;
sudo_setutent();
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 = 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);
/* 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 = 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 /* !HAVE_GETUTSID && !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;
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);
/*
* Temporarily point stdin to the tty since ttyslot()
* doesn't take an argument.
*/
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 /* !HAVE_TTYSLOT */
static int
utmp_slot(const char *line, int ttyfd)
{
return -1;
}
# endif /* HAVE_GETTTYENT */
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);
/* 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, 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);
/* 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 (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 /* HAVE_GETUTSID || HAVE_GETUTXID || HAVE_GETUTID */