Coverage Report

Created: 2026-05-23 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_auth_file.c
Line
Count
Source
1
/*
2
 * ProFTPD: mod_auth_file - file-based authentication module that supports
3
 *                          restrictions on the file contents
4
 * Copyright (c) 2002-2026 The ProFTPD Project team
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
18
 *
19
 * As a special exemption, the ProFTPD Project team and other respective
20
 * copyright holders give permission to link this program with OpenSSL, and
21
 * distribute the resulting executable, without including the source code for
22
 * OpenSSL in the source distribution.
23
 */
24
25
#include "conf.h"
26
#include "privs.h"
27
28
/* AIX has some rather stupid function prototype inconsistencies between
29
 * their crypt.h and stdlib.h's setkey() declarations.
30
 */
31
#if defined(HAVE_CRYPT_H) && !defined(AIX4) && !defined(AIX5)
32
# include <crypt.h>
33
#endif
34
35
0
#define MOD_AUTH_FILE_VERSION "mod_auth_file/1.0"
36
37
/* Make sure the version of proftpd is as necessary. */
38
#if PROFTPD_VERSION_NUMBER < 0x0001020702
39
# error "ProFTPD 1.2.7rc2 or later required"
40
#endif
41
42
module auth_file_module;
43
44
typedef union {
45
  uid_t uid;
46
  gid_t gid;
47
48
} authfile_id_t;
49
50
typedef struct file_rec {
51
  char *af_path;
52
  pr_fh_t *af_file_fh;
53
  unsigned int af_lineno;
54
55
  unsigned char af_restricted_ids;
56
  authfile_id_t af_min_id;
57
  authfile_id_t af_max_id;
58
59
#ifdef PR_USE_REGEX
60
  unsigned char af_restricted_names;
61
  char *af_name_filter;
62
  pr_regex_t *af_name_regex;
63
  unsigned char af_name_regex_inverted;
64
65
  /* These are AuthUserFile-specific */
66
  unsigned char af_restricted_homes;
67
  char *af_home_filter;
68
  pr_regex_t *af_home_regex;
69
  unsigned char af_home_regex_inverted;
70
71
#endif /* regex support */
72
73
} authfile_file_t;
74
75
/* List of server-specific AuthFiles */
76
static authfile_file_t *af_user_file = NULL;
77
static authfile_file_t *af_group_file = NULL;
78
static unsigned long auth_file_opts = 0UL;
79
80
/* Tell mod_auth_file to skip/ignore the permissions checks on the configured
81
 * AuthUserFile/AuthGroupFile.
82
 */
83
0
#define AUTH_FILE_OPT_INSECURE_PERMS    0x0001
84
85
/* Tell mod_auth_file to perform a syntax check of the configured files on
86
 * startup.
87
 */
88
0
#define AUTH_FILE_OPT_SYNTAX_CHECK    0x0002
89
90
static int handle_empty_salt = FALSE;
91
92
static int authfile_sess_init(void);
93
94
static int af_setpwent(pool *);
95
static int af_setgrent(pool *);
96
97
static const char *trace_channel = "auth.file";
98
99
/* Support routines.  Move the passwd/group functions out of lib/ into here. */
100
101
0
#define PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE    0x001
102
0
#define PR_AUTH_FILE_FL_USE_TRACE_LOG     0x002
103
104
0
static int af_check_parent_dir(pool *p, const char *name, const char *path) {
105
0
  struct stat st;
106
0
  int res;
107
0
  char *dir_path, *ptr = NULL;
108
109
0
  ptr = strrchr(path, '/');
110
0
  if (ptr != path) {
111
0
    dir_path = pstrndup(p, path, ptr - path);
112
113
0
  } else {
114
0
    dir_path = "/";
115
0
  }
116
117
0
  res = stat(dir_path, &st);
118
0
  if (res < 0) {
119
0
    int xerrno = errno;
120
121
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
122
0
      ": unable to stat %s directory '%s': %s", name, dir_path,
123
0
      strerror(xerrno));
124
125
0
    errno = xerrno;
126
0
    return -1;
127
0
  }
128
129
0
  if (st.st_mode & S_IWOTH) {
130
0
    int xerrno = EPERM;
131
132
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
133
0
      ": unable to use %s from world-writable directory '%s' (perms %04o): %s",
134
0
      name, dir_path, st.st_mode & ~S_IFMT, strerror(xerrno));
135
136
0
    errno = xerrno;
137
0
    return -1;
138
0
  }
139
140
0
  return 0;
141
0
}
142
143
static int af_check_file(pool *p, const char *name, const char *path,
144
0
    int flags) {
145
0
  struct stat st;
146
0
  int res;
147
0
  const char *orig_path;
148
149
0
  orig_path = path;
150
151
0
  res = lstat(path, &st);
152
0
  if (res < 0) {
153
0
    int xerrno = errno;
154
155
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to lstat %s '%s': %s",
156
0
      name, path, strerror(xerrno));
157
158
0
    errno = xerrno;
159
0
    return -1;
160
0
  }
161
162
0
  if (S_ISLNK(st.st_mode)) {
163
0
    char buf[PR_TUNABLE_PATH_MAX+1];
164
165
    /* Check the permissions on the parent directory; if they're world-writable,
166
     * then this symlink can be deleted/pointed somewhere else.
167
     */
168
0
    res = af_check_parent_dir(p, name, path);
169
0
    if (res < 0) {
170
0
      return -1;
171
0
    }
172
173
    /* Follow the link to the target path; that path will then have its
174
     * parent directory checked.
175
     */
176
0
    memset(buf, '\0', sizeof(buf));
177
0
    res = pr_fsio_readlink(path, buf, sizeof(buf)-1);
178
0
    if (res > 0) {
179
180
      /* The path contained in the symlink might itself be relative, thus
181
       * we need to make sure that we get an absolute path (Bug#4145).
182
       */
183
0
      path = dir_abs_path(p, buf, FALSE);
184
0
      if (path != NULL) {
185
0
        orig_path = path;
186
0
      }
187
0
    }
188
189
0
    res = stat(orig_path, &st);
190
0
    if (res < 0) {
191
0
      int xerrno = errno;
192
193
0
      pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to stat %s '%s': %s",
194
0
        name, orig_path, strerror(xerrno));
195
196
0
      errno = xerrno;
197
0
      return -1;
198
0
    }
199
0
  }
200
201
0
  if (S_ISDIR(st.st_mode)) {
202
0
    int xerrno = EISDIR;
203
204
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION ": unable to use %s '%s': %s",
205
0
      name, orig_path, strerror(xerrno));
206
207
0
    errno = xerrno;
208
0
    return -1;
209
0
  }
210
211
  /* World-readable files MAY be insecure, and are thus not usable/trusted. */
212
0
  if ((st.st_mode & S_IROTH) &&
213
0
       !(flags & PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE)) {
214
0
    int xerrno = EPERM;
215
216
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
217
0
      ": unable to use world-readable %s '%s' (perms %04o): %s",
218
0
      name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));
219
220
0
    errno = xerrno;
221
0
    return -1;
222
0
  }
223
224
  /* World-writable files are insecure, and are thus not usable/trusted. */
225
0
  if (st.st_mode & S_IWOTH) {
226
0
    int xerrno = EPERM;
227
228
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
229
0
      ": unable to use world-writable %s '%s' (perms %04o): %s",
230
0
      name, orig_path, st.st_mode & ~S_IFMT, strerror(xerrno));
231
232
0
    errno = xerrno;
233
0
    return -1;
234
0
  }
235
236
0
  if (!S_ISREG(st.st_mode)) {
237
0
    pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION
238
0
      ": %s '%s' is not a regular file", name, orig_path);
239
0
  }
240
241
  /* Check the parent directory of this file.  If the parent directory
242
   * is world-writable, that too is insecure.
243
   */
244
0
  res = af_check_parent_dir(p, name, orig_path);
245
0
  if (res < 0) {
246
0
    return -1;
247
0
  }
248
249
0
  return 0;
250
0
}
251
252
0
#define NPWDFIELDS      7
253
254
static char pwdbuf[PR_TUNABLE_BUFFER_SIZE];
255
static char *pwdfields[NPWDFIELDS];
256
static struct passwd pwent;
257
258
static struct passwd *af_parse_passwd(const char *buf, unsigned int lineno,
259
0
    int flags) {
260
0
  register unsigned int i;
261
0
  register char *cp = NULL;
262
0
  char *ptr = NULL, *buffer = NULL;
263
0
  char **fields = NULL;
264
0
  struct passwd *pwd = NULL;
265
266
0
  fields = pwdfields;
267
0
  buffer = pwdbuf;
268
0
  pwd = &pwent;
269
270
0
  sstrncpy(buffer, buf, PR_TUNABLE_BUFFER_SIZE-1);
271
0
  buffer[PR_TUNABLE_BUFFER_SIZE-1] = '\0';
272
273
0
  for (cp = buffer, i = 0; i < NPWDFIELDS && cp; i++) {
274
0
    fields[i] = cp;
275
0
    while (*cp && *cp != ':') {
276
0
      ++cp;
277
0
    }
278
279
0
    if (*cp) {
280
0
      *cp++ = '\0';
281
282
0
    } else {
283
0
      cp = 0;
284
0
    }
285
0
  }
286
287
0
  if (i != NPWDFIELDS) {
288
0
    pr_log_pri(PR_LOG_ERR,
289
0
      "Malformed entry in AuthUserFile file (field count %d != %d, line %u)",
290
0
      i, (int) NPWDFIELDS, lineno);
291
0
    return NULL;
292
0
  }
293
294
0
  pwd->pw_name = fields[0];
295
0
  pwd->pw_passwd = fields[1];
296
297
0
  if (*fields[2] == '\0' ||
298
0
      *fields[3] == '\0') {
299
0
    if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) {
300
0
      pr_trace_msg(trace_channel, 3,
301
0
        "missing UID/GID fields for user '%.100s' (line %u), skipping",
302
0
        pwd->pw_name, lineno);
303
304
0
    } else {
305
0
      pr_log_pri(PR_LOG_WARNING, "AuthUserFile: missing UID/GID fields for "
306
0
        "user '%.100s' (line %u), skipping", pwd->pw_name, lineno);
307
0
    }
308
309
0
    return NULL;
310
0
  }
