Coverage Report

Created: 2026-08-22 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_auth_unix.c
Line
Count
Source
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5
 * Copyright (c) 2001-2026 The ProFTPD Project team
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21
 * and other respective copyright holders give permission to link this program
22
 * with OpenSSL, and distribute the resulting executable, without including
23
 * the source code for OpenSSL in the source distribution.
24
 */
25
26
/* Unix authentication module for ProFTPD */
27
28
#include "conf.h"
29
30
/* AIX has some rather stupid function prototype inconsistencies between
31
 * their crypt.h and stdlib.h's setkey() declarations.  *sigh*
32
 */
33
#if defined(HAVE_CRYPT_H) && !defined(AIX4) && !defined(AIX5)
34
# include <crypt.h>
35
#endif
36
37
#ifdef PR_USE_SHADOW
38
# ifdef HAVE_SHADOW_H
39
#   include <shadow.h>
40
# endif
41
#endif
42
43
#ifdef HAVE_SYS_SECURITY_H
44
# include <sys/security.h>
45
#endif
46
47
#ifdef HAVE_KRB_H
48
# include <krb.h>
49
#endif
50
51
#ifdef HAVE_LOGIN_H
52
# include <login.h>
53
#endif
54
55
#if defined(HAVE_HPSECURITY_H) || defined(HPUX10) || defined(HPUX11)
56
# include <hpsecurity.h>
57
# ifndef COMSEC
58
#  define COMSEC 1
59
# endif /* !COMSEC */
60
#endif /* HAVE_HPSECURITY_H or HPUX10 or HPUX11 */
61
62
#if defined(HAVE_PROT_H) || defined(COMSEC)
63
# include <prot.h>
64
#endif
65
66
#ifdef HAVE_USERSEC_H
67
# include <usersec.h>
68
#endif
69
70
#ifdef PR_USE_SIA
71
# ifdef HAVE_SIA_H
72
#  include <sia.h>
73
# endif
74
# ifdef HAVE_SIAD_H
75
#  include <siad.h>
76
# endif
77
#endif /* PR_USE_SIA */
78
79
#ifdef CYGWIN
80
typedef void *HANDLE;
81
typedef unsigned long DWORD;
82
# define INVALID_HANDLE_VALUE (HANDLE)(-1)
83
# define WINAPI __stdcall
84
DWORD WINAPI GetVersion(void);
85
extern HANDLE cygwin_logon_user (const struct passwd *, const char *);
86
extern void cygwin_set_impersonation_token (const HANDLE);
87
#endif /* CYGWIN */
88
89
#ifdef SETGRENT_VOID
90
0
# define RETSETGRENTTYPE  void
91
#else
92
# define RETSETGRENTTYPE  int
93
#endif
94
95
#include "privs.h"
96
97
#ifdef HAVE__PW_STAYOPEN
98
extern int _pw_stayopen;
99
#endif
100
101
module auth_unix_module;
102
103
static const char *pwdfname = "/etc/passwd";
104
static FILE *pwdf = NULL;
105
106
static const char *grpfname = "/etc/group";
107
static FILE *grpf = NULL;
108
109
static int unix_persistent_passwd = FALSE;
110
static const char *trace_channel = "auth.unix";
111
112
#undef PASSWD
113
0
#define PASSWD    pwdfname
114
#undef GROUP
115
0
#define GROUP   grpfname
116
117
#ifdef PR_USE_SHADOW
118
119
/* Shadow password entries are stored as number of days, not seconds
120
 * and are -1 if unused
121
 */
