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 #include <signal.h>
52
53 #include <security/pam_modules.h>
54 #include <security/pam_modutil.h>
55 #include <security/pam_ext.h>
56 #include <security/_pam_macros.h>
57 #include "pam_inline.h"
58
59 #define ENV_ITEM(n) { (n), #n }
60 static struct {
61 int item;
62 const char *name;
63 } env_items[] = {
64 ENV_ITEM(PAM_SERVICE),
65 ENV_ITEM(PAM_USER),
66 ENV_ITEM(PAM_TTY),
67 ENV_ITEM(PAM_RHOST),
68 ENV_ITEM(PAM_RUSER),
69 };
70
71 /* move_fd_to_non_stdio copies the given file descriptor to something other
72 * than stdin, stdout, or stderr. Assumes that the caller will close all
73 * unwanted fds after calling. */
74 static int
75 move_fd_to_non_stdio (pam_handle_t *pamh, int fd)
76 {
77 while (fd < 3)
78 {
79 fd = dup(fd);
80 if (fd == -1)
81 {
82 int err = errno;
83 pam_syslog (pamh, LOG_ERR, "dup failed: %m");
84 _exit (err);
85 }
86 }
87 return fd;
88 }
89
90 static int
91 call_exec (const char *pam_type, pam_handle_t *pamh,
92 int argc, const char **argv)
93 {
94 int debug = 0;
95 int call_setuid = 0;
96 int quiet = 0;
97 int quiet_log = 0;
98 int expose_authtok = 0;
99 int use_stdout = 0;
100 int optargc;
101 const char *logfile = NULL;
102 char authtok[PAM_MAX_RESP_SIZE] = {};
103 pid_t pid;
104 int fds[2];
105 int stdout_fds[2];
106 FILE *stdout_file = NULL;
107 int retval;
108 const char *name;
109 struct sigaction newsa, oldsa;
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 const char *str;
120
121 if (argv[optargc][0] == '/') /* paths starts with / */
122 break;
123
124 if (strcasecmp (argv[optargc], "debug") == 0)
125 debug = 1;
126 else if (strcasecmp (argv[optargc], "stdout") == 0)
127 use_stdout = 1;
128 else if ((str = pam_str_skip_icase_prefix (argv[optargc], "log=")) != NULL)
129 logfile = str;
130 else if ((str = pam_str_skip_icase_prefix (argv[optargc], "type=")) != NULL)
131 {
132 if (strcmp (pam_type, str) != 0)
133 return PAM_IGNORE;
134 }
135 else if (strcasecmp (argv[optargc], "seteuid") == 0)
136 call_setuid = 1;
137 else if (strcasecmp (argv[optargc], "quiet") == 0)
138 quiet = 1;
139 else if (strcasecmp (argv[optargc], "quiet_log") == 0)
140 quiet_log = 1;
141 else if (strcasecmp (argv[optargc], "expose_authtok") == 0)
142 expose_authtok = 1;
143 else
144 break; /* Unknown option, assume program to execute. */
145 }
146
147 /* Request user name to be available. */
148
149 retval = pam_get_user(pamh, &name, NULL);
150 if (retval != PAM_SUCCESS)
151 {
152 if (retval == PAM_CONV_AGAIN)
153 retval = PAM_INCOMPLETE;
154 return retval;
155 }
156
157 if (expose_authtok == 1)
158 {
159 if (strcmp (pam_type, "auth") != 0)
160 {
161 pam_syslog (pamh, LOG_ERR,
162 "expose_authtok not supported for type %s", pam_type);
163 expose_authtok = 0;
164 }
165 else
166 {
167 const void *void_pass;
168
169 retval = pam_get_item (pamh, PAM_AUTHTOK, &void_pass);
170 if (retval != PAM_SUCCESS)
171 {
172 if (debug)
173 pam_syslog (pamh, LOG_DEBUG,
174 "pam_get_item (PAM_AUTHTOK) failed, return %d",
175 retval);
176 return retval;
177 }
178 else if (void_pass == NULL)
179 {
180 char *resp = NULL;
181
182 retval = pam_prompt (pamh, PAM_PROMPT_ECHO_OFF,
183 &resp, _("Password: "));
184
185 if (retval != PAM_SUCCESS)
186 {
187 pam_overwrite_string (resp);
188 _pam_drop (resp);
189 if (retval == PAM_CONV_AGAIN)
190 retval = PAM_INCOMPLETE;
191 return retval;
192 }
193
194 if (resp)
195 {
196 pam_set_item (pamh, PAM_AUTHTOK, resp);
197 strncpy (authtok, resp, sizeof(authtok) - 1);
198 pam_overwrite_string (resp);
199 _pam_drop (resp);
200 }
201 }
202 else
203 strncpy (authtok, void_pass, sizeof(authtok) - 1);
204
205 if (pipe(fds) != 0)
206 {
207 pam_overwrite_array(authtok);
208 pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
209 return PAM_SYSTEM_ERR;
210 }
211 }
212 }
213
214 if (use_stdout)
215 {
216 if (pipe(stdout_fds) != 0)
217 {
218 pam_overwrite_array(authtok);
219 pam_syslog (pamh, LOG_ERR, "Could not create pipe: %m");
220 return PAM_SYSTEM_ERR;
221 }
222 stdout_file = fdopen(stdout_fds[0], "r");
223 if (!stdout_file)
224 {
225 pam_overwrite_array(authtok);
226 pam_syslog (pamh, LOG_ERR, "Could not fdopen pipe: %m");
227 return PAM_SYSTEM_ERR;
228 }
229 }
230
231 if (optargc >= argc) {
232 pam_overwrite_array(authtok);
233 pam_syslog (pamh, LOG_ERR, "No path given as argument");
234 return PAM_SERVICE_ERR;
235 }
236
237 memset(&newsa, '\0', sizeof(newsa));
238 newsa.sa_handler = SIG_DFL;
239 if (sigaction(SIGCHLD, &newsa, &oldsa) == -1) {
240 pam_overwrite_array(authtok);
241 pam_syslog(pamh, LOG_ERR, "failed to reset SIGCHLD handler: %m");
242 return PAM_SYSTEM_ERR;
243 }
244
245 pid = fork();
246 if (pid == -1) {
247 pam_overwrite_array(authtok);
248 return PAM_SYSTEM_ERR;
249 }
250 if (pid > 0) /* parent */
251 {
252 int status = 0;
253 pid_t rc;
254
255 if (expose_authtok) /* send the password to the child */
256 {
257 if (debug)
258 pam_syslog (pamh, LOG_DEBUG, "send password to child");
259 if (write(fds[1], authtok, strlen(authtok)) == -1)
260 pam_syslog (pamh, LOG_ERR,
261 "sending password to child failed: %m");
262
263 close(fds[0]); /* close here to avoid possible SIGPIPE above */
264 close(fds[1]);
265 }
266
267 pam_overwrite_array(authtok);
268
269 if (use_stdout)
270 {
271 char buf[4096];
272 close(stdout_fds[1]);
273 while (fgets(buf, sizeof(buf), stdout_file) != NULL)
274 {
275 size_t len;
276 len = strlen(buf);
277 if (buf[len-1] == '\n')
278 buf[len-1] = '\0';
279 pam_info(pamh, "%s", buf);
280 }
281 fclose(stdout_file);
282 }
283
284 while ((rc = waitpid (pid, &status, 0)) == -1 &&
285 errno == EINTR);
286 sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
287 if (rc == (pid_t)-1)
288 {
289 pam_syslog (pamh, LOG_ERR, "waitpid returns with -1: %m");
290 return PAM_SYSTEM_ERR;
291 }
292 else if (status != 0)
293 {
294 if (WIFEXITED(status))
295 {
296 if (!quiet_log)
297 pam_syslog (pamh, LOG_ERR, "%s failed: exit code %d",
298 argv[optargc], WEXITSTATUS(status));
299 if (!quiet)
300 pam_error (pamh, _("%s failed: exit code %d"),
301 argv[optargc], WEXITSTATUS(status));
302 }
303 else if (WIFSIGNALED(status))
304 {
305 if (!quiet_log)
306 pam_syslog (pamh, LOG_ERR, "%s failed: caught signal %d%s",
307 argv[optargc], WTERMSIG(status),
308 WCOREDUMP(status) ? " (core dumped)" : "");
309 if (!quiet)
310 pam_error (pamh, _("%s failed: caught signal %d%s"),
311 argv[optargc], WTERMSIG(status),
312 WCOREDUMP(status) ? " (core dumped)" : "");
313 }
314 else
315 {
316 if (!quiet_log)
317 pam_syslog (pamh, LOG_ERR, "%s failed: unknown status 0x%x",
318 argv[optargc], status);
319 if (!quiet)
320 pam_error (pamh, _("%s failed: unknown status 0x%x"),
321 argv[optargc], status);
322 }
323 return PAM_SYSTEM_ERR;
324 }
325 return PAM_SUCCESS;
326 }
327 else /* child */
328 {
329 const char **arggv;
330 int i;
331 char **envlist;
332 int envlen, nitems;
333 char *envstr;
334 enum pam_modutil_redirect_fd redirect_stdin =
335 expose_authtok ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_PIPE_FD;
336 enum pam_modutil_redirect_fd redirect_stdout =
337 (use_stdout || logfile) ? PAM_MODUTIL_IGNORE_FD : PAM_MODUTIL_NULL_FD;
338
339 pam_overwrite_array(authtok);
340
341 /* First, move all the pipes off of stdin, stdout, and stderr, to ensure
342 * that calls to dup2 won't close them. */
343
344 if (expose_authtok)
345 {
346 fds[0] = move_fd_to_non_stdio(pamh, fds[0]);
347 close(fds[1]);
348 }
349
350 if (use_stdout)
351 {
352 stdout_fds[1] = move_fd_to_non_stdio(pamh, stdout_fds[1]);
353 close(stdout_fds[0]);
354 }
355
356 /* Set up stdin. */
357
358 if (expose_authtok)
359 {
360 /* reopen stdin as pipe */
361 if (dup2(fds[0], STDIN_FILENO) == -1)
362 {
363 int err = errno;
364 pam_syslog (pamh, LOG_ERR, "dup2 of STDIN failed: %m");
365 _exit (err);
366 }
367 }
368
369 /* Set up stdout. */
370
371 if (use_stdout)
372 {
373 if (dup2(stdout_fds[1], STDOUT_FILENO) == -1)
374 {
375 int err = errno;
376 pam_syslog (pamh, LOG_ERR, "dup2 to stdout failed: %m");
377 _exit (err);
378 }
379 }
380 else if (logfile)
381 {
382 time_t tm = time (NULL);
383 char *buffer = NULL;
384
385 close (STDOUT_FILENO);
386 if ((i = open (logfile, O_CREAT|O_APPEND|O_WRONLY,
387 S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1)
388 {
389 int err = errno;
390 pam_syslog (pamh, LOG_ERR, "open of %s failed: %m",
391 logfile);
392 _exit (err);
393 }
394 if (i != STDOUT_FILENO)
395 {
396 if (dup2 (i, STDOUT_FILENO) == -1)
397 {
398 int err = errno;
399 pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
400 _exit (err);
401 }
402 close (i);
403 }
404 if (asprintf (&buffer, "*** %s", ctime (&tm)) > 0)
405 {
406 pam_modutil_write (STDOUT_FILENO, buffer, strlen (buffer));
407 free (buffer);
408 }
409 }
410
411 if ((use_stdout || logfile) &&
412 dup2 (STDOUT_FILENO, STDERR_FILENO) == -1)
413 {
414 int err = errno;
415 pam_syslog (pamh, LOG_ERR, "dup2 failed: %m");
416 _exit (err);
417 }
418
419 if (pam_modutil_sanitize_helper_fds(pamh, redirect_stdin,
420 redirect_stdout, redirect_stdout) < 0)
421 _exit(1);
422
423 if (call_setuid)
424 if (setuid (geteuid ()) == -1)
425 {
426 int err = errno;
427 pam_syslog (pamh, LOG_ERR, "setuid(%lu) failed: %m",
428 (unsigned long) geteuid ());
429 _exit (err);
430 }
431
432 if (setsid () == -1)
433 {
434 int err = errno;
435 pam_syslog (pamh, LOG_ERR, "setsid failed: %m");
436 _exit (err);
437 }
438
439 arggv = calloc (argc + 4, sizeof (char *));
440 if (arggv == NULL)
441 _exit (ENOMEM);
442
443 for (i = 0; i < (argc - optargc); i++)
444 arggv[i] = argv[i+optargc];
445 arggv[i] = NULL;
446
447 /*
448 * Set up the child's environment list. It consists of the PAM
449 * environment, plus a few hand-picked PAM items.
450 */
451 envlist = pam_getenvlist(pamh);
452 for (envlen = 0; envlist[envlen] != NULL; ++envlen)
453 /* nothing */ ;
454 nitems = PAM_ARRAY_SIZE(env_items);
455 /* + 2 because of PAM_TYPE and NULL entry */
456 envlist = realloc(envlist, (envlen + nitems + 2) * sizeof(*envlist));
457 if (envlist == NULL)
458 {
459 pam_syslog (pamh, LOG_CRIT, "realloc environment failed: %m");
460 _exit (ENOMEM);
461 }
462 for (i = 0; i < nitems; ++i)
463 {
464 const void *item;
465
466 if (pam_get_item(pamh, env_items[i].item, &item) != PAM_SUCCESS || item == NULL)
467 continue;
468 if (asprintf(&envstr, "%s=%s", env_items[i].name, (const char *)item) < 0)
469 {
470 pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
471 _exit (ENOMEM);
472 }
473 envlist[envlen++] = envstr;
474 envlist[envlen] = NULL;
475 }
476
477 if (asprintf(&envstr, "PAM_TYPE=%s", pam_type) < 0)
478 {
479 pam_syslog (pamh, LOG_CRIT, "prepare environment failed: %m");
480 _exit (ENOMEM);
481 }
482 envlist[envlen++] = envstr;
483 envlist[envlen] = NULL;
484
485 if (debug)
486 pam_syslog (pamh, LOG_DEBUG, "Calling %s ...", arggv[0]);
487
488 DIAG_PUSH_IGNORE_CAST_QUAL;
489 execve (arggv[0], (char **) arggv, envlist);
490 DIAG_POP_IGNORE_CAST_QUAL;
491 i = errno;
492 pam_syslog (pamh, LOG_ERR, "execve(%s,...) failed: %m", arggv[0]);
493 _exit (i);
494 }
495 return PAM_SYSTEM_ERR; /* will never be reached. */
496 }
497
498 int
499 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
500 int argc, const char **argv)
501 {
502 return call_exec ("auth", pamh, argc, argv);
503 }
504
505 int
506 pam_sm_setcred (pam_handle_t *pamh UNUSED, int flags UNUSED,
507 int argc UNUSED, const char **argv UNUSED)
508 {
509 return PAM_IGNORE;
510 }
511
512 /* password updating functions */
513
514 int
515 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
516 int argc, const char **argv)
517 {
518 if (flags & PAM_PRELIM_CHECK)
519 return PAM_SUCCESS;
520 return call_exec ("password", pamh, argc, argv);
521 }
522
523 int
524 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
525 int argc, const char **argv)
526 {
527 return call_exec ("account", pamh, argc, argv);
528 }
529
530 int
531 pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
532 int argc, const char **argv)
533 {
534 return call_exec ("open_session", pamh, argc, argv);
535 }
536
537 int
538 pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
539 int argc, const char **argv)
540 {
541 return call_exec ("close_session", pamh, argc, argv);
542 }