File: mod_sftp_pam.c

package info (click to toggle)
proftpd-dfsg 1.3.4a-5%2Bdeb7u3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 27,820 kB
  • sloc: perl: 154,169; ansic: 128,582; sh: 13,564; php: 11,586; makefile: 2,156
file content (757 lines) | stat: -rw-r--r-- 20,828 bytes parent folder | download
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
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
/*
 * ProFTPD: mod_sftp_pam -- a module which provides an SSH2
 *                          "keyboard-interactive" driver using PAM
 *
 * Copyright (c) 2008-2011 TJ Saunders
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
 *
 * As a special exemption, TJ Saunders and other respective copyright holders
 * give permission to link this program with OpenSSL, and distribute the
 * resulting executable, without including the source code for OpenSSL in the
 * source distribution.
 *
 * This is mod_sftp_pam, contrib software for proftpd 1.3.x and above.
 * For more information contact TJ Saunders <tj@castaglia.org>.
 *
 * $Id: mod_sftp_pam.c,v 1.10 2011/05/23 20:56:40 castaglia Exp $
 * $Libraries: -lpam $
 */

#include "conf.h"
#include "privs.h"
#include "mod_sftp.h"

#ifndef HAVE_PAM
# error "mod_sftp_pam requires PAM support on your system"
#endif

#define MOD_SFTP_PAM_VERSION		"mod_sftp_pam/0.2"

/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030202
# error "ProFTPD 1.3.2rc2 or later required"
#endif

#ifdef HAVE_SECURITY_PAM_APPL_H
# ifdef HPUX11
#  ifndef COMSEC
#    define COMSEC 1
#  endif
# endif /* HPUX11 */
# include <security/pam_appl.h>
#endif /* HAVE_SECURITY_PAM_APPL_H */

#ifdef HAVE_SECURITY_PAM_MODULES_H
# include <security/pam_modules.h>
#endif /* HAVE_SECURITY_PAM_MODULES_H */

/* Needed for the MAXLOGNAME restriction. */
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif

#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif /* HAVE_PAM_PAM_APPL_H */

/* There is ambiguity in the PAM spec, with regard to the list of
 * struct pam_message that is passed to the conversation callback.  Is it
 * a pointer to an array, or an array of pointers?
 */
#if defined(SOLARIS2) || defined(HPUX11)
# define SFTP_PAM_MSG_MEMBER(msg, n, member)	((*(msg))[(n)].member)
#else
# define SFTP_PAM_MSG_MEMBER(msg, n, member)	((msg)[(n)]->member)
#endif

/* On non-Solaris systems, the struct pam_message argument of pam_conv is
 * declared const, but on Solaris, it isn't.  To avoid compiler warnings about
 * incompatible pointer types, we need to use const or not as appropriate.
 */
#ifndef SOLARIS2
# define PR_PAM_CONST   const
#else
# define PR_PAM_CONST 
#endif 

#define SFTP_PAM_OPT_NO_TTY		0x001
#define SFTP_PAM_OPT_NO_INFO_MSGS	0x002

module sftp_pam_module;

static void sftppam_exit_ev(const void *, void *);
MODRET sftppam_auth(cmd_rec *);

static sftp_kbdint_driver_t sftppam_driver;
static authtable sftppam_authtab[];

static pam_handle_t *sftppam_pamh = NULL;
static const char *sftppam_service = "sshd";

static int sftppam_authoritative = FALSE;
static int sftppam_auth_code = PR_AUTH_OK;
static int sftppam_handle_auth = FALSE;
static unsigned long sftppam_opts = 0UL;
static char *sftppam_user = NULL;
static size_t sftppam_userlen = 0;
static char sftppam_tty[32];

static const char *trace_channel = "ssh2";

/* PAM interaction
 */

