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