311
312
0
  ptr = NULL;
313
0
  pwd->pw_uid = strtol(fields[2], &ptr, 10);
314
0
  if (*ptr != '\0') {
315
0
    if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) {
316
0
      pr_trace_msg(trace_channel, 3,
317
0
        "non-numeric UID field '%.100s' for user '%.100s' (line %u), skipping",
318
0
        fields[2], pwd->pw_name, lineno);
319
320
0
    } else {
321
0
      pr_log_pri(PR_LOG_WARNING, "AuthUserFile: non-numeric UID field "
322
0
        "'%.100s' for user '%.100s' (line %u), skipping", fields[2],
323
0
        pwd->pw_name, lineno);
324
0
    }
325
326
0
    return NULL;
327
0
  }
328
329
0
  ptr = NULL;
330
0
  pwd->pw_gid = strtol(fields[3], &ptr, 10);
331
0
  if (*ptr != '\0') {
332
0
    if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) {
333
0
      pr_trace_msg(trace_channel, 3,
334
0
        "non-numeric GID field '%.100s' for user '%.100s' (line %u), skipping",
335
0
        fields[3], pwd->pw_name, lineno);
336
337
0
    } else {
338
0
      pr_log_pri(PR_LOG_WARNING, "AuthUserFile: non-numeric GID field "
339
0
        "'%.100s' for user '%.100s' (line %u), skipping", fields[3],
340
0
        pwd->pw_name, lineno);
341
0
    }
342
343
0
    return NULL;
344
0
  }
345
346
0
  pwd->pw_gecos = fields[4];
347
0
  pwd->pw_dir = fields[5];
348
0
  pwd->pw_shell = fields[6];
349
350
0
  return pwd;
351
0
}
352
353
0
#define MAXMEMBERS  4096
354
0
#define NGRPFIELDS      4
355
356
static char *grpbuf = NULL;
357
static size_t grpbufsz = 0;
358
static struct group grent;
359
static char *grpfields[NGRPFIELDS];
360
static char *members[MAXMEMBERS+1];
361
362
static char *af_getgrentline(char **buf, size_t *bufsz, pr_fh_t *fh,
363
0
    unsigned int *lineno) {
364
0
  char *ptr, *res;
365
0
  size_t original_bufsz, buflen;
366
367
0
  original_bufsz = *bufsz;
368
0
  buflen = *bufsz;
369
370
  /* Try to keep our unfilled buffer zeroed out, so that strlen(3) et al
371
   * work as expected.
372
   */
373
0
  memset(*buf, '\0', *bufsz);
374
375
0
  ptr = *buf;
376
0
  res = pr_fsio_gets(ptr, buflen, fh);
377
0
  while (res != NULL) {
378
0
    pr_signals_handle();
379
380
    /* Is this a full line? */
381
0
    if (strchr(*buf, '\n') != NULL) {
382
0
      pr_trace_msg(trace_channel, 25,
383
0
        "found LF, returning line: '%s' (%lu bytes)", *buf,
384
0
        (unsigned long) strlen(*buf));
385
0
      (*lineno)++;
386
0
      return *buf;
387
0
    }
388
389
    /* No -- allocate a larger buffer.  Note that doubling the buflen
390
     * each time may cause issues; fgetgrent(3) would increment the
391
     * allocated buffer by the original buffer length each time.  So we
392
     * do the same (Issue #1321).
393
     */
394
0
    {
395
0
      size_t new_bufsz;
396
0
      char *new_buf;
397
398
0
      pr_trace_msg(trace_channel, 25, "getgrentline() buffer (%lu bytes): "
399
0
        "'%.*s'", (unsigned long) *bufsz, (int) *bufsz, *buf);
400
401
0
      pr_trace_msg(trace_channel, 19,
402
0
        "no LF found in group line, increasing buffer (%lu bytes) by %lu bytes",
403
0
        (unsigned long) *bufsz, (unsigned long) original_bufsz);
404
0
      new_bufsz = *bufsz + original_bufsz;
405
406
0
      new_buf = realloc(*buf, new_bufsz);
407
0
      if (new_buf == NULL) {
408
0
        break;
409
0
      }
410
411
0
      ptr = new_buf + *bufsz;
412
0
      *buf = new_buf;
413
0
      *bufsz = new_bufsz;
414
0
      buflen = original_bufsz;
415
416
0
      memset(ptr, '\0', buflen);
417
0
    }
418
419
0
    res = pr_fsio_gets(ptr, buflen, fh);
420
0
  }
421
422
0
  free(*buf);
423
0
  *buf = NULL;
424
0
  *bufsz = 0;
425
426
0
  return NULL;
427
0
}
428
429
0
static char **af_getgrmems(char *s) {
430
0
  int nmembers = 0;
431
432
0
  while (s && *s && nmembers < MAXMEMBERS) {
433
0
    pr_signals_handle();
434
435
0
    members[nmembers++] = s;
436
0
    while (*s && *s != ',') {
437
0
      s++;
438
0
    }
439
440
0
    if (*s) {
441
0
      *s++ = '\0';
442
0
    }
443
0
  }
444
445
0
  members[nmembers] = NULL;
446
0
  return members;
447
0
}
448
449
static struct group *af_parse_grp(const char *buf, unsigned int lineno,
450
0
    int flags) {
451
0
  unsigned int i;
452
0
  char *cp;
453
454
0
  i = strlen(buf) + 1;
455
456
0
  if (grpbuf == NULL) {
457
0
    grpbufsz = i;
458
0
    grpbuf = malloc(grpbufsz);
459
460
0
  } else if (grpbufsz < (size_t) i) {
461
0
    char *new_buf;
462
463
0
    pr_trace_msg(trace_channel, 19,
464
0
      "parsing group line '%s' (%lu bytes), allocating %lu bytes via "
465
0
      "realloc(3)", buf, (unsigned long) i, (unsigned long) i);
466
467
0
    new_buf = realloc(grpbuf, i);
468
0
    if (new_buf == NULL) {
469
0
      return NULL;
470
0
    }
471
472
0
    grpbuf = new_buf;
473
0
    grpbufsz = i;
474
0
  }
475
476
0
  if (grpbuf == NULL) {
477
0
    return NULL;
478
0
  }
479
480
0
  sstrncpy(grpbuf, buf, i);
481
482
0
  cp = strrchr(grpbuf, '\n');
483
0
  if (cp) {
484
0
    *cp = '\0';
485
0
  }
486
487
0
  for (cp = grpbuf, i = 0; i < NGRPFIELDS && cp; i++) {
488
0
    grpfields[i] = cp;
489
490
0
    cp = strchr(cp, ':');
491
0
    if (cp != NULL) {
492
0
      *cp++ = 0;
493
0
    }
494
0
  }
495
496
0
  if (i != NGRPFIELDS) {
497
0
    pr_log_pri(PR_LOG_ERR, "Malformed entry in AuthGroupFile file (line %u)",
498
0
      lineno);
499
0
    return NULL;
500
0
  }
501
502
0
  grent.gr_name = grpfields[0];
503
0
  grent.gr_passwd = grpfields[1];
504
505
0
  if (*grpfields[2] == '\0') {
506
0
    if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) {
507
0
      pr_trace_msg(trace_channel, 3,
508
0
        "missing GID field for group '%.100s' (line %u), skipping",
509
0
        grent.gr_name, lineno);
510
511
0
    } else {
512
0
      pr_log_pri(PR_LOG_WARNING, "AuthGroupFile: missing GID field for "
513
0
        "group '%.100s' (line %u), skipping", grent.gr_name, lineno);
514
0
    }
515
516
0
    return NULL;
517
0
  }
518
519
0
  cp = NULL;
520
0
  grent.gr_gid = strtol(grpfields[2], &cp, 10);
521
0
  if (*cp != '\0') {
522
0
    if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) {
523
0
      pr_trace_msg(trace_channel, 3,
524
0
        "non-numeric GID field '%.100s' for group '%.100s' (line %u)",
525
0
        grpfields[2], grent.gr_name, lineno);
526
527
0
    } else {
528
0
      pr_log_pri(PR_LOG_WARNING, "AuthGroupFile: non-numeric GID field "
529
0
        "'%.100s' for group '%.100s' (line %u)", grpfields[2],
530
0
        grent.gr_name, lineno);
531
0
    }
532
0
  }
533
534
0
  grent.gr_mem = af_getgrmems(grpfields[3]);
535
536
0
  return &grent;
537
0
}
538
539
0
static int af_allow_grent(pool *p, struct group *grp) {
540
0
  if (af_group_file == NULL) {
541
0
    errno = EPERM;
542
0
    return -1;
543
0
  }
544
545
  /* Check that the grent is within the ID restrictions (if present). */
546
0
  if (af_group_file->af_restricted_ids) {
547
548
0
    if (grp->gr_gid < af_group_file->af_min_id.gid) {
549
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': "
550
0
        "GID %s below the minimum allowed (%s)", grp->gr_name,
551
0
        pr_gid2str(p, grp->gr_gid),
552
0
        pr_gid2str(p, af_group_file->af_min_id.gid));
553
0
      errno = EINVAL;
554
0
      return -1;
555
0
    }
556
557
0
    if (grp->gr_gid > af_group_file->af_max_id.gid) {
558
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': "
559
0
        "GID %s above the maximum allowed (%s)", grp->gr_name,
560
0
        pr_gid2str(p, grp->gr_gid),
561
0
        pr_gid2str(p, af_group_file->af_max_id.gid));
562
0
      errno = EINVAL;
563
0
      return -1;
564
0
    }
565
0
  }
566
567
0
#ifdef PR_USE_REGEX
568
  /* Check if the grent has an acceptable name. */
