]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libpam/modules/pam_exec/pam_exec.c
9539 Make zvol operations use _by_dnode routines
[FreeBSD/FreeBSD.git] / lib / libpam / modules / pam_exec / pam_exec.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2001,2003 Networks Associates Technology, Inc.
5  * Copyright (c) 2017 Dag-Erling Smørgrav
6  * All rights reserved.
7  *
8  * This software was developed for the FreeBSD Project by ThinkSec AS and
9  * NAI Labs, the Security Research Division of Network Associates, Inc.
10  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
11  * DARPA CHATS research program.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The name of the author may not be used to endorse or promote
22  *    products derived from this software without specific prior written
23  *    permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
40
41 #include <sys/types.h>
42 #include <sys/poll.h>
43 #include <sys/procdesc.h>
44 #include <sys/wait.h>
45
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52
53 #include <security/pam_appl.h>
54 #include <security/pam_modules.h>
55 #include <security/openpam.h>
56
57 #define PAM_ITEM_ENV(n) { (n), #n }
58 static struct {
59         int item;
60         const char *name;
61 } pam_item_env[] = {
62         PAM_ITEM_ENV(PAM_SERVICE),
63         PAM_ITEM_ENV(PAM_USER),
64         PAM_ITEM_ENV(PAM_TTY),
65         PAM_ITEM_ENV(PAM_RHOST),
66         PAM_ITEM_ENV(PAM_RUSER),
67 };
68 #define NUM_PAM_ITEM_ENV (sizeof(pam_item_env) / sizeof(pam_item_env[0]))
69
70 #define PAM_ERR_ENV_X(str, num) str "=" #num
71 #define PAM_ERR_ENV(pam_err) PAM_ERR_ENV_X(#pam_err, pam_err)
72 static const char *pam_err_env[] = {
73         PAM_ERR_ENV(PAM_SUCCESS),
74         PAM_ERR_ENV(PAM_OPEN_ERR),
75         PAM_ERR_ENV(PAM_SYMBOL_ERR),
76         PAM_ERR_ENV(PAM_SERVICE_ERR),
77         PAM_ERR_ENV(PAM_SYSTEM_ERR),
78         PAM_ERR_ENV(PAM_BUF_ERR),
79         PAM_ERR_ENV(PAM_CONV_ERR),
80         PAM_ERR_ENV(PAM_PERM_DENIED),
81         PAM_ERR_ENV(PAM_MAXTRIES),
82         PAM_ERR_ENV(PAM_AUTH_ERR),
83         PAM_ERR_ENV(PAM_NEW_AUTHTOK_REQD),
84         PAM_ERR_ENV(PAM_CRED_INSUFFICIENT),
85         PAM_ERR_ENV(PAM_AUTHINFO_UNAVAIL),
86         PAM_ERR_ENV(PAM_USER_UNKNOWN),
87         PAM_ERR_ENV(PAM_CRED_UNAVAIL),
88         PAM_ERR_ENV(PAM_CRED_EXPIRED),
89         PAM_ERR_ENV(PAM_CRED_ERR),
90         PAM_ERR_ENV(PAM_ACCT_EXPIRED),
91         PAM_ERR_ENV(PAM_AUTHTOK_EXPIRED),
92         PAM_ERR_ENV(PAM_SESSION_ERR),
93         PAM_ERR_ENV(PAM_AUTHTOK_ERR),
94         PAM_ERR_ENV(PAM_AUTHTOK_RECOVERY_ERR),
95         PAM_ERR_ENV(PAM_AUTHTOK_LOCK_BUSY),
96         PAM_ERR_ENV(PAM_AUTHTOK_DISABLE_AGING),
97         PAM_ERR_ENV(PAM_NO_MODULE_DATA),
98         PAM_ERR_ENV(PAM_IGNORE),
99         PAM_ERR_ENV(PAM_ABORT),
100         PAM_ERR_ENV(PAM_TRY_AGAIN),
101         PAM_ERR_ENV(PAM_MODULE_UNKNOWN),
102         PAM_ERR_ENV(PAM_DOMAIN_UNKNOWN),
103         PAM_ERR_ENV(PAM_NUM_ERR),
104 };
105 #define NUM_PAM_ERR_ENV (sizeof(pam_err_env) / sizeof(pam_err_env[0]))
106
107 struct pe_opts {
108         int     return_prog_exit_status;
109         int     capture_stdout;
110         int     capture_stderr;
111 };
112
113 static int
114 parse_options(const char *func, int *argc, const char **argv[],
115     struct pe_opts *options)
116 {
117         int i;
118
119         /*
120          * Parse options:
121          *   return_prog_exit_status:
122          *     use the program exit status as the return code of pam_exec
123          *   --:
124          *     stop options parsing; what follows is the command to execute
125          */
126         memset(options, 0, sizeof(*options));
127
128         for (i = 0; i < *argc; ++i) {
129                 if (strcmp((*argv)[i], "debug") == 0 ||
130                     strcmp((*argv)[i], "no_warn") == 0) {
131                         /* ignore */
132                 } else if (strcmp((*argv)[i], "capture_stdout") == 0) {
133                         options->capture_stdout = 1;
134                 } else if (strcmp((*argv)[i], "capture_stderr") == 0) {
135                         options->capture_stderr = 1;
136                 } else if (strcmp((*argv)[i], "return_prog_exit_status") == 0) {
137                         options->return_prog_exit_status = 1;
138                 } else {
139                         if (strcmp((*argv)[i], "--") == 0) {
140                                 (*argc)--;
141                                 (*argv)++;
142                         }
143                         break;
144                 }
145                 openpam_log(PAM_LOG_DEBUG, "%s: option \"%s\" enabled",
146                     func, (*argv)[i]);
147         }
148
149         (*argc) -= i;
150         (*argv) += i;
151
152         return (0);
153 }
154
155 static int
156 _pam_exec(pam_handle_t *pamh,
157     const char *func, int flags __unused, int argc, const char *argv[],
158     struct pe_opts *options)
159 {
160         char buf[PAM_MAX_MSG_SIZE];
161         struct pollfd pfd[3];
162         const void *item;
163         char **envlist, *envstr, *resp, **tmp;
164         ssize_t rlen;
165         int envlen, extralen, i;
166         int pam_err, serrno, status;
167         int chout[2], cherr[2], pd;
168         nfds_t nfds;
169         pid_t pid;
170
171         pd = -1;
172         pid = 0;
173         chout[0] = chout[1] = cherr[0] = cherr[1] = -1;
174         envlist = NULL;
175
176 #define OUT(ret) do { pam_err = (ret); goto out; } while (0)
177
178         /* Check there's a program name left after parsing options. */
179         if (argc < 1) {
180                 openpam_log(PAM_LOG_ERROR, "%s: No program specified: aborting",
181                     func);
182                 OUT(PAM_SERVICE_ERR);
183         }
184
185         /*
186          * Set up the child's environment list.  It consists of the PAM
187          * environment, a few hand-picked PAM items, the name of the
188          * service function, and if return_prog_exit_status is set, the
189          * numerical values of all PAM error codes.
190          */
191
192         /* compute the final size of the environment. */
193         envlist = pam_getenvlist(pamh);
194         for (envlen = 0; envlist[envlen] != NULL; ++envlen)
195                 /* nothing */ ;
196         extralen = NUM_PAM_ITEM_ENV + 1;
197         if (options->return_prog_exit_status)
198                 extralen += NUM_PAM_ERR_ENV;
199         tmp = reallocarray(envlist, envlen + extralen + 1, sizeof(*envlist));
200         openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d tmp = %p",
201             envlen, extralen, tmp);
202         if (tmp == NULL)
203                 OUT(PAM_BUF_ERR);
204         envlist = tmp;
205         extralen += envlen;
206
207         /* copy selected PAM items to the environment */
208         for (i = 0; i < NUM_PAM_ITEM_ENV; ++i) {
209                 pam_err = pam_get_item(pamh, pam_item_env[i].item, &item);
210                 if (pam_err != PAM_SUCCESS || item == NULL)
211                         continue;
212                 if (asprintf(&envstr, "%s=%s", pam_item_env[i].name, item) < 0)
213                         OUT(PAM_BUF_ERR);
214                 envlist[envlen++] = envstr;
215                 envlist[envlen] = NULL;
216                 openpam_log(PAM_LOG_DEBUG, "setenv %s", envstr);
217         }
218
219         /* add the name of the service function to the environment */
220         if (asprintf(&envstr, "PAM_SM_FUNC=%s", func) < 0)
221                 OUT(PAM_BUF_ERR);
222         envlist[envlen++] = envstr;
223         envlist[envlen] = NULL;
224
225         /* add the PAM error codes to the environment. */
226         if (options->return_prog_exit_status) {
227                 for (i = 0; i < (int)NUM_PAM_ERR_ENV; ++i) {
228                         if ((envstr = strdup(pam_err_env[i])) == NULL)
229                                 OUT(PAM_BUF_ERR);
230                         envlist[envlen++] = envstr;
231                         envlist[envlen] = NULL;
232                 }
233         }
234
235         openpam_log(PAM_LOG_DEBUG, "envlen = %d extralen = %d envlist = %p",
236             envlen, extralen, envlist);
237
238         /* set up pipes if capture was requested */
239         if (options->capture_stdout) {
240                 if (pipe(chout) != 0) {
241                         openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
242                         OUT(PAM_SYSTEM_ERR);
243                 }
244                 if (fcntl(chout[0], F_SETFL, O_NONBLOCK) != 0) {
245                         openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
246                         OUT(PAM_SYSTEM_ERR);
247                 }
248         } else {
249                 if ((chout[1] = open("/dev/null", O_RDWR)) < 0) {
250                         openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
251                         OUT(PAM_SYSTEM_ERR);
252                 }
253         }
254         if (options->capture_stderr) {
255                 if (pipe(cherr) != 0) {
256                         openpam_log(PAM_LOG_ERROR, "%s: pipe(): %m", func);
257                         OUT(PAM_SYSTEM_ERR);
258                 }
259                 if (fcntl(cherr[0], F_SETFL, O_NONBLOCK) != 0) {
260                         openpam_log(PAM_LOG_ERROR, "%s: fcntl(): %m", func);
261                         OUT(PAM_SYSTEM_ERR);
262                 }
263         } else {
264                 if ((cherr[1] = open("/dev/null", O_RDWR)) < 0) {
265                         openpam_log(PAM_LOG_ERROR, "%s: /dev/null: %m", func);
266                         OUT(PAM_SYSTEM_ERR);
267                 }
268         }
269
270         if ((pid = pdfork(&pd, 0)) == 0) {
271                 /* child */
272                 if ((chout[0] >= 0 && close(chout[0]) != 0) ||
273                     (cherr[0] >= 0 && close(cherr[0]) != 0)) {
274                         openpam_log(PAM_LOG_ERROR, "%s: close(): %m", func);
275                 } else if (dup2(chout[1], STDOUT_FILENO) != STDOUT_FILENO ||
276                     dup2(cherr[1], STDERR_FILENO) != STDERR_FILENO) {
277                         openpam_log(PAM_LOG_ERROR, "%s: dup2(): %m", func);
278                 } else {
279                         execve(argv[0], (char * const *)argv,
280                             (char * const *)envlist);
281                         openpam_log(PAM_LOG_ERROR, "%s: execve(%s): %m",
282                             func, argv[0]);
283                 }
284                 _exit(1);
285         }
286         /* parent */
287         if (pid == -1) {
288                 openpam_log(PAM_LOG_ERROR, "%s: pdfork(): %m", func);
289                 OUT(PAM_SYSTEM_ERR);
290         }
291         /* use poll() to watch the process and stdout / stderr */
292         if (chout[1] >= 0)
293                 close(chout[1]);
294         if (cherr[1] >= 0)
295                 close(cherr[1]);
296         memset(pfd, 0, sizeof pfd);
297         pfd[0].fd = pd;
298         pfd[0].events = POLLHUP;
299         nfds = 1;
300         if (options->capture_stdout) {
301                 pfd[nfds].fd = chout[0];
302                 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
303                 nfds++;
304         }
305         if (options->capture_stderr) {
306                 pfd[nfds].fd = cherr[0];
307                 pfd[nfds].events = POLLIN|POLLERR|POLLHUP;
308                 nfds++;
309         }
310
311         /* loop until the process exits */
312         do {
313                 if (poll(pfd, nfds, INFTIM) < 0) {
314                         openpam_log(PAM_LOG_ERROR, "%s: poll(): %m", func);
315                         OUT(PAM_SYSTEM_ERR);
316                 }
317                 for (i = 1; i < nfds; ++i) {
318                         if ((pfd[i].revents & POLLIN) == 0)
319                                 continue;
320                         if ((rlen = read(pfd[i].fd, buf, sizeof(buf) - 1)) < 0) {
321                                 openpam_log(PAM_LOG_ERROR, "%s: read(): %m",
322                                     func);
323                                 OUT(PAM_SYSTEM_ERR);
324                         } else if (rlen == 0) {
325                                 continue;
326                         }
327                         buf[rlen] = '\0';
328                         (void)pam_prompt(pamh, pfd[i].fd == chout[0] ?
329                             PAM_TEXT_INFO : PAM_ERROR_MSG, &resp, "%s", buf);
330                 }
331         } while (pfd[0].revents == 0);
332
333         /* the child process has exited */
334         while (waitpid(pid, &status, 0) == -1) {
335                 if (errno == EINTR)
336                         continue;
337                 openpam_log(PAM_LOG_ERROR, "%s: waitpid(): %m", func);
338                 OUT(PAM_SYSTEM_ERR);
339         }
340
341         /* check exit code */
342         if (WIFSIGNALED(status)) {
343                 openpam_log(PAM_LOG_ERROR, "%s: %s caught signal %d%s",
344                     func, argv[0], WTERMSIG(status),
345                     WCOREDUMP(status) ? " (core dumped)" : "");
346                 OUT(PAM_SERVICE_ERR);
347         }
348         if (!WIFEXITED(status)) {
349                 openpam_log(PAM_LOG_ERROR, "%s: unknown status 0x%x",
350                     func, status);
351                 OUT(PAM_SERVICE_ERR);
352         }
353
354         if (options->return_prog_exit_status) {
355                 openpam_log(PAM_LOG_DEBUG,
356                     "%s: Use program exit status as return value: %d",
357                     func, WEXITSTATUS(status));
358                 OUT(WEXITSTATUS(status));
359         } else {
360                 OUT(WEXITSTATUS(status) == 0 ? PAM_SUCCESS : PAM_PERM_DENIED);
361         }
362         /* unreachable */
363 out:
364         serrno = errno;
365         if (pd >= 0)
366                 close(pd);
367         if (chout[0] >= 0)
368                 close(chout[0]);
369         if (chout[1] >= 0)
370                 close(chout[1]);
371         if (cherr[0] >= 0)
372                 close(cherr[0]);
373         if (cherr[0] >= 0)
374                 close(cherr[1]);
375         if (envlist != NULL)
376                 openpam_free_envlist(envlist);
377         errno = serrno;
378         return (pam_err);       
379 }
380
381 PAM_EXTERN int
382 pam_sm_authenticate(pam_handle_t *pamh, int flags,
383     int argc, const char *argv[])
384 {
385         int ret;
386         struct pe_opts options;
387
388         ret = parse_options(__func__, &argc, &argv, &options);
389         if (ret != 0)
390                 return (PAM_SERVICE_ERR);
391
392         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
393
394         /*
395          * We must check that the program returned a valid code for this
396          * function.
397          */
398         switch (ret) {
399         case PAM_SUCCESS:
400         case PAM_ABORT:
401         case PAM_AUTHINFO_UNAVAIL:
402         case PAM_AUTH_ERR:
403         case PAM_BUF_ERR:
404         case PAM_CONV_ERR:
405         case PAM_CRED_INSUFFICIENT:
406         case PAM_IGNORE:
407         case PAM_MAXTRIES:
408         case PAM_PERM_DENIED:
409         case PAM_SERVICE_ERR:
410         case PAM_SYSTEM_ERR:
411         case PAM_USER_UNKNOWN:
412                 break;
413         default:
414                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
415                     argv[0], ret);
416                 ret = PAM_SERVICE_ERR;
417         }
418
419         return (ret);
420 }
421
422 PAM_EXTERN int
423 pam_sm_setcred(pam_handle_t *pamh, int flags,
424     int argc, const char *argv[])
425 {
426         int ret;
427         struct pe_opts options;
428
429         ret = parse_options(__func__, &argc, &argv, &options);
430         if (ret != 0)
431                 return (PAM_SERVICE_ERR);
432
433         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
434
435         /*
436          * We must check that the program returned a valid code for this
437          * function.
438          */
439         switch (ret) {
440         case PAM_SUCCESS:
441         case PAM_ABORT:
442         case PAM_BUF_ERR:
443         case PAM_CONV_ERR:
444         case PAM_CRED_ERR:
445         case PAM_CRED_EXPIRED:
446         case PAM_CRED_UNAVAIL:
447         case PAM_IGNORE:
448         case PAM_PERM_DENIED:
449         case PAM_SERVICE_ERR:
450         case PAM_SYSTEM_ERR:
451         case PAM_USER_UNKNOWN:
452                 break;
453         default:
454                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
455                     argv[0], ret);
456                 ret = PAM_SERVICE_ERR;
457         }
458
459         return (ret);
460 }
461
462 PAM_EXTERN int
463 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
464     int argc, const char *argv[])
465 {
466         int ret;
467         struct pe_opts options;
468
469         ret = parse_options(__func__, &argc, &argv, &options);
470         if (ret != 0)
471                 return (PAM_SERVICE_ERR);
472
473         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
474
475         /*
476          * We must check that the program returned a valid code for this
477          * function.
478          */
479         switch (ret) {
480         case PAM_SUCCESS:
481         case PAM_ABORT:
482         case PAM_ACCT_EXPIRED:
483         case PAM_AUTH_ERR:
484         case PAM_BUF_ERR:
485         case PAM_CONV_ERR:
486         case PAM_IGNORE:
487         case PAM_NEW_AUTHTOK_REQD:
488         case PAM_PERM_DENIED:
489         case PAM_SERVICE_ERR:
490         case PAM_SYSTEM_ERR:
491         case PAM_USER_UNKNOWN:
492                 break;
493         default:
494                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
495                     argv[0], ret);
496                 ret = PAM_SERVICE_ERR;
497         }
498
499         return (ret);
500 }
501
502 PAM_EXTERN int
503 pam_sm_open_session(pam_handle_t *pamh, int flags,
504     int argc, const char *argv[])
505 {
506         int ret;
507         struct pe_opts options;
508
509         ret = parse_options(__func__, &argc, &argv, &options);
510         if (ret != 0)
511                 return (PAM_SERVICE_ERR);
512
513         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
514
515         /*
516          * We must check that the program returned a valid code for this
517          * function.
518          */
519         switch (ret) {
520         case PAM_SUCCESS:
521         case PAM_ABORT:
522         case PAM_BUF_ERR:
523         case PAM_CONV_ERR:
524         case PAM_IGNORE:
525         case PAM_PERM_DENIED:
526         case PAM_SERVICE_ERR:
527         case PAM_SESSION_ERR:
528         case PAM_SYSTEM_ERR:
529                 break;
530         default:
531                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
532                     argv[0], ret);
533                 ret = PAM_SERVICE_ERR;
534         }
535
536         return (ret);
537 }
538
539 PAM_EXTERN int
540 pam_sm_close_session(pam_handle_t *pamh, int flags,
541     int argc, const char *argv[])
542 {
543         int ret;
544         struct pe_opts options;
545
546         ret = parse_options(__func__, &argc, &argv, &options);
547         if (ret != 0)
548                 return (PAM_SERVICE_ERR);
549
550         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
551
552         /*
553          * We must check that the program returned a valid code for this
554          * function.
555          */
556         switch (ret) {
557         case PAM_SUCCESS:
558         case PAM_ABORT:
559         case PAM_BUF_ERR:
560         case PAM_CONV_ERR:
561         case PAM_IGNORE:
562         case PAM_PERM_DENIED:
563         case PAM_SERVICE_ERR:
564         case PAM_SESSION_ERR:
565         case PAM_SYSTEM_ERR:
566                 break;
567         default:
568                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
569                     argv[0], ret);
570                 ret = PAM_SERVICE_ERR;
571         }
572
573         return (ret);
574 }
575
576 PAM_EXTERN int
577 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
578     int argc, const char *argv[])
579 {
580         int ret;
581         struct pe_opts options;
582
583         ret = parse_options(__func__, &argc, &argv, &options);
584         if (ret != 0)
585                 return (PAM_SERVICE_ERR);
586
587         ret = _pam_exec(pamh, __func__, flags, argc, argv, &options);
588
589         /*
590          * We must check that the program returned a valid code for this
591          * function.
592          */
593         switch (ret) {
594         case PAM_SUCCESS:
595         case PAM_ABORT:
596         case PAM_AUTHTOK_DISABLE_AGING:
597         case PAM_AUTHTOK_ERR:
598         case PAM_AUTHTOK_LOCK_BUSY:
599         case PAM_AUTHTOK_RECOVERY_ERR:
600         case PAM_BUF_ERR:
601         case PAM_CONV_ERR:
602         case PAM_IGNORE:
603         case PAM_PERM_DENIED:
604         case PAM_SERVICE_ERR:
605         case PAM_SYSTEM_ERR:
606         case PAM_TRY_AGAIN:
607                 break;
608         default:
609                 openpam_log(PAM_LOG_ERROR, "%s returned invalid code %d",
610                     argv[0], ret);
611                 ret = PAM_SERVICE_ERR;
612         }
613
614         return (ret);
615 }
616
617 PAM_MODULE_ENTRY("pam_exec");