122
0
#define SP_CVT_DAYS(x)  ((x) == (time_t)-1 ? (x) : ((x) * 86400))
123
124
#endif /* PR_USE_SHADOW */
125
126
/* mod_auth_unix option flags */
127
0
#define AUTH_UNIX_OPT_AIX_NO_RLOGIN   0x0001
128
0
#define AUTH_UNIX_OPT_NO_GETGROUPLIST   0x0002
129
0
#define AUTH_UNIX_OPT_MAGIC_TOKEN_CHROOT  0x0004
130
0
#define AUTH_UNIX_OPT_NO_INITGROUPS   0x0008
131
0
#define AUTH_UNIX_OPT_AIX_NO_AUTHENTICATE 0x0010
132
133
static unsigned long auth_unix_opts = 0UL;
134
135
/* Necessary prototypes */
136
static void auth_unix_exit_ev(const void *, void *);
137
static int auth_unix_sess_init(void);
138
139
0
static void p_setpwent(void) {
140
0
  if (pwdf != NULL) {
141
0
    rewind(pwdf);
142
143
0
  } else {
144
0
    pwdf = fopen(PASSWD, "r");
145
0
    if (pwdf == NULL) {
146
0
      pr_log_pri(PR_LOG_ERR, "unable to open password file %s for reading: %s",
147
0
        PASSWD, strerror(errno));
148
0
    }
149
0
  }
150
0
}
151
152
0
static void p_endpwent(void) {
153
0
  if (pwdf != NULL) {
154
0
    fclose(pwdf);
155
0
    pwdf = NULL;
156
0
  }
157
0
}
158
159
0
static RETSETGRENTTYPE p_setgrent(void) {
160
0
  if (grpf != NULL) {
161
0
    rewind(grpf);
162
163
0
  } else {
164
0
    grpf = fopen(GROUP, "r");
165
0
    if (grpf == NULL) {
166
0
      pr_log_pri(PR_LOG_ERR, "unable to open group file %s for reading: %s",
167
0
        GROUP, strerror(errno));
168
0
    }
169
0
  }
170
171
#ifndef SETGRENT_VOID
172
  return 0;
173
#endif
174
0
}
175
176
0
static void p_endgrent(void) {
177
0
  if (grpf != NULL) {
178
0
    fclose(grpf);
179
0
    grpf = NULL;
180
0
  }
181
0
}
182
183
0
static struct passwd *p_getpwent(void) {
184
0
  if (pwdf == NULL) {
185
0
    p_setpwent();
186
0
  }
187
188
0
  if (pwdf == NULL) {
189
0
    return NULL;
190
0
  }
191
192
0
  return fgetpwent(pwdf);
193
0
}
194
195
0
static struct group *p_getgrent(void) {
196
0
  if (grpf == NULL) {
197
0
    p_setgrent();
198
0
  }
199
200
0
  if (grpf == NULL) {
201
0
    return NULL;
202
0
  }
203
204
0
  return fgetgrent(grpf);
205
0
}
206
207
0
static struct passwd *p_getpwnam(const char *name) {
208
0
  struct passwd *pw = NULL;
209
0
  size_t name_len;
210
211
0
  p_setpwent();
212
0
  name_len = strlen(name);
213
214
0
  while ((pw = p_getpwent()) != NULL) {
215
0
    pr_signals_handle();
216
217
0
    if (strncmp(name, pw->pw_name, name_len + 1) == 0) {
218
0
      break;
219
0
    }
220
0
  }
221
222
0
  return pw;
223
0
}
224
225
0
static struct passwd *p_getpwuid(uid_t uid) {
226
0
  struct passwd *pw = NULL;
227
228
0
  p_setpwent();
229
0
  while ((pw = p_getpwent()) != NULL) {
230
0
    pr_signals_handle();
231
232
0
    if (pw->pw_uid == uid) {
233
0
      break;
234
0
    }
235
0
  }
236
237
0
  return pw;
238
0
}
239
240
0
static struct group *p_getgrnam(const char *name) {
241
0
  struct group *gr = NULL;
242
0
  size_t name_len;
243
244
0
  p_setgrent();
245
0
  name_len = strlen(name);
246
247
0
  while ((gr = p_getgrent()) != NULL) {
248
0
    pr_signals_handle();
249
250
0
    if (strncmp(name, gr->gr_name, name_len + 1) == 0) {
251
0
      break;
252
0
    }
253
0
  }
254
255
0
  return gr;
256
0
}
257
258
0
static struct group *p_getgrgid(gid_t gid) {
259
0
  struct group *gr = NULL;
260
261
0
  p_setgrent();
262
0
  while ((gr = p_getgrent()) != NULL) {
263
0
    pr_signals_handle();
264
265
0
    if (gr->gr_gid == gid) {
266
0
      break;
267
0
    }
268
0
  }
269
270
0
  return gr;
271
0
}
272
273
0
MODRET pw_setpwent(cmd_rec *cmd) {
274
0
  if (unix_persistent_passwd) {
275
0
    p_setpwent();
276
277
0
  } else {
278
0
    setpwent();
279
0
  }
280
281
0
  return PR_DECLINED(cmd);
282
0
}
283
284
0
MODRET pw_endpwent(cmd_rec *cmd) {
285
0
  if (unix_persistent_passwd) {
286
0
    p_endpwent();
287
288
0
  } else {
289
0
    endpwent();
290
0
  }
291
292
0
  return PR_DECLINED(cmd);
293
0
}
294
295
0
MODRET pw_setgrent(cmd_rec *cmd) {
296
0
  if (unix_persistent_passwd) {
297
0
    p_setgrent();
298
299
0
  } else {
300
0
    setgrent();
301
0
  }
302
303
0
  return PR_DECLINED(cmd);
304
0
}
305
306
0
MODRET pw_endgrent(cmd_rec *cmd) {
307
0
  if (unix_persistent_passwd) {
308
0
    p_endgrent();
309
310
0
  } else {
311
0
    endgrent();
312
0
  }
313
314
0
  return PR_DECLINED(cmd);
315
0
}
316
317
0
MODRET pw_getgrent(cmd_rec *cmd) {
318
0
  struct group *gr = NULL;
319
320
0
  if (unix_persistent_passwd) {
321
0
    gr = p_getgrent();
322
323
0
  } else {
324
0
    gr = getgrent();
325
0
  }
326
327
0
  return gr ? mod_create_data(cmd, gr) : PR_DECLINED(cmd);
328
0
}
329
330
0
MODRET pw_getpwent(cmd_rec *cmd) {
331
0
  struct passwd *pw = NULL;
332
333
0
  if (unix_persistent_passwd) {
334
0
    pw = p_getpwent();
335
336
0
  } else {
337
0
    pw = getpwent();
338
0
  }
339
340
0
  return pw ? mod_create_data(cmd, pw) : PR_DECLINED(cmd);
341
0
}
342
343
0
MODRET pw_getpwuid(cmd_rec *cmd) {
344
0
  struct passwd *pw = NULL;
345
0
  uid_t uid;
346
347
0
  uid = *((uid_t *) cmd->argv[0]);
348
0
  if (unix_persistent_passwd) {
349
0
    pw = p_getpwuid(uid);
350
351
0
  } else {
352
0
    pw = getpwuid(uid);
353
0
  }
354
355
0
  return pw ? mod_create_data(cmd, pw) : PR_DECLINED(cmd);
356
0
}
357
358
0
MODRET pw_getpwnam(cmd_rec *cmd) {
359
0
  struct passwd *pw = NULL;
360
0
  const char *name;
361
362
0
  name = cmd->argv[0];
363
0
  if (unix_persistent_passwd) {
364
0
    pw = p_getpwnam(name);
365
366
0
  } else {
367
0
    pw = getpwnam(name);
368
0
  }
369
370
0
  if (pw == NULL) {
371
0
    return PR_DECLINED(cmd);
372
0
  }
373
374
0
  if (auth_unix_opts & AUTH_UNIX_OPT_MAGIC_TOKEN_CHROOT) {
375
0
    char *home_dir, *ptr;
376
377
    /* Here is where we do the "magic token" chroot monstrosity inflicted
378
     * on the world by wu-ftpd.
379
     *
380
     * If the magic token '/./' appears in the user's home directory, the
381
     * directory portion before the token is the directory to use for
382
     * the chroot; the directory portion after the token is the directory
383
     * to use for the initial chdir.
384
     */
385
386
0
    home_dir = pstrdup(cmd->tmp_pool, pw->pw_dir);
387
388
    /* We iterate through the home directory string since it is possible
389
     * for the '.' character to appear without it being part of the magic
390
     * token.
391
     */
392
0
    ptr = strchr(home_dir, '.');
393
0
    while (ptr != NULL) {
394
0
      pr_signals_handle();
395
396
      /* If we're at the start of the home directory string, stop looking:
397
       * this home directory is not really valid anyway.
398
       */
399
0
      if (ptr == home_dir) {
400
0
        break;
401
0
      }
402
403
      /* Back up one character. */
404
0
      ptr--;
405
406
      /* If we're at the start of the home directory now, stop looking:
407
       * this home directory cannot contain a valid magic token.  I.e.
408
       *
409
       * /./home/foo
410
       *
411
       * cannot be valid, as there is no directory portion before the
412
       * token.
413
       */
414
0
      if (ptr == home_dir) {
415
0
        break;
416
0
      }
417
418
0
      if (strncmp(ptr, "/./", 3) == 0) {
419
0
        char *default_chdir;
420
0
        config_rec *c;
421
422
0
        *ptr = '\0';
423
0
        default_chdir = pstrdup(cmd->tmp_pool, ptr + 2);
424
425
        /* In order to make sure that this user is chrooted to this
426
         * directory, we remove all DefaultRoot directives and add a new
427
         * one.  Same for the DefaultChdir directive.
428
         */
429
430
0
        (void) remove_config(main_server->conf, "DefaultRoot", FALSE);
431
0
        c = add_config_param_set(&main_server->conf, "DefaultRoot", 1, NULL);
432
0
        c->argv[0] = pstrdup(c->pool, home_dir);
433
434
0
        (void) remove_config(main_server->conf, "DefaultChdir", FALSE);
435
0
        c = add_config_param_set(&main_server->conf, "DefaultChdir", 1, NULL);
436
0
        c->argv[0] = pstrdup(c->pool, default_chdir);
437
438
0
        pr_log_debug(DEBUG9, "AuthUnixOption magicTokenChroot: "
439
0
          "found magic token in '%s', using 'DefaultRoot %s' and "
440
0
          "'DefaultChdir %s'", pw->pw_dir, home_dir, default_chdir);
441
442
        /* We need to use a long-lived memory pool for overwriting the
443
         * normal home directory.
444
         */
445
0
        pw->pw_dir = pstrdup(session.pool, home_dir);
446
447
0
        break;
448
0
      }
449
450
0
      ptr = strchr(ptr + 2, '.');
451
0
    }
452
0
  }
453
454
0
  return pw ? mod_create_data(cmd, pw) : PR_DECLINED(cmd);
455
0
}
456
457
0
MODRET pw_getgrnam(cmd_rec *cmd) {
458
0
  struct group *gr = NULL;
459
0
  const char *name;
460
461
0
  name = cmd->argv[0];
462
0
  if (unix_persistent_passwd) {
463
0
    gr = p_getgrnam(name);
464
465
0
  } else {
466
0
    gr = getgrnam(name);
467
0
  }
468
469
0
  return gr ? mod_create_data(cmd, gr) : PR_DECLINED(cmd);
470
0
}
471
472
0
MODRET pw_getgrgid(cmd_rec *cmd) {
473
0
  struct group *gr = NULL;
474
0
  gid_t gid;
475
476
0
  gid = *((gid_t *) cmd->argv[0]);
477
0
  if (unix_persistent_passwd) {
478
0
    gr = p_getgrgid(gid);
479
480
0
  } else {
481
0
    gr = getgrgid(gid);
482
0
  }
483
484
0
  return gr ? mod_create_data(cmd, gr) : PR_DECLINED(cmd);
485
0
}
486
487
#ifdef PR_USE_SHADOW
488
static char *get_pwd_info(pool *p, const char *u, time_t *lstchg, time_t *min,
489
0
    time_t *max, time_t *warn, time_t *inact, time_t *expire) {
490
0
  struct spwd *sp;
491
0
  char *cpw = NULL;
492
493
0
  pr_trace_msg(trace_channel, 7,
494
0
    "looking up user '%s' via Unix shadow mechanism", u);
495
496
0
  PRIVS_ROOT
497
0
#ifdef HAVE_SETSPENT
498
0
  setspent();
499
0
#endif /* HAVE_SETSPENT */
500
501
0
  sp = getspnam(u);
502
0
  if (sp != NULL) {
503
0
    cpw = pstrdup(p, sp->sp_pwdp);
504
505
0
    if (lstchg != NULL) {
506
0
      *lstchg = SP_CVT_DAYS(sp->sp_lstchg);
507
0
    }
508
509
0
    if (min != NULL) {
510
0
      *min = SP_CVT_DAYS(sp->sp_min);
511
0
    }
512
513
0
    if (max != NULL) {
514
0
      *max = SP_CVT_DAYS(sp->sp_max);
515
0
    }
516
517
0
#ifdef HAVE_SPWD_SP_WARN
518
0
    if (warn != NULL) {
519
0
      *warn = SP_CVT_DAYS(sp->sp_warn);
520
0
    }
521
0
#endif /* HAVE_SPWD_SP_WARN */
522
523
0
#ifdef HAVE_SPWD_SP_INACT
524
0
    if (inact != NULL) {
525
0
      *inact = SP_CVT_DAYS(sp->sp_inact);
526
0
    }
527
0
#endif /* HAVE_SPWD_SP_INACT */
528
529
0
#ifdef HAVE_SPWD_SP_EXPIRE
530
0
    if (expire != NULL) {
531
0
      *expire = SP_CVT_DAYS(sp->sp_expire);
532
0
    }
533
0
#endif /* HAVE_SPWD_SP_EXPIRE */
534
535
0
  } else {
536
0
    pr_log_debug(DEBUG5, "mod_auth_unix: getspnam(3) for user '%s' error: %s",
537
0
      u, strerror(errno));
538
0
  }
539
540
#ifdef PR_USE_AUTO_SHADOW
541
  if (sp == NULL) {
542
    struct passwd *pw;
543
544
    pr_trace_msg(trace_channel, 7,
545
      "looking up user '%s' via Unix autoshadow mechanism", u);
546
547
    endspent();
548
    PRIVS_RELINQUISH
549
550
    pw = getpwnam(u);
551
    if (pw != NULL) {
552
      cpw = pstrdup(p, pw->pw_passwd);
553
554
      if (lstchg != NULL) {
555
        *lstchg = (time_t) -1;
556
      }
557
558
      if (min != NULL) {
559
        *min = (time_t) -1;
560
      }
561
562
      if (max != NULL) {
563
        *max = (time_t) -1;
564
      }
565
566
      if (warn != NULL) {
567
        *warn = (time_t) -1;
568
      }
569
570
      if (inact != NULL) {
571
        *inact = (time_t) -1;
572
      }
573
574
      if (expire != NULL) {
575
        *expire = (time_t) -1;
576
      }
577
578
    } else {
579
      pr_log_debug(DEBUG5, "mod_auth_unix: getpwnam(3) for user '%s' error: %s",
580
        u, strerror(errno));
581
    }
582
583
  } else {
584
    PRIVS_RELINQUISH
585
  }
586
#else
587
0
  endspent();
588
0
  PRIVS_RELINQUISH
589
0
#endif /* PR_USE_AUTO_SHADOW */
590
591
0
  return cpw;
592
0
}
593
594
#else /* PR_USE_SHADOW */
595
596
static char *get_pwd_info(pool *p, const char *u, time_t *lstchg, time_t *min,
597
    time_t *max, time_t *warn, time_t *inact, time_t *expire) {
598
  char *cpw = NULL;
599
#if defined(HAVE_GETPRPWENT) || defined(COMSEC)
600
  struct pr_passwd *prpw;
601
#endif
602
#if !defined(HAVE_GETPRPWENT) || defined(COMSEC)
603
  struct passwd *pw;
604
#endif
605
606
 /* Some platforms (i.e. BSD) provide "transparent" shadowing, which
607
  * requires that we are root in order to have the password member
608
  * filled in.
609
  */
610
611
  pr_trace_msg(trace_channel, 7,
612
    "looking up user '%s' via normal Unix mechanism", u);
613
614
  PRIVS_ROOT
615
#if !defined(HAVE_GETPRPWENT) || defined(COMSEC)
616
# ifdef COMSEC
617
  if (!iscomsec()) {
618
# endif /* COMSEC */
619
  endpwent();
620
#if defined(BSDI3) || defined(BSDI4)
621
  /* endpwent() seems to be buggy on BSDI3.1 (is this true for 4.0?)
622
   * setpassent(0) _seems_ to do the same thing, however this conflicts
623
   * with the man page documented behavior.  Argh, why do all the bsds
624
   * have to be different in this area (except OpenBSD, grin).
625
   */
626
  setpassent(0);
627
#else /* BSDI3 || BSDI4 */
628
  setpwent();
629
#endif /* BSDI3 || BSDI4 */
630
631
  pw = getpwnam(u);
632
  if (pw) {
633
    cpw = pstrdup(p, pw->pw_passwd);
634
635
    if (lstchg)
636
      *lstchg = (time_t) -1;
637
638
    if (min)
639
      *min = (time_t) -1;
640
641
    if (max)
642
      *max = (time_t) -1;
643
644
    if (warn)
645
      *warn = (time_t) -1;
646
647
    if (inact)
648
      *inact = (time_t) -1;
649
650
    if (expire)
651
      *expire = (time_t) -1;
652
653
  } else {
654
    pr_log_debug(DEBUG5, "mod_auth_unix: getpwnam(3) for user '%s' error: %s",
655
      u, strerror(errno));
656
  }
657
658
  endpwent();
659
#ifdef COMSEC
660
  } else {
661
#endif /* COMSEC */
662
#endif /* !HAVE_GETPRWENT or COMSEC */
663
664
#if defined(HAVE_GETPRPWENT) || defined(COMSEC)
665
  endprpwent();
666
  setprpwent();
667
668
  prpw = getprpwnam((char *) u);
669
670
  if (prpw) {
671
    cpw = pstrdup(p, prpw->ufld.fd_encrypt);
672
673
    if (lstchg)
674
      *lstchg = (time_t) -1;
675
676
    if (min)
677
      *min = prpw->ufld.fd_min;
678
679
    if (max)
680
      *max = (time_t) -1;
681
682
    if (warn)
683
      *warn = (time_t) -1;
684
685
    if (inact)
686
      *inact = (time_t) -1;
687
688
    if (expire)
689
      *expire = prpw->ufld.fd_expire;
690
  }
691
692
  endprpwent();
693
#ifdef COMSEC
694
  }
695
#endif /* COMSEC */
696
#endif /* HAVE_GETPRPWENT or COMSEC */
697
698
  PRIVS_RELINQUISH
699
#if defined(BSDI3) || defined(BSDI4)
700
  setpassent(1);
701
#endif
702
  return cpw;
703
}
704
705
#endif /* PR_USE_SHADOW */
706
707
/* High-level auth handlers
708
 */
