Coverage Report

Created: 2024-09-30 06:24

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