569
0
  if (af_group_file->af_restricted_names) {
570
0
    int res;
571
572
0
    res = pr_regexp_exec(af_group_file->af_name_regex, grp->gr_name, 0,
573
0
      NULL, 0, 0, 0);
574
575
0
    if ((res != 0 && !af_group_file->af_name_regex_inverted) ||
576
0
        (res == 0 && af_group_file->af_name_regex_inverted)) {
577
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': "
578
0
        "name '%s' does not meet allowed filter '%s'", grp->gr_name,
579
0
        grp->gr_name, af_group_file->af_name_filter);
580
0
      errno = EINVAL;
581
0
      return -1;
582
0
    }
583
0
  }
584
0
#endif /* regex support */
585
586
0
  return 0;
587
0
}
588
589
0
static void af_endgrent(void) {
590
0
  if (af_group_file != NULL &&
591
0
      af_group_file->af_file_fh != NULL) {
592
0
    pr_fsio_close(af_group_file->af_file_fh);
593
0
    af_group_file->af_file_fh = NULL;
594
0
    af_group_file->af_lineno = 0;
595
0
  }
596
0
}
597
598
static struct group *af_getgrent(pool *p, int flags,
599
0
    unsigned int *bad_entry_count) {
600
0
  struct group *grp = NULL, *res = NULL;
601
602
0
  if (af_group_file == NULL ||
603
0
      af_group_file->af_file_fh == NULL) {
604
0
    errno = EINVAL;
605
0
    return NULL;
606
0
  }
607
608
0
  while (TRUE) {
609
0
    char *cp = NULL, *buf = NULL;
610
0
    size_t buflen;
611
612
0
    buflen = PR_TUNABLE_BUFFER_SIZE;
613
614
0
    if (af_group_file->af_file_fh->fh_iosz > 0) {
615
      /* This aligns our group(5) buffer with the preferred filesystem read
616
       * block size.
617
       */
618
0
      buflen = af_group_file->af_file_fh->fh_iosz;
619
0
    }
620
621
0
    pr_signals_handle();
622
623
0
    buf = malloc(buflen);
624
0
    if (buf == NULL) {
625
0
      pr_log_pri(PR_LOG_ALERT, "Out of memory!");
626
0
      _exit(1);
627
0
    }
628
0
    pr_trace_msg(trace_channel, 19,
629
0
      "getgrent(3): allocated buffer %p (%lu bytes)", buf,
630
0
      (unsigned long) buflen);
631
632
0
    grp = NULL;
633
634
0
    while (af_getgrentline(&buf, &buflen, af_group_file->af_file_fh,
635
0
        &(af_group_file->af_lineno)) != NULL) {
636
637
0
      pr_signals_handle();
638
639
      /* Ignore comment and empty lines */
640
0
      if (buf[0] == '\0' ||
641
0
          buf[0] == '#') {
642
0
        continue;
643
0
      }
644
645
0
      cp = strchr(buf, '\n');
646
0
      if (cp != NULL) {
647
0
        *cp = '\0';
648
0
      }
649
650
0
      grp = af_parse_grp(buf, af_group_file->af_lineno, flags);
651
0
      if (grp == NULL) {
652
        /* If grp is NULL here, it's a malformed entry; keep looking. */
653
0
        if (bad_entry_count != NULL) {
654
0
          (*bad_entry_count)++;
655
0
        }
656
657
0
        continue;
658
0
      }
659
660
0
      free(buf);
661
0
      break;
662
0
    }
663
664
    /* If grp is NULL now, the file is empty - nothing more to be read. */
665
0
    if (grp == NULL) {
666
0
      break;
667
0
    }
668
669
0
    if (af_allow_grent(p, grp) < 0) {
670
0
      continue;
671
0
    }
672
673
0
    res = grp;
674
0
    break;
675
0
  }
676
677
0
  return res;
678
0
}
679
680
0
static struct group *af_getgrnam(pool *p, const char *name) {
681
0
  struct group *grp = NULL;
682
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
683
684
0
  if (af_setgrent(p) < 0) {
685
0
    return NULL;
686
0
  }
687
688
0
  grp = af_getgrent(p, flags, NULL);
689
0
  while (grp != NULL) {
690
0
    pr_signals_handle();
691
692
0
    if (strcmp(name, grp->gr_name) == 0) {
693
      /* Found the requested group */
694
0
      break;
695
0
    }
696
697
0
    grp = af_getgrent(p, flags, NULL);
698
0
  }
699
700
0
  return grp;
701
0
}
702
703
0
static struct group *af_getgrgid(pool *p, gid_t gid) {
704
0
  struct group *grp = NULL;
705
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
706
707
0
  if (af_setgrent(p) < 0) {
708
0
    return NULL;
709
0
  }
710
711
0
  grp = af_getgrent(p, flags, NULL);
712
0
  while (grp != NULL) {
713
0
    pr_signals_handle();
714
715
0
    if (grp->gr_gid == gid) {
716
      /* Found the requested GID */
717
0
      break;
718
0
    }
719
720
0
    grp = af_getgrent(p, flags, NULL);
721
0
  }
722
723
0
  return grp;
724
0
}
725
726
0
static int af_setgrent(pool *p) {
727
728
0
  if (af_group_file != NULL) {
729
0
    int xerrno;
730
0
    struct stat st;
731
732
0
    if (af_group_file->af_file_fh != NULL) {
733
0
      pr_buffer_t *pbuf;
734
735
      /* If already opened, rewind */
736
0
      (void) pr_fsio_lseek(af_group_file->af_file_fh, 0, SEEK_SET);
737
738
      /* Make sure to clear any buffers as well. */
739
0
      pbuf = af_group_file->af_file_fh->fh_buf;
740
0
      if (pbuf != NULL) {
741
0
        memset(pbuf->buf, '\0', pbuf->buflen);
742
0
        pbuf->current = pbuf->buf;
743
0
        pbuf->remaining = pbuf->buflen;
744
0
      }
745
746
0
      if (grpbuf != NULL) {
747
0
        free(grpbuf);
748
0
        grpbuf = NULL;
749
0
      }
750
0
      grpbufsz = 0;
751
752
0
      return 0;
753
0
    }
754
755
0
    PRIVS_ROOT
756
0
    af_group_file->af_file_fh = pr_fsio_open(af_group_file->af_path, O_RDONLY);
757
0
    xerrno = errno;
758
0
    PRIVS_RELINQUISH
759
760
0
    if (af_group_file->af_file_fh == NULL) {
761
0
      if (pr_fsio_stat(af_group_file->af_path, &st) == 0) {
762
0
        pr_log_pri(PR_LOG_WARNING,
763
0
          "error: unable to open AuthGroupFile file '%s' (file owned by "
764
0
          "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s",
765
0
          af_group_file->af_path, pr_uid2str(p, st.st_uid),
766
0
          pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT,
767
0
          pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()),
768
0
          strerror(xerrno));
769
770
0
      } else {
771
0
        pr_log_pri(PR_LOG_WARNING,
772
0
          "error: unable to open AuthGroupFile file '%s': %s",
773
0
          af_group_file->af_path, strerror(xerrno));
774
0
      }
775
776
0
      errno = xerrno;
777
0
      return -1;
778
0
    }
779
780
    /* Set the optimum buffer/block size for this filehandle. */
781
0
    if (pr_fsio_fstat(af_group_file->af_file_fh, &st) == 0) {
782
0
      af_group_file->af_file_fh->fh_iosz = st.st_blksize;
783
0
    }
784
785
0
    if (fcntl(PR_FH_FD(af_group_file->af_file_fh), F_SETFD, FD_CLOEXEC) < 0) {
786
0
      pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION
787
0
        ": unable to set CLOEXEC on AuthGroupFile %s (fd %d): %s",
788
0
        af_group_file->af_path, PR_FH_FD(af_group_file->af_file_fh),
789
0
        strerror(errno));
790
0
    }
791
792
0
    pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using group file '%s'",
793
0
      af_group_file->af_path);
794
0
    return 0;
795
0
  }
796
797
0
  pr_trace_msg(trace_channel, 8, "no AuthGroupFile configured");
798
0
  errno = EPERM;
799
0
  return -1;
800
0
}
801
802
0
static int af_allow_pwent(pool *p, struct passwd *pwd) {
803
0
  if (af_user_file == NULL) {
804
0
    errno = EPERM;
805
0
    return -1;
806
0
  }
807
808
  /* Check that the pwent is within the ID restrictions (if present). */
809
0
  if (af_user_file->af_restricted_ids) {
810
811
0
    if (pwd->pw_uid < af_user_file->af_min_id.uid) {
812
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': "
813
0
        "UID %s below the minimum allowed (%s)", pwd->pw_name,
814
0
        pr_uid2str(p, pwd->pw_uid),
815
0
        pr_uid2str(p, af_user_file->af_min_id.uid));
816
0
      errno = EINVAL;
817
0
      return -1;
818
0
    }
819
820
0
    if (pwd->pw_uid > af_user_file->af_max_id.gid) {
821
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': "
822
0
        "UID %s above the maximum allowed (%s)", pwd->pw_name,
823
0
        pr_uid2str(p, pwd->pw_uid),
824
0
        pr_uid2str(p, af_user_file->af_max_id.uid));
825
0
      errno = EINVAL;
826
0
      return -1;
827
0
    }
828
0
  }
829
830
0
#ifdef PR_USE_REGEX
831
  /* Check if the pwent has an acceptable name. */
832
0
  if (af_user_file->af_restricted_names) {
833
0
    int res;
834
835
0
    res = pr_regexp_exec(af_user_file->af_name_regex, pwd->pw_name, 0, NULL,
836
0
      0, 0, 0);
837
838
0
    if ((res != 0 && !af_user_file->af_name_regex_inverted) ||
839
0
        (res == 0 && af_user_file->af_name_regex_inverted)) {
840
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': "
841
0
        "name '%s' does not meet allowed filter '%s'", pwd->pw_name,
842
0
        pwd->pw_name, af_user_file->af_name_filter);
843
0
      errno = EINVAL;
844
0
      return -1;
845
0
    }
846
0
  }
