pam_xdg: new port: all-around carefree XDG Base Directories package

This commit is contained in:
Steffen Nurpmeso 2021-01-30 23:10:12 +01:00
parent b9c269e2db
commit 254e705c27
7 changed files with 609 additions and 0 deletions

8
pam_xdg/.footprint Normal file
View File

@ -0,0 +1,8 @@
drwxr-xr-x root/root lib/
drwxr-xr-x root/root lib/security/
-rwxr-xr-x root/root lib/security/pam_xdg.so
drwxr-xr-x root/root usr/
drwxr-xr-x root/root usr/share/
drwxr-xr-x root/root usr/share/man/
drwxr-xr-x root/root usr/share/man/man8/
-rw-r--r-- root/root usr/share/man/man8/pam_xdg.8.gz

7
pam_xdg/.signature Normal file
View File

@ -0,0 +1,7 @@
untrusted comment: verify with /etc/ports/contrib.pub
RWSagIOpLGJF3yVeUzlfQ7+UJq2gIuabqKJqhiWq7h2/UY5x8mlWVY7NEag9ndV9ypuodnfpLrP0QWzNjqO3eRJ1b5zTk3MRnAs=
SHA256 (Pkgfile) = e1b2ba87fd768518b4f81f3f9fb1fcce6780b2538cded18600564532e86f6a05
SHA256 (.footprint) = 56d789b652e6167f5fb93e1e6d48243e13f598c6d9a72705a8e54a003574ba31
SHA256 (pam_xdg.c) = 9125ac3749b087f78844953a1a43790055a62efa0b8c25bd8766e35b061ca58c
SHA256 (pam_xdg.8) = 0f19e9f2437c6d0cb24465798ded6d6a3b2be07c012ee611648a4e4f1a21bbf1
SHA256 (makefile) = 2466f499c3e84fd821176371fa9ff78143bf94b9ec09fd9e654b35613e4ead7d

14
pam_xdg/Pkgfile Normal file
View File

@ -0,0 +1,14 @@
# Description: PAM module to manage XDG Base Directories
# URL: https://www.sdaoden.eu/code.html#s-toolbox
# Maintainer: Steffen Nurpmeso, steffen at sdaoden dot eu
name=pam_xdg
version=20210130
release=1
source=($name.c $name.8 makefile)
build () {
make install DESTDIR=$PKG
}
# s-sh-mode

17
pam_xdg/README Normal file
View File

@ -0,0 +1,17 @@
README for pam_xdg
This is a module for PAM that handles the XDG Base Directory
Specification[1] directories, including lifetime management
of XDG_RUNTIME_DIR and injection of according environment variables
into the user session.
For it to work it must be included in /etc/pam.d -- to make it a
vivid part of session handling the file /etc/pam.d/common-session
seems best. Include the following early:
session optional pam_xdg.so [notroot] [runtime]
Use notroot argument to only handle XDG for non-root users.
Use runtime argument to only handle of XDG_RUNTIME_DIR.
[1] https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

41
pam_xdg/makefile Normal file
View File

@ -0,0 +1,41 @@
#@ Makefile for pam_xdg(8).
PREFIX = /
MANPREFIX = /usr
DESTDIR =
LIBDIR = $(DESTDIR)$(PREFIX)/lib/security
MANDIR = $(DESTDIR)$(MANPREFIX)/share/man/man8
NAME = pam_xdg
CC = cc
CFLAGS = -DNDEBUG \
-O2 -W -Wall -Wextra -pedantic \
-Wno-uninitialized -Wno-unused-result -Wno-unused-value \
-fno-asynchronous-unwind-tables -fno-unwind-tables \
-fno-common \
-fstrict-aliasing -fstrict-overflow \
-fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE
LDFLAGS = -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,--as-needed \
-Wl,--enable-new-dtags -pie -shared
INSTALL = install
RM = rm
.PHONY: all clean distclean install uninstall
all: $(NAME).so
$(NAME).so: $(NAME).c
$(CC) $(CFLAGS) $(LDFLAGS) -o $(@) $(?)
clean:
$(RM) -f $(NAME).so
distclean: clean
install: all
$(INSTALL) -D -m 0755 $(NAME).so $(LIBDIR)/$(NAME).so
$(INSTALL) -D -m 0644 $(NAME).8 $(MANDIR)/$(NAME).8
uninstall:
$(RM) -f $(LIBDIR)/$(NAME).so $(MANDIR)/$(NAME).8
# s-mk-mode

