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
_pam_xdg_open(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)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
remove_dir(int fd)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
_pam_xdg_close(pam_handle_t * pamh __unused,int flags __unused,int argc __unused,const char * argv[]__unused)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
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])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
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char * argv[])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