847
848
  /* Check if the pwent has an acceptable home directory. */
849
0
  if (af_user_file->af_restricted_homes) {
850
0
    int res;
851
852
0
    res = pr_regexp_exec(af_user_file->af_home_regex, pwd->pw_dir, 0, NULL,
853
0
      0, 0, 0);
854
855
0
    if ((res != 0 && !af_user_file->af_home_regex_inverted) ||
856
0
        (res == 0 && af_user_file->af_home_regex_inverted)) {
857
0
      pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping user '%s': "
858
0
        "home '%s' does not meet allowed filter '%s'", pwd->pw_name,
859
0
        pwd->pw_dir, af_user_file->af_home_filter);
860
0
      errno = EINVAL;
861
0
      return -1;
862
0
    }
863
0
  }
864
0
#endif /* regex support */
865
866
0
  return 0;
867
0
}
868
869
0
static void af_endpwent(void) {
870
0
  if (af_user_file != NULL &&
871
0
      af_user_file->af_file_fh != NULL) {
872
0
    pr_fsio_close(af_user_file->af_file_fh);
873
0
    af_user_file->af_file_fh = NULL;
874
0
    af_user_file->af_lineno = 0;
875
0
  }
876
0
}
877
878
static struct passwd *af_getpwent(pool *p, int flags,
879
0
    unsigned int *bad_entry_count) {
880
0
  struct passwd *pwd = NULL, *res = NULL;
881
882
0
  if (af_user_file == NULL ||
883
0
      af_user_file->af_file_fh == NULL) {
884
0
    errno = EINVAL;
885
0
    return NULL;
886
0
  }
887
888
0
  while (TRUE) {
889
0
    char buf[PR_TUNABLE_BUFFER_SIZE+1] = {'\0'};
890
891
0
    pr_signals_handle();
892
893
0
    memset(buf, '\0', sizeof(buf));
894
0
    pwd = NULL;
895
896
0
    while (pr_fsio_gets(buf, sizeof(buf)-1, af_user_file->af_file_fh) != NULL) {
897
0
      pr_signals_handle();
898
899
0
      af_user_file->af_lineno++;
900
901
      /* Ignore empty and comment lines */
902
0
      if (buf[0] == '\0' ||
903
0
          buf[0] == '#') {
904
0
        memset(buf, '\0', sizeof(buf));
905
0
        continue;
906
0
      }
907
908
0
      buf[strlen(buf)-1] = '\0';
909
0
      pwd = af_parse_passwd(buf, af_user_file->af_lineno, flags);
910
911
0
      if (pwd == NULL) {
912
        /* If pwd is NULL here, it's a malformed entry; keep looking. */
913
0
        if (bad_entry_count != NULL) {
914
0
          (*bad_entry_count)++;
915
0
        }
916
917
0
        memset(buf, '\0', sizeof(buf));
918
0
        continue;
919
0
      }
920
921
0
      break;
922
0
    }
923
924
    /* If pwd is NULL now, the file is empty - nothing more to be read. */
925
0
    if (pwd == NULL) {
926
0
      break;
927
0
    }
928
929
0
    if (af_allow_pwent(p, pwd) < 0) {
930
0
      memset(buf, '\0', sizeof(buf));
931
0
      continue;
932
0
    }
933
934
0
    res = pwd;
935
0
    break;
936
0
  }
937
938
0
  return res;
939
0
}
940
941
0
static struct passwd *af_getpwnam(pool *p, const char *name) {
942
0
  struct passwd *pwd = NULL;
943
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
944
945
0
  if (af_setpwent(p) < 0) {
946
0
    return NULL;
947
0
  }
948
949
0
  pwd = af_getpwent(p, flags, NULL);
950
0
  while (pwd != NULL) {
951
0
    pr_signals_handle();
952
953
0
    if (strcmp(name, pwd->pw_name) == 0) {
954
      /* Found the requested user */
955
0
      break;
956
0
    }
957
958
0
    pwd = af_getpwent(p, flags, NULL);
959
0
  }
960
961
0
  return pwd;
962
0
}
963
964
0
static char *af_getpwpass(pool *p, const char *name) {
965
0
  struct passwd *pwd = af_getpwnam(p, name);
966
0
  return pwd ? pwd->pw_passwd : NULL;
967
0
}
968
969
0
static struct passwd *af_getpwuid(pool *p, uid_t uid) {
970
0
  struct passwd *pwd = NULL;
971
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
972
973
0
  if (af_setpwent(p) < 0) {
974
0
    return NULL;
975
0
  }
976
977
0
  pwd = af_getpwent(p, flags, NULL);
978
0
  while (pwd != NULL) {
979
0
    pr_signals_handle();
980
981
0
    if (pwd->pw_uid == uid) {
982
      /* Found the requested UID */
983
0
      break;
984
0
    }
985
986
0
    pwd = af_getpwent(p, flags, NULL);
987
0
  }
988
989
0
  return pwd;
990
0
}
991
992
0
static int af_setpwent(pool *p) {
993
994
0
  if (af_user_file != NULL) {
995
0
    int xerrno;
996
0
    struct stat st;
997
998
0
    if (af_user_file->af_file_fh != NULL) {
999
0
      pr_buffer_t *pbuf;
1000
1001
      /* If already opened, rewind */
1002
0
      (void) pr_fsio_lseek(af_user_file->af_file_fh, 0, SEEK_SET);
1003
1004
      /* Make sure to clear any buffers as well. */
1005
0
      pbuf = af_user_file->af_file_fh->fh_buf;
1006
0
      if (pbuf != NULL) {
1007
0
        memset(pbuf->buf, '\0', pbuf->buflen);
1008
0
        pbuf->current = pbuf->buf;
1009
0
        pbuf->remaining = pbuf->buflen;
1010
0
      }
1011
1012
0
      return 0;
1013
0
    }
1014
1015
0
    PRIVS_ROOT
1016
0
    af_user_file->af_file_fh = pr_fsio_open(af_user_file->af_path, O_RDONLY);
1017
0
    xerrno = errno;
1018
0
    PRIVS_RELINQUISH
1019
1020
0
    if (af_user_file->af_file_fh == NULL) {
1021
0
      if (pr_fsio_stat(af_user_file->af_path, &st) == 0) {
1022
0
        pr_log_pri(PR_LOG_WARNING,
1023
0
          "error: unable to open AuthUserFile file '%s' (file owned by "
1024
0
          "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s",
1025
0
          af_user_file->af_path, pr_uid2str(p, st.st_uid),
1026
0
          pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT,
1027
0
          pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()),
1028
0
          strerror(xerrno));
1029
1030
0
      } else {
1031
0
        pr_log_pri(PR_LOG_WARNING,
1032
0
          "error: unable to open AuthUserFile file '%s': %s",
1033
0
          af_user_file->af_path, strerror(xerrno));
1034
0
      }
1035
1036
0
      errno = xerrno;
1037
0
      return -1;
1038
0
    }
1039
1040
    /* Set the optimum buffer/block size for this filehandle. */
1041
0
    if (pr_fsio_fstat(af_user_file->af_file_fh, &st) == 0) {
1042
0
      af_user_file->af_file_fh->fh_iosz = st.st_blksize;
1043
0
    }
1044
1045
0
    if (fcntl(PR_FH_FD(af_user_file->af_file_fh), F_SETFD, FD_CLOEXEC) < 0) {
1046
0
      pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION
1047
0
        ": unable to set CLOEXEC on AuthUserFile %s (fd %d): %s",
1048
0
        af_user_file->af_path, PR_FH_FD(af_user_file->af_file_fh),
1049
0
        strerror(errno));
1050
0
    }
1051
1052
0
    pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using passwd file '%s'",
1053
0
      af_user_file->af_path);
1054
0
    return 0;
1055
0
  }
1056
1057
0
  pr_trace_msg(trace_channel, 8, "no AuthUserFile configured");
1058
0
  errno = EPERM;
1059
0
  return -1;
1060
0
}
1061
1062
0
static int af_check_group_syntax(pool *p, const char *path) {
1063
0
  int flags = 0, xerrno, res = 0;
1064
0
  struct group *grp;
1065
0
  unsigned int bad_entry_count = 0;
1066
1067
0
  af_group_file = pcalloc(p, sizeof(authfile_file_t));
1068
0
  af_group_file->af_path = pstrdup(p, path);
1069
1070
0
  PRIVS_ROOT
1071
0
  af_group_file->af_file_fh = pr_fsio_open(af_group_file->af_path, O_RDONLY);
1072
0
  xerrno = errno;
1073
0
  PRIVS_RELINQUISH
1074
1075
0
  if (af_group_file->af_file_fh == NULL) {
1076
0
    pr_log_pri(PR_LOG_WARNING,
1077
0
      "error: unable to open AuthGroupFile file '%s': %s",
1078
0
      af_group_file->af_path, strerror(xerrno));
1079
0
    af_group_file = NULL;
1080
0
    errno = xerrno;
1081
0
    return -1;
1082
0
  }
1083
1084
0
  grp = af_getgrent(p, flags, &bad_entry_count);
1085
0
  while (grp != NULL) {
1086
0
    pr_signals_handle();
1087
1088
0
    grp = af_getgrent(p, flags, &bad_entry_count);
1089
0
  }
1090
1091
0
  pr_fsio_close(af_group_file->af_file_fh);
1092
0
  af_group_file->af_file_fh = NULL;
1093
0
  af_group_file->af_lineno = 0;
1094
0
  af_group_file = NULL;
1095
1096
0
  if (bad_entry_count > 0) {
1097
0
    pr_log_pri(PR_LOG_WARNING, "bad entries (%u) detected in AuthGroupFile %s",
1098
0
      bad_entry_count, path);
1099
0
    errno = EINVAL;
1100
0
    res = -1;
1101
0
  }
1102
1103
0
  return res;
1104
0
}
1105
1106
0
static int af_check_user_syntax(pool *p, const char *path) {
1107
0
  int flags = 0, xerrno, res = 0;
1108
0
  struct passwd *pwd;
1109
0
  unsigned int bad_entry_count = 0;
1110
1111
0
  af_user_file = pcalloc(p, sizeof(authfile_file_t));
1112
0
  af_user_file->af_path = pstrdup(p, path);
1113
1114
0
  PRIVS_ROOT
1115
0
  af_user_file->af_file_fh = pr_fsio_open(af_user_file->af_path, O_RDONLY);
1116
0
  xerrno = errno;
1117
0
  PRIVS_RELINQUISH
1118
1119
0
  if (af_user_file->af_file_fh == NULL) {
1120
0
    pr_log_pri(PR_LOG_WARNING,
1121
0
      "error: unable to open AuthUserFile file '%s': %s",
1122
0
      af_user_file->af_path, strerror(xerrno));
1123
0
    af_user_file = NULL;
1124
0
    errno = xerrno;
1125
0
    return -1;
1126
0
  }
1127
1128
0
  bad_entry_count = 0;
1129
0
  pwd = af_getpwent(p, flags, &bad_entry_count);
1130
0
  while (pwd != NULL) {
1131
0
    pr_signals_handle();
1132
1133
0
    pwd = af_getpwent(p, flags, &bad_entry_count);
1134
0
  }
1135
1136
0
  pr_fsio_close(af_user_file->af_file_fh);
1137
0
  af_user_file->af_file_fh = NULL;
1138
0
  af_user_file->af_lineno = 0;
1139
0
  af_user_file = NULL;
1140
1141
0
  if (bad_entry_count > 0) {
1142
0
    pr_log_pri(PR_LOG_WARNING, "bad entries (%u) detected in AuthUserFile %s",
1143
0
      bad_entry_count, path);
1144
0
    errno = EINVAL;
1145
0
    res = -1;
1146
0
  }
1147
1148
0
  return res;
1149
0
}
1150
1151
/* Authentication handlers.
1152
 */
