summaryrefslogtreecommitdiff
path: root/modules/pam_filter/pam_filter.c
blob: de8c35ad08cc06de02ef3a8436bef982812dcfdc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
/*
 * $Id$
 *
 * written by Andrew Morgan <morgan@transmeta.com> with much help from
 * Richard Stevens' UNIX Network Programming book.
 */

#include "config.h"

#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <termios.h>

#include <signal.h>

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD

#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include "pam_filter.h"

/* ------ some tokens used for convenience throughout this file ------- */

#define FILTER_DEBUG     01
#define FILTER_RUN1      02
#define FILTER_RUN2      04
#define NEW_TERM        010
#define NON_TERM        020

/* -------------------------------------------------------------------- */

/* log errors */

#include <stdarg.h>

#define DEV_PTMX "/dev/ptmx"

static int
master (void)
{
    int fd;

    if ((fd = open(DEV_PTMX, O_RDWR)) >= 0) {
	return fd;
    }

    return -1;
}

static int process_args(pam_handle_t *pamh
			, int argc, const char **argv, const char *type
			, char ***evp, const char **filtername)
{
    int ctrl=0;

    while (argc-- > 0) {
	if (strcmp("debug",*argv) == 0) {
	    ctrl |= FILTER_DEBUG;
	} else if (strcmp("new_term",*argv) == 0) {
	    ctrl |= NEW_TERM;
	} else if (strcmp("non_term",*argv) == 0) {
	    ctrl |= NON_TERM;
	} else if (strcmp("run1",*argv) == 0) {
	    ctrl |= FILTER_RUN1;
	    if (argc <= 0) {
		pam_syslog(pamh, LOG_ERR, "no run filter supplied");
	    } else
		break;
	} else if (strcmp("run2",*argv) == 0) {
	    ctrl |= FILTER_RUN2;
	    if (argc <= 0) {
		pam_syslog(pamh, LOG_ERR, "no run filter supplied");
	    } else
		break;
	} else {
	    pam_syslog(pamh, LOG_ERR, "unrecognized option: %s", *argv);
	}
	++argv;                   /* step along list */
    }

    if (argc < 0) {
	/* there was no reference to a filter */
	*filtername = NULL;
	*evp = NULL;
    } else {
	char **levp;
	const char *user = NULL;
	const void *tmp;
	int i,size, retval;

	*filtername = *++argv;
	if (ctrl & FILTER_DEBUG) {
	    pam_syslog(pamh, LOG_DEBUG, "will run filter %s", *filtername);
	}

	levp = (char **) malloc(5*sizeof(char *));
	if (levp == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "no memory for environment of filter");
	    return -1;
	}

	for (size=i=0; i<argc; ++i) {
	    size += strlen(argv[i])+1;
	}

	/* the "ARGS" variable */

#define ARGS_NAME      "ARGS="
#define ARGS_OFFSET    (sizeof(ARGS_NAME) - 1)

	size += ARGS_OFFSET;

	levp[0] = (char *) malloc(size);
	if (levp[0] == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "no memory for filter arguments");
	    if (levp) {
		free(levp);
	    }
	    return -1;
	}

	strcpy(levp[0], ARGS_NAME);
	for (i=0,size=ARGS_OFFSET; i<argc; ++i) {
	    strcpy(levp[0]+size, argv[i]);
	    size += strlen(argv[i]);
	    levp[0][size++] = ' ';
	}
	levp[0][--size] = '\0';                    /* <NUL> terminate */

	/* the "SERVICE" variable */

#define SERVICE_NAME      "SERVICE="
#define SERVICE_OFFSET    (sizeof(SERVICE_NAME) - 1)

	retval = pam_get_item(pamh, PAM_SERVICE, &tmp);
	if (retval != PAM_SUCCESS || tmp == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "service name not found");
	    if (levp) {
		free(levp[0]);
		free(levp);
	    }
	    return -1;
	}
	size = SERVICE_OFFSET+strlen(tmp);

	levp[1] = (char *) malloc(size+1);
	if (levp[1] == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "no memory for service name");
	    if (levp) {
		free(levp[0]);
		free(levp);
	    }
	    return -1;
	}

	strcpy(levp[1], SERVICE_NAME);
	strcpy(levp[1]+SERVICE_OFFSET, tmp);
	levp[1][size] = '\0';                      /* <NUL> terminate */

	/* the "USER" variable */