709
710
/* cmd->argv[0] : user name
711
 * cmd->argv[1] : cleartext password
712
 */
713
714
0
MODRET pw_auth(cmd_rec *cmd) {
715
0
  int res;
716
0
  time_t now;
717
0
  char *cleartxt_passwd;
718
0
  time_t lstchg = -1, max = -1, inact = -1, expire = -1;
719
0
  const char *name;
720
0
  size_t cleartxt_passwdlen;
721
722
0
  name = cmd->argv[0];
723
724
0
  cleartxt_passwd = get_pwd_info(cmd->tmp_pool, name, &lstchg, NULL, &max,
725
0
    NULL, &inact, &expire);
726
0
  if (cleartxt_passwd == NULL) {
727
0
    return PR_DECLINED(cmd);
728
0
  }
729
730
0
  res = pr_auth_check(cmd->tmp_pool, cleartxt_passwd, cmd->argv[0],
731
0
    cmd->argv[1]);
732
0
  cleartxt_passwdlen = strlen(cleartxt_passwd);
733
0
  pr_memscrub(cleartxt_passwd, cleartxt_passwdlen);
734
735
0
  if (res < PR_AUTH_OK) {
736
0
    return PR_ERROR_INT(cmd, res);
737
0
  }
738
739
0
  time(&now);
740
741
0
  if (lstchg > (time_t) 0 &&
742
0
      max > (time_t) 0 &&
743
0
      inact > (time_t) 0) {
744
0
    if (now > (lstchg + max + inact)) {
745
0
      pr_trace_msg("auth", 7, "password last changed for user '%s' on %s + "
746
0
        "maximum time between password changes (%lu secs) + "
747
0
        "inactivity time after expiration (%lu secs) = %s, "
748
0
        "rejecting authorization with AGEPWD", name, pr_strtime(lstchg),
749
0
        (unsigned long) max, (unsigned long) inact,
750
0
        pr_strtime(lstchg + max + inact));
751
0
      return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
752
0
    }
753
0
  }
754
755
0
  if (expire > (time_t) 0 &&
756
0
      now > expire) {
757
0
    pr_trace_msg("auth", 7, "password expired for user '%s' expired on %s, "
758
0
      "rejecting authorization with DISABLEDPWD", name, pr_strtime(now));
759
0
    return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
760
0
  }
761
762
0
  session.auth_mech = "mod_auth_unix.c";
763
0
  return PR_HANDLED(cmd);
764
0
}
765
766
0
MODRET pw_authz(cmd_rec *cmd) {
767
0
  time_t now;
768
0
  char *user, *cleartxt_passwd;
769
0
  time_t lstchg = -1, max = -1, inact = -1, expire = -1;
770
0
  size_t cleartxt_passwdlen;
771
772
0
  user = cmd->argv[0];
773
774
0
  cleartxt_passwd = get_pwd_info(cmd->tmp_pool, user, &lstchg, NULL, &max,
775
0
    NULL, &inact, &expire);
776
0
  if (cleartxt_passwd == NULL) {
777
0
    pr_trace_msg(trace_channel, 3,
778
0
      "no password information found for user '%.100s'", user);
779
0
    return PR_DECLINED(cmd);
780
0
  }
781
782
0
  cleartxt_passwdlen = strlen(cleartxt_passwd);
783
0
  pr_memscrub(cleartxt_passwd, cleartxt_passwdlen);
784
785
0
  time(&now);
786
787
0
  if (lstchg > (time_t) 0 &&
788
0
      max > (time_t) 0 &&
789
0
      inact > (time_t) 0) {
790
0
    if (now > (lstchg + max + inact)) {
791
0
      pr_log_auth(LOG_WARNING,
792
0
        "account for user '%.100s' disabled due to inactivity", user);
793
0
      return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
794
0
    }
795
0
  }
796
797
0
  if (expire > (time_t) 0 &&
798
0
      now > expire) {
799
0
    pr_log_auth(LOG_WARNING,
800
0
      "account for user '%.100s' disabled due to password expiration", user);
801
0
    return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
802
0
  }
803
804
  /* XXX Any other implementations here? */
805
806
#ifdef HAVE_LOGINRESTRICTIONS
807
  if (!(auth_unix_opts & AUTH_UNIX_OPT_AIX_NO_RLOGIN)) {
808
    int res, xerrno, code = 0;
809
    char *reason = NULL;
810
811
    /* Check for account login restrictions and such using AIX-specific
812
     * functions.
813
     */
814
    PRIVS_ROOT
815
    res = loginrestrictions(user, S_RLOGIN, NULL, &reason);
816
    xerrno = errno;
817
    PRIVS_RELINQUISH
818
819
    if (res != 0) {
820
      if (reason != NULL &&
821
          *reason) {
822
        pr_trace_msg(trace_channel, 9,
823
          "AIX loginrestrictions() failed for user '%s': %.100s", user, reason);
824
        pr_log_auth(LOG_WARNING, "login restricted for user '%s': %.100s",
825
          user, reason);
826
      }
827
828
      pr_log_auth(LOG_NOTICE,
829
        "AIX loginrestrictions() failed for user '%s': %s", user,
830
        strerror(xerrno));
831
832
      return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
833
    }
834
835
    PRIVS_ROOT
836
    code = passwdexpired(user, &reason);
837
    PRIVS_RELINQUISH
838
839
    switch (code) {
840
      case 0:
841
        /* Password not expired for user */
842
        break;
843
844
      case 1:
845
        /* Password expired and needs to be changed */
846
        pr_log_auth(LOG_WARNING, "password expired for user '%s': %.100s",
847
          cmd->argv[0], reason);
848
        return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
849
850
      case 2:
851
        /* Password expired, requires sysadmin to change it */
852
        pr_log_auth(LOG_WARNING,
853
          "password expired for user '%s', requires sysadmin intervention: "
854
          "%.100s", user, reason);
855
        return PR_ERROR_INT(cmd, PR_AUTH_AGEPWD);
856
857
      default:
858
        /* Other error */
859
        pr_log_auth(LOG_WARNING, "AIX passwdexpired() failed for user '%s': "
860
          "%.100s", user, reason);
861
        return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
862
    }
863
  }
864
#endif /* !HAVE_LOGINRESTRICTIONS */
865
866
0
  return PR_HANDLED(cmd);
867
0
}
868
869
/* cmd->argv[0] = hashed password
870
 * cmd->argv[1] = user
871
 * cmd->argv[2] = cleartext
872
 */
