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 /* Check that the already create dir is correctly owned */ 112 rv = fstatat(rt_dir_prefix, user, &sb, 0); 113 if (rv == -1) { 114 PAM_VERBOSE_ERROR("fstatat %s/%s failed (%d)", RUNTIME_DIR_PREFIX, user, errno); 115 rv = PAM_SESSION_ERR; 116 goto out; 117 } 118 if (sb.st_uid != passwd->pw_uid || 119 sb.st_gid != passwd->pw_gid) { 120 PAM_VERBOSE_ERROR("%s/%s isn't owned by %d:%d\n", RUNTIME_DIR_PREFIX, user, passwd->pw_uid, passwd->pw_gid); 121 rv = PAM_SESSION_ERR; 122 goto out; 123 } 124 /* Test directory mode */ 125 if ((sb.st_mode & 0x1FF) != RUNTIME_DIR_MODE) { 126 PAM_VERBOSE_ERROR("%s/%s have wrong mode\n", RUNTIME_DIR_PREFIX, user); 127 rv = PAM_SESSION_ERR; 128 goto out; 129 } 130 } 131 132 /* Setup the environment variable */ 133 rv = asprintf(&runtime_dir, "XDG_RUNTIME_DIR=%s/%s", RUNTIME_DIR_PREFIX, user); 134 if (rv < 0) { 135 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 136 rv = PAM_SESSION_ERR; 137 goto out; 138 } 139 rv = pam_putenv(pamh, runtime_dir); 140 if (rv != PAM_SUCCESS) { 141 PAM_VERBOSE_ERROR("pam_putenv: failed (%d)", rv); 142 rv = PAM_SESSION_ERR; 143 goto out; 144 } 145 146 /* Setup the session count file */ 147 for (i = 0; i < XDG_MAX_SESSION; i++) { 148 rv = asprintf(&xdg_session_file, "%s/xdg_session.%d", user, i); 149 if (rv < 0) { 150 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 151 rv = PAM_SESSION_ERR; 152 goto out; 153 } 154 rv = 0; 155 session_file = openat(rt_dir_prefix, xdg_session_file, O_CREAT | O_EXCL, RUNTIME_DIR_MODE); 156 free(xdg_session_file); 157 if (session_file >= 0) 158 break; 159 } 160 if (session_file < 0) { 161 PAM_VERBOSE_ERROR("Too many sessions"); 162 rv = PAM_SESSION_ERR; 163 goto out; 164 } 165 166 out: 167 if (session_file >= 0) 168 close(session_file); 169 if (rt_dir_prefix >= 0) 170 close(rt_dir_prefix); 171 172 if (runtime_dir) 173 free(runtime_dir); 174 return (rv); 175 } 176 177 static int 178 remove_dir(int fd) 179 { 180 DIR *dirp; 181 struct dirent *dp; 182 183 dirp = fdopendir(fd); 184 if (dirp == NULL) 185 return (-1); 186 187 while ((dp = readdir(dirp)) != NULL) { 188 if (dp->d_type == DT_DIR) { 189 int dirfd; 190 191 if (strcmp(dp->d_name, ".") == 0 || 192 strcmp(dp->d_name, "..") == 0) 193 continue; 194 dirfd = openat(fd, dp->d_name, 0); 195 remove_dir(dirfd); 196 close(dirfd); 197 unlinkat(fd, dp->d_name, AT_REMOVEDIR); 198 continue; 199 } 200 unlinkat(fd, dp->d_name, 0); 201 } 202 closedir(dirp); 203 204 return (0); 205 } 206 207 static int 208 _pam_xdg_close(pam_handle_t *pamh __unused, int flags __unused, 209 int argc __unused, const char *argv[] __unused) 210 { 211 struct passwd *passwd; 212 const char *user; 213 const char *runtime_dir_prefix; 214 struct stat sb; 215 char *xdg_session_file; 216 int rv, rt_dir_prefix, rt_dir, session_file, i; 217 218 rt_dir = -1; 219 rt_dir_prefix = -1; 220 runtime_dir_prefix = openpam_get_option(pamh, "runtime_dir_prefix"); 221 222 /* Get user info */ 223 rv = pam_get_item(pamh, PAM_USER, (const void **)&user); 224 if (rv != PAM_SUCCESS || user == NULL) { 225 PAM_VERBOSE_ERROR("Can't get user information"); 226 goto out; 227 } 228 if ((passwd = getpwnam(user)) == NULL) { 229 PAM_VERBOSE_ERROR("Can't get user information"); 230 rv = PAM_SESSION_ERR; 231 goto out; 232 } 233 234 /* Open the xdg base directory */ 235 rt_dir_prefix = open(RUNTIME_DIR_PREFIX, O_DIRECTORY | O_NOFOLLOW); 236 if (rt_dir_prefix < 0) { 237 PAM_VERBOSE_ERROR("open: %s failed (%d)\n", runtime_dir_prefix, rt_dir_prefix); 238 rv = PAM_SESSION_ERR; 239 goto out; 240 } 241 /* Check that the already created dir is correctly owned */ 242 rv = fstatat(rt_dir_prefix, user, &sb, 0); 243 if (rv == -1) { 244 PAM_VERBOSE_ERROR("fstatat %s/%s failed (%d)", RUNTIME_DIR_PREFIX, user, errno); 245 rv = PAM_SESSION_ERR; 246 goto out; 247 } 248 if (sb.st_uid != passwd->pw_uid || 249 sb.st_gid != passwd->pw_gid) { 250 PAM_VERBOSE_ERROR("%s/%s isn't owned by %d:%d\n", RUNTIME_DIR_PREFIX, user, passwd->pw_uid, passwd->pw_gid); 251 rv = PAM_SESSION_ERR; 252 goto out; 253 } 254 /* Test directory mode */ 255 if ((sb.st_mode & 0x1FF) != RUNTIME_DIR_MODE) { 256 PAM_VERBOSE_ERROR("%s/%s have wrong mode\n", RUNTIME_DIR_PREFIX, user); 257 rv = PAM_SESSION_ERR; 258 goto out; 259 } 260 261 /* Open the user xdg directory */ 262 rt_dir = openat(rt_dir_prefix, user, O_DIRECTORY | O_NOFOLLOW); 263 if (rt_dir < 0) { 264 PAM_VERBOSE_ERROR("openat: %s/%s failed (%d)\n", RUNTIME_DIR_PREFIX, user, rt_dir_prefix); 265 rv = PAM_SESSION_ERR; 266 goto out; 267 } 268 269 /* Get the last session file created */ 270 for (i = XDG_MAX_SESSION; i >= 0; i--) { 271 rv = asprintf(&xdg_session_file, "%s/xdg_session.%d", user, i); 272 if (rv < 0) { 273 PAM_VERBOSE_ERROR("asprintf failed %d\n", rv); 274 rv = PAM_SESSION_ERR; 275 goto out; 276 } 277 rv = 0; 278 session_file = openat(rt_dir_prefix, xdg_session_file, 0); 279 if (session_file >= 0) { 280 unlinkat(rt_dir_prefix, xdg_session_file, 0); 281 free(xdg_session_file); 282 break; 283 } 284 free(xdg_session_file); 285 } 286 if (session_file < 0) { 287 PAM_VERBOSE_ERROR("Can't find session number\n"); 288 rv = PAM_SESSION_ERR; 289 goto out; 290 } 291 close(session_file); 292 293 /* Final cleanup if last user session */ 294 if (i == 0) { 295 remove_dir(rt_dir); 296 if (unlinkat(rt_dir_prefix, user, AT_REMOVEDIR) != 0) { 297 PAM_VERBOSE_ERROR("Can't cleanup %s/%s\n", runtime_dir_prefix, user); 298 rv = PAM_SESSION_ERR; 299 goto out; 300 } 301 } 302 303 rv = PAM_SUCCESS; 304 out: 305 if (rt_dir >= 0) 306 close(rt_dir); 307 if (rt_dir_prefix >= 0) 308 close(rt_dir_prefix); 309 return (rv); 310 } 311 312 PAM_EXTERN int 313 pam_sm_open_session(pam_handle_t *pamh, int flags, 314 int argc, const char *argv[]) 315 { 316 317 return (_pam_xdg_open(pamh, flags, argc, argv)); 318 } 319 320 PAM_EXTERN int 321 pam_sm_close_session(pam_handle_t *pamh, int flags, 322 int argc, const char *argv[]) 323 { 324 325 return (_pam_xdg_close(pamh, flags, argc, argv)); 326 } 327 328 PAM_MODULE_ENTRY("pam_xdg"); 329