static int sftppam_converse(int nmsgs, PR_PAM_CONST struct pam_message **msgs,
    struct pam_response **resps, void *app_data) {
  register unsigned int i;
  array_header *list;
  unsigned int recvd_count = 0;
  const char **recvd_responses = NULL;
  struct pam_response *res = NULL;

  if (nmsgs <= 0 ||
      nmsgs > PAM_MAX_NUM_MSG) {
    pr_trace_msg(trace_channel, 3, "bad number of PAM messages (%d)", nmsgs);
    return PAM_CONV_ERR;
  }

  pr_trace_msg(trace_channel, 9, "handling %d PAM %s", nmsgs,
    nmsgs == 1 ? "message" : "messages");

  /* First, send these messages to the client. */

  list = make_array(sftppam_driver.driver_pool, 1,
    sizeof(sftp_kbdint_challenge_t));

  for (i = 0; i < nmsgs; i++) {
    sftp_kbdint_challenge_t *challenge;

    /* Skip PAM_ERROR_MSG messages; we don't want to send these to the client.
     */
    if (SFTP_PAM_MSG_MEMBER(msgs, i, msg_style) == PAM_TEXT_INFO) {
      if (sftppam_opts & SFTP_PAM_OPT_NO_INFO_MSGS) {
        pr_trace_msg(trace_channel, 9,
          "skipping sending of PAM_TEXT_INFO '%s' to client",
          SFTP_PAM_MSG_MEMBER(msgs, i, msg));

      } else {
        pr_trace_msg(trace_channel, 9, "sending PAM_TEXT_INFO '%s' to client",
          SFTP_PAM_MSG_MEMBER(msgs, i, msg));

        sftp_auth_send_banner(SFTP_PAM_MSG_MEMBER(msgs, i, msg));
      }

      continue;

    } else if (SFTP_PAM_MSG_MEMBER(msgs, i, msg_style) == PAM_ERROR_MSG) {
      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_PAM_VERSION,
        "received PAM_ERROR_MSG '%s'", SFTP_PAM_MSG_MEMBER(msgs, i, msg));
      continue;
    }

    challenge = push_array(list);
    challenge->challenge = pstrdup(sftppam_driver.driver_pool,
      SFTP_PAM_MSG_MEMBER(msgs, i, msg));
    challenge->display_response = FALSE;
  }

  if (list->nelts == 0) {
    /* Nothing to see here, move along. */
    return PAM_SUCCESS;
  }

  if (sftp_kbdint_send_challenge(NULL, NULL, list->nelts, list->elts) < 0) {
    pr_trace_msg(trace_channel, 3,
      "error sending keyboard-interactive challenges: %s", strerror(errno));
    return PAM_CONV_ERR;
  }

  if (sftp_kbdint_recv_response(sftppam_driver.driver_pool, list->nelts,
      &recvd_count, &recvd_responses) < 0) {
    pr_trace_msg(trace_channel, 3,
      "error receiving keyboard-interactive responses: %s", strerror(errno));
    return PAM_CONV_ERR;
  }

  res = calloc(nmsgs, sizeof(struct pam_response));
  if (res == NULL) {
    pr_log_pri(PR_LOG_CRIT, "Out of memory!");
    return PAM_BUF_ERR;
  }

  for (i = 0; i < nmsgs; i++) {
    res[i].resp_retcode = 0;

    switch (SFTP_PAM_MSG_MEMBER(msgs, i, msg_style)) {
      case PAM_PROMPT_ECHO_ON:
        pr_trace_msg(trace_channel, 9,
          "received PAM_PROMPT_ECHO_ON message '%s', responding with '%s'",
          SFTP_PAM_MSG_MEMBER(msgs, i, msg), recvd_responses[i]);
        res[i].resp = strdup(recvd_responses[i]); 
        break;

      case PAM_PROMPT_ECHO_OFF:
        pr_trace_msg(trace_channel, 9,
          "received PAM_PROMPT_ECHO_OFF message '%s', responding with text",
          SFTP_PAM_MSG_MEMBER(msgs, i, msg));
        res[i].resp = strdup(recvd_responses[i]); 
        break;

      case PAM_TEXT_INFO:
      case PAM_ERROR_MSG:
        pr_trace_msg(trace_channel, 9, "received %s message: %s",
          msgs[i]->msg_style == PAM_TEXT_INFO ? "PAM_TEXT_INFO" :
          "PAM_ERROR_MSG", SFTP_PAM_MSG_MEMBER(msgs, i, msg));
        res[i].resp = NULL;
        break;

      default:
        pr_trace_msg(trace_channel, 3,
          "received unknown PAM message style (%d), treating it as an error",
          SFTP_PAM_MSG_MEMBER(msgs, i, msg_style));
        free(res);

        return PAM_CONV_ERR;
    }
  }

  *resps = res;
  return PAM_SUCCESS;
}