1153
1154
0
MODRET authfile_endpwent(cmd_rec *cmd) {
1155
0
  af_endpwent();
1156
0
  return PR_DECLINED(cmd);
1157
0
}
1158
1159
0
MODRET authfile_getpwent(cmd_rec *cmd) {
1160
0
  struct passwd *pwd = NULL;
1161
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
1162
1163
0
  pwd = af_getpwent(cmd->tmp_pool, flags, NULL);
1164
1165
0
  return pwd ? mod_create_data(cmd, pwd) : PR_DECLINED(cmd);
1166
0
}
1167
1168
0
MODRET authfile_getpwnam(cmd_rec *cmd) {
1169
0
  struct passwd *pwd = NULL;
1170
0
  const char *name = cmd->argv[0];
1171
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
1172
1173
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1174
0
    return PR_DECLINED(cmd);
1175
0
  }
1176
1177
  /* Ugly -- we iterate through the file.  Time-consuming. */
1178
0
  pwd = af_getpwent(cmd->tmp_pool, flags, NULL);
1179
0
  while (pwd != NULL) {
1180
0
    pr_signals_handle();
1181
1182
0
    if (strcmp(name, pwd->pw_name) == 0) {
1183
      /* Found the requested name */
1184
0
      break;
1185
0
    }
1186
1187
0
    pwd = af_getpwent(cmd->tmp_pool, flags, NULL);
1188
0
  }
1189
1190
0
  return pwd ? mod_create_data(cmd, pwd) : PR_DECLINED(cmd);
1191
0
}
1192
1193
0
MODRET authfile_getpwuid(cmd_rec *cmd) {
1194
0
  struct passwd *pwd = NULL;
1195
0
  uid_t uid = *((uid_t *) cmd->argv[0]);
1196
1197
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1198
0
    return PR_DECLINED(cmd);
1199
0
  }
1200
1201
0
  pwd = af_getpwuid(cmd->tmp_pool, uid);
1202
1203
0
  return pwd ? mod_create_data(cmd, pwd) : PR_DECLINED(cmd);
1204
0
}
1205
1206
0
MODRET authfile_name2uid(cmd_rec *cmd) {
1207
0
  struct passwd *pwd = NULL;
1208
1209
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1210
0
    return PR_DECLINED(cmd);
1211
0
  }
1212
1213
0
  pwd = af_getpwnam(cmd->tmp_pool, cmd->argv[0]);
1214
1215
0
  return pwd ? mod_create_data(cmd, (void *) &pwd->pw_uid) : PR_DECLINED(cmd);
1216
0
}
1217
1218
0
MODRET authfile_setpwent(cmd_rec *cmd) {
1219
0
  if (af_setpwent(cmd->tmp_pool) == 0) {
1220
0
    return PR_DECLINED(cmd);
1221
0
  }
1222
1223
0
  return PR_DECLINED(cmd);
1224
0
}
1225
1226
0
MODRET authfile_uid2name(cmd_rec *cmd) {
1227
0
  struct passwd *pwd = NULL;
1228
1229
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1230
0
    return PR_DECLINED(cmd);
1231
0
  }
1232
1233
0
  pwd = af_getpwuid(cmd->tmp_pool, *((uid_t *) cmd->argv[0]));
1234
1235
0
  return pwd ? mod_create_data(cmd, pwd->pw_name) : PR_DECLINED(cmd);
1236
0
}
1237
1238
0
MODRET authfile_endgrent(cmd_rec *cmd) {
1239
0
  af_endgrent();
1240
0
  return PR_DECLINED(cmd);
1241
0
}
1242
1243
0
MODRET authfile_getgrent(cmd_rec *cmd) {
1244
0
  struct group *grp = NULL;
1245
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
1246
1247
0
  grp = af_getgrent(cmd->tmp_pool, flags, NULL);
1248
1249
0
  return grp ? mod_create_data(cmd, grp) : PR_DECLINED(cmd);
1250
0
}
1251
1252
0
MODRET authfile_getgrgid(cmd_rec *cmd) {
1253
0
  struct group *grp = NULL;
1254
0
  gid_t gid = *((gid_t *) cmd->argv[0]);
1255
1256
0
  if (af_setgrent(cmd->tmp_pool) < 0) {
1257
0
    return PR_DECLINED(cmd);
1258
0
  }
1259
1260
0
  grp = af_getgrgid(cmd->tmp_pool, gid);
1261
1262
0
  return grp ? mod_create_data(cmd, grp) : PR_DECLINED(cmd);
1263
0
}
1264
1265
0
MODRET authfile_getgrnam(cmd_rec *cmd) {
1266
0
  struct group *grp = NULL;
1267
0
  const char *name;
1268
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
1269
1270
0
  if (af_setgrent(cmd->tmp_pool) < 0) {
1271
0
    return PR_DECLINED(cmd);
1272
0
  }
1273
1274
0
  name = cmd->argv[0];
1275
1276
0
  grp = af_getgrent(cmd->tmp_pool, flags, NULL);
1277
0
  while (grp != NULL) {
1278
0
    pr_signals_handle();
1279
1280
0
    if (strcmp(name, grp->gr_name) == 0) {
1281
      /* Found the name requested */
1282
0
      break;
1283
0
    }
1284
1285
0
    grp = af_getgrent(cmd->tmp_pool, flags, NULL);
1286
0
  }
1287
1288
0
  return grp ? mod_create_data(cmd, grp) : PR_DECLINED(cmd);
1289
0
}
1290
1291
0
MODRET authfile_getgroups(cmd_rec *cmd) {
1292
0
  struct passwd *pwd = NULL;
1293
0
  struct group *grp = NULL;
1294
0
  array_header *gids = NULL, *groups = NULL;
1295
0
  char *name = cmd->argv[0];
1296
0
  int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG;
1297
1298
0
  if (name == NULL) {
1299
0
    return PR_DECLINED(cmd);
1300
0
  }
1301
1302
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1303
0
    return PR_DECLINED(cmd);
1304
0
  }
1305
1306
0
  if (af_setgrent(cmd->tmp_pool) < 0) {
1307
0
    return PR_DECLINED(cmd);
1308
0
  }
1309
1310
  /* Check for NULLs */
1311
0
  if (cmd->argv[1] != NULL) {
1312
0
    gids = (array_header *) cmd->argv[1];
1313
0
  }
1314
1315
0
  if (cmd->argv[2] != NULL) {
1316
0
    groups = (array_header *) cmd->argv[2];
1317
0
  }
1318
1319
  /* Retrieve the necessary info. */
1320
0
  pwd = af_getpwnam(cmd->tmp_pool, name);
1321
0
  if (pwd == NULL) {
1322
0
    return PR_DECLINED(cmd);
1323
0
  }
1324
1325
  /* Populate the first group ID and name. */
1326
0
  if (gids != NULL) {
1327
0
    *((gid_t *) push_array(gids)) = pwd->pw_gid;
1328
0
  }
1329
1330
0
  if (groups != NULL) {
1331
0
    grp = af_getgrgid(cmd->tmp_pool, pwd->pw_gid);
1332
1333
0
    if (grp != NULL) {
1334
0
      *((char **) push_array(groups)) = pstrdup(session.pool, grp->gr_name);
1335
0
    }
1336
0
  }
1337
1338
0
  (void) af_setgrent(cmd->tmp_pool);
1339
1340
  /* This is where things get slow, expensive, and ugly.  Loop through
1341
   * everything, checking to make sure we haven't already added it.
1342
   */
1343
0
  grp = af_getgrent(cmd->tmp_pool, flags, NULL);
1344
0
  while (grp != NULL &&
1345
0
         grp->gr_mem) {
1346
0
    char **gr_mems = NULL;
1347
1348
0
    pr_signals_handle();
1349
1350
    /* Loop through each member name listed */
1351
0
    for (gr_mems = grp->gr_mem; *gr_mems; gr_mems++) {
1352
1353
      /* If it matches the given username... */
1354
0
      if (strcmp(*gr_mems, pwd->pw_name) == 0) {
1355
1356
        /* ...add the GID and name */
1357
0
        if (gids != NULL) {
1358
0
          *((gid_t *) push_array(gids)) = grp->gr_gid;
1359
0
        }
1360
1361
0
        if (groups != NULL) {
1362
0
          *((char **) push_array(groups)) = pstrdup(session.pool, grp->gr_name);
1363
0
        }
1364
0
      }
1365
0
    }
1366
1367
0
    grp = af_getgrent(cmd->tmp_pool, flags, NULL);
1368
0
  }
