xref: /freebsd/lib/libpam/modules/pam_xdg/pam_xdg.c (revision e9ac41698b2f322d55ccf9da50a3596edb2c1800)
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