Merge pull request #2745 from matt335672/wtmp

Add utmp/wtmp support
This commit is contained in:
matt335672 2024-02-21 14:22:16 +00:00 committed by GitHub
commit b2c0c506e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 393 additions and 41 deletions

View File

@ -110,10 +110,11 @@ jobs:
# define the standard environment variable used in the rest of the steps.
CONF_FLAGS_amd64_min: "--disable-ipv6 --disable-jpeg --disable-fuse --disable-mp3lame
--disable-fdkaac --disable-opus --disable-rfxcodec --disable-painter
--disable-pixman"
--disable-pixman --disable-utmp"
CONF_FLAGS_amd64_max: "--enable-ipv6 --enable-jpeg --enable-fuse --enable-mp3lame
--enable-fdkaac --enable-opus --enable-rfxcodec --enable-painter
--enable-pixman --with-imlib2 --with-freetype2 --enable-tests"
--enable-pixman --enable-utmp
--with-imlib2 --with-freetype2 --enable-tests"
CONF_FLAGS_i386_max: "--enable-ipv6 --enable-jpeg --enable-mp3lame
--enable-opus --enable-rfxcodec --enable-painter
--disable-pixman --with-imlib2 --with-freetype2

View File

@ -3302,10 +3302,10 @@ g_set_allusercontext(int uid)
/*****************************************************************************/
/* does not work in win32
returns pid of process that exits or zero if signal occurred
an exit_status struct can optionally be passed in to get the
a proc_exit_status struct can optionally be passed in to get the
exit status of the child */
int
g_waitchild(struct exit_status *e)
g_waitchild(struct proc_exit_status *e)
{
#if defined(_WIN32)
return 0;
@ -3313,14 +3313,14 @@ g_waitchild(struct exit_status *e)
int wstat;
int rv;
struct exit_status dummy;
struct proc_exit_status dummy;
if (e == NULL)
{
e = &dummy; // Set this, then throw it away
}
e->reason = E_XR_UNEXPECTED;
e->reason = E_PXR_UNEXPECTED;
e->val = 0;
rv = waitpid(-1, &wstat, WNOHANG);
@ -3335,12 +3335,12 @@ g_waitchild(struct exit_status *e)
}
else if (WIFEXITED(wstat))
{
e->reason = E_XR_STATUS_CODE;
e->reason = E_PXR_STATUS_CODE;
e->val = WEXITSTATUS(wstat);
}
else if (WIFSIGNALED(wstat))
{
e->reason = E_XR_SIGNAL;
e->reason = E_PXR_SIGNAL;
e->val = WTERMSIG(wstat);
}
@ -3381,10 +3381,14 @@ g_waitpid(int pid)
Note that signal handlers are established with BSD-style semantics,
so this call is NOT interrupted by a signal */
struct exit_status
struct proc_exit_status
g_waitpid_status(int pid)
{
struct exit_status exit_status = {.reason = E_XR_UNEXPECTED, .val = 0};
struct proc_exit_status exit_status =
{
.reason = E_PXR_UNEXPECTED,
.val = 0
};
#if !defined(_WIN32)
if (pid > 0)
@ -3399,12 +3403,12 @@ g_waitpid_status(int pid)
{
if (WIFEXITED(status))
{
exit_status.reason = E_XR_STATUS_CODE;
exit_status.reason = E_PXR_STATUS_CODE;
exit_status.val = WEXITSTATUS(status);
}
if (WIFSIGNALED(status))
{
exit_status.reason = E_XR_SIGNAL;
exit_status.reason = E_PXR_SIGNAL;
exit_status.val = WTERMSIG(status);
}
}

View File

@ -23,16 +23,16 @@
#include "arch.h"
enum exit_reason
enum proc_exit_reason
{
E_XR_STATUS_CODE = 0, ///< 'val' contains exit status
E_XR_SIGNAL, ///< 'val' contains a signal number
E_XR_UNEXPECTED
E_PXR_STATUS_CODE = 0, ///< 'val' contains exit status
E_PXR_SIGNAL, ///< 'val' contains a signal number
E_PXR_UNEXPECTED
};
struct exit_status
struct proc_exit_status
{
enum exit_reason reason;
enum proc_exit_reason reason;
int val;
};
@ -352,9 +352,9 @@ int g_setlogin(const char *name);
*/
int g_set_allusercontext(int uid);
#endif
int g_waitchild(struct exit_status *e);
int g_waitchild(struct proc_exit_status *e);
int g_waitpid(int pid);
struct exit_status g_waitpid_status(int pid);
struct proc_exit_status g_waitpid_status(int pid);
/*
* Sets the process group ID of the indicated process to the specified value.
* (POSIX.1)

View File

@ -6,6 +6,7 @@ AC_DEFINE([VERSION_YEAR], 2024, [Copyright year])
AC_CONFIG_HEADERS(config_ac.h:config_ac-h.in)
AM_INIT_AUTOMAKE([1.7.2 foreign])
AC_CONFIG_MACRO_DIR([m4])
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC
AC_PROG_CXX
AC_C_CONST
@ -183,6 +184,11 @@ AC_ARG_ENABLE(rdpsndaudin, AS_HELP_STRING([--enable-rdpsndaudin],
[], [enable_rdpsndaudin=no])
AM_CONDITIONAL(XRDP_RDPSNDAUDIN, [test x$enable_rdpsndaudin = xyes])
AC_ARG_ENABLE(utmp, AS_HELP_STRING([--enable-utmp],
[Update utmp (default: no)]),
[], [enable_utmp=no])
AM_CONDITIONAL(XRDP_UTMP, [test x$enable_utmp = xyes])
AC_ARG_WITH(imlib2, AS_HELP_STRING([--with-imlib2=ARG], [imlib2 library to use for non-BMP backgrounds (ARG=yes/no/<abs-path>)]),,)
AC_ARG_WITH(freetype2, AS_HELP_STRING([--with-freetype2=ARG], [freetype2 library to use for rendering fonts (ARG=yes/no/<abs-path>)]),,)
@ -515,6 +521,15 @@ AC_CHECK_HEADER([X11/extensions/Xrandr.h], [],
[AC_MSG_ERROR([please install libxrandr-dev or libXrandr-devel])],
[#include <X11/Xlib.h>])
if test "x$enable_utmp" = "xyes"
then
AC_CHECK_HEADERS(utmp.h utmpx.h)
# Test for non-standard extensions in struct utmpx
AXRDP_CHECK_UTMPX_MEMBER_EXISTS([ut_host], [HAVE_UTMPX_UT_HOST])
AXRDP_CHECK_UTMPX_MEMBER_EXISTS([ut_exit], [HAVE_UTMPX_UT_EXIT])
fi
CFLAGS="$save_CFLAGS"
# perform unit tests if libcheck and libmocka found
@ -632,6 +647,12 @@ echo " ipv6only $enable_ipv6only"
echo " vsock $enable_vsock"
echo " auth mechanism $auth_mech"
echo " rdpsndaudin $enable_rdpsndaudin"
echo " utmp support $enable_utmp"
if test x$enable_utmp = xyes; then
echo " utmpx.ut_host $ac_cv_utmpx_has_ut_host"
echo " utmpx.ut_exit $ac_cv_utmpx_has_ut_exit"
fi
echo
echo " with imlib2 $use_imlib2"
echo " with freetype2 $use_freetype2"

View File

@ -1,4 +1,3 @@
#%PAM-1.0
auth include system-remote-login
-auth optional pam_gnome_keyring.so
-auth optional pam_kwallet5.so
@ -8,5 +7,8 @@ account include system-remote-login
password include system-remote-login
session include system-remote-login
# For wtmp/lastlog support uncomment one of the following lines:-
#session optional pam_lastlog.so quiet
#session optional pam_lastlog2.so silent
-session optional pam_gnome_keyring.so auto_start
-session optional pam_kwallet5.so auto_start

View File

@ -9,6 +9,10 @@ auth required pam_env.so readenv=1 envfile=/etc/default/locale
@include common-password
# Set the loginuid process attribute.
session required pam_loginuid.so
# Update wtmp/lastlog
session optional pam_lastlog.so quiet
@include common-session
-session optional pam_gnome_keyring.so auto_start
-session optional pam_kwallet5.so auto_start

View File

@ -1,5 +1,11 @@
#%PAM-1.0
auth include password-auth
account include password-auth
# Set the loginuid process attribute.
session required pam_loginuid.so
# Update wtmp/lastlog
session optional pam_lastlog.so quiet
session include password-auth
password include password-auth

View File

@ -1,5 +1,11 @@
#%PAM-1.0
auth include common-auth
account include common-account
# Set the loginuid process attribute.
session required pam_loginuid.so
# Update lastlog database
session optional pam_lastlog2.so silent
session include common-session
password include common-password

View File

@ -2,4 +2,8 @@
auth include system-auth
account include system-auth
password include system-auth
# For wtmp/lastlog support uncomment one of the following lines:-
#session optional pam_lastlog.so quiet
#session optional pam_lastlog2.so silent
session include system-auth

43
m4/axrdp.m4 Normal file
View File

@ -0,0 +1,43 @@
# SYNOPSIS
#
# AXRDP_CHECK_UTMPX_MEMBER_EXISTS(MEMBER, COMPILE-DEFINE)
#
# EXAMPLE
#
# AXRDP_CHECK_UTMPX_MEMBER_EXISTS([ut_exit], [HAVE_UTMPX_UT_EXIT])
#
# DESCRIPTION
#
# If the member MEMBER exists in the utmpx struct, the COMPILE-DEFINE
# is set for the C compiler.
#
# The shell variable 'ac_cv_utmpx_has_$MEMBER' is set to 'yes' or 'no'
# and cached
#
AC_DEFUN([AXRDP_CHECK_UTMPX_MEMBER_EXISTS],
[
AS_VAR_PUSHDEF([x_var], [ac_cv_utmpx_has_$1])
AS_VAR_PUSHDEF([x_define], [$2])
AC_CACHE_CHECK(
[for $1 in struct utmpx],
[x_var],
[AC_COMPILE_IFELSE(
[AC_LANG_SOURCE([[
# include <utmpx.h>
# include <stddef.h>
int main()
{
return offsetof(struct utmpx,$1);
}]])],
[AS_VAR_SET([x_var], [yes])],
[AS_VAR_SET([x_var], [no])])]
)
AS_VAR_IF(
[x_var],
[yes],
[AC_DEFINE([x_define], [1], [Define if '$1' is in struct utmpx.])])
AS_VAR_POPDEF([x_var])
AS_VAR_POPDEF([x_define])
])

View File

@ -9,6 +9,10 @@ AM_CPPFLAGS = \
SESEXEC_EXTRA_LIBS =
if XRDP_UTMP
AM_CPPFLAGS += -DUSE_UTMP
endif
pkglibexec_PROGRAMS = \
xrdp-sesexec
@ -25,6 +29,8 @@ xrdp_sesexec_SOURCES = \
env.h \
login_info.c \
login_info.h \
sessionrecord.c \
sessionrecord.h \
xauth.c \
xauth.h \
xwait.c \

View File

@ -241,7 +241,7 @@ sesexec_terminate_main_loop(int status)
static void
process_sigchld_event(void)
{
struct exit_status e;
struct proc_exit_status e;
int pid;
// Check for any finished children

View File

@ -47,6 +47,7 @@
#include "login_info.h"
#include "os_calls.h"
#include "sesexec.h"
#include "sessionrecord.h"
#include "string_calls.h"
#include "xauth.h"
#include "xwait.h"
@ -656,6 +657,7 @@ session_start_wrapped(struct login_info *login_info,
}
else
{
utmp_login(window_manager_pid, s->display, login_info);
LOG(LOG_LEVEL_INFO,
"Starting the xrdp channel server for display :%d",
s->display);
@ -807,11 +809,11 @@ cleanup_sockets(int uid, int display)
/******************************************************************************/
static void
exit_status_to_str(const struct exit_status *e, char buff[], int bufflen)
exit_status_to_str(const struct proc_exit_status *e, char buff[], int bufflen)
{
switch (e->reason)
{
case E_XR_STATUS_CODE:
case E_PXR_STATUS_CODE:
if (e->val == 0)
{
g_snprintf(buff, bufflen, "exit code zero");
@ -822,7 +824,7 @@ exit_status_to_str(const struct exit_status *e, char buff[], int bufflen)
}
break;
case E_XR_SIGNAL:
case E_PXR_SIGNAL:
{
char sigstr[MAXSTRSIGLEN];
g_snprintf(buff, bufflen, "signal %s",
@ -840,7 +842,7 @@ exit_status_to_str(const struct exit_status *e, char buff[], int bufflen)
void
session_process_child_exit(struct session_data *sd,
int pid,
const struct exit_status *e)
const struct proc_exit_status *e)
{
if (pid == sd->x_server)
{
@ -860,7 +862,7 @@ session_process_child_exit(struct session_data *sd,
{
int wm_wait_time = g_time1() - sd->start_time;
if (e->reason == E_XR_STATUS_CODE && e->val == 0)
if (e->reason == E_PXR_STATUS_CODE && e->val == 0)
{
LOG(LOG_LEVEL_INFO,
"Window manager (pid %d, display %d) "
@ -886,6 +888,7 @@ session_process_child_exit(struct session_data *sd,
sd->win_mgr, sd->params.display, wm_wait_time);
}
utmp_logout(sd->win_mgr, sd->params.display, e);
sd->win_mgr = -1;
if (sd->x_server > 0)

View File

@ -35,7 +35,7 @@
#include "xrdp_constants.h"
struct login_info;
struct exit_status;
struct proc_exit_status;
/**
* Information used to start a session
@ -89,7 +89,7 @@ session_start(struct login_info *login_info,
void
session_process_child_exit(struct session_data *sd,
int pid,
const struct exit_status *e);
const struct proc_exit_status *e);
/**
* Returns a count of active processes in the session

View File

@ -0,0 +1,200 @@
/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Emmanuel Blindauer 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* str2memcpy() is taken from util-linux/include/strutils.h v2.39 which
* has the following header:-
*
* No copyright is claimed. This code is in the public domain; do with
* it what you wish.
*/
/**
*
* @file sessionrecord.c
* @brief utmp handling code
*
* wtmp/lastlog/btmp is handled by PAM or (on FreeBSD) UTX
*
* Idea: Only implement actual utmp, i.e. utmpx for 99%.
* See http://80386.nl/unix/utmpx/
*/
#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif
#include "sessionrecord.h"
#include "login_info.h"
#include "log.h"
// Operational mode of add_xtmp_entry()
//
// We can't use USER_PROCESS/DEAD_PROCESS directly, as they
// won't be available for platforms without USE_UTMP
enum add_xtmp_mode
{
MODE_LOGIN,
MODE_LOGOUT
};
#ifdef USE_UTMP
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_UTMPX_H
#include <utmpx.h>
typedef struct utmpx _utmp;
#else
#include <utmp.h>
typedef struct utmp _utmp;
#endif
#include "os_calls.h"
#include "string_calls.h"
#define XRDP_LINE_FORMAT "xrdp:%d"
// ut_id is a very small field on some platforms, so use the display
// number in hex
#define XRDP_ID_FORMAT ":%x"
/******************************************************************************/
/**
* utmp-specific strncpy() replacement
*
* @param dest Destination pointer
* @param src Source pointer
* @param n bytes to copy
*
* This is like strncpy(), but based on memcpy(), so compilers and static
* analyzers do not complain when sizeof(destination) is the same as 'n' and
* result is not terminated by zero.
*
* ONLY use this function to copy string to logs with fixed sizes
* (wtmp/utmp. ...) where string terminator is optional.
*/
static inline void *__attribute__((nonnull (1)))
str2memcpy(void *dest, const char *src, size_t n)
{
size_t bytes = strlen(src) + 1;
if (bytes > n)
{
bytes = n;
}
memcpy(dest, src, bytes);
return dest;
}
/******************************************************************************/
/**
* Prepare the utmp struct and write it.
*
* @param pid PID of session manager
* @param display Display number of session
* @param login_info Login info (NULL for MODE_LOGOUT)
* @param mode see enum add_xtmp_mode
* @param e Exit status (NULL unless MODE_LOGOUT)
*/
static void
add_xtmp_entry(int pid, int display, const struct login_info *login_info,
enum add_xtmp_mode mode, const struct proc_exit_status *e)
{
char idbuff[16];
char str_display[16];
_utmp ut;
struct timeval tv;
g_memset(&ut, 0, sizeof(ut));
g_snprintf(str_display, sizeof(str_display), XRDP_LINE_FORMAT, display);
g_snprintf(idbuff, sizeof(idbuff), XRDP_ID_FORMAT, display);
gettimeofday(&tv, NULL);
ut.ut_type = (mode == MODE_LOGIN) ? USER_PROCESS : DEAD_PROCESS;
ut.ut_pid = pid;
str2memcpy(ut.ut_id, idbuff, sizeof(ut.ut_id));
// Linux utmp(5) suggests ut_line, ut_time, ut_user, and ut_host
// are not set for a DEAD_PROCESS
if (ut.ut_type != DEAD_PROCESS)
{
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;
str2memcpy(ut.ut_line, str_display, sizeof(ut.ut_line));
if (login_info != NULL)
{
str2memcpy(ut.ut_user, login_info->username, sizeof(ut.ut_user));
#ifdef HAVE_UTMPX_UT_HOST
str2memcpy(ut.ut_host, login_info->ip_addr, sizeof(ut.ut_host));
#endif
}
}
#ifdef HAVE_UTMPX_UT_EXIT
if (e != NULL && e->reason == E_PXR_STATUS_CODE)
{
ut.ut_exit.e_exit = e->val;
}
else if (e != NULL && e->reason == E_PXR_SIGNAL)
{
ut.ut_exit.e_termination = e->val;
}
#endif
/* update the utmp file */
/* open utmp */
setutxent();
/* add the computed entry */
pututxline(&ut);
/* closes utmp */
endutxent();
}
#else // USE_UTMP
static void
add_xtmp_entry(int pid, int display, const struct login_info *login_info,
short state, const struct proc_exit_status *e)
{
}
#endif
/******************************************************************************/
void
utmp_login(int pid, int display, const struct login_info *login_info)
{
log_message(LOG_LEVEL_DEBUG,
"adding login info for utmp: %d - %d - %s - %s",
pid, display, login_info->username, login_info->ip_addr);
add_xtmp_entry(pid, display, login_info, MODE_LOGIN, NULL);
}
/******************************************************************************/
void
utmp_logout(int pid, int display, const struct proc_exit_status *exit_status)
{
log_message(LOG_LEVEL_DEBUG, "adding logout info for utmp: %d - %d",
pid, display);
add_xtmp_entry(pid, display, NULL, MODE_LOGOUT, exit_status);
}

View File

@ -0,0 +1,52 @@
/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Emmanuel Blindauer 2017
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
* @file sessionrecord.h
* @brief utmp handling code
*
*/
#ifndef SESSIONRECORD_H
#define SESSIONRECORD_H
struct login_info;
struct proc_exit_status;
/**
* @brief Record login in utmp
*
* @param pid PID of window manager
* @param display Display number
* @param login_info Information about logged in user
*/
void
utmp_login(int pid, int display, const struct login_info *login_info);
/**
* @brief Record logout in utmp
*
* @param pid PID of window manager
* @param display Display number
* @param exit_status Exit status of process
*/
void
utmp_logout(int pid, int display, const struct proc_exit_status *exit_status);
#endif

View File

@ -142,7 +142,7 @@ wait_for_xserver(uid_t uid,
}
else
{
struct exit_status e;
struct proc_exit_status e;
fd[0] = -1; // File descriptor closed by fclose()
log_waitforx_messages(dp);
@ -150,11 +150,11 @@ wait_for_xserver(uid_t uid,
e = g_waitpid_status(pid);
switch (e.reason)
{
case E_XR_STATUS_CODE:
case E_PXR_STATUS_CODE:
rv = (enum xwait_status)e.val;
break;
case E_XR_SIGNAL:
case E_PXR_SIGNAL:
{
char sigstr[MAXSTRSIGLEN];
LOG(LOG_LEVEL_ERROR,

View File

@ -79,7 +79,7 @@ END_TEST
/******************************************************************************/
START_TEST(test_g_signal_child_stop_1)
{
struct exit_status e;
struct proc_exit_status e;
g_reset_wait_obj(g_wobj1);
ck_assert_int_eq(g_is_wait_obj_set(g_wobj1), 0);
@ -98,7 +98,7 @@ START_TEST(test_g_signal_child_stop_1)
e = g_waitpid_status(pid);
ck_assert_int_eq(e.reason, E_XR_STATUS_CODE);
ck_assert_int_eq(e.reason, E_PXR_STATUS_CODE);
ck_assert_int_eq(e.val, 45);
// Try another one to make sure the signal handler is still in place.
@ -116,7 +116,7 @@ START_TEST(test_g_signal_child_stop_1)
e = g_waitpid_status(pid);
ck_assert_int_eq(e.reason, E_XR_SIGNAL);
ck_assert_int_eq(e.reason, E_PXR_SIGNAL);
ck_assert_int_eq(e.val, SIGSEGV);
// Clean up
@ -133,7 +133,7 @@ START_TEST(test_g_signal_child_stop_2)
int pids[CHILD_COUNT];
unsigned int i;
struct exit_status e;
struct proc_exit_status e;
g_reset_wait_obj(g_wobj1);
ck_assert_int_eq(g_is_wait_obj_set(g_wobj1), 0);
@ -157,7 +157,7 @@ START_TEST(test_g_signal_child_stop_2)
for (i = 0 ; i < CHILD_COUNT; ++i)
{
e = g_waitpid_status(pids[i]);
ck_assert_int_eq(e.reason, E_XR_STATUS_CODE);
ck_assert_int_eq(e.reason, E_PXR_STATUS_CODE);
ck_assert_int_eq(e.val, (i + 1));
}
@ -246,12 +246,12 @@ START_TEST(test_waitpid_not_interrupted_by_sig)
g_reset_wait_obj(g_wobj1);
g_set_alarm(set_wobj1, 1);
struct exit_status e = g_waitpid_status(child_pid);
struct proc_exit_status e = g_waitpid_status(child_pid);
// We should have had the alarm...
ck_assert_int_ne(g_is_wait_obj_set(g_wobj1), 0);
// ..and got the status of the child
ck_assert_int_eq(e.reason, E_XR_STATUS_CODE);
ck_assert_int_eq(e.reason, E_PXR_STATUS_CODE);
ck_assert_int_eq(e.val, 42);
// Clean up

View File

@ -856,12 +856,12 @@ xrdp_listen_conn_in(struct trans *self, struct trans *new_self)
static void
process_pending_sigchld_events(void)
{
struct exit_status e;
struct proc_exit_status e;
int pid;
while ((pid = g_waitchild(&e)) > 0)
{
if (e.reason == E_XR_SIGNAL)
if (e.reason == E_PXR_SIGNAL)
{
char sigstr[MAXSTRSIGLEN];
LOG(LOG_LEVEL_ERROR,