#define USER_NAME      "USER="
#define USER_OFFSET    (sizeof(USER_NAME) - 1)

	if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS ||
	    user == NULL) {
	    user = "<unknown>";
	}
	size = USER_OFFSET+strlen(user);

	levp[2] = (char *) malloc(size+1);
	if (levp[2] == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "no memory for user's name");
	    if (levp) {
		free(levp[1]);
		free(levp[0]);
		free(levp);
	    }
	    return -1;
	}

	strcpy(levp[2], USER_NAME);
	strcpy(levp[2]+USER_OFFSET, user);
	levp[2][size] = '\0';                      /* <NUL> terminate */

	/* the "USER" variable */

#define TYPE_NAME      "TYPE="
#define TYPE_OFFSET    (sizeof(TYPE_NAME) - 1)

	size = TYPE_OFFSET+strlen(type);

	levp[3] = (char *) malloc(size+1);
	if (levp[3] == NULL) {
	    pam_syslog(pamh, LOG_CRIT, "no memory for type");
	    if (levp) {
		free(levp[2]);
		free(levp[1]);
		free(levp[0]);
		free(levp);
	    }
	    return -1;
	}

	strcpy(levp[3], TYPE_NAME);
	strcpy(levp[3]+TYPE_OFFSET, type);
	levp[3][size] = '\0';                      /* <NUL> terminate */

	levp[4] = NULL;	                     /* end list */

	*evp = levp;
    }

    if ((ctrl & FILTER_DEBUG) && *filtername) {
	char **e;

	pam_syslog(pamh, LOG_DEBUG, "filter[%s]: %s", type, *filtername);
	pam_syslog(pamh, LOG_DEBUG, "environment:");
	for (e=*evp; e && *e; ++e) {
	    pam_syslog(pamh, LOG_DEBUG, "  %s", *e);
	}
    }

    return ctrl;
}

static void free_evp(char *evp[])
{
    int i;

    if (evp)
	for (i=0; i<4; ++i) {
	    if (evp[i])
		free(evp[i]);
	}
    free(evp);
}

