pax_global_header00006660000000000000000000000064152321721510014511gustar00rootroot0000000000000052 comment=b41f0929f3a736c0e1aee3ca392d808ecbfd8136 proftpd-mod_procfs-0.3/000077500000000000000000000000001523217215100151625ustar00rootroot00000000000000proftpd-mod_procfs-0.3/.codeql.yml000066400000000000000000000000441523217215100172300ustar00rootroot00000000000000--- paths: - contrib/mod_procfs.c proftpd-mod_procfs-0.3/.gitattributes000066400000000000000000000000621523217215100200530ustar00rootroot00000000000000*.pl linguist-language=C *.pm linguist-language=C proftpd-mod_procfs-0.3/.github/000077500000000000000000000000001523217215100165225ustar00rootroot00000000000000proftpd-mod_procfs-0.3/.github/workflows/000077500000000000000000000000001523217215100205575ustar00rootroot00000000000000proftpd-mod_procfs-0.3/.github/workflows/ci.yml000066400000000000000000000070171523217215100217020ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: branches: - main schedule: - cron: '12 2 * * 0' jobs: build: runs-on: ubuntu-latest env: # Let's try opting into use of Nodejs v24; see: # https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ # GitHub runners still causing problems with NodeJS v20; see: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true strategy: matrix: compiler: - clang - gcc container: - almalinux:8 - alpine:3.18 - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v6 with: repository: proftpd/proftpd path: proftpd - name: Checkout module source code uses: actions/checkout@v6 with: path: proftpd-mod_procfs - name: Whitespace check if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq apt-get install -y git cd proftpd-mod_procfs if [[ -n $(git diff --check HEAD^) ]]; then echo "You must remove whitespace before submitting a pull request" echo "" git diff --check HEAD^ exit 1 fi - name: Prepare module source code run: | cp proftpd-mod_procfs/mod_procfs.c proftpd/contrib/ - name: Install Alpine packages if: ${{ matrix.container == 'alpine:3.18' }} run: | apk update # for builds apk add bash build-base clang compiler-rt gcc make zlib-dev # for debugging clang --version gcc --version - name: Install RPM packages if: ${{ matrix.container == 'almalinux:8' }} run: | # Need to add other repos for e.g. libsodium yum install -y dnf-plugins-core epel-release yum-utils clang gcc make zlib-devel dnf config-manager --enable epel dnf config-manager --set-enabled powertools # for debugging clang --version gcc --version - name: Install Ubuntu packages if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq # for builds apt-get install -y clang gcc make # for test code coverage apt-get install -y lcov ruby gem install coveralls-lcov # for HTML validation apt-get install -y tidy # for debugging clang --version gcc --version - name: Prepare code coverage if: ${{ matrix.container == 'ubuntu:22.04' }} run: | lcov --directory proftpd --zerocounters - name: Build as static module env: CC: ${{ matrix.compiler }} run: | cd proftpd ./configure --with-modules=mod_procfs make - name: Install as static module run: | cd proftpd make install - name: Build as shared module env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure --enable-dso --with-shared=mod_procfs make - name: Install as shared module run: | cd proftpd make install - name: Check HTML docs run: | cd proftpd-mod_procfs echo "Processing mod_procfs.html" tidy -errors -omit -q mod_procfs.html | exit 0 proftpd-mod_procfs-0.3/.github/workflows/codeql.yml000066400000000000000000000036201523217215100225520ustar00rootroot00000000000000name: CodeQL on: push: branches: - main paths-ignore: - '**/*.md' - '**/doc/*' pull_request: branches: - main paths-ignore: - '**/*.md' - '**/doc/*' schedule: - cron: "55 8 * * 1" jobs: analyze: name: CodeQL Analysis runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write env: # Let's try opting into use of Nodejs v24; see: # https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ # GitHub runners still causing problems with NodeJS v20; see: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true strategy: fail-fast: true matrix: language: - cpp steps: - name: Checkout ProFTPD uses: actions/checkout@v6 with: repository: proftpd/proftpd - name: Checkout mod_procfs uses: actions/checkout@v6 with: path: proftpd-mod_procfs - name: Install Packages run: | sudo apt-get update # for builds sudo apt-get install -y clang gcc make - name: Prepare module run: | cp proftpd-mod_procfs/mod_procfs.c contrib/mod_procfs.c - name: Configure run: | ./configure --with-modules=mod_procfs - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} config-file: proftpd-mod_procfs/.codeql.yml queries: +security-and-quality source-root: proftpd-mod_procfs - name: Build run: | make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 with: category: "/language:${{ matrix.language }}" checkout_path: proftpd-mod_procfs output: sarif-results upload: true proftpd-mod_procfs-0.3/.github/workflows/regressions.yml000066400000000000000000000054231523217215100236510ustar00rootroot00000000000000name: Regression Tests on: push: branches: - main paths-ignore: - '*.html' - '*.md' pull_request: branches: - main workflow_dispatch: jobs: build: runs-on: ubuntu-latest env: # Let's try opting into use of Nodejs v24; see: # https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ # GitHub runners still causing problems with NodeJS v20; see: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true DEBIAN_FRONTEND: noninteractive TZ: America/Los_Angeles strategy: matrix: compiler: - gcc container: - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v6 with: repository: proftpd/proftpd path: proftpd - name: Checkout module source code uses: actions/checkout@v6 with: path: mod_procfs - name: Install Ubuntu packages run: | apt-get update -qq # for builds apt-get install -y gcc git make tzdata # for OpenSSL support apt-get install -y libssl-dev # for Sodium support apt-get install -y --force-yes libsodium-dev # for integration/regression tests apt-get install -y \ libarchive-zip-perl \ libauthen-oath-perl \ libcompress-raw-zlib-perl \ libdata-dumper-simple-perl \ libdatetime-perl \ libfile-copy-recursive-perl \ libfile-path-tiny-perl \ libfile-spec-native-perl \ libmime-base32-perl \ libnet-address-ip-local-perl \ libnet-inet6glue-perl \ libnet-ssh2-perl \ libnet-ssleay-perl \ libnet-telnet-perl \ libposix-2008-perl \ libtest-unit-perl \ libtime-hr-perl \ libwww-perl PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Net::FTPSSL' # for debugging gcc --version - name: Prepare module source code run: | cp mod_procfs/mod_procfs.c proftpd/contrib/mod_procfs.c - name: Install with static modules # NOTE: Docker does not have good IPv6 support, hence we disable it. run: | cd proftpd ./configure --disable-ipv6 --with-modules=mod_site_misc:mod_copy:mod_digest:mod_sftp:mod_procfs make ./proftpd -V make install - name: Run integration tests env: PROFTPD_TEST_BIN: /usr/local/sbin/proftpd PROFTPD_TEST_CI: github PROFTPD_TEST_DIR: ${{ github.workspace }}/proftpd/tests run: | cd mod_procfs perl tests.pl proftpd-mod_procfs-0.3/.gitignore000066400000000000000000000002331523217215100171500ustar00rootroot00000000000000configure Makefile config.log config.status autom4te.cache mod_procfs.h tests.log t/api-tests t/api-tests.log .libs *.a *.sw? *.la *.lo *Tests*.log *.o *~ proftpd-mod_procfs-0.3/README.md000066400000000000000000000015671523217215100164520ustar00rootroot00000000000000proftpd-mod_procfs =================== Status ------ [![GitHub Actions CI Status](https://github.com/Castaglia/proftpd-mod_procfs/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Castaglia/proftpd-mod_procfs/actions/workflows/ci.yml) [![CodeQL Analysis](https://github.com/Castaglia/proftpd-mod_procfs/actions/workflows/codeql.yml/badge.svg)](https://github.com/Castaglia/proftpd-mod_procfs/actions/workflows/codeql.yml) [![License](https://img.shields.io/badge/license-GPL-brightgreen.svg)](https://img.shields.io/badge/license-GPL-brightgreen.svg) Synopsis -------- The `mod_procfs` module for ProFTPD affects the visibility and use of the `/proc` filesystem directory that is commonly available. See the [mod_procfs.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_procfs/blob/main/mod_procfs.html) documentation for more details. proftpd-mod_procfs-0.3/mod_procfs.c000066400000000000000000000467301523217215100174730ustar00rootroot00000000000000/* * ProFTPD: mod_procfs -- a module for hiding the /proc filesystem * Copyright (c) 2026 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * As a special exemption, TJ Saunders and other respective copyright holders * give permission to link this program with OpenSSL, and distribute the * resulting executable, without including the source code for OpenSSL in the * source distribution. * * This is mod_procfs contrib software for proftpd 1.3.x and above. * For more information contact TJ Saunders . * * -----DO NOT CHANGE THE LINES BELOW----- */ #include "conf.h" #include "privs.h" #if !defined(HAVE_MNTENT_H) /* Older ProFTPD versions did not check for the header, so we * will use heuristics to guess whether it is present. Some platforms, * such as Mac OSX, do not have this header. * * If/when I get access to BSD platforms, I can add heuristics to handle them * appropriately in the future. */ # if defined(__GLIBC__) # define HAVE_MNTENT_H 1 # elif defined(LINUX) # define HAVE_MNTENT_H 1 # endif /* LINUX */ #endif /* HAVE_MNTENT_H */ #if defined(HAVE_MNTENT_H) # include #endif /* HAVE_MNTENT_H */ #if PROFTPD_VERSION_NUMBER < 0x0001030602 # error "ProFTPD 1.3.6rc2 or later required" #endif #define MOD_PROCFS_VERSION "mod_procfs/0.3" module procfs_module; static int procfs_engine = FALSE; static int procfs_logfd = -1; static pool *procfs_pool = NULL; static const char *trace_channel = "procfs"; struct procfs_mount { const char *path; size_t path_len; const char *type; }; static array_header *procfs_mounts = NULL; static int get_procfs_mounts(pool *p) { #if defined(HAVE_MNTENT_H) FILE *mountf = NULL; struct mntent *mnt = NULL; mountf = setmntent("/etc/mtab", "r"); if (mountf == NULL) { int xerrno = errno; pr_trace_msg(trace_channel, 1, "unable to read /etc/mtab: %s", strerror(xerrno)); errno = xerrno; return -1; } mnt = getmntent(mountf); while (mnt != NULL) { struct procfs_mount *mount = NULL; size_t path_len = 0; pr_signals_handle(); if (strcmp(mnt->mnt_type, "proc") == 0) { pr_log_debug(DEBUG0, MOD_PROCFS_VERSION ": discovered procfs mounted at '%s'", mnt->mnt_dir); mount = palloc(p, sizeof(struct procfs_mount)); mount->type = pstrdup(p, "procfs"); } else if (strcmp(mnt->mnt_type, "sysfs") == 0) { pr_log_debug(DEBUG0, MOD_PROCFS_VERSION ": discovered sysfs mounted at '%s'", mnt->mnt_dir); mount = palloc(p, sizeof(struct procfs_mount)); mount->type = pstrdup(p, "sysfs"); } if (mount != NULL) { /* If the mount point path does not end with a trailing slash, add it. * We use this property when checking paths that reference this mount * point. */ path_len = strlen(mnt->mnt_dir); if (mnt->mnt_dir[path_len-1] != '/') { mount->path = pstrcat(p, mnt->mnt_dir, "/", NULL); mount->path_len = path_len + 1; } else { mount->path = pstrdup(p, mnt->mnt_dir); mount->path_len = path_len; } if (procfs_mounts == NULL) { procfs_mounts = make_array(p, 0, sizeof(struct procfs_mount *)); } *((struct procfs_mount **) push_array(procfs_mounts)) = mount; } mnt = getmntent(mountf); } if (endmntent(mountf) != 1) { pr_trace_msg(trace_channel, 1, "error closing /etc/mtab: %s", strerror(errno)); } #else struct stat st; /* Check for the presence of the /proc filesystem on this host. If it * is not present, then we need do nothing else. */ if (lstat("/proc/", &st) == 0) { pr_log_debug(DEBUG10, MOD_PROCFS_VERSION ": found /proc/ filesystem"); if (S_ISDIR(st.st_mode)) { struct procfs_mount *mount; mount = palloc(p, sizeof(struct procfs_mount)); mount->path = pstrdup(p, "/proc/"); mount->path_len = 6; procfs_mounts = make_array(p, 0, sizeof(struct procfs_mount *)); *((struct procfs_mount **) push_array(procfs_mounts)) = mount; } else { pr_log_debug(DEBUG10, MOD_PROCFS_VERSION ": /proc/ is not a directory"); } } #endif /* HAVE_MNTENT_H */ if (procfs_mounts != NULL) { /* Automatically enable ProcfsEngine on in such cases. */ procfs_engine = TRUE; } else { pr_log_debug(DEBUG5, MOD_PROCFS_VERSION ": did not find /proc filesystem: %s", strerror(errno)); } return 0; } static int is_blocked_path(pool *p, const char *path) { register unsigned int i; int res = FALSE; char *abs_path; size_t abs_pathlen; struct procfs_mount **mounts; abs_path = dir_abs_path(p, path, FALSE); abs_pathlen = strlen(abs_path); mounts = procfs_mounts->elts; for (i = 0; i < procfs_mounts->nelts; i++) { struct procfs_mount *mount; pr_signals_handle(); mount = mounts[i]; pr_trace_msg(trace_channel, 19, "checking path '%s' against %s mount '%s'", abs_path, mount->type, mount->path); if (abs_pathlen >= mount->path_len && strncmp(abs_path, mount->path, mount->path_len) == 0) { res = TRUE; } else if (abs_pathlen == (mount->path_len - 1) && strncmp(abs_path, mount->path, mount->path_len - 1) == 0) { res = TRUE; } if (res == TRUE) { break; } } return res; } /* Configuration handlers */ /* usage: ProcfsEngine on|off */ MODRET set_procfsengine(cmd_rec *cmd) { int engine = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); engine = get_boolean(cmd, 1); if (engine == -1) { CONF_ERROR(cmd, "expected Boolean parameter"); } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = palloc(c->pool, sizeof(int)); *((int *) c->argv[0]) = engine; return PR_HANDLED(cmd); } /* usage: ProcfsLog path|"none" */ MODRET set_procfslog(cmd_rec *cmd) { CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); if (pr_fs_valid_path(cmd->argv[1]) < 0) { CONF_ERROR(cmd, "must be an absolute path"); } (void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } static const char *get_cmd_resp_code(cmd_rec *cmd) { const char *resp_code = R_550; if (pr_cmd_cmp(cmd, PR_CMD_LIST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_NLST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STAT_ID) == 0) { resp_code = R_450; } return resp_code; } static modret_t *handle_path(cmd_rec *cmd, const char *cmd_name, const char *path) { pr_trace_msg(trace_channel, 19, "checking path '%s' for %s", path, cmd_name); if (is_blocked_path(cmd->tmp_pool, path) == TRUE) { const char *proto, *resp_code; proto = pr_session_get_protocol(0); (void) pr_log_writefile(procfs_logfd, MOD_PROCFS_VERSION, "%s %s denied by mod_procfs for user '%s', client IP %s, protocol %s", cmd_name, path, session.user, pr_netaddr_get_ipstr(session.c->remote_addr), proto); pr_log_pri(PR_LOG_NOTICE, "%s %s denied by mod_procfs", cmd_name, path); /* The response code to use depends on the command. */ resp_code = get_cmd_resp_code(cmd); pr_response_add_err(resp_code, _("%s: %s"), path, strerror(ENOENT)); pr_cmd_set_errno(cmd, ENOENT); errno = ENOENT; return PR_ERROR(cmd); } return PR_DECLINED(cmd); } /* Command handlers */ MODRET procfs_pre_mfmt(cmd_rec *cmd) { const char *path, *ptr; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 3) { return PR_DECLINED(cmd); } /* The path can contain spaces. Thus we need to use cmd->arg, not cmd->argv, * to find the path. But cmd->arg contains the facts as well. Thus we * find the FIRST space in cmd->arg; the path is everything past that space. */ ptr = strchr(cmd->arg, ' '); if (ptr == NULL) { return PR_DECLINED(cmd); } path = ptr + 1; return handle_path(cmd, cmd->argv[0], path); } MODRET procfs_pre_path(cmd_rec *cmd) { const char *path; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 2) { return PR_DECLINED(cmd); } /* TODO: Add similar decoding, handling of spaces as done by mod_core. */ path = cmd->arg; return handle_path(cmd, cmd->argv[0], path); } MODRET procfs_pre_site(cmd_rec *cmd) { char *cmd_name, *path; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } if (cmd->argc < 3) { return PR_DECLINED(cmd); } /* These are the SITE commands we know about: * * SITE CHGRP * SITE CHMOD * * SITE MKDIR (mod_site_misc) * SITE RMDIR (mod_site_misc) * SITE SYMLINK (mod_site_misc) * SITE UTIME (mod_site_misc) * * SITE CPFR (mod_copy) * SITE CPTO (mod_copy) * SITE COPY (mod_copy) */ if (strcmp(cmd->argv[1], "CHGRP") == 0 || strcmp(cmd->argv[1], "CHMOD") == 0 || strcmp(cmd->argv[1], "UTIME") == 0) { register unsigned int i; char *arg = ""; if (cmd->argc < 4) { return PR_DECLINED(cmd); } /* Construct the path by concatenating all of the parameter after the * operational data, separating them with spaces. */ for (i = 3; i < cmd->argc; i++) { arg = pstrcat(cmd->tmp_pool, arg, *arg ? " " : "", cmd->argv[i], NULL); } cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL); path = arg; return handle_path(cmd, cmd_name, path); } else if (strcmp(cmd->argv[1], "CPFR") == 0 || strcmp(cmd->argv[1], "CPTO") == 0 || strcmp(cmd->argv[1], "MKDIR") == 0 || strcmp(cmd->argv[1], "RMDIR") == 0) { cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL); path = cmd->argv[2]; return handle_path(cmd, cmd_name, path); } else if (strcmp(cmd->argv[1], "COPY") == 0 || strcmp(cmd->argv[1], "SYMLINK") == 0) { char *from, *to; modret_t *mr; if (cmd->argc < 4) { return PR_DECLINED(cmd); } cmd_name = pstrcat(cmd->tmp_pool, cmd->argv[0], " ", cmd->argv[1], NULL); from = cmd->argv[2]; to = cmd->argv[3]; mr = handle_path(cmd, cmd_name, from); if (MODRET_ISERROR(mr)) { return mr; } return handle_path(cmd, cmd_name, to); } else { pr_trace_msg(trace_channel, 7, "unknown/unsupported SITE '%s' command, ignoring", (char *) cmd->argv[1]); } return PR_DECLINED(cmd); } MODRET procfs_sftp_pre_path(cmd_rec *cmd) { const char *path, *proto; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); if (strcmp(proto, "sftp") != 0) { return PR_DECLINED(cmd); } path = cmd->argv[1]; return handle_path(cmd, cmd->argv[0], path); } MODRET procfs_sftp_pre_hardlink(cmd_rec *cmd) { const char *src_path, *dst_path, *proto; char *ptr; modret_t *mr; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); if (strcmp(proto, "sftp") != 0) { return PR_DECLINED(cmd); } /* Unfortunately, mod_sftp currently does NOT break the two paths into * the cmd->argv array; it only populates cmd->arg. * * In the future, if/when mod_sftp behavior changes, we can look at the * cmd->argc to determine which style is being used by mod_sftp. */ ptr = strchr(cmd->arg, ' '); if (ptr == NULL) { return PR_DECLINED(cmd); } src_path = pstrndup(cmd->tmp_pool, cmd->arg, ptr - cmd->arg); dst_path = pstrdup(cmd->tmp_pool, ptr + 1); mr = handle_path(cmd, cmd->argv[0], src_path); if (MODRET_ISERROR(mr)) { return mr; } return handle_path(cmd, cmd->argv[0], dst_path); } MODRET procfs_sftp_pre_symlink(cmd_rec *cmd) { const char *src_path, *dst_path, *proto; char *ptr; modret_t *mr; if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); if (strcmp(proto, "sftp") != 0) { return PR_DECLINED(cmd); } /* Unfortunately, mod_sftp currently does NOT break the two paths into * the cmd->argv array; it only populates cmd->arg. * * In the future, if/when mod_sftp behavior changes, we can look at the * cmd->argc to determine which style is being used by mod_sftp. */ ptr = strchr(cmd->arg, '\t'); if (ptr == NULL) { return PR_DECLINED(cmd); } src_path = pstrndup(cmd->tmp_pool, cmd->arg, ptr - cmd->arg); dst_path = pstrdup(cmd->tmp_pool, ptr + 1); mr = handle_path(cmd, cmd->argv[0], src_path); if (MODRET_ISERROR(mr)) { return mr; } return handle_path(cmd, cmd->argv[0], dst_path); } MODRET procfs_post_pass(cmd_rec *cmd) { config_rec *c; if (procfs_mounts == NULL) { procfs_engine = FALSE; return PR_DECLINED(cmd); } c = find_config(main_server->conf, CONF_PARAM, "ProcfsEngine", FALSE); if (c != NULL) { procfs_engine = *((int *) c->argv[0]); } if (procfs_engine == FALSE) { return PR_DECLINED(cmd); } /* Check whether we are chrooted. If so, then we need not do anything. */ if (session.chroot_path != NULL) { if (strcmp(session.chroot_path, "/") != 0) { pr_trace_msg(trace_channel, 3, "session is chrooted to '%s', disabling mod_procfs", session.chroot_path); procfs_engine = FALSE; } } return PR_DECLINED(cmd); } /* Event Listeners */ #if defined(PR_SHARED_MODULE) static void procfs_mod_unload_ev(const void *event_data, void *user_data) { if (strcmp("mod_procfs.c", (const char *) event_data) != 0) { return; } pr_event_unregister(&procfs_module, NULL, NULL); (void) close(procfs_logfd); procfs_logfd = -1; if (procfs_pool != NULL) { destroy_pool(procfs_pool); procfs_pool = NULL; procfs_mounts = NULL; } procfs_engine = FALSE; } #endif /* PR_SHARED_MODULE */ static void procfs_restart_ev(const void *event_data, void *user_data) { (void) close(procfs_logfd); procfs_logfd = -1; if (procfs_pool != NULL) { destroy_pool(procfs_pool); } procfs_pool = make_sub_pool(permanent_pool); pr_pool_tag(procfs_pool, MOD_PROCFS_VERSION); } static void procfs_shutdown_ev(const void *event_data, void *user_data) { (void) close(procfs_logfd); procfs_logfd = -1; if (procfs_pool != NULL) { destroy_pool(procfs_pool); procfs_pool = NULL; procfs_mounts = NULL; } } /* Initialization functions */ static int procfs_init(void) { if (procfs_pool != NULL) { destroy_pool(procfs_pool); } procfs_pool = make_sub_pool(permanent_pool); pr_pool_tag(procfs_pool, MOD_PROCFS_VERSION); #if defined(PR_SHARED_MODULE) pr_event_register(&procfs_module, "core.module-unload", procfs_mod_unload_ev, NULL); #endif /* PR_SHARED_MODULE */ pr_event_register(&procfs_module, "core.restart", procfs_restart_ev, NULL); pr_event_register(&procfs_module, "core.shutdown", procfs_shutdown_ev, NULL); if (get_procfs_mounts(procfs_pool) < 0) { pr_trace_msg(trace_channel, 1, "unable to discover procfs mounts: %s", strerror(errno)); } return 0; } static int procfs_sess_init(void) { config_rec *c; const char *path; int res, xerrno; c = find_config(main_server->conf, CONF_PARAM, "ProcfsLog", FALSE); if (c == NULL) { return 0; } path = c->argv[0]; if (strcasecmp(path, "none") == 0) { return 0; } PRIVS_ROOT res = pr_log_openfile(path, &procfs_logfd, 0660); xerrno = errno; PRIVS_RELINQUISH switch (res) { case 0: break; case -1: pr_log_debug(DEBUG1, MOD_PROCFS_VERSION ": unable to open ProcfsLog '%s': %s", path, strerror(xerrno)); break; case PR_LOG_SYMLINK: pr_log_debug(DEBUG1, MOD_PROCFS_VERSION ": unable to open ProcfsLog '%s': %s", path, "is a symlink"); break; case PR_LOG_WRITABLE_DIR: pr_log_debug(DEBUG1, MOD_PROCFS_VERSION ": unable to open ProcfsLog '%s': %s", path, "parent directory is world-writable"); break; } return 0; } /* Module API tables */ static conftable procfs_conftab[] = { { "ProcfsEngine", set_procfsengine, NULL }, { "ProcfsLog", set_procfslog, NULL }, { NULL } }; static cmdtable procfs_cmdtab[] = { /* FTP */ { PRE_CMD, C_APPE, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_CWD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_XCWD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_DELE, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_LIST, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_MDTM, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_MFF, G_NONE, procfs_pre_mfmt, FALSE, FALSE }, { PRE_CMD, C_MFMT, G_NONE, procfs_pre_mfmt, FALSE, FALSE }, { PRE_CMD, C_MKD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_XMKD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_MLSD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_MLST, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_NLST, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_RETR, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_RMD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_XRMD, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_RNFR, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_RNTO, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_SITE, G_NONE, procfs_pre_site, FALSE, FALSE }, { PRE_CMD, C_SIZE, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_STAT, G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, C_STOR, G_NONE, procfs_pre_path, FALSE, FALSE }, /* mod_digest FTP commands */ { PRE_CMD, "HASH", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "MD5", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XCRC", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XMD5", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XSHA", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XSHA1", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XSHA256", G_NONE, procfs_pre_path, FALSE, FALSE }, { PRE_CMD, "XSHA512", G_NONE, procfs_pre_path, FALSE, FALSE }, /* SFTP */ { PRE_CMD, "HARDLINK", G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE }, { PRE_CMD, "LINK", G_NONE, procfs_sftp_pre_hardlink, FALSE, FALSE }, { PRE_CMD, "LSTAT", G_NONE, procfs_sftp_pre_path, FALSE, FALSE }, { PRE_CMD, "OPENDIR", G_NONE, procfs_sftp_pre_path, FALSE, FALSE }, { PRE_CMD, "READLINK", G_NONE, procfs_sftp_pre_path, FALSE, FALSE }, { PRE_CMD, "REALPATH", G_NONE, procfs_sftp_pre_path, FALSE, FALSE }, { PRE_CMD, "SETSTAT", G_NONE, procfs_sftp_pre_path, FALSE, FALSE }, { PRE_CMD, "SYMLINK", G_NONE, procfs_sftp_pre_symlink, FALSE, FALSE }, { POST_CMD, C_PASS, G_NONE, procfs_post_pass, FALSE, FALSE }, { 0, NULL } }; module procfs_module = { NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "procfs", /* Module configuration handler table */ procfs_conftab, /* Module command handler table */ procfs_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization function */ procfs_init, /* Session initialization function */ procfs_sess_init, /* Module version */ MOD_PROCFS_VERSION }; proftpd-mod_procfs-0.3/mod_procfs.html000066400000000000000000000124471523217215100202130ustar00rootroot00000000000000 ProFTPD module mod_procfs

