commit eab8dce31fb76bf601842bb9eb5c27880fc45865 Author: Steffen Nurpmeso <steffen@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@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@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; }