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