ProFTPD module mod_procfs



The mod_procfs module affects the visibility and usage of the /proc and /sys pseudo-filesystem directories.

This module is contained in the mod_procfs file for ProFTPD 1.3.x, and is not compiled by default. Installation instructions are discussed here. More examples of mod_procfs usage can be found here.

The most current version of mod_procfs can be found at:

  https://github.com/Castaglia/proftpd-mod_procfs

Author

Please contact TJ Saunders <tj at castaglia.org> with any questions, concerns, or suggestions regarding this module.

Directives


ProcfsEngine

Syntax: ProcfsEngine on|off
Default: on
Context: "server config", <VirtualHost>, <Global>
Module: mod_procfs
Compatibility: 1.3.1rc1 and later

The ProcfsEngine directive enables or disables the module's hiding of the /proc and /sys filesystems. When enabled, mod_procfs works to prevent access to the /proc and /sys pseudo-filesystems, returning "No such file or directory" errors for such access attempts.

Note that ProcfsEngine is automatically enabled if the module detects, on startup, that the /proc or /sys filesystems are present. If the /proc and /sys filesystems are not present on startup, then ProcfsEngine is automatically disabled. Explicitly setting this directive overrides the automatic behavior.


ProcfsLog

Syntax: ProcfsLog file
Default: None
Context: server config, <VirtualHost>, <Global>
Module: mod_procfs
Compatibility: 1.3.1rc1 and later