85
pam_xdg/pam_xdg.8 Normal file
View File

@ -0,0 +1,85 @@
.\"@ pam_xdg - manage XDG Base Directories (runtime dir life time, environ).
.\"
.\" Copyright (c) 2021 Steffen Nurpmeso <steffen@sdaoden.eu>.
.\" SPDX-License-Identifier: ISC
.\"
.\" Permission to use, copy, modify, and/or 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.
.
.Dd January 30, 2021
.Dt PAM_XDG 8
.Os
.
.
.Sh NAME
.Nm pam_xdg.so
.Nd PAM module that manages XDG Base Directories
.
.
.Sh SYNOPSIS
.
.Nm
.Op Ar rundir
.Op Ar notroot
.
.
.Sh DESCRIPTION
.
.Nm
is a PAM module that manages creation and deletion of the
.Ev XDG_RUNTIME_DIR
directory, as well as injection of environment variables denoting all
directories specified by the
.Lk https://specifications.\:freedesktop.\:org/basedir-\:\
spec/\:basedir-\:spec-\:latest.html "XDG Base Directory Specification"
into user sessions.
.
.Pp
When linked into the PAM system, the runtime directory will be created as
.Ql /run/user/`id -u`
once a user creates his or her first login session, and it will be
removed recursively once the last such session ends.
Unless
.Ar rundir
was given all XDG related environment variables will be created in the
user session with their default or computed values, otherwise only
.Ev XDG_RUNTIME_DIR .
If
.Ar notroot
was given the module will bypass itself for root account logins, that
is, no actions will be performed.
.
.Pp
In order to make use of this script, place the following in the control
file of desire under
.Pa /etc/pam.d ,
best maybe
.Pa /etc/pam.d/common-session
if that exists (possibly adjusting paths):
.
.Bd -literal -offset indent
session optional pam_xdg.so notroot
.Ed
.
.
.Sh "SEE ALSO"
.
.Xr pam.conf 5 ,
.Xr pam.d 8 ,
.Xr pam 8
.
.
.Sh AUTHORS
.
.An "Steffen Nurpmeso" Aq steffen@sdaoden.eu .
.
.\" s-ts-mode

437
pam_xdg/pam_xdg.c Normal file
View File

