Index: proftpd-dfsg-1.3.6c/modules/mod_auth_file.c =================================================================== --- proftpd-dfsg-1.3.6c.orig/modules/mod_auth_file.c +++ proftpd-dfsg-1.3.6c/modules/mod_auth_file.c @@ -1,7 +1,7 @@ /* * ProFTPD: mod_auth_file - file-based authentication module that supports * restrictions on the file contents - * Copyright (c) 2002-2016 The ProFTPD Project team + * Copyright (c) 2002-2021 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,10 +40,6 @@ # error "ProFTPD 1.2.7rc2 or later required" #endif -#ifndef BUFSIZ -# define BUFSIZ PR_TUNABLE_BUFFER_SIZE -#endif /* !BUFSIZ */ - module auth_file_module; typedef union { @@ -54,7 +50,7 @@ typedef union { typedef struct file_rec { char *af_path; - FILE *af_file; + pr_fh_t *af_file_fh; unsigned int af_lineno; unsigned char af_restricted_ids; @@ -80,6 +76,17 @@ typedef struct file_rec { /* List of server-specific AuthFiles */ static authfile_file_t *af_user_file = NULL; static authfile_file_t *af_group_file = NULL; +static unsigned long auth_file_opts = 0UL; + +/* Tell mod_auth_file to skip/ignore the permissions checks on the configured + * AuthUserFile/AuthGroupFile. + */ +#define AUTH_FILE_OPT_INSECURE_PERMS 0x0001 + +/* Tell mod_auth_file to perform a syntax check of the configured files on + * startup. + */ +#define AUTH_FILE_OPT_SYNTAX_CHECK 0x0002 static int handle_empty_salt = FALSE; @@ -93,6 +100,7 @@ static const char *trace_channel = "auth /* Support routines. Move the passwd/group functions out of lib/ into here. */ #define PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE 0x001 +#define PR_AUTH_FILE_FL_USE_TRACE_LOG 0x002 static int af_check_parent_dir(pool *p, const char *name, const char *path) { struct stat st; @@ -242,18 +250,17 @@ static int af_check_file(pool *p, const return 0; } -#ifndef HAVE_FGETPWENT - #define NPWDFIELDS 7 -static char pwdbuf[BUFSIZ]; +static char pwdbuf[PR_TUNABLE_BUFFER_SIZE]; static char *pwdfields[NPWDFIELDS]; static struct passwd pwent; -static struct passwd *af_getpasswd(const char *buf, unsigned int lineno) { +static struct passwd *af_parse_passwd(const char *buf, unsigned int lineno, + int flags) { register unsigned int i; register char *cp = NULL; - char *ep = NULL, *buffer = NULL; + char *ptr = NULL, *buffer = NULL; char **fields = NULL; struct passwd *pwd = NULL; @@ -261,8 +268,8 @@ static struct passwd *af_getpasswd(const buffer = pwdbuf; pwd = &pwent; - sstrncpy(buffer, buf, BUFSIZ-1); - buffer[BUFSIZ-1] = '\0'; + sstrncpy(buffer, buf, PR_TUNABLE_BUFFER_SIZE-1); + buffer[PR_TUNABLE_BUFFER_SIZE-1] = '\0'; for (cp = buffer, i = 0; i < NPWDFIELDS && cp; i++) { fields[i] = cp; @@ -279,27 +286,62 @@ static struct passwd *af_getpasswd(const } if (i != NPWDFIELDS) { - pr_log_pri(PR_LOG_ERR, "Malformed entry in AuthUserFile file (line %u)", - lineno); + pr_log_pri(PR_LOG_ERR, + "Malformed entry in AuthUserFile file (field count %d != %d, line %u)", + i, (int) NPWDFIELDS, lineno); return NULL; } + pwd->pw_name = fields[0]; + pwd->pw_passwd = fields[1]; + if (*fields[2] == '\0' || *fields[3] == '\0') { + if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) { + pr_trace_msg(trace_channel, 3, + "missing UID/GID fields for user '%.100s' (line %u), skipping", + pwd->pw_name, lineno); + + } else { + pr_log_pri(PR_LOG_WARNING, "AuthUserFile: missing UID/GID fields for " + "user '%.100s' (line %u), skipping", pwd->pw_name, lineno); + } + return NULL; } - pwd->pw_name = fields[0]; - pwd->pw_passwd = fields[1]; + ptr = NULL; + pwd->pw_uid = strtol(fields[2], &ptr, 10); + if (*ptr != '\0') { + if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) { + pr_trace_msg(trace_channel, 3, + "non-numeric UID field '%.100s' for user '%.100s' (line %u), skipping", + fields[2], pwd->pw_name, lineno); + + } else { + pr_log_pri(PR_LOG_WARNING, "AuthUserFile: non-numeric UID field " + "'%.100s' for user '%.100s' (line %u), skipping", fields[2], + pwd->pw_name, lineno); + } - if (fields[2][0] == '\0' || - ((pwd->pw_uid = strtol(fields[2], &ep, 10)) == 0 && *ep)) { - return NULL; + return NULL; } - if (fields[3][0] == '\0' || - ((pwd->pw_gid = strtol(fields[3], &ep, 10)) == 0 && *ep)) { - return NULL; + ptr = NULL; + pwd->pw_gid = strtol(fields[3], &ptr, 10); + if (*ptr != '\0') { + if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) { + pr_trace_msg(trace_channel, 3, + "non-numeric GID field '%.100s' for user '%.100s' (line %u), skipping", + fields[3], pwd->pw_name, lineno); + + } else { + pr_log_pri(PR_LOG_WARNING, "AuthUserFile: non-numeric GID field " + "'%.100s' for user '%.100s' (line %u), skipping", fields[3], + pwd->pw_name, lineno); + } + + return NULL; } pwd->pw_gecos = fields[4]; @@ -308,9 +350,6 @@ static struct passwd *af_getpasswd(const return pwd; } -#endif /* !HAVE_FGETPWENT */ - -#ifndef HAVE_FGETGRENT #define MAXMEMBERS 4096 #define NGRPFIELDS 4 @@ -320,11 +359,14 @@ static struct group grent; static char *grpfields[NGRPFIELDS]; static char *members[MAXMEMBERS+1]; -static char *af_getgrentline(char **buf, int *buflen, FILE *fp, +static char *af_getgrentline(char **buf, int *buflen, pr_fh_t *fh, unsigned int *lineno) { char *cp = *buf; + int original_buflen; - while (fgets(cp, (*buflen) - (cp - *buf), fp) != NULL) { + original_buflen = *buflen; + + while (pr_fsio_gets(cp, (*buflen) - (cp - *buf), fh) != NULL) { pr_signals_handle(); (*lineno)++; @@ -334,8 +376,12 @@ static char *af_getgrentline(char **buf, return *buf; } - /* No -- allocate a larger buffer, doubling buflen. */ - *buflen += *buflen; + /* No -- allocate a larger buffer. Note that doubling the buflen + * each time may cause issues; fgetgrent(3) would increment the + * allocated buffer by the original buffer length each time. So we + * do the same (Issue #1321). + */ + *buflen += original_buflen; { char *new_buf; @@ -379,13 +425,14 @@ static char **af_getgrmems(char *s) { return members; } -static struct group *af_getgrp(const char *buf, unsigned int lineno) { - int i; +static struct group *af_parse_grp(const char *buf, unsigned int lineno, + int flags) { + unsigned int i; char *cp; i = strlen(buf) + 1; - if (!grpbuf) { + if (grpbuf == NULL) { grpbuf = malloc(i); } else { @@ -399,8 +446,9 @@ static struct group *af_getgrp(const cha grpbuf = new_buf; } - if (!grpbuf) + if (grpbuf == NULL) { return NULL; + } sstrncpy(grpbuf, buf, i); @@ -413,7 +461,7 @@ static struct group *af_getgrp(const cha grpfields[i] = cp; cp = strchr(cp, ':'); - if (cp) { + if (cp != NULL) { *cp++ = 0; } } @@ -424,18 +472,42 @@ static struct group *af_getgrp(const cha return NULL; } + grent.gr_name = grpfields[0]; + grent.gr_passwd = grpfields[1]; + if (*grpfields[2] == '\0') { + if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) { + pr_trace_msg(trace_channel, 3, + "missing GID field for group '%.100s' (line %u), skipping", + grent.gr_name, lineno); + + } else { + pr_log_pri(PR_LOG_WARNING, "AuthGroupFile: missing GID field for " + "group '%.100s' (line %u), skipping", grent.gr_name, lineno); + } + return NULL; } - grent.gr_name = grpfields[0]; - grent.gr_passwd = grpfields[1]; - grent.gr_gid = atoi(grpfields[2]); + cp = NULL; + grent.gr_gid = strtol(grpfields[2], &cp, 10); + if (*cp != '\0') { + if (flags & PR_AUTH_FILE_FL_USE_TRACE_LOG) { + pr_trace_msg(trace_channel, 3, + "non-numeric GID field '%.100s' for group '%.100s' (line %u)", + grpfields[2], grent.gr_name, lineno); + + } else { + pr_log_pri(PR_LOG_WARNING, "AuthGroupFile: non-numeric GID field " + "'%.100s' for group '%.100s' (line %u)", grpfields[2], + grent.gr_name, lineno); + } + } + grent.gr_mem = af_getgrmems(grpfields[3]); return &grent; } -#endif /* !HAVE_FGETGRENT */ static int af_allow_grent(pool *p, struct group *grp) { if (af_group_file == NULL) { @@ -488,43 +560,38 @@ static int af_allow_grent(pool *p, struc } static void af_endgrent(void) { - if (af_group_file && - af_group_file->af_file) { - fclose(af_group_file->af_file); - af_group_file->af_file = NULL; + if (af_group_file != NULL && + af_group_file->af_file_fh != NULL) { + pr_fsio_close(af_group_file->af_file_fh); + af_group_file->af_file_fh = NULL; af_group_file->af_lineno = 0; } - - return; } -static struct group *af_getgrent(pool *p) { +static struct group *af_getgrent(pool *p, int flags, + unsigned int *bad_entry_count) { struct group *grp = NULL, *res = NULL; - if (!af_group_file || - !af_group_file->af_file) { + if (af_group_file == NULL || + af_group_file->af_file_fh == NULL) { errno = EINVAL; return NULL; } while (TRUE) { -#ifdef HAVE_FGETGRENT - pr_signals_handle(); - grp = fgetgrent(af_group_file->af_file); -#else char *cp = NULL, *buf = NULL; - int buflen = BUFSIZ; + int buflen = PR_TUNABLE_BUFFER_SIZE; pr_signals_handle(); - buf = malloc(BUFSIZ); + buf = malloc(buflen); if (buf == NULL) { pr_log_pri(PR_LOG_ALERT, "Out of memory!"); _exit(1); } grp = NULL; - while (af_getgrentline(&buf, &buflen, af_group_file->af_file, + while (af_getgrentline(&buf, &buflen, af_group_file->af_file_fh, &(af_group_file->af_lineno)) != NULL) { pr_signals_handle(); @@ -540,12 +607,19 @@ static struct group *af_getgrent(pool *p *cp = '\0'; } - grp = af_getgrp(buf, af_group_file->af_lineno); - free(buf); + grp = af_parse_grp(buf, af_group_file->af_lineno, flags); + if (grp == NULL) { + /* If grp is NULL here, it's a malformed entry; keep looking. */ + if (bad_entry_count != NULL) { + (*bad_entry_count)++; + } + continue; + } + + free(buf); break; } -#endif /* !HAVE_FGETGRENT */ /* If grp is NULL now, the file is empty - nothing more to be read. */ if (grp == NULL) { @@ -565,18 +639,22 @@ static struct group *af_getgrent(pool *p static struct group *af_getgrnam(pool *p, const char *name) { struct group *grp = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setgrent(p) < 0) { return NULL; } - while ((grp = af_getgrent(p)) != NULL) { + grp = af_getgrent(p, flags, NULL); + while (grp != NULL) { pr_signals_handle(); if (strcmp(name, grp->gr_name) == 0) { /* Found the requested group */ break; } + + grp = af_getgrent(p, flags, NULL); } return grp; @@ -584,18 +662,22 @@ static struct group *af_getgrnam(pool *p static struct group *af_getgrgid(pool *p, gid_t gid) { struct group *grp = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setgrent(p) < 0) { return NULL; } - while ((grp = af_getgrent(p)) != NULL) { + grp = af_getgrent(p, flags, NULL); + while (grp != NULL) { pr_signals_handle(); if (grp->gr_gid == gid) { /* Found the requested GID */ break; } + + grp = af_getgrent(p, flags, NULL); } return grp; @@ -604,57 +686,66 @@ static struct group *af_getgrgid(pool *p static int af_setgrent(pool *p) { if (af_group_file != NULL) { - if (af_group_file->af_file != NULL) { - /* If already opened, rewind */ - rewind(af_group_file->af_file); - return 0; + int xerrno; + struct stat st; - } else { - int xerrno; + if (af_group_file->af_file_fh != NULL) { + pr_buffer_t *pbuf; - PRIVS_ROOT - af_group_file->af_file = fopen(af_group_file->af_path, "r"); - xerrno = errno; - PRIVS_RELINQUISH - - if (af_group_file->af_file == NULL) { - struct stat st; - - if (pr_fsio_stat(af_group_file->af_path, &st) == 0) { - pr_log_pri(PR_LOG_WARNING, - "error: unable to open AuthGroupFile file '%s' (file owned by " - "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s", - af_group_file->af_path, pr_uid2str(p, st.st_uid), - pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT, - pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()), - strerror(xerrno)); - - } else { - pr_log_pri(PR_LOG_WARNING, - "error: unable to open AuthGroupFile file '%s': %s", - af_group_file->af_path, strerror(xerrno)); - } + /* If already opened, rewind */ + (void) pr_fsio_lseek(af_group_file->af_file_fh, 0, SEEK_SET); - errno = xerrno; - return -1; + /* Make sure to clear any buffers as well. */ + pbuf = af_group_file->af_file_fh->fh_buf; + if (pbuf != NULL) { + memset(pbuf->buf, '\0', pbuf->buflen); + pbuf->current = pbuf->buf; + pbuf->remaining = pbuf->buflen; } - /* As the file may contain sensitive data, we do not want it lingering - * around in stdio buffers. - */ - (void) setvbuf(af_group_file->af_file, NULL, _IONBF, 0); + return 0; + } + + PRIVS_ROOT + af_group_file->af_file_fh = pr_fsio_open(af_group_file->af_path, O_RDONLY); + xerrno = errno; + PRIVS_RELINQUISH + + if (af_group_file->af_file_fh == NULL) { + if (pr_fsio_stat(af_group_file->af_path, &st) == 0) { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthGroupFile file '%s' (file owned by " + "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s", + af_group_file->af_path, pr_uid2str(p, st.st_uid), + pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT, + pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()), + strerror(xerrno)); - if (fcntl(fileno(af_group_file->af_file), F_SETFD, FD_CLOEXEC) < 0) { - pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION - ": unable to set CLOEXEC on AuthGroupFile %s (fd %d): %s", - af_group_file->af_path, fileno(af_group_file->af_file), - strerror(errno)); + } else { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthGroupFile file '%s': %s", + af_group_file->af_path, strerror(xerrno)); } - pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using group file '%s'", - af_group_file->af_path); - return 0; + errno = xerrno; + return -1; } + + /* Set the optimum buffer/block size for this filehandle. */ + if (pr_fsio_fstat(af_group_file->af_file_fh, &st) == 0) { + af_group_file->af_file_fh->fh_iosz = st.st_blksize; + } + + if (fcntl(PR_FH_FD(af_group_file->af_file_fh), F_SETFD, FD_CLOEXEC) < 0) { + pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION + ": unable to set CLOEXEC on AuthGroupFile %s (fd %d): %s", + af_group_file->af_path, PR_FH_FD(af_group_file->af_file_fh), + strerror(errno)); + } + + pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using group file '%s'", + af_group_file->af_path); + return 0; } pr_trace_msg(trace_channel, 8, "no AuthGroupFile configured"); @@ -730,38 +821,33 @@ static int af_allow_pwent(pool *p, struc } static void af_endpwent(void) { - if (af_user_file && - af_user_file->af_file) { - fclose(af_user_file->af_file); - af_user_file->af_file = NULL; + if (af_user_file != NULL && + af_user_file->af_file_fh != NULL) { + pr_fsio_close(af_user_file->af_file_fh); + af_user_file->af_file_fh = NULL; af_user_file->af_lineno = 0; } - - return; } -static struct passwd *af_getpwent(pool *p) { +static struct passwd *af_getpwent(pool *p, int flags, + unsigned int *bad_entry_count) { struct passwd *pwd = NULL, *res = NULL; if (af_user_file == NULL || - af_user_file->af_file == NULL) { + af_user_file->af_file_fh == NULL) { errno = EINVAL; return NULL; } while (TRUE) { -#ifdef HAVE_FGETPWENT - pr_signals_handle(); - pwd = fgetpwent(af_user_file->af_file); -#else - char buf[BUFSIZ+1] = {'\0'}; + char buf[PR_TUNABLE_BUFFER_SIZE+1] = {'\0'}; pr_signals_handle(); memset(buf, '\0', sizeof(buf)); pwd = NULL; - while (fgets(buf, sizeof(buf)-1, af_user_file->af_file) != NULL) { + while (pr_fsio_gets(buf, sizeof(buf)-1, af_user_file->af_file_fh) != NULL) { pr_signals_handle(); af_user_file->af_lineno++; @@ -774,10 +860,20 @@ static struct passwd *af_getpwent(pool * } buf[strlen(buf)-1] = '\0'; - pwd = af_getpasswd(buf, af_user_file->af_lineno); + pwd = af_parse_passwd(buf, af_user_file->af_lineno, flags); + + if (pwd == NULL) { + /* If pwd is NULL here, it's a malformed entry; keep looking. */ + if (bad_entry_count != NULL) { + (*bad_entry_count)++; + } + + memset(buf, '\0', sizeof(buf)); + continue; + } + break; } -#endif /* !HAVE_FGETPWENT */ /* If pwd is NULL now, the file is empty - nothing more to be read. */ if (pwd == NULL) { @@ -785,9 +881,7 @@ static struct passwd *af_getpwent(pool * } if (af_allow_pwent(p, pwd) < 0) { -#ifndef HAVE_FGETPWENT memset(buf, '\0', sizeof(buf)); -#endif continue; } @@ -800,18 +894,22 @@ static struct passwd *af_getpwent(pool * static struct passwd *af_getpwnam(pool *p, const char *name) { struct passwd *pwd = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setpwent(p) < 0) { return NULL; } - while ((pwd = af_getpwent(p)) != NULL) { + pwd = af_getpwent(p, flags, NULL); + while (pwd != NULL) { pr_signals_handle(); if (strcmp(name, pwd->pw_name) == 0) { /* Found the requested user */ break; } + + pwd = af_getpwent(p, flags, NULL); } return pwd; @@ -824,18 +922,22 @@ static char *af_getpwpass(pool *p, const static struct passwd *af_getpwuid(pool *p, uid_t uid) { struct passwd *pwd = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setpwent(p) < 0) { return NULL; } - while ((pwd = af_getpwent(p)) != NULL) { + pwd = af_getpwent(p, flags, NULL); + while (pwd != NULL) { pr_signals_handle(); if (pwd->pw_uid == uid) { /* Found the requested UID */ break; } + + pwd = af_getpwent(p, flags, NULL); } return pwd; @@ -844,57 +946,66 @@ static struct passwd *af_getpwuid(pool * static int af_setpwent(pool *p) { if (af_user_file != NULL) { - if (af_user_file->af_file != NULL) { - /* If already opened, rewind */ - rewind(af_user_file->af_file); - return 0; + int xerrno; + struct stat st; - } else { - int xerrno; + if (af_user_file->af_file_fh != NULL) { + pr_buffer_t *pbuf; - PRIVS_ROOT - af_user_file->af_file = fopen(af_user_file->af_path, "r"); - xerrno = errno; - PRIVS_RELINQUISH - - if (af_user_file->af_file == NULL) { - struct stat st; - - if (pr_fsio_stat(af_user_file->af_path, &st) == 0) { - pr_log_pri(PR_LOG_WARNING, - "error: unable to open AuthUserFile file '%s' (file owned by " - "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s", - af_user_file->af_path, pr_uid2str(p, st.st_uid), - pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT, - pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()), - strerror(xerrno)); - - } else { - pr_log_pri(PR_LOG_WARNING, - "error: unable to open AuthUserFile file '%s': %s", - af_user_file->af_path, strerror(xerrno)); - } + /* If already opened, rewind */ + (void) pr_fsio_lseek(af_user_file->af_file_fh, 0, SEEK_SET); - errno = xerrno; - return -1; + /* Make sure to clear any buffers as well. */ + pbuf = af_user_file->af_file_fh->fh_buf; + if (pbuf != NULL) { + memset(pbuf->buf, '\0', pbuf->buflen); + pbuf->current = pbuf->buf; + pbuf->remaining = pbuf->buflen; } - /* As the file may contain sensitive data, we do not want it lingering - * around in stdio buffers. - */ - (void) setvbuf(af_user_file->af_file, NULL, _IONBF, 0); + return 0; + } + + PRIVS_ROOT + af_user_file->af_file_fh = pr_fsio_open(af_user_file->af_path, O_RDONLY); + xerrno = errno; + PRIVS_RELINQUISH + + if (af_user_file->af_file_fh == NULL) { + if (pr_fsio_stat(af_user_file->af_path, &st) == 0) { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthUserFile file '%s' (file owned by " + "UID %s, GID %s, perms %04o, accessed by UID %s, GID %s): %s", + af_user_file->af_path, pr_uid2str(p, st.st_uid), + pr_gid2str(p, st.st_gid), st.st_mode & ~S_IFMT, + pr_uid2str(p, geteuid()), pr_gid2str(p, getegid()), + strerror(xerrno)); - if (fcntl(fileno(af_user_file->af_file), F_SETFD, FD_CLOEXEC) < 0) { - pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION - ": unable to set CLOEXEC on AuthUserFile %s (fd %d): %s", - af_user_file->af_path, fileno(af_user_file->af_file), - strerror(errno)); + } else { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthUserFile file '%s': %s", + af_user_file->af_path, strerror(xerrno)); } - pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using passwd file '%s'", - af_user_file->af_path); - return 0; + errno = xerrno; + return -1; + } + + /* Set the optimum buffer/block size for this filehandle. */ + if (pr_fsio_fstat(af_user_file->af_file_fh, &st) == 0) { + af_user_file->af_file_fh->fh_iosz = st.st_blksize; } + + if (fcntl(PR_FH_FD(af_user_file->af_file_fh), F_SETFD, FD_CLOEXEC) < 0) { + pr_log_pri(PR_LOG_WARNING, MOD_AUTH_FILE_VERSION + ": unable to set CLOEXEC on AuthUserFile %s (fd %d): %s", + af_user_file->af_path, PR_FH_FD(af_user_file->af_file_fh), + strerror(errno)); + } + + pr_log_debug(DEBUG7, MOD_AUTH_FILE_VERSION ": using passwd file '%s'", + af_user_file->af_path); + return 0; } pr_trace_msg(trace_channel, 8, "no AuthUserFile configured"); @@ -902,6 +1013,93 @@ static int af_setpwent(pool *p) { return -1; } +static int af_check_group_syntax(pool *p, const char *path) { + int flags = 0, xerrno, res = 0; + struct group *grp; + unsigned int bad_entry_count = 0; + + af_group_file = pcalloc(p, sizeof(authfile_file_t)); + af_group_file->af_path = pstrdup(p, path); + + PRIVS_ROOT + af_group_file->af_file_fh = pr_fsio_open(af_group_file->af_path, O_RDONLY); + xerrno = errno; + PRIVS_RELINQUISH + + if (af_group_file->af_file_fh == NULL) { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthGroupFile file '%s': %s", + af_group_file->af_path, strerror(xerrno)); + errno = xerrno; + return -1; + } + + grp = af_getgrent(p, flags, &bad_entry_count); + while (grp != NULL) { + pr_signals_handle(); + + grp = af_getgrent(p, flags, &bad_entry_count); + } + + pr_fsio_close(af_group_file->af_file_fh); + af_group_file->af_file_fh = NULL; + af_group_file->af_lineno = 0; + af_group_file = NULL; + + if (bad_entry_count > 0) { + pr_log_pri(PR_LOG_WARNING, "bad entries (%u) detected in AuthGroupFile %s", + bad_entry_count, path); + errno = EINVAL; + res = -1; + } + + return res; +} + +static int af_check_user_syntax(pool *p, const char *path) { + int flags = 0, xerrno, res = 0; + struct passwd *pwd; + unsigned int bad_entry_count = 0; + + af_user_file = pcalloc(p, sizeof(authfile_file_t)); + af_user_file->af_path = pstrdup(p, path); + + PRIVS_ROOT + af_user_file->af_file_fh = pr_fsio_open(af_user_file->af_path, O_RDONLY); + xerrno = errno; + PRIVS_RELINQUISH + + if (af_user_file->af_file_fh == NULL) { + pr_log_pri(PR_LOG_WARNING, + "error: unable to open AuthUserFile file '%s': %s", + af_user_file->af_path, strerror(xerrno)); + errno = xerrno; + return -1; + } + + bad_entry_count = 0; + pwd = af_getpwent(p, flags, &bad_entry_count); + while (pwd != NULL) { + pr_signals_handle(); + + pwd = af_getpwent(p, flags, &bad_entry_count); + } + + pr_fsio_close(af_user_file->af_file_fh); + af_user_file->af_file_fh = NULL; + af_user_file->af_lineno = 0; + af_user_file = NULL; + + if (bad_entry_count > 0) { + pr_log_pri(PR_LOG_WARNING, "bad entries (%u) detected in AuthUserFile %s", + bad_entry_count, path); + errno = EINVAL; + res = -1; + } + + return res; +} + /* Authentication handlers. */ @@ -912,8 +1110,9 @@ MODRET authfile_endpwent(cmd_rec *cmd) { MODRET authfile_getpwent(cmd_rec *cmd) { struct passwd *pwd = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; - pwd = af_getpwent(cmd->tmp_pool); + pwd = af_getpwent(cmd->tmp_pool, flags, NULL); return pwd ? mod_create_data(cmd, pwd) : PR_DECLINED(cmd); } @@ -921,19 +1120,23 @@ MODRET authfile_getpwent(cmd_rec *cmd) { MODRET authfile_getpwnam(cmd_rec *cmd) { struct passwd *pwd = NULL; const char *name = cmd->argv[0]; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setpwent(cmd->tmp_pool) < 0) { return PR_DECLINED(cmd); } /* Ugly -- we iterate through the file. Time-consuming. */ - while ((pwd = af_getpwent(cmd->tmp_pool)) != NULL) { + pwd = af_getpwent(cmd->tmp_pool, flags, NULL); + while (pwd != NULL) { pr_signals_handle(); if (strcmp(name, pwd->pw_name) == 0) { /* Found the requested name */ break; } + + pwd = af_getpwent(cmd->tmp_pool, flags, NULL); } return pwd ? mod_create_data(cmd, pwd) : PR_DECLINED(cmd); @@ -991,8 +1194,9 @@ MODRET authfile_endgrent(cmd_rec *cmd) { MODRET authfile_getgrent(cmd_rec *cmd) { struct group *grp = NULL; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; - grp = af_getgrent(cmd->tmp_pool); + grp = af_getgrent(cmd->tmp_pool, flags, NULL); return grp ? mod_create_data(cmd, grp) : PR_DECLINED(cmd); } @@ -1012,19 +1216,25 @@ MODRET authfile_getgrgid(cmd_rec *cmd) { MODRET authfile_getgrnam(cmd_rec *cmd) { struct group *grp = NULL; - const char *name = cmd->argv[0]; + const char *name; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (af_setgrent(cmd->tmp_pool) < 0) { return PR_DECLINED(cmd); } - while ((grp = af_getgrent(cmd->tmp_pool)) != NULL) { + name = cmd->argv[0]; + + grp = af_getgrent(cmd->tmp_pool, flags, NULL); + while (grp != NULL) { pr_signals_handle(); if (strcmp(name, grp->gr_name) == 0) { /* Found the name requested */ break; } + + grp = af_getgrent(cmd->tmp_pool, flags, NULL); } return grp ? mod_create_data(cmd, grp) : PR_DECLINED(cmd); @@ -1035,6 +1245,7 @@ MODRET authfile_getgroups(cmd_rec *cmd) struct group *grp = NULL; array_header *gids = NULL, *groups = NULL; char *name = cmd->argv[0]; + int flags = PR_AUTH_FILE_FL_USE_TRACE_LOG; if (name == NULL) { return PR_DECLINED(cmd); @@ -1049,11 +1260,11 @@ MODRET authfile_getgroups(cmd_rec *cmd) } /* Check for NULLs */ - if (cmd->argv[1]) { + if (cmd->argv[1] != NULL) { gids = (array_header *) cmd->argv[1]; } - if (cmd->argv[2]) { + if (cmd->argv[2] != NULL) { groups = (array_header *) cmd->argv[2]; } @@ -1064,13 +1275,16 @@ MODRET authfile_getgroups(cmd_rec *cmd) } /* Populate the first group ID and name. */ - if (gids) { + if (gids != NULL) { *((gid_t *) push_array(gids)) = pwd->pw_gid; } - if (groups && - (grp = af_getgrgid(cmd->tmp_pool, pwd->pw_gid)) != NULL) { - *((char **) push_array(groups)) = pstrdup(session.pool, grp->gr_name); + if (groups != NULL) { + grp = af_getgrgid(cmd->tmp_pool, pwd->pw_gid); + + if (grp != NULL) { + *((char **) push_array(groups)) = pstrdup(session.pool, grp->gr_name); + } } (void) af_setgrent(cmd->tmp_pool); @@ -1078,8 +1292,9 @@ MODRET authfile_getgroups(cmd_rec *cmd) /* This is where things get slow, expensive, and ugly. Loop through * everything, checking to make sure we haven't already added it. */ - while ((grp = af_getgrent(cmd->tmp_pool)) != NULL && - grp->gr_mem) { + grp = af_getgrent(cmd->tmp_pool, flags, NULL); + while (grp != NULL && + grp->gr_mem) { char **gr_mems = NULL; pr_signals_handle(); @@ -1091,21 +1306,26 @@ MODRET authfile_getgroups(cmd_rec *cmd) if (strcmp(*gr_mems, pwd->pw_name) == 0) { /* ...add the GID and name */ - if (gids) { + if (gids != NULL) { *((gid_t *) push_array(gids)) = grp->gr_gid; } - if (groups) { + if (groups != NULL) { *((char **) push_array(groups)) = pstrdup(session.pool, grp->gr_name); } } } + + grp = af_getgrent(cmd->tmp_pool, flags, NULL); } - if (gids && gids->nelts > 0) { + if (gids != NULL && + gids->nelts > 0) { return mod_create_data(cmd, (void *) &gids->nelts); + } - } else if (groups && groups->nelts > 0) { + if (groups != NULL && + groups->nelts > 0) { return mod_create_data(cmd, (void *) &groups->nelts); } @@ -1232,7 +1452,7 @@ static void check_unsupported_algo(const if (strcmp(fips_enabled, "0") != 0) { /* FIPS mode enabled on this system. If our salt string doesn't start - * with a '$', it uses DES; if it starts wit '$1$', it uses MD5. Either + * with a '$', it uses DES; if it starts with '$1$', it uses MD5. Either * way, on a FIPS-enabled system, those algorithms aren't supported. */ if (ciphertxt_pass[0] != '$') { @@ -1334,6 +1554,48 @@ MODRET authfile_chkpass(cmd_rec *cmd) { /* Configuration handlers */ +/* usage: AuthFileOptions opt1 ... */ +MODRET set_authfileoptions(cmd_rec *cmd) { + config_rec *c = NULL; + register unsigned int i = 0; + unsigned long opts = 0UL; + + if (cmd->argc-1 == 0) { + CONF_ERROR(cmd, "wrong number of parameters"); + } + + CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); + + c = add_config_param(cmd->argv[0], 1, NULL); + + for (i = 1; i < cmd->argc; i++) { + if (strcmp(cmd->argv[i], "InsecurePerms") == 0) { + opts |= AUTH_FILE_OPT_INSECURE_PERMS; + + /* Note that this option disables some parse-time checks, so we need + * to set it globally now, rather than at sess_init time. + */ + auth_file_opts |= AUTH_FILE_OPT_INSECURE_PERMS; + + } else if (strcmp(cmd->argv[i], "SyntaxCheck") == 0) { + + /* Note that this option enables some parse-time checks, so we need + * to set it globally now, rather than at sess_init time. + */ + auth_file_opts |= AUTH_FILE_OPT_SYNTAX_CHECK; + + } else { + CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown AuthFileOption '", + cmd->argv[i], "'", NULL)); + } + } + + c->argv[0] = pcalloc(c->pool, sizeof(unsigned long)); + *((unsigned long *) c->argv[0]) = opts; + + return PR_HANDLED(cmd); +} + /* usage: AuthGroupFile path [id ] [name ] */ MODRET set_authgroupfile(cmd_rec *cmd) { config_rec *c = NULL; @@ -1360,14 +1622,31 @@ MODRET set_authgroupfile(cmd_rec *cmd) { path, "'.", NULL)); } - /* Make sure the configured file has the correct permissions. Note that - * AuthGroupFiles, unlike AuthUserFiles, do not contain any sensitive - * information, and can thus be world-readable. - */ - flags = PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE; - if (af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags) < 0) { - CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, - "unable to use ", path, ": ", strerror(errno), NULL)); + if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) { + int res, xerrno; + + /* Make sure the configured file has the correct permissions. Note that + * AuthGroupFiles, unlike AuthUserFiles, do not contain any sensitive + * information, and can thus be world-readable. + */ + flags = PR_AUTH_FILE_FL_ALLOW_WORLD_READABLE; + + PRIVS_ROOT + res = af_check_file(cmd->tmp_pool, cmd->argv[0], path, flags); + xerrno = errno; + PRIVS_RELINQUISH + + if (res < 0) { + CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, + "unable to use ", path, ": ", strerror(xerrno), NULL)); + } + } + + if (auth_file_opts & AUTH_FILE_OPT_SYNTAX_CHECK) { + if (af_check_group_syntax(cmd->tmp_pool, path) < 0) { + CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, + "unable to use ", path, ": ", strerror(errno), NULL)); + } } c = add_config_param(cmd->argv[0], 1, NULL); @@ -1381,7 +1660,7 @@ MODRET set_authgroupfile(cmd_rec *cmd) { register unsigned int i = 0; for (i = 2; i < cmd->argc; i++) { - if (strncmp(cmd->argv[i], "id", 3) == 0) { + if (strcasecmp(cmd->argv[i], "id") == 0) { gid_t min, max; char *sep = NULL, *tmp = NULL; @@ -1417,7 +1696,7 @@ MODRET set_authgroupfile(cmd_rec *cmd) { file->af_restricted_ids = TRUE; #ifdef PR_USE_REGEX - } else if (strncmp(cmd->argv[i], "name", 5) == 0) { + } else if (strcasecmp(cmd->argv[i], "name") == 0) { char *filter = cmd->argv[++i]; pr_regex_t *pre = NULL; int res = 0; @@ -1482,16 +1761,32 @@ MODRET set_authuserfile(cmd_rec *cmd) { path, "'.", NULL)); } - /* Make sure the configured file has the correct permissions. Note that - * AuthUserFiles, unlike AuthGroupFiles, DO contain any sensitive - * information, and thus CANNOT be world-readable. - */ - flags = 0; - if (af_check_file(cmd->tmp_pool, cmd->argv[0], cmd->argv[1], flags) < 0) { - CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, - "unable to use ", path, ": ", strerror(errno), NULL)); + if (!(auth_file_opts & AUTH_FILE_OPT_INSECURE_PERMS)) { + int res, xerrno; + + /* Make sure the configured file has the correct permissions. Note that + * AuthUserFiles, unlike AuthGroupFiles, DO contain any sensitive + * information, and thus CANNOT be world-readable. + */ + flags = 0; + + PRIVS_ROOT + res = af_check_file(cmd->tmp_pool, cmd->argv[0], path, flags); + xerrno = errno; + PRIVS_RELINQUISH + + if (res < 0) { + CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, + "unable to use ", path, ": ", strerror(xerrno), NULL)); + } } + if (auth_file_opts & AUTH_FILE_OPT_SYNTAX_CHECK) { + if (af_check_user_syntax(cmd->tmp_pool, path) < 0) { + CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, + "unable to use ", path, ": ", strerror(errno), NULL)); + } + } c = add_config_param(cmd->argv[0], 1, NULL); file = pcalloc(c->pool, sizeof(authfile_file_t)); @@ -1503,7 +1798,7 @@ MODRET set_authuserfile(cmd_rec *cmd) { register unsigned int i = 0; for (i = 2; i < cmd->argc; i++) { - if (strncmp(cmd->argv[i], "id", 3) == 0) { + if (strcasecmp(cmd->argv[i], "id") == 0) { uid_t min, max; char *sep = NULL, *tmp = NULL; @@ -1540,7 +1835,7 @@ MODRET set_authuserfile(cmd_rec *cmd) { file->af_restricted_ids = TRUE; #ifdef PR_USE_REGEX - } else if (strncmp(cmd->argv[i], "home", 5) == 0) { + } else if (strcasecmp(cmd->argv[i], "home") == 0) { char *filter = cmd->argv[++i]; pr_regex_t *pre = NULL; int res = 0; @@ -1568,7 +1863,7 @@ MODRET set_authuserfile(cmd_rec *cmd) { file->af_home_regex = pre; file->af_restricted_homes = TRUE; - } else if (strncmp(cmd->argv[i], "name", 5) == 0) { + } else if (strcasecmp(cmd->argv[i], "name") == 0) { char *filter = cmd->argv[++i]; pr_regex_t *pre = NULL; int res = 0; @@ -1668,12 +1963,12 @@ static int authfile_sess_init(void) { authfile_sess_reinit_ev, NULL); c = find_config(main_server->conf, CONF_PARAM, "AuthUserFile", FALSE); - if (c) { + if (c != NULL) { af_user_file = c->argv[0]; } c = find_config(main_server->conf, CONF_PARAM, "AuthGroupFile", FALSE); - if (c) { + if (c != NULL) { af_group_file = c->argv[0]; } @@ -1684,6 +1979,7 @@ static int authfile_sess_init(void) { */ static conftable authfile_conftab[] = { + { "AuthFileOptions", set_authfileoptions, NULL }, { "AuthGroupFile", set_authgroupfile, NULL }, { "AuthUserFile", set_authuserfile, NULL }, { NULL } @@ -1745,4 +2041,3 @@ module auth_file_module = { /* Module version */ MOD_AUTH_FILE_VERSION }; -