1 /*
2 * Copyright 2001,2002,2003,2012 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the Free
16 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
17 * MA 02111-1307, USA
18 *
19 */
20
21 #ifndef HAVE_CONFIG_H
22 #include "../../config.h"
23 #endif
24
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <sys/wait.h>
28 #include <dlfcn.h>
29 #include <errno.h>
30 #include <grp.h>
31 #include <limits.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <security/pam_appl.h>
38 #include <security/pam_modules.h>
39
40 /* Copy the PAM environment into the main environment. */
41 static void
pull_pam_environment(pam_handle_t * pamh)42 pull_pam_environment(pam_handle_t *pamh)
43 {
44 char **env = pam_getenvlist(pamh);
45 int i;
46 for (i = 0; env && env[i]; i++) {
47 putenv(env[i]);
48 }
49 }
50
51 /* A conversation function which uses static strings to supply modules with
52 * responses. */
53 static int
converse(int num_msgs,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)54 converse(int num_msgs,
55 const struct pam_message **msg,
56 struct pam_response **resp,
57 void *appdata_ptr)
58 {
59 char **argv = appdata_ptr;
60 static int used = 0;
61 int i;
62 if (appdata_ptr == NULL) {
63 return PAM_CONV_ERR;
64 }
65 *resp = malloc(sizeof(struct pam_response) * num_msgs);
66 for (i = 0; i < num_msgs; i++) {
67 memset(&((*resp)[i]), 0, sizeof(struct pam_response));
68 switch (msg[i]->msg_style) {
69 case PAM_PROMPT_ECHO_ON:
70 case PAM_PROMPT_ECHO_OFF:
71 if ((argv != NULL) && (argv[used] != NULL)) {
72 (*resp)[i].resp = strdup(argv[used++]);
73 } else {
74 #ifdef BROKENAPP
75 (*resp)[i].resp = NULL;
76 #else
77 (*resp)[i].resp = strdup("");
78 #endif
79 }
80 (*resp)[i].resp_retcode = PAM_SUCCESS;
81 printf("`%s' -> `%s'\n", msg[i]->msg,
82 (*resp)[i].resp ?: "");
83 fflush(NULL);
84 break;
85 case PAM_ERROR_MSG:
86 case PAM_TEXT_INFO:
87 (*resp)[i].resp_retcode = PAM_SUCCESS;
88 printf("`%s'\n", msg[i]->msg);
89 fflush(NULL);
90 break;
91 default:
92 fprintf(stderr, "Unknown message type "
93 "(shouldn't happen)!\n");
94 fflush(NULL);
95 exit(255);
96 }
97 }
98 return PAM_SUCCESS;
99 }
100
101 #define call_fn(name,tag,flags) do { \
102 fn = dlsym(dlhandle, name); \
103 if (fn == NULL) { \
104 printf("Error locating symbol `%s': %s.\n", name, dlerror()); \
105 fflush(NULL); \
106 return 255; \
107 } \
108 ret = fn(pamh, flags, argcount, &argv[args]); \
109 printf(tag"\t%d\t%s\n", ret, pam_strerror(pamh, ret)); \
110 fflush(NULL); } while (0)
111 #define call_stack(fn,tag,flags) do { \
112 ret = fn(pamh, flags); \
113 printf(tag"\t%d\t%s\n", ret, pam_strerror(pamh, ret)); \
114 fflush(NULL); } while (0)
115
116 int
main(int argc,char ** argv)117 main(int argc, char **argv)
118 {
119 void *dlhandle;
120 int doauth, doaccount, dosession, dosetcred, dochauthtok, doprompt;
121 int dofork, dorefresh;
122 int noreentrancy;
123 int i, ret, responses, args, argcount;
124 const char *user, *module, *envvar;
125 pam_handle_t *pamh;
126 struct pam_partial_handle {
127 char *authtok;
128 unsigned caller;
129 } *partial = NULL;
130 struct pam_conv conv;
131 char *tty, *ruser, *rhost, *authtok, *oldauthtok, *run, *prompt;
132
133 struct passwd *pwd;
134 uid_t pw_uid;
135 struct group *grp;
136 gid_t pw_gid, gr_gid;
137
138 if (argc < 4) {
139 printf("Usage: %s\n"
140 " [-auth | -account | -session | -setcred | "
141 "-chauthtok | -refreshcred ]\n"
142 " [-tty tty] [-ruser ruser] [-rhost rhost] "
143 "[-authtok tok] [-oldauthtok tok]\n"
144 " [-setenv VAR=VAL]\n"
145 " [-prompt string] [-showprompt] [-run command] "
146 "[-noreentrancy] [-fork]\n"
147 " user [module [arg ...]| stack] "
148 "[-- response ...]\n",
149 strchr(argv[0], '/') ?
150 strrchr(argv[0], '/') + 1 :
151 argv[0]);
152 return 255;
153 }
154
155 user = module = NULL;
156 doauth = doaccount = dosession = dosetcred = dochauthtok = doprompt = 0;
157 dofork = dorefresh = 0;
158 noreentrancy = 0;
159 args = argcount = responses = 0;
160 tty = ruser = rhost = authtok = oldauthtok = run = prompt = NULL;
161 envvar = NULL;
162 for (i = 1; i < argc; i++) {
163 if (strcmp(argv[i], "-auth") == 0) {
164 doauth++;
165 continue;
166 }
167 if (strcmp(argv[i], "-account") == 0) {
168 doaccount++;
169 continue;
170 }
171 if (strcmp(argv[i], "-session") == 0) {
172 dosession++;
173 continue;
174 }
175 if (strcmp(argv[i], "-setcred") == 0) {
176 dosetcred++;
177 continue;
178 }
179 if (strcmp(argv[i], "-refreshcred") == 0) {
180 dorefresh++;
181 continue;
182 }
183 if (strcmp(argv[i], "-chauthtok") == 0) {
184 dochauthtok++;
185 continue;
186 }
187 if (strcmp(argv[i], "-showprompt") == 0) {
188 doprompt++;
189 continue;
190 }
191 if (strcmp(argv[i], "-noreentrancy") == 0) {
192 noreentrancy++;
193 continue;
194 }
195 if (strcmp(argv[i], "-fork") == 0) {
196 dofork++;
197 continue;
198 }
199 if (strcmp(argv[i], "-tty") == 0) {
200 tty = argv[++i];
201 continue;
202 }
203 if (strcmp(argv[i], "-rhost") == 0) {
204 rhost = argv[++i];
205 continue;
206 }
207 if (strcmp(argv[i], "-authtok") == 0) {
208 authtok = argv[++i];
209 continue;
210 }
211 if (strcmp(argv[i], "-oldauthtok") == 0) {
212 oldauthtok = argv[++i];
213 continue;
214 }
215 if (strcmp(argv[i], "-run") == 0) {
216 run = argv[++i];
217 continue;
218 }
219 if (strcmp(argv[i], "-prompt") == 0) {
220 prompt = argv[++i];
221 continue;
222 }
223 if (strcmp(argv[i], "-ruser") == 0) {
224 ruser = argv[++i];
225 continue;
226 }
227 if (strcmp(argv[i], "-setenv") == 0) {
228 envvar = argv[++i];
229 continue;
230 }
231 if (user == NULL) {
232 user = argv[i];
233 continue;
234 }
235 if (module == NULL) {
236 module = argv[i];
237 continue;
238 }
239 if (strcmp(argv[i], "--") == 0) {
240 if (responses == 0) responses = i + 1;
241 }
242 if (args == 0) {
243 args = i;
244 }
245 }
246 /* Find the beginning of the response list. */
247 if (responses == 0) {
248 responses = argc;
249 }
250 /* Count the number of non-response arguments we have. */
251 if (args != 0) {
252 argcount = 0;
253 for (i = args;
254 (argv[i] != NULL) && (strcmp(argv[i], "--") != 0);
255 i++) {
256 argcount++;
257 }
258 }
259
260 /* Bail on invocation errors. */
261 if ((user == NULL) || (module == NULL)) {
262 printf("Not enough arguments.\n");
263 return 255;
264 }
265 if ((doauth | doaccount | dosession | dosetcred | dorefresh |
266 dochauthtok) == 0) {
267 printf("No action requested.\n");
268 return 255;
269 }
270
271 /* Set up the conversation structure to point to our conversation
272 * function and the list of responses we've gotten. */
273 memset(&conv, 0, sizeof(conv));
274 conv.conv = converse;
275 conv.appdata_ptr = argv + responses;
276
277 /* Check if the module screws with static buffers. The docs say
278 * it's allowed, but I consider it bad practice, because there's
279 * no way we can fix all of the broken apps out there. */
280 for (pw_uid = 1, pwd = NULL; pwd == NULL; pw_uid++) {
281 if (pw_uid == 0) {
282 fprintf(stderr, "No groups.\n");
283 exit(255);
284 }
285 pwd = getpwuid(pw_uid);
286 }
287 for (gr_gid = 1, grp = NULL; grp == NULL; gr_gid++) {
288 if (gr_gid == 0) {
289 fprintf(stderr, "No groups.\n");
290 exit(255);
291 }
292 grp = getgrgid(gr_gid);
293 }
294 pw_uid = pwd->pw_uid;
295 pw_gid = pwd->pw_gid;
296 gr_gid = grp->gr_gid;
297
298 /* Hack: if the name of the "module" argument starts with "pam_",
299 * assume it's a module, and use "login" as the service name. */
300 if ((strchr(module, '/') == NULL) &&
301 (strncmp(module, "pam_", 4) != 0)) {
302 i = pam_start(module, user, &conv, &pamh);
303 if (i != PAM_SUCCESS) {
304 printf("Error initializing PAM for `%s'.\n", module);
305 return 255;
306 }
307 } else {
308 i = pam_start("login", user, &conv, &pamh);
309 if (i != PAM_SUCCESS) {
310 printf("Error initializing PAM for `%s'.\n", "login");
311 return 255;
312 }
313 }
314
315 /* Set ITEMs. */
316 if (envvar) {
317 i = pam_putenv(pamh, envvar);
318 if (i != PAM_SUCCESS) {
319 printf("Error setting PAM environment `%s'.\n", envvar);
320 return 255;
321 }
322 }
323 if (tty) {
324 i = pam_set_item(pamh, PAM_TTY, tty);
325 if (i != PAM_SUCCESS) {
326 printf("Error setting TTY item `%s'.\n", tty);
327 return 255;
328 }
329 }
330 if (rhost) {
331 i = pam_set_item(pamh, PAM_RHOST, rhost);
332 if (i != PAM_SUCCESS) {
333 printf("Error setting RHOST item `%s'.\n", rhost);
334 return 255;
335 }
336 }
337 if (ruser) {
338 i = pam_set_item(pamh, PAM_RUSER, ruser);
339 if (i != PAM_SUCCESS) {
340 printf("Error setting RUSER item `%s'.\n", ruser);
341 return 255;
342 }
343 }
344 if (authtok) {
345 /* Hackeroo. Linux-PAM 0.75 and later don't like it when we
346 * do this sort of thing. */
347 partial = (struct pam_partial_handle*)pamh;
348 partial->caller = 1;
349
350 i = pam_set_item(pamh, PAM_AUTHTOK, authtok);
351 if (i != PAM_SUCCESS) {
352 printf("Error setting AUTHTOK item `%s'.\n", authtok);
353 return 255;
354 }
355
356 partial->caller = 0;
357 }
358 if (oldauthtok) {
359 /* Hackeroo. Linux-PAM 0.75 and later don't like it when we
360 * do this sort of thing. */
361 partial = (struct pam_partial_handle*)pamh;
362 partial->caller = 1;
363
364 i = pam_set_item(pamh, PAM_OLDAUTHTOK, oldauthtok);
365 if (i != PAM_SUCCESS) {
366 printf("Error setting OLDAUTHTOK item `%s'.\n",
367 oldauthtok);
368 return 255;
369 }
370
371 partial->caller = 0;
372 }
373 if (prompt) {
374 i = pam_set_item(pamh, PAM_USER_PROMPT, prompt);
375 if (i != PAM_SUCCESS) {
376 printf("Error setting USER_PROMPT item `%s'.\n",
377 prompt);
378 return 255;
379 }
380 }
381
382 /* Hack: if the name of the "module" argument doesn't start with "pam_",
383 * assume it's the stack name, and run with it. */
384 if ((strchr(module, '/') == NULL) &&
385 (strncmp(module, "pam_", 4) != 0)) {
386 printf("Calling stack `%s'.\n", module);
387 if (dofork) {
388 int fds[2];
389 pid_t pid;
390 if (pipe(fds) == -1) {
391 printf("Error creating pipe: %s\n",
392 strerror(errno));
393 return 255;
394 }
395 if ((pid = fork()) == 0) {
396 char **env;
397 int i, j;
398 if (doauth) {
399 call_stack(pam_authenticate,
400 "AUTH", 0);
401 }
402 if (doprompt) {
403 const void *prmpt;
404 if (pam_get_item(pamh, PAM_USER_PROMPT,
405 &prmpt) ==
406 PAM_SUCCESS) {
407 printf("Prompt = `%s'.\n",
408 (const char*)prmpt);
409 } else {
410 printf("Error reading "
411 "USER_PROMPT item.\n");
412 return 255;
413 }
414 }
415 if (doaccount) {
416 call_stack(pam_acct_mgmt, "ACCT", 0);
417 }
418 close(fds[0]);
419 env = pam_getenvlist(pamh);
420 for (i = 0; env && env[i]; i++) {
421 printf("Sending environment = `%s'.\n",
422 env[i]);
423 j = write(fds[1], env[i],
424 strlen(env[i]));
425 j = write(fds[1], "\n", 1);
426 j++;
427 }
428 close(fds[1]);
429 exit(0);
430 } else {
431 char buf[LINE_MAX];
432 FILE *fp;
433 close(fds[1]);
434 fp = fdopen(fds[0], "r");
435 while (fgets(buf, sizeof(buf), fp) != NULL) {
436 buf[strcspn(buf, "\r\n")] = '\0';
437 printf("Environment = `%s'.\n", buf);
438 pam_putenv(pamh, strdup(buf));
439 }
440 fclose(fp);
441 waitpid(pid, NULL, 0);
442 }
443 } else {
444 if (doauth) {
445 call_stack(pam_authenticate, "AUTH", 0);
446 }
447 if (doprompt) {
448 const void *prmpt;
449 if (pam_get_item(pamh, PAM_USER_PROMPT,
450 &prmpt) == PAM_SUCCESS) {
451 printf("Prompt = `%s'.\n",
452 (const char*)prmpt);
453 } else {
454 printf("Error reading USER_PROMPT "
455 "item.\n");
456 return 255;
457 }
458 }
459 if (doaccount) {
460 call_stack(pam_acct_mgmt, "ACCT", 0);
461 }
462 }
463 if (dorefresh) {
464 call_stack(pam_setcred, "REINITCRED",
465 PAM_REINITIALIZE_CRED);
466 }
467 if (dosetcred) {
468 call_stack(pam_setcred, "ESTCRED", PAM_ESTABLISH_CRED);
469 }
470 if (dosession) {
471 call_stack(pam_open_session, "OPENSESS", 0);
472 }
473 if (run) {
474 pid_t pid;
475 if ((pid = fork()) == 0) {
476 pull_pam_environment(pamh);
477 i = setregid(getegid(), getegid());
478 i = setreuid(geteuid(), geteuid());
479 execlp(run, run, NULL);
480 _exit(0);
481 } else {
482 waitpid(pid, NULL, 0);
483 }
484 }
485 if (dosession) {
486 call_stack(pam_close_session, "CLOSESESS", 0);
487 }
488 if (dosetcred) {
489 call_stack(pam_setcred, "DELCRED", PAM_DELETE_CRED);
490 }
491 if (dochauthtok) {
492 call_stack(pam_chauthtok, "CHAUTHTOK", 0);
493 }
494 } else {
495 /* Hack: if the name of the "module" argument starts with
496 * "pam_", assume it's a module name, open it, and run the
497 * function. */
498 char path[PATH_MAX];
499 struct stat st;
500 int (*fn)(pam_handle_t *pamh, int flags, int argc, char **argv);
501 if (strchr(module, '/') != NULL) {
502 snprintf(path, sizeof(path), "%s", module);
503 } else {
504 snprintf(path, sizeof(path), "/lib/security/%s.so",
505 module);
506 }
507 dlhandle = dlopen(path, RTLD_NOW);
508 if ((dlhandle == NULL) && (strchr(module, '/') == NULL)) {
509 if (stat("/lib64/security", &st) == 0) {
510 snprintf(path, sizeof(path),
511 "/lib64/security/%s.so", module);
512 dlhandle = dlopen(path, RTLD_NOW);
513 }
514 }
515 if (dlhandle == NULL) {
516 printf("Error opening module: %s\n", dlerror());
517 return 255;
518 }
519 printf("Calling module `%s'.\n", module);
520
521 /* Hackeroo. Linux-PAM 0.75 and later don't like it when we
522 * do this sort of thing. */
523 partial = (struct pam_partial_handle*)pamh;
524 partial->caller = 1;
525
526 if (dofork) {
527 int fds[2];
528 pid_t pid;
529 if (pipe(fds) == -1) {
530 printf("Error creating pipe: %s\n",
531 strerror(errno));
532 return 255;
533 }
534 if ((pid = fork()) == 0) {
535 char **env;
536 int i, j;
537 if (doauth) {
538 call_fn("pam_sm_authenticate",
539 "AUTH", 0);
540 }
541 if (doprompt) {
542 const void *prmpt;
543 if (pam_get_item(pamh, PAM_USER_PROMPT,
544 &prmpt) ==
545 PAM_SUCCESS) {
546 printf("Prompt = `%s'.\n",
547 (const char*)prmpt);
548 } else {
549 printf("Error reading "
550 "USER_PROMPT item.\n");
551 return 255;
552 }
553 }
554 if (doaccount) {
555 call_fn("pam_sm_acct_mgmt", "ACCT", 0);
556 }
557 close(fds[0]);
558 env = pam_getenvlist(pamh);
559 for (i = 0; env && env[i]; i++) {
560 printf("Sending environment = `%s'.\n",
561 env[i]);
562 j = write(fds[1], env[i],
563 strlen(env[i]));
564 j = write(fds[1], "\n", 1);
565 j++;
566 }
567 close(fds[1]);
568 exit(0);
569 } else {
570 char buf[LINE_MAX];
571 FILE *fp;
572 close(fds[1]);
573 fp = fdopen(fds[0], "r");
574 while (fgets(buf, sizeof(buf), fp) != NULL) {
575 buf[strcspn(buf, "\r\n")] = '\0';
576 printf("Environment = `%s'.\n", buf);
577 pam_putenv(pamh, strdup(buf));
578 }
579 fclose(fp);
580 waitpid(pid, NULL, 0);
581 }
582 } else {
583 if (doauth) {
584 call_fn("pam_sm_authenticate", "AUTH", 0);
585 }
586 if (doprompt) {
587 const void *prmpt;
588 if (pam_get_item(pamh, PAM_USER_PROMPT,
589 &prmpt) == 0) {
590 printf("Prompt = `%s'.\n",
591 (const char*)prmpt);
592 } else {
593 printf("Error reading USER_PROMPT "
594 "item.\n");
595 return 255;
596 }
597 }
598 if (doaccount) {
599 call_fn("pam_sm_acct_mgmt", "ACCT", 0);
600 }
601 }
602 if (dochauthtok) {
603 if (oldauthtok) {
604 if (pam_set_item(pamh, PAM_AUTHTOK,
605 oldauthtok) != 0) {
606 printf("Error setting AUTHTOK item.\n");
607 return 255;
608 }
609 }
610 call_fn("pam_sm_chauthtok", "CHAUTHTOK1",
611 PAM_PRELIM_CHECK);
612 if (oldauthtok) {
613 if (pam_set_item(pamh, PAM_OLDAUTHTOK,
614 oldauthtok) != PAM_SUCCESS) {
615 printf("Error setting OLDAUTHTOK "
616 "item.\n");
617 return 255;
618 }
619 }
620 if (authtok) {
621 if (pam_set_item(pamh, PAM_AUTHTOK,
622 authtok) != PAM_SUCCESS) {
623 printf("Error setting AUTHTOK "
624 "item.\n");
625 return 255;
626 }
627 }
628 call_fn("pam_sm_chauthtok", "CHAUTHTOK2",
629 PAM_UPDATE_AUTHTOK);
630 }
631 if (dorefresh) {
632 call_fn("pam_sm_setcred", "REINITCRED",
633 PAM_REINITIALIZE_CRED);
634 }
635 if (dosetcred) {
636 call_fn("pam_sm_setcred", "ESTCRED",
637 PAM_ESTABLISH_CRED);
638 }
639 if (dosession) {
640 call_fn("pam_sm_open_session", "OPENSESS", 0);
641 }
642 if (run) {
643 pid_t pid;
644 if ((pid = fork()) == 0) {
645 pull_pam_environment(pamh);
646 i = setregid(getegid(), getegid());
647 i = setreuid(geteuid(), geteuid());
648 execlp(run, run, NULL);
649 _exit(0);
650 } else {
651 waitpid(pid, NULL, 0);
652 }
653 }
654 if (dosession) {
655 call_fn("pam_sm_close_session", "CLOSESESS", 0);
656 }
657 if (dosetcred) {
658 call_fn("pam_sm_setcred", "DELCRED",
659 PAM_DELETE_CRED);
660 }
661 }
662
663 pam_end(pamh, PAM_SUCCESS);
664
665 if (!noreentrancy) {
666 /* Check if the buffer's been changed. */
667 if ((pwd->pw_uid != pw_uid) || (pwd->pw_gid != pw_gid)) {
668 printf("Module calls getpw functions (%s).\n",
669 pwd->pw_name);
670 printf("UID before: %d, after: %d.\n",
671 pw_uid, pwd->pw_uid);
672 printf("GID before: %d, after: %d.\n",
673 pw_gid, pwd->pw_gid);
674 }
675 if (grp->gr_gid != gr_gid) {
676 printf("Module calls getgr functions (%s).\n",
677 grp->gr_name);
678 printf("GID before: %d, after: %d.\n",
679 gr_gid, grp->gr_gid);
680 }
681 }
682
683 return 0;
684 }
685