pam_exec.c revision 233507
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: head/lib/libpam/modules/pam_exec/pam_exec.c 233507 2012-03-26 12:18:15Z dumbbell $");
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
63#define	PAM_RV_COUNT 24
64
65static 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
246PAM_EXTERN int
247pam_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
282PAM_EXTERN int
283pam_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
317PAM_EXTERN int
318pam_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
352PAM_EXTERN int
353pam_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
384PAM_EXTERN int
385pam_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
416PAM_EXTERN int
417pam_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
452PAM_MODULE_ENTRY("pam_exec");
453