xref: /freebsd/lib/libpam/modules/pam_exec/pam_exec.c (revision 3ef51c5fb9163f2aafb1c14729e06a8bf0c4d113)
1 /*-
2  * Copyright (c) 2001,2003 Networks Associates Technology, Inc.
3  * All rights reserved.
4  *
5  * This software was developed for the FreeBSD Project by ThinkSec AS and
6  * NAI Labs, the Security Research Division of Network Associates, Inc.
7  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8  * DARPA CHATS research program.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. The name of the author may not be used to endorse or promote
19  *    products derived from this software without specific prior written
20  *    permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 #include <sys/types.h>
39 #include <sys/wait.h>
40 
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 
47 #include <security/pam_appl.h>
48 #include <security/pam_modules.h>
49 #include <security/openpam.h>
50 
51 #define ENV_ITEM(n) { (n), #n }
52 static struct {
53 	int item;
54 	const char *name;
55 } env_items[] = {
56 	ENV_ITEM(PAM_SERVICE),
57 	ENV_ITEM(PAM_USER),
58 	ENV_ITEM(PAM_TTY),
59 	ENV_ITEM(PAM_RHOST),
60 	ENV_ITEM(PAM_RUSER),
61 };
62 
63 #define	PAM_RV_COUNT 24
64 
65 static int
66 _pam_exec(pam_handle_t *pamh __unused,
67     const char *func, int flags __unused, int argc, const char *argv[])
68 {
69 	int envlen, i, nitems, pam_err, status, return_prog_exit_status;
70 	int nitems_rv;
71 	char *env, **envlist, **tmp, *envstr;
72 	volatile int childerr;
73 	pid_t pid;
74 
75 	/*
76 	 * XXX For additional credit, divert child's stdin/stdout/stderr
77 	 * to the conversation function.
78 	 */
79 
80 	/*
81 	 * Parse options:
82 	 *   return_prog_exit_status:
83 	 *     use the program exit status as the return code of pam_exec
84 	 *   --:
85 	 *     stop options parsing; what follows is the command to execute
86 	 */
87 	return_prog_exit_status = 0;
88 	for (i = 0; i < argc; ++i) {
89 		if (strcmp(argv[i], "return_prog_exit_status") == 0) {
90 			openpam_log(PAM_LOG_DEBUG,
91 			    "%s: Option \"return_prog_exit_status\" enabled",
92 			    func);
93 			return_prog_exit_status = 1;
94 		} else {
95 			if (strcmp(argv[i], "--") == 0) {
96 				argc--;
97 				argv++;
98 			}
99 
100 			break;
101 		}
102 	}
103 
104 	argc -= i;
105 	argv += i;
106 
107 	/* Check there's a program name left after parsing options. */
108 	if (argc < 1) {
109 		openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
110 		    func);
111 		return (PAM_SERVICE_ERR);
112 	}
113 
114 	/*
115 	 * Set up the child's environment list. It consists of the PAM
116 	 * environment, plus a few hand-picked PAM items, the pam_sm_*
117 	 * function name calling it and, if return_prog_exit_status is
118 	 * set, the valid return codes numerical values.
119 	 */
120 	envlist = pam_getenvlist(pamh);
121 	for (envlen = 0; envlist[envlen] != NULL; ++envlen)
122 		/* nothing */ ;
123 	nitems = sizeof(env_items) / sizeof(*env_items);
124 	/* Count PAM return values put in the environment. */
125 	nitems_rv = return_prog_exit_status ? PAM_RV_COUNT : 0;
126 	tmp = realloc(envlist, (envlen + nitems + 1 + nitems_rv + 1) *
127 	    sizeof(*envlist));
128 	if (tmp == NULL) {
129 		openpam_free_envlist(envlist);
130 		return (PAM_BUF_ERR);
131 	}
132 	envlist = tmp;
133 	for (i = 0; i < nitems; ++i) {
134 		const void *item;
135 
136 		pam_err = pam_get_item(pamh, env_items[i].item, &item);
137 		if (pam_err != PAM_SUCCESS || item == NULL)
138 			continue;
139 		asprintf(&envstr, "%s=%s", env_items[i].name, item);
140 		if (envstr == NULL) {
141 			openpam_free_envlist(envlist);
142 			return (PAM_BUF_ERR);
143 		}
144 		envlist[envlen++] = envstr;
145 		envlist[envlen] = NULL;
146 	}
147 
148 	/* Add the pam_sm_* function name to the environment. */
149 	asprintf(&envstr, "PAM_SM_FUNC=%s", func);
150 	if (envstr == NULL) {
151 		openpam_free_envlist(envlist);
152 		return (PAM_BUF_ERR);
153 	}
154 	envlist[envlen++] = envstr;
155 
156 	/* Add the PAM return values to the environment. */
157 	if (return_prog_exit_status) {
158 #define	ADD_PAM_RV_TO_ENV(name)						\
159 		asprintf(&envstr, #name "=%d", name);			\
160 		if (envstr == NULL) {					\
161 			openpam_free_envlist(envlist);			\
162 			return (PAM_BUF_ERR);				\
163 		}							\
164 		envlist[envlen++] = envstr
165 		/*
166 		 * CAUTION: When adding/removing an item in the list
167 		 * below, be sure to update the value of PAM_RV_COUNT.
168 		 */
169 		ADD_PAM_RV_TO_ENV(PAM_ABORT);
170 		ADD_PAM_RV_TO_ENV(PAM_ACCT_EXPIRED);
171 		ADD_PAM_RV_TO_ENV(PAM_AUTHINFO_UNAVAIL);
172 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_DISABLE_AGING);
173 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_ERR);
174 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_LOCK_BUSY);
175 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_RECOVERY_ERR);
176 		ADD_PAM_RV_TO_ENV(PAM_AUTH_ERR);
177 		ADD_PAM_RV_TO_ENV(PAM_BUF_ERR);
178 		ADD_PAM_RV_TO_ENV(PAM_CONV_ERR);
179 		ADD_PAM_RV_TO_ENV(PAM_CRED_ERR);
180 		ADD_PAM_RV_TO_ENV(PAM_CRED_EXPIRED);
181 		ADD_PAM_RV_TO_ENV(PAM_CRED_INSUFFICIENT);
182 		ADD_PAM_RV_TO_ENV(PAM_CRED_UNAVAIL);
183 		ADD_PAM_RV_TO_ENV(PAM_IGNORE);
184 		ADD_PAM_RV_TO_ENV(PAM_MAXTRIES);
185 		ADD_PAM_RV_TO_ENV(PAM_NEW_AUTHTOK_REQD);
186 		ADD_PAM_RV_TO_ENV(PAM_PERM_DENIED);
187 		ADD_PAM_RV_TO_ENV(PAM_SERVICE_ERR);
188 		ADD_PAM_RV_TO_ENV(PAM_SESSION_ERR);
189 		ADD_PAM_RV_TO_ENV(PAM_SUCCESS);
190 		ADD_PAM_RV_TO_ENV(PAM_SYSTEM_ERR);
191 		ADD_PAM_RV_TO_ENV(PAM_TRY_AGAIN);
192 		ADD_PAM_RV_TO_ENV(PAM_USER_UNKNOWN);
193 	}
194 
195 	envlist[envlen] = NULL;
196 
197 	/*
198 	 * Fork and run the command.  By using vfork() instead of fork(),
199 	 * we can distinguish between an execve() failure and a non-zero
200 	 * exit status from the command.
201 	 */
202 	childerr = 0;
203 	if ((pid = vfork()) == 0) {
204 		execve(argv[0], (char * const *)argv, (char * const *)envlist);
205 		childerr = errno;
206 		_exit(1);
207 	}
208 	openpam_free_envlist(envlist);
209 	if (pid == -1) {
210 		openpam_log(PAM_LOG_ERROR, "%s: vfork(): %m", func);
211 		return (PAM_SYSTEM_ERR);
212 	}
213 	while (waitpid(pid, &status, 0) == -1) {
214 		if (errno == EINTR)
215 			continue;
216 		openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
217 		return (PAM_SYSTEM_ERR);
218 	}
219 	if (childerr != 0) {
220 		openpam_log(PAM_LOG_ERROR, "%s: execve(): %m", func);
221 		return (PAM_SYSTEM_ERR);
222 	}
223 	if (WIFSIGNALED(status)) {
224 		openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
225 		    func, argv[0], WTERMSIG(status),
226 		    WCOREDUMP(status) ? " (core dumped)" : "");
227 		return (PAM_SERVICE_ERR);
228 	}
229 	if (!WIFEXITED(status)) {
230 		openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
231 		    func, status);
232 		return (PAM_SERVICE_ERR);
233 	}
234 
235 	if (return_prog_exit_status) {
236 		openpam_log(PAM_LOG_DEBUG,
237 		    "%s: Use program exit status as return value: %d",
238 		    func, WEXITSTATUS(status));
239 		return (WEXITSTATUS(status));
240 	} else {
241 		return (WEXITSTATUS(status) == 0 ?
242 		    PAM_SUCCESS : PAM_PERM_DENIED);
243 	}
244 }
245 
246 PAM_EXTERN int
247 pam_sm_authenticate(pam_handle_t *pamh, int flags,
248     int argc, const char *argv[])
249 {
250 	int ret;
251 
252 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
253 
254 	/*
255 	 * We must check that the program returned a valid code for this
256 	 * function.
257 	 */
258 	switch (ret) {
259 	case PAM_SUCCESS:
260 	case PAM_ABORT:
261 	case PAM_AUTHINFO_UNAVAIL:
262 	case PAM_AUTH_ERR:
263 	case PAM_BUF_ERR:
264 	case PAM_CONV_ERR:
265 	case PAM_CRED_INSUFFICIENT:
266 	case PAM_IGNORE:
267 	case PAM_MAXTRIES:
268 	case PAM_PERM_DENIED:
269 	case PAM_SERVICE_ERR:
270 	case PAM_SYSTEM_ERR:
271 	case PAM_USER_UNKNOWN:
272 		break;
273 	default:
274 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
275 		    argv[0], ret);
276 		ret = PAM_SERVICE_ERR;
277 	}
278 
279 	return (ret);
280 }
281 
282 PAM_EXTERN int
283 pam_sm_setcred(pam_handle_t *pamh, int flags,
284     int argc, const char *argv[])
285 {
286 	int ret;
287 
288 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
289 
290 	/*
291 	 * We must check that the program returned a valid code for this
292 	 * function.
293 	 */
294 	switch (ret) {
295 	case PAM_SUCCESS:
296 	case PAM_ABORT:
297 	case PAM_BUF_ERR:
298 	case PAM_CONV_ERR:
299 	case PAM_CRED_ERR:
300 	case PAM_CRED_EXPIRED:
301 	case PAM_CRED_UNAVAIL:
302 	case PAM_IGNORE:
303 	case PAM_PERM_DENIED:
304 	case PAM_SERVICE_ERR:
305 	case PAM_SYSTEM_ERR:
306 	case PAM_USER_UNKNOWN:
307 		break;
308 	default:
309 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
310 		    argv[0], ret);
311 		ret = PAM_SERVICE_ERR;
312 	}
313 
314 	return (ret);
315 }
316 
317 PAM_EXTERN int
318 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
319     int argc, const char *argv[])
320 {
321 	int ret;
322 
323 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
324 
325 	/*
326 	 * We must check that the program returned a valid code for this
327 	 * function.
328 	 */
329 	switch (ret) {
330 	case PAM_SUCCESS:
331 	case PAM_ABORT:
332 	case PAM_ACCT_EXPIRED:
333 	case PAM_AUTH_ERR:
334 	case PAM_BUF_ERR:
335 	case PAM_CONV_ERR:
336 	case PAM_IGNORE:
337 	case PAM_NEW_AUTHTOK_REQD:
338 	case PAM_PERM_DENIED:
339 	case PAM_SERVICE_ERR:
340 	case PAM_SYSTEM_ERR:
341 	case PAM_USER_UNKNOWN:
342 		break;
343 	default:
344 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
345 		    argv[0], ret);
346 		ret = PAM_SERVICE_ERR;
347 	}
348 
349 	return (ret);
350 }
351 
352 PAM_EXTERN int
353 pam_sm_open_session(pam_handle_t *pamh, int flags,
354     int argc, const char *argv[])
355 {
356 	int ret;
357 
358 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
359 
360 	/*
361 	 * We must check that the program returned a valid code for this
362 	 * function.
363 	 */
364 	switch (ret) {
365 	case PAM_SUCCESS:
366 	case PAM_ABORT:
367 	case PAM_BUF_ERR:
368 	case PAM_CONV_ERR:
369 	case PAM_IGNORE:
370 	case PAM_PERM_DENIED:
371 	case PAM_SERVICE_ERR:
372 	case PAM_SESSION_ERR:
373 	case PAM_SYSTEM_ERR:
374 		break;
375 	default:
376 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
377 		    argv[0], ret);
378 		ret = PAM_SERVICE_ERR;
379 	}
380 
381 	return (ret);
382 }
383 
384 PAM_EXTERN int
385 pam_sm_close_session(pam_handle_t *pamh, int flags,
386     int argc, const char *argv[])
387 {
388 	int ret;
389 
390 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
391 
392 	/*
393 	 * We must check that the program returned a valid code for this
394 	 * function.
395 	 */
396 	switch (ret) {
397 	case PAM_SUCCESS:
398 	case PAM_ABORT:
399 	case PAM_BUF_ERR:
400 	case PAM_CONV_ERR:
401 	case PAM_IGNORE:
402 	case PAM_PERM_DENIED:
403 	case PAM_SERVICE_ERR:
404 	case PAM_SESSION_ERR:
405 	case PAM_SYSTEM_ERR:
406 		break;
407 	default:
408 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
409 		    argv[0], ret);
410 		ret = PAM_SERVICE_ERR;
411 	}
412 
413 	return (ret);
414 }
415 
416 PAM_EXTERN int
417 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
418     int argc, const char *argv[])
419 {
420 	int ret;
421 
422 	ret = _pam_exec(pamh, __func__, flags, argc, argv);
423 
424 	/*
425 	 * We must check that the program returned a valid code for this
426 	 * function.
427 	 */
428 	switch (ret) {
429 	case PAM_SUCCESS:
430 	case PAM_ABORT:
431 	case PAM_AUTHTOK_DISABLE_AGING:
432 	case PAM_AUTHTOK_ERR:
433 	case PAM_AUTHTOK_LOCK_BUSY:
434 	case PAM_AUTHTOK_RECOVERY_ERR:
435 	case PAM_BUF_ERR:
436 	case PAM_CONV_ERR:
437 	case PAM_IGNORE:
438 	case PAM_PERM_DENIED:
439 	case PAM_SERVICE_ERR:
440 	case PAM_SYSTEM_ERR:
441 	case PAM_TRY_AGAIN:
442 		break;
443 	default:
444 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
445 		    argv[0], ret);
446 		ret = PAM_SERVICE_ERR;
447 	}
448 
449 	return (ret);
450 }
451 
452 PAM_MODULE_ENTRY("pam_exec");
453