1369
1370
0
  if (gids != NULL &&
1371
0
      gids->nelts > 0) {
1372
0
    return mod_create_data(cmd, (void *) &gids->nelts);
1373
0
  }
1374
1375
0
  if (groups != NULL &&
1376
0
      groups->nelts > 0) {
1377
0
    return mod_create_data(cmd, (void *) &groups->nelts);
1378
0
  }
1379
1380
0
  return PR_DECLINED(cmd);
1381
0
}
1382
1383
0
MODRET authfile_gid2name(cmd_rec *cmd) {
1384
0
  struct group *grp = NULL;
1385
1386
0
  if (af_setgrent(cmd->tmp_pool) < 0) {
1387
0
    return PR_DECLINED(cmd);
1388
0
  }
1389
1390
0
  grp = af_getgrgid(cmd->tmp_pool, *((gid_t *) cmd->argv[0]));
1391
1392
0
  return grp ? mod_create_data(cmd, grp->gr_name) : PR_DECLINED(cmd);
1393
0
}
1394
1395
0
MODRET authfile_name2gid(cmd_rec *cmd) {
1396
0
  struct group *grp = NULL;
1397
1398
0
  if (af_setgrent(cmd->tmp_pool) < 0) {
1399
0
    return PR_DECLINED(cmd);
1400
0
  }
1401
1402
0
  grp = af_getgrnam(cmd->tmp_pool, cmd->argv[0]);
1403
1404
0
  return grp ? mod_create_data(cmd, (void *) &grp->gr_gid) : PR_DECLINED(cmd);
1405
0
}
1406
1407
0
MODRET authfile_setgrent(cmd_rec *cmd) {
1408
0
  if (af_setgrent(cmd->tmp_pool) == 0) {
1409
0
    return PR_DECLINED(cmd);
1410
0
  }
1411
1412
0
  return PR_DECLINED(cmd);
1413
0
}
1414
1415
0
MODRET authfile_auth(cmd_rec *cmd) {
1416
0
  char *tmp = NULL, *cleartxt_pass = NULL;
1417
0
  const char *name = cmd->argv[0];
1418
1419
0
  if (af_setpwent(cmd->tmp_pool) < 0) {
1420
0
    return PR_DECLINED(cmd);
1421
0
  }
1422
1423
  /* Lookup the cleartxt password for this user. */
1424
0
  tmp = af_getpwpass(cmd->tmp_pool, name);
1425
0
  if (tmp == NULL) {
1426
1427
    /* For now, return DECLINED.  Ideally, we could stash an auth module
1428
     * identifier in the session structure, so that all auth modules could
1429
     * coordinate/use their methods as long as they matched the auth module
1430
     * used.
1431
     */
1432
0
    return PR_DECLINED(cmd);
1433
1434
#if 0
1435
    /* When the above is implemented, and if the user being checked was
1436
     * provided by mod_auth_file, we'd return this.
1437
     */
1438
    return PR_ERROR_INT(cmd, PR_AUTH_NOPWD);
1439
#endif
1440
0
  }
1441
1442
0
  cleartxt_pass = pstrdup(cmd->tmp_pool, tmp);
1443
1444
0
  if (pr_auth_check(cmd->tmp_pool, cleartxt_pass, name, cmd->argv[1])) {
1445
0
    return PR_ERROR_INT(cmd, PR_AUTH_BADPWD);
1446
0
  }
1447
1448
0
  session.auth_mech = "mod_auth_file.c";
1449
0
  return PR_HANDLED(cmd);
1450
0
}
1451
1452
/* Per Bug#4171, if we see EINVAL (or EPERM, as documented in same man pages),
1453
 * check the /proc/sys/crypto/fips_enabled setting and the salt string, to see
1454
 * if an unsupported algorithm in FIPS mode, e.g. DES or MD5, was used to
1455
 * generate this salt string.
1456
 *
1457
 * There's not much we can do at this point other than log a message for the
1458
 * admin that this is the case, and let them know how to fix things (if they
1459
 * can).  Ultimately this breakage comes from those kind folks distributing
1460
 * glibc.  Sigh.
1461
 */
1462
static void check_unsupported_algo(const char *user,
1463
0
    const char *ciphertxt_pass, size_t ciphertxt_passlen) {
1464
0
  FILE *fp = NULL;
1465
0
  char fips_enabled[256];
1466
0
  size_t len = 0, sz = 0;
1467
1468
  /* First, read in /proc/sys/crypto/fips_enabled. */
1469
0
  fp = fopen("/proc/sys/crypto/fips_enabled", "r");
1470
0
  if (fp == NULL) {
1471
0
    pr_trace_msg(trace_channel, 4,
1472
0
      "unable to open /proc/sys/crypto/fips_enabled: %s", strerror(errno));
1473
0
    return;
1474
0
  }
1475
1476
0
  memset(fips_enabled, '\0', sizeof(fips_enabled));
1477
0
  sz = sizeof(fips_enabled)-1;
1478
0
  len = fread(fips_enabled, 1, sz, fp);
1479
0
  if (len == 0) {
1480
0
    if (feof(fp)) {
1481
      /* An empty /proc/sys/crypto/fips_enabled?  Weird. */
1482
0
      pr_trace_msg(trace_channel, 4,
1483
0
        "/proc/sys/crypto/fips_enabled is unexpectedly empty!");
1484
1485
0
    } else if (ferror(fp)) {
1486
0
      pr_trace_msg(trace_channel, 4,
1487
0
        "error reading /proc/sys/crypto/fips_enabled: %s", strerror(errno));
1488
0
    }
1489
1490
0
    fclose(fp);
1491
0
    return;
1492
0
  }
1493
1494
0
  fclose(fp);
1495
1496
  /* Trim any newline. */
1497
0
  if (fips_enabled[len-1] == '\n') {
1498
0
    fips_enabled[len-1] = '\0';
1499
0
  }
1500
1501
0
  if (strcmp(fips_enabled, "0") != 0) {
1502
    /* FIPS mode enabled on this system.  If our salt string doesn't start
1503
     * with a '$', it uses DES; if it starts with '$1$', it uses MD5.  Either
1504
     * way, on a FIPS-enabled system, those algorithms aren't supported.
1505
     */
1506
0
    if (ciphertxt_pass[0] != '$') {
1507
      /* DES */
1508
0
      pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION
1509
0
        ": AuthUserFile entry for user '%s' uses DES, which is not supported "
1510
0
        "on a FIPS-enabled system (see /proc/sys/crypto/fips_enabled)", user);
1511
0
      pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION
1512
0
        ": recommend updating user '%s' entry to use SHA256/SHA512 "
1513
0
        "(using ftpasswd --sha256/--sha512)", user);
1514
1515
0
    } else if (ciphertxt_passlen >= 3 &&
1516
0
               strncmp(ciphertxt_pass, "$1$", 3) == 0) {
1517
      /* MD5 */
1518
0
      pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION
1519
0
        ": AuthUserFile entry for user '%s' uses MD5, which is not supported "
1520
0
        "on a FIPS-enabled system (see /proc/sys/crypto/fips_enabled)", user);
1521
0
      pr_log_pri(PR_LOG_ERR, MOD_AUTH_FILE_VERSION
1522
0
        ": recommend updating user '%s' entry to use SHA256/SHA512 "
1523
0
        "(using ftpasswd --sha256/--sha512)", user);
1524
1525
0
    } else {
1526
0
      pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
1527
0
        ": possible illegal salt characters in AuthUserFile entry "
1528
0
        "for user '%s'?", user);
1529
0
    }
1530
1531
0
  } else {
1532
    /* The only other time crypt(3) would return EINVAL/EPERM, on a system
1533
     * with procfs, is if the salt characters were illegal.  Right?
1534
     */
1535
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
1536
0
      ": possible illegal salt characters in AuthUserFile entry for "
1537
0
      "user '%s'?", user);
1538
0
  }
1539
0
}
1540
1541
0
MODRET authfile_chkpass(cmd_rec *cmd) {
1542
0
  const char *ciphertxt_pass = cmd->argv[0];
1543
0
  const char *cleartxt_pass = cmd->argv[2];
1544
0
  char *crypted_pass = NULL;
1545
0
  size_t ciphertxt_passlen = 0;
1546
0
  int xerrno;
1547
1548
0
  if (ciphertxt_pass == NULL) {
1549
0
    pr_log_debug(DEBUG2, MOD_AUTH_FILE_VERSION
1550
0
      ": missing ciphertext password for comparison");
1551
0
    return PR_DECLINED(cmd);
1552
0
  }
1553
1554
0
  if (cleartxt_pass == NULL) {
1555
0
    pr_log_debug(DEBUG2, MOD_AUTH_FILE_VERSION
1556
0
      ": missing client-provided password for comparison");
1557
0
    return PR_DECLINED(cmd);
1558
0
  }
1559
1560
  /* Even though the AuthUserFile is not used here, there must be one
1561
   * configured before this function should attempt to check the password.
1562
   * Otherwise, it could be checking a password retrieved by some other
1563
   * auth module.
1564
   */
1565
0
  if (af_user_file == NULL) {
1566
0
    return PR_DECLINED(cmd);
1567
0
  }
1568
1569
0
  crypted_pass = crypt(cleartxt_pass, ciphertxt_pass);
1570
0
  xerrno = errno;
1571
1572
0
  ciphertxt_passlen = strlen(ciphertxt_pass);
1573
0
  if (handle_empty_salt == TRUE &&
1574
0
      ciphertxt_passlen == 0) {
1575
0
    crypted_pass = "";
1576
0
  }
1577
1578
0
  if (crypted_pass == NULL) {
1579
0
    const char *user;
1580
1581
0
    user = cmd->argv[1];
1582
0
    pr_log_debug(DEBUG0, MOD_AUTH_FILE_VERSION
1583
0
      ": error using crypt(3) for user '%s': %s", user, strerror(xerrno));
1584
1585
0
    if (ciphertxt_passlen > 0 &&
1586
0
        (xerrno == EINVAL ||
1587
0
         xerrno == EPERM)) {
1588
0
      check_unsupported_algo(user, ciphertxt_pass, ciphertxt_passlen);
1589
0
    }
1590
1591
0
    return PR_DECLINED(cmd);
1592
0
  }
1593
1594
0
  if (strcmp(crypted_pass, ciphertxt_pass) == 0) {
1595
0
    session.auth_mech = "mod_auth_file.c";
1596
0
    return PR_HANDLED(cmd);
1597
0
  }
1598
1599
0
  return PR_DECLINED(cmd);
1600
0
}
1601
1602
/* Configuration handlers
1603
 */
