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