873
874
0
MODRET pw_check(cmd_rec *cmd) {
875
0
  const char *cpw = cmd->argv[0];
876
0
  const char *pw = cmd->argv[2];
877
0
  modret_t *mr = NULL;
878
0
  cmd_rec *cmd2 = NULL;
879
0
  char *crypted_text = NULL;
880
881
#ifdef PR_USE_SIA
882
  SIAENTITY *ent = NULL;
883
  int res = SIASUCCESS;
884
  char *info[2];
885
  struct passwd *pwd;
886
  char *user = NULL;
887
#endif
888
889
#ifdef COMSEC
890
  if (iscomsec()) {
891
    if (strcmp(bigcrypt((char *) pw, (char *) cpw), cpw) != 0) {
892
      return PR_DECLINED(cmd);
893
    }
894
895
  } else {
896
#endif /* COMSEC */
897
898
#ifdef PR_USE_SIA
899
  /* Use Tru64's C2 SIA subsystem for authenticating this user. */
900
  user = cmd->argv[1];
901
902
  pr_log_auth(PR_LOG_INFO, "using SIA for user '%s'", user);
903
904
  info[0] = "ProFTPD";
905
  info[1] = NULL;
906
907
  /* Prepare the SIA subsystem. */
908
  PRIVS_ROOT
909
  res = sia_ses_init(&ent, 1, info, NULL, user, NULL, 0, NULL);
910
  if (res != SIASUCCESS) {
911
    pr_log_auth(PR_LOG_NOTICE, "sia_ses_init() returned %d for user '%s'", res,
912
      user);
913
914
  } else {
915
916
    res = sia_ses_authent(NULL, pw, ent);
917
    if (res != SIASUCCESS) {
918
      sia_ses_release(&ent);
919
      PRIVS_RELINQUISH
920
      pr_log_auth(PR_LOG_NOTICE, "sia_ses_authent() returned %d for user '%s'",
921
        res, user);
922
      return PR_ERROR(cmd);
923
    }
924
925
    res = sia_ses_estab(NULL, ent);
926
    if (res != SIASUCCESS) {
927
      PRIVS_RELINQUISH
928
      pr_log_auth(PR_LOG_NOTICE, "sia_ses_estab() returned %d for user '%s'",
929
        res, user);
930
      return PR_ERROR(cmd);
931
    }
932
933
    res = sia_ses_release(&ent);
934
    if (res != SIASUCCESS) {
935
      PRIVS_RELINQUISH
936
      pr_log_auth(PR_LOG_NOTICE, "sia_ses_release() returned %d", res);
937
      return PR_ERROR(cmd);
938
    }
939
  }
940
  PRIVS_RELINQUISH
941
942
  if (res != SIASUCCESS) {
943
    return PR_DECLINED(cmd);
944
  }
945
946
#else /* !PR_USE_SIA */
947
948
# ifdef CYGWIN
949
  /* We have to do special Windows NT voodoo with Cygwin in order to be
950
   * able to switch UID/GID. More info at
951
   * http://cygwin.com/cygwin-ug-net/ntsec.html#NTSEC-SETUID
952
   */
953
  if (GetVersion() < 0x80000000) {
954
    struct passwd *pwent = NULL;
955
    HANDLE token;
956
957
    /* A struct passwd * is needed.  To look one up via pw_getpwnam(), though,
958
     * we'll need a cmd_rec.
959
     */
960
    cmd2 = pr_cmd_alloc(cmd->tmp_pool, 1, cmd->argv[1]);
961
962
    /* pw_getpwnam() returns a MODRET, so we need to handle that.  Yes, this
963
     * might have been easier if we'd used pr_auth_getpwnam(), but that would
964
     * dispatch through other auth modules, which is _not_ what we want.
965
     */
966
    mr = pw_getpwnam(cmd2);
967
968
    /* Note: we don't handle the case where pw_getpwnam() returns anything
969
     * other than HANDLED at the moment.
970
     */
971
972
    if (MODRET_ISHANDLED(mr) &&
973
        MODRET_HASDATA(mr)) {
974
      pwent = mr->data;
975
976
      token = cygwin_logon_user((const struct passwd *) pwent, pw);
977
      if (token == INVALID_HANDLE_VALUE) {
978
        pr_log_pri(PR_LOG_NOTICE, "error authenticating Cygwin user: %s",
979
          strerror(errno));
980
        return PR_DECLINED(cmd);
981
      }
982
983
      cygwin_set_impersonation_token(token);
984
985
    } else {
986
      return PR_DECLINED(cmd);
987
    }
988
989
  } else {
990
# endif /* CYGWIN */
991
992
#ifdef HAVE_AUTHENTICATE
993
  if (!(auth_unix_opts & AUTH_UNIX_OPT_AIX_NO_AUTHENTICATE)) {
994
    int res, xerrno, reenter = 0;
995
    char *user, *passwd, *msg = NULL;
996
997
    user = cmd->argv[1];
998
    passwd = cmd->argv[2];
999
1000
    pr_trace_msg(trace_channel, 9, "calling AIX authenticate() for user '%s'",
1001
      user);
1002
1003
    PRIVS_ROOT
1004
    do {
1005
      res = authenticate(user, passwd, &reenter, &msg);
1006
      xerrno = errno;
1007
1008
      pr_trace_msg(trace_channel, 9,
1009
        "AIX authenticate result: %d (msg '%.100s')", res, msg);
1010
1011
    } while (reenter != 0);
1012
# if defined(HAVE_LOGINSUCCESS)
1013
    if (res == 0) {
1014
      const char *host, *sess_ttyname;
1015
      char *msg = NULL;
1016
1017
      host = pr_netaddr_get_dnsstr(session.c->remote_addr);
1018
      sess_ttyname = pr_session_get_ttyname(cmd->tmp_pool);
1019
1020
      if (loginsuccess(user, (char *) host, (char *) sess_ttyname, &msg) == 0) {
1021
        if (msg != NULL) {
1022
          pr_trace_msg("auth", 14, "AIX loginsuccess() report: %s", msg);
1023
        }
1024
1025
      } else {
1026
        pr_trace_msg("auth", 3, "AIX loginsuccess() error for user '%s', "
1027
          "host '%s', tty '%s': %s", user, host, sess_ttyname, strerror(errno));
1028
      }
1029
    }
1030
# endif /* HAVE_LOGINSUCCESS */
1031
    PRIVS_RELINQUISH
1032
1033
    /* AIX indicates failure with a return value of 1. */
1034
    if (res != 0) {
1035
      pr_log_auth(LOG_WARNING,
1036
       "AIX authenticate failed for user '%s': %.100s", user, msg);
1037
1038
      if (xerrno == ENOENT) {
1039
        return PR_ERROR_INT(cmd, PR_AUTH_NOPWD);
1040
      }
1041
1042
      return PR_ERROR_INT(cmd, PR_AUTH_DISABLEDPWD);
1043
    }
1044
  }
1045
#endif /* HAVE_AUTHENTICATE */
1046
1047
  /* Call pw_authz here, to make sure the user is authorized to login. */
1048
1049
0
  if (cmd2 == NULL) {
1050
0
    cmd2 = pr_cmd_alloc(cmd->tmp_pool, 1, cmd->argv[1]);
1051
0
  }
1052
1053
0
  mr = pw_authz(cmd2);
1054
0
  if (MODRET_ISERROR(mr)) {
1055
0
    int err_code;
1056
1057
0
    err_code = MODRET_ERROR(mr);
1058
0
    return PR_ERROR_INT(cmd, err_code);
1059
0
  }
1060
1061
0
  if (MODRET_ISDECLINED(mr)) {
1062
0
    return PR_DECLINED(cmd);
1063
0
  }
1064
1065
0
  crypted_text = (char *) crypt(pw, cpw);
1066
0
  if (crypted_text == NULL) {
1067
0
    pr_log_pri(PR_LOG_NOTICE, "crypt(3) failed: %s", strerror(errno));
1068
0
    return PR_DECLINED(cmd);
1069
0
  }
1070
1071
0
  if (strcmp(crypted_text, cpw) != 0) {
1072
0
    return PR_DECLINED(cmd);
1073
0
  }
1074
1075
# ifdef CYGWIN
1076
  }
1077
# endif /* CYGWIN */
1078
1079
0
#endif /* PR_USE_SIA */
1080
1081
#ifdef COMSEC
1082
  }
1083
#endif /* COMSEC */
1084
1085
0
  session.auth_mech = "mod_auth_unix.c";
1086
0
  return PR_HANDLED(cmd);
1087
0
}
1088
1089
0
MODRET pw_uid2name(cmd_rec *cmd) {
1090
0
  struct passwd *pw = NULL;
1091
0
  uid_t uid;
1092
1093
0
  uid = *((uid_t *) cmd->argv[0]);
1094
1095
0
  if (unix_persistent_passwd) {
1096
0
    pw = p_getpwuid(uid);
1097
1098
0
  } else {
1099
0
    pw = getpwuid(uid);
1100
0
  }
1101
1102
0
  if (pw) {
1103
0
    return mod_create_data(cmd, pw->pw_name);
1104
0
  }
1105
1106
0
  return PR_DECLINED(cmd);
1107
0
}
1108
1109
0
MODRET pw_gid2name(cmd_rec *cmd) {
1110
0
  struct group *gr = NULL;
1111
0
  gid_t gid;
1112
1113
0
  gid = *((gid_t *) cmd->argv[0]);
1114
0
  if (unix_persistent_passwd) {
1115
0
    gr = p_getgrgid(gid);
1116
1117
0
  } else {
1118
0
    gr = getgrgid(gid);
1119
0
  }
1120
1121
0
  if (gr) {
1122
0
    return mod_create_data(cmd, gr->gr_name);
1123
0
  }
1124
1125
0
  return PR_DECLINED(cmd);
1126
0
}
1127
1128
0
MODRET pw_name2uid(cmd_rec *cmd) {
1129
0
  struct passwd *pw = NULL;
1130
0
  const char *name;
1131
1132
0
  name = cmd->argv[0];
1133
1134
0
  if (unix_persistent_passwd) {
1135
0
    pw = p_getpwnam(name);
1136
1137
0
  } else {
1138
0
    pw = getpwnam(name);
1139
0
  }
1140
1141
0
  return pw ? mod_create_data(cmd, (void *) &pw->pw_uid) : PR_DECLINED(cmd);
1142
0
}
1143
1144
0
MODRET pw_name2gid(cmd_rec *cmd) {
1145
0
  struct group *gr = NULL;
1146
0
  const char *name;
1147
1148
0
  name = cmd->argv[0];
1149
1150
0
  if (unix_persistent_passwd) {
1151
0
    gr = p_getgrnam(name);
1152
1153
0
  } else {
1154
0
    gr = getgrnam(name);
1155
0
  }
1156
1157
0
  return gr ? mod_create_data(cmd, (void *) &gr->gr_gid) : PR_DECLINED(cmd);
1158
0
}
1159
1160
static int get_groups_by_getgrset(const char *user, gid_t primary_gid,
1161
    array_header *gids, array_header *groups,
1162
0
    struct group *(*my_getgrgid)(gid_t)) {
1163
0
  int res;
1164
#ifdef HAVE_GETGRSET
1165
  gid_t group_ids[NGROUPS_MAX];
1166
  unsigned int ngroups = 0;
1167
  register unsigned int i;
1168
  char *grgid, *grouplist, *ptr;
1169
1170
  pr_trace_msg("auth", 4,
1171
    "using getgrset(3) to look up group membership");
1172
1173
  grouplist = getgrset(user);
1174
  if (grouplist == NULL) {
1175
    int xerrno = errno;
1176
1177
    pr_log_pri(PR_LOG_WARNING, "getgrset(3) error: %s", strerror(xerrno));
1178
1179
    errno = xerrno;
1180
    return -1;
1181
  }
1182
1183
  ptr = grouplist;
1184
  memset(group_ids, 0, sizeof(group_ids));
1185
1186
  /* The getgrset(3) function returns a string which is a comma-delimited
1187
   * list of group IDs.
1188
   */
1189
  grgid = strsep(&grouplist, ",");
1190
  while (grgid != NULL) {
1191
    gid_t gid;
1192
1193
    pr_signals_handle();
1194
1195
    if (ngroups >= sizeof(group_ids)) {
1196
      /* Reached capacity of the group_ids array. */
1197
      break;
1198
    }
1199
1200
    pr_str2gid(grgid, &gid);
1201
1202
    /* Skip the primary group. */
1203
    if (gid == primary_gid) {
1204
      grgid = strsep(&grouplist, ",");
1205
      continue;
1206
    }
1207
1208
    group_ids[ngroups] = gid;
1209
    ngroups++;
1210
1211
    grgid = strsep(&grouplist, ",");
1212
  }
1213
1214
  for (i = 0; i < ngroups; i++) {
1215
    struct group *gr;
1216
1217
    gr = my_getgrgid(group_ids[i]);
1218
    if (gr != NULL) {
1219
      if (gids != NULL &&
1220
          primary_gid != gr->gr_gid) {
1221
        *((gid_t *) push_array(gids)) = gr->gr_gid;
1222
      }
1223
1224
      if (groups != NULL &&
1225
          primary_gid != gr->gr_gid) {
1226
        *((char **) push_array(groups)) = pstrdup(session.pool,
1227
          gr->gr_name);
1228
      }
1229
    }
1230
  }
1231
1232
  free(ptr);
1233
  res = 0;
1234
1235
#else
1236
0
  errno = ENOSYS;
1237
0
  res = -1;
1238
0
#endif /* HAVE_GETGRSET */
1239
1240
0
  return res;
1241
0
}
1242
1243
static int get_groups_by_getgrouplist(const char *user, gid_t primary_gid,
1244
    array_header *gids, array_header *groups,
1245
0
    struct group *(*my_getgrgid)(gid_t)) {
1246
0
  int res;
1247
0
#ifdef HAVE_GETGROUPLIST
1248
0
  int use_getgrouplist = TRUE;
1249
0
  gid_t group_ids[NGROUPS_MAX];
1250
0
  int ngroups = NGROUPS_MAX;
1251
0
  register int i;
1252
1253
  /* Determine whether to use getgrouplist(3), if available.  Older glibc
1254
   * versions (i.e. 2.2.4 and older) had buggy getgrouplist() implementations
1255
   * which allowed for buffer overflows (see CVS-2003-0689); do not use
1256
   * getgrouplist() on such glibc versions.
1257
   */
1258
1259
# if defined(__GLIBC__) && \
1260
     defined(__GLIBC_MINOR__) && \
1261
     __GLIBC__ <= 2 && \
1262
     __GLIBC_MINOR__ < 3
1263
  use_getgrouplist = FALSE;
1264
# endif
1265
1266
  /* Use of getgrouplist(3) might have been disabled via the "NoGetgrouplist"
1267
   * AuthUnixOption as well.
1268
   */
1269
0
  if (auth_unix_opts & AUTH_UNIX_OPT_NO_GETGROUPLIST) {
1270
0
    use_getgrouplist = FALSE;
1271
0
  }
1272
1273
0
  if (use_getgrouplist == FALSE) {
1274
0
    errno = ENOSYS;
1275
0
    return -1;
1276
0
  }
1277
1278
0
  pr_trace_msg("auth", 4,
1279
0
    "using getgrouplist(3) to look up group membership");
1280
1281
0
  memset(group_ids, 0, sizeof(group_ids));
1282
#ifdef HAVE_GETGROUPLIST_TAKES_INTS
1283
  res = getgrouplist(user, primary_gid, (int *) group_ids, &ngroups);
1284
#else
1285
0
  res = getgrouplist(user, primary_gid, group_ids, &ngroups);
1286
0
#endif
1287
0
  if (res < 0) {
1288
0
    int xerrno = errno;
1289
1290
0
    pr_log_pri(PR_LOG_WARNING, "getgrouplist(3) error: %s", strerror(xerrno));
1291
1292
0
    errno = xerrno;
1293
0
    return -1;
1294
0
  }
1295
1296
0
  for (i = 0; i < ngroups; i++) {
1297
0
    struct group *gr;
1298
1299
0
    gr = my_getgrgid(group_ids[i]);
1300
0
    if (gr != NULL) {
1301
0
      if (gids != NULL &&
1302
0
          primary_gid != gr->gr_gid) {
1303
0
        *((gid_t *) push_array(gids)) = gr->gr_gid;
1304
0
      }
1305
1306
0
      if (groups != NULL &&
1307
0
          primary_gid != gr->gr_gid) {
1308
0
        *((char **) push_array(groups)) = pstrdup(session.pool,
1309
0
          gr->gr_name);
1310
0
      }
1311
0
    }
1312
0
  }
1313
1314
0
  res = 0;
1315
#else
1316
  errno = ENOSYS;
1317
  res = -1;
1318
#endif /* HAVE_GETGROUPLIST */
1319
1320
0
  return res;
1321
0
}
1322
1323
static int get_groups_by_getgrent(const char *user, gid_t primary_gid,
1324
    array_header *gids, array_header *groups,
1325
0
    struct group *(*my_getgrent)(void)) {
1326
0
  struct group *gr;
1327
0
  size_t user_len;
1328
1329
  /* This is where things get slow, expensive, and ugly.  Loop through
1330
   * everything, checking to make sure we haven't already added it.
1331
   */
1332
0
  user_len = strlen(user);
1333
0
  while ((gr = my_getgrent()) != NULL &&
1334
0
         gr->gr_mem != NULL) {
1335
0
    char **gr_member = NULL;
1336
1337
0
    pr_signals_handle();
1338
1339
    /* Loop through each member name listed */
1340
0
    for (gr_member = gr->gr_mem; *gr_member; gr_member++) {
1341
1342
     /* If it matches the given username... */
1343
0
      if (strncmp(*gr_member, user, user_len + 1) == 0) {
1344
1345
0
        if (gids != NULL &&
1346
0
            primary_gid != gr->gr_gid) {
1347
0
          *((gid_t *) push_array(gids)) = gr->gr_gid;
1348
0
        }
1349
1350
0
        if (groups != NULL &&
1351
0
            primary_gid != gr->gr_gid) {
1352
0
          *((char **) push_array(groups)) = pstrdup(session.pool,
1353
0
            gr->gr_name);
1354
0
        }
1355
0
      }
1356
0
    }
1357
0
  }
1358
1359
0
  return 0;
1360
0
}
1361
1362
static int get_groups_by_initgroups(const char *user, gid_t primary_gid,
1363
    array_header *gids, array_header *groups,
1364
0
    struct group *(*my_getgrgid)(gid_t)) {
1365
0
  int res;
1366
0
#if defined(HAVE_INITGROUPS) && defined(HAVE_GETGROUPS)
1367
0
  gid_t group_ids[NGROUPS_MAX+1];
1368
0
  int ngroups, use_initgroups = TRUE, xerrno;
1369
0
  register int i;
1370
1371
  /* On Mac OSX, the getgroups(2) man page has this unsettling tidbit:
1372
   *
1373
   *  Calling initgroups(3) to opt-in for supplementary groups will cause
1374
   *  getgroups() to return a single entry, the GID that was passed to
1375
   *  initgroups(3).
1376
   *
1377
   * But in our case, we WANT all of those groups.  Thus on Mac OSX, we
1378
   * will skip the use of initgroups(3) in favor of other mechanisms
1379
   * (e.g. getgrouplist(3)).
1380
   */
1381
# if defined(DARWIN10) || \
1382
     defined(DARWIN11) || \
1383
     defined(DARWIN12) || \
1384
     defined(DARWIN13) || \
1385
     defined(DARWIN14) || \
1386
     defined(DARWIN15)
1387
  use_initgroups = FALSE;
1388
# endif /* Mac OSX */
1389
1390
  /* Use of initgroups(3) might have been disabled via the "NoInitgroups"
1391
   * AuthUnixOption as well.
1392
   */
1393
0
  if (auth_unix_opts & AUTH_UNIX_OPT_NO_INITGROUPS) {
1394
0
    use_initgroups = FALSE;
1395
0
  }
1396
1397
  /* If we are not root, then initgroups(3) will most likely fail. */
1398
0
  if (geteuid() != PR_ROOT_UID) {
1399
0
    use_initgroups = FALSE;
1400
0
  }
1401
1402
0
  if (use_initgroups == FALSE) {
1403
0
    errno = ENOSYS;
1404
0
    return -1;
1405
0
  }
1406
1407
0
  pr_trace_msg("auth", 4,
1408
0
    "using initgroups(3) to look up group membership");
1409
1410
0
  PRIVS_ROOT
1411
0
  res = initgroups(user, primary_gid);
1412
0
  xerrno = errno;
1413
0
  PRIVS_RELINQUISH
1414
1415
0
  if (res < 0) {
1416
0
    pr_log_pri(PR_LOG_WARNING, "initgroups(3) error: %s", strerror(xerrno));
1417
1418
0
    errno = xerrno;
1419
0
    return -1;
1420
0
  }
1421
1422
0
  ngroups = getgroups(NGROUPS_MAX+1, group_ids);
1423
0
  if (ngroups < 0) {
1424
0
    xerrno = errno;
1425
1426
0
    pr_log_pri(PR_LOG_WARNING, "getgroups(2) error: %s", strerror(xerrno));
1427
1428
0
    errno = xerrno;
1429
0
    return -1;
1430
0
  }
1431
1432
0
  for (i = 0; i < ngroups; i++) {
1433
0
    struct group *gr;
1434
1435
0
    gr = my_getgrgid(group_ids[i]);
1436
0
    if (gr != NULL) {
1437
0
      if (gids != NULL &&
1438
0
          primary_gid != gr->gr_gid) {
1439
0
        *((gid_t *) push_array(gids)) = gr->gr_gid;
1440
0
      }
1441
1442
0
      if (groups != NULL &&
1443
0
          primary_gid != gr->gr_gid) {
1444
0
        *((char **) push_array(groups)) = pstrdup(session.pool,
1445
0
          gr->gr_name);
1446
0
      }
1447
0
    }
1448
0
  }
1449
1450
0
  res = 0;
1451
1452
#else
1453
  errno = ENOSYS;
1454
  res = -1;
1455
#endif /* HAVE_INITGROUPS and HAVE_GETGROUPS */
1456
1457
0
  return res;
1458
0
}
1459
1460
/* cmd->argv[0] = name
1461
 * cmd->argv[1] = (array_header **) group_ids
1462
 * cmd->argv[2] = (array_header **) group_names
1463
 */
