static int is_tmp_file(const char *file) { register unsigned int i; for (i = 0; tmpfile_patterns[i]; i++) { if (pr_fnmatch(tmpfile_patterns[i], file, PR_FNM_PERIOD) == 0) { return TRUE; } } return FALSE; }
int pr_netaddr_fnmatch(pr_netaddr_t *na, const char *pattern, int flags) { /* Note: I'm still not sure why proftpd bundles an fnmatch(3) * implementation rather than using the system library's implementation. * Needs looking into. * * The FNM_CASEFOLD flag is a GNU extension; perhaps the bundled * implementation was added to make that flag available on other platforms. */ int match_flags = PR_FNM_NOESCAPE|PR_FNM_CASEFOLD; if (!na || !pattern) { errno = EINVAL; return -1; } if (flags & PR_NETADDR_MATCH_DNS) { const char *dnsstr = pr_netaddr_get_dnsstr(na); if (pr_fnmatch(pattern, dnsstr, match_flags) == 0) { pr_trace_msg(trace_channel, 6, "DNS name '%s' matches pattern '%s'", dnsstr, pattern); return TRUE; } } if (flags & PR_NETADDR_MATCH_IP) { const char *ipstr = pr_netaddr_get_ipstr(na); if (pr_fnmatch(pattern, ipstr, match_flags) == 0) { pr_trace_msg(trace_channel, 6, "DNS name '%s' matches pattern '%s'", ipstr, pattern); return TRUE; } } pr_trace_msg(trace_channel, 4, "addr %s does not match pattern '%s'", pr_netaddr_get_ipstr(na), pattern); return FALSE; }
static int parse_wildcard_config_path(pool *p, const char *path, unsigned int depth) { register unsigned int i; int res, xerrno; pool *tmp_pool; array_header *globbed_dirs = NULL; const char *component = NULL, *parent_path = NULL, *suffix_path = NULL; struct stat st; size_t path_len, component_len; char *name_pattern = NULL; void *dirh = NULL; struct dirent *dent = NULL; if (depth > PR_PARSER_INCLUDE_MAX_DEPTH) { pr_log_pri(PR_LOG_WARNING, "error: resolving wildcard pattern in '%s' " "exceeded maximum filesystem depth (%u)", path, (unsigned int) PR_PARSER_INCLUDE_MAX_DEPTH); errno = EINVAL; return -1; } path_len = strlen(path); if (path_len < 2) { pr_trace_msg(trace_channel, 7, "path '%s' too short to be wildcard path", path); /* The first character must be a slash, and we need at least one more * character in the path as a glob character. */ errno = EINVAL; return -1; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Include sub-pool"); /* We need to find the first component of the path which contains glob * characters. We then use the path up to the previous component as the * parent directory to open, and the glob-bearing component as the filter * for directories within the parent. */ component = path + 1; while (TRUE) { int last_component = FALSE; char *ptr; pr_signals_handle(); ptr = strchr(component, '/'); if (ptr != NULL) { component_len = ptr - component; } else { component_len = strlen(component); last_component = TRUE; } if (memchr(component, (int) '*', component_len) != NULL || memchr(component, (int) '?', component_len) != NULL || memchr(component, (int) '[', component_len) != NULL) { name_pattern = pstrndup(tmp_pool, component, component_len); if (parent_path == NULL) { parent_path = pstrndup(tmp_pool, "/", 1); } if (ptr != NULL) { suffix_path = pstrdup(tmp_pool, ptr + 1); } break; } if (parent_path != NULL) { parent_path = pdircat(tmp_pool, parent_path, pstrndup(tmp_pool, component, component_len), NULL); } else { parent_path = pstrndup(tmp_pool, "/", 1); } if (last_component) { break; } component = ptr + 1; } if (name_pattern == NULL) { pr_trace_msg(trace_channel, 4, "unable to process invalid, non-globbed path '%s'", path); errno = ENOENT; return -1; } pr_fs_clear_cache2(parent_path); if (pr_fsio_lstat(parent_path, &st) < 0) { xerrno = errno; pr_log_pri(PR_LOG_WARNING, "error: failed to check configuration path '%s': %s", parent_path, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISLNK(st.st_mode) && !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) { pr_log_pri(PR_LOG_WARNING, "error: cannot read configuration path '%s': Symbolic link", parent_path); destroy_pool(tmp_pool); errno = ENOTDIR; return -1; } pr_log_pri(PR_LOG_DEBUG, "processing configuration directory '%s' using pattern '%s', suffix '%s'", parent_path, name_pattern, suffix_path); dirh = pr_fsio_opendir(parent_path); if (dirh == NULL) { pr_log_pri(PR_LOG_WARNING, "error: unable to open configuration directory '%s': %s", parent_path, strerror(errno)); destroy_pool(tmp_pool); errno = EINVAL; return -1; } globbed_dirs = make_array(tmp_pool, 0, sizeof(char *)); while ((dent = pr_fsio_readdir(dirh)) != NULL) { pr_signals_handle(); if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) { if (is_tmp_file(dent->d_name) == TRUE) { pr_trace_msg(trace_channel, 19, "ignoring temporary file '%s' found in directory '%s'", dent->d_name, parent_path); continue; } } if (pr_fnmatch(name_pattern, dent->d_name, PR_FNM_PERIOD) == 0) { pr_trace_msg(trace_channel, 17, "matched '%s' path with wildcard pattern '%s'", dent->d_name, name_pattern); *((char **) push_array(globbed_dirs)) = pdircat(tmp_pool, parent_path, dent->d_name, suffix_path, NULL); } } pr_fsio_closedir(dirh); if (globbed_dirs->nelts == 0) { pr_log_pri(PR_LOG_WARNING, "error: no matches found for wildcard directory '%s'", path); destroy_pool(tmp_pool); errno = ENOENT; return -1; } depth++; qsort((void *) globbed_dirs->elts, globbed_dirs->nelts, sizeof(char *), config_filename_cmp); for (i = 0; i < globbed_dirs->nelts; i++) { const char *globbed_dir; globbed_dir = ((const char **) globbed_dirs->elts)[i]; res = parse_config_path2(p, globbed_dir, depth); if (res < 0) { xerrno = errno; pr_trace_msg(trace_channel, 7, "error parsing wildcard path '%s': %s", globbed_dir, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } } destroy_pool(tmp_pool); return 0; }
int parse_config_path2(pool *p, const char *path, unsigned int depth) { struct stat st; int have_glob; void *dirh; struct dirent *dent; array_header *file_list; char *dup_path, *ptr; pool *tmp_pool; if (p == NULL || path == NULL || (depth > PR_PARSER_INCLUDE_MAX_DEPTH)) { errno = EINVAL; return -1; } if (pr_fs_valid_path(path) < 0) { errno = EINVAL; return -1; } have_glob = pr_str_is_fnmatch(path); if (have_glob) { /* Even though the path may be valid, it also may not be a filesystem * path; consider custom FSIO modules. Thus if the path does not start * with a slash, it should not be treated as having globs. */ if (*path != '/') { have_glob = FALSE; } } pr_fs_clear_cache2(path); if (have_glob) { pr_trace_msg(trace_channel, 19, "parsing '%s' as a globbed path", path); } if (!have_glob && pr_fsio_lstat(path, &st) < 0) { return -1; } /* If path is not a glob pattern, and is a symlink OR is not a directory, * then use the normal parsing function for the file. */ if (have_glob == FALSE && (S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode))) { int res, xerrno; PRIVS_ROOT res = pr_parser_parse_file(p, path, NULL, 0); xerrno = errno; PRIVS_RELINQUISH errno = xerrno; return res; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "Include sub-pool"); /* Handle the glob/directory. */ dup_path = pstrdup(tmp_pool, path); ptr = strrchr(dup_path, '/'); if (have_glob) { int have_glob_dir; /* Note that we know, by definition, that ptr CANNOT be null here; dup_path * is a duplicate of path, and the first character (if nothing else) of * path MUST be a slash, per earlier checks. */ *ptr = '\0'; /* We just changed ptr, thus we DO need to check whether the now-modified * path contains fnmatch(3) characters again. */ have_glob_dir = pr_str_is_fnmatch(dup_path); if (have_glob_dir) { const char *glob_dir; if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_WILDCARDS) { pr_log_pri(PR_LOG_WARNING, "error: wildcard patterns not allowed in " "configuration directory name '%s'", dup_path); destroy_pool(tmp_pool); errno = EINVAL; return -1; } *ptr = '/'; glob_dir = pstrdup(p, dup_path); destroy_pool(tmp_pool); return parse_wildcard_config_path(p, glob_dir, depth); } ptr++; /* Check the directory component. */ pr_fs_clear_cache2(dup_path); if (pr_fsio_lstat(dup_path, &st) < 0) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "error: failed to check configuration path '%s': %s", dup_path, strerror(xerrno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISLNK(st.st_mode) && !(parser_include_opts & PR_PARSER_INCLUDE_OPT_ALLOW_SYMLINKS)) { pr_log_pri(PR_LOG_WARNING, "error: cannot read configuration path '%s': Symbolic link", path); destroy_pool(tmp_pool); errno = ENOTDIR; return -1; } if (have_glob_dir == FALSE && pr_str_is_fnmatch(ptr) == FALSE) { pr_log_pri(PR_LOG_WARNING, "error: wildcard pattern required for file '%s'", ptr); destroy_pool(tmp_pool); errno = EINVAL; return -1; } } pr_log_pri(PR_LOG_DEBUG, "processing configuration directory '%s'", dup_path); dirh = pr_fsio_opendir(dup_path); if (dirh == NULL) { pr_log_pri(PR_LOG_WARNING, "error: unable to open configuration directory '%s': %s", dup_path, strerror(errno)); destroy_pool(tmp_pool); errno = EINVAL; return -1; } file_list = make_array(tmp_pool, 0, sizeof(char *)); while ((dent = pr_fsio_readdir(dirh)) != NULL) { pr_signals_handle(); if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } if (parser_include_opts & PR_PARSER_INCLUDE_OPT_IGNORE_TMP_FILES) { if (is_tmp_file(dent->d_name) == TRUE) { pr_trace_msg(trace_channel, 19, "ignoring temporary file '%s' found in directory '%s'", dent->d_name, dup_path); continue; } } if (have_glob == FALSE || (ptr != NULL && pr_fnmatch(ptr, dent->d_name, PR_FNM_PERIOD) == 0)) { *((char **) push_array(file_list)) = pdircat(tmp_pool, dup_path, dent->d_name, NULL); } } pr_fsio_closedir(dirh); if (file_list->nelts) { register unsigned int i; qsort((void *) file_list->elts, file_list->nelts, sizeof(char *), config_filename_cmp); for (i = 0; i < file_list->nelts; i++) { int res, xerrno; char *file; file = ((char **) file_list->elts)[i]; /* Make sure we always parse the files with root privs. The * previously parsed file might have had root privs relinquished * (e.g. by its directive handlers), but when we first start up, * we have root privs. See Bug#3855. */ PRIVS_ROOT res = pr_parser_parse_file(tmp_pool, file, NULL, 0); xerrno = errno; PRIVS_RELINQUISH if (res < 0) { pr_log_pri(PR_LOG_WARNING, "error: unable to open parse file '%s': %s", file, strerror(xerrno)); } } } destroy_pool(tmp_pool); return 0; }
static pr_fh_t *counter_get_fh(pool *p, const char *path) { struct counter_fh *iter, *cfh = NULL; const char *abs_path; /* Find the CounterFile handle to use for the given path, if any. */ if (counter_fhs == NULL) { errno = ENOENT; return NULL; } if (session.chroot_path) { abs_path = counter_abs_path(p, path, FALSE); } else { abs_path = path; } /* In order to handle globs, we do two passes. On the first pass, * we look for the closest-matching glob area. On the second pass, * we look for any closest-matching non-glob area. This means that * exact matches override glob matches (as they should). */ for (iter = (struct counter_fh *) counter_fhs->xas_list; iter; iter = iter->next) { pr_signals_handle(); if (!iter->isglob) { continue; } if (cfh == NULL) { /* Haven't found anything matching yet. */ if (pr_fnmatch(iter->area, abs_path, 0) == 0) { cfh = iter; } } else { /* Have a previous match. Is this a closer matching area? */ if (iter->arealen > cfh->arealen && pr_fnmatch(iter->area, abs_path, 0) == 0) { cfh = iter; } } } for (iter = (struct counter_fh *) counter_fhs->xas_list; iter; iter = iter->next) { if (iter->isglob) { continue; } if (cfh == NULL) { /* Haven't found anything matching yet. */ if (strncmp(iter->area, abs_path, iter->arealen) == 0) { cfh = iter; } } else { /* Have a previous match. Is this a closer matching area? */ if (iter->arealen > cfh->arealen && strncmp(iter->area, abs_path, iter->arealen) == 0) { cfh = iter; } } } if (cfh != NULL) { (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "using CounterFile '%s' covering area '%s' for path '%s'", cfh->fh->fh_path, cfh->area, path); return cfh->fh; } errno = ENOENT; return NULL; }
pr_namebind_t *pr_namebind_find(const char *name, pr_netaddr_t *addr, unsigned int port, unsigned char skip_inactive) { pr_ipbind_t *ipbind = NULL; pr_namebind_t *namebind = NULL; if (name == NULL || addr == NULL) { errno = EINVAL; return NULL; } /* First, find an active ipbind for the given addr/port */ ipbind = pr_ipbind_find(addr, port, skip_inactive); if (ipbind == NULL) { pr_netaddr_t wildcard_addr; int addr_family; /* If not found, look for the wildcard address. */ addr_family = pr_netaddr_get_family(addr); pr_netaddr_clear(&wildcard_addr); pr_netaddr_set_family(&wildcard_addr, addr_family); pr_netaddr_set_sockaddr_any(&wildcard_addr); ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE); #ifdef PR_USE_IPV6 if (ipbind == FALSE && addr_family == AF_INET6 && pr_netaddr_use_ipv6()) { /* No IPv6 wildcard address found; try the IPv4 wildcard address. */ pr_netaddr_clear(&wildcard_addr); pr_netaddr_set_family(&wildcard_addr, AF_INET); pr_netaddr_set_sockaddr_any(&wildcard_addr); ipbind = pr_ipbind_find(&wildcard_addr, port, FALSE); } #endif /* PR_USE_IPV6 */ } if (ipbind == NULL) { errno = ENOENT; return NULL; } if (!ipbind->ib_namebinds) { return NULL; } else { register unsigned int i = 0; pr_namebind_t **namebinds = (pr_namebind_t **) ipbind->ib_namebinds->elts; for (i = 0; i < ipbind->ib_namebinds->nelts; i++) { namebind = namebinds[i]; /* Skip inactive namebinds */ if (skip_inactive == TRUE && namebind != NULL && namebind->nb_isactive == FALSE) { continue; } /* At present, this looks for an exactly matching name. In the future, * we may want to have something like Apache's matching scheme, which * looks for the most specific domain to the most general. Note that * that scheme, however, is specific to DNS; should any other naming * scheme be desired, that sort of matching will be unnecessary. */ if (namebind != NULL && namebind->nb_name != NULL) { if (namebind->nb_iswildcard == FALSE) { if (strcasecmp(namebind->nb_name, name) == 0) return namebind; } } else { int match_flags = PR_FNM_NOESCAPE|PR_FNM_CASEFOLD; if (pr_fnmatch(namebind->nb_name, name, match_flags) == 0) { pr_trace_msg(trace_channel, 9, "matched name '%s' against pattern '%s'", name, namebind->nb_name); return namebind; } pr_trace_msg(trace_channel, 9, "failed to match name '%s' against pattern '%s'", name, namebind->nb_name); } } } return NULL; }