1604
1605
/* usage: AuthFileOptions opt1 ... */
1606
0
MODRET set_authfileoptions(cmd_rec *cmd) {
1607
0
  config_rec *c = NULL;
1608
0
  register unsigned int i = 0;
1609
0
  unsigned long opts = 0UL;
1610
1611
0
  if (cmd->argc-1 == 0) {
1612
0
    CONF_ERROR(cmd, "wrong number of parameters");
1613
0
  }
1614
1615
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1616
1617
0
  c = add_config_param(cmd->argv[0], 1, NULL);
1618
1619
0
  for (i = 1; i < cmd->argc; i++) {
1620
0
    if (strcmp(cmd->argv[i], "InsecurePerms") == 0) {
1621
0
      opts |= AUTH_FILE_OPT_INSECURE_PERMS;
1622
1623
      /* Note that this option disables some parse-time checks, so we need
1624
       * to set it globally now, rather than at sess_init time.
1625
       */
1626
0
      auth_file_opts |= AUTH_FILE_OPT_INSECURE_PERMS;
1627
1628
0
    } else if (strcmp(cmd->argv[i], "SyntaxCheck") == 0) {
1629
1630
      /* Note that this option enables some parse-time checks, so we need
1631
       * to set it globally now, rather than at sess_init time.
1632
       */
1633
0
      auth_file_opts |= AUTH_FILE_OPT_SYNTAX_CHECK;
1634
1635
0
    } else {
1636
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown AuthFileOption '",
1637
0
        cmd->argv[i], "'", NULL));
1638
0
    }
1639
0
  }
1640
1641
0
  c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
1642
0
  *((unsigned long *) c->argv[0]) = opts;
1643
1644
0
  return PR_HANDLED(cmd);
1645
0
}
1646
1647
/* usage: AuthGroupFile path [id <min-max>] [name <regex>] */
1648
0
MODRET set_authgroupfile(cmd_rec *cmd) {
1649
0
  config_rec *c = NULL;
1650
0
  authfile_file_t *file = NULL;
1651
0
  int flags = 0;
1652
0
  char *path;
1653
1654
0
#ifdef PR_USE_REGEX
1655
0
  if (cmd->argc-1 < 1 ||
1656
0
      cmd->argc-1 > 5) {
1657
#else
1658
  if (cmd->argc-1 < 1 ||
1659
      cmd->argc-1 > 2) {
1660
#endif /* regex support */
1661
0
    CONF_ERROR(cmd, "wrong number of parameters");
1662
0
  }
1663
1664
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1665
1666
0
  path = cmd->argv[1];
1667
0
  if (*path != '/') {
1668
0
    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1669
0
      "unable to use relative path for ", (char *) cmd->argv[0], " '",
1670
0
      path, "'.", NULL));
1671
0
  }
1672
1673
0
  if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) {
1674
0
    int res, xerrno;
1675
1676
    /* Make sure the configured file has the correct permissions.  Note that
1677
     * AuthGroupFiles, unlike AuthUserFiles, do not contain any sensitive
1678
     * information, and can thus be world-readable.
1679
     */
1680
0
    flags = PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE;
1681
1682
0
    PRIVS_ROOT
1683
0
    res = af_check_file(cmd->tmp_pool, cmd->argv[0], path, flags);
1684
0
    xerrno = errno;
1685
0
    PRIVS_RELINQUISH
1686
1687
0
    if (res < 0) {
1688
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1689
0
        "unable to use ", path, ": ", strerror(xerrno), NULL));
1690
0
    }
1691
0
  }
1692
1693
0
  if (auth_file_opts & AUTH_FILE_OPT_SYNTAX_CHECK) {
1694
0
    if (af_check_group_syntax(cmd->tmp_pool, path) < 0) {
1695
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1696
0
        "unable to use ", path, ": ", strerror(errno), NULL));
1697
0
    }
1698
0
  }
1699
1700
0
  c = add_config_param(cmd->argv[0], 1, NULL);
1701
1702
0
  file = pcalloc(c->pool, sizeof(authfile_file_t));
1703
0
  file->af_path = pstrdup(c->pool, path);
1704
0
  c->argv[0] = (void *) file;
1705
1706
  /* Check for restrictions */
1707
0
  if (cmd->argc-1 != 1) {
1708
0
    register unsigned int i = 0;
1709
1710
0
    for (i = 2; i < cmd->argc; i++) {
1711
0
      if (strcasecmp(cmd->argv[i], "id") == 0) {
1712
0
        gid_t min, max;
1713
0
        char *sep = NULL, *tmp = NULL;
1714
1715
        /* The range restriction parameter is of the form "min-max", where max
1716
         * must be >= min.
1717
         */
1718
1719
0
        sep = strchr(cmd->argv[++i], '-');
1720
0
        if (sep == NULL) {
1721
0
          CONF_ERROR(cmd, "badly formatted ID restriction parameter");
1722
0
        }
1723
1724
0
        *sep = '\0';
1725
1726
0
        min = strtol(cmd->argv[i], &tmp, 10);
1727
0
        if (tmp && *tmp) {
1728
0
          CONF_ERROR(cmd, "badly formatted minimum ID");
1729
0
        }
1730
1731
0
        tmp = NULL;
1732
1733
0
        max = strtol(sep+1, &tmp, 10);
1734
0
        if (tmp && *tmp) {
1735
0
          CONF_ERROR(cmd, "badly formatted maximum ID");
1736
0
        }
1737
1738
0
        if (min > max) {
1739
0
          CONF_ERROR(cmd, "minimum cannot be larger than maximum");
1740
0
        }
1741
1742
0
        file->af_min_id.gid = min;
1743
0
        file->af_max_id.gid = max;
1744
0
        file->af_restricted_ids = TRUE;
1745
1746
0
#ifdef PR_USE_REGEX
1747
0
      } else if (strcasecmp(cmd->argv[i], "name") == 0) {
1748
0
        char *filter = cmd->argv[++i];
1749
0
        pr_regex_t *pre = NULL;
1750
0
        int res = 0;
1751
1752
0
        pre = pr_regexp_alloc(&auth_file_module);
1753
1754
        /* Check for a ! negation/inversion filter prefix. */
1755
0
        if (*filter == '!') {
1756
0
          filter++;
1757
0
          file->af_name_regex_inverted = TRUE;
1758
0
        }
1759
1760
0
        res = pr_regexp_compile(pre, filter, REG_EXTENDED|REG_NOSUB);
1761
0
        if (res != 0) {
1762
0
          char errstr[200] = {'\0'};
1763
1764
0
          pr_regexp_error(res, pre, errstr, sizeof(errstr));
1765
0
          pr_regexp_free(NULL, pre);
1766
1767
0
          CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", filter, "' failed "
1768
0
            "regex compilation: ", errstr, NULL));
1769
0
        }
1770
1771
0
        file->af_name_filter = pstrdup(c->pool, cmd->argv[i]);
1772
0
        file->af_name_regex = pre;
1773
0
        file->af_restricted_names = TRUE;
1774
0
#endif /* regex support */
1775
1776
0
      } else {
1777
0
        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown restriction '",
1778
0
          cmd->argv[i], "'", NULL));
1779
0
      }
1780
0
    }
1781
0
  }
1782
1783
0
  return PR_HANDLED(cmd);
1784
0
}
1785
1786
/* usage: AuthUserFile path [home <regexp>] [id <min-max>] [name <regex>] */
1787
0
MODRET set_authuserfile(cmd_rec *cmd) {
1788
0
  config_rec *c = NULL;
1789
0
  authfile_file_t *file = NULL;
1790
0
  int flags = 0;
1791
0
  char *path;
1792
1793
0
#ifdef PR_USE_REGEX
1794
0
  if (cmd->argc-1 < 1 ||
1795
0
      cmd->argc-1 > 7) {
1796
#else
1797
  if (cmd->argc-1 < 1 ||
1798
      cmd->argc-1 > 2) {
1799
#endif /* regex support */
1800
0
    CONF_ERROR(cmd, "wrong number of parameters");
1801
0
  }
1802
1803
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1804
1805
0
  path = cmd->argv[1];
1806
0
  if (*path != '/') {
1807
0
    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1808
0
      "unable to use relative path for ", (char *) cmd->argv[0], " '",
1809
0
      path, "'.", NULL));
1810
0
  }
1811
1812
0
  if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) {
1813
0
    int res, xerrno;
1814
1815
    /* Make sure the configured file has the correct permissions.  Note that
1816
     * AuthUserFiles, unlike AuthGroupFiles, DO contain any sensitive
1817
     * information, and thus CANNOT be world-readable.
1818
     */
1819
0
    flags = 0;
1820
1821
0
    PRIVS_ROOT
1822
0
    res = af_check_file(cmd->tmp_pool, cmd->argv[0], path, flags);
1823
0
    xerrno = errno;
1824
0
    PRIVS_RELINQUISH
1825
1826
0
    if (res < 0) {
1827
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1828
0
        "unable to use ", path, ": ", strerror(xerrno), NULL));
1829
0
    }
1830
0
  }
