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