pam_limits: use systemd-logind instead of utmp (#822)

The utmp database is unreliable for counting logged in users, since
there is no standard which defines who should create an entry at which
time for which reason. And it has a Y2038 problem with glibc/x86-64.
Query systemd-logind for the number of user sessions instead.
This commit is contained in:
Thorsten Kukuk 2024-09-06 11:55:46 +02:00 committed by Thorsten Kukuk
parent 8401cef10c
commit f5db2603d2
2 changed files with 80 additions and 5 deletions

View File

@ -24,7 +24,7 @@ limits_conf_dir = $(SCONFIGDIR)/limits.d
AM_CFLAGS = -I$(top_srcdir)/libpam/include \
-DLIMITS_FILE_DIR=\"$(limits_conf_dir)\" \
$(WARN_CFLAGS)
$(LOGIND_CFLAGS) $(WARN_CFLAGS)
AM_LDFLAGS = -no-undefined -avoid-version -module
if HAVE_VERSIONING
AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map
@ -32,7 +32,7 @@ endif
securelib_LTLIBRARIES = pam_limits.la
pam_limits_la_LIBADD = $(top_builddir)/libpam_internal/libpam_internal.la \
$(top_builddir)/libpam/libpam.la
$(top_builddir)/libpam/libpam.la $(SYSTEMD_LIBS)
dist_secureconf_DATA = limits.conf

View File

@ -36,7 +36,12 @@
#include <sys/resource.h>
#include <limits.h>
#include <glob.h>
#ifdef USE_LOGIND
#include <systemd/sd-login.h>
#else
#include <utmp.h>
#endif
#ifndef UT_USER /* some systems have ut_name instead of ut_user */
#define UT_USER ut_user
#endif
@ -240,7 +245,6 @@ static int
check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
struct pam_limit_s *pl)
{
struct utmp *ut;
int count;
if (ctrl & PAM_DEBUG_ARG) {
@ -255,8 +259,6 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
return LOGIN_ERR;
}
setutent();
/* Because there is no definition about when an application
actually adds a utmp entry, some applications bizarrely do the
utmp call before the have PAM authenticate them to the system:
@ -273,6 +275,78 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
count = 1;
}
#ifdef USE_LOGIND
char **sessions_list;
int sessions = sd_get_sessions(&sessions_list);
/* maxlogins needs to be 2 with systemd-logind because
of the systemd --user process started with first login by
pam_systemd.
Which is also calling pam_limits, but in this very first special
case the session does already exist and is counted twice.
With start of the second session, session manager is already running
and no longer counted. */
if (limit == 1) {
pam_syslog(pamh, LOG_WARNING, "Maxlogin limit needs to be 2 or higher with systemd-logind");
return LIMIT_ERR;
}
if (sessions < 0) {
pam_syslog(pamh, LOG_ERR, "logind error getting session list: %s",
strerror(-sessions));
return LIMIT_ERR;
} else if (sessions > 0 && sessions_list != NULL && !pl->flag_numsyslogins) {
int i;
for (i = 0; i < sessions; i++) {
char *user = NULL;
char *class = NULL;
if (sd_session_get_class(sessions_list[i], &class) < 0 || class == NULL)
continue;
if (strncmp(class, "user", 4) != 0) { /* user, user-early, user-incomplete */
free (class);
continue;
}
free (class);
if (sd_session_get_username(sessions_list[i], &user) < 0 || user == NULL) {
pam_syslog(pamh, LOG_ERR, "logind error getting username: %s",
strerror(-sessions));
return LIMIT_ERR;
}
if (((pl->login_limit_def == LIMITS_DEF_USER)
|| (pl->login_limit_def == LIMITS_DEF_GROUP)
|| (pl->login_limit_def == LIMITS_DEF_DEFAULT))
&& strcmp(name, user) != 0) {
free(user);
continue;
}
if ((pl->login_limit_def == LIMITS_DEF_ALLGROUP)
&& pl->login_group != NULL
&& !pam_modutil_user_in_group_nam_nam(pamh, user, pl->login_group)) {
free(user);
continue;
}
free(user);
if (++count > limit) {
break;
}
}
for (i = 0; i < sessions; i++)
free(sessions_list[i]);
free(sessions_list);
} else {
count = sessions;
}
#else
struct utmp *ut;
setutent();
while((ut = getutent())) {
#ifdef USER_PROCESS
if (ut->ut_type != USER_PROCESS) {
@ -311,6 +385,7 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
}
}
endutent();
#endif
if (count > limit) {
if (name) {
pam_syslog(pamh, LOG_NOTICE,