1 /*
2  *  Written by Jeroen Nijhof <jeroen@jeroennijhof.nl> 2005/03/01
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (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
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program - see the file COPYING.
16  */
17 
18 /* --- includes --- */
19 #include <stdio.h>
20 #include <stdarg.h>			/* varg... */
21 #include <string.h>			/* strcmp,strncpy,... */
22 #include <sys/types.h>			/* stat, fork, wait */
23 #include <sys/stat.h>			/* stat */
24 #include <sys/wait.h>			/* wait */
25 #include <unistd.h>			/* stat, fork, execve, **environ */
26 #include <stdlib.h>			/* calloc, setenv, putenv */
27 
28 /* enable these module-types */
29 #define PAM_SM_AUTH
30 #define PAM_SM_ACCOUNT
31 #define PAM_SM_SESSION
32 #define PAM_SM_PASSWORD
33 
34 #include <security/pam_appl.h>		/* pam_* */
35 #include <security/pam_modules.h>
36 #ifdef HAVE_CONFIG_H
37 #  include "config.h"
38 #endif
39 #if HAVE_VSYSLOG
40 #  include <syslog.h>			/* vsyslog */
41 #endif
42 
43 /* --- customize these defines --- */
44 
45 #ifndef PAM_SCRIPT_DIR
46 #  define PAM_SCRIPT_DIR	"/usr/bin"
47 #endif
48 #define PAM_SCRIPT_AUTH		"pam_script_auth"
49 #define PAM_SCRIPT_ACCT		"pam_script_acct"
50 #define PAM_SCRIPT_PASSWD	"pam_script_passwd"
51 #define PAM_SCRIPT_SES_OPEN	"pam_script_ses_open"
52 #define PAM_SCRIPT_SES_CLOSE	"pam_script_ses_close"
53 
54 /* --- defines --- */
55 
56 #define PAM_EXTERN	extern
57 #define BUFSIZE	128
58 #define DEFAULT_USER "nobody"
59 
60 /* --- macros --- */
61 #define PAM_SCRIPT_SETENV(key)						\
62 	{if (pam_get_item(pamh, key, &envval) == PAM_SUCCESS)		\
63 		pam_script_setenv(#key, (const char *) envval);		\
64 	else	pam_script_setenv(#key, (const char *) NULL);}
65 
66 /* external variables */
67 extern char **environ;
68 
69 #if 0
70 /* convenient function to throw into one of the methods below
71  * for setting as a breakpoint for debugging purposes.
72  */
73 void pam_script_xxx(void) {
74 	int i = 1;
75 }
76 #endif
77 
78 /* internal helper functions */
79 
pam_script_syslog(int priority,const char * format,...)80 static void pam_script_syslog(int priority, const char *format, ...) {
81 	va_list args;
82 	va_start(args, format);
83 
84 #if HAVE_VSYSLOG
85 	openlog(PACKAGE, LOG_CONS|LOG_PID, LOG_AUTH);
86 	vsyslog(priority, format, args);
87 	closelog();
88 #else
89 	vfprintf(stderr, format, args);
90 #endif
91 }
92 
pam_script_setenv(const char * key,const char * value)93 static void pam_script_setenv(const char *key, const char *value) {
94 #if HAVE_SETENV
95 	setenv(key, (value?value:""), 1);
96 #elif HAVE_PUTENV
97 	char	 buffer[BUFSIZE],
98 		*str;
99 	if (snprintf(buffer, BUFSIZE, "%s=%s", key, value ? value : "") > BUFSIZE) {
100 		// insufficient space
101 		return;
102 	}
103 	if ((str = strdup(buffer)) != NULL) {
104 		putenv(str);
105 	} /* else {
106 	     untrapped memory error - just do not add to environment
107 	} */
108 #else
109 #  error Can not set the environment
110 #endif
111 }
112 
pam_script_get_user(pam_handle_t * pamh,const char ** user)113 static int pam_script_get_user(pam_handle_t *pamh, const char **user) {
114 	int retval;
115 
116 	retval = pam_get_user(pamh, user, NULL);
117 	if (retval != PAM_SUCCESS) {
118 		pam_script_syslog(LOG_ALERT, "pam_get_user returned error: %s",
119 			pam_strerror(pamh,retval));
120 		return retval;
121 	}
122 	if (*user == NULL || **user == '\0') {
123 		pam_script_syslog(LOG_ALERT, "username not known");
124 		retval = pam_set_item(pamh, PAM_USER,
125 			(const void *) DEFAULT_USER);
126 		if (retval != PAM_SUCCESS)
127 		return PAM_USER_UNKNOWN;
128 	}
129 	return retval;
130 }
131 
check_path_perms(const char * path)132 static int check_path_perms(const char *path) {
133 
134 	struct stat fs;
135 	const mode_t ALL_EXEC_MASK = (S_IXUSR|S_IXGRP|S_IXOTH);
136 
137 	/*
138 	 * note: checking security properties like this leaves us with a race
139 	 * condition, because the stat()/execve() execution is not atomic.
140 	 *
141 	 * likely candidates for overcoming this would have been fexecve() or
142 	 * execveat(). But both suffer from a side effect (at least on
143 	 * Linux/glibc implementation) that causes scripts to be called like
144 	 *
145 	 * "bash /proc/self/fd/<num> <argv1> ..."
146 	 *
147 	 * This would break existing scripts that can't make out their
148 	 * basename/dirname any more to base decisions on them.
149 	 *
150 	 * An explicit environment variable could be added that carries the
151 	 * "real" basename but that would complicate things even more.
152 	 */
153 
154 	/* test for script existence first */
155 	if (stat(path, &fs) < 0) {
156 		/* stat failure */
157 		pam_script_syslog(LOG_ERR,"can not stat %s", path);
158 		return 1;
159 	}
160 
161 	if ((fs.st_mode & ALL_EXEC_MASK) != ALL_EXEC_MASK) {
162 		/* script not executable at all levels */
163 		pam_script_syslog(LOG_ALERT,
164 			"path %s not fully executable", path);
165 		return 1;
166 	}
167 	else if ((fs.st_mode & S_IWOTH) != 0) {
168 		/* script is world writeable, probably not a good idea */
169 		pam_script_syslog(LOG_ALERT,
170 				"path %s is world-writeable, rejecting for "
171 				"security reasons", path);
172 		return 1;
173 	}
174 	else if (fs.st_uid != 0 || fs.st_gid != 0) {
175 		/* script should be owned by root:root */
176 		pam_script_syslog(LOG_ALERT,
177 				"path %s is not owned by root:root, rejecting for security reasons", path);
178 		return 1;
179 	}
180 
181 	return 0;
182 }
183 
pam_script_exec(pam_handle_t * pamh,const char * type,const char * script,const char * user,int rv,int argc,const char ** argv)184 static int pam_script_exec(pam_handle_t *pamh,
185 	const char *type, const char *script, const char *user,
186 	int rv, int argc, const char **argv) {
187 
188 	int	retval = rv,
189 		status,
190 		i;
191 	char	cmd[BUFSIZE] = { '\0' };
192 	char	**newargv;
193 	const void *envval = NULL;
194 	pid_t	child_pid = 0;
195 
196 	/* check for pam.conf options */
197 	for (i = 0; i < argc; i++) {
198 		if (strncmp(argv[i],"onerr=",6) == 0) {
199 			if (strcmp(argv[i],"onerr=fail") == 0)
200 				retval = rv;
201 			else if (strcmp(argv[i],"onerr=success") == 0)
202 				retval = PAM_SUCCESS;
203 			else
204 				pam_script_syslog(LOG_ERR,
205 					"invalid option: %s", argv[i]);
206 		}
207 		if (strncmp(argv[i],"dir=",4) == 0) {
208 			const char *new_dir = argv[i] + 4;
209 			const int MAX_DIR_LEN = BUFSIZE - 2;
210 
211 			if (*new_dir) { /* got new scriptdir */
212 				if (snprintf(cmd, MAX_DIR_LEN, "%s", new_dir) > MAX_DIR_LEN) {
213 					pam_script_syslog(LOG_ERR,"script dir %s exceeds maximum supported path length", new_dir);
214 					cmd[0] = '\0';
215 				}
216 			}
217 		}
218 	}
219 
220 	if (cmd[0] == '\0') {
221 		strncpy(cmd, PAM_SCRIPT_DIR, BUFSIZE - 1);
222 	}
223 
224 	/* strip trailing '/' */
225 	while (1) {
226 		size_t curlen = strlen(cmd);
227 
228 		if (curlen == 0 || cmd[curlen-1] != '/')
229 			break;
230 
231 		cmd[curlen - 1] = '\0';
232 	}
233 
234 	/* check the base directory permissions */
235 	if (check_path_perms(cmd) != 0)
236 		return retval;
237 
238 	strcat(cmd,"/");
239 
240 	if (strlen(script) > (BUFSIZE - strlen(cmd) - 1)) {
241 		pam_script_syslog(LOG_ERR,"script path %s/%s exceeds maximum supported path length", cmd, script);
242 		return retval;
243 	}
244 	strncat(cmd,script,BUFSIZE-strlen(cmd)-1);
245 
246 	/* check the script permissions */
247 	if (check_path_perms(cmd) != 0)
248 		return retval;
249 
250 	/* Execute external program */
251 	/* fork process */
252 	switch(child_pid = fork()) {
253 	case -1:				/* fork failure */
254 		pam_script_syslog(LOG_ALERT,
255 			"script %s fork failure", cmd);
256 		return retval;
257 	case  0:				/* child */
258 		/* Get PAM environment, pass it onto the child's environment */
259 		PAM_SCRIPT_SETENV(PAM_SERVICE);
260 		pam_script_setenv("PAM_TYPE", type);
261 		pam_script_setenv("PAM_USER", user);
262 		PAM_SCRIPT_SETENV(PAM_RUSER);
263 		PAM_SCRIPT_SETENV(PAM_RHOST);
264 		PAM_SCRIPT_SETENV(PAM_TTY);
265 		PAM_SCRIPT_SETENV(PAM_AUTHTOK);
266 		PAM_SCRIPT_SETENV(PAM_OLDAUTHTOK);
267 
268 		/* construct newargv */
269 		if (!(newargv = (char **) calloc(sizeof(char *), argc+2))) {
270 			// for rationale see below
271 			_exit(127);
272 		}
273 		newargv[0] = cmd;
274 		for (i = 0; i < argc; i++) {
275 			newargv[1+i] = (char *) argv[i];
276 		}
277 		(void) execve(cmd, newargv, environ);
278 		/* shouldn't get here, unless an error */
279 		pam_script_syslog(LOG_ALERT,
280 			"script %s exec failure", cmd);
281 		/*
282 		 * explicitly exit() here to avoid continuing execution of the
283 		 * PAM stack in the forked process in an undefined manner.
284 		 *
285 		 * 127 is typically the exit code when something fundamentally
286 		 * went wrong with starting a child process.
287 		 *
288 		 * use _exit() instead of exit() to avoid execution of any
289 		 * cleanup code like atexit() handlers.
290 		 */
291 		_exit(127);
292 		return retval;
293 
294 	default:				/* parent */
295 		(void) waitpid(child_pid, &status, 0);
296 		if (WIFEXITED(status))
297 			return (WEXITSTATUS(status) ? rv : PAM_SUCCESS);
298 		else
299 			return retval;
300 	}
301 	return PAM_SUCCESS;
302 }
303 
pam_script_converse(pam_handle_t * pamh,int argc,struct pam_message ** message,struct pam_response ** response)304 static int pam_script_converse(pam_handle_t *pamh, int argc,
305 	struct pam_message **message, struct pam_response **response)
306 {
307 	int retval;
308 	struct pam_conv *conv;
309 
310 	retval = pam_get_item(pamh, PAM_CONV, (const void **)(void *) &conv);
311 	if (retval == PAM_SUCCESS) {
312 		retval = conv->conv(argc, (const struct pam_message **) message,
313 				response, conv->appdata_ptr);
314 	}
315 	return retval;
316 }
317 
pam_script_set_authtok(pam_handle_t * pamh,int flags,int argc,const char ** argv,char * prompt,int authtok)318 static int pam_script_set_authtok(pam_handle_t *pamh, int flags,
319 	int argc, const char **argv, char *prompt, int authtok)
320 {
321 	int	retval;
322 	char	*password;
323 
324 	struct pam_message msg[1],*pmsg[1];
325 	struct pam_response *response;
326 
327 	/* set up conversation call */
328 	pmsg[0] = &msg[0];
329 	msg[0].msg_style = PAM_PROMPT_ECHO_OFF;
330 	msg[0].msg = prompt;
331 	response = NULL;
332 
333 	if ((retval = pam_script_converse(pamh, 1, pmsg, &response)) != PAM_SUCCESS)
334 		return retval;
335 
336 	if (response) {
337 		if ((flags & PAM_DISALLOW_NULL_AUTHTOK) && response[0].resp == NULL) {
338 			free(response);
339 			return PAM_AUTH_ERR;
340 		}
341 		password = response[0].resp;
342 	  	response[0].resp = NULL;
343 	}
344 	else
345 		return PAM_CONV_ERR;
346 
347 	free(response);
348 	pam_set_item(pamh, authtok, password);
349 	return PAM_SUCCESS;
350 }
351 
pam_script_senderr(pam_handle_t * pamh,int flags,int argc,const char ** argv,char * message)352 static int pam_script_senderr(pam_handle_t *pamh, int flags,
353 	int argc, const char **argv, char *message)
354 {
355 	int retval;
356 	struct pam_message msg[1],*pmsg[1];
357 	struct pam_response *response;
358 
359 	/* set up conversation call */
360 	pmsg[0] = &msg[0];
361 	msg[0].msg_style = PAM_ERROR_MSG;
362 	msg[0].msg = message;
363 	response = NULL;
364 
365 	if ((retval = pam_script_converse(pamh, 1, pmsg, &response)) != PAM_SUCCESS)
366 		return retval;
367 
368 	free(response);
369 	return PAM_SUCCESS;
370 }
371 
372 
373 /* --- authentication management functions --- */
374 
375 PAM_EXTERN
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)376 int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
377 			const char **argv)
378 {
379 	int retval;
380 	const char *user=NULL;
381 	char *password;
382 
383 	if ((retval = pam_script_get_user(pamh, &user)) != PAM_SUCCESS)
384 		return retval;
385 
386 	/*
387 	* Check if PAM_AUTHTOK is set by early pam modules and
388 	* if not ask user for password.
389 	*/
390 	pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
391 
392 	if (!password) {
393 		retval = pam_script_set_authtok(pamh, flags, argc, argv, "Password: ", PAM_AUTHTOK);
394 		if (retval != PAM_SUCCESS)
395 			return retval;
396 	}
397 
398 	return pam_script_exec(pamh, "auth", PAM_SCRIPT_AUTH,
399 		user, PAM_AUTH_ERR, argc, argv);
400 }
401 
402 PAM_EXTERN
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)403 int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
404 		   const char **argv)
405 {
406 	return PAM_SUCCESS;
407 }
408 
409 /* --- account management functions --- */
410 
411 PAM_EXTERN
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char ** argv)412 int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
413 		     const char **argv)
414 {
415 	int retval;
416 	const char *user=NULL;
417 
418 	if ((retval = pam_script_get_user(pamh, &user)) != PAM_SUCCESS)
419 		return retval;
420 
421 	return pam_script_exec(pamh, "account", PAM_SCRIPT_ACCT,
422 		user,PAM_AUTH_ERR,argc,argv);
423 }
424 
425 /* --- password management --- */
426 
427 /*
428  * instead of memset for clearing memory, hopefully not being optimized out
429  * by the compiler
430  */
safe_clearmem(char * mem,size_t size)431 static void safe_clearmem(char *mem, size_t size) {
432 	size_t i;
433 	for (i = 0; i < size; i++) {
434 		mem[i] = 0;
435 	}
436 }
437 
438 PAM_EXTERN
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)439 int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,
440 		     const char **argv)
441 {
442 	int retval;
443 	const char *user = NULL;
444 	char *password = NULL;
445         char new_pass1[BUFSIZE];
446         char new_pass2[BUFSIZE];
447 
448 	if ((retval = pam_script_get_user(pamh, &user)) != PAM_SUCCESS)
449 		return retval;
450 
451 	if ( flags & PAM_UPDATE_AUTHTOK ) {
452 		/*
453 		 * Check if PAM_OLDAUTHTOK is set by early pam modules and
454 		 * if not ask user (not root) for current password.
455 		 */
456 		pam_get_item(pamh, PAM_OLDAUTHTOK, (void*) &password);
457 		if (!password && strcmp(user, "root") != 0) {
458 			retval = pam_script_set_authtok(pamh, flags, argc, argv, "Current password: ", PAM_OLDAUTHTOK);
459 			if (retval != PAM_SUCCESS)
460 				return retval;
461 			pam_get_item(pamh, PAM_OLDAUTHTOK, (void*) &password);
462 		}
463 
464 		/*
465 		 * Check if PAM_AUTHTOK is set by early pam modules and
466 		 * if not ask user for the new password.
467 		 */
468 		pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
469 		if (!password) {
470 			retval = pam_script_set_authtok(pamh, flags, argc, argv, "New password: ", PAM_AUTHTOK);
471 			if (retval != PAM_SUCCESS)
472 				return retval;
473 			pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
474 			snprintf(new_pass1, BUFSIZE, "%s", password);
475 			password = NULL;
476 
477 			retval = pam_script_set_authtok(pamh, flags, argc, argv, "New password (again): ", PAM_AUTHTOK);
478 			if (retval != PAM_SUCCESS) {
479 				safe_clearmem(new_pass1, sizeof(new_pass1));
480 				return retval;
481 			}
482 			retval = pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
483 			snprintf(new_pass2, BUFSIZE, "%s", password);
484 			password = NULL;
485 
486 			/* Check if new passwords are the same */
487 			if (strcmp(new_pass1, new_pass2) != 0) {
488 				retval = pam_script_senderr(pamh, flags, argc, argv,
489 						"You must enter the same password twice.");
490 
491 				if (retval == PAM_SUCCESS)
492 					retval = PAM_AUTHTOK_ERR;
493 			}
494 
495 			safe_clearmem(new_pass1, sizeof(new_pass1));
496 			safe_clearmem(new_pass2, sizeof(new_pass2));
497 
498 			if (retval != PAM_SUCCESS)
499 				return PAM_AUTHTOK_ERR;
500 		}
501 		return pam_script_exec(pamh, "password", PAM_SCRIPT_PASSWD,
502 			user, PAM_AUTHTOK_ERR, argc, argv);
503 	}
504 	return PAM_SUCCESS;
505 }
506 
507 /* --- session management --- */
508 
509 PAM_EXTERN
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)510 int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,
511 			const char **argv)
512 {
513 	int retval;
514 	const char *user = NULL;
515 
516 	if ((retval = pam_script_get_user(pamh, &user)) != PAM_SUCCESS)
517 		return retval;
518 
519 	return pam_script_exec(pamh, "session", PAM_SCRIPT_SES_OPEN,
520 		user, PAM_SESSION_ERR, argc, argv);
521 }
522 
523 PAM_EXTERN
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)524 int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,
525 			 const char **argv)
526 {
527 	int retval;
528 	const char *user = NULL;
529 
530 	if ((retval = pam_script_get_user(pamh, &user)) != PAM_SUCCESS)
531 		return retval;
532 
533 	return pam_script_exec(pamh, "session", PAM_SCRIPT_SES_CLOSE,
534 		user, PAM_SESSION_ERR, argc, argv);
535 }
536 
537 /* end of module definition */
538 
539 #ifdef PAM_STATIC
540 
541 /* static module data */
542 
543 struct pam_module _pam_script_modstruct = {
544 	"pam_script",
545 	pam_sm_authenticate,
546 	pam_sm_setcred,
547 	pam_sm_acct_mgmt,
548 	pam_sm_open_session,
549 	pam_sm_close_session,
550 	pam_sm_chauthtok
551 };
552 
553 #endif
554