commit eab8dce31fb76bf601842bb9eb5c27880fc45865
Author: Steffen Nurpmeso <steffen(a)sdaoden.eu>
Date: Sat Jul 31 01:09:51 2021 +0200
pam_xdg.c: update to 20210731
diff --git a/pam_xdg/.md5sum b/pam_xdg/.md5sum
deleted file mode 100644
index f1f334aef..000000000
--- a/pam_xdg/.md5sum
+++ /dev/null
@@ -1,3 +0,0 @@
-c9095bcca36ad19232016d2871e59546 makefile
-c8562d9eb117543c267e992a898ad617 pam_xdg.8
-09f7153e4300cf57d6a6a4bfe5fa0f3a pam_xdg.c
diff --git a/pam_xdg/.signature b/pam_xdg/.signature
index 1c5851ad2..417c693bd 100644
--- a/pam_xdg/.signature
+++ b/pam_xdg/.signature
@@ -1,7 +1,7 @@
untrusted comment: verify with /etc/ports/contrib.pub
-RWSagIOpLGJF3wSopQt7TvcJLCKXnqY7+rEYaiXbcNkFqhjUOyafFSwoNvjHD/yU0MwPrRqfsDzZj4+X//VOhTm+XpBl8CXh4Ag=
-SHA256 (Pkgfile) = 4dff33d08e9f6699bffa06a6137c05c9bb446827b9ccde2b376f8aa8495306d1
+RWSagIOpLGJF3z9vXj+fCpENPa65XMag5E6REcBr0Kj7oKCdaZuUaD45UgkBxtRvncVQRDW3rQ8But8+nu14Ag1LHZ7s0OHJXg4=
+SHA256 (Pkgfile) = 2c85163d75631d082443136b26d215babc716a9bf2521c616f58f4f1706ae4b4
SHA256 (.footprint) = 56d789b652e6167f5fb93e1e6d48243e13f598c6d9a72705a8e54a003574ba31
-SHA256 (pam_xdg.c) = 4e9215a0f695920f04e925f55fd221167b2f376a75cc2668f9d4842540ccdeed
-SHA256 (pam_xdg.8) = 2929bcd6655d28127d386215d3d8c4fed6744b65c4866ac7e49d54cb438d9133
+SHA256 (pam_xdg.c) = 8569b0cbab468de159143bf58f9207dd9c3db204f44b7153ee410d0733526cea
+SHA256 (pam_xdg.8) = 999548e64134d26d0ffeec2931f15a905e4029e6b404e86825d02978d2393d7a
SHA256 (makefile) = 2466f499c3e84fd821176371fa9ff78143bf94b9ec09fd9e654b35613e4ead7d
diff --git a/pam_xdg/Pkgfile b/pam_xdg/Pkgfile
index 79694314a..26f7c28e4 100644
--- a/pam_xdg/Pkgfile
+++ b/pam_xdg/Pkgfile
@@ -3,7 +3,7 @@
# Maintainer: Steffen Nurpmeso, steffen at sdaoden dot eu
name=pam_xdg
-version=20210222
+version=20210731
release=1
source=($name.c $name.8 makefile)
diff --git a/pam_xdg/pam_xdg.8 b/pam_xdg/pam_xdg.8
index 67c62ad81..c4d0eba66 100644
--- a/pam_xdg/pam_xdg.8
+++ b/pam_xdg/pam_xdg.8
@@ -15,7 +15,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.
-.Dd January 31, 2021
+.Dd July 29, 2021
.Dt PAM_XDG 8
.Os
.
@@ -28,8 +28,9 @@
.Sh SYNOPSIS
.
.Nm
-.Op Ar rundir
+.Op Ar runtime
.Op Ar notroot
+.\".Op Ar track_user_sessions Op Ar per_user_lock
.
.
.Sh DESCRIPTION
@@ -47,7 +48,7 @@ into user sessions.
When linked into the PAM session system the runtime directory will be
created once a user creates his or her first login session.
Unless
-.Ar rundir
+.Ar runtime
was given all XDG related environment variables will be created in all
user sessions with their default or computed values, otherwise only
.Ev XDG_RUNTIME_DIR .
@@ -55,29 +56,58 @@ If
.Ar notroot
was given the module will bypass itself for root account logins and
perform no actions for root.
+.\"Lastly
+.\".Ar track_user_sessions
+.\"will enable session tracking: once the last session ends, the user's
+.\".Ev XDG_RUNTIME_DIR
+.\"will be recursively removed; on high-load servers setting
+.\".Ar per_user_lock
+.\"then will reduce lock file lock contention.
.
.Pp
-In order to make use of this script, place the following in the control
-file of desire under
+In order to make use of this module, place the following in the
+.Ql session
+part of the control file of desire under
.Pa /etc/pam.d ,
-best maybe
+on Linux it may be
.Pa /etc/pam.d/common-session
-if that exists (possibly adjusting paths):
+if that exists, on BSD's the files
+.Pa /etc/pam.d/system
+as well as
+.Pa /etc/pam.d/login ,
+.Pa /etc/pam.d/sshd
+and
+.Pa /etc/pam.d/su
+may be desirable, adjusting paths as necessary:
.
.Bd -literal -offset indent
-session optional pam_xdg.so notroot
+session optional pam_xdg.so notroot \"track_user_sessions
.Ed
.
.
.Sh "SEE ALSO"
.
-.Xr pam.conf 5 ,
-.Xr pam.d 8 ,
-.Xr pam 8
+.Xr pam 3 ,
+.Xr pam.conf 5
.
.
.Sh AUTHORS
.
.An "Steffen Nurpmeso" Aq steffen(a)sdaoden.eu .
.
+.
+.Sh CAVEATS
+.
+On Unix systems any
+.Dq daemonized
+program or script is reparented to the program running with PID 1,
+therefore leaving the PAM user session without PAM recognizing this.
+Yet careless such code may hold or expect availability of resources of
+the session it just left, truly performing cleanup when sessions end
+seems thus unwise.
+.\"However, many PAM modules do support cleanup upon closing the last
+.\"session of a user, and therefore
+.\".Nm
+.\"supports this optionally, too.
+.
.\" s-ts-mode
diff --git a/pam_xdg/pam_xdg.c b/pam_xdg/pam_xdg.c
index 4c121e93c..54fd7178c 100644
--- a/pam_xdg/pam_xdg.c
+++ b/pam_xdg/pam_xdg.c
@@ -1,12 +1,8 @@
/*@ pam_xdg - manage XDG Base Directories (runtime dir life time, environment).
- *@ Create /run/user/`id -u` when the first session is opened.
- *@ 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: - according to XDG Base Directory Specification, v0.7.
- *@ - Linux-only (i think).
+ *@ See pam_xdg.8 for more.
+ *@ - According to XDG Base Directory Specification, v0.7.
+ *@ - Supports libpam (Linux) and *BSD OpenPAM.
+ *@ - Requires C preprocessor with __VA_ARGS__ support!
*
* Copyright (c) 2021 Steffen Nurpmeso <steffen(a)sdaoden.eu>.
* SPDX-License-Identifier: ISC
@@ -32,11 +28,10 @@
#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. */
+/* We create the outer directories as necessary (stack buffer storage!) */
+#define a_RUNTIME_DIR_OUTER "/run"
+#define a_RUNTIME_DIR_OUTER_MODE 0755
+#define a_RUNTIME_DIR_BASE "user"
#define a_RUNTIME_DIR_BASE_MODE 0755 /* 0711? */
/* >8 -- 8< */
@@ -52,57 +47,142 @@
#include <errno.h>
#include <fcntl.h>
+#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <syslog.h>
#include <unistd.h>
+#include <security/pam_appl.h>
#include <security/pam_modules.h>
-#include <security/pam_ext.h>
+#ifdef OPENPAM_VERSION
+# include <security/openpam.h>
+#else
+# include <security/pam_ext.h>
+#endif
+
+#ifdef OPENPAM_VERSION
+#else
+# include <syslog.h>
+#endif
+
+/* Who are we? */
+#define a_XDG "pam_xdg"
/* _XOPEN_PATH_MAX POSIX 2008/Cor 1-2013 */
#ifndef PATH_MAX
# define PATH_MAX 1024
#endif
+/* */
+#ifdef O_SEARCH
+# define a_O_SEARCH O_SEARCH
+#elif defined O_PATH
+# define a_O_SEARCH O_PATH
+#else
+ /* Well, hardly, but not in practice so do not #error out */
+# define a_O_SEARCH 0
+#endif
+
+/* libpam / OpenPAM compat */
+#ifdef OPENPAM_VERSION
+# define a_LOG(HDL, LVL, ...) ((void)HDL, openpam_log(LVL, __VA_ARGS__))
+# define a_LOG_ERR PAM_LOG_ERROR
+# define a_LOG_NOTICE PAM_LOG_NOTICE
+#else
+# define a_LOG(HDL, LVL, ...) pam_syslog(HDL, LVL, __VA_ARGS__)
+# define a_LOG_ERR LOG_ERR
+# define a_LOG_NOTICE LOG_NOTICE
+#endif
+
+/* Because of complicated file locking, use one function with two exec paths */
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"],
+ enum a_flags{
+ a_NONE,
+ /* Options */
+ a_RUNTIME = 1u<<0,
+ a_NOTROOT = 1u<<1,
+#if 0
+ a_SESSIONS = 1u<<16,
+ a_USER_LOCK = 1u<<17,
+#endif
+ /* Flags */
+ a_MPV = 1u<<29, /* Multi-Purpose-Vehicle */
+ a_SKIP_XDG = 1u<<30 /* We shall not act */
+ };
+
+ struct a_dirtree{
+ char const *name;
+ int mode;
+ };
+
+ static struct a_dirtree const a_dirtree[] = {
+ {a_RUNTIME_DIR_OUTER, a_RUNTIME_DIR_OUTER_MODE},
+ {a_RUNTIME_DIR_BASE, a_RUNTIME_DIR_BASE_MODE},
+ {NULL, 0} /* XXX -> nelem/item/countof */
+ };
+ static int f_saved;
+
+ char uidbuf[sizeof ".18446744073709551615"],
wbuf[((sizeof("XDG_RUNTIME_DIR=") + sizeof(a_RUNTIME_DIR_OUTER) +
- sizeof(a_RUNTIME_DIR_BASE) + sizeof("18446744073709551615")) |
+ sizeof(a_RUNTIME_DIR_BASE) + sizeof(".18446744073709551615")) |
(sizeof("XDG_CONFIG_DIRS=") + PATH_MAX)
) +1];
- struct stat st;
+ struct a_dirtree dt_user;
+ struct a_dirtree const *dtp;
struct passwd *pwp;
char const *emsg;
- int cwdfd, only_runtime, notroot, res, uidbuflen;
+ int cwdfd, f, res, uidbuflen;
char const *user;
- (void)flags;
user = "<unset>";
- cwdfd = -1;
- only_runtime = notroot = 0;
+ cwdfd = AT_FDCWD;
/* Command line */
if(isopen){
+ f = a_NONE;
+
for(; argc > 0; ++argv, --argc){
if(!strcmp(argv[0], "runtime"))
- only_runtime = 1;
+ f |= a_RUNTIME;
+ else if(!strcmp(argv[0], "rundir")){ /* XXX COMPAT */
+ a_LOG(pamh, a_LOG_NOTICE,
+ a_XDG ": \"rundir\" was a misdocumentation of \"runtime\", "
+ "sorry for this");
+ f |= a_RUNTIME;
+ }
else if(!strcmp(argv[0], "notroot"))
- notroot = 1;
+ f |= a_NOTROOT;
+#if 0
+ else if(!strcmp(argv[0], "track_user_sessions"))
+ f |= a_SESSIONS;
+ else if(!strcmp(argv[0], "per_user_lock"))
+ f |= a_USER_LOCK;
+#endif
else if(!(flags & PAM_SILENT)){
emsg = "command line";
errno = EINVAL;
goto jerr;
}
}
- }else
+
+#if 0
+ if((f & a_USER_LOCK) && !(f & a_SESSIONS))
+ a_LOG(pamh, a_LOG_NOTICE,
+ a_XDG ": \"per_user_lock\" requires \"track_user_sessions\"");
+#endif
+ }else{
+ f = f_saved;
+
+ if(f & a_SKIP_XDG)
+ goto jok;
goto jok; /* No longer used, session counting does not work */
+ }
/* We need the user we go for */
if((res = pam_get_item(pamh, PAM_USER, (void const**)&user)
@@ -113,102 +193,85 @@ a_xdg(int isopen, pam_handle_t *pamh, int flags, int argc, const char **argv){
}
if((pwp = getpwnam(user)) == NULL){
- emsg = "host machine does not know about user";
+ emsg = "host does not know about user";
errno = EINVAL;
goto jerr;
}
- if(notroot && pwp->pw_uid == 0)
+ if((f & a_NOTROOT) && pwp->pw_uid == 0){
+ f |= a_SKIP_XDG;
goto jok;
-
- /* I admit all this is overly complicated and expensive */
- 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)*/{
- gid_t oegid;
- mode_t oumask;
-
- 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;
- }
+ /* Our lockfile and per-user directory name */
+ uidbuflen = snprintf(uidbuf, sizeof(uidbuf), ".%lu",
+ (unsigned long)pwp->pw_uid);
- oumask = umask(0000);
- oegid = getegid();
- setegid(0);
+ dt_user.name = &uidbuf[1];
+ dt_user.mode = 0700;
- if(mkdirat(cwdfd, a_RUNTIME_DIR_BASE, a_RUNTIME_DIR_BASE_MODE
- ) == -1 && errno != EEXIST){
- emsg = "cannot create base directory "
- a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE;
- goto jerr;
- }
+ /* Handle tree. On *BSD outermost may not exist! */
+ for(/*f &= ~a_MPV,*/ dtp = a_dirtree;;){
+ int e;
+ gid_t oegid;
+ mode_t oumask;
- setegid(oegid);
- umask(oumask);
+ if((res = openat(cwdfd, dtp->name,
+ (a_O_SEARCH | O_DIRECTORY | O_NOFOLLOW))) != -1){
+ if(cwdfd != AT_FDCWD)
+ close(cwdfd); /* XXX error hdl */
+ cwdfd = res;
+
+ if(dtp == &dt_user)
+ break;
+ else if((++dtp)->name == NULL)
+ dtp = &dt_user;
+ f &= ~a_MPV;
+ continue;
}
- /* Not worth doing S_ISDIR(st.st_mode), O_DIRECTORY will bail next */
- }
-
- 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;
- /* 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(!isopen)
+ /* Someone removed the entire directory tree while sessions were open!
+ * Silently out!?! */
+ goto jok;
- if(mkdirat(cwdfd, uidbuf, 0700) == -1 && errno != EEXIST){
- emsg = "cannot create per user XDG_RUNTIME_DIR";
+ /* We try creating the directories once as necessary */
+ if((f & a_MPV) || errno != ENOENT){
+ emsg = "cannot obtain chdir(2) descriptor (within) tree "
+ a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE;
goto jerr;
}
-
+ f |= a_MPV;
+
+ oumask = umask(0000);
+ oegid = getegid();
+ setegid(0);
+ res = mkdirat(cwdfd, dtp->name, dtp->mode);
+ e = (res == -1) ? errno : 0;
+ setegid(oegid);
+ umask(oumask);
+
+ if(res == -1){
+ if(e != EEXIST){
+ emsg = "cannot create directory (within) tree "
+ a_RUNTIME_DIR_OUTER "/" a_RUNTIME_DIR_BASE;
+ goto jerr;
+ }
+ }else if(cwdfd == AT_FDCWD)
+ a_LOG(pamh, a_LOG_NOTICE,
+ a_XDG ": " a_RUNTIME_DIR_OUTER " did not exist, but should be "
+ "(a mount point of) volatile storage!");
/* Just chown it! */
- if(fchownat(cwdfd, uidbuf, pwp->pw_uid, pwp->pw_gid,
- AT_SYMLINK_NOFOLLOW) == -1){
+ else if(dtp == &dt_user &&
+ fchownat(cwdfd, &uidbuf[1], pwp->pw_uid, pwp->pw_gid,
+ AT_SYMLINK_NOFOLLOW) == -1){
emsg = "cannot chown(2) per user XDG_RUNTIME_DIR";
goto jerr;
}
}
/* When opening, we want to put environment variables, too */
- /*if(isopen)*/{
+ if(isopen){
char *cp;
/* XDG_RUNTIME_DIR */
@@ -221,18 +284,20 @@ jeurd:
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);
+ memcpy(cp, &uidbuf[1], uidbuflen);
if((res = pam_putenv(pamh, wbuf)) != PAM_SUCCESS)
goto jepam;
- /* And the rest */
- if(!only_runtime){
- struct adir{
+ /* And the rest unless disallowed */
+ if(!(f & a_RUNTIME)){
+ struct a_dir{
char const *name;
size_t len;
char const *defval;
- } const adirs[] = {
+ };
+
+ static struct a_dir const a_dirs[] = {
{"XDG_DATA_HOME=", sizeof("XDG_DATA_HOME=") -1,
a_XDG_DATA_HOME_DEF},
{"XDG_CONFIG_HOME=", sizeof("XDG_CONFIG_HOME=") -1,
@@ -243,15 +308,16 @@ jeurd:
a_XDG_CONFIG_DIRS_DEF},
{"XDG_CACHE_HOME=", sizeof("XDG_CACHE_HOME=") -1,
a_XDG_CACHE_HOME_DEF},
- {NULL,0,NULL} /* xxx nelem */
- }, *adp;
+ {NULL,0,NULL} /* XXX -> nelem/item/countof */
+ };
char const *src;
+ struct a_dir const *adp;
size_t i;
i = strlen(pwp->pw_dir);
- for(adp = adirs; adp->name != NULL; ++adp){
+ for(adp = a_dirs; adp->name != NULL; ++adp){
cp = wbuf;
memcpy(cp, adp->name, adp->len);
cp += adp->len;
@@ -271,20 +337,23 @@ jeurd:
jok:
res = PAM_SUCCESS;
jleave:
- if(cwdfd != -1)
+ if(cwdfd != -1 && cwdfd != AT_FDCWD) /* >=0, but AT_FDCWD unspecified */
close(cwdfd);
+ f_saved = f;
return (res == PAM_SUCCESS) ? PAM_SUCCESS : PAM_SESSION_ERR;
jerr:
- pam_syslog(pamh, LOG_ERR, a_XDG ": user %s: %s: %s\n",
+ a_LOG(pamh, a_LOG_ERR, a_XDG ": user %s: %s: %s\n",
user, emsg, strerror(errno));
+ f |= a_SKIP_XDG;
res = PAM_SESSION_ERR;
goto jleave;
jepam:
- pam_syslog(pamh, LOG_ERR, a_XDG ": user %s: PAM failure: %s\n",
+ a_LOG(pamh, a_LOG_ERR, a_XDG ": user %s: PAM failure: %s\n",
user, pam_strerror(pamh, res));
+ f |= a_SKIP_XDG;
goto jleave;
}
@@ -305,7 +374,9 @@ 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);
+
+ a_LOG(pamh, a_LOG_NOTICE, a_XDG ": pam_sm_acct_mgmt not used");
+
return PAM_SERVICE_ERR;
}
@@ -314,7 +385,9 @@ 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);
+
+ a_LOG(pamh, a_LOG_NOTICE, a_XDG ": pam_sm_setcred not used");
+
return PAM_SERVICE_ERR;
}
@@ -323,7 +396,9 @@ 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);
+
+ a_LOG(pamh, a_LOG_NOTICE, a_XDG ": pam_sm_chauthtok not used");
+
return PAM_SERVICE_ERR;
}