static int
set_filter (pam_handle_t *pamh, int flags UNUSED, int ctrl,
	    const char **evp, const char *filtername)
{
    int status=-1;
    char* terminal = NULL;
    struct termios stored_mode;           /* initial terminal mode settings */
    int fd[2], child=0, child2=0, aterminal;

    if (filtername == NULL || *filtername != '/') {
	pam_syslog(pamh, LOG_ERR,
		   "filtername not permitted; full pathname required");
	return PAM_ABORT;
    }

    if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
	aterminal = 0;
    } else {
	aterminal = 1;
    }

    if (aterminal) {

	/* open the master pseudo terminal */

	fd[0] = master();
	if (fd[0] < 0) {
	    pam_syslog(pamh, LOG_CRIT, "no master terminal");
	    return PAM_AUTH_ERR;
	}

	/* set terminal into raw mode.. remember old mode so that we can
	   revert to it after the child has quit. */

	/* this is termios terminal handling... */

	if ( tcgetattr(STDIN_FILENO, &stored_mode) < 0 ) {
	    pam_syslog(pamh, LOG_CRIT, "couldn't copy terminal mode: %m");
	    /* in trouble, so close down */
	    close(fd[0]);
	    return PAM_ABORT;
	} else {
	    struct termios t_mode = stored_mode;

	    t_mode.c_iflag = 0;            /* no input control */
	    t_mode.c_oflag &= ~OPOST;      /* no ouput post processing */

	    /* no signals, canonical input, echoing, upper/lower output */
#ifdef XCASE
	    t_mode.c_lflag &= ~(XCASE);
#endif
	    t_mode.c_lflag &= ~(ISIG|ICANON|ECHO);
	    t_mode.c_cflag &= ~(CSIZE|PARENB);  /* no parity */
	    t_mode.c_cflag |= CS8;              /* 8 bit chars */

	    t_mode.c_cc[VMIN] = 1; /* number of chars to satisfy a read */
	    t_mode.c_cc[VTIME] = 0;          /* 0/10th second for chars */

	    if ( tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_mode) < 0 ) {
		pam_syslog(pamh, LOG_ERR,
			   "couldn't put terminal in RAW mode: %m");
		close(fd[0]);
		return PAM_ABORT;
	    }

	    /*
	     * NOTE: Unlike the stream socket case here the child
	     * opens the slave terminal as fd[1] *after* the fork...
	     */
	}
    } else {

	/*
	 * not a terminal line so just open a stream socket fd[0-1]
	 * both set...
	 */

	if ( socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0 ) {
	    pam_syslog(pamh, LOG_ERR, "couldn't open a stream pipe: %m");
	    return PAM_ABORT;
	}
    }

    /* start child process */

    if ( (child = fork()) < 0 ) {

	pam_syslog(pamh, LOG_ERR, "first fork failed: %m");
	if (aterminal) {
		(void) tcsetattr(STDIN_FILENO, TCSAFLUSH, &stored_mode);
		close(fd[0]);
	} else {
		/* Socket pair */
		close(fd[0]);
		close(fd[1]);
	}

	return PAM_AUTH_ERR;
    }

    if ( child == 0 ) {                  /* child process *is* application */

	if (aterminal) {

	    /* close the controlling tty */

#if defined(__hpux) && defined(O_NOCTTY)
	    int t = open("/dev/tty", O_RDWR|O_NOCTTY);
#else
	    int t = open("/dev/tty",O_RDWR);
	    if (t > 0) {
		(void) ioctl(t, TIOCNOTTY, NULL);
		close(t);
	    }
#endif /* defined(__hpux) && defined(O_NOCTTY) */

	    /* make this process it's own process leader */
	    if (setsid() == -1) {
		pam_syslog(pamh, LOG_ERR,
			   "child cannot become new session: %m");
		return PAM_ABORT;
	    }

	    /* grant slave terminal */
	    if (grantpt (fd[0]) < 0) {
		pam_syslog(pamh, LOG_ERR, "Cannot grant acccess to slave terminal");
		return PAM_ABORT;
	    }

	    /* unlock slave terminal */
	    if (unlockpt (fd[0]) < 0) {
		pam_syslog(pamh, LOG_ERR, "Cannot unlock slave terminal");
		return PAM_ABORT;
	    }

	    /* find slave's name */
	    terminal = ptsname(fd[0]); /* returned value should not be freed */

	    if (terminal == NULL) {
		pam_syslog(pamh, LOG_ERR,
			   "Cannot get the name of the slave terminal: %m");
		return PAM_ABORT;
	    }

	    fd[1] = open(terminal, O_RDWR);
	    close(fd[0]);      /* process is the child -- uses line fd[1] */

	    if (fd[1] < 0) {
		pam_syslog(pamh, LOG_ERR,
			   "cannot open slave terminal: %s: %m", terminal);
		return PAM_ABORT;
	    }

	    /* initialize the child's terminal to be the way the
	       parent's was before we set it into RAW mode */

	    if ( tcsetattr(fd[1], TCSANOW, &stored_mode) < 0 ) {
		pam_syslog(pamh, LOG_ERR,
			   "cannot set slave terminal mode: %s: %m", terminal);
		close(fd[1]);
		return PAM_ABORT;
	    }
	} else {

	    /* nothing to do for a simple stream socket */

	}

	/* re-assign the stdin/out to fd[1] <- (talks to filter). */

	if ( dup2(fd[1],STDIN_FILENO) != STDIN_FILENO ||
	     dup2(fd[1],STDOUT_FILENO) != STDOUT_FILENO ||
	     dup2(fd[1],STDERR_FILENO) != STDERR_FILENO )  {
	    pam_syslog(pamh, LOG_ERR,
		       "unable to re-assign STDIN/OUT/ERR: %m");
	    close(fd[1]);
	    return PAM_ABORT;
	}

	/* make sure that file descriptors survive 'exec's */

	if ( fcntl(STDIN_FILENO, F_SETFD, 0) ||
	     fcntl(STDOUT_FILENO,F_SETFD, 0) ||
	     fcntl(STDERR_FILENO,F_SETFD, 0) ) {
	    pam_syslog(pamh, LOG_ERR,
		       "unable to re-assign STDIN/OUT/ERR: %m");
	    return PAM_ABORT;
	}

	/* now the user input is read from the parent/filter: forget fd */

	close(fd[1]);

	/* the current process is now aparently working with filtered
	   stdio/stdout/stderr --- success! */

	return PAM_SUCCESS;
    }

    /* Clear out passwords... there is a security problem here in
     * that this process never executes pam_end.  Consequently, any
     * other sensitive data in this process is *not* explicitly
     * overwritten, before the process terminates */

    (void) pam_set_item(pamh, PAM_AUTHTOK, NULL);
    (void) pam_set_item(pamh, PAM_OLDAUTHTOK, NULL);

    /* fork a copy of process to run the actual filter executable */

    if ( (child2 = fork()) < 0 ) {

	pam_syslog(pamh, LOG_ERR, "filter fork failed: %m");
	child2 = 0;

    } else if ( child2 == 0 ) {              /* exec the child filter */

	if ( dup2(fd[0],APPIN_FILENO) != APPIN_FILENO ||
	     dup2(fd[0],APPOUT_FILENO) != APPOUT_FILENO ||
	     dup2(fd[0],APPERR_FILENO) != APPERR_FILENO )  {
	    pam_syslog(pamh, LOG_ERR,
		       "unable to re-assign APPIN/OUT/ERR: %m");
	    close(fd[0]);
	    _exit(1);
	}

	/* make sure that file descriptors survive 'exec's */

	if ( fcntl(APPIN_FILENO, F_SETFD, 0) == -1 ||
	     fcntl(APPOUT_FILENO,F_SETFD, 0) == -1 ||
	     fcntl(APPERR_FILENO,F_SETFD, 0) == -1 ) {
	    pam_syslog(pamh, LOG_ERR,
		       "unable to retain APPIN/OUT/ERR: %m");
	    close(APPIN_FILENO);
	    close(APPOUT_FILENO);
	    close(APPERR_FILENO);
	    _exit(1);
	}

	/* now the user input is read from the parent through filter */

	execle(filtername, "<pam_filter>", NULL, evp);

	/* getting to here is an error */

	pam_syslog(pamh, LOG_ERR, "filter: %s: %m", filtername);
	_exit(1);

    } else {           /* wait for either of the two children to exit */

	while (child && child2) {    /* loop if there are two children */
	    int lstatus=0;
	    int chid;

	    chid = wait(&lstatus);
	    if (chid == child) {

		if (WIFEXITED(lstatus)) {            /* exited ? */
		    status = WEXITSTATUS(lstatus);
		} else if (WIFSIGNALED(lstatus)) {   /* killed ? */
		    status = -1;
		} else
		    continue;             /* just stopped etc.. */
		child = 0;        /* the child has exited */

	    } else if (chid == child2) {
		/*
		 * if the filter has exited. Let the child die
		 * naturally below
		 */
		if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus))
		    child2 = 0;
	    } else {

		pam_syslog(pamh, LOG_ERR,
			   "programming error <chid=%d,lstatus=%x> "
			   "in file %s at line %d",
			   chid, lstatus, __FILE__, __LINE__);
		child = child2 = 0;
		status = -1;

	    }
	}
    }

    close(fd[0]);

    /* if there is something running, wait for it to exit */

    while (child || child2) {
	int lstatus=0;
	int chid;

	chid = wait(&lstatus);

	if (child && chid == child) {

	    if (WIFEXITED(lstatus)) {            /* exited ? */
		status = WEXITSTATUS(lstatus);
	    } else if (WIFSIGNALED(lstatus)) {   /* killed ? */
		status = -1;
	    } else
		continue;             /* just stopped etc.. */
	    child = 0;        /* the child has exited */

	} else if (child2 && chid == child2) {

	    if (WIFEXITED(lstatus) || WIFSIGNALED(lstatus))
		child2 = 0;

	} else {

	    pam_syslog(pamh, LOG_ERR,
		       "programming error <chid=%d,lstatus=%x> "
		       "in file %s at line %d",
		       chid, lstatus, __FILE__, __LINE__);
	    child = child2 = 0;
	    status = -1;

	}
    }

    if (aterminal) {
	/* reset to initial terminal mode */
	    (void) tcsetattr(STDIN_FILENO, TCSANOW, &stored_mode);
    }

    if (ctrl & FILTER_DEBUG) {
	pam_syslog(pamh, LOG_DEBUG, "parent process exited");      /* clock off */
    }

    /* quit the parent process, returning the child's exit status */

    exit(status);
    return status; /* never reached, to make gcc happy */
}

