static int site_misc_create_dir(const char *dir) { struct stat st; int res; pr_fs_clear_cache(); res = pr_fsio_stat(dir, &st); if (res == -1 && errno != ENOENT) { pr_log_debug(DEBUG2, MOD_SITE_MISC_VERSION ": error checking '%s': %s", dir, strerror(errno)); return -1; } if (res == 0) return 0; if (pr_fsio_mkdir(dir, 0777) < 0) { pr_log_debug(DEBUG2, MOD_SITE_MISC_VERSION ": error creating '%s': %s", dir, strerror(errno)); return -1; } return 0; }
/* usage: ModulePath path */ MODRET set_modulepath(cmd_rec *cmd) { int res; struct stat st; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT); if (pr_fs_valid_path(cmd->argv[1]) < 0) CONF_ERROR(cmd, "must be an absolute path"); /* Make sure that the configured path is not world-writeable. */ res = pr_fsio_stat(cmd->argv[1], &st); if (res < 0) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error checking '", cmd->argv[1], "': ", strerror(errno), NULL)); if (!S_ISDIR(st.st_mode)) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, cmd->argv[1], " is not a directory", NULL)); if (st.st_mode & S_IWOTH) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, cmd->argv[1], " is world-writable", NULL)); if (lt_dlsetsearchpath(cmd->argv[1]) < 0) CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error setting module path: ", lt_dlerror(), NULL)); dso_module_path = pstrdup(dso_pool, cmd->argv[1]); return PR_HANDLED(cmd); }
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 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; }
static int wrap_is_usable_file(char *filename) { struct stat statbuf; pr_fh_t *fh = NULL; /* check the easy case first */ if (filename == NULL) return FALSE; if (pr_fsio_stat(filename, &statbuf) == -1) { pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": \"%s\": %s", filename, strerror(errno)); return FALSE; } /* OK, the file exists. Now, to make sure that the current process * can _read_ the file */ fh = pr_fsio_open(filename, O_RDONLY); if (fh == NULL) { pr_log_pri(PR_LOG_INFO, MOD_WRAP_VERSION ": \"%s\": %s", filename, strerror(errno)); return FALSE; } pr_fsio_close(fh); return TRUE; }
static int create_dir(const char *dir, uid_t uid, gid_t gid, mode_t mode) { mode_t prev_mask; struct stat st; int res = -1; pr_fs_clear_cache2(dir); res = pr_fsio_stat(dir, &st); if (res == -1 && errno != ENOENT) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "error checking '%s': %s", dir, strerror(xerrno)); errno = xerrno; return -1; } /* The directory already exists. */ if (res == 0) { pr_trace_msg(trace_channel, 8, "'%s' already exists", dir); pr_log_debug(DEBUG3, "CreateHome: '%s' already exists", dir); return 0; } /* The given mode is absolute, not subject to any Umask setting. */ prev_mask = umask(0); if (pr_fsio_mkdir(dir, mode) < 0) { int xerrno = errno; umask(prev_mask); pr_log_pri(PR_LOG_WARNING, "error creating '%s': %s", dir, strerror(xerrno)); errno = xerrno; return -1; } umask(prev_mask); if (pr_fsio_chown(dir, uid, gid) < 0) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, "error setting ownership of '%s': %s", dir, strerror(xerrno)); errno = xerrno; return -1; } pr_trace_msg(trace_channel, 8, "directory '%s' created", dir); pr_log_debug(DEBUG6, "CreateHome: directory '%s' created", dir); 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 af_setpwent(void) { 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 %lu, GID %lu, perms %04o, accessed by UID %lu, GID %lu): %s", af_user_file->af_path, (unsigned long) st.st_uid, (unsigned long) st.st_gid, st.st_mode & ~S_IFMT, (unsigned long) geteuid(), (unsigned long) 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; } 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 site_misc_delete_path(pool *p, const char *path) { struct stat st; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) < 0) return -1; if (!S_ISDIR(st.st_mode)) { errno = EINVAL; return -1; } return site_misc_delete_dir(p, path); }
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; }
static int create_dir(const char *dir) { struct stat st; int res = -1; pr_fs_clear_cache2(dir); res = pr_fsio_stat(dir, &st); if (res < 0 && errno != ENOENT) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error checking '%s': %s", dir, strerror(xerrno)); errno = xerrno; return -1; } /* The directory already exists. */ if (res == 0) { pr_trace_msg(trace_channel, 9, "path '%s' already exists", dir); return 1; } if (pr_fsio_mkdir(dir, 0777) < 0) { int xerrno = errno; pr_log_pri(PR_LOG_WARNING, MOD_COPY_VERSION ": error creating '%s': %s", dir, strerror(xerrno)); errno = xerrno; return -1; } pr_log_debug(DEBUG6, MOD_COPY_VERSION ": directory '%s' created", dir); 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; }
MODRET site_chmod(cmd_rec *cmd) { int res; mode_t mode = 0; char *dir, *endp, *mode_str, *tmp, *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 %s' denied by PathAllowFilter", (char *) cmd->argv[0], (char *) cmd->argv[1], 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 %s' denied by PathDenyFilter", (char *) cmd->argv[0], (char *) cmd->argv[1], 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); } } } dir = dir_realpath(cmd->tmp_pool, arg); if (dir == 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); } /* If the first character isn't '0', prepend it and attempt conversion. * This will fail if the chmod is a symbolic, but takes care of the * case where an octal number is sent without the leading '0'. */ mode_str = cmd->argv[1]; if (mode_str[0] != '0') { tmp = pstrcat(cmd->tmp_pool, "0", mode_str, NULL); } else { tmp = mode_str; } mode = strtol(tmp, &endp, 0); if (endp && *endp) { /* It's not an absolute number, try symbolic */ char *cp = mode_str; int mask = 0, mode_op = 0, curr_mode = 0, curr_umask = umask(0); int invalid = 0; char *who, *how, *what; umask(curr_umask); mode = 0; if (pr_fsio_stat(dir, &st) != -1) { curr_mode = st.st_mode; } while (TRUE) { pr_signals_handle(); who = pstrdup(cmd->tmp_pool, cp); tmp = strpbrk(who, "+-="); if (tmp != NULL) { how = pstrdup(cmd->tmp_pool, tmp); if (*how != '=') { mode = curr_mode; } *tmp = '\0'; } else { invalid++; break; } tmp = strpbrk(how, "rwxXstugo"); if (tmp != NULL) { what = pstrdup(cmd->tmp_pool, tmp); *tmp = '\0'; } else { invalid++; break; } cp = what; while (cp) { switch (*who) { case 'u': mask = 0077; break; case 'g': mask = 0707; break; case 'o': mask = 0770; break; case 'a': mask = 0000; break; case '\0': mask = curr_umask; break; default: invalid++; break; } if (invalid) break; switch (*how) { case '+': case '-': case '=': break; default: invalid++; } if (invalid) break; switch (*cp) { case 'r': mode_op |= (S_IRUSR|S_IRGRP|S_IROTH); break; case 'w': mode_op |= (S_IWUSR|S_IWGRP|S_IWOTH); break; case 'x': mode_op |= (S_IXUSR|S_IXGRP|S_IXOTH); break; /* 'X' not implemented */ case 's': /* setuid */ mode_op |= S_ISUID; break; case 't': /* sticky */ mode_op |= S_ISVTX; break; case 'o': mode_op |= (curr_mode & S_IRWXO); mode_op |= ((curr_mode & S_IRWXO) << 3); mode_op |= ((curr_mode & S_IRWXO) << 6); break; case 'g': mode_op |= ((curr_mode & S_IRWXG) >> 3); mode_op |= (curr_mode & S_IRWXG); mode_op |= ((curr_mode & S_IRWXG) << 3); break; case 'u': mode_op |= ((curr_mode & S_IRWXU) >> 6); mode_op |= ((curr_mode & S_IRWXU) >> 3); mode_op |= (curr_mode & S_IRWXU); break; case '\0': /* Apply the mode and move on */ switch (*how) { case '+': case '=': mode |= (mode_op & ~mask); break; case '-': mode &= ~(mode_op & ~mask); break; } mode_op = 0; if (*who && *(who+1)) { who++; cp = what; continue; } else { cp = NULL; } break; default: invalid++; } if (invalid) { break; } if (cp) { cp++; } } break; } if (invalid) { pr_response_add_err(R_550, _("'%s': invalid mode"), (char *) cmd->argv[1]); pr_cmd_set_errno(cmd, EINVAL); errno = EINVAL; return PR_ERROR(cmd); } }
static int check_facl(pool *p, const char *path, int mode, void *acl, int nents, struct stat *st, uid_t uid, gid_t gid, array_header *suppl_gids) { # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) register unsigned int i; int have_access_entry = FALSE, res = -1; pool *acl_pool; acl_t facl = acl; acl_entry_t ae; acl_tag_t ae_type; acl_entry_t acl_user_entry = NULL; acl_entry_t acl_group_entry = NULL; acl_entry_t acl_other_entry = NULL; acl_entry_t acl_mask_entry = NULL; array_header *acl_groups; array_header *acl_users; /* Iterate through all of the ACL entries, sorting them for later * checking. */ res = acl_get_entry(facl, ACL_FIRST_ENTRY, &ae); if (res < 0) { pr_log_debug(DEBUG10, "FS: unable to retrieve first ACL entry for '%s': %s", path, strerror(errno)); errno = EACCES; return -1; } if (res == 0) { pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s' has no entries!", path); errno = EACCES; return -1; } acl_pool = make_sub_pool(p); acl_groups = make_array(acl_pool, 1, sizeof(acl_entry_t)); acl_users = make_array(acl_pool, 1, sizeof(acl_entry_t)); while (res > 0) { if (acl_get_tag_type(ae, &ae_type) < 0) { pr_log_debug(DEBUG5, "FS: error retrieving type of ACL entry for '%s': %s", path, strerror(errno)); res = acl_get_entry(facl, ACL_NEXT_ENTRY, &ae); continue; } if (ae_type & ACL_USER_OBJ) { acl_copy_entry(acl_user_entry, ae); } else if (ae_type & ACL_USER) { acl_entry_t *ae_dup = push_array(acl_users); acl_copy_entry(*ae_dup, ae); } else if (ae_type & ACL_GROUP_OBJ) { acl_copy_entry(acl_group_entry, ae); } else if (ae_type & ACL_GROUP) { acl_entry_t *ae_dup = push_array(acl_groups); acl_copy_entry(*ae_dup, ae); } else if (ae_type & ACL_OTHER) { acl_copy_entry(acl_other_entry, ae); } else if (ae_type & ACL_MASK) { acl_copy_entry(acl_mask_entry, ae); } res = acl_get_entry(facl, ACL_NEXT_ENTRY, &ae); } /* Select the ACL entry that determines access. */ res = -1; /* 1. If the given user ID matches the file owner, use that entry for * access. */ if (uid == st->st_uid) { /* Check the acl_user_entry for access. */ acl_copy_entry(ae, acl_user_entry); ae_type = ACL_USER_OBJ; have_access_entry = TRUE; } /* 2. If not matched above, and f the given user ID matches one of the * named user entries, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_users->nelts; i++) { acl_entry_t e = ((acl_entry_t *) acl_users->elts)[i]; if (uid == *((uid_t *) acl_get_qualifier(e))) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ acl_copy_entry(ae, e); ae_type = ACL_USER; have_access_entry = TRUE; break; } } /* 3. If not matched above, and if one of the group IDs matches the * group owner entry, and the group owner entry contains the * requested permissions, use that entry for access. */ if (!have_access_entry && gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ acl_permset_t perms; acl_get_permset(acl_group_entry, &perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(perms, mode) == 1) { # endif acl_copy_entry(ae, acl_group_entry); ae_type = ACL_GROUP_OBJ; have_access_entry = TRUE; } } if (suppl_gids) { for (i = 0; !have_access_entry && i < suppl_gids->nelts; i++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[i]; if (suppl_gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ acl_permset_t perms; acl_get_permset(acl_group_entry, &perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(perms, mode) == 1) { # endif acl_copy_entry(ae, acl_group_entry); ae_type = ACL_GROUP_OBJ; have_access_entry = TRUE; break; } } } } /* 5. If not matched above, and if one of the group IDs matches one * of the named group entries, and that entry contains the requested * permissions, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_groups->nelts; i++) { acl_entry_t e = ((acl_entry_t *) acl_groups->elts)[i]; if (gid == *((gid_t *) acl_get_qualifier(e))) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ acl_permset_t perms; acl_get_permset(e, &perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(perms, mode) == 1) { # endif acl_copy_entry(ae, e); ae_type = ACL_GROUP; have_access_entry = TRUE; break; } } if (suppl_gids) { register unsigned int j; for (j = 0; !have_access_entry && j < suppl_gids->nelts; j++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[j]; if (suppl_gid == *((gid_t *) acl_get_qualifier(e))) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ acl_permset_t perms; acl_get_permset(e, &perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(perms, mode) == 1) { # endif acl_copy_entry(ae, e); ae_type = ACL_GROUP; have_access_entry = TRUE; break; } } } } } /* 6. If not matched above, and if one of the group IDs matches * the group owner or any of the named group entries, but neither * the group owner entry nor any of the named group entries contains * the requested permissions, access is denied. */ /* 7. If not matched above, the other entry determines access. */ if (!have_access_entry) { acl_copy_entry(ae, acl_other_entry); ae_type = ACL_OTHER; have_access_entry = TRUE; } /* Access determination: * * If either the user owner entry or other entry were used, and the * entry contains the requested permissions, access is permitted. * * Otherwise, if the selected entry and the mask entry both contain * the requested permissions, access is permitted. * * Otherwise, access is denied. */ switch (ae_type) { case ACL_USER_OBJ: case ACL_OTHER: { acl_permset_t perms; acl_get_permset(ae, &perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(perms, mode) == 1) { # endif res = 0; } break; } default: { acl_permset_t ent_perms, mask_perms; acl_get_permset(ae, &ent_perms); acl_get_permset(acl_mask_entry, &mask_perms); # if defined(HAVE_BSD_POSIX_ACL) if (acl_get_perm_np(ent_perms, mode) == 1 && acl_get_perm_np(mask_perms, mode) == 1) { # elif defined(HAVE_LINUX_POSIX_ACL) if (acl_get_perm(ent_perms, mode) == 1 && acl_get_perm(mask_perms, mode) == 1) { # endif res = 0; } break; } } destroy_pool(acl_pool); if (res < 0) errno = EACCES; return res; # elif defined(HAVE_SOLARIS_POSIX_ACL) register unsigned int i; int have_access_entry = FALSE, idx, res = -1; pool *acl_pool; aclent_t *acls = acl; aclent_t ae; int ae_type = 0; aclent_t acl_user_entry; aclent_t acl_group_entry; aclent_t acl_other_entry; aclent_t acl_mask_entry; array_header *acl_groups; array_header *acl_users; /* In the absence of any clear documentation, I'll assume that * Solaris ACLs follow the same selection and checking algorithm * as do BSD and Linux. */ res = aclcheck(acls, nents, &idx); switch (res) { case 0: break; case GRP_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "too many GROUP entries"); errno = EACCES; return -1; case USER_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "too many USER entries"); errno = EACCES; return -1; case OTHER_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "too many OTHER entries"); errno = EACCES; return -1; case CLASS_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "too many CLASS entries"); errno = EACCES; return -1; case DUPLICATE_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "duplicate entries"); errno = EACCES; return -1; case MISS_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "missing required entry"); errno = EACCES; return -1; case MEM_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "Out of memory!"); errno = EACCES; return -1; case ENTRY_ERROR: pr_log_debug(DEBUG3, "FS: ill-formed ACL for '%s': %s", path, "invalid entry type"); errno = EACCES; return -1; } /* Iterate through all of the ACL entries, sorting them for later * checking. */ acl_pool = make_sub_pool(p); acl_groups = make_array(acl_pool, 1, sizeof(aclent_t)); acl_users = make_array(acl_pool, 1, sizeof(aclent_t)); for (i = 0; i < nents; i++) { if (acls[i].a_type & USER_OBJ) { memcpy(&acl_user_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & USER) { aclent_t *ae_dup = push_array(acl_users); memcpy(ae_dup, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & GROUP_OBJ) { memcpy(&acl_group_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & GROUP) { aclent_t *ae_dup = push_array(acl_groups); memcpy(ae_dup, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & OTHER_OBJ) { memcpy(&acl_other_entry, &(acls[i]), sizeof(aclent_t)); } else if (acls[i].a_type & CLASS_OBJ) { memcpy(&acl_mask_entry, &(acls[i]), sizeof(aclent_t)); } } /* Select the ACL entry that determines access. */ res = -1; /* 1. If the given user ID matches the file owner, use that entry for * access. */ if (uid == st->st_uid) { /* Check the acl_user_entry for access. */ memcpy(&ae, &acl_user_entry, sizeof(aclent_t)); ae_type = USER_OBJ; have_access_entry = TRUE; } /* 2. If not matched above, and f the given user ID matches one of the * named user entries, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_users->nelts; i++) { aclent_t e; memcpy(&e, &(((aclent_t *) acl_users->elts)[i]), sizeof(aclent_t)); if (uid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ memcpy(&ae, &e, sizeof(aclent_t)); ae_type = USER; have_access_entry = TRUE; break; } } /* 3. If not matched above, and if one of the group IDs matches the * group owner entry, and the group owner entry contains the * requested permissions, use that entry for access. */ if (!have_access_entry && gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ if (acl_group_entry.a_perm & mode) { memcpy(&ae, &acl_group_entry, sizeof(aclent_t)); ae_type = GROUP_OBJ; have_access_entry = TRUE; } } if (suppl_gids) { for (i = 0; !have_access_entry && i < suppl_gids->nelts; i++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[i]; if (suppl_gid == st->st_gid) { /* Check the acl_group_entry for access. First though, we need to * see if the acl_group_entry contains the requested permissions. */ if (acl_group_entry.a_perm & mode) { memcpy(&ae, &acl_group_entry, sizeof(aclent_t)); ae_type = GROUP_OBJ; have_access_entry = TRUE; break; } } } } /* 5. If not matched above, and if one of the group IDs matches one * of the named group entries, and that entry contains the requested * permissions, use that entry for access. */ for (i = 0; !have_access_entry && i < acl_groups->nelts; i++) { aclent_t e; memcpy(&e, &(((aclent_t *) acl_groups->elts)[i]), sizeof(aclent_t)); if (gid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ if (e.a_perm & mode) { memcpy(&ae, &e, sizeof(aclent_t)); ae_type = GROUP; have_access_entry = TRUE; break; } } if (suppl_gids) { register unsigned int j; for (j = 0; !have_access_entry && j < suppl_gids->nelts; j++) { gid_t suppl_gid = ((gid_t *) suppl_gids->elts)[j]; if (suppl_gid == e.a_id) { /* Check this entry for access. Note that it'll need to * be modified by the mask, if any, later. */ if (e.a_perm & mode) { memcpy(&ae, &e, sizeof(aclent_t)); ae_type = GROUP; have_access_entry = TRUE; break; } } } } } /* 6. If not matched above, and if one of the group IDs matches * the group owner or any of the named group entries, but neither * the group owner entry nor any of the named group entries contains * the requested permissions, access is denied. */ /* 7. If not matched above, the other entry determines access. */ if (!have_access_entry) { memcpy(&ae, &acl_other_entry, sizeof(aclent_t)); ae_type = OTHER_OBJ; have_access_entry = TRUE; } /* Access determination: * * If either the user owner entry or other entry were used, and the * entry contains the requested permissions, access is permitted. * * Otherwise, if the selected entry and the mask entry both contain * the requested permissions, access is permitted. * * Otherwise, access is denied. */ switch (ae_type) { case USER_OBJ: case OTHER_OBJ: if (ae.a_perm & mode) res = 0; break; default: if ((ae.a_perm & mode) && (acl_mask_entry.a_perm & mode)) res = 0; break; } destroy_pool(acl_pool); if (res < 0) errno = EACCES; return res; # endif /* HAVE_SOLARIS_POSIX_ACL */ } /* FSIO handlers */ static int facl_fsio_access(pr_fs_t *fs, const char *path, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { int nents = 0; struct stat st; void *acls; pr_fs_clear_cache(); if (pr_fsio_stat(path, &st) < 0) return -1; /* Look up the acl for this path. */ # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) acls = acl_get_file(path, ACL_TYPE_ACCESS); if (!acls) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s", path, strerror(errno)); return -1; } # elif defined(HAVE_SOLARIS_POSIX_ACL) nents = acl(path, GETACLCNT, 0, NULL); if (nents < 0) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL count for '%s': %s", path, strerror(errno)); return -1; } acls = pcalloc(fs->fs_pool, nents * sizeof(aclent_t)); nents = acl(path, GETACL, nents, acls); if (nents < 0) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s", path, strerror(errno)); return -1; } # endif return check_facl(fs->fs_pool, path, mode, acls, nents, &st, uid, gid, suppl_gids); } static int facl_fsio_faccess(pr_fh_t *fh, int mode, uid_t uid, gid_t gid, array_header *suppl_gids) { int nents = 0; struct stat st; void *acls; pr_fs_clear_cache(); if (pr_fsio_fstat(fh, &st) < 0) return -1; /* Look up the acl for this fd. */ # if defined(HAVE_BSD_POSIX_ACL) || defined(HAVE_LINUX_POSIX_ACL) acls = acl_get_fd(PR_FH_FD(fh)); if (!acls) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s", fh->fh_path, strerror(errno)); return -1; } # elif defined(HAVE_SOLARIS_POSIX_ACL) nents = facl(PR_FH_FD(fh), GETACLCNT, 0, NULL); if (nents < 0) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL count for '%s': %s", fh->fh_path, strerror(errno)); return -1; } acls = pcalloc(fh->fh_fs->fs_pool, nents * sizeof(aclent_t)); nents = facl(PR_FH_FD(fh), GETACL, nents, acls); if (nents < 0) { pr_log_debug(DEBUG10, "FS: unable to retrieve ACL for '%s': %s", fh->fh_path, strerror(errno)); return -1; } # endif return check_facl(fh->fh_fs->fs_pool, fh->fh_path, mode, acls, nents, &st, uid, gid, suppl_gids); } #endif /* HAVE_POSIX_ACL */ /* Initialization routines */ static int facl_init(void) { #if defined(PR_USE_FACL) && defined(HAVE_POSIX_ACL) pr_fs_t *fs = pr_register_fs(permanent_pool, "facl", "/"); if (!fs) { pr_log_pri(PR_LOG_ERR, MOD_FACL_VERSION ": error registering fs: %s", strerror(errno)); return -1; } /* Ensure that our ACL-checking handlers are used. */ fs->access = facl_fsio_access; fs->faccess = facl_fsio_faccess; #endif /* PR_USE_FACL and HAVE_POSIX_ACL */ return 0; } /* Module Tables */ module facl_module = { /* Always NULL */ NULL, NULL, /* Module API version */ 0x20, /* Module name */ "facl", /* Module configuration directive handlers */ NULL, /* Module command handlers */ NULL, /* Module authentication handlers */ NULL, /* Module initialization */ facl_init, /* Session initialization */ NULL, /* Module version */ MOD_FACL_VERSION };
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; }
static int copy_paths(pool *p, const char *from, const char *to) { struct stat st; int res; xaset_t *set; set = get_dir_ctxt(p, (char *) to); res = pr_filter_allow_path(set, to); switch (res) { case 0: break; case PR_FILTER_ERR_FAILS_ALLOW_FILTER: pr_log_debug(DEBUG7, MOD_COPY_VERSION ": path '%s' denied by PathAllowFilter", to); errno = EPERM; return -1; case PR_FILTER_ERR_FAILS_DENY_FILTER: pr_log_debug(DEBUG7, MOD_COPY_VERSION ": path '%s' denied by PathDenyFilter", to); errno = EPERM; return -1; } /* Check whether from is a file, a directory, a symlink, or something * unsupported. */ res = pr_fsio_lstat(from, &st); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error checking '%s': %s", from, strerror(xerrno)); errno = xerrno; return -1; } if (S_ISREG(st.st_mode)) { char *abs_path; pr_fs_clear_cache2(to); res = pr_fsio_stat(to, &st); if (res == 0) { unsigned char *allow_overwrite; allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE); if (allow_overwrite == NULL || *allow_overwrite == FALSE) { pr_log_debug(DEBUG6, MOD_COPY_VERSION ": AllowOverwrite permission denied for '%s'", to); errno = EACCES; return -1; } } res = pr_fs_copy_file(from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying file '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } pr_fs_clear_cache2(to); if (pr_fsio_stat(to, &st) < 0) { pr_trace_msg(trace_channel, 3, "error stat'ing '%s': %s", to, strerror(errno)); } /* Write a TransferLog entry as well. */ abs_path = dir_abs_path(p, to, 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', "_"); } } else if (S_ISDIR(st.st_mode)) { res = create_path(p, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error creating path '%s': %s", to, strerror(xerrno)); errno = xerrno; return -1; } res = copy_dir(p, from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying directory '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } } else if (S_ISLNK(st.st_mode)) { pr_fs_clear_cache2(to); res = pr_fsio_stat(to, &st); if (res == 0) { unsigned char *allow_overwrite; allow_overwrite = get_param_ptr(CURRENT_CONF, "AllowOverwrite", FALSE); if (allow_overwrite == NULL || *allow_overwrite == FALSE) { pr_log_debug(DEBUG6, MOD_COPY_VERSION ": AllowOverwrite permission denied for '%s'", to); errno = EACCES; return -1; } } res = copy_symlink(p, from, to); if (res < 0) { int xerrno = errno; pr_log_debug(DEBUG7, MOD_COPY_VERSION ": error copying symlink '%s' to '%s': %s", from, to, strerror(xerrno)); errno = xerrno; return -1; } } else { pr_log_debug(DEBUG7, MOD_COPY_VERSION ": unsupported file type for '%s'", from); errno = EINVAL; return -1; } 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; }
MODRET site_chmod(cmd_rec *cmd) { mode_t mode = 0; char *dir, *endp, *tmp, *arg = ""; register unsigned int i = 0; #if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP) regex_t *preg; #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++) arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL); #if defined(HAVE_REGEX_H) && defined(HAVE_REGCOMP) preg = (regex_t *) get_param_ptr(CURRENT_CONF, "PathAllowFilter", FALSE); if (preg && regexec(preg, arg, 0, NULL, 0) != 0) { pr_log_debug(DEBUG2, "'%s %s %s' denied by PathAllowFilter", cmd->argv[0], cmd->argv[1], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); return PR_ERROR(cmd); } preg = (regex_t *) get_param_ptr(CURRENT_CONF, "PathDenyFilter", FALSE); if (preg && regexec(preg, arg, 0, NULL, 0) == 0) { pr_log_debug(DEBUG2, "'%s %s %s' denied by PathDenyFilter", cmd->argv[0], cmd->argv[1], arg); pr_response_add_err(R_550, _("%s: Forbidden filename"), cmd->arg); return PR_ERROR(cmd); } #endif dir = dir_realpath(cmd->tmp_pool, arg); if (!dir) { pr_response_add_err(R_550, "%s: %s", arg, strerror(errno)); return PR_ERROR(cmd); } /* If the first character isn't '0', prepend it and attempt conversion. * This will fail if the chmod is a symbolic, but takes care of the * case where an octal number is sent without the leading '0'. */ if (cmd->argv[1][0] != '0') tmp = pstrcat(cmd->tmp_pool, "0", cmd->argv[1], NULL); else tmp = cmd->argv[1]; mode = strtol(tmp,&endp,0); if (endp && *endp) { /* It's not an absolute number, try symbolic */ char *cp = cmd->argv[1]; int mask = 0, mode_op = 0, curmode = 0, curumask = umask(0); int invalid = 0; char *who, *how, *what; struct stat st; umask(curumask); mode = 0; if (pr_fsio_stat(dir, &st) != -1) curmode = st.st_mode; while (TRUE) { who = pstrdup(cmd->tmp_pool, cp); tmp = strpbrk(who, "+-="); if (tmp != NULL) { how = pstrdup(cmd->tmp_pool, tmp); if (*how != '=') mode = curmode; *tmp = '\0'; } else { invalid++; break; } tmp = strpbrk(how, "rwxXstugo"); if (tmp != NULL) { what = pstrdup(cmd->tmp_pool, tmp); *tmp = '\0'; } else { invalid++; break; } cp = what; while (cp) { switch (*who) { case 'u': mask = 0077; break; case 'g': mask = 0707; break; case 'o': mask = 0770; break; case 'a': mask = 0000; break; case '\0': mask = curumask; break; default: invalid++; break; } if (invalid) break; switch (*how) { case '+': case '-': case '=': break; default: invalid++; } if (invalid) break; switch (*cp) { case 'r': mode_op |= (S_IRUSR|S_IRGRP|S_IROTH); break; case 'w': mode_op |= (S_IWUSR|S_IWGRP|S_IWOTH); break; case 'x': mode_op |= (S_IXUSR|S_IXGRP|S_IXOTH); break; /* 'X' not implemented */ case 's': /* setuid */ mode_op |= S_ISUID; break; case 't': /* sticky */ mode_op |= S_ISVTX; break; case 'o': mode_op |= curmode & S_IRWXO; mode_op |= (curmode & S_IRWXO) << 3; mode_op |= (curmode & S_IRWXO) << 6; break; case 'g': mode_op |= (curmode & S_IRWXG) >> 3; mode_op |= curmode & S_IRWXG; mode_op |= (curmode & S_IRWXG) << 3; break; case 'u': mode_op |= (curmode & S_IRWXO) >> 6; mode_op |= (curmode & S_IRWXO) >> 3; mode_op |= curmode & S_IRWXU; break; case '\0': /* Apply the mode and move on */ switch (*how) { case '+': case '=': mode |= (mode_op & ~mask); break; case '-': mode &= ~(mode_op & ~mask); break; } mode_op = 0; if (*who && *(who+1)) { who++; cp = what; continue; } else cp = NULL; break; default: invalid++; } if (invalid) break; if (cp) cp++; } break; } if (invalid) { pr_response_add_err(R_550, _("'%s': invalid mode"), cmd->argv[1]); return PR_ERROR(cmd); } }