pam_exec: Use program exit status as return code
[dragonfly.git] / lib / pam_module / pam_exec / pam_exec.c
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");