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  * $FreeBSD: src/lib/libpam/modules/pam_exec/pam_exec.c,v 1.9 2012/04/12 14:02:59 dumbbell Exp $
35  */
36 
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 
40 #include <errno.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include <security/pam_appl.h>
47 #include <security/pam_modules.h>
48 #include <security/openpam.h>
49 
50 #define ENV_ITEM(n) { (n), #n }
51 static struct {
52 	int item;
53 	const char *name;
54 } env_items[] = {
55 	ENV_ITEM(PAM_SERVICE),
56 	ENV_ITEM(PAM_USER),
57 	ENV_ITEM(PAM_TTY),
58 	ENV_ITEM(PAM_RHOST),
59 	ENV_ITEM(PAM_RUSER),
60 };
61 
62 struct pe_opts {
63 	int	return_prog_exit_status;
64 };
65 
66 #define	PAM_RV_COUNT 24
67 
68 static int
69 parse_options(const char *func, int *argc, const char **argv[],
70     struct pe_opts *options)
71 {
72 	int i;
73 
74 	/*
75 	 * Parse options:
76 	 *   return_prog_exit_status:
77 	 *     use the program exit status as the return code of pam_exec
78 	 *   --:
79 	 *     stop options parsing; what follows is the command to execute
80 	 */
81 	options->return_prog_exit_status = 0;
82 
83 	for (i = 0; i < *argc; ++i) {
84 		if (strcmp((*argv)[i], "return_prog_exit_status") == 0) {
85 			openpam_log(PAM_LOG_DEBUG,
86 			    "%s: Option \"return_prog_exit_status\" enabled",
87 			    func);
88 			options->return_prog_exit_status = 1;
89 		} else {
90 			if (strcmp((*argv)[i], "--") == 0) {
91 				(*argc)--;
92 				(*argv)++;
93 			}
94 
95 			break;
96 		}
97 	}
98 
99 	(*argc) -= i;
100 	(*argv) += i;
101 
102 	return (0);
103 }
104 
105 static int
106 _pam_exec(pam_handle_t *pamh __unused,
107     const char *func, int flags __unused, int argc, const char *argv[],
108     struct pe_opts *options)
109 {
110 	int envlen, i, nitems, pam_err, status;
111 	int nitems_rv;
112 	char **envlist, **tmp, *envstr;
113 	volatile int childerr;
114 	pid_t pid;
115 
116 	/*
117 	 * XXX For additional credit, divert child's stdin/stdout/stderr
118 	 * to the conversation function.
119 	 */
120 
121 	/* Check there's a program name left after parsing options. */
122 	if (argc < 1) {
123 		openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
124 		    func);
125 		return (PAM_SERVICE_ERR);
126 	}
127 
128 	/*
129 	 * Set up the child's environment list. It consists of the PAM
130 	 * environment, plus a few hand-picked PAM items, the pam_sm_*
131 	 * function name calling it and, if return_prog_exit_status is
132 	 * set, the valid return codes numerical values.
133 	 */
134 	envlist = pam_getenvlist(pamh);
135 	for (envlen = 0; envlist[envlen] != NULL; ++envlen)
136 		/* nothing */ ;
137 	nitems = sizeof(env_items) / sizeof(*env_items);
138 	/* Count PAM return values put in the environment. */
139 	nitems_rv = options->return_prog_exit_status ? PAM_RV_COUNT : 0;
140 	tmp = realloc(envlist, (envlen + nitems + 1 + nitems_rv + 1) *
141 	    sizeof(*envlist));
142 	if (tmp == NULL) {
143 		openpam_free_envlist(envlist);
144 		return (PAM_BUF_ERR);
145 	}
146 	envlist = tmp;
147 	for (i = 0; i < nitems; ++i) {
148 		const void *item;
149 
150 		pam_err = pam_get_item(pamh, env_items[i].item, &item);
151 		if (pam_err != PAM_SUCCESS || item == NULL)
152 			continue;
153 		asprintf(&envstr, "%s=%s", env_items[i].name,
154 		    (const char *)item);
155 		if (envstr == NULL) {
156 			openpam_free_envlist(envlist);
157 			return (PAM_BUF_ERR);
158 		}
159 		envlist[envlen++] = envstr;
160 		envlist[envlen] = NULL;
161 	}
162 
163 	/* Add the pam_sm_* function name to the environment. */
164 	asprintf(&envstr, "PAM_SM_FUNC=%s", func);
165 	if (envstr == NULL) {
166 		openpam_free_envlist(envlist);
167 		return (PAM_BUF_ERR);
168 	}
169 	envlist[envlen++] = envstr;
170 
171 	/* Add the PAM return values to the environment. */
172 	if (options->return_prog_exit_status) {
173 #define	ADD_PAM_RV_TO_ENV(name)						\
174 		asprintf(&envstr, #name "=%d", name);			\
175 		if (envstr == NULL) {					\
176 			openpam_free_envlist(envlist);			\
177 			return (PAM_BUF_ERR);				\
178 		}							\
179 		envlist[envlen++] = envstr
180 		/*
181 		 * CAUTION: When adding/removing an item in the list
182 		 * below, be sure to update the value of PAM_RV_COUNT.
183 		 */
184 		ADD_PAM_RV_TO_ENV(PAM_ABORT);
185 		ADD_PAM_RV_TO_ENV(PAM_ACCT_EXPIRED);
186 		ADD_PAM_RV_TO_ENV(PAM_AUTHINFO_UNAVAIL);
187 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_DISABLE_AGING);
188 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_ERR);
189 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_LOCK_BUSY);
190 		ADD_PAM_RV_TO_ENV(PAM_AUTHTOK_RECOVERY_ERR);
191 		ADD_PAM_RV_TO_ENV(PAM_AUTH_ERR);
192 		ADD_PAM_RV_TO_ENV(PAM_BUF_ERR);
193 		ADD_PAM_RV_TO_ENV(PAM_CONV_ERR);
194 		ADD_PAM_RV_TO_ENV(PAM_CRED_ERR);
195 		ADD_PAM_RV_TO_ENV(PAM_CRED_EXPIRED);
196 		ADD_PAM_RV_TO_ENV(PAM_CRED_INSUFFICIENT);
197 		ADD_PAM_RV_TO_ENV(PAM_CRED_UNAVAIL);
198 		ADD_PAM_RV_TO_ENV(PAM_IGNORE);
199 		ADD_PAM_RV_TO_ENV(PAM_MAXTRIES);
200 		ADD_PAM_RV_TO_ENV(PAM_NEW_AUTHTOK_REQD);
201 		ADD_PAM_RV_TO_ENV(PAM_PERM_DENIED);
202 		ADD_PAM_RV_TO_ENV(PAM_SERVICE_ERR);
203 		ADD_PAM_RV_TO_ENV(PAM_SESSION_ERR);
204 		ADD_PAM_RV_TO_ENV(PAM_SUCCESS);
205 		ADD_PAM_RV_TO_ENV(PAM_SYSTEM_ERR);
206 		ADD_PAM_RV_TO_ENV(PAM_TRY_AGAIN);
207 		ADD_PAM_RV_TO_ENV(PAM_USER_UNKNOWN);
208 	}
209 
210 	envlist[envlen] = NULL;
211 
212 	/*
213 	 * Fork and run the command.  By using vfork() instead of fork(),
214 	 * we can distinguish between an execve() failure and a non-zero
215 	 * exit status from the command.
216 	 */
217 	childerr = 0;
218 	if ((pid = vfork()) == 0) {
219 		execve(argv[0], (char * const *)argv, (char * const *)envlist);
220 		childerr = errno;
221 		_exit(1);
222 	}
223 	openpam_free_envlist(envlist);
224 	if (pid == -1) {
225 		openpam_log(PAM_LOG_ERROR, "%s: vfork(): %m", func);
226 		return (PAM_SYSTEM_ERR);
227 	}
228 	while (waitpid(pid, &status, 0) == -1) {
229 		if (errno == EINTR)
230 			continue;
231 		openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
232 		return (PAM_SYSTEM_ERR);
233 	}
234 	if (childerr != 0) {
235 		openpam_log(PAM_LOG_ERROR, "%s: execve(): %m", func);
236 		return (PAM_SYSTEM_ERR);
237 	}
238 	if (WIFSIGNALED(status)) {
239 		openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
240 		    func, argv[0], WTERMSIG(status),
241 		    WCOREDUMP(status) ? " (core dumped)" : "");
242 		return (PAM_SERVICE_ERR);
243 	}
244 	if (!WIFEXITED(status)) {
245 		openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
246 		    func, status);
247 		return (PAM_SERVICE_ERR);
248 	}
249 
250 	if (options->return_prog_exit_status) {
251 		openpam_log(PAM_LOG_DEBUG,
252 		    "%s: Use program exit status as return value: %d",
253 		    func, WEXITSTATUS(status));
254 		return (WEXITSTATUS(status));
255 	} else {
256 		return (WEXITSTATUS(status) == 0 ?
257 		    PAM_SUCCESS : PAM_PERM_DENIED);
258 	}
259 }
260 
261 PAM_EXTERN int
262 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[])
263 {
264 	int ret;
265 	struct pe_opts options;
266 
267 	ret = parse_options(__func__, &argc, &argv, &options);
268 	if (ret != 0)
269 		return (PAM_SERVICE_ERR);
270 
271 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
272 
273 	/*
274 	 * We must check that the program returned a valid code for this
275 	 * function.
276 	 */
277 	switch (ret) {
278 	case PAM_SUCCESS:
279 	case PAM_ABORT:
280 	case PAM_AUTHINFO_UNAVAIL:
281 	case PAM_AUTH_ERR:
282 	case PAM_BUF_ERR:
283 	case PAM_CONV_ERR:
284 	case PAM_CRED_INSUFFICIENT:
285 	case PAM_IGNORE:
286 	case PAM_MAXTRIES:
287 	case PAM_PERM_DENIED:
288 	case PAM_SERVICE_ERR:
289 	case PAM_SYSTEM_ERR:
290 	case PAM_USER_UNKNOWN:
291 		break;
292 	default:
293 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
294 		    argv[0], ret);
295 		ret = PAM_SERVICE_ERR;
296 	}
297 
298 	return (ret);
299 }
300 
301 PAM_EXTERN int
302 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[])
303 {
304 	int ret;
305 	struct pe_opts options;
306 
307 	ret = parse_options(__func__, &argc, &argv, &options);
308 	if (ret != 0)
309 		return (PAM_SERVICE_ERR);
310 
311 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
312 
313 	/*
314 	 * We must check that the program returned a valid code for this
315 	 * function.
316 	 */
317 	switch (ret) {
318 	case PAM_SUCCESS:
319 	case PAM_ABORT:
320 	case PAM_BUF_ERR:
321 	case PAM_CONV_ERR:
322 	case PAM_CRED_ERR:
323 	case PAM_CRED_EXPIRED:
324 	case PAM_CRED_UNAVAIL:
325 	case PAM_IGNORE:
326 	case PAM_PERM_DENIED:
327 	case PAM_SERVICE_ERR:
328 	case PAM_SYSTEM_ERR:
329 	case PAM_USER_UNKNOWN:
330 		break;
331 	default:
332 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
333 		    argv[0], ret);
334 		ret = PAM_SERVICE_ERR;
335 	}
336 
337 	return (ret);
338 }
339 
340 PAM_EXTERN int
341 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[])
342 {
343 	int ret;
344 	struct pe_opts options;
345 
346 	ret = parse_options(__func__, &argc, &argv, &options);
347 	if (ret != 0)
348 		return (PAM_SERVICE_ERR);
349 
350 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
351 
352 	/*
353 	 * We must check that the program returned a valid code for this
354 	 * function.
355 	 */
356 	switch (ret) {
357 	case PAM_SUCCESS:
358 	case PAM_ABORT:
359 	case PAM_ACCT_EXPIRED:
360 	case PAM_AUTH_ERR:
361 	case PAM_BUF_ERR:
362 	case PAM_CONV_ERR:
363 	case PAM_IGNORE:
364 	case PAM_NEW_AUTHTOK_REQD:
365 	case PAM_PERM_DENIED:
366 	case PAM_SERVICE_ERR:
367 	case PAM_SYSTEM_ERR:
368 	case PAM_USER_UNKNOWN:
369 		break;
370 	default:
371 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
372 		    argv[0], ret);
373 		ret = PAM_SERVICE_ERR;
374 	}
375 
376 	return (ret);
377 }
378 
379 PAM_EXTERN int
380 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[])
381 {
382 	int ret;
383 	struct pe_opts options;
384 
385 	ret = parse_options(__func__, &argc, &argv, &options);
386 	if (ret != 0)
387 		return (PAM_SERVICE_ERR);
388 
389 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
390 
391 	/*
392 	 * We must check that the program returned a valid code for this
393 	 * function.
394 	 */
395 	switch (ret) {
396 	case PAM_SUCCESS:
397 	case PAM_ABORT:
398 	case PAM_BUF_ERR:
399 	case PAM_CONV_ERR:
400 	case PAM_IGNORE:
401 	case PAM_PERM_DENIED:
402 	case PAM_SERVICE_ERR:
403 	case PAM_SESSION_ERR:
404 	case PAM_SYSTEM_ERR:
405 		break;
406 	default:
407 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
408 		    argv[0], ret);
409 		ret = PAM_SERVICE_ERR;
410 	}
411 
412 	return (ret);
413 }
414 
415 PAM_EXTERN int
416 pam_sm_close_session(pam_handle_t *pamh, int flags,
417 		     int argc, const char *argv[])
418 {
419 	int ret;
420 	struct pe_opts options;
421 
422 	ret = parse_options(__func__, &argc, &argv, &options);
423 	if (ret != 0)
424 		return (PAM_SERVICE_ERR);
425 
426 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
427 
428 	/*
429 	 * We must check that the program returned a valid code for this
430 	 * function.
431 	 */
432 	switch (ret) {
433 	case PAM_SUCCESS:
434 	case PAM_ABORT:
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_SESSION_ERR:
441 	case PAM_SYSTEM_ERR:
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_EXTERN int
453 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[])
454 {
455 	int ret;
456 	struct pe_opts options;
457 
458 	ret = parse_options(__func__, &argc, &argv, &options);
459 	if (ret != 0)
460 		return (PAM_SERVICE_ERR);
461 
462 	ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
463 
464 	/*
465 	 * We must check that the program returned a valid code for this
466 	 * function.
467 	 */
468 	switch (ret) {
469 	case PAM_SUCCESS:
470 	case PAM_ABORT:
471 	case PAM_AUTHTOK_DISABLE_AGING:
472 	case PAM_AUTHTOK_ERR:
473 	case PAM_AUTHTOK_LOCK_BUSY:
474 	case PAM_AUTHTOK_RECOVERY_ERR:
475 	case PAM_BUF_ERR:
476 	case PAM_CONV_ERR:
477 	case PAM_IGNORE:
478 	case PAM_PERM_DENIED:
479 	case PAM_SERVICE_ERR:
480 	case PAM_SYSTEM_ERR:
481 	case PAM_TRY_AGAIN:
482 		break;
483 	default:
484 		openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
485 		    argv[0], ret);
486 		ret = PAM_SERVICE_ERR;
487 	}
488 
489 	return (ret);
490 }
491 
492 PAM_MODULE_ENTRY("pam_exec");
493