Files
shadow/libmisc/utmp.c
Alejandro Colomar f45498a6c2 libmisc/write_full.c: Improve write_full()
Documentation:

-  Correct the comment documenting the function:

   write_full() doesn't write "up to" count bytes (which is write(2)'s
   behavior, and exactly what this function is designed to avoid), but
   rather exactly count bytes (on success).

-  While fixing the documentation, take the time to add a man-page-like
   comment as in other APIs.  Especially, since we'll have to document
   a few other changes from this patch, such as the modified return
   values.

-  Partial writes are still possible on error.  It's the caller's
   responsibility to handle that possibility.

API:

-  In write(2), it's useful to know how many bytes were transferred,
   since it can have short writes.  In this API, since it either writes
   it all or fails, that value is useless, and callers only want to know
   if it succeeded or not.  Thus, just return 0 or -1.

Implementation:

-  Use `== -1` instead of `< 0` to check for write(2) syscall errors.
   This is wisdom from Michael Kerrisk.  This convention is useful
   because it more explicitly tells maintainers that the only value
   which can lead to that path is -1.  Otherwise, a maintainer of the
   code might be confused to think that other negative values are
   possible.  Keep it simple.

-  The path under `if (res == 0)` was unreachable, since the loop
   condition `while (count > 0)` precludes that possibility.  Remove the
   dead code.

-  Use a temporary variable of type `const char *` to avoid a cast.

