1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2024 Beckhoff Automation GmbH & Co. KG 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/stat.h> 29 #include <dirent.h> 30 #include <errno.h> 31 #include <fcntl.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 #include <pwd.h> 37 38 #define PAM_SM_SESSION 39 40 #include <security/pam_appl.h> 41 #include <security/pam_modules.h> 42 #include <security/pam_mod_misc.h> 43 44 #define BASE_RUNTIME_DIR_PREFIX "/var/run/xdg" 45 #define RUNTIME_DIR_PREFIX runtime_dir_prefix != NULL ? runtime_dir_prefix : BASE_RUNTIME_DIR_PREFIX 46 47 #define RUNTIME_DIR_PREFIX_MODE 0711 48 #define RUNTIME_DIR_MODE 0700 /* XDG spec */ 49 50 #define XDG_MAX_SESSION 100 /* Arbitrary limit because we need one */ 51 52 static int 53 _pam_xdg_open(pam_handle_t *pamh, int flags __unused, 54 int argc __unused, const char *argv[] __unused) 55 { 56 struct passwd *passwd; 57 const char *user; 58 const char *runtime_dir_prefix; 59 struct stat sb; 60 char *runtime_dir = NULL; 61 char *xdg_session_file; 62 int rv, rt_dir_prefix, rt_dir, session_file, i; 63 64 session_file = -1; 65 rt_dir_prefix = -1; 66 runtime_dir_prefix = openpam_get_option(pamh, "runtime_dir_prefix"); 67 68 /* Get user info */ 69 rv = pam_get_item(pamh, PAM_USER, (const void **)&user); 70 if (rv != PAM_SUCCESS || user == NULL) { 71 PAM_VERBOSE_ERROR("Can't get user information"); 72 goto out; 73 } 74 if ((passwd = getpwnam(user)) == NULL) { 75 PAM_VERBOSE_ERROR("Can't get user information"); 76 rv = PAM_SESSION_ERR; 77 goto out; 78 } 79 80 /* Open or create the base xdg directory */ 81 rt_dir_prefix = open(RUNTIME_DIR_PREFIX, O_DIRECTORY | O_NOFOLLOW); 82 if (rt_dir_prefix < 0) { 83 rt_dir_prefix = mkdir(RUNTIME_DIR_PREFIX, RUNTIME_DIR_PREFIX_MODE); 84 if (rt_dir_prefix != 0) { 85 PAM_VERBOSE_ERROR("Can't mkdir %s", RUNTIME_DIR_PREFIX); 86 rv = PAM_SESSION_ERR; 87 goto out; 88 } 89 rt_dir_prefix = open(RUNTIME_DIR_PREFIX, O_DIRECTORY | O_NOFOLLOW); 90 } 91 92 /* Open or create the user xdg directory */ 93 rt_dir = openat(rt_dir_prefix, user, O_DIRECTORY | O_NOFOLLOW); 94 if (rt_dir < 0) { 95 rt_dir = mkdirat(rt_dir_prefix, user, RUNTIME_DIR_MODE); 96 if (rt_dir != 0) { 97 PAM_VERBOSE_ERROR("mkdir: %s/%s (%d)", RUNTIME_DIR_PREFIX, user, rt_dir); 98 rv = PAM_SESSION_ERR; 99 goto out; 100 } 101 rv = fchownat(rt_dir_prefix, user, passwd->pw_uid, passwd->pw_gid, 0); 102 if (rv != 0) { 103 PAM_VERBOSE_ERROR("fchownat: %s/%s (%d)", RUNTIME_DIR_PREFIX, user, rv); 104 rv = unlinkat(rt_dir_prefix, user, AT_REMOVEDIR); 105 if (rv == -1) 106 PAM_VERBOSE_ERROR("unlinkat: %s/%s (%d)", RUNTIME_DIR_PREFIX, user, errno); 107 rv = PAM_SESSION_ERR; 108 goto out; 109 } 110 } else { 111 close(rt_dir); 112 /* Check that the already create dir is correctly owned */ 113 rv = fstatat(rt_dir_prefix, user, &sb, 0); 114 if (rv == -1) { 115 PAM_VERBOSE_ERROR("fstatat %s/%s failed (%d)", RUNTIME_DIR_PREFIX, user, errno); 116 rv = PAM_SESSION_ERR; 117 goto out; 118 } 119 if (sb.st_uid != passwd->pw_uid || 120 sb.st_gid != passwd->pw_gid) { 121 PAM_VERBOSE_ERROR("%s/%s isn't owned by %d:%d\n", RUNTIME_DIR_PREFIX, user, passwd->pw_uid, passwd->pw_gid); 122 rv = PAM_SESSION_ERR; 123 goto out; 124 } 125 /* Test directory mode */ 126 if ((sb.st_mode & 0x1FF) != RUNTIME_DIR_MODE) { 127 PAM_VERBOSE_ERROR("%s/%s have wrong mode\n", RUNTIME_DIR_PREFIX, user); 128 rv = PAM_SESSION_ERR; 129 goto out; 130 } 131 } 132 133 /* Setup the environment variable */ 134 rv = asprintf(&runtime_dir, "XDG_RUNTIME_DIR=%s/%s", RUNTIME_DIR_PREFIX, user); 135 if (rv < 0) { 136 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 137 rv = PAM_SESSION_ERR; 138 goto out; 139 } 140 rv = pam_putenv(pamh, runtime_dir); 141 if (rv != PAM_SUCCESS) { 142 PAM_VERBOSE_ERROR("pam_putenv: failed (%d)", rv); 143 rv = PAM_SESSION_ERR; 144 goto out; 145 } 146 147 /* Setup the session count file */ 148 for (i = 0; i < XDG_MAX_SESSION; i++) { 149 rv = asprintf(&xdg_session_file, "%s/xdg_session.%d", user, i); 150 if (rv < 0) { 151 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 152 rv = PAM_SESSION_ERR; 153 goto out; 154 } 155 rv = 0; 156 session_file = openat(rt_dir_prefix, xdg_session_file, O_CREAT | O_EXCL, RUNTIME_DIR_MODE); 157 free(xdg_session_file); 158 if (session_file >= 0) 159 break; 160 } 161 if (session_file < 0) { 162 PAM_VERBOSE_ERROR("Too many sessions"); 163 rv = PAM_SESSION_ERR; 164 goto out; 165 } 166 167 out: 168 if (session_file >= 0) 169 close(session_file); 170 if (rt_dir_prefix >= 0) 171 close(rt_dir_prefix); 172 173 if (runtime_dir) 174 free(runtime_dir); 175 return (rv); 176 } 177 178 static int 179 remove_dir(int fd) 180 { 181 DIR *dirp; 182 struct dirent *dp; 183 184 dirp = fdopendir(fd); 185 if (dirp == NULL) 186 return (-1); 187 188 while ((dp = readdir(dirp)) != NULL) { 189 if (dp->d_type == DT_DIR) { 190 int dirfd; 191 192 if (strcmp(dp->d_name, ".") == 0 || 193 strcmp(dp->d_name, "..") == 0) 194 continue; 195 dirfd = openat(fd, dp->d_name, 0); 196 remove_dir(dirfd); 197 close(dirfd); 198 unlinkat(fd, dp->d_name, AT_REMOVEDIR); 199 continue; 200 } 201 unlinkat(fd, dp->d_name, 0); 202 } 203 closedir(dirp); 204 205 return (0); 206 } 207 208 static int 209 _pam_xdg_close(pam_handle_t *pamh __unused, int flags __unused, 210 int argc __unused, const char *argv[] __unused) 211 { 212 struct passwd *passwd; 213 const char *user; 214 const char *runtime_dir_prefix; 215 struct stat sb; 216 char *xdg_session_file; 217 int rv, rt_dir_prefix, rt_dir, session_file, i; 218 219 rt_dir = -1; 220 rt_dir_prefix = -1; 221 runtime_dir_prefix = openpam_get_option(pamh, "runtime_dir_prefix"); 222 223 /* Get user info */ 224 rv = pam_get_item(pamh, PAM_USER, (const void **)&user); 225 if (rv != PAM_SUCCESS || user == NULL) { 226 PAM_VERBOSE_ERROR("Can't get user information"); 227 goto out; 228 } 229 if ((passwd = getpwnam(user)) == NULL) { 230 PAM_VERBOSE_ERROR("Can't get user information"); 231 rv = PAM_SESSION_ERR; 232 goto out; 233 } 234 235 /* Open the xdg base directory */ 236 rt_dir_prefix = open(RUNTIME_DIR_PREFIX, O_DIRECTORY | O_NOFOLLOW); 237 if (rt_dir_prefix < 0) { 238 PAM_VERBOSE_ERROR("open: %s failed (%d)\n", runtime_dir_prefix, rt_dir_prefix); 239 rv = PAM_SESSION_ERR; 240 goto out; 241 } 242 /* Check that the already created dir is correctly owned */ 243 rv = fstatat(rt_dir_prefix, user, &sb, 0); 244 if (rv == -1) { 245 PAM_VERBOSE_ERROR("fstatat %s/%s failed (%d)", RUNTIME_DIR_PREFIX, user, errno); 246 rv = PAM_SESSION_ERR; 247 goto out; 248 } 249 if (sb.st_uid != passwd->pw_uid || 250 sb.st_gid != passwd->pw_gid) { 251 PAM_VERBOSE_ERROR("%s/%s isn't owned by %d:%d\n", RUNTIME_DIR_PREFIX, user, passwd->pw_uid, passwd->pw_gid); 252 rv = PAM_SESSION_ERR; 253 goto out; 254 } 255 /* Test directory mode */ 256 if ((sb.st_mode & 0x1FF) != RUNTIME_DIR_MODE) { 257 PAM_VERBOSE_ERROR("%s/%s have wrong mode\n", RUNTIME_DIR_PREFIX, user); 258 rv = PAM_SESSION_ERR; 259 goto out; 260 } 261 262 /* Open the user xdg directory */ 263 rt_dir = openat(rt_dir_prefix, user, O_DIRECTORY | O_NOFOLLOW); 264 if (rt_dir < 0) { 265 PAM_VERBOSE_ERROR("openat: %s/%s failed (%d)\n", RUNTIME_DIR_PREFIX, user, rt_dir_prefix); 266 rv = PAM_SESSION_ERR; 267 goto out; 268 } 269 270 /* Get the last session file created */ 271 for (i = XDG_MAX_SESSION; i >= 0; i--) { 272 rv = asprintf(&xdg_session_file, "%s/xdg_session.%d", user, i); 273 if (rv < 0) { 274 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 275 rv = PAM_SESSION_ERR; 276 goto out; 277 } 278 rv = 0; 279 session_file = openat(rt_dir_prefix, xdg_session_file, 0); 280 if (session_file >= 0) { 281 unlinkat(rt_dir_prefix, xdg_session_file, 0); 282 free(xdg_session_file); 283 break; 284 } 285 free(xdg_session_file); 286 } 287 if (session_file < 0) { 288 PAM_VERBOSE_ERROR("Can't find session number\n"); 289 rv = PAM_SESSION_ERR; 290 goto out; 291 } 292 close(session_file); 293 294 /* Final cleanup if last user session */ 295 if (i == 0) { 296 remove_dir(rt_dir); 297 if (unlinkat(rt_dir_prefix, user, AT_REMOVEDIR) != 0) { 298 PAM_VERBOSE_ERROR("Can't cleanup %s/%s\n", runtime_dir_prefix, user); 299 rv = PAM_SESSION_ERR; 300 goto out; 301 } 302 } 303 304 rv = PAM_SUCCESS; 305 out: 306 if (rt_dir >= 0) 307 close(rt_dir); 308 if (rt_dir_prefix >= 0) 309 close(rt_dir_prefix); 310 return (rv); 311 } 312 313 PAM_EXTERN int 314 pam_sm_open_session(pam_handle_t *pamh, int flags, 315 int argc, const char *argv[]) 316 { 317 318 return (_pam_xdg_open(pamh, flags, argc, argv)); 319 } 320 321 PAM_EXTERN int 322 pam_sm_close_session(pam_handle_t *pamh, int flags, 323 int argc, const char *argv[]) 324 { 325 326 return (_pam_xdg_close(pamh, flags, argc, argv)); 327 } 328 329 PAM_MODULE_ENTRY("pam_xdg"); 330