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