-  Rename `res`, which just holds the result from write(2), to `w`,
   which more clearly shows that it's just a very-short-lived variable
   (by it's one-letter name), and also relates itself more to write(2).
   I find it more readable.

-  Move the definition of `w` to the top of the function.  Now that the
   function is significantly shorter, the lifetime of the variable is
   clearer, and I find it more readable this way.

Use:

-  Also use `== -1` to check errors.

Cc: Christian Göttsche <cgzones@googlemail.com>
Cc: Serge Hallyn <serge@hallyn.com>
Signed-off-by: Alejandro Colomar <alx@kernel.org>
2023-08-18 20:35:15 -05:00

425 lines
10 KiB
C

/*
* SPDX-FileCopyrightText: 1989 - 1994, Julianne Frances Haugh
* SPDX-FileCopyrightText: 1996 - 1999, Marek Michałkiewicz
* SPDX-FileCopyrightText: 2001 - 2005, Tomasz Kłoczko
* SPDX-FileCopyrightText: 2008 - 2009, Nicolas François
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <config.h>
#include "defines.h"
#include "prototypes.h"
#include "getdef.h"
#include <utmp.h>
#include <assert.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <fcntl.h>
#include "alloc.h"
#ident "$Id$"
/*
* is_my_tty -- determine if "tty" is the same TTY stdin is using
*/
static bool is_my_tty (const char tty[UT_LINESIZE])
{
char full_tty[STRLEN("/dev/") + UT_LINESIZE + 1];
/* tmptty shall be bigger than full_tty */
static char tmptty[sizeof(full_tty) + 1];
full_tty[0] = '\0';
if (tty[0] != '/')
strcpy (full_tty, "/dev/");
strncat (full_tty, tty, UT_LINESIZE);
if ('\0' == tmptty[0]) {
const char *tname = ttyname (STDIN_FILENO);
if (NULL != tname)
(void) strlcpy (tmptty, tname, sizeof(tmptty));
}
if ('\0' == tmptty[0]) {
(void) puts (_("Unable to determine your tty name."));
exit (EXIT_FAILURE);
}
return strcmp (full_tty, tmptty) == 0;
}
/*
* failtmp - update the cumulative failure log
*
* failtmp updates the (struct utmp) formatted failure log which
* maintains a record of all login failures.
*/
static void failtmp (const char *username, const struct utmp *failent)
{
const char *ftmp;
int fd;
/*
* Get the name of the failure file. If no file has been defined
* in login.defs, don't do this.
*/
ftmp = getdef_str ("FTMP_FILE");
if (NULL == ftmp) {
return;
}
/*
* Open the file for append. It must already exist for this
* feature to be used.
*/
if (access (ftmp, F_OK) != 0) {
return;
}
fd = open (ftmp, O_WRONLY | O_APPEND);
if (-1 == fd) {
SYSLOG ((LOG_WARN,
"Can't append failure of user %s to %s.",
username, ftmp));
return;
}
/*
* Append the new failure record and close the log file.
*/
if ( (write_full(fd, failent, sizeof *failent) == -1)
|| (close (fd) != 0)) {
SYSLOG ((LOG_WARN,
"Can't append failure of user %s to %s.",
username, ftmp));
(void) close (fd);
}
}
/*
* get_current_utmp - return the most probable utmp entry for the current
* session
*
* The utmp file is scanned for an entry with the same process ID.
* The line entered by the *getty / telnetd, etc. should also match
* the current terminal.
*
* When an entry is returned by get_current_utmp, and if the utmp
* structure has a ut_id field, this field should be used to update
* the entry information.
*
* Return NULL if no entries exist in utmp for the current process.
*/
static
/*@null@*/ /*@only@*/struct utmp *get_current_utmp (void)
{
struct utmp *ut;
struct utmp *ret = NULL;
setutent ();
/* First, try to find a valid utmp entry for this process. */
while ((ut = getutent ()) != NULL) {
if ( (ut->ut_pid == getpid ())
#ifdef HAVE_STRUCT_UTMP_UT_ID
&& ('\0' != ut->ut_id[0])
#endif
#ifdef HAVE_STRUCT_UTMP_UT_TYPE
&& ( (LOGIN_PROCESS == ut->ut_type)
|| (USER_PROCESS == ut->ut_type))
#endif
/* A process may have failed to close an entry
* Check if this entry refers to the current tty */
&& is_my_tty (ut->ut_line)) {
break;
}
}
if (NULL != ut) {
ret = XMALLOC(1, struct utmp);
memcpy (ret, ut, sizeof (*ret));
}
endutent ();
return ret;
}
int get_session_host (char **out)
{
char *hostname = NULL;
struct utmp *ut = NULL;
int ret = 0;
ut = get_current_utmp();
#ifdef HAVE_STRUCT_UTMP_UT_HOST
if ((ut != NULL) && (ut->ut_host[0] != '\0')) {
hostname = XMALLOC(sizeof(ut->ut_host) + 1, char);
strncpy (hostname, ut->ut_host, sizeof (ut->ut_host));
hostname[sizeof (ut->ut_host)] = '\0';
*out = hostname;
free (ut);
} else {
*out = NULL;
ret = -2;
}
#else
*out = NULL;
ret = -2;
#endif /* HAVE_STRUCT_UTMP_UT_HOST */
return ret;
}
#ifndef USE_PAM
/*
* Some systems already have updwtmp() and possibly updwtmpx(). Others
* don't, so we re-implement these functions if necessary.
*/
#ifndef HAVE_UPDWTMP
static void updwtmp (const char *filename, const struct utmp *ut)
{
int fd;
fd = open (filename, O_APPEND | O_WRONLY, 0);
if (fd >= 0) {
write_full(fd, ut, sizeof(*ut));
close (fd);
}
}
#endif /* ! HAVE_UPDWTMP */
#endif /* ! USE_PAM */
/*
* prepare_utmp - prepare an utmp entry so that it can be logged in a
* utmp/wtmp file.
*
* It accepts an utmp entry in input (ut) to return an entry with
* the right ut_id. This is typically an entry returned by
* get_current_utmp
* If ut is NULL, ut_id will be forged based on the line argument.
*
* The ut_host field of the input structure may also be kept, and is
* used to define the ut_addr/ut_addr_v6 fields. (if these fields
* exist)
*
* Other fields are discarded and filed with new values (if they
* exist).
*
* The returned structure shall be freed by the caller.
*/
static
/*@only@*/struct utmp *prepare_utmp (const char *name,
const char *line,
const char *host,
/*@null@*/const struct utmp *ut)
{
struct timeval tv;
char *hostname = NULL;
struct utmp *utent;
assert (NULL != name);
assert (NULL != line);
if ( (NULL != host)
&& ('\0' != host[0])) {
hostname = XMALLOC(strlen(host) + 1, char);
strcpy (hostname, host);
#ifdef HAVE_STRUCT_UTMP_UT_HOST
} else if ( (NULL != ut)
&& ('\0' != ut->ut_host[0])) {
hostname = XMALLOC(sizeof(ut->ut_host) + 1, char);
strncpy (hostname, ut->ut_host, sizeof (ut->ut_host));
hostname[sizeof (ut->ut_host)] = '\0';
#endif /* HAVE_STRUCT_UTMP_UT_HOST */
}
if (strncmp(line, "/dev/", 5) == 0) {
line += 5;
}
utent = XCALLOC (1, struct utmp);
#ifdef HAVE_STRUCT_UTMP_UT_TYPE
utent->ut_type = USER_PROCESS;
#endif /* HAVE_STRUCT_UTMP_UT_TYPE */
utent->ut_pid = getpid ();
strncpy (utent->ut_line, line, sizeof (utent->ut_line) - 1);
#ifdef HAVE_STRUCT_UTMP_UT_ID
if (NULL != ut) {
strncpy (utent->ut_id, ut->ut_id, sizeof (utent->ut_id));
} else {
/* XXX - assumes /dev/tty?? */
strncpy (utent->ut_id, line + 3, sizeof (utent->ut_id) - 1);
}
#endif /* HAVE_STRUCT_UTMP_UT_ID */
#ifdef HAVE_STRUCT_UTMP_UT_NAME
strncpy (utent->ut_name, name, sizeof (utent->ut_name));
#endif /* HAVE_STRUCT_UTMP_UT_NAME */
#ifdef HAVE_STRUCT_UTMP_UT_USER
strncpy (utent->ut_user, name, sizeof (utent->ut_user) - 1);
#endif /* HAVE_STRUCT_UTMP_UT_USER */
if (NULL != hostname) {
struct addrinfo *info = NULL;
#ifdef HAVE_STRUCT_UTMP_UT_HOST
strncpy (utent->ut_host, hostname, sizeof (utent->ut_host) - 1);
#endif /* HAVE_STRUCT_UTMP_UT_HOST */
#ifdef HAVE_STRUCT_UTMP_UT_SYSLEN
utent->ut_syslen = MIN (strlen (hostname),
sizeof (utent->ut_host));
#endif /* HAVE_STRUCT_UTMP_UT_SYSLEN */
#if defined(HAVE_STRUCT_UTMP_UT_ADDR) || defined(HAVE_STRUCT_UTMP_UT_ADDR_V6)
if (getaddrinfo (hostname, NULL, NULL, &info) == 0) {
/* getaddrinfo might not be reliable.
* Just try to log what may be useful.
*/
if (info->ai_family == AF_INET) {
struct sockaddr_in *sa =
(struct sockaddr_in *) info->ai_addr;
#ifdef HAVE_STRUCT_UTMP_UT_ADDR
memcpy (&(utent->ut_addr),
&(sa->sin_addr),
MIN (sizeof (utent->ut_addr),
sizeof (sa->sin_addr)));
#endif /* HAVE_STRUCT_UTMP_UT_ADDR */
#ifdef HAVE_STRUCT_UTMP_UT_ADDR_V6
memcpy (utent->ut_addr_v6,
&(sa->sin_addr),
MIN (sizeof (utent->ut_addr_v6),
sizeof (sa->sin_addr)));
} else if (info->ai_family == AF_INET6) {
struct sockaddr_in6 *sa =
(struct sockaddr_in6 *) info->ai_addr;
memcpy (utent->ut_addr_v6,
&(sa->sin6_addr),
MIN (sizeof (utent->ut_addr_v6),
sizeof (sa->sin6_addr)));
#endif /* HAVE_STRUCT_UTMP_UT_ADDR_V6 */
}
freeaddrinfo (info);
}
#endif /* HAVE_STRUCT_UTMP_UT_ADDR || HAVE_STRUCT_UTMP_UT_ADDR_V6 */
free (hostname);
}
/* ut_exit is only for DEAD_PROCESS */
utent->ut_session = getsid (0);
if (gettimeofday (&tv, NULL) == 0) {
#ifdef HAVE_STRUCT_UTMP_UT_TIME
utent->ut_time = tv.tv_sec;
#endif /* HAVE_STRUCT_UTMP_UT_TIME */
#ifdef HAVE_STRUCT_UTMP_UT_XTIME
utent->ut_xtime = tv.tv_usec;
#endif /* HAVE_STRUCT_UTMP_UT_XTIME */
#ifdef HAVE_STRUCT_UTMP_UT_TV
utent->ut_tv.tv_sec = tv.tv_sec;
utent->ut_tv.tv_usec = tv.tv_usec;
#endif /* HAVE_STRUCT_UTMP_UT_TV */
}
return utent;
}
/*
* setutmp - Update an entry in utmp and log an entry in wtmp
*
* Return 1 on failure and 0 on success.
*/
static int setutmp (struct utmp *ut)
{
int err = 0;
assert (NULL != ut);
setutent ();
if (pututline (ut) == NULL) {
err = 1;
}
endutent ();
#ifndef USE_PAM
/* This is done by pam_lastlog */
updwtmp (_WTMP_FILE, ut);
#endif /* ! USE_PAM */
return err;
}
int update_utmp (const char *user,
const char *tty,
const char *host)
{
struct utmp *utent, *ut;
utent = get_current_utmp ();
if (utent == NULL) {
return -1;
}
ut = prepare_utmp (user, tty, host, utent);
(void) setutmp (ut); /* make entry in the utmp & wtmp files */
free (utent);
free (ut);
return 1;
}
void record_failure(const char *failent_user,
const char *tty,
const char *hostname)
{
struct utmp *utent, *failent;
if (getdef_str ("FTMP_FILE") != NULL) {
utent = get_current_utmp ();
failent = prepare_utmp (failent_user, tty, hostname, utent);
failtmp (failent_user, failent);
free (utent);
free (failent);
}
}
unsigned long active_sessions_count(const char *name, unsigned long limit)
{
struct utmp *ut;
unsigned long count = 0;
setutent ();
while ((ut = getutent ()))
{
if (USER_PROCESS != ut->ut_type) {
continue;
}
if ('\0' == ut->ut_user[0]) {
continue;
}
if (strncmp (name, ut->ut_user, sizeof (ut->ut_user)) != 0) {
continue;
}
count++;
if (count > limit) {
break;
}
}
endutent ();
return count;
}