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; } else { int xerrno; 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)); } errno = xerrno; return -1; } /* 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); 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)); } 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"); errno = EPERM; return -1; }
static int af_allow_grent(pool *p, struct group *grp) { if (af_group_file == NULL) { errno = EPERM; return -1; } /* Check that the grent is within the ID restrictions (if present). */ if (af_group_file->af_restricted_ids) { if (grp->gr_gid < af_group_file->af_min_id.gid) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': " "GID %s below the minimum allowed (%s)", grp->gr_name, pr_gid2str(p, grp->gr_gid), pr_gid2str(p, af_group_file->af_min_id.gid)); errno = EINVAL; return -1; } if (grp->gr_gid > af_group_file->af_max_id.gid) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': " "GID %s above the maximum allowed (%s)", grp->gr_name, pr_gid2str(p, grp->gr_gid), pr_gid2str(p, af_group_file->af_max_id.gid)); errno = EINVAL; return -1; } } #ifdef PR_USE_REGEX /* Check if the grent has an acceptable name. */ if (af_group_file->af_restricted_names) { int res; res = pr_regexp_exec(af_group_file->af_name_regex, grp->gr_name, 0, NULL, 0, 0, 0); if ((res != 0 && !af_group_file->af_name_regex_inverted) || (res == 0 && af_group_file->af_name_regex_inverted)) { pr_log_debug(DEBUG3, MOD_AUTH_FILE_VERSION ": skipping group '%s': " "name '%s' does not meet allowed filter '%s'", grp->gr_name, grp->gr_name, af_group_file->af_name_filter); errno = EINVAL; return -1; } } #endif /* regex support */ return 0; }
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) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "CreateHome: error reading link '%s': %s", src_path, strerror(xerrno)); errno = xerrno; 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) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "CreateHome: error symlinking '%s' to '%s': %s", link_path, dst_path, strerror(xerrno)); errno = xerrno; 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 %s/%s: %s", dst_path, pr_uid2str(p, uid), pr_gid2str(p, gid), strerror(errno)); } return 0; }
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; }
/* 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) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "CreateHome: error copying '%s' skel files: %s", src_dir, strerror(xerrno)); errno = xerrno; 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 %s/%s: %s", dst_path, pr_uid2str(p, uid), pr_gid2str(p, 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; }
MODRET site_chgrp(cmd_rec *cmd) { int res; gid_t gid; char *path = NULL, *tmp = NULL, *arg = ""; struct stat st; register unsigned int i = 0; #ifdef PR_USE_REGEX pr_regex_t *pre; #endif if (cmd->argc < 3) { pr_response_add_err(R_500, _("'SITE %s' not understood"), _get_full_cmd(cmd)); return NULL; } /* Construct the target file name by concatenating all the parameters after * the mode, separating them with spaces. */ for (i = 2; i <= cmd->argc-1; i++) { char *decoded_path; decoded_path = pr_fs_decode_path2(cmd->tmp_pool, cmd->argv[i], FSIO_DECODE_FL_TELL_ERRORS); if (decoded_path == NULL) { int xerrno = errno; pr_log_debug(DEBUG8, "'%s' failed to decode properly: %s", (char *) cmd->argv[i], strerror(xerrno)); pr_response_add_err(R_550, _("SITE %s: Illegal character sequence in command"), (char *) cmd->argv[1]); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", decoded_path, NULL); } #ifdef PR_USE_REGEX pre = get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) != 0) { pr_log_debug(DEBUG2, "'%s %s' denied by PathAllowFilter", (char *) cmd->argv[0], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } pre = get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE); if (pre != NULL && pr_regexp_exec(pre, arg, 0, NULL, 0, 0, 0) == 0) { pr_log_debug(DEBUG2, "'%s %s' denied by PathDenyFilter", (char *) cmd->argv[0], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); pr_cmd_set_errno(cmd, EPERM); errno = EPERM; return PR_ERROR(cmd); } #endif if (pr_fsio_lstat(arg, &st) == 0) { if (S_ISLNK(st.st_mode)) { char link_path[PR_TUNABLE_PATH_MAX]; int len; memset(link_path, '\0', sizeof(link_path)); len = dir_readlink(cmd->tmp_pool, arg, link_path, sizeof(link_path)-1, PR_DIR_READLINK_FL_HANDLE_REL_PATH); if (len > 0) { link_path[len] = '\0'; arg = pstrdup(cmd->tmp_pool, link_path); } } } path = dir_realpath(cmd->tmp_pool, arg); if (path == NULL) { int xerrno = errno; pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } /* Map the given group argument, if a string, to a GID. If already a * number, pass through as is. */ gid = strtoul(cmd->argv[1], &tmp, 10); if (tmp && *tmp) { /* Try the parameter as a group name. */ gid = pr_auth_name2gid(cmd->tmp_pool, cmd->argv[1]); if (gid == (gid_t) -1) { int xerrno = EINVAL; pr_log_debug(DEBUG9, "SITE CHGRP: Unable to resolve group name '%s' to GID", (char *) cmd->argv[1]); pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } } res = core_chgrp(cmd, path, (uid_t) -1, gid); if (res < 0) { int xerrno = errno; (void) pr_trace_msg("fileperms", 1, "%s, user '%s' (UID %s, GID %s): " "error chown'ing '%s' to GID %s: %s", (char *) cmd->argv[0], session.user, pr_uid2str(cmd->tmp_pool, session.uid), pr_gid2str(cmd->tmp_pool, session.gid), path, pr_gid2str(cmd->tmp_pool, gid), strerror(xerrno)); pr_response_add_err(R_550, "%s: %s", arg, strerror(xerrno)); pr_cmd_set_errno(cmd, xerrno); errno = xerrno; return PR_ERROR(cmd); } pr_response_add(R_200, _("SITE %s command successful"), (char *) cmd->argv[0]); return PR_HANDLED(cmd); }