1464
0
MODRET pw_getgroups(cmd_rec *cmd) {
1465
0
  int res;
1466
0
  struct passwd *pw = NULL;
1467
0
  struct group *gr = NULL;
1468
0
  array_header *gids = NULL, *groups = NULL;
1469
0
  const char *name = NULL;
1470
1471
  /* Function pointers for which lookup functions to use */
1472
0
  struct passwd *(*my_getpwnam)(const char *) = NULL;
1473
0
  struct group *(*my_getgrgid)(gid_t) = NULL;
1474
0
  struct group *(*my_getgrent)(void) = NULL;
1475
0
  RETSETGRENTTYPE (*my_setgrent)(void) = NULL;
1476
1477
  /* Play function pointer games */
1478
0
  if (unix_persistent_passwd) {
1479
0
    my_getpwnam = p_getpwnam;
1480
0
    my_getgrgid = p_getgrgid;
1481
0
    my_getgrent = p_getgrent;
1482
0
    my_setgrent = p_setgrent;
1483
1484
0
  } else {
1485
0
    my_getpwnam = getpwnam;
1486
0
    my_getgrgid = getgrgid;
1487
0
    my_getgrent = getgrent;
1488
0
    my_setgrent = setgrent;
1489
0
  }
1490
1491
0
  name = cmd->argv[0];
1492
1493
0
  if (cmd->argv[1] != NULL) {
1494
0
    gids = (array_header *) cmd->argv[1];
1495
0
  }
1496
1497
0
  if (cmd->argv[2] != NULL) {
1498
0
    groups = (array_header *) cmd->argv[2];
1499
0
  }
1500
1501
  /* Retrieve the necessary info. */
1502
0
  if (name == NULL ||
1503
0
      !(pw = my_getpwnam(name))) {
1504
0
    return PR_DECLINED(cmd);
1505
0
  }
1506
1507
  /* Populate the first group ID and name. */
1508
0
  if (gids != NULL) {
1509
0
    *((gid_t *) push_array(gids)) = pw->pw_gid;
1510
0
  }
1511
1512
0
  if (groups != NULL &&
1513
0
      (gr = my_getgrgid(pw->pw_gid)) != NULL) {
1514
0
    *((char **) push_array(groups)) = pstrdup(session.pool, gr->gr_name);
1515
0
  }
1516
1517
0
  my_setgrent();
1518
1519
  /* Myriad are the ways of obtaining the group membership of a user. */
1520
1521
0
  res = get_groups_by_initgroups(name, pw->pw_gid, gids, groups, my_getgrgid);
1522
0
  if (res < 0 &&
1523
0
      errno == ENOSYS) {
1524
0
    res = get_groups_by_getgrouplist(name, pw->pw_gid, gids, groups,
1525
0
      my_getgrgid);
1526
0
  }
1527
1528
0
  if (res < 0 &&
1529
0
      errno == ENOSYS) {
1530
0
    res = get_groups_by_getgrset(name, pw->pw_gid, gids, groups, my_getgrgid);
1531
0
  }
1532
1533
0
  if (res < 0 &&
1534
0
      errno == ENOSYS) {
1535
0
    res = get_groups_by_getgrent(name, pw->pw_gid, gids, groups, my_getgrent);
1536
0
  }
1537
1538
0
  if (res < 0) {
1539
0
    return PR_DECLINED(cmd);
1540
0
  }
1541
1542
0
  if (gids != NULL &&
1543
0
      gids->nelts > 0) {
1544
0
    return mod_create_data(cmd, (void *) &gids->nelts);
1545
0
  }
1546
1547
0
  if (groups != NULL &&
1548
0
      groups->nelts > 0) {
1549
0
    return mod_create_data(cmd, (void *) &groups->nelts);
1550
0
  }
1551
1552
0
  return PR_DECLINED(cmd);
1553
0
}
1554
1555
/* Configuration handlers
1556
 */
