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