static int set_the_terminal(pam_handle_t *pamh)
{
    const void *tty;

    if (pam_get_item(pamh, PAM_TTY, &tty) != PAM_SUCCESS
	|| tty == NULL) {
	tty = ttyname(STDIN_FILENO);
	if (tty == NULL) {
	    pam_syslog(pamh, LOG_ERR, "couldn't get the tty name");
	    return PAM_ABORT;
	}
	if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS) {
	    pam_syslog(pamh, LOG_ERR, "couldn't set tty name");
	    return PAM_ABORT;
	}
    }
    return PAM_SUCCESS;
}

static int need_a_filter(pam_handle_t *pamh
			 , int flags, int argc, const char **argv
			 , const char *name, int which_run)
{
    int ctrl;
    char **evp;
    const char *filterfile;
    int retval;

    ctrl = process_args(pamh, argc, argv, name, &evp, &filterfile);
    if (ctrl == -1) {
	return PAM_AUTHINFO_UNAVAIL;
    }

    /* set the tty to the old or the new one? */

    if (!(ctrl & NON_TERM) && !(ctrl & NEW_TERM)) {
	retval = set_the_terminal(pamh);
	if (retval != PAM_SUCCESS) {
	    pam_syslog(pamh, LOG_ERR, "tried and failed to set PAM_TTY");
	}
    } else {
	retval = PAM_SUCCESS;  /* nothing to do which is always a success */
    }

    if (retval == PAM_SUCCESS && (ctrl & which_run)) {
	retval = set_filter(pamh, flags, ctrl
			    , (const char **)evp, filterfile);
    }

    if (retval == PAM_SUCCESS
	&& !(ctrl & NON_TERM) && (ctrl & NEW_TERM)) {
	retval = set_the_terminal(pamh);
	if (retval != PAM_SUCCESS) {
	    pam_syslog(pamh, LOG_ERR,
		       "tried and failed to set new terminal as PAM_TTY");
	}
    }

    free_evp(evp);

    if (ctrl & FILTER_DEBUG) {
	pam_syslog(pamh, LOG_DEBUG, "filter/%s, returning %d", name, retval);
	pam_syslog(pamh, LOG_DEBUG, "[%s]", pam_strerror(pamh, retval));
    }

    return retval;
}