The ProcfsLog directive is used to a specify a log file for mod_procfs reporting and debugging, and can be done a per-server basis. The file parameter must be the full path to the file to use for logging. Note that this path must not be to a world-writeable directory and, unless AllowLogSymlinks is explicitly set to on (generally a bad idea), the path must not be a symbolic link.

If file is "none", no logging will be done at all; this setting can be used to override a ProcfsLog setting inherited from a <Global> context.


Installation

To install mod_procfs, copy the mod_procfs.c file into:
  proftpd-dir/contrib/
after unpacking the latest proftpd-1.3.x source code. For including mod_procfs as a statically linked module:
  ./configure --with-modules=mod_procfs
To build mod_procfs as a DSO module:
  ./configure --enable-dso --with-shared=mod_procfs
Then follow the usual steps:
  make
  make install

For those with an existing ProFTPD installation, you can use the prxs tool to add mod_procfs, as a DSO module, to your existing server:

  # prxs -c -i -d mod_procfs.c


Usage

The mod_procfs module works by using the ProFTPD command dispatch system to intercept all commands/requests that use paths; if the paths being used reference the /proc or /sys filesystems, those commands will fail with a "No such file or directory" error.

Example configuration:

  <IfModule mod_procfs.c>
    # This is not required; the module automatically determines when it is
    # needed.
    ProcfsEngine on
    ProcfsLog /var/log/proftpd/procfs.log
  </IfModule>

