]> granicus.if.org Git - linux-pam/blob - modules/pam_exec/pam_exec.c
Introduce pam_modutil_sanitize_helper_fds
[linux-pam] / modules / pam_exec / pam_exec.c
1 /*
2  * Copyright (c) 2006, 2008 Thorsten Kukuk <kukuk@thkukuk.de>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, and the entire permission notice in its entirety,
9  *    including the disclaimer of warranties.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. The name of the author may not be used to endorse or promote
14  *    products derived from this software without specific prior
15  *    written permission.
16  *
17  * ALTERNATIVELY, this product may be distributed under the terms of
18  * the GNU Public License, in which case the provisions of the GPL are
19  * required INSTEAD OF the above restrictions.  (This clause is
20  * necessary due to a potential bad interaction between the GPL and
21  * the restrictions contained in a BSD-style copyright.)
22  *
23  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
24  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33  * OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35
36 #if defined(HAVE_CONFIG_H)
37 #include "config.h"
38 #endif
39
40 #include <time.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <syslog.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <sys/wait.h>
49 #include <sys/stat.h>
50 #include <sys/types.h>
51
52
53 #define PAM_SM_AUTH
54 #define PAM_SM_ACCOUNT
55 #define PAM_SM_SESSION
56 #define PAM_SM_PASSWORD
57
58 #include <security/pam_modules.h>
59 #include <security/pam_modutil.h>
60 #include <security/pam_ext.h>
61 #include <security/_pam_macros.h>
62
63 #define ENV_ITEM(n) { (n), #n }
64 static struct {
65   int item;
66   const char *name;
67 } env_items[] = {
68   ENV_ITEM(PAM_SERVICE),
69   ENV_ITEM(PAM_USER),
70   ENV_ITEM(PAM_TTY),
71   ENV_ITEM(PAM_RHOST),
72   ENV_ITEM(PAM_RUSER),
73 };
74
75 /* move_fd_to_non_stdio copies the given file descriptor to something other
76  * than stdin, stdout, or stderr.  Assumes that the caller will close all
77  * unwanted fds after calling. */
78 static int
79 move_fd_to_non_stdio (pam_handle_t *pamh, int fd)
80 {
81   while (fd < 3)
82     {
83       fd = dup(fd);
84       if (fd == -1)
85         {
86           int err = errno;
87           pam_syslog (pamh, LOG_ERR, "dup failed: %m");
88           _exit (err);
89         }
90     }
91   return fd;
92 }
93
94 static int
95 call_exec (const char *pam_type, pam_handle_t *pamh,
96            int argc, const char **argv)
97 {
98   int debug = 0;
99   int call_setuid = 0;
100   int quiet = 0;
101   int expose_authtok = 0;
102   int use_stdout = 0;
103   int optargc;
104   const char *logfile = NULL;
105   const char *authtok = NULL;
106   pid_t pid;
107   int fds[2];
108   int stdout_fds[2];
109   FILE *stdout_file = NULL;
110
111   if (argc < 1) {
112     pam_syslog (pamh, LOG_ERR,
113                 "This module needs at least one argument");
114     return PAM_SERVICE_ERR;
115   }
116
117   for (optargc = 0; optargc < argc; optargc++)
118     {
119       if (argv[optargc][0] == '/') /* paths starts with / */
120         break;
121
122       if (strcasecmp (argv[optargc], "debug") == 0)
123         debug = 1;
124       else if (strcasecmp (argv[optargc], "stdout") == 0)
125         use_stdout = 1;
126       else if (strncasecmp (argv[optargc], "log=", 4) == 0)
127         logfile = &argv[optargc][4];
128       else if (strncasecmp (argv[optargc], "type=", 5) == 0)
129         {
130           if (strcmp (pam_type, &argv[optargc][5]) != 0)
131             return PAM_IGNORE;
132         }
133       else if (strcasecmp (argv[optargc], "seteuid") == 0)
134         call_setuid = 1;
135       else if (strcasecmp (argv[optargc], "quiet") == 0)
136         quiet = 1;
137       else if (strcasecmp (argv[optargc], "expose_authtok") == 0)
138         expose_authtok = 1;
139       else
140         break; /* Unknown option, assume program to execute. */
141     }
142
143   if (expose_authtok == 1)
144     {
145       if (strcmp (pam_type, "auth") != 0)
146         {
147           pam_syslog (pamh, LOG_ERR,
148                       "expose_authtok not supported for type %s", pam_type);
149           expose_authtok = 0;
150         }
151       else
152         {
153           const void *void_pass;
154           int retval;
155
156           retval = pam_get_item (pamh, PAM_AUTHTOK, &void_pass);
157           if (retval != PAM_SUCCESS)
158             {
159               if (debug)
160                 pam_syslog (pamh, LOG_DEBUG,
161                             "pam_get_item (PAM_AUTHTOK) failed, return %d",
162                             retval);
163               return retval;
164             }
165           else if (void_pass == NULL)
166             {
167               char *resp = NULL;
168
169               retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF,
170                                    &resp, _("Password: "));
171
172               if (retval != PAM_SUCCESS)
173                 {
174                   _pam_drop (resp);
175                   if (retval == PAM_CONV_AGAIN)
176                     retval = PAM_INCOMPLETE;
177                   return retval;
178                 }
179
180               pam_set_item (pamh, PAM_AUTHTOK, resp);
181               authtok = strdupa (resp);
182               _pam_drop (resp);
183             }
184           else
185             authtok = void_pass;
186
187           if (pipe(fds) != 0)
188             {
189               pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
190               return PAM_SYSTEM_ERR;
191             }
192         }
193     }
194
195   if (use_stdout)
196     {
197       if (pipe(stdout_fds) != 0)
198         {
199           pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
200           return PAM_SYSTEM_ERR;
201         }
202       stdout_file = fdopen(stdout_fds[0], "r");
203       if (!stdout_file)
204         {
205           pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m");
206           return PAM_SYSTEM_ERR;
207         }
208     }
209
210   if (optargc >= argc) {
211     pam_syslog (pamh, LOG_ERR, "No path given as argument");
212     return PAM_SERVICE_ERR;
213   }
214
215   pid = fork();
216   if (pid == -1)
217     return PAM_SYSTEM_ERR;
218   if (pid > 0) /* parent */
219     {
220       int status = 0;
221       pid_t retval;
222
223       if (expose_authtok) /* send the password to the child */
224         {
225           if (authtok != NULL)
226             {            /* send the password to the child */
227               if (debug)
228                 pam_syslog (pamh, LOG_DEBUG, "send password to child");
229               if (write(fds[1], authtok, strlen(authtok)+1) == -1)
230                 pam_syslog (pamh, LOG_ERR,
231                             "sending password to child failed: %m");
232               authtok = NULL;
233             }
234           else
235             {
236               if (write(fds[1], "", 1) == -1)   /* blank password */
237                 pam_syslog (pamh, LOG_ERR,
238                             "sending password to child failed: %m");
239             }
240         close(fds[0]);       /* close here to avoid possible SIGPIPE above */
241         close(fds[1]);
242         }
243
244       if (use_stdout)
245         {
246           char buf[4096];
247           close(stdout_fds[1]);
248           while (fgets(buf, sizeof(buf), stdout_file) != NULL)
249             {
250               size_t len;
251               len = strlen(buf);
252               if (buf[len-1] == '\n')
253                 buf[len-1] = '\0';
254               pam_info(pamh, "%s", buf);
255             }
256           fclose(stdout_file);
257         }
258
259       while ((retval = waitpid (pid, &status, 0)) == -1 &&
260              errno == EINTR);
261       if (retval == (pid_t)-1)
262         {
263           pam_syslog (pamh, LOG_ERR, "waitpid returns with -1: %m");
264           return PAM_SYSTEM_ERR;
265         }
266       else if (status != 0)
267         {
268           if (WIFEXITED(status))
269             {
270               pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d",
271                           argv[optargc], WEXITSTATUS(status));
272                 if (!quiet)
273               pam_error (pamh, _("%s failed: exit code %d"),
274                          argv[optargc], WEXITSTATUS(status));
275             }
276           else if (WIFSIGNALED(status))
277             {
278               pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s",
279                           argv[optargc], WTERMSIG(status),
280                           WCOREDUMP(status) ? " (core dumped)" : "");
281                 if (!quiet)
282               pam_error (pamh, _("%s failed: caught signal %d%s"),
283                          argv[optargc], WTERMSIG(status),
284                          WCOREDUMP(status) ? " (core dumped)" : "");
285             }
286           else
287             {
288               pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x",
289                           argv[optargc], status);
290                 if (!quiet)
291               pam_error (pamh, _("%s failed: unknown status 0x%x"),
292                          argv[optargc], status);
293             }
294           return PAM_SYSTEM_ERR;
295         }
296       return PAM_SUCCESS;
297     }
298   else /* child */
299     {
300       char **arggv;
301       int i;
302       char **envlist, **tmp;
303       int envlen, nitems;
304       char *envstr;
305       enum pam_modutil_redirect_fd redirect_stdin =
306               expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
307       enum pam_modutil_redirect_fd redirect_stdout =
308               (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
309
310       /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
311        * that calls to dup2 won't close them. */
312
313       if (expose_authtok)
314         {
315           fds[0] = move_fd_to_non_stdio(pamh, fds[0]);
316           close(fds[1]);
317         }
318
319       if (use_stdout)
320         {
321           stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]);
322           close(stdout_fds[0]);
323         }
324
325       /* Set up stdin. */
326
327       if (expose_authtok)
328         {
329           /* reopen stdin as pipe */
330           if (dup2(fds[0], STDIN_FILENO) == -1)
331             {
332               int err = errno;
333               pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m");
334               _exit (err);
335             }
336         }
337
338       /* Set up stdout. */
339
340       if (use_stdout)
341         {
342           if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
343             {
344               int err = errno;
345               pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m");
346               _exit (err);
347             }
348         }
349       else if (logfile)
350         {
351           time_t tm = time (NULL);
352           char *buffer = NULL;
353
354           close (STDOUT_FILENO);
355           if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY,
356                          S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
357             {
358               int err = errno;
359               pam_syslog (pamh, LOG_ERR, "open of %s failed: %m",
360                           logfile);
361               _exit (err);
362             }
363           if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0)
364             {
365               pam_modutil_write (i, buffer, strlen (buffer));
366               free (buffer);
367             }
368         }
369
370       if ((use_stdout || logfile) &&
371           dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
372         {
373           int err = errno;
374           pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
375           _exit (err);
376         }
377
378       if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
379                                           redirect_stdout, redirect_stdout) < 0)
380         _exit(1);
381
382       if (call_setuid)
383         if (setuid (geteuid ()) == -1)
384           {
385             int err = errno;
386             pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
387                         (unsigned long) geteuid ());
388             _exit (err);
389           }
390
391       if (setsid () == -1)
392         {
393           int err = errno;
394           pam_syslog (pamh, LOG_ERR, "setsid failed: %m");
395           _exit (err);
396         }
397
398       arggv = calloc (argc + 4, sizeof (char *));
399       if (arggv == NULL)
400         _exit (ENOMEM);
401
402       for (i = 0; i < (argc - optargc); i++)
403         arggv[i] = strdup(argv[i+optargc]);
404       arggv[i] = NULL;
405
406       /*
407        * Set up the child's environment list.  It consists of the PAM
408        * environment, plus a few hand-picked PAM items.
409        */
410       envlist = pam_getenvlist(pamh);
411       for (envlen = 0; envlist[envlen] != NULL; ++envlen)
412         /* nothing */ ;
413       nitems = sizeof(env_items) / sizeof(*env_items);
414       /* + 2 because of PAM_TYPE and NULL entry */
415       tmp = realloc(envlist, (envlen + nitems + 2) * sizeof(*envlist));
416       if (tmp == NULL)
417       {
418         free(envlist);
419         pam_syslog (pamh, LOG_ERR, "realloc environment failed: %m");
420         _exit (ENOMEM);
421       }
422       envlist = tmp;
423       for (i = 0; i < nitems; ++i)
424       {
425         const void *item;
426
427         if (pam_get_item(pamh, env_items[i].item, &item) != PAM_SUCCESS || item == NULL)
428           continue;
429         if (asprintf(&envstr, "%s=%s", env_items[i].name, (const char *)item) < 0)
430         {
431           free(envlist);
432           pam_syslog (pamh, LOG_ERR, "prepare environment failed: %m");
433           _exit (ENOMEM);
434         }
435         envlist[envlen++] = envstr;
436         envlist[envlen] = NULL;
437       }
438
439       if (asprintf(&envstr, "PAM_TYPE=%s", pam_type) < 0)
440         {
441           free(envlist);
442           pam_syslog (pamh, LOG_ERR, "prepare environment failed: %m");
443           _exit (ENOMEM);
444         }
445       envlist[envlen++] = envstr;
446       envlist[envlen] = NULL;
447
448       if (debug)
449         pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]);
450
451       execve (arggv[0], arggv, envlist);
452       i = errno;
453       pam_syslog (pamh, LOG_ERR, "execve(%s,...) failed: %m", arggv[0]);
454       free(envlist);
455       _exit (i);
456     }
457   return PAM_SYSTEM_ERR; /* will never be reached. */
458 }
459
460 PAM_EXTERN int
461 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
462                      int argc, const char **argv)
463 {
464   return call_exec ("auth", pamh, argc, argv);
465 }
466
467 PAM_EXTERN int
468 pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
469                 int argc UNUSED, const char **argv UNUSED)
470 {
471   return PAM_IGNORE;
472 }
473
474 /* password updating functions */
475
476 PAM_EXTERN int
477 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
478                  int argc, const char **argv)
479 {
480   if (flags & PAM_PRELIM_CHECK)
481     return PAM_SUCCESS;
482   return call_exec ("password", pamh, argc, argv);
483 }
484
485 PAM_EXTERN int
486 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
487                  int argc, const char **argv)
488 {
489   return call_exec ("account", pamh, argc, argv);
490 }
491
492 PAM_EXTERN int
493 pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
494                     int argc, const char **argv)
495 {
496   return call_exec ("open_session", pamh, argc, argv);
497 }
498
499 PAM_EXTERN int
500 pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
501                      int argc, const char **argv)
502 {
503   return call_exec ("close_session", pamh, argc, argv);
504 }
505
506 #ifdef PAM_STATIC
507 struct pam_module _pam_exec_modstruct = {
508   "pam_exec",
509   pam_sm_authenticate,
510   pam_sm_setcred,
511   pam_sm_acct_mgmt,
512   pam_sm_open_session,
513   pam_sm_close_session,
514   pam_sm_chauthtok,
515 };
516 #endif