static const struct pam_conv sftppam_conv = { &sftppam_converse, NULL };

/* Driver callbacks
 */

static int sftppam_driver_open(sftp_kbdint_driver_t *driver, const char *user) {
  int res;
  config_rec *c;

  /* XXX Should we pay attention to AuthOrder here?  I.e. if AuthOrder
   * does not include mod_sftp_pam or mod_auth_pam, should we fail to
   * open this driver, since the AuthOrder indicates that no PAM check is
   * desired?  For this to work, AuthOrder needs to have been processed
   * prior to this callback being invoked...
   */

  /* Figure out our default return style: whether or not PAM should allow
   * other auth modules a shot at this user or not is controlled by adding
   * '*' to a module name in the AuthOrder directive.  By default, auth
   * modules are not authoritative, and allow other auth modules a chance at
   * authenticating the user.  This is not the most secure configuration, but
   * it allows things like AuthUserFile to work "out of the box".
   */
  if (sftppam_authtab[0].auth_flags & PR_AUTH_FL_REQUIRED) {
    sftppam_authoritative = TRUE;
  }

  sftppam_userlen = strlen(user) + 1;
  if (sftppam_userlen > (PAM_MAX_MSG_SIZE + 1)) {
    sftppam_userlen = PAM_MAX_MSG_SIZE + 1;
  }

#ifdef MAXLOGNAME
  /* Some platforms' PAM libraries do not handle login strings that exceed
   * this length.
   */
  if (sftppam_userlen > MAXLOGNAME) {
    pr_log_pri(PR_LOG_NOTICE,
      "PAM(%s): Name exceeds maximum login length (%u)", user, MAXLOGNAME);
    pr_trace_msg(trace_channel, 1,
      "user name '%s' exceeds maximum login length %u, declining", user,
      MAXLOGNAME);
    errno = EPERM;
    return -1;
  }
#endif

  sftppam_user = malloc(sftppam_userlen);
  if (sftppam_user == NULL) {
    pr_log_pri(PR_LOG_CRIT, "Out of memory!");
    exit(1);
  }

  memset(sftppam_user, '\0', sizeof(sftppam_user));
  sstrncpy(sftppam_user, user, sftppam_userlen);

  c = find_config(main_server->conf, CONF_PARAM, "SFTPPAMOptions", FALSE);
  if (c != NULL) {
    sftppam_opts = *((unsigned long *) c->argv[0]);
  }
 
#ifdef SOLARIS2
  /* For Solaris environments, the TTY environment will always be set,
   * in order to workaround a bug (Solaris Bug ID 4250887) where
   * pam_open_session() will crash unless both PAM_RHOST and PAM_TTY are
   * set, and the PAM_TTY setting is at least greater than the length of
   * the string "/dev/".
   */
  sftppam_opts &= ~SFTP_PAM_OPT_NO_TTY;
#endif /* SOLARIS2 */
 
  pr_signals_block();
  PRIVS_ROOT

  res = pam_start(sftppam_service, sftppam_user, &sftppam_conv, &sftppam_pamh);
  if (res != PAM_SUCCESS) {
    free(sftppam_user);
    sftppam_user = NULL;
    sftppam_userlen = 0;

    PRIVS_RELINQUISH
    pr_signals_unblock();

    switch (res) {
      case PAM_SYSTEM_ERR:
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_PAM_VERSION,
          "error starting PAM service: %s", strerror(errno));
        break;

      case PAM_BUF_ERR:
        (void) pr_log_writefile(sftp_logfd, MOD_SFTP_PAM_VERSION,
          "error starting PAM service: Memory buffer error");
        break;
    }

    return -1;
  }

  pam_set_item(sftppam_pamh, PAM_RUSER, sftppam_user);
  pam_set_item(sftppam_pamh, PAM_RHOST, session.c->remote_name);

  if (!(sftppam_opts & SFTP_PAM_OPT_NO_TTY)) {
    memset(sftppam_tty, '\0', sizeof(sftppam_tty));
    snprintf(sftppam_tty, sizeof(sftppam_tty), "/dev/ftpd%02lu",
      (unsigned long) (session.pid ? session.pid : getpid()));
    sftppam_tty[sizeof(sftppam_tty)-1] = '\0';

    pr_trace_msg(trace_channel, 9, "setting PAM_TTY to '%s'", sftppam_tty);
    pam_set_item(sftppam_pamh, PAM_TTY, sftppam_tty);
  }

  PRIVS_RELINQUISH
  pr_signals_unblock();

  /* We need to disable mod_auth_pam, since both mod_auth_pam and us want
   * to talk to the PAM API, just in different fashions.
   */

  c = add_config_param_set(&(main_server->conf), "AuthPAM", 1, NULL);
  c->argv[0] = palloc(c->pool, sizeof(unsigned char));
  *((unsigned char *) c->argv[0]) = FALSE;

  if (pr_auth_remove_auth_only_module("mod_auth_pam.c") < 0) {
    pr_log_pri(PR_LOG_NOTICE, MOD_SFTP_PAM_VERSION
      ": error removing 'mod_auth_pam.c' from the auth-only module list: %s",
      strerror(errno));
  }

  if (pr_auth_add_auth_only_module("mod_sftp_pam.c") < 0) {
    pr_log_pri(PR_LOG_NOTICE, MOD_SFTP_PAM_VERSION
      ": error adding 'mod_sftp_pam.c' to the auth-only module list: %s",
      strerror(errno));
  }

  sftppam_handle_auth = TRUE;

  driver->driver_pool = make_sub_pool(permanent_pool);
  pr_pool_tag(driver->driver_pool, "PAM keyboard-interactive driver pool");

  return 0;
}