@ -0,0 +1,437 @@
/*@ pam_xdg - manage XDG Base Directories (runtime dir life time, environment).
*@ Create /run/user/`id -u` when the first session is opened, and remove it
*@ again once the last is closed.
*@ It also creates according XDG_RUNTIME_DIR etc. environment variables in the
*@ user sessions, except when given the "runtime" option, in which case it
*@ only creates XDG_RUNTIME_DIR and not the others.
*@ Place for example in /etc/pam.d/common-session one of the following:
*@ session options pam_xdg.so [runtime] [notroot]
*@ Notes: - effectively needs ISO C99 as it uses strtoull(3).
*@ - according to XDG Base Directory Specification, v0.7.
*@ - Linux.
*
* Copyright (c) 2021 Steffen Nurpmeso <steffen@sdaoden.eu>.
* SPDX-License-Identifier: ISC
*
* Permission to use, copy, modify, and/or 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.
*/
/* For these a leading \1 is replaced with struct passwd::pw_dir.
* Be aware we use a stack buffer for storage */
#define a_XDG_DATA_HOME_DEF "\1/.local/share"
#define a_XDG_CONFIG_HOME_DEF "\1/.config"
#define a_XDG_DATA_DIRS_DEF "/usr/local/share:/usr/share"
#define a_XDG_CONFIG_DIRS_DEF "/etc/xdg/"
#define a_XDG_CACHE_HOME_DEF "\1/.cache"
/* */
#define a_XDG "pam_xdg"
#define a_RUNTIME_DIR_OUTER "/run" /* This must exist already */
#define a_RUNTIME_DIR_BASE "user" /* We create this as necessary, thus. */
#define a_LOCK_FILE "." a_XDG ".lck"
#define a_LOCK_TRIES 10
#define a_DAT_FILE "." a_XDG ".dat"
/* >8 -- 8< */
/*
#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
*/
#define _GNU_SOURCE /* Always the same mess */
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <security/pam_modules.h>
#include <security/pam_ext.h>
/* _XOPEN_PATH_MAX POSIX 2008/Cor 1-2013 */
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
static int a_xdg(int isopen, pam_handle_t *pamh, int flags, int argc,
const char **argv);
static int
a_xdg(int isopen, pam_handle_t *pamh, int flags, int argc, const char **argv){
char uidbuf[sizeof "18446744073709551615"],
wbuf[(((sizeof("18446744073709551615") -1) * 2) |
(sizeof("cd ..;rm -rf ") + sizeof("18446744073709551615")) |
(sizeof("XDG_RUNTIME_DIR=") + sizeof(a_RUNTIME_DIR_OUTER) +
sizeof(a_RUNTIME_DIR_BASE) + sizeof("18446744073709551615")) |
(sizeof("XDG_CONFIG_DIRS=") + PATH_MAX)
) +1];
struct flock flp;
struct stat st;
uint64_t sessions;
struct passwd *pwp;
char const *emsg;
int cntrlfd, fd, cwdfd, only_runtime, notroot, res, uidbuflen;
char const *user;
(void)flags;
user = "<unset>";
cntrlfd = fd = cwdfd = -1;
only_runtime = notroot = 0;
/* Command line */
if(isopen){
for(; argc > 0; ++argv, --argc){
if(!strcmp(argv[0], "runtime"))
only_runtime = 1;
else if(!strcmp(argv[0], "notroot"))
notroot = 1;
else if(!(flags & PAM_SILENT)){
emsg = "command line";
errno = EINVAL;
goto jerr;
}
}
}
/* We need the user we go for */
if((res = pam_get_item(pamh, PAM_USER, (void const**)&user)
) != PAM_SUCCESS){
user = "<lookup failed>";
emsg = "cannot query PAM_USER name";
goto jepam;
}
if((pwp = getpwnam(user)) == NULL){
emsg = "host machine does not know about user";
errno = EINVAL;
goto jerr;
}
if(notroot && pwp->pw_uid == 0)
goto jok;
/* I admit all this is overly complicated and expensive */
umask(0022);
if((cwdfd = open(a_RUNTIME_DIR_OUTER, (O_PATH | O_DIRECTORY | O_NOFOLLOW))
) == -1){
emsg = "cannot obtain chdir(2) descriptor to " a_RUNTIME_DIR_OUTER;
goto jerr;
}
/* We try create the base directory once as necessary */
if(isopen){
res = 0;
while(fstatat(cwdfd, a_RUNTIME_DIR_BASE, &st, AT_SYMLINK_NOFOLLOW
) == -1){
if(res++ != 0 || errno != ENOENT){
emsg = "base directory " a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE
" not accessible";
goto jerr;
}
if(mkdirat(cwdfd, a_RUNTIME_DIR_BASE, 0711) == -1){
emsg = "cannot create base directory "
a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE;
goto jerr;
}
}
}
if((res = openat(cwdfd, a_RUNTIME_DIR_BASE,
(O_PATH | O_DIRECTORY | O_NOFOLLOW))) == -1){
emsg = "cannot obtain chdir(2) descriptor to " a_RUNTIME_DIR_OUTER "/"
a_RUNTIME_DIR_BASE;
goto jerr;
}
close(cwdfd);
cwdfd = res;
/* Landed in the runtime base dir, obtain our lock */
if((cntrlfd = openat(cwdfd, a_LOCK_FILE,
(O_CREAT | O_WRONLY | O_NOFOLLOW | O_NOCTTY),
(S_IRUSR | S_IWUSR))) == -1){
emsg = "cannot open control lock file";
goto jerr;
}
for(res = a_LOCK_TRIES;;){
memset(&flp, 0, sizeof flp);
flp.l_type = F_WRLCK;
flp.l_start = 0;
flp.l_whence = SEEK_SET;
flp.l_len = 0;
if(fcntl(cntrlfd, F_SETLKW, &flp) != -1)
break;
if(errno != EINTR){
emsg = "unexpected error obtaining lock on control lock file";
goto jerr;
}
if(--res == 0){
emsg = "cannot obtain lock on control lock file";
goto jerr;
}
}
/* Turn to user management */
uidbuflen = snprintf(uidbuf, sizeof(uidbuf), "%lu",
(unsigned long)pwp->pw_uid);
/* We create the per-user directory on isopen time as necessary */
for(res = 0;; ++res){
int nfd;
if((nfd = openat(cwdfd, uidbuf, (O_PATH | O_DIRECTORY | O_NOFOLLOW))
) != -1){
close(cwdfd);
cwdfd = nfd;
break;
}else{
if(errno == ENOENT){
if(!isopen)
goto jok;
if(res != 0)
goto jeurd;
}else{
jeurd:
emsg = "per user XDG_RUNTIME_DIR not accessible";
goto jerr;
}
}
if(mkdirat(cwdfd, uidbuf, 0700) == -1){
emsg = "cannot create per user XDG_RUNTIME_DIR";
goto jerr;
}
if(fchownat(cwdfd, uidbuf, pwp->pw_uid, pwp->pw_gid, AT_SYMLINK_NOFOLLOW
) == -1){
emsg = "cannot chown(2) per user XDG_RUNTIME_DIR";
goto jerr;
}
}
/* Read session counter; be simple and assume 0 if non-existent, this should
* not happen in practice */
sessions = 0;
if((fd = openat(cwdfd, a_DAT_FILE, O_RDONLY)) != -1){
char *ep;
ssize_t r;
while((r = read(fd, wbuf, sizeof(wbuf) -1)) == -1){
if(errno != EINTR){
emsg = "I/O error while reading session counter";
goto jerr;
}
}
/* We have written this as a valid POSIX text file, then, so chop tail */
if(r < 1 || (size_t)r >= (sizeof(wbuf) -1) / 2){
jecnt:
emsg = "session counter corrupted, ask administrator to remove "
a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE "/YOUR-UID/"
a_DAT_FILE;
goto jerr;
}
for(;;){
char c;
c = wbuf[(size_t)r - 1];
if(c == '\0' || c == '\n'){
if(--r == 0)
goto jecnt;
}else
break;
}
wbuf[(size_t)r] = '\0';
sessions = strtoull(wbuf, &ep, 10);
if(sessions == ULLONG_MAX || ep == wbuf || *ep != '\0')
goto jecnt;
close(fd);
fd = -1;
}
if(isopen)
++sessions;
/* == 0 should never happen, but just handled it easily */
else if(sessions == 0 || --sessions == 0){
/* This is ridiculously simple, but everything else would be opposite */
char const cmd[] = "rm -rf " a_RUNTIME_DIR_OUTER "/"
a_RUNTIME_DIR_BASE "/";
memcpy(wbuf, cmd, sizeof(cmd) -1);
memcpy(&wbuf[sizeof(cmd) -1], uidbuf, uidbuflen +1);
res = system(wbuf);
if(!WIFEXITED(res) || WEXITSTATUS(res) != 0){
emsg = "unable to rm(1) -rf per user XDG_RUNTIME_DIR";
errno = EINVAL;
goto jerr;
}
goto jok;
}
/* Write out session counter (as a valid POSIX text file) */
res = snprintf(wbuf, sizeof wbuf, "%llu\n", (unsigned long long)sessions);
if(((fd = openat(cwdfd, a_DAT_FILE,
(O_CREAT | O_TRUNC | O_WRONLY | O_SYNC | O_NOFOLLOW | O_NOCTTY),
(S_IRUSR | S_IWUSR))) == -1) || write(fd, wbuf, res) != res){
emsg = "cannot write session counter, ask administrator to remove "
a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE "/YOUR-UID/"
a_DAT_FILE;
goto jerr;
}
close(fd);
fd = -1;
/* When opening, we want to put environment variables, too */
if(isopen){
char *cp;
/* XDG_RUNTIME_DIR */
cp = wbuf;
memcpy(cp, "XDG_RUNTIME_DIR=", sizeof("XDG_RUNTIME_DIR=") -1);
cp += sizeof("XDG_RUNTIME_DIR=") -1;
memcpy(cp, a_RUNTIME_DIR_OUTER, sizeof(a_RUNTIME_DIR_OUTER) -1);
cp += sizeof(a_RUNTIME_DIR_OUTER) -1;
*cp++ = '/';
memcpy(cp, a_RUNTIME_DIR_BASE, sizeof(a_RUNTIME_DIR_BASE) -1);
cp += sizeof(a_RUNTIME_DIR_BASE) -1;
*cp++ = '/';
memcpy(cp, uidbuf, uidbuflen +1);
if((res = pam_putenv(pamh, wbuf)) != PAM_SUCCESS)
goto jepam;
/* And the rest */
if(!only_runtime){
struct adir{
char const *name;
size_t len;
char const *defval;
} const adirs[] = {
{"XDG_DATA_HOME=", sizeof("XDG_DATA_HOME=") -1,
a_XDG_DATA_HOME_DEF},
{"XDG_CONFIG_HOME=", sizeof("XDG_CONFIG_HOME=") -1,
a_XDG_CONFIG_HOME_DEF},
{"XDG_DATA_DIRS=", sizeof("XDG_DATA_DIRS=") -1,
a_XDG_DATA_DIRS_DEF},
{"XDG_CONFIG_DIRS=", sizeof("XDG_CONFIG_DIRS=") -1,
a_XDG_CONFIG_DIRS_DEF},
{"XDG_CACHE_HOME=", sizeof("XDG_CACHE_HOME=") -1,
a_XDG_CACHE_HOME_DEF},
{NULL,0,NULL} /* xxx nelem */
}, *adp;
char const *src;
size_t i;
i = strlen(pwp->pw_dir);
for(adp = adirs; adp->name != NULL; ++adp){
cp = wbuf;
memcpy(cp, adp->name, adp->len);
cp += adp->len;
if(*(src = adp->defval) == '\1'){
memcpy(cp, pwp->pw_dir, i);
cp += i;
++src;
}
memcpy(cp, src, strlen(src) +1);
if((res = pam_putenv(pamh, wbuf)) != PAM_SUCCESS)
goto jepam;
}
}
}
jok:
res = PAM_SUCCESS;
jleave:
if(fd != -1)
close(fd);
if(cntrlfd != -1)
close(cntrlfd);
if(cwdfd != -1)
close(cwdfd);
return (res == PAM_SUCCESS) ? PAM_SUCCESS : PAM_SESSION_ERR;
jerr:
pam_syslog(pamh, LOG_ERR, a_XDG ": user %s: %s: %s\n",
user, emsg, strerror(errno));
res = PAM_SESSION_ERR;
goto jleave;
jepam:
pam_syslog(pamh, LOG_ERR, a_XDG ": user %s: PAM failure: %s\n",
user, pam_strerror(pamh, res));
goto jleave;
}
int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv){
return a_xdg(1, pamh, flags, argc, argv);
}
int
pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char **argv){
return a_xdg(0, pamh, flags, argc, argv);
}
int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv){
(void)flags;
(void)argc;
(void)argv;
pam_syslog(pamh, LOG_NOTICE, "pam_sm_acct_mgmt not used by " a_XDG);
return PAM_SERVICE_ERR;
}
int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv){
(void)flags;
(void)argc;
(void)argv;
pam_syslog(pamh, LOG_NOTICE, "pam_sm_setcred not used by " a_XDG);
return PAM_SERVICE_ERR;
}
int
pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv){
(void)flags;
(void)argc;
(void)argv;
pam_syslog(pamh, LOG_NOTICE, "pam_sm_chauthtok not used by " a_XDG);
return PAM_SERVICE_ERR;
}
/* s-it-mode */