Logging
The mod_procfs module supports different forms of logging. The main module logging is done via the ProcfsLog directive. For debugging purposes, the module also uses trace logging, via the module-specific channels:

  • procfs

Thus for trace logging, to aid in debugging, you would use the following in your proftpd.conf:

  TraceLog /path/to/proftpd-trace.log
  Trace command:10 response:10 procfs:20
This trace logging can generate large files; it is intended for debugging use only, and should be removed from any production configuration.


© Copyright 2026 TJ Saunders
All Rights Reserved

proftpd-mod_procfs-0.3/t/000077500000000000000000000000001523217215100154255ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/000077500000000000000000000000001523217215100161735ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/ProFTPD/000077500000000000000000000000001523217215100174115ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/000077500000000000000000000000001523217215100205135ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/Modules/000077500000000000000000000000001523217215100221235ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/Modules/mod_procfs.pm000066400000000000000000002414771523217215100246330ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_procfs; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Cwd; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { procfs_engine_off => { order => ++$order, test_class => [qw(bug forking)], }, procfs_chrooted_nonrootfs => { order => ++$order, test_class => [qw(bug forking rootprivs)], }, procfs_chrooted_rootfs => { order => ++$order, test_class => [qw(bug forking rootprivs)], }, procfs_rename_issue2170 => { order => ++$order, test_class => [qw(bug forking)], }, procfs_appe_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_cwd_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_dele_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_list_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_mdtm_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_mkd_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_mlsd_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_mlst_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_nlst_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_retr_procfs_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_retr_sysfs_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_rmd_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_rnto_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_size_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_stat_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_stor_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_site_chgrp_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_site_chmod_rejected => { order => ++$order, test_class => [qw(forking)], }, procfs_site_mkdir_rejected => { order => ++$order, test_class => [qw(forking mod_site_misc)], }, procfs_site_rmdir_rejected => { order => ++$order, test_class => [qw(forking mod_site_misc)], }, procfs_site_symlink_rejected => { order => ++$order, test_class => [qw(forking mod_site_misc)], }, procfs_site_utime_rejected => { order => ++$order, test_class => [qw(forking mod_site_misc)], }, procfs_site_cpfr_rejected => { order => ++$order, test_class => [qw(forking mod_copy)], }, procfs_site_cpto_rejected => { order => ++$order, test_class => [qw(forking mod_copy)], }, procfs_site_copy_rejected => { order => ++$order, test_class => [qw(forking mod_copy)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub procfs_engine_off { my $self = shift; unless (-d '/proc/') { print STDERR "# /proc/ filesystem does not exist, skipping\n"; return; } my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'off', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->list('/proc'); sleep(0.25); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->list('/proc/'); sleep(0.25); $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_chrooted_nonrootfs { my $self = shift; unless (-d '/proc/') { print STDERR "# /proc/ filesystem does not exist, skipping\n"; return; } my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultRoot => '~', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->list('/proc') }; unless ($@) { die("LIST /proc succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->list('/proc/') }; unless ($@) { die("LIST succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_chrooted_rootfs { my $self = shift; unless (-d '/proc/') { print STDERR "# /proc/ filesystem does not exist, skipping\n"; return; } my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultRoot => '/', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->list('/proc') }; unless ($@) { die("LIST /proc succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->list('/proc/') }; unless ($@) { die("LIST succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_rename_issue2170 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/private"); mkpath($test_dir); my $test_file = File::Spec->rel2abs("$test_dir/test.txt"); if (open(my $fh, "> $test_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $test_file: $!"); } # Make sure that, if we're running as root, that the test file has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $test_dir)) { die("Can't set perms on $test_dir: $!"); } unless (chown($setup->{uid}, $setup->{gid}, $test_dir, $test_file)) { die("Can't set owner of $test_dir to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); if (open(my $fh, ">> $setup->{config_file}")) { print $fh < DenyAll EOC unless (close($fh)) { die("Can't write $setup->{config_file}: $!"); } } else { die("Can't open $setup->{config_file}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->rnfr("/proc/self/root/$test_file") }; unless ($@) { die("RNFR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/self/root/$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_appe_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->appe_raw('/proc/test.txt'); if ($conn) { die("APPE succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_cwd_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->cwd('/proc/') }; unless ($@) { die("CWD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->cwd('/proc') }; unless ($@) { die("CWD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->xcwd('/proc/') }; unless ($@) { die("XCWD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->xcwd('/proc') }; unless ($@) { die("XCWD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_dele_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->dele('/proc/test.txt') }; unless ($@) { die("DELE succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_list_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->list('/proc') }; unless ($@) { die("LIST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->list('/proc/') }; unless ($@) { die("LIST succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mdtm_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mdtm('/proc/test.txt') }; unless ($@) { die("MDTM succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mff_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mff('modify=20020717210715;', '/proc/test.txt') }; unless ($@) { die("MFF succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mfmt_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mfmt('20020717210715', '/proc/test.txt') }; unless ($@) { die("MFMT succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mkd_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mkd('/proc/test.d') }; unless ($@) { die("MKD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.d: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->xmkd('/proc/test.d') }; unless ($@) { die("XMKD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.d: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mlsd_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mlsd('/proc') }; unless ($@) { die("MLSD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->mlsd('/proc/') }; unless ($@) { die("MLSD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_mlst_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->mlst('/proc') }; unless ($@) { die("MLST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '(No such file or directory|cannot be listed)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->mlst('/proc/') }; unless ($@) { die("MLST succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '(No such file or directory|cannot be listed)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_nlst_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->nlst('/proc') }; unless ($@) { die("NLST succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->nlst('/proc/') }; unless ($@) { die("NLST succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '/proc(\/)?: No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_retr_procfs_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw('/proc/test.txt'); if ($conn) { die("RETR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_retr_sysfs_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/sys/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw($test_file); if ($conn) { die("RETR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /RETR $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_rmd_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->rmd('/proc/') }; unless ($@) { die("RMD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->xrmd('/proc') }; unless ($@) { die("XRMD succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_rnto_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $src_file = File::Spec->rel2abs("$tmpdir/src.txt"); if (open(my $fh, "> $src_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $src_file: $!"); } } else { die("Can't open $src_file: $!"); } if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $src_file)) { die("Can't set owner of $src_file to $setup->{uid}/$setup->{gid}: $!"); } } my $dst_file = File::Spec->rel2abs("/proc/test.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->rnfr($src_file); eval { $client->rnto($dst_file) }; unless ($@) { die("RNTO succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$dst_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_size_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); eval { $client->size('/proc/test.txt') }; unless ($@) { die("SIZE succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_stat_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->stat('/proc/test.txt') }; unless ($@) { die("STAT succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 450; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_stor_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw('/proc/test.txt'); if ($conn) { die("STOR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_chgrp_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('CHGRP', $setup->{group}, '/proc/test.txt') }; unless ($@) { die("SITE CHGRP succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_chmod_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('CHMOD', '777', '/proc/test.txt') }; unless ($@) { die("SITE CHMOD succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_mkdir_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('MKDIR', '/proc/test.d') }; unless ($@) { die("SITE MKDIR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '(No such file or directory|Read-only file system)'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_rmdir_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('RMDIR', '/proc') }; unless ($@) { die("SITE RMDIR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_symlink_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('SYMLINK', '/proc/from.txt', 'to.txt') }; unless ($@) { die("SITE SYMLINK succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->site('SYMLINK', 'from.txt', '/proc/to.txt') }; unless ($@) { die("SITE SYMLINK succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_utime_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('UTIME', '200002240826', '/proc/test.txt') }; unless ($@) { die("SITE UTIME succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_cpfr_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('CPFR', '/proc/test.txt') }; unless ($@) { die("SITE CPFR succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "/proc/test.txt: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_cpto_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $src_file = File::Spec->rel2abs("$tmpdir/src.txt"); if (open(my $fh, "> $src_file")) { print $fh "Hello, World!\n"; unless (close($fh)) { die("Can't write $src_file: $!"); } } else { die("Can't open $src_file: $!"); } if ($< == 0) { unless (chown($setup->{uid}, $setup->{gid}, $src_file)) { die("Can't set owner of $src_file to $setup->{uid}/$setup->{gid}: $!"); } } my $dst_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->site('CPFR', $src_file); eval { $client->site('CPTO', $dst_file) }; unless ($@) { die("SITE CPTO succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $client->quit(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } sub procfs_site_copy_rejected { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'command:10 response:10 procfs:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->site('COPY', '/proc/from.txt', 'to.txt') }; unless ($@) { die("SITE COPY succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->site('COPY', 'from.txt', '/proc/to.txt') }; unless ($@) { die("SITE COPY succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'No such file or directory'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup, $ex); } 1; proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/Modules/mod_procfs/000077500000000000000000000000001523217215100242565ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/Modules/mod_procfs/digest.pm000066400000000000000000000616371523217215100261100ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_procfs::digest; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Cwd; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IPC::Open3; use Socket; use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { procfs_digest_hash => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_md5 => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xmd5 => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xcrc => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xsha => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xsha1 => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xsha256 => { order => ++$order, test_class => [qw(forking)], }, procfs_digest_xsha512 => { order => ++$order, test_class => [qw(forking)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); # procfs_digest_xsha # procfs_digest_xsha1 # procfs_digest_xsha256 } sub procfs_digest_hash { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $algo = 'CRC32'; my ($resp_code, $resp_msg) = $client->opts('HASH', $algo); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = $algo; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->quote('HASH', $test_file) }; unless ($@) { die("HASH command succeeded unexpectedly"); } $resp_code = $client->response_code(); $resp_msg = $client->response_msg(); $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /HASH $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_md5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('MD5', $test_file) }; unless ($@) { die("MD5 command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /MD5 $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xmd5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XMD5', $test_file) }; unless ($@) { die("XMD5 command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XMD5 $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xcrc { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XCRC', $test_file) }; unless ($@) { die("XCRC command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XCRC $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xsha { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XSHA', $test_file) }; unless ($@) { die("XSHA command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XSHA $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xsha1 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XSHA1', $test_file) }; unless ($@) { die("XSHA1 command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XSHA1 $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xsha256 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XSHA256', $test_file) }; unless ($@) { die("XSHA256 command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XSHA256 $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_digest_xsha512 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 digest:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_digest.c' => { DigestEngine => 'on', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); eval { $client->quote('XSHA512', $test_file) }; unless ($@) { die("XSHA512 command succeeded unexpectedly"); } my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 550; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "$test_file: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /XSHA512 $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } 1; proftpd-mod_procfs-0.3/t/lib/ProFTPD/Tests/Modules/mod_procfs/sftp.pm000066400000000000000000001631151523217215100255770ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_procfs::sftp; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use Cwd; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use IPC::Open3; use POSIX qw(:fcntl_h); use Socket; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { procfs_sftp_open_readonly => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_open_readwrite => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_lstat => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_mkdir => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_opendir => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_readlink => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_realpath => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_remove => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_rename => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_rmdir => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_setstat => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_stat => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_symlink => { order => ++$order, test_class => [qw(forking)], }, procfs_sftp_ext_hardlink => { order => ++$order, test_class => [qw(forking)], }, # TODO # procfs_sftp_link }; sub new { return shift()->SUPER::new(@_); } sub list_tests { # Check for the required Perl modules: # # Net-SSH2 # Net-SSH2-SFTP my $required = [qw( Net::SSH2 Net::SSH2::SFTP )]; foreach my $req (@$required) { eval "use $req"; if ($@) { print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n"; if ($ENV{TEST_VERBOSE}) { print STDERR "Unable to load $req: $@\n"; } return qw(testsuite_empty_test); } } return testsuite_get_runnable_tests($TESTS); } sub set_up { my $self = shift; $self->SUPER::set_up(@_); # Make sure that mod_sftp does not complain about permissions on the hostkey # files. my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); unless (chmod(0400, $rsa_host_key, $dsa_host_key)) { die("Can't set perms on $rsa_host_key, $dsa_host_key: $!"); } } sub procfs_sftp_open_readonly { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $fh = $sftp->open($test_file, O_RDONLY); if ($fh) { die("OPEN $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_NO_SUCH_FILE'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 2; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /RETR $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_open_readwrite { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $fh = $sftp->open($test_file, O_RDWR|O_CREAT); if ($fh) { die("OPEN $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_NO_SUCH_FILE'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 2; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /STOR $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_lstat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $st = $sftp->stat($test_file, 0); if ($st) { die("LSTAT $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /LSTAT $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_mkdir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_path = '/proc/test.d'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->mkdir($test_path); if ($res) { die("MKDIR $test_path succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /MKD $test_path denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_opendir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_path = '/proc/test.d'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $dir = $sftp->opendir($test_path); if ($dir) { die("OPENDIR $test_path succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /OPENDIR $test_path denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_readlink { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->readlink($test_file); if ($res) { die("READLINK $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /READLINK $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_realpath { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->realpath($test_file); if ($res) { die("REALPATH $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /REALPATH $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_remove { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->unlink($test_file); if ($res) { die("REMOVE $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /DELE $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_rename { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $from_file = '/proc/src.txt'; my $to_file = '/proc/dst.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->rename($from_file, 'dst.txt'); if ($res) { die("RENAME $from_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $res = $sftp->rename('src.txt', $to_file); if ($res) { die("RENAME $to_file succeeded unexpectedly"); } ($err_code, $err_name) = $sftp->error(); $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $from_ok = 0; my $to_ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /RNFR $from_file denied by mod_procfs/) { $from_ok = 1; } if ($line =~ /RNTO $to_file denied by mod_procfs/) { $to_ok = 1; } if ($from_ok && $to_ok) { last; } } close($fh); $self->assert($from_ok && $to_ok, test_msg("Did not see expected ProcfsLog messages")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_rmdir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_path = '/proc/test.d'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->rmdir($test_path); if ($res) { die("RMDIR $test_path succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /RMD $test_path denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_setstat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->setstat($test_file, atime => 0, mtime => 0 ); if ($res) { die("SETSTAT $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /SETSTAT $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_stat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = '/proc/test.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $st = $sftp->stat($test_file, 1); if ($st) { die("STAT $test_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /STAT $test_file denied by mod_procfs/) { $ok = 1; last; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog message")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_symlink { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $from_file = '/proc/src.txt'; my $to_file = '/proc/dst.txt'; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $ssh2 = Net::SSH2->new(); unless ($ssh2->connect('127.0.0.1', $port)) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str"); } unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str"); } my $sftp = $ssh2->sftp(); unless ($sftp) { my ($err_code, $err_name, $err_str) = $ssh2->error(); die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str"); } my $res = $sftp->symlink($from_file, 'dst.txt'); if ($res) { die("SYMLINK $from_file succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $res = $sftp->symlink('src.txt', $to_file); if ($res) { die("SYMLINK $to_file succeeded unexpectedly"); } ($err_code, $err_name) = $sftp->error(); $expected = 'SSH_FX_PERMISSION_DENIED'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); $expected = 3; $self->assert($expected == $err_code, test_msg("Expected error code $expected, got $err_code")); $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $from_ok = 0; my $to_ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /SYMLINK $from_file denied by mod_procfs/) { $from_ok = 1; } if ($line =~ /SYMLINK $to_file denied by mod_procfs/) { $to_ok = 1; } if ($from_ok && $to_ok) { last; } } close($fh); $self->assert($from_ok && $to_ok, test_msg("Did not see expected ProcfsLog messages")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } sub procfs_sftp_ext_hardlink { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'procfs'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key"); my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/test_rsa_key.pub"); my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_sftp/authorized_rsa_keys"); my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys"); unless (copy($rsa_rfc4716_key, $authorized_keys)) { die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!"); } my $from_file = '/proc/src.txt'; my $to_file = '/proc/dst.txt'; # As Net::SSH2::SFTP (via libssh2) does not implement LINK, we'll use # OpenSSH's sftp(1) and its "hardlink@openssh.com" extension. my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf"); if (open(my $fh, "> $batch_file")) { print $fh "ln $from_file $to_file\n"; unless (close($fh)) { die("Can't write $batch_file: $!"); } } else { die("Can't open $batch_file: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'procfs:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_procfs.c' => { ProcfsEngine => 'on', ProcfsLog => $setup->{log_file}, }, 'mod_sftp.c' => [ 'SFTPEngine on', "SFTPLog $setup->{log_file}", "SFTPHostKey $rsa_host_key", "SFTPHostKey $dsa_host_key", "SFTPAuthorizedUserKeys file:~/.authorized_keys", ], }, }; my ($port, $config_user, $config_group) = config_write($setup->{config_file}, $config); # Open pipes, for use between the parent and child processes. Specifically, # the child will indicate when it's done with its test by writing a message # to the parent. my ($rfh, $wfh); unless (pipe($rfh, $wfh)) { die("Can't open pipe: $!"); } require Net::SSH2; my $ex; # Ignore SIGPIPE local $SIG{PIPE} = sub { }; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Allow for server startup sleep(1); my $sftp = 'sftp'; my @cmd = ( $sftp, '-oBatchMode=yes', '-oCheckHostIP=no', '-oCompression=yes', "-oPort=$port", "-oIdentityFile=$rsa_priv_key", '-oIdentitiesOnly=yes', '-oPubkeyAuthentication=yes', '-oStrictHostKeyChecking=no', '-oUserKnownHostsFile=/dev/null', '-vvv', '-b', $batch_file, "$setup->{user}\@127.0.0.1", ); my $sftp_rh = IO::Handle->new(); my $sftp_wh = IO::Handle->new(); my $sftp_eh = IO::Handle->new(); $sftp_wh->autoflush(1); sleep(1); local $SIG{CHLD} = 'DEFAULT'; # Make sure that the perms on the priv key are what OpenSSH wants unless (chmod(0400, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key to 0400: $!"); } if ($ENV{TEST_VERBOSE}) { print STDERR "Executing: ", join(' ', @cmd), "\n"; } my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd); waitpid($sftp_pid, 0); my $exit_status = $?; # Restore the perms on the priv key unless (chmod(0644, $rsa_priv_key)) { die("Can't set perms on $rsa_priv_key to 0644: $!"); } my ($res, $errstr); if ($exit_status >> 8 == 0) { $errstr = join('', <$sftp_eh>); $res = 0; } else { $errstr = join('', <$sftp_eh>); if ($ENV{TEST_VERBOSE}) { print STDERR "Stderr: $errstr\n"; } $res = 1; } if ($res == 0) { die("Hardlink $from_file on server succeeded unexpectedly"); } }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); eval { if (open(my $fh, "< $setup->{log_file}")) { my $ok = 0; while (my $line = <$fh>) { chomp($line); next unless $line =~ /mod_procfs\//; if ($ENV{TEST_VERBOSE}) { print STDERR "# $line\n"; } if ($line =~ /HARDLINK $from_file denied by mod_procfs/) { $ok = 1; } } close($fh); $self->assert($ok, test_msg("Did not see expected ProcfsLog messages")); } else { die("Can't read $setup->{log_file}: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup, $ex); } 1; proftpd-mod_procfs-0.3/t/modules/000077500000000000000000000000001523217215100170755ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/modules/mod_procfs.t000066400000000000000000000002661523217215100214210ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_procfs"); proftpd-mod_procfs-0.3/t/modules/mod_procfs/000077500000000000000000000000001523217215100212305ustar00rootroot00000000000000proftpd-mod_procfs-0.3/t/modules/mod_procfs/digest.t000066400000000000000000000002761523217215100227010ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_procfs::digest"); proftpd-mod_procfs-0.3/t/modules/mod_procfs/sftp.t000066400000000000000000000002741523217215100223740ustar00rootroot00000000000000#!/usr/bin/env perl use lib qw(t/lib); use strict; use Test::Unit::HarnessUnit; $| = 1; my $r = Test::Unit::HarnessUnit->new(); $r->start("ProFTPD::Tests::Modules::mod_procfs::sftp"); proftpd-mod_procfs-0.3/tests.pl000066400000000000000000000052401523217215100166620ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use Cwd qw(abs_path); use File::Spec; use Getopt::Long; use Test::Harness qw(&runtests $verbose); my $opts = {}; GetOptions($opts, 'h|help', 'C|class=s@', 'K|keep-tmpfiles', 'F|file-pattern=s', 'V|verbose'); if ($opts->{h}) { usage(); } if ($opts->{K}) { $ENV{KEEP_TMPFILES} = 1; } $verbose = 1; if ($opts->{V}) { $ENV{TEST_VERBOSE} = 1; } # We use this, rather than use(), since use() is equivalent to a BEGIN # block, and we want the module to be loaded at run-time. if ($ENV{PROFTPD_TEST_DIR}) { push(@INC, "$ENV{PROFTPD_TEST_DIR}/t/lib"); } my $test_dir = (File::Spec->splitpath(abs_path(__FILE__)))[1]; push(@INC, "$test_dir/t/lib"); require ProFTPD::TestSuite::Utils; import ProFTPD::TestSuite::Utils qw(:testsuite); # This is to handle the case where this tests.pl script might be # being used to run test files other than those that ship with proftpd, # e.g. to run the tests that come with third-party modules. unless (defined($ENV{PROFTPD_TEST_BIN})) { $ENV{PROFTPD_TEST_BIN} = File::Spec->catfile($test_dir, '..', 'proftpd'); } $| = 1; my $test_files; if (scalar(@ARGV) > 0) { $test_files = [@ARGV]; } else { $test_files = [qw( t/modules/mod_procfs.t )]; # Now interrogate the build to see which module/feature-specific test files # should be added to the list. my $order = 0; my $FEATURE_TESTS = { 't/modules/mod_procfs/digest.t' => { order => ++$order, test_class => [qw(mod_digest)], }, 't/modules/mod_procfs/sftp.t' => { order => ++$order, test_class => [qw(mod_sftp)], }, }; my @feature_tests = testsuite_get_runnable_tests($FEATURE_TESTS); my $feature_ntests = scalar(@feature_tests); if ($feature_ntests > 1 || ($feature_ntests == 1 && $feature_tests[0] ne 'testsuite_empty_test')) { push(@$test_files, @feature_tests); } } $ENV{PROFTPD_TEST} = 1; if (defined($opts->{C})) { $ENV{PROFTPD_TEST_ENABLE_CLASS} = join(':', @{ $opts->{C} }); } else { # Disable all 'inprogress' and 'slow' tests by default $ENV{PROFTPD_TEST_DISABLE_CLASS} = 'inprogress:slow'; } if (defined($opts->{F})) { # Using the provided string as a regex, and run only the tests whose # files match the pattern my $file_pattern = $opts->{F}; my $filtered_files = []; foreach my $test_file (@$test_files) { if ($test_file =~ /$file_pattern/) { push(@$filtered_files, $test_file); } } $test_files = $filtered_files; } runtests(@$test_files) if scalar(@$test_files) > 0; exit 0; sub usage { print STDOUT <