static int sftppam_driver_authenticate(sftp_kbdint_driver_t *driver,
    const char *user) {
  int res;

  pr_signals_block();
  PRIVS_ROOT

  res = pam_authenticate(sftppam_pamh, 0);
  if (res != PAM_SUCCESS) {
    switch (res) {
      case PAM_USER_UNKNOWN:
        sftppam_auth_code = PR_AUTH_NOPWD;
        break;

      default:
        sftppam_auth_code = PR_AUTH_BADPWD;
    }

    pr_trace_msg(trace_channel, 1,
      "PAM authentication error (%d) for user '%s': %s", res, user,
      pam_strerror(sftppam_pamh, res));

    PRIVS_RELINQUISH
    pr_signals_unblock();

    errno = EPERM;
    return -1;
  }

  res = pam_acct_mgmt(sftppam_pamh, 0);
  if (res != PAM_SUCCESS) {
    switch (res) {
#ifdef PAM_AUTHTOKEN_REQD
      case PAM_AUTHTOKEN_REQD:
        pr_trace_msg(trace_channel, 8,
          "PAM account mgmt error: PAM_AUTHTOKEN_REQD");
        break;
#endif

      case PAM_ACCT_EXPIRED:
        pr_trace_msg(trace_channel, 8,
          "PAM account mgmt error: PAM_ACCT_EXPIRED");
        sftppam_auth_code = PR_AUTH_DISABLEDPWD;
        break;

#ifdef PAM_ACCT_DISABLED
      case PAM_ACCT_DISABLED:
        pr_trace_msg(trace_channel, 8,
          "PAM account mgmt error: PAM_ACCT_DISABLED");
        sftppam_auth_code = PR_AUTH_DISABLEDPWD;
        break;
#endif

      case PAM_USER_UNKNOWN:
        pr_trace_msg(trace_channel, 8,
          "PAM account mgmt error: PAM_USER_UNKNOWN");
        sftppam_auth_code = PR_AUTH_NOPWD;
        break;

      default:
        sftppam_auth_code = PR_AUTH_BADPWD;
        break;
    }

    pr_trace_msg(trace_channel, 1,
      "PAM account mgmt error (%d) for user '%s': %s", res, user,
      pam_strerror(sftppam_pamh, res));

    PRIVS_RELINQUISH
    pr_signals_unblock();

    errno = EPERM;
    return -1;
  }
 
  res = pam_open_session(sftppam_pamh, 0);
  if (res != PAM_SUCCESS) { 
    sftppam_auth_code = PR_AUTH_DISABLEDPWD;

    pr_trace_msg(trace_channel, 1,
      "PAM session error (%d) for user '%s': %s", res, user,
      pam_strerror(sftppam_pamh, res));

    PRIVS_RELINQUISH
    pr_signals_unblock();

    errno = EPERM;
    return -1;
  }

#ifdef PAM_CRED_ESTABLISH
  res = pam_setcred(sftppam_pamh, PAM_CRED_ESTABLISH);
#else
  res = pam_setcred(sftppam_pamh, PAM_ESTABLISH_CRED);
#endif /* !PAM_CRED_ESTABLISH */
  if (res != PAM_SUCCESS) {
    switch (res) {
      case PAM_CRED_EXPIRED:
        pr_trace_msg(trace_channel, 8,
          "PAM credentials error: PAM_CRED_EXPIRED");
        sftppam_auth_code = PR_AUTH_AGEPWD;
        break;

      case PAM_USER_UNKNOWN:
        pr_trace_msg(trace_channel, 8,
          "PAM credentials error: PAM_USER_UNKNOWN");
        sftppam_auth_code = PR_AUTH_NOPWD;
        break;

      default:
        sftppam_auth_code = PR_AUTH_BADPWD;
        break;
    }

    pr_trace_msg(trace_channel, 1,
      "PAM credentials error (%d) for user '%s': %s", res, user,
      pam_strerror(sftppam_pamh, res));

    PRIVS_RELINQUISH
    pr_signals_unblock();

    errno = EPERM;
    return -1;
  }

  /* XXX Not sure why these platforms have different treatment...? */
#if defined(SOLARIS2) || defined(HPUX10) || defined(HPUX11)
  res = pam_close_session(sftppam_pamh, 0);
  if (sftppam_pamh) {
    pam_end(sftppam_pamh, res);
    sftppam_pamh = NULL;
  }
#endif

  PRIVS_RELINQUISH
  pr_signals_unblock();

  return 0;
}

