Coverage Report

Created: 2026-09-03 06:37

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