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