static int sftppam_driver_close(sftp_kbdint_driver_t *driver) {
  if (driver->driver_pool) {
    destroy_pool(driver->driver_pool);
    driver->driver_pool = NULL;
  }

  if (sftppam_user) {
    free(sftppam_user);
    sftppam_user = NULL;
    sftppam_userlen = 0;
  }

  return 0;
}

/* Auth handlers
 */

MODRET sftppam_auth(cmd_rec *cmd) {
  if (!sftppam_handle_auth) {
    return PR_DECLINED(cmd);
  }

  if (sftppam_auth_code != PR_AUTH_OK) {
    if (sftppam_authoritative)
      return PR_ERROR_INT(cmd, sftppam_auth_code);

    return PR_DECLINED(cmd);
  }

  session.auth_mech = "mod_sftp_pam.c";
  pr_event_register(&sftp_pam_module, "core.exit", sftppam_exit_ev, NULL);
  return PR_HANDLED(cmd);
}

/* Configuration handlers
 */

/* usage: SFTPPAMEngine on|off */
MODRET set_sftppamengine(cmd_rec *cmd) {
  int bool = -1;
  config_rec *c;

  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

  bool = get_boolean(cmd, 1);
  if (bool == -1) {
    CONF_ERROR(cmd, "expected Boolean parameter");
  }

  c = add_config_param(cmd->argv[0], 1, NULL);
  c->argv[0] = pcalloc(c->pool, sizeof(int));
  *((int *) c->argv[0]) = bool;

  return PR_HANDLED(cmd);
}

/* usage: SFTPPAMOptions opt1 ... */
MODRET set_sftppamoptions(cmd_rec *cmd) {
  config_rec *c = NULL;
  register unsigned int i = 0;
  unsigned long opts = 0UL;

  if (cmd->argc-1 == 0)
    CONF_ERROR(cmd, "wrong number of parameters");

  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

  c = add_config_param(cmd->argv[0], 1, NULL);

  for (i = 1; i < cmd->argc; i++) {
    if (strcmp(cmd->argv[i], "NoTTY") == 0) {
      opts |= SFTP_PAM_OPT_NO_TTY;

    } else if (strcmp(cmd->argv[i], "NoInfoMsgs") == 0) {
      opts |= SFTP_PAM_OPT_NO_INFO_MSGS;

    } else {
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown SFTPPAMOption: '",
        cmd->argv[i], "'", NULL));
    }
  }

  c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
  *((unsigned long *) c->argv[0]) = opts;

  return PR_HANDLED(cmd);
}

/* usage: SFTPPAMServiceName name */
MODRET set_sftppamservicename(cmd_rec *cmd) {
  CHECK_ARGS(cmd, 1);
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

  add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
  return PR_HANDLED(cmd);
}

/* Event Handlers
 */

static void sftppam_exit_ev(const void *event_data, void *user_data) {
  int res;

  /* Close the PAM session */

  if (sftppam_pamh == NULL)
    return;

#ifdef PAM_CRED_DELETE
  res = pam_setcred(sftppam_pamh, PAM_CRED_DELETE);
#else
  res = pam_setcred(sftppam_pamh, PAM_DELETE_CRED);
#endif
  if (res != PAM_SUCCESS) {
    pr_trace_msg(trace_channel, 9, "PAM error setting PAM_DELETE_CRED: %s",
      pam_strerror(sftppam_pamh, res));
  }

  res = pam_close_session(sftppam_pamh, PAM_SILENT);
  pam_end(sftppam_pamh, res);
  sftppam_pamh = NULL;

  if (sftppam_user != NULL) {
    free(sftppam_user);
    sftppam_user = NULL;
    sftppam_userlen = 0;
  }
}

#if defined(PR_SHARED_MODULE)
static void sftppam_mod_unload_ev(const void *event_data, void *user_data) {
  if (strcmp("mod_sftp_pam.c", (const char *) event_data) == 0) {
    if (sftppam_user) {
      free(sftppam_user);
      sftppam_user = NULL;
      sftppam_userlen = 0;
    }

    sftp_kbdint_unregister_driver("pam");
    pr_event_unregister(&sftp_pam_module, NULL, NULL);
  }
}
#endif /* !PR_SHARED_MODULE */

/* Initialization functions
 */

static int sftppam_init(void) {
#if defined(PR_SHARED_MODULE)
  pr_event_register(&sftp_pam_module, "core.module-unload",
    sftppam_mod_unload_ev, NULL);
#endif /* !PR_SHARED_MODULE */

  /* Prepare our driver. */
  memset(&sftppam_driver, 0, sizeof(sftppam_driver));
  sftppam_driver.open = sftppam_driver_open;
  sftppam_driver.authenticate = sftppam_driver_authenticate;
  sftppam_driver.close = sftppam_driver_close;

  /* Register ourselves with mod_sftp. */
  if (sftp_kbdint_register_driver("pam", &sftppam_driver) < 0) {
    pr_log_pri(PR_LOG_NOTICE, MOD_SFTP_PAM_VERSION
      ": notice: error registering 'keyboard-interactive' driver: %s",
      strerror(errno));
    return -1;
  }

  return 0;
}

static int sftppam_sess_init(void) {
  config_rec *c;

  c = find_config(main_server->conf, CONF_PARAM, "SFTPPAMEngine", FALSE);
  if (c) {
    int engine;

    engine = *((int *) c->argv[0]);
    if (!engine) {
      (void) pr_log_writefile(sftp_logfd, MOD_SFTP_PAM_VERSION,
        "disabled by SFTPPAMEngine setting, unregistered 'pam' driver");
      sftp_kbdint_unregister_driver("pam");
      return 0;
    }
  }

  c = find_config(main_server->conf, CONF_PARAM, "SFTPPAMServiceName", FALSE);
  if (c) {
    sftppam_service = c->argv[0];
  }

  pr_trace_msg(trace_channel, 8, "using PAM service name '%s'",
    sftppam_service);

  return 0;
}

/* Module API tables
 */

static conftable sftppam_conftab[] = {
  { "SFTPPAMEngine",		set_sftppamengine,		NULL },
  { "SFTPPAMOptions",		set_sftppamoptions,		NULL },
  { "SFTPPAMServiceName",	set_sftppamservicename,		NULL },
  { NULL }
};

static authtable sftppam_authtab[] = {
  { 0, "auth", sftppam_auth },
  { 0, NULL, NULL }
};

module sftp_pam_module = {
  NULL, NULL,

  /* Module API version 2.0 */
  0x20,

  /* Module name */
  "sftp_pam",

  /* Module configuration handler table */
  sftppam_conftab,

  /* Module command handler table */
  NULL,

  /* Module authentication handler table */
  sftppam_authtab,

  /* Module initialization function */
  sftppam_init,

  /* Session initialization function */
  sftppam_sess_init,

  /* Module version */
  MOD_SFTP_PAM_VERSION
};