/* Takes a directory and returns its absolute version. ~username references * are appropriately interpolated. "Absolute" includes a _full_ reference * based on the root directory, not upon a chrooted dir. */ char *dir_abs_path(pool *p, const char *path, int interpolate) { char *res = NULL; if (p == NULL || path == NULL) { errno = EINVAL; return NULL; } if (interpolate) { char buf[PR_TUNABLE_PATH_MAX+1]; memset(buf, '\0', sizeof(buf)); switch (pr_fs_interpolate(path, buf, sizeof(buf)-1)) { case -1: return NULL; case 0: /* Do nothing; path exists */ break; case 1: /* Interpolation occurred; make a copy of the interpolated path. */ path = pstrdup(p, buf); break; } } if (*path != '/') { if (session.chroot_path) { res = pdircat(p, session.chroot_path, pr_fs_getcwd(), path, NULL); } else { res = pdircat(p, pr_fs_getcwd(), path, NULL); } } else { if (session.chroot_path) { if (strncmp(path, session.chroot_path, strlen(session.chroot_path)) != 0) { res = pdircat(p, session.chroot_path, path, NULL); } else { res = pstrdup(p, path); } } else { res = pstrdup(p, path); } } return res; }
/* Interpolates a pathname, expanding ~ notation if necessary */ char *dir_interpolate(pool *p, const char *path) { struct passwd *pw; char *user,*tmp; char *ret = (char *)path; if (!ret) return NULL; if (*ret == '~') { user = pstrdup(p, ret+1); tmp = strchr(user, '/'); if (tmp) *tmp++ = '\0'; if (!*user) user = session.user; pw = pr_auth_getpwnam(p, user); if (!pw) { errno = ENOENT; return NULL; } ret = pdircat(p, pw->pw_dir, tmp, NULL); } return ret; }
static int copy_symlink(pool *p, const char *src_dir, const char *src_path, const char *dst_dir, const char *dst_path, uid_t uid, gid_t gid) { char *link_path = pcalloc(p, PR_TUNABLE_BUFFER_SIZE); int len; len = pr_fsio_readlink(src_path, link_path, PR_TUNABLE_BUFFER_SIZE-1); if (len < 0) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error reading link '%s': %s", src_path, strerror(errno)); return -1; } link_path[len] = '\0'; /* If the target of the link lies within the src path, rename that portion * of the link to be the corresponding part of the dst path. */ if (strncmp(link_path, src_dir, strlen(src_dir)) == 0) { link_path = pdircat(p, dst_dir, link_path + strlen(src_dir), NULL); } if (pr_fsio_symlink(link_path, dst_path) < 0) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error symlinking '%s' to '%s': %s", link_path, dst_path, strerror(errno)); return -1; } /* Make sure the new symlink has the proper ownership. */ if (pr_fsio_chown(dst_path, uid, gid) < 0) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error chown'ing '%s' to %u/%u: %s", dst_path, (unsigned int) uid, (unsigned int) gid, strerror(errno)); } return 0; }
static int site_misc_create_path(pool *p, const char *path) { struct stat st; char *curr_path, *dup_path; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) == 0) return 0; dup_path = pstrdup(p, path); curr_path = session.cwd; while (dup_path && *dup_path) { char *curr_dir = strsep(&dup_path, "/"); curr_path = pdircat(p, curr_path, curr_dir, NULL); if (site_misc_create_dir(curr_path) < 0) return -1; pr_signals_handle(); } return 0; }
/* Walk along a path, making sure that all directories in that path exist, * creating them if necessary. */ static int create_path(pool *p, const char *path, const char *user, uid_t dir_uid, gid_t dir_gid, mode_t dir_mode, uid_t dst_uid, gid_t dst_gid, mode_t dst_mode) { char *currpath = NULL, *tmppath = NULL; struct stat st; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) == 0) { /* Path already exists, nothing to be done. */ errno = EEXIST; return -1; } pr_event_generate("core.create-home", user); /* The special-case values of -1 for dir UID/GID mean that the destination * UID/GID should be used for the parent directories. */ if (dir_uid == (uid_t) -1) { dir_uid = dst_uid; } if (dir_gid == (gid_t) -1) { dir_gid = dst_gid; } pr_trace_msg(trace_channel, 5, "creating home directory '%s' for user '%s'", path, user); pr_log_debug(DEBUG3, "creating home directory '%s' for user '%s'", path, user); tmppath = pstrdup(p, path); currpath = "/"; while (tmppath && *tmppath) { char *currdir = strsep(&tmppath, "/"); currpath = pdircat(p, currpath, currdir, NULL); /* If tmppath is NULL, we are creating the last part of the path, so we * use the configured mode, and chown it to the given UID and GID. */ if (tmppath == NULL || (*tmppath == '\0')) { create_dir(currpath, dst_uid, dst_gid, dst_mode); } else { create_dir(currpath, dir_uid, dir_gid, dir_mode); } pr_signals_handle(); } pr_trace_msg(trace_channel, 5, "home directory '%s' created", path); pr_log_debug(DEBUG3, "home directory '%s' created", path); return 0; }
static int site_misc_delete_dir(pool *p, const char *dir) { void *dirh; struct dirent *dent; dirh = pr_fsio_opendir(dir); if (!dirh) return -1; while ((dent = pr_fsio_readdir(dirh)) != NULL) { struct stat st; char *file; pr_signals_handle(); if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; file = pdircat(p, dir, dent->d_name, NULL); if (pr_fsio_stat(file, &st) < 0) return -1; if (S_ISDIR(st.st_mode)) { if (site_misc_delete_dir(p, file) < 0) return -1; } else if (pr_fsio_unlink(file) < 0) return -1; } pr_fsio_closedir(dirh); if (pr_fsio_rmdir(dir) < 0) return -1; return 0; }
/* Walk along a path, making sure that all directories in that path exist, * creating them if necessary. */ static int create_path(pool *p, const char *path, const char *user, uid_t uid, gid_t gid, mode_t dir_mode, mode_t dst_mode) { char *currpath = NULL, *tmppath = NULL; struct stat st; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) == 0) { /* Path already exists, nothing to be done. */ errno = EEXIST; return -1; } pr_event_generate("core.create-home", user); pr_log_debug(DEBUG3, "creating home directory '%s' for user '%s'", path, user); tmppath = pstrdup(p, path); currpath = "/"; while (tmppath && *tmppath) { char *currdir = strsep(&tmppath, "/"); currpath = pdircat(p, currpath, currdir, NULL); /* If tmppath is NULL, we are creating the last part of the path, so we * use the configured mode, and chown it to the given UID and GID. */ if ((tmppath == NULL) || (*tmppath == '\0')) create_dir(currpath, uid, gid, dst_mode); else create_dir(currpath, 0, 0, dir_mode); pr_signals_handle(); } pr_log_debug(DEBUG3, "home directory '%s' created", path); return 0; }
/* Interpolates a pathname, expanding ~ notation if necessary */ char *dir_interpolate(pool *p, const char *path) { struct passwd *pw; char *res = NULL; if (p == NULL || path == NULL) { errno = EINVAL; return NULL; } if (*path == '~') { char *ptr, *user; user = pstrdup(p, path + 1); ptr = strchr(user, '/'); if (ptr) { *ptr++ = '\0'; } if (!*user) { user = session.user; } pw = pr_auth_getpwnam(p, user); if (pw == NULL) { errno = ENOENT; return NULL; } res = pdircat(p, pw->pw_dir, ptr, NULL); } else { res = pstrdup(p, path); } return res; }
const char *explain_path_error(pool *p, int err_errno, const char *full_path, int flags, mode_t mode) { register unsigned int i; array_header *components = NULL; const char *explained = NULL, *path = NULL, *prev_path = NULL; unsigned long name_max, no_trunc; if (p == NULL || full_path == NULL) { errno = EINVAL; return NULL; } /* Try to get some of the easy cases out of the way first. */ if (err_errno == ENAMETOOLONG) { unsigned long path_max; path_max = explain_platform_path_max(p, full_path); if (path_max > 0) { size_t path_len; path_len = strlen(full_path); if (path_len > path_max) { explained = describe_enametoolong_path(p, full_path, path_len, path_max, flags); return explained; } } } /* Now we need to walk the path. Whee. * * To do this, we split the path into an array, one element per component. * This SHOULD make it easier to progressively construct the longer and * longer paths, leading up to the final component/full path, for checking * each fully qualified component along the way. */ components = path_split(p, full_path); name_max = explain_platform_name_max(p, full_path); no_trunc = explain_platform_no_trunc(p, full_path); for (i = 0; i < components->nelts; i++) { const char **elts, *component; int final_component = FALSE, res, xerrno = 0; struct stat st; pr_signals_handle(); elts = components->elts; component = elts[i]; if (err_errno == ENAMETOOLONG) { size_t component_len; component_len = strlen(component); /* Component names can be too long only if the filesystem on which * the path resides does not silently truncate long names. */ if (component_len > name_max && no_trunc == 1) { explained = describe_enametoolong_name(p, component, component_len, name_max, flags); return explained; } } if (path == NULL) { path = component; } else { path = pdircat(p, path, component, NULL); } /* The following are the checks on the non-final components. The final * component has different constraints. */ final_component = (i == (components->nelts-1)); res = pr_fsio_lstat(path, &st); xerrno = errno; if (res < 0) { pr_trace_msg(trace_channel, 3, "error checking component #%u (of %u), path '%s': %s", i+1, components->nelts, path, strerror(xerrno)); } if (final_component == FALSE) { if (res < 0) { switch (xerrno) { case ENOENT: explained = describe_enoent_dir(p, path, flags); break; case EACCES: explained = describe_eacces_dir(p, path, flags); break; default: pr_trace_msg(trace_channel, 3, "unexplained error [%s (%d)] for directory '%s'", strerror(xerrno), errno, path); } break; } /* XXX What if this is a symlink? */ if (!S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode)) { /* Explains ENOTDIR */ explained = pstrcat(p, "path '", path, "' does not refer to a directory", NULL); break; } /* if WANT_SEARCH and no search, EACCES */ /* if found, and a directory, set current lookup directory, and go to * next component. */ } else { /* Last component, full path, leaf file. */ if (res < 0) { switch (xerrno) { case ENOENT: explained = describe_enoent_file(p, path, flags); break; case EACCES: explained = describe_eacces_file(p, path, flags); break; default: pr_trace_msg(trace_channel, 3, "unexplained error [%s (%d)] for file '%s'", strerror(xerrno), errno, path); } break; } switch (err_errno) { case EACCES: explained = describe_eacces_file(p, path, flags); if (explained != NULL) { if (pr_fsio_lstat(prev_path, &st) == 0) { explained = pstrcat(p, explained, "; parent directory '", prev_path, "' has perms ", mode2s(p, st.st_mode), ", and is owned by UID ", pr_uid2str(p, st.st_uid), ", GID ", pr_gid2str(p, st.st_gid), NULL); } } break; default: pr_trace_msg(trace_channel, 3, "unexplained error [%s (%d)] for file '%s'", strerror(xerrno), errno, path); } } prev_path = path; } return explained; }
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 pr_parser_parse_file(pool *p, const char *path, config_rec *start, int flags) { pr_fh_t *fh; struct stat st; struct config_src *cs; cmd_rec *cmd; pool *tmp_pool; char *buf, *report_path; size_t bufsz; if (path == NULL) { errno = EINVAL; return -1; } if (parser_servstack == NULL) { errno = EPERM; return -1; } tmp_pool = make_sub_pool(p ? p : permanent_pool); pr_pool_tag(tmp_pool, "parser file pool"); report_path = (char *) path; if (session.chroot_path) { report_path = pdircat(tmp_pool, session.chroot_path, path, NULL); } if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path); } fh = pr_fsio_open(path, O_RDONLY); if (fh == NULL) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Stat the opened file to determine the optimal buffer size for IO. */ memset(&st, 0, sizeof(st)); if (pr_fsio_fstat(fh, &st) < 0) { int xerrno = errno; pr_fsio_close(fh); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISDIR(st.st_mode)) { pr_fsio_close(fh); destroy_pool(tmp_pool); errno = EISDIR; return -1; } /* Advise the platform that we will be only reading this file * sequentially. */ pr_fs_fadvise(PR_FH_FD(fh), 0, 0, PR_FS_FADVISE_SEQUENTIAL); /* Check for world-writable files (and later, files in world-writable * directories). * * For now, just warn about these; later, we will be more draconian. */ if (st.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable", path); } fh->fh_iosz = st.st_blksize; /* Push the configuration information onto the stack of configuration * sources. */ cs = add_config_source(fh); if (start != NULL) { (void) pr_parser_config_ctxt_push(start); } bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE; buf = pcalloc(tmp_pool, bufsz + 1); while (pr_parser_read_line(buf, bufsz) != NULL) { pr_signals_handle(); cmd = pr_parser_parse_line(tmp_pool, buf, 0); if (cmd == NULL) { continue; } if (cmd->argc) { conftable *conftab; char found = FALSE; cmd->server = *parser_curr_server; cmd->config = *parser_curr_config; conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL, &cmd->stash_index, &cmd->stash_hash); while (conftab != NULL) { modret_t *mr; pr_signals_handle(); cmd->argv[0] = conftab->directive; pr_trace_msg(trace_channel, 7, "dispatching directive '%s' to module mod_%s", conftab->directive, conftab->m->name); mr = pr_module_call(conftab->m, conftab->handler, cmd); if (mr != NULL) { if (MODRET_ISERROR(mr)) { if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); destroy_pool(tmp_pool); errno = EPERM; return -1; } pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); } } if (!MODRET_ISDECLINED(mr)) { found = TRUE; } conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab, &cmd->stash_index, &cmd->stash_hash); } if (cmd->tmp_pool) { destroy_pool(cmd->tmp_pool); } if (found == FALSE) { register unsigned int i; char *name; size_t namelen; int non_ascii = FALSE; /* I encountered a case where a particular configuration file had * what APPEARED to be a valid directive, but the parser kept reporting * that the directive was unknown. I now suspect that the file in * question had embedded UTF8 characters (spaces, perhaps), which * would appear as normal spaces in e.g. UTF8-aware editors/terminals, * but which the parser would rightly refuse. * * So to indicate that this might be the case, check for any non-ASCII * characters in the "unknown" directive name, and if found, log * about them. */ name = cmd->argv[0]; namelen = strlen(name); for (i = 0; i < namelen; i++) { if (!isascii((int) name[i])) { non_ascii = TRUE; break; } } if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive " "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path); if (non_ascii) { pr_log_pri(PR_LOG_WARNING, "fatal: malformed directive name " "'%s' (contains non-ASCII characters)", name); } else { array_header *directives, *similars; directives = get_all_directives(tmp_pool); similars = pr_str_get_similars(tmp_pool, name, directives, 0, PR_STR_FL_IGNORE_CASE); if (similars != NULL && similars->nelts > 0) { unsigned int nelts; const char **names, *msg; names = similars->elts; nelts = similars->nelts; if (nelts > 4) { nelts = 4; } msg = "fatal: Did you mean:"; if (nelts == 1) { msg = pstrcat(tmp_pool, msg, " ", names[0], NULL); } else { for (i = 0; i < nelts; i++) { msg = pstrcat(tmp_pool, msg, "\n ", names[i], NULL); } } pr_log_pri(PR_LOG_WARNING, "%s", msg); } } destroy_pool(tmp_pool); errno = EPERM; return -1; } pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive " "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path); if (non_ascii) { pr_log_pri(PR_LOG_WARNING, "warning: malformed directive name " "'%s' (contains non-ASCII characters)", name); } } } destroy_pool(cmd->pool); memset(buf, '\0', bufsz); } /* Pop this configuration stream from the stack. */ remove_config_source(); pr_fsio_close(fh); 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 int dso_load_module(char *name) { int res; char *symbol_name, *path, *tmp; module *m; lt_ptr mh = NULL; lt_dladvise advise; if (name == NULL) { errno = EINVAL; return -1; } if (strncmp(name, "mod_", 4) != 0 || name[strlen(name)-2] != '.' || name[strlen(name)-1] != 'c') { errno = EINVAL; return -1; } pr_log_debug(DEBUG7, "loading '%s'", name); tmp = strrchr(name, '.'); if (tmp == NULL) { errno = EINVAL; return -1; } if (lt_dladvise_init(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to initialise advise: %s", lt_dlerror()); errno = EPERM; return -1; } if (lt_dladvise_ext(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to setting 'ext' advise hint: %s", lt_dlerror()); lt_dladvise_destroy(&advise); errno = EPERM; return -1; } if (lt_dladvise_global(&advise) < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": unable to setting 'global' advise hint: %s", lt_dlerror()); lt_dladvise_destroy(&advise); errno = EPERM; return -1; } *tmp = '\0'; /* Load file: $prefix/libexec/<module> */ path = pdircat(dso_pool, dso_module_path, name, NULL); pr_trace_msg(trace_channel, 5, "loading module '%s'", path); mh = lt_dlopenadvise(path, advise); if (mh == NULL) { *tmp = '.'; pr_log_debug(DEBUG3, MOD_DSO_VERSION ": unable to dlopen '%s': %s (%s)", name, lt_dlerror(), strerror(errno)); pr_log_debug(DEBUG3, MOD_DSO_VERSION ": defaulting to 'self' for symbol resolution"); lt_dladvise_destroy(&advise); mh = lt_dlopen(NULL); if (mh == NULL) { pr_log_debug(DEBUG0, MOD_DSO_VERSION ": error loading 'self': %s", lt_dlerror()); if (errno == ENOENT) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": check to see if '%s.la' exists", path); } return -1; } } lt_dladvise_destroy(&advise); /* Tease name of the module structure out of the given name: * <module>.<ext> --> <module>_module */ *tmp = '\0'; symbol_name = pstrcat(dso_pool, name+4, "_module", NULL); /* Lookup module structure symbol by name. */ pr_trace_msg(trace_channel, 7, "looking for symbol '%s' in loaded module", symbol_name); m = (module *) lt_dlsym(mh, symbol_name); if (m == NULL) { *tmp = '.'; pr_log_debug(DEBUG1, MOD_DSO_VERSION ": unable to find module symbol '%s' in '%s'", symbol_name, mh ? name : "self"); pr_trace_msg(trace_channel, 1, "unable to find module symbol '%s' in '%s'", symbol_name, mh ? name : "self"); lt_dlclose(mh); mh = NULL; if (errno == ENOENT) { pr_log_pri(PR_LOG_NOTICE, MOD_DSO_VERSION ": check to see if '%s.la' exists", path); } return -1; } *tmp = '.'; m->handle = mh; /* Add the module to the core structures */ res = pr_module_load(m); if (res < 0) { if (errno == EEXIST) { pr_log_pri(PR_LOG_INFO, MOD_DSO_VERSION ": module 'mod_%s.c' already loaded", m->name); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' already loaded", m->name); } else if (errno == EACCES) { pr_log_pri(PR_LOG_ERR, MOD_DSO_VERSION ": module 'mod_%s.c' has wrong API version (0x%x), must be 0x%x", m->name, m->api_version, PR_MODULE_API_VERSION); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' has wrong API version (0x%x), must be 0x%x", m->name, m->api_version, PR_MODULE_API_VERSION); } else if (errno == EPERM) { pr_log_pri(PR_LOG_ERR, MOD_DSO_VERSION ": module 'mod_%s.c' failed to initialize", m->name); pr_trace_msg(trace_channel, 1, "module 'mod_%s.c' failed to initialize", m->name); } lt_dlclose(mh); mh = NULL; return -1; } pr_trace_msg(trace_channel, 8, "module '%s' successfully loaded", path); return 0; }
int pr_parser_parse_file(pool *p, const char *path, config_rec *start, int flags) { pr_fh_t *fh; struct stat st; struct config_src *cs; cmd_rec *cmd; pool *tmp_pool; char *report_path; if (path == NULL) { errno = EINVAL; return -1; } tmp_pool = make_sub_pool(p ? p : permanent_pool); pr_pool_tag(tmp_pool, "parser file pool"); report_path = (char *) path; if (session.chroot_path) report_path = pdircat(tmp_pool, session.chroot_path, path, NULL); if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path); fh = pr_fsio_open(path, O_RDONLY); if (fh == NULL) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Stat the opened file to determine the optimal buffer size for IO. */ memset(&st, 0, sizeof(st)); if (pr_fsio_fstat(fh, &st) < 0) { int xerrno = errno; pr_fsio_close(fh); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISDIR(st.st_mode)) { pr_fsio_close(fh); destroy_pool(tmp_pool); errno = EISDIR; return -1; } /* Check for world-writable files (and later, files in world-writable * directories). * * For now, just warn about these; later, we will be more draconian. */ if (st.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable", path); } fh->fh_iosz = st.st_blksize; /* Push the configuration information onto the stack of configuration * sources. */ cs = add_config_source(fh); if (start) add_config_ctxt(start); while ((cmd = pr_parser_parse_line(tmp_pool)) != NULL) { pr_signals_handle(); if (cmd->argc) { conftable *conftab; char found = FALSE; cmd->server = *parser_curr_server; cmd->config = *parser_curr_config; conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL, &cmd->stash_index, &cmd->stash_hash); while (conftab) { modret_t *mr; pr_signals_handle(); cmd->argv[0] = conftab->directive; pr_trace_msg(trace_channel, 7, "dispatching directive '%s' to module mod_%s", conftab->directive, conftab->m->name); mr = pr_module_call(conftab->m, conftab->handler, cmd); if (mr != NULL) { if (MODRET_ISERROR(mr)) { if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); exit(1); } else { pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); } } } if (!MODRET_ISDECLINED(mr)) { found = TRUE; } conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab, &cmd->stash_index, &cmd->stash_hash); } if (cmd->tmp_pool) destroy_pool(cmd->tmp_pool); if (!found) { if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive " "'%s' on line %u of '%s'", cmd->argv[0], cs->cs_lineno, report_path); exit(1); } else { pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive " "'%s' on line %u of '%s'", cmd->argv[0], cs->cs_lineno, report_path); } } } destroy_pool(cmd->pool); } /* Pop this configuration stream from the stack. */ remove_config_source(); pr_fsio_close(fh); destroy_pool(tmp_pool); return 0; }
static int copy_dir(pool *p, const char *src_dir, const char *dst_dir) { DIR *dh = NULL; struct dirent *dent = NULL; int res = 0; pool *iter_pool = NULL; dh = opendir(src_dir); if (dh == NULL) { pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error reading directory '%s': %s", src_dir, strerror(errno)); return -1; } while ((dent = readdir(dh)) != NULL) { struct stat st; char *src_path, *dst_path; pr_signals_handle(); /* Skip "." and ".." */ if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } if (iter_pool != NULL) { destroy_pool(iter_pool); } iter_pool = pr_pool_create_sz(p, 128); src_path = pdircat(iter_pool, src_dir, dent->d_name, NULL); dst_path = pdircat(iter_pool, dst_dir, dent->d_name, NULL); if (pr_fsio_lstat(src_path, &st) < 0) { pr_log_debug(DEBUG3, MOD_COPY_VERSION ": unable to stat '%s' (%s), skipping", src_path, strerror(errno)); continue; } /* Is this path to a directory? */ if (S_ISDIR(st.st_mode)) { if (create_path(iter_pool, dst_path) < 0) { res = -1; break; } if (copy_dir(iter_pool, src_path, dst_path) < 0) { res = -1; break; } continue; /* Is this path to a regular file? */ } else if (S_ISREG(st.st_mode)) { cmd_rec *cmd; /* Dispatch fake COPY command, e.g. for mod_quotatab */ cmd = pr_cmd_alloc(iter_pool, 4, pstrdup(iter_pool, "SITE"), pstrdup(iter_pool, "COPY"), pstrdup(iter_pool, src_path), pstrdup(iter_pool, dst_path)); cmd->arg = pstrcat(iter_pool, "COPY ", src_path, " ", dst_path, NULL); cmd->cmd_class = CL_WRITE; pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); if (pr_cmd_dispatch_phase(cmd, PRE_CMD, 0) < 0) { int xerrno = errno; pr_log_debug(DEBUG3, MOD_COPY_VERSION ": COPY of '%s' to '%s' blocked by COPY handler: %s", src_path, dst_path, strerror(xerrno)); pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); errno = xerrno; res = -1; break; } else { if (pr_fs_copy_file(src_path, dst_path) < 0) { pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); res = -1; break; } else { char *abs_path; pr_cmd_dispatch_phase(cmd, POST_CMD, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD, 0); pr_response_clear(&resp_list); /* Write a TransferLog entry as well. */ pr_fs_clear_cache2(dst_path); pr_fsio_stat(dst_path, &st); abs_path = dir_abs_path(p, dst_path, TRUE); if (session.sf_flags & SF_ANON) { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'a', session.anon_user, 'c', "_"); } else { xferlog_write(0, session.c->remote_name, st.st_size, abs_path, (session.sf_flags & SF_ASCII ? 'a' : 'b'), 'd', 'r', session.user, 'c', "_"); } } } continue; /* Is this path a symlink? */ } else if (S_ISLNK(st.st_mode)) { if (copy_symlink(iter_pool, src_path, dst_path) < 0) { res = -1; break; } continue; /* All other file types are skipped */ } else { pr_log_debug(DEBUG3, MOD_COPY_VERSION ": skipping supported file '%s'", src_path); continue; } } if (iter_pool != NULL) { destroy_pool(iter_pool); } closedir(dh); return res; }
/* srcdir is to be considered a "skeleton" directory, in the manner of * /etc/skel, and destdir is a user's newly created home directory that needs * to be populated with the files in srcdir. */ static int copy_dir(pool *p, const char *src_dir, const char *dst_dir, uid_t uid, gid_t gid) { DIR *dh = NULL; struct dirent *dent = NULL; dh = opendir(src_dir); if (dh == NULL) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error copying '%s' skel files: %s", src_dir, strerror(errno)); return -1; } while ((dent = readdir(dh)) != NULL) { struct stat st; char *src_path, *dst_path; pr_signals_handle(); /* Skip "." and ".." */ if (strncmp(dent->d_name, ".", 2) == 0 || strncmp(dent->d_name, "..", 3) == 0) { continue; } src_path = pdircat(p, src_dir, dent->d_name, NULL); dst_path = pdircat(p, dst_dir, dent->d_name, NULL); if (pr_fsio_lstat(src_path, &st) < 0) { pr_log_debug(DEBUG3, "CreateHome: unable to stat '%s' (%s), skipping", src_path, strerror(errno)); continue; } /* Is this path to a directory? */ if (S_ISDIR(st.st_mode)) { create_dir(dst_path, uid, gid, st.st_mode); copy_dir(p, src_path, dst_path, uid, gid); continue; /* Is this path to a regular file? */ } else if (S_ISREG(st.st_mode)) { mode_t dst_mode = st.st_mode; /* Make sure to prevent S{U,G}ID permissions on target files. */ if (dst_mode & S_ISUID) dst_mode &= ~S_ISUID; if (dst_mode & S_ISGID) dst_mode &= ~S_ISGID; (void) pr_fs_copy_file(src_path, dst_path); /* Make sure the destination file has the proper ownership and mode. */ if (pr_fsio_chown(dst_path, uid, gid) < 0) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error chown'ing '%s' " "to %u/%u: %s", dst_path, (unsigned int) uid, (unsigned int) gid, strerror(errno)); } if (pr_fsio_chmod(dst_path, dst_mode) < 0) { pr_log_pri(PR_LOG_WARNING, "CreateHome: error chmod'ing '%s' to " "%04o: %s", dst_path, (unsigned int) dst_mode, strerror(errno)); } continue; /* Is this path a symlink? */ } else if (S_ISLNK(st.st_mode)) { copy_symlink(p, src_dir, src_path, dst_dir, dst_path, uid, gid); continue; /* All other file types are skipped */ } else { pr_log_debug(DEBUG3, "CreateHome: skipping skel file '%s'", src_path); continue; } } closedir(dh); return 0; }
static int create_path(pool *p, const char *path) { struct stat st; char *curr_path, *dup_path; pr_fs_clear_cache2(path); if (pr_fsio_stat(path, &st) == 0) { return 0; } dup_path = pstrdup(p, path); curr_path = "/"; while (dup_path && *dup_path) { char *curr_dir; int res; cmd_rec *cmd; pool *sub_pool; pr_signals_handle(); curr_dir = strsep(&dup_path, "/"); curr_path = pdircat(p, curr_path, curr_dir, NULL); /* Dispatch fake C_MKD command, e.g. for mod_quotatab */ sub_pool = pr_pool_create_sz(p, 64); cmd = pr_cmd_alloc(sub_pool, 2, pstrdup(sub_pool, C_MKD), pstrdup(sub_pool, curr_path)); cmd->arg = pstrdup(cmd->pool, curr_path); cmd->cmd_class = CL_DIRS|CL_WRITE; pr_response_clear(&resp_list); pr_response_clear(&resp_err_list); res = pr_cmd_dispatch_phase(cmd, PRE_CMD, 0); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG3, MOD_COPY_VERSION ": creating directory '%s' blocked by MKD handler: %s", curr_path, strerror(xerrno)); pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); destroy_pool(sub_pool); errno = xerrno; return -1; } res = create_dir(curr_path); if (res < 0) { pr_cmd_dispatch_phase(cmd, POST_CMD_ERR, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD_ERR, 0); pr_response_clear(&resp_err_list); destroy_pool(sub_pool); return -1; } pr_cmd_dispatch_phase(cmd, POST_CMD, 0); pr_cmd_dispatch_phase(cmd, LOG_CMD, 0); pr_response_clear(&resp_list); destroy_pool(sub_pool); } return 0; }
/* Performs chroot-aware handling of symlinks. */ int dir_readlink(pool *p, const char *path, char *buf, size_t bufsz, int flags) { int is_abs_dst, clean_flags, len, res = -1; size_t chroot_pathlen = 0, adj_pathlen = 0; char *dst_path, *adj_path; pool *tmp_pool; if (p == NULL || path == NULL || buf == NULL) { errno = EINVAL; return -1; } if (bufsz == 0) { return 0; } len = pr_fsio_readlink(path, buf, bufsz); if (len < 0) { return -1; } if (len == 0 || len == bufsz) { /* If we read nothing in, OR if the given buffer was completely * filled WITHOUT terminating NUL, there's really nothing we can/should * be doing. */ return len; } is_abs_dst = FALSE; if (*buf == '/') { is_abs_dst = TRUE; } if (session.chroot_path != NULL) { chroot_pathlen = strlen(session.chroot_path); } if (chroot_pathlen <= 1) { char *ptr; if (is_abs_dst == TRUE || !(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) { return len; } /* Since we have a relative destination path, we will concat it * with the source path's directory, then clean up that path. */ ptr = strrchr(path, '/'); if (ptr != NULL && ptr != path) { char *parent_dir; tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "dir_readlink pool"); parent_dir = pstrndup(tmp_pool, path, (ptr - path)); dst_path = pdircat(tmp_pool, parent_dir, buf, NULL); adj_pathlen = bufsz + 1; adj_path = pcalloc(tmp_pool, adj_pathlen); res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, 0); if (res == 0) { pr_trace_msg("fsio", 19, "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path); dst_path = adj_path; } pr_trace_msg("fsio", 19, "adjusted relative symlink path '%s', yielding '%s'", buf, dst_path); memset(buf, '\0', bufsz); sstrncpy(buf, dst_path, bufsz); len = strlen(buf); destroy_pool(tmp_pool); } return len; } if (is_abs_dst == FALSE) { /* If we are to ignore relative destination paths, return now. */ if (!(flags & PR_DIR_READLINK_FL_HANDLE_REL_PATH)) { return len; } } if (is_abs_dst == TRUE && len < chroot_pathlen) { /* If the destination path length is shorter than the chroot path, * AND the destination path is absolute, then by definition it CANNOT * point within the chroot. */ return len; } tmp_pool = make_sub_pool(p); pr_pool_tag(tmp_pool, "dir_readlink pool"); dst_path = pstrdup(tmp_pool, buf); if (is_abs_dst == FALSE) { char *ptr; /* Since we have a relative destination path, we will concat it * with the source path's directory, then clean up that path. */ ptr = strrchr(path, '/'); if (ptr != NULL && ptr != path) { char *parent_dir; parent_dir = pstrndup(tmp_pool, path, (ptr - path)); dst_path = pdircat(tmp_pool, parent_dir, dst_path, NULL); } else { dst_path = pdircat(tmp_pool, path, dst_path, NULL); } } adj_pathlen = bufsz + 1; adj_path = pcalloc(tmp_pool, adj_pathlen); clean_flags = PR_FSIO_CLEAN_PATH_FL_MAKE_ABS_PATH; res = pr_fs_clean_path2(dst_path, adj_path, adj_pathlen-1, clean_flags); if (res == 0) { pr_trace_msg("fsio", 19, "cleaned symlink path '%s', yielding '%s'", dst_path, adj_path); dst_path = adj_path; memset(buf, '\0', bufsz); sstrncpy(buf, dst_path, bufsz); len = strlen(dst_path); } if (strncmp(dst_path, session.chroot_path, chroot_pathlen) == 0 && *(dst_path + chroot_pathlen) == '/') { char *ptr; ptr = dst_path + chroot_pathlen; if (is_abs_dst == FALSE && res == 0) { /* If we originally had a relative destination path, AND we cleaned * that adjusted path, then we should try to re-adjust the path * back to being a relative path. Within reason. */ ptr = pstrcat(tmp_pool, ".", ptr, NULL); } /* Since we are making the destination path shorter, the given buffer * (which was big enough for the original destination path) should * always be large enough for this adjusted, shorter version. Right? */ pr_trace_msg("fsio", 19, "adjusted symlink path '%s' for chroot '%s', yielding '%s'", dst_path, session.chroot_path, ptr); memset(buf, '\0', bufsz); sstrncpy(buf, ptr, bufsz); len = strlen(buf); } destroy_pool(tmp_pool); return len; }