1831
1832
0
  if (auth_file_opts & AUTH_FILE_OPT_SYNTAX_CHECK) {
1833
0
    if (af_check_user_syntax(cmd->tmp_pool, path) < 0) {
1834
0
      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool,
1835
0
        "unable to use ", path, ": ", strerror(errno), NULL));
1836
0
    }
1837
0
  }
1838
0
  c = add_config_param(cmd->argv[0], 1, NULL);
1839
1840
0
  file = pcalloc(c->pool, sizeof(authfile_file_t));
1841
0
  file->af_path = pstrdup(c->pool, path);
1842
0
  c->argv[0] = (void *) file;
1843
1844
  /* Check for restrictions */
1845
0
  if (cmd->argc-1 != 1) {
1846
0
    register unsigned int i = 0;
1847
1848
0
    for (i = 2; i < cmd->argc; i++) {
1849
0
      if (strcasecmp(cmd->argv[i], "id") == 0) {
1850
0
        uid_t min, max;
1851
0
        char *sep = NULL, *tmp = NULL;
1852
1853
        /* The range restriction parameter is of the form "min-max", where max
1854
         * must be >= min.
1855
         */
1856
1857
0
        sep = strchr(cmd->argv[++i], '-');
1858
0
        if (sep == NULL) {
1859
0
          CONF_ERROR(cmd, "badly formatted ID restriction parameter");
1860
0
        }
1861
1862
0
        *sep = '\0';
1863
1864
0
        min = strtol(cmd->argv[i], &tmp, 10);
1865
0
        if (tmp && *tmp) {
1866
0
          CONF_ERROR(cmd, "badly formatted minimum ID");
1867
0
        }
1868
1869
0
        tmp = NULL;
1870
1871
0
        max = strtol(sep+1, &tmp, 10);
1872
1873
0
        if (tmp && *tmp) {
1874
0
          CONF_ERROR(cmd, "badly formatted maximum ID");
1875
0
        }
1876
1877
0
        if (min > max) {
1878
0
          CONF_ERROR(cmd, "minimum cannot be larger than maximum");
1879
0
        }
1880
1881
0
        file->af_min_id.uid = min;
1882
0
        file->af_max_id.uid = max;
1883
0
        file->af_restricted_ids = TRUE;
1884
1885
0
#ifdef PR_USE_REGEX
1886
0
      } else if (strcasecmp(cmd->argv[i], "home") == 0) {
1887
0
        char *filter = cmd->argv[++i];
1888
0
        pr_regex_t *pre = NULL;
1889
0
        int res = 0;
1890
1891
0
        pre = pr_regexp_alloc(&auth_file_module);
1892
1893
        /* Check for a ! negation/inversion filter prefix. */
1894
0
        if (*filter == '!') {
1895
0
          filter++;
1896
0
          file->af_home_regex_inverted = TRUE;
1897
0
        }
1898
1899
0
        res = pr_regexp_compile(pre, filter, REG_EXTENDED|REG_NOSUB);
1900
0
        if (res != 0) {
1901
0
          char errstr[200] = {'\0'};
1902
1903
0
          pr_regexp_error(res, pre, errstr, sizeof(errstr));
1904
0
          pr_regexp_free(NULL, pre);
1905
1906
0
          CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", filter, "' failed "
1907
0
            "regex compilation: ", errstr, NULL));
1908
0
        }
1909
1910
0
        file->af_home_filter = pstrdup(c->pool, cmd->argv[i]);
1911
0
        file->af_home_regex = pre;
1912
0
        file->af_restricted_homes = TRUE;
1913
1914
0
      } else if (strcasecmp(cmd->argv[i], "name") == 0) {
1915
0
        char *filter = cmd->argv[++i];
1916
0
        pr_regex_t *pre = NULL;
1917
0
        int res = 0;
1918
1919
0
        pre = pr_regexp_alloc(&auth_file_module);
1920
1921
        /* Check for a ! negation/inversion filter prefix. */
1922
0
        if (*filter == '!') {
1923
0
          filter++;
1924
0
          file->af_name_regex_inverted = TRUE;
1925
0
        }
1926
1927
0
        res = pr_regexp_compile(pre, filter, REG_EXTENDED|REG_NOSUB);
1928
0
        if (res != 0) {
1929
0
          char errstr[200] = {'\0'};
1930
1931
0
          pr_regexp_error(res, pre, errstr, sizeof(errstr));
1932
0
          pr_regexp_free(NULL, pre);
1933
1934
0
          CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", filter, "' failed "
1935
0
            "regex compilation: ", errstr, NULL));
1936
0
        }
1937
1938
0
        file->af_name_filter = pstrdup(c->pool, cmd->argv[i]);
1939
0
        file->af_name_regex = pre;
1940
0
        file->af_restricted_names = TRUE;
1941
0
#endif /* regex support */
1942
1943
0
      } else {
1944
0
        CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown restriction '",
1945
0
          cmd->argv[i], "'", NULL));
1946
0
      }
1947
0
    }
1948
0
  }
1949
1950
0
  return PR_HANDLED(cmd);
1951
0
}
1952
1953
/* Event listeners
1954
 */
1955
1956
0
static void authfile_sess_reinit_ev(const void *event_data, void *user_data) {
1957
0
  int res;
1958
1959
  /* A HOST command changed the main_server pointer, reinitialize ourselves. */
1960
1961
0
  pr_event_unregister(&auth_file_module, "core.session-reinit",
1962
0
    authfile_sess_reinit_ev);
1963
1964
0
  af_user_file = NULL;
1965
0
  af_group_file = NULL;
1966
1967
0
  res = authfile_sess_init();
1968
0
  if (res < 0) {
1969
0
    pr_session_disconnect(&auth_file_module,
1970
0
      PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
1971
0
  }
1972
0
}
1973
1974
/* Initialization routines
1975
 */
1976
1977
0
static int authfile_init(void) {
1978
0
  const char *key, *salt, *hash;
1979
1980
  /* On some Unix platforms, giving crypt(3) an empty string for the salt,
1981
   * no matter what the input key, results in an empty string being returned.
1982
   * (The salt string is what is obtained from the AuthUserFile that has been
1983
   * configured.)
1984
   *
1985
   * On other platforms, given crypt(3) a real key and an empty string for
1986
   * the salt returns in a real string.  (I'm looking at you, Mac OSX.)
1987
   *
1988
   * Thus in order to handle the edge case of an AuthUserFile with a passwd
1989
   * field being empty the same on such differing platforms, we perform a
1990
   * runtime check (at startup), to see how crypt(3) behaves -- and then
1991
   * preserve the principle of least surprise appropriately.
1992
   */
1993
1994
0
  key = "key";
1995
0
  salt = "";
1996
0
  hash = crypt(key, salt);
1997
0
  if (hash != NULL) {
1998
0
    if (strcmp(hash, "") != 0) {
1999
      /* We're probably on a Mac OSX or similar platform. */
2000
0
      handle_empty_salt = TRUE;
2001
0
    }
2002
0
  }
2003
2004
0
  return 0;
2005
0
}
2006
2007
0
static int authfile_sess_init(void) {
2008
0
  config_rec *c = NULL;
2009
2010
0
  pr_event_register(&auth_file_module, "core.session-reinit",
2011
0
    authfile_sess_reinit_ev, NULL);
2012
2013
0
  c = find_config(main_server->conf, CONF_PARAM, "AuthUserFile", FALSE);
2014
0
  if (c != NULL) {
2015
0
    af_user_file = c->argv[0];
2016
0
  }
2017
2018
0
  c = find_config(main_server->conf, CONF_PARAM, "AuthGroupFile", FALSE);
2019
0
  if (c != NULL) {
2020
0
    af_group_file = c->argv[0];
2021
0
  }
2022
2023
0
  return 0;
2024
0
}
2025
2026
/* Module API tables
2027
 */
2028
2029
static conftable authfile_conftab[] = {
2030
  { "AuthFileOptions",  set_authfileoptions,  NULL },
2031
  { "AuthGroupFile",  set_authgroupfile,  NULL },
2032
  { "AuthUserFile", set_authuserfile, NULL },
2033
  { NULL }
2034
};
2035
2036
static authtable authfile_authtab[] = {
2037
2038
  /* User information callbacks */
2039
  { 0, "endpwent",  authfile_endpwent },
2040
  { 0, "getpwent",  authfile_getpwent },
2041
  { 0, "getpwnam",  authfile_getpwnam },
2042
  { 0, "getpwuid",  authfile_getpwuid },
2043
  { 0, "name2uid",  authfile_name2uid },
2044
  { 0, "setpwent",  authfile_setpwent },
2045
  { 0, "uid2name",  authfile_uid2name },
2046
2047
  /* Group information callbacks */
2048
  { 0, "endgrent",  authfile_endgrent },
2049
  { 0, "getgrent",  authfile_getgrent },
2050
  { 0, "getgrgid",  authfile_getgrgid },
2051
  { 0, "getgrnam",  authfile_getgrnam },
2052
  { 0, "getgroups", authfile_getgroups },
2053
  { 0, "gid2name",  authfile_gid2name },
2054
  { 0, "name2gid",  authfile_name2gid },
2055
  { 0, "setgrent",  authfile_setgrent },
2056
2057
  /* Miscellaneous callbacks */
2058
  { 0, "auth",    authfile_auth },
2059
  { 0, "check",   authfile_chkpass },
2060
2061
  { 0, NULL, NULL }
2062
};
2063
2064
module auth_file_module = {
2065
  /* Always NULL */
2066
  NULL, NULL,
2067
2068
  /* Module API version 2.0 */
2069
  0x20,
2070
2071
  /* Module name */
2072
  "auth_file",
2073
2074
  /* Module configuration handler table */
2075
  authfile_conftab,
2076
2077
  /* Module command handler table */
2078
  NULL,
2079
2080
  /* Module authentication handler table */
2081
  authfile_authtab,
2082
2083
  /* Module initialization function */
2084
  authfile_init,
2085
2086
  /* Session initialization function */
2087
  authfile_sess_init,
2088
2089
  /* Module version */
2090
  MOD_AUTH_FILE_VERSION
2091
};