/* ----------------- public functions ---------------- */

/*
 * here are the advertised access points ...
 */

/* ------------------ authentication ----------------- */

int pam_sm_authenticate(pam_handle_t *pamh,
			int flags, int argc, const char **argv)
{
    return need_a_filter(pamh, flags, argc, argv
			 , "authenticate", FILTER_RUN1);
}

int pam_sm_setcred(pam_handle_t *pamh, int flags,
		   int argc, const char **argv)
{
    return need_a_filter(pamh, flags, argc, argv, "setcred", FILTER_RUN2);
}

/* --------------- account management ---------------- */

int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,
                     const char **argv)
{
    return need_a_filter(pamh, flags, argc, argv
			 , "setcred", FILTER_RUN1|FILTER_RUN2 );
}

/* --------------- session management ---------------- */

int pam_sm_open_session(pam_handle_t *pamh, int flags,
			int argc, const char **argv)
{
    return need_a_filter(pamh, flags, argc, argv
			 , "open_session", FILTER_RUN1);
}

int pam_sm_close_session(pam_handle_t *pamh, int flags,
                         int argc, const char **argv)
{
    return need_a_filter(pamh, flags, argc, argv
			 , "close_session", FILTER_RUN2);
}

/* --------- updating authentication tokens --------- */


int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
		     int argc, const char **argv)
{
    int runN;

    if (flags & PAM_PRELIM_CHECK)
	runN = FILTER_RUN1;
    else if (flags & PAM_UPDATE_AUTHTOK)
	runN = FILTER_RUN2;
    else {
	pam_syslog(pamh, LOG_ERR, "unknown flags for chauthtok (0x%X)", flags);
	return PAM_TRY_AGAIN;
    }

    return need_a_filter(pamh, flags, argc, argv, "chauthtok", runN);
}