1557
1558
/* usage: AuthUnixOptions opt1 ... */
1559
0
MODRET set_authunixoptions(cmd_rec *cmd) {
1560
0
  config_rec *c;
1561
0
  register unsigned int i;
1562
0
  unsigned long opts = 0UL;
1563
1564
0
  if (cmd->argc == 1) {
1565
0
    CONF_ERROR(cmd, "wrong number of parameters");
1566
0
  }
1567
1568
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1569
1570
0
  c = add_config_param(cmd->argv[0], 1, NULL);
1571
1572
0
  for (i = 1; i < cmd->argc; i++) {
1573
0
    if (strcasecmp(cmd->argv[i], "AIXNoRLogin") == 0) {
1574
0
      opts |= AUTH_UNIX_OPT_AIX_NO_RLOGIN;
1575
1576
0
    } else if (strcasecmp(cmd->argv[i], "NoGetgrouplist") == 0) {
1577
0
      opts |= AUTH_UNIX_OPT_NO_GETGROUPLIST;
1578
1579
0
    } else if (strcasecmp(cmd->argv[i], "NoInitgroups") == 0) {
1580
0
      opts |= AUTH_UNIX_OPT_NO_INITGROUPS;
1581
1582
0
    } else if (strcasecmp(cmd->argv[i], "MagicTokenChroot") == 0) {
1583
0
      opts |= AUTH_UNIX_OPT_MAGIC_TOKEN_CHROOT;
1584
1585
0
    } else if (strcasecmp(cmd->argv[i], "AIXNoAuthenticate") == 0) {
1586
0
      opts |= AUTH_UNIX_OPT_AIX_NO_AUTHENTICATE;
1587
1588
0
    } else {
1589
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown AuthUnixOption '",
1590
0
        cmd->argv[i], "'", NULL));
1591
0
    }
1592
0
  }
1593
1594
0
  c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
1595
0
  *((unsigned long *) c->argv[0]) = opts;
1596
1597
0
  return PR_HANDLED(cmd);
1598
0
}
1599
1600
0
MODRET set_persistentpasswd(cmd_rec *cmd) {
1601
0
  int persistence = -1;
1602
0
  config_rec *c;
1603
1604
0
  CHECK_ARGS(cmd, 1);
1605
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1606
1607
0
  persistence = get_boolean(cmd, 1);
1608
0
  if (persistence == -1) {
1609
0
    CONF_ERROR(cmd, "expected Boolean parameter");
1610
0
  }
1611
1612
0
  c = add_config_param(cmd->argv[0], 1, NULL);
1613
0
  c->argv[0] = palloc(c->pool, sizeof(int));
1614
0
  *((int *) c->argv[0]) = persistence;
1615
1616
0
  return PR_HANDLED(cmd);
1617
0
}
1618
1619
/* Events handlers
1620
 */
1621
1622
0
static void auth_unix_exit_ev(const void *event_data, void *user_data) {
1623
0
  pr_auth_endpwent(session.pool);
1624
0
  pr_auth_endgrent(session.pool);
1625
0
}
1626
1627
0
static void auth_unix_sess_reinit_ev(const void *event_data, void *user_data) {
1628
0
  int res;
1629
1630
  /* A HOST command changed the main_server pointer, reinitialize ourselves. */
1631
1632
0
  pr_event_unregister(&auth_unix_module, "core.exit", auth_unix_exit_ev);
1633
0
  pr_event_unregister(&auth_unix_module, "core.session-reinit",
1634
0
    auth_unix_sess_reinit_ev);
1635
0
  auth_unix_opts = 0UL;
1636
0
  unix_persistent_passwd = FALSE;
1637
1638
0
  res = auth_unix_sess_init();
1639
0
  if (res < 0) {
1640
0
    pr_session_disconnect(&auth_unix_module,
1641
0
      PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
1642
0
  }
1643
0
}
1644
1645
/* Initialization routines
1646
 */
1647
1648
0
static int auth_unix_init(void) {
1649
1650
#ifdef HAVE__PW_STAYOPEN
1651
  _pw_stayopen = 1;
1652
#endif
1653
1654
0
  return 0;
1655
0
}
1656
1657
0
static int auth_unix_sess_init(void) {
1658
0
  config_rec *c;
1659
1660
0
  pr_event_register(&auth_unix_module, "core.exit", auth_unix_exit_ev, NULL);
1661
0
  pr_event_register(&auth_unix_module, "core.session-reinit",
1662
0
    auth_unix_sess_reinit_ev, NULL);
1663
1664
0
  c = find_config(main_server->conf, CONF_PARAM, "AuthUnixOptions", FALSE);
1665
0
  if (c != NULL) {
1666
0
    auth_unix_opts = *((unsigned long *) c->argv[0]);
1667
0
  }
1668
1669
0
  c = find_config(main_server->conf, CONF_PARAM, "PersistentPasswd", FALSE);
1670
0
  if (c != NULL) {
1671
0
    unix_persistent_passwd = *((int *) c->argv[0]);
1672
0
  }
1673
1674
0
  return 0;
1675
0
}
1676
1677
/* Module API tables
1678
 */
1679
1680
static conftable auth_unix_conftab[] = {
1681
  { "AuthUnixOptions",    set_authunixoptions,    NULL },
1682
  { "PersistentPasswd",   set_persistentpasswd,   NULL },
1683
  { NULL,     NULL,       NULL }
1684
};
1685
1686
static authtable auth_unix_authtab[] = {
1687
  { 0,  "setpwent", pw_setpwent },
1688
  { 0,  "endpwent", pw_endpwent },
1689
  { 0,  "setgrent",     pw_setgrent },
1690
  { 0,  "endgrent", pw_endgrent },
1691
  { 0,  "getpwent", pw_getpwent },
1692
  { 0,  "getgrent", pw_getgrent },
1693
  { 0,  "getpwnam", pw_getpwnam },
1694
  { 0,  "getpwuid", pw_getpwuid },
1695
  { 0,  "getgrnam",     pw_getgrnam },
1696
  { 0,  "getgrgid",     pw_getgrgid },
1697
  { 0,  "auth",         pw_auth },
1698
  { 0,  "authorize",  pw_authz },
1699
  { 0,  "check",  pw_check },
1700
  { 0,  "uid2name", pw_uid2name },
1701
  { 0,  "gid2name", pw_gid2name },
1702
  { 0,  "name2uid", pw_name2uid },
1703
  { 0,  "name2gid", pw_name2gid },
1704
  { 0,  "getgroups",  pw_getgroups },
1705
  { 0,  NULL }
1706
};
1707
1708
module auth_unix_module = {
1709
  NULL, NULL,
1710
1711
  /* Module API version */
1712
  0x20,
1713
1714
  /* Module name */
1715
  "auth_unix",
1716
1717
  /* Module configuration handler table */
1718
  auth_unix_conftab,
1719
1720
  /* Module command handler table */
1721
  NULL,
1722
1723
  /* Module authentication handler table */
1724
  auth_unix_authtab,
1725
1726
  /* Module initialization */
1727
  auth_unix_init,
1728
1729
  /* Session initialization */
1730
  auth_unix_sess_init
1731
};