pax_global_header00006660000000000000000000000064147617101170014517gustar00rootroot0000000000000052 comment=1170c50dae90d74e25f77018ecb1d8b7d6af563b proftpd-mod_case-0.9.1/000077500000000000000000000000001476171011700147545ustar00rootroot00000000000000proftpd-mod_case-0.9.1/.codeql.yml000066400000000000000000000000421476171011700170200ustar00rootroot00000000000000--- paths: - contrib/mod_case.c proftpd-mod_case-0.9.1/.gitattributes000066400000000000000000000000621476171011700176450ustar00rootroot00000000000000*.pl linguist-language=C *.pm linguist-language=C proftpd-mod_case-0.9.1/.github/000077500000000000000000000000001476171011700163145ustar00rootroot00000000000000proftpd-mod_case-0.9.1/.github/workflows/000077500000000000000000000000001476171011700203515ustar00rootroot00000000000000proftpd-mod_case-0.9.1/.github/workflows/ci.yml000066400000000000000000000117461476171011700215000ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches: - master schedule: - cron: '11 1 * * 0' jobs: build: runs-on: ubuntu-latest strategy: matrix: compiler: - clang - gcc container: - almalinux:8 - alpine:3.18 - ubuntu:22.04 container: ${{ matrix.container }} steps: - name: Checkout ProFTPD uses: actions/checkout@v3 with: repository: proftpd/proftpd path: proftpd - name: Checkout module source code uses: actions/checkout@v3 with: path: proftpd-mod_case - name: Whitespace check if: ${{ matrix.container == 'ubuntu:22.04' }} run: | apt-get update -qq apt-get install -y git cd proftpd-mod_case 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_case/mod_case.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 unit tests apk add check check-dev subunit subunit-dev # for OpenSSL support apk add openssl openssl-dev # for debugging clang --version gcc --version openssl version -a - 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 unit tests yum install -y check-devel https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-1.4.0-1.el8.x86_64.rpm https://cbs.centos.org/kojifiles/packages/subunit/1.4.0/1.el8/x86_64/subunit-devel-1.4.0-1.el8.x86_64.rpm # for OpenSSL support yum install -y openssl openssl-devel # for debugging clang --version gcc --version openssl version -a - 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 unit tests apt-get install -y check libsubunit-dev # for OpenSSL support apt-get install -y libssl-dev # for integration/regression test apt-get install -y \ libdata-dumper-simple-perl \ libdatetime-perl \ libfile-copy-recursive-perl \ libfile-path-tiny-perl \ libfile-spec-native-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 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 openssl version -a - 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 LIBS="-lm -lsubunit -lrt -pthread" --enable-devel=coverage --enable-tests --with-modules=mod_case:mod_copy:mod_sftp:mod_tls make - name: Install as static module run: | cd proftpd make install - name: Run integration tests if: ${{ matrix.compiler == 'gcc' && matrix.container == 'ubuntu:22.04' }} env: PROFTPD_TEST_BIN: /usr/local/sbin/proftpd PROFTPD_TEST_DIR: ${{ github.workspace }}/proftpd run: | cd proftpd-mod_case perl tests.pl - name: Build as shared module env: CC: ${{ matrix.compiler }} run: | cd proftpd make clean ./configure LIBS="-lm -lsubunit -lrt -pthread" --enable-devel --enable-dso --with-shared=mod_case make - name: Install as shared module run: | cd proftpd make install - name: Check HTML docs run: | cd proftpd-mod_case echo "Processing mod_case.html" tidy -errors -omit -q mod_case.html | exit 0 proftpd-mod_case-0.9.1/.github/workflows/codeql.yml000066400000000000000000000030421476171011700223420ustar00rootroot00000000000000name: CodeQL on: push: branches: - master paths-ignore: - '**/*.md' - '**/doc/*' pull_request: branches: - master paths-ignore: - '**/*.md' - '**/doc/*' schedule: - cron: "22 7 * * 5" jobs: analyze: name: CodeQL Analysis runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: true matrix: language: - cpp steps: - name: Checkout ProFTPD uses: actions/checkout@v3 with: repository: proftpd/proftpd - name: Checkout mod_case uses: actions/checkout@v3 with: path: proftpd-mod_case - name: Install Packages run: | sudo apt-get update - name: Prepare module run: | cp proftpd-mod_case/mod_case.c contrib/mod_case.c - name: Configure run: | ./configure --with-modules=mod_case - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} config-file: proftpd-mod_case/.codeql.yml queries: +security-and-quality source-root: proftpd-mod_case - name: Build run: | make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{ matrix.language }}" checkout_path: proftpd-mod_case output: sarif-results upload: true proftpd-mod_case-0.9.1/.gitignore000066400000000000000000000000361476171011700167430ustar00rootroot00000000000000*.lo *~ *.swo *.swp tests.log proftpd-mod_case-0.9.1/README.md000066400000000000000000000013011476171011700162260ustar00rootroot00000000000000proftpd-mod_case ================ Status ------ [![GitHub Actions CI Status](https://github.com/Castaglia/proftpd-mod_case/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Castaglia/proftpd-mod_case/actions/workflows/ci.yml) [![License](https://img.shields.io/badge/license-GPL-brightgreen.svg)](https://img.shields.io/badge/license-GPL-brightgreen.svg) Synopsis -------- The `mod_case` module for ProFTPD provides case-insensitivity support to FTP commands; useful for dealing with Windows FTP clients. For further module documentation, see the [mod_case.html](https://htmlpreview.github.io/?https://github.com/Castaglia/proftpd-mod_case/blob/master/mod_case.html) documentation. proftpd-mod_case-0.9.1/mod_case.c000066400000000000000000000657061476171011700167100ustar00rootroot00000000000000/* * ProFTPD: mod_case -- provides case-insensivity * Copyright (c) 2004-2025 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * This is mod_case, contrib software for proftpd 1.3.x and above. * For more information contact TJ Saunders . */ #include "conf.h" #include "privs.h" #define MOD_CASE_VERSION "mod_case/0.9.1" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030402 # error "ProFTPD 1.3.4rc2 or later required" #endif static int case_engine = FALSE; static int case_logfd = -1; static const char *trace_channel = "case"; /* Support routines */ static int case_expr_eval_cmds(cmd_rec *cmd, array_header *list) { int cmd_id, found; register unsigned int i; for (i = 0; i < list->nelts; i++) { char *c = ((char **) list->elts)[i]; found = 0; if (*c == '!') { found = !found; c++; } cmd_id = pr_cmd_get_id(c); if (cmd_id > 0) { if (pr_cmd_cmp(cmd, cmd_id) == 0) { found = !found; } } else { /* Fallback to doing a full strcmp(3). */ if (strcmp(cmd->argv[0], c) == 0) { found = !found; } } if (found) { return 1; } } return 0; } static char *case_get_opts_path(cmd_rec *cmd, int *path_index) { char *ptr; char *path; size_t pathlen; if (cmd->arg == NULL) { return NULL; } ptr = path = cmd->arg; pathlen = strlen(path); if (pathlen == 0) { return NULL; } while (isspace((int) *ptr)) { pr_signals_handle(); ptr++; } if (*ptr == '-') { /* Options are found; skip past the leading whitespace. */ path = ptr; } while (path && *path == '-') { /* Advance to the next whitespace */ while (*path != '\0' && !isspace((int) *path)) { path++; } ptr = path; while (*ptr && isspace((int) *ptr)) { pr_signals_handle(); ptr++; } if (*ptr == '-') { /* Options are found; skip past the leading whitespace. */ path = ptr; } else if (*(path + 1) == ' ') { /* If the next character is a blank space, advance just one * character. */ path++; break; } else { path = ptr; break; } } pathlen = strlen(path); if (pathlen == 0) { return NULL; } *path_index = (ptr - cmd->arg); return path; } static void case_replace_copy_paths(cmd_rec *cmd, const char *proto, const char *src_path, const char *dst_path) { /* Minor nit: if src_path/dst_path is "//", then reduce it to just "/". */ if (strcmp(src_path, "//") == 0) { src_path = pstrdup(cmd->tmp_pool, "/"); } if (strcmp(dst_path, "//") == 0) { dst_path = pstrdup(cmd->tmp_pool, "/"); } if (strcmp(proto, "ftp") == 0 || strcmp(proto, "ftps") == 0) { array_header *argv; /* We should only be handling SITE COPY (over FTP/FTPS) requests here */ argv = make_array(cmd->pool, 4, sizeof(char *)); *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[0]); *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[1]); *((char **) push_array(argv)) = pstrdup(cmd->pool, src_path); *((char **) push_array(argv)) = pstrdup(cmd->pool, dst_path); cmd->argc = argv->nelts; *((char **) push_array(argv)) = NULL; cmd->argv = argv->elts; cmd->arg = pstrcat(cmd->pool, cmd->argv[1], " ", src_path, " ", dst_path, NULL); } pr_cmd_clear_cache(cmd); } static void case_replace_link_paths(cmd_rec *cmd, const char *proto, const char *src_path, const char *dst_path) { /* Minor nit: if src_path/dst_path is "//", then reduce it to just "/". */ if (strcmp(src_path, "//") == 0) { src_path = pstrdup(cmd->tmp_pool, "/"); } if (strcmp(dst_path, "//") == 0) { dst_path = pstrdup(cmd->tmp_pool, "/"); } if (strcmp(proto, "sftp") == 0) { /* We should only be handling SFTP SYMLINK and LINK requests here. */ cmd->arg = pstrcat(cmd->pool, src_path, "\t", dst_path, NULL); if (cmd->argv[1] != cmd->arg) { cmd->argv[1] = cmd->arg; } } pr_cmd_clear_cache(cmd); } static void case_replace_path(cmd_rec *cmd, const char *proto, const char *path, int path_index) { if (strcmp(proto, "ftp") == 0 || strcmp(proto, "ftps") == 0) { /* Special handling of LIST/NLST/STAT commands, which can take options */ 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) { /* XXX Be sure to overwrite the entire cmd->argv array, not just * cmd->arg. */ if (path_index > 0) { unsigned int i; char *arg; arg = pstrdup(cmd->tmp_pool, cmd->arg); arg[path_index] = '\0'; arg = pstrcat(cmd->pool, arg, path, NULL); cmd->arg = arg; /* We also need to find the index into cmd->argv to replace. Look * for the first item that does not start with `-`. */ for (i = 1; i < cmd->argc; i++) { if (*((char *) cmd->argv[i]) != '-') { break; } } cmd->argv[i] = pstrdup(cmd->pool, path); } else { cmd->arg = pstrdup(cmd->pool, path); } pr_cmd_clear_cache(cmd); } else { char *arg, *dup_path; array_header *argv; int flags = PR_STR_FL_PRESERVE_COMMENTS; dup_path = pstrdup(cmd->pool, path); /* Be sure to overwrite the entire cmd->argv array, not just cmd->arg. */ argv = make_array(cmd->pool, 2, sizeof(char *)); *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[0]); if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0) { if (strncmp(cmd->argv[1], "CHGRP", 6) == 0 || strncmp(cmd->argv[1], "CHMOD", 6) == 0) { *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[1]); *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[2]); } else if (strncmp(cmd->argv[1], "CPFR", 5) == 0 || strncmp(cmd->argv[1], "CPTO", 5) == 0) { *((char **) push_array(argv)) = pstrdup(cmd->pool, cmd->argv[1]); } } /* Handle spaces in the new path properly by breaking them up and adding * them into the argv. */ arg = pr_str_get_word(&dup_path, flags); while (arg != NULL) { pr_signals_handle(); *((char **) push_array(argv)) = pstrdup(cmd->pool, arg); arg = pr_str_get_word(&dup_path, flags); } cmd->argc = argv->nelts; *((char **) push_array(argv)) = NULL; cmd->argv = argv->elts; pr_cmd_clear_cache(cmd); /* In the case of many commands, we also need to overwrite cmd->arg. */ if (pr_cmd_cmp(cmd, PR_CMD_APPE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_CWD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MDTM_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MLSD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MLST_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNFR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_SIZE_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_XCWD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_XMKD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_XRMD_ID) == 0) { cmd->arg = pstrdup(cmd->pool, path); } } if (pr_trace_get_level(trace_channel) >= 19) { register unsigned int i; pr_trace_msg(trace_channel, 19, "replacing path: cmd->argc = %d", cmd->argc); for (i = 0; i < cmd->argc; i++) { pr_trace_msg(trace_channel, 19, "replacing path: cmd->argv[%u] = '%s'", i, (char *) cmd->argv[i]); } } return; } if (strcmp(proto, "sftp") == 0) { /* Main SFTP commands */ if (pr_cmd_cmp(cmd, PR_CMD_RETR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_STOR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_MKD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RMD_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNFR_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_RNTO_ID) == 0 || pr_cmd_cmp(cmd, PR_CMD_DELE_ID) == 0 || pr_cmd_strcmp(cmd, "LSTAT") == 0 || pr_cmd_strcmp(cmd, "OPENDIR") == 0 || pr_cmd_strcmp(cmd, "READLINK") == 0 || pr_cmd_strcmp(cmd, "REALPATH") == 0 || pr_cmd_strcmp(cmd, "SETSTAT") == 0 || pr_cmd_strcmp(cmd, "STAT") == 0) { cmd->arg = pstrdup(cmd->pool, path); } pr_cmd_clear_cache(cmd); return; } } static int case_scan_directory(pool *p, DIR *dirh, const char *dir_name, const char *file, char **matched_file) { struct dirent *dent; const char *file_match; /* Escape any existing fnmatch(3) characters in the file name. */ file_match = pstrdup(p, file); if (strchr(file_match, '?') != NULL) { file_match = sreplace(p, file_match, "?", "\\?", NULL); } if (strchr(file_match, '*') != NULL) { file_match = sreplace(p, file_match, "*", "\\*", NULL); } if (strchr(file_match, '[') != NULL) { file_match = sreplace(p, file_match, "[", "\\[", NULL); } /* For each file in the directory, check it against the given name, both * as an exact match and as a possible match. */ dent = pr_fsio_readdir(dirh); while (dent != NULL) { pr_signals_handle(); if (strcmp(dent->d_name, file) == 0) { pr_trace_msg(trace_channel, 9, "found exact match for file '%s' in directory '%s'", file, dir_name); *matched_file = NULL; return 0; } if (pr_fnmatch(file_match, dent->d_name, PR_FNM_CASEFOLD) == 0) { (void) pr_log_writefile(case_logfd, MOD_CASE_VERSION, "found case-insensitive match '%s' for '%s' in directory '%s'", dent->d_name, file_match, dir_name); *matched_file = pstrdup(p, dent->d_name); return 0; } dent = pr_fsio_readdir(dirh); } errno = ENOENT; return -1; } static const char *case_normalize_path(pool *p, const char *path, int *changed) { register unsigned int i; int xerrno; char *iter_path, *normalized_path, **elts; size_t path_len; pr_fh_t *fh; array_header *components; pool *tmp_pool; /* Special cases. */ path_len = strlen(path); if (path_len == 1) { if (path[0] == '/' || path[1] == '.') { /* Nothing to do. */ return path; } } /* Can we open the path as is? If so, we can avoid the more expensive * filesystem walk. Note that the path might point to a directory. */ fh = pr_fsio_open(path, O_RDONLY); xerrno = errno; if (fh != NULL) { (void) pr_fsio_close(fh); return path; } if (xerrno != ENOENT) { /* The path exists as is; that's OK. */ return path; } tmp_pool = make_sub_pool(p); /* Note that it is tempting to use `pr_fs_split_path()`, however its * semantics (resolving to an absolute path first) are not quite expected * here. So we'll just use pr_str_text_to_array() directly. */ components = pr_str_text_to_array(tmp_pool, path, '/'); /* For the first component, what is the directory to open? Depends; * did the path start with '/', '.', or neither? */ iter_path = pstrdup(tmp_pool, "."); if (*path == '/') { iter_path = pstrdup(tmp_pool, "/"); } elts = components->elts; for (i = 0; i < components->nelts; i++) { int res; pool *iter_pool; DIR *dirh; char *matched_elt = NULL; /* Note that the last component in the list should be the target; we * don't want to use opendir(3) on the target. */ iter_pool = make_sub_pool(tmp_pool); dirh = pr_fsio_opendir(iter_path); if (dirh == NULL) { int xerrno = errno; /* This should never happen, right? It could, due to races with other * processes' changes to the filesystem. */ (void) pr_log_writefile(case_logfd, MOD_CASE_VERSION, "error opening directory '%s': %s", iter_path, strerror(xerrno)); destroy_pool(iter_pool); errno = xerrno; return NULL; } res = case_scan_directory(iter_pool, dirh, iter_path, elts[i], &matched_elt); if (res == 0 && matched_elt != NULL) { ((char **) components->elts)[i] = pstrdup(tmp_pool, matched_elt); if (changed != NULL) { *changed = TRUE; } } pr_fsio_closedir(dirh); destroy_pool(iter_pool); iter_path = pdircat(tmp_pool, iter_path, elts[i], NULL); } /* Now return the normalized path, built from our possibly-modified * components. We would use `pr_fs_join_join()`, but it has a now-corrected * bug. */ elts = components->elts; if (*path == '/') { normalized_path = pstrcat(p, "/", elts[0], NULL); } else { normalized_path = pstrdup(p, elts[0]); } for (i = 1; i < components->nelts; i++) { char *elt; elt = ((char **) components->elts)[i]; normalized_path = pdircat(p, normalized_path, elt, NULL); } destroy_pool(tmp_pool); pr_trace_msg(trace_channel, 19, "normalized path '%s' to '%s'", path, normalized_path); return normalized_path; } static int case_have_file(pool *p, const char *path, const char **matched_path) { int changed = FALSE; const char *normalized_path; normalized_path = case_normalize_path(p, path, &changed); if (normalized_path == NULL) { return FALSE; } if (changed == TRUE) { *matched_path = normalized_path; } return TRUE; } /* Command handlers */ /* The SITE COPY requests are different enough to warrant their own command * handler. */ MODRET case_pre_copy(cmd_rec *cmd) { config_rec *c; const char *proto, *matched_path = NULL; char *src_path, *dst_path; int modified_arg = FALSE, res; if (case_engine == FALSE) { return PR_DECLINED(cmd); } c = find_config(CURRENT_CONF, CONF_PARAM, "CaseIgnore", FALSE); if (c == NULL) { return PR_DECLINED(cmd); } if (*((unsigned int *) c->argv[0]) != TRUE) { return PR_DECLINED(cmd); } if (c->argv[1] != NULL && case_expr_eval_cmds(cmd, *((array_header **) c->argv[1])) == 0) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); if (strncasecmp(cmd->argv[2], "HELP", 5) == 0) { /* Ignore SITE COPY HELP requests */ return PR_DECLINED(cmd); } /* We know the protocol here will always be "ftp" or "ftps", right? And that * we are only handling SITE COPY requests here. */ if (cmd->argc != 4) { /* Malformed SITE COPY cmd_rec */ (void) pr_log_writefile(case_logfd, MOD_CASE_VERSION, "malformed SITE COPY request, ignoring"); return PR_DECLINED(cmd); } src_path = cmd->argv[2]; dst_path = cmd->argv[3]; pr_trace_msg(trace_channel, 9, "checking client-sent source path '%s', destination path '%s'", src_path, dst_path); res = case_have_file(cmd->tmp_pool, src_path, &matched_path); if (res < 0) { return PR_DECLINED(cmd); } if (res == TRUE && matched_path != NULL) { /* Replace the source path */ src_path = pstrdup(cmd->tmp_pool, matched_path); modified_arg = TRUE; } else { pr_trace_msg(trace_channel, 9, "no case-insensitive matches found for path '%s'", src_path); } matched_path = NULL; res = case_have_file(cmd->tmp_pool, dst_path, &matched_path); if (res == TRUE) { if (matched_path != NULL) { /* Replace the destination path */ dst_path = pstrdup(cmd->tmp_pool, matched_path); modified_arg = TRUE; } } else { pr_trace_msg(trace_channel, 9, "no case-insensitive matches found for path '%s'", dst_path); } /* Overwrite the client-given paths. */ if (modified_arg == TRUE) { case_replace_copy_paths(cmd, proto, src_path, dst_path); } return PR_DECLINED(cmd); } MODRET case_pre_cmd(cmd_rec *cmd) { config_rec *c; const char *proto = NULL, *matched_path = NULL; char *path = NULL; int path_index = -1, res; if (case_engine == FALSE) { return PR_DECLINED(cmd); } c = find_config(CURRENT_CONF, CONF_PARAM, "CaseIgnore", FALSE); if (c == NULL) { return PR_DECLINED(cmd); } if (*((unsigned int *) c->argv[0]) != TRUE) { return PR_DECLINED(cmd); } if (c->argv[1] != NULL && case_expr_eval_cmds(cmd, *((array_header **) c->argv[1])) == 0) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); if (strcmp(proto, "sftp") == 0) { path = pstrdup(cmd->tmp_pool, cmd->arg); } else { /* Special handling of LIST/NLST/STAT, given that they may have options * in the command. */ 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) { path = case_get_opts_path(cmd, &path_index); /* LIST, NLST, and STAT can send no path arguments. If that's the * case, we're done. */ if (path == NULL) { return PR_DECLINED(cmd); } /* Make sure we operate on a duplicate of the extracted path. */ path = pstrdup(cmd->tmp_pool, path); } else if (pr_cmd_cmp(cmd, PR_CMD_SITE_ID) == 0) { register unsigned int i; if (strncmp(cmd->argv[1], "COPY", 5) == 0) { return case_pre_copy(cmd); } if (strncmp(cmd->argv[1], "CHGRP", 6) == 0 || strncmp(cmd->argv[1], "CHMOD", 6) == 0) { if (cmd->argc < 4) { pr_trace_msg(trace_channel, 3, "ignoring SITE %s: not enough parameters (%d)", (char *) cmd->argv[1], cmd->argc - 2); return PR_DECLINED(cmd); } path = ""; /* Skip over "SITE, "CHMOD" (or "CHGRP"), and the mode (or group). */ for (i = 3; i < cmd->argc; i++) { path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL); } } else if (strncmp(cmd->argv[1], "CPFR", 5) == 0 || strncmp(cmd->argv[1], "CPTO", 5) == 0) { if (cmd->argc < 3) { pr_trace_msg(trace_channel, 3, "ignoring SITE %s: not enough parameters (%d)", (char *) cmd->argv[1], cmd->argc - 2); return PR_DECLINED(cmd); } path = ""; /* Skip over "SITE, and "CPFR" (or "CPTO"). */ for (i = 2; i < cmd->argc; i++) { path = pstrcat(cmd->tmp_pool, path, *path ? " " : "", pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL); } } else { (void) pr_log_writefile(case_logfd, MOD_CASE_VERSION, "unsupported SITE %s command, ignoring", (char *) cmd->argv[1]); return PR_DECLINED(cmd); } } else { path = pstrdup(cmd->tmp_pool, cmd->arg); } } pr_trace_msg(trace_channel, 9, "checking client-sent path '%s'", path); res = case_have_file(cmd->tmp_pool, path, &matched_path); if (res < 0) { return PR_DECLINED(cmd); } if (res == FALSE) { /* No match found. */ pr_trace_msg(trace_channel, 9, "no case-insensitive matches found for path '%s'", path); return PR_DECLINED(cmd); } /* We found a match for the given file. */ if (matched_path == NULL) { /* Exact match found; nothing more to do. */ return PR_DECLINED(cmd); } /* Overwrite the client-given path. */ pr_trace_msg(trace_channel, 9, "replacing path '%s' with '%s'", path, matched_path); case_replace_path(cmd, proto, matched_path, path_index); return PR_DECLINED(cmd); } /* The SYMLINK/LINK SFTP requests are different enough to warrant their own * command handler. */ MODRET case_pre_link(cmd_rec *cmd) { config_rec *c; const char *proto = NULL, *matched_path = NULL; char *arg = NULL, *src_path, *dst_path, *ptr; int modified_arg = FALSE, res; if (case_engine == FALSE) { return PR_DECLINED(cmd); } c = find_config(CURRENT_CONF, CONF_PARAM, "CaseIgnore", FALSE); if (c == NULL) { return PR_DECLINED(cmd); } if (*((unsigned int *) c->argv[0]) != TRUE) { return PR_DECLINED(cmd); } if (c->argv[1] != NULL && case_expr_eval_cmds(cmd, *((array_header **) c->argv[1])) == 0) { return PR_DECLINED(cmd); } proto = pr_session_get_protocol(0); /* We know the protocol here will always be "sftp", right? And that we * are only handling SFTP SYMLINK and LINK requests here. */ arg = pstrdup(cmd->tmp_pool, cmd->arg); ptr = strchr(arg, '\t'); if (ptr == NULL) { /* Malformed SFTP SYMLINK/LINK cmd_rec. */ (void) pr_log_writefile(case_logfd, MOD_CASE_VERSION, "malformed SFTP %s request, ignoring", (char *) cmd->argv[0]); return PR_DECLINED(cmd); } *ptr = '\0'; src_path = arg; dst_path = ptr + 1; pr_trace_msg(trace_channel, 9, "checking client-sent source path '%s', destination path '%s'", src_path, dst_path); res = case_have_file(cmd->tmp_pool, src_path, &matched_path); if (res == TRUE) { if (matched_path != NULL) { /* Replace the source path */ src_path = pstrdup(cmd->tmp_pool, matched_path); modified_arg = TRUE; } } else { pr_trace_msg(trace_channel, 9, "no case-insensitive matches found for path '%s'", src_path); } matched_path = NULL; res = case_have_file(cmd->tmp_pool, dst_path, &matched_path); if (res == TRUE) { if (matched_path != NULL) { /* Replace the destination path */ dst_path = pstrdup(cmd->tmp_pool, matched_path); modified_arg = TRUE; } } else { pr_trace_msg(trace_channel, 9, "no case-insensitive matches found for path '%s'", dst_path); } /* Overwrite the client-given paths. */ if (modified_arg == TRUE) { pr_trace_msg(trace_channel, 9, "replacing %s paths with '%s' and '%s'", (char *) cmd->argv[0], src_path, dst_path); case_replace_link_paths(cmd, proto, src_path, dst_path); } return PR_DECLINED(cmd); } /* Configuration handlers */ /* usage: CaseEngine on|off */ MODRET set_caseengine(cmd_rec *cmd) { int engine; config_rec *c; CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); CHECK_ARGS(cmd, 1); 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] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = engine; return PR_HANDLED(cmd); } /* usage: CaseIgnore on|off|cmd-list */ MODRET set_caseignore(cmd_rec *cmd) { unsigned int argc; int ignore = FALSE; char **argv; config_rec *c; CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR); CHECK_ARGS(cmd, 1); ignore = get_boolean(cmd, 1); c = add_config_param(cmd->argv[0], 2, NULL, NULL); c->flags |= CF_MERGEDOWN_MULTI; c->argv[0] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = 1; if (ignore != -1) { *((unsigned int *) c->argv[0]) = ignore; return PR_HANDLED(cmd); } /* Parse the parameter as a command list. */ argc = cmd->argc-1; argv = (char **) cmd->argv; c->argv[1] = pcalloc(c->pool, sizeof(array_header *)); *((array_header **) c->argv[1]) = pr_expr_create(c->pool, &argc, argv); return PR_HANDLED(cmd); } /* usage: CaseLog path|"none" */ MODRET set_caselog(cmd_rec *cmd) { CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); CHECK_ARGS(cmd, 1); if (pr_fs_valid_path(cmd->argv[1]) < 0) CONF_ERROR(cmd, "must be an absolute path"); add_config_param_str(cmd->argv[0], 1, cmd->argv[1]); return PR_HANDLED(cmd); } /* Initialization functions */ static int case_sess_init(void) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "CaseEngine", FALSE); if (c != NULL && *((unsigned int *) c->argv[0]) == TRUE) { case_engine = TRUE; } if (case_engine == FALSE) { return 0; } c = find_config(main_server->conf, CONF_PARAM, "CaseLog", FALSE); if (c == NULL) { return 0; } if (strncasecmp((char *) c->argv[0], "none", 5) != 0) { int res, xerrno; pr_signals_block(); PRIVS_ROOT res = pr_log_openfile((char *) c->argv[0], &case_logfd, 0660); xerrno = errno; PRIVS_RELINQUISH pr_signals_unblock(); if (res < 0) { pr_log_pri(PR_LOG_NOTICE, MOD_CASE_VERSION ": error opening CaseLog '%s': %s", (char *) c->argv[0], strerror(xerrno)); } } return 0; } /* Module API tables */ static conftable case_conftab[] = { { "CaseEngine", set_caseengine, NULL }, { "CaseIgnore", set_caseignore, NULL }, { "CaseLog", set_caselog, NULL }, { NULL } }; static cmdtable case_cmdtab[] = { { PRE_CMD, C_APPE, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_CWD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_DELE, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_LIST, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_MDTM, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_MKD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_MLSD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_MLST, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_NLST, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_RETR, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_RMD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_RNFR, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_RNTO, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_SITE, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_SIZE, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_STAT, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_STOR, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_XCWD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_XMKD, G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, C_XRMD, G_NONE, case_pre_cmd, TRUE, FALSE }, /* The following are SFTP requests */ { PRE_CMD, "LINK", G_NONE, case_pre_link, TRUE, FALSE }, { PRE_CMD, "LSTAT", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "OPENDIR", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "READLINK", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "REALPATH", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "SETSTAT", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "STAT", G_NONE, case_pre_cmd, TRUE, FALSE }, { PRE_CMD, "SYMLINK", G_NONE, case_pre_link, TRUE, FALSE }, { 0, NULL } }; module case_module = { NULL, NULL, /* Module API version 2.0 */ 0x20, /* Module name */ "case", /* Module configuration handler table */ case_conftab, /* Module command handler table */ case_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization function */ NULL, /* Session initialization function */ case_sess_init, /* Module version */ MOD_CASE_VERSION }; proftpd-mod_case-0.9.1/mod_case.html000066400000000000000000000131211476171011700174120ustar00rootroot00000000000000 ProFTPD module mod_case

ProFTPD module mod_case



The mod_case module is designed to help ProFTPD be case-insensitive, for those sites that may need it (e.g. those that are migrating from a Windows environment or have mounted Windows filesystems).

The mod_case module works by performing two checks on the filename used in FTP commands. First, mod_case will scan the directory to see if there is already a file whose name exactly matches the given filename. If not, mod_case will then looks for any case-insensitive matches.

This module is contained in the mod_case.c file for ProFTPD 1.3.x, and is not compiled by default. Installation instructions are discussed here.

The most current version of mod_case can be found at:

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

Author

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

Directives


CaseEngine

Syntax: CaseEngine on|off
Default: off
Context: server config, <VirtualHost>, <Global>
Module: mod_case
Compatibility: 1.2.9 and later

The CaseEngine directive enables or disables the module's runtime case-matching engine. If it is set to off this module does no case-insensitive checking. Use this directive to disable the module instead of commenting out all mod_case directives.


CaseIgnore

Syntax: CaseIgnore on|off|cmd-list
Default: off
Context: server config, <VirtualHost>, <Global>, <Anonymous>, <Directory>
Module: mod_case
Compatibility: 1.2.9 and later

The CaseIgnore directive is used to enable case-insensitive matching, possibly on a per-FTP command basis. If it is set to off, no case-insensitive matching is performed. If set to on, then case-insensitive matching is performed for all FTP commands that mod_case handles (see below). Otherwise, one can configure a cmd-list, which is a comma-separated list of FTP commands for which mod_case is to do case-insensitive matching.

The mod_case module handles the following FTP commands:

Examples:

  # Enable case-insensitivity for all FTP commands handled by mod_case
  CaseIgnore on

  # Enable case-insensitivity only for downloads
  CaseIgnore RETR

  # Enable case-insensitivity for uploads and downloads
  CaseIgnore APPE,RETR,STOR


CaseLog

Syntax: CaseLog path|"none"
Default: None
Context: server config, <VirtualHost>, <Global>
Module: mod_case
Compatibility: 1.2.9 and later

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

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


Installation

To install mod_case, copy the mod_case.c file into
  proftpd-dir/contrib/
after unpacking the latest proftpd-1.3.x source code. Then follow the usual steps for using third-party modules in proftpd:
  $ ./configure --with-modules=mod_case
  $ make
  $ make install

Alternatively, if your proftpd was compiled with DSO support, you can use the prxs tool to build mod_case as a shared module:

  $ prxs -c -i -d mod_case.c



© Copyright 2004-2021 TJ Saunders
All Rights Reserved


proftpd-mod_case-0.9.1/t/000077500000000000000000000000001476171011700152175ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/000077500000000000000000000000001476171011700157655ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/ProFTPD/000077500000000000000000000000001476171011700172035ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/000077500000000000000000000000001476171011700203055ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/000077500000000000000000000000001476171011700217155ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/mod_case.pm000066400000000000000000002571251476171011700240410ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_case; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; 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 = { caseignore_appe => { order => ++$order, test_class => [qw(forking)], }, caseignore_cwd => { order => ++$order, test_class => [qw(forking)], }, caseignore_cwd_exact_match => { order => ++$order, test_class => [qw(forking)], }, caseignore_cwd_issue5 => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_dele => { order => ++$order, test_class => [qw(forking)], }, caseignore_dele_issue5 => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_mdtm => { order => ++$order, test_class => [qw(forking)], }, caseignore_mkd => { order => ++$order, test_class => [qw(forking)], }, caseignore_mlsd => { order => ++$order, test_class => [qw(forking)], }, caseignore_mlst => { order => ++$order, test_class => [qw(forking)], }, caseignore_retr => { order => ++$order, test_class => [qw(forking)], }, caseignore_retr_issue5 => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_rmd => { order => ++$order, test_class => [qw(forking)], }, caseignore_rnfr => { order => ++$order, test_class => [qw(forking)], }, caseignore_rnto => { order => ++$order, test_class => [qw(forking)], }, caseignore_size => { order => ++$order, test_class => [qw(forking)], }, caseignore_stat => { order => ++$order, test_class => [qw(forking)], }, caseignore_stor => { order => ++$order, test_class => [qw(forking)], }, caseignore_stor_filename_with_spaces => { order => ++$order, test_class => [qw(forking)], }, caseignore_stor_issue5 => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_list => { order => ++$order, test_class => [qw(forking)], }, caseignore_list_filename_with_spaces => { order => ++$order, test_class => [qw(forking)], }, caseignore_list_no_matches => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_list_issue5 => { order => ++$order, test_class => [qw(bug forking)], }, caseignore_nlst => { order => ++$order, test_class => [qw(forking)], }, caseignore_list_extlog_var_r => { order => ++$order, test_class => [qw(forking rootprivs)], }, caseignore_site_chmod => { order => ++$order, test_class => [qw(forking)], }, caseignore_site_chmod_filename_with_spaces => { order => ++$order, test_class => [qw(forking)], }, caseignore_site_chgrp => { order => ++$order, test_class => [qw(forking)], }, caseignore_site_chgrp_filename_with_spaces => { order => ++$order, test_class => [qw(forking)], }, caseignore_cmds_cwd => { order => ++$order, test_class => [qw(forking bug)], }, caseignore_multi_cwds_to_dst => { order => ++$order, test_class => [qw(forking bug)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } # Support functions sub create_test_dir { my $setup = shift; my $sub_dir = shift; mkpath($sub_dir); # Make sure that, if we're running as root, that the sub directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $sub_dir)) { die("Can't set perms on $sub_dir to 0755: $!"); } unless (chown($setup->{uid}, $setup->{gid}, $sub_dir)) { die("Can't set owner of $sub_dir to $setup->{uid}/$setup->{gid}: $!"); } } } sub create_test_file { my $setup = shift; my $test_file = shift; 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 (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } } # Test cases sub caseignore_appe { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 the server to start up sleep(1); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->appe_raw("TeSt.TxT"); unless ($conn) { die("APPE TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello again!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $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->{log_file}, $ex); } sub caseignore_cwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->cwd('SuBdIr'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'CWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Reset to home directory, and try XCWD, too. ($resp_code, $resp_msg) = $client->cwd($setup->{home_dir}); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); ($resp_code, $resp_msg) = $client->xcwd('SuBdIr'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'XCWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current 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->{log_file}, $ex); } sub caseignore_cwd_exact_match { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/test.d/sub.d"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->cwd('test.d/sub.d'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'CWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Reset to home directory, and try XCWD, too. ($resp_code, $resp_msg) = $client->cwd($setup->{home_dir}); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); ($resp_code, $resp_msg) = $client->xcwd('test.d/sub.d'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'XCWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "\"$sub_dir\" is the current 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->{log_file}, $ex); } sub caseignore_cwd_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/TeSt.d/Sub.D"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->cwd('test.d/sub.d'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'CWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Reset to home directory, and try XCWD, too. ($resp_code, $resp_msg) = $client->cwd($setup->{home_dir}); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); ($resp_code, $resp_msg) = $client->xcwd('tEsT.D/sUb.D'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'XCWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current 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->{log_file}, $ex); } sub caseignore_dele { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->dele('TeSt.TxT'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'DELE command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $self->assert(!-f $test_file, test_msg("File $test_file exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_dele_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/SuB.D"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/Test.TXT"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->dele('sub.d/TeSt.TxT'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'DELE command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $self->assert(!-f $test_file, test_msg("File $test_file exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_mdtm { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->mdtm('TeSt.TxT'); my $expected = 213; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '\d+'; $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->{log_file}, $ex); } sub caseignore_mkd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $test_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); # Due to Issue #1639, ProFTPD now treats EEXIST errors for MKDIR # requests as success. my ($resp_code, $resp_msg) = $client->mkd('TeSt.D'); my $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Directory successfully created'; $self->assert(qr/$expected/, $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->xmkd('TeSt.D'); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'Directory successfully created'; $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->{log_file}, $ex); } sub caseignore_mlsd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $test_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->mlsd('TeSt.D'); $self->assert_transfer_ok($resp_code, $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->{log_file}, $ex); } sub caseignore_mlst { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->mlst("TeSt.TxT"); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); }; 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->{log_file}, $ex); } sub caseignore_retr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw("TeSt.TxT"); unless ($conn) { die("RETR TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; while ($conn->read($buf, 25) > 0) { } eval { $conn->close(5) }; sleep(1); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $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->{log_file}, $ex); } sub caseignore_retr_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/TEST.D"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/tEst.tXt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->retr_raw("test.d/TeSt.TxT"); unless ($conn) { die("RETR TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 25); sleep(1); eval { $conn->close() }; my $resp_code = $client->response_code(); my $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->{log_file}, $ex); } sub caseignore_rmd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $test_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->rmd('TeSt.D'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'RMD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); # Create that directory again, for an XRMD test create_test_dir($setup, $test_dir); ($resp_code, $resp_msg) = $client->xrmd('TeSt.D'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'XRMD command successful'; $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->{log_file}, $ex); } sub caseignore_rnfr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->rnfr('TeSt.TxT'); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'File or directory exists, ready for destination name'; $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->{log_file}, $ex); } sub caseignore_rnto { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $src_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$setup->{home_dir}/dst.txt"); create_test_file($setup, $dst_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'off', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->rnfr('TeSt.TxT'); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'File or directory exists, ready for destination name'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); eval { $client->rnto('DsT.tXt') }; unless ($@) { die("RNTO DsT.tXt 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 = 'dst.txt: Rename permission denied'; $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->{log_file}, $ex); } sub caseignore_size { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my ($resp_code, $resp_msg) = $client->size("TeSt.TxT"); my $expected = 213; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = '14'; $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->{log_file}, $ex); } sub caseignore_stat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->stat("TeSt.TxT"); my $expected = 213; $self->assert($expected == $resp_code, "Expected response code $expected, got $resp_code"); }; 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->{log_file}, $ex); } sub caseignore_stor { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $new_file = File::Spec->rel2abs("$setup->{home_dir}/TeSt.TxT"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw("TeSt.TxT"); unless ($conn) { die("STOR TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello again!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # Make sure that the original file exists... $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); # ...and that the new file does not. Unfortunately, we cannot do this # check on MacOSX; its default filesystem is case-insensitive but # case-preserving. Yuck. if ($^O ne 'darwin') { $self->assert(!-f $new_file, test_msg("File '$new_file' exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_stor_filename_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test file.txt"); create_test_file($setup, $test_file); my $new_file = File::Spec->rel2abs("$setup->{home_dir}/TeSt FiLe.TxT"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw("TeSt FiLe.TxT"); unless ($conn) { die("STOR TeSt FiLe.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello again!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # Make sure that the original file exists... $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); # ...and that the new file does not. Unfortunately, we cannot do this # check on MacOSX; its default filesystem is case-insensitive but # case-preserving. Yuck. if ($^O ne 'darwin') { $self->assert(!-f $new_file, test_msg("File '$new_file' exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_stor_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/sUb.D/tEsT.d"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/TeSt.TxT"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->stor_raw("sub.d/test.d/TeSt.TxT"); unless ($conn) { die("STOR TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello again!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; sleep(1); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); $client->quit(); # Make sure that the new file exists... $self->assert(-f $test_file, test_msg("File '$test_file' does not exist as expected")); }; 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->{log_file}, $ex); } sub caseignore_list { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw('-a -l SuBdIr'); unless ($conn) { die("Failed to LIST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { '.' => 1, '..' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex); } sub caseignore_list_filename_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/sub dir"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw('-a -l SuB dIr'); unless ($conn) { die("Failed to LIST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { '.' => 1, '..' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex); } sub caseignore_list_no_matches { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d/test.d"); create_test_dir($setup, $sub_dir); my $subdir_file = File::Spec->rel2abs("$sub_dir/foo.txt"); create_test_file($setup, $subdir_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw('sub.d/test.d'); unless ($conn) { die("Failed to LIST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); if ($ENV{TEST_VERBOSE}) { print STDERR "# LIST:\n$buf\n"; } # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { '.' => 1, '..' => 1, 'foo.txt' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex); } sub caseignore_list_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$tmpdir/SuB.d/tEsT.d"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/TeSt.DAT"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw('-a -l sub.d/test.d'); unless ($conn) { die("Failed to LIST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { '.' => 1, '..' => 1, 'TeSt.DAT' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex); } sub caseignore_nlst { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->nlst_raw('-a -l SuBdIr'); unless ($conn) { die("Failed to NLST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { $res->{$line} = 1; } my $expected = { 'subdir/.' => 1, 'subdir/..' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex); } sub caseignore_list_extlog_var_r { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); create_test_dir($setup, $sub_dir); my $ext_log = File::Spec->rel2abs("$tmpdir/custom.log"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', DefaultRoot => '~', LogFormat => 'custom "%r"', ExtendedLog => "$ext_log DIRS custom", IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my $conn = $client->list_raw('SuBdIr'); unless ($conn) { die("Failed to LIST: " . $client->response_code() . " " . $client->response_msg()); } my $buf; $conn->read($buf, 8192, 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); $self->assert_transfer_ok($resp_code, $resp_msg); # We have to be careful of the fact that readdir returns directory # entries in an unordered fashion. my $res = {}; my $lines = [split(/\n/, $buf)]; foreach my $line (@$lines) { if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) { $res->{$1} = 1; } } my $expected = { '.' => 1, '..' => 1, }; my $ok = 1; my $mismatch = ''; foreach my $name (keys(%$res)) { unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } $self->assert($ok, test_msg("Unexpected name '$mismatch' appeared in LIST data")); }; 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->{log_file}, $ex) if $ex; # Now, read in the ExtendedLog, and see whether the %r variable was # properly written out. eval { if (open(my $fh, "< $ext_log")) { my $line = <$fh>; chomp($line); close($fh); my $expected = 'LIST SuBdIr'; $self->assert($expected eq $line, test_msg("Expected '$expected', got '$line'")); } else { die("Can't read $ext_log: $!"); } }; if ($@) { $ex = $@; } test_cleanup($setup->{log_file}, $ex); } sub caseignore_site_chmod { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->site('CHMOD', '444', 'TeSt.TxT'); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'SITE CHMOD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $file_mode = sprintf("%lo", (stat($test_file))[2] & 07777); $expected = '444'; $self->assert($expected eq $file_mode, test_msg("Expected file mode '$expected', got '$file_mode'")); }; 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->{log_file}, $ex); } sub caseignore_site_chmod_filename_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test file.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->site('CHMOD', '444', 'TeSt FiLe.TxT'); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'SITE CHMOD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $file_mode = sprintf("%lo", (stat($test_file))[2] & 07777); $expected = '444'; $self->assert($expected eq $file_mode, test_msg("Expected file mode '$expected', got '$file_mode'")); }; 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->{log_file}, $ex); } sub caseignore_site_chgrp { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $file_group = (config_get_identity())[1]; my $file_gid = (split(' ', $())[0]; if ($< == 0) { $file_group = $setup->{group}; $file_gid = $setup->{gid}; } my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->site('CHGRP', $file_gid, 'TeSt.TxT'); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'SITE CHGRP command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $test_gid = (stat($test_file))[5]; $expected = $file_gid; $self->assert($expected == $test_gid, test_msg("Expected GID $expected, got $test_gid")); }; 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->{log_file}, $ex); } sub caseignore_site_chgrp_filename_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $file_group = (config_get_identity())[1]; my $file_gid = (split(' ', $())[0]; if ($< == 0) { $file_group = $setup->{group}; $file_gid = $setup->{gid}; } my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test file.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); $client->site('CHGRP', $file_gid, 'TeSt FiLe.TxT'); my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'SITE CHGRP command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); $client->quit(); my $test_gid = (stat($test_file))[5]; $expected = $file_gid; $self->assert($expected == $test_gid, test_msg("Expected GID $expected, got $test_gid")); }; 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->{log_file}, $ex); } sub caseignore_cmds_cwd { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $sub_dir = File::Spec->rel2abs("$tmpdir/subdir"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'APPE,CWD,DELE,LIST,MDTM,MKD,MLSD,MLST,NLST,RETR,RMD,RNFR,RNTO,SIZE,STOR,XCWD,XMKD,XRMD,CD,', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->cwd('SuBdIr'); my $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = 'CWD command successful'; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->pwd(); $expected = 257; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); if ($^O eq 'darwin') { # MacOSX-specific hack to deal with their tmp filesystem $sub_dir = ('/private' . $sub_dir); } $expected = "\"$sub_dir\" is the current 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->{log_file}, $ex); } sub caseignore_multi_cwds_to_dst { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $dst_dir = File::Spec->rel2abs("$tmpdir/foo/bar/baz/quxx/quzz.d"); create_test_dir($setup, $dst_dir); my $test_file = File::Spec->rel2abs("$dst_dir/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0); $client->login($setup->{user}, $setup->{passwd}); $client->cwd("FOO"); $client->cwd("bAr"); $client->cwd("BaZ"); $client->cwd("quXX"); $client->cwd("QuZz.D"); my $conn = $client->appe_raw("TeSt.TxT"); unless ($conn) { die("APPE TeSt.TxT failed: " . $client->response_code() . " " . $client->response_msg()); } my $buf = "Hello again!\n"; $conn->write($buf, length($buf), 25); eval { $conn->close() }; my $resp_code = $client->response_code(); my $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->{log_file}, $ex); } 1; proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/mod_case/000077500000000000000000000000001476171011700234675ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/mod_case/copy.pm000066400000000000000000000356711476171011700250130ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_case::copy; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use POSIX qw(:fcntl_h); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { caseignore_site_copy => { order => ++$order, test_class => [qw(forking mod_copy)], }, caseignore_site_copy_issue5 => { order => ++$order, test_class => [qw(bug forking mod_copy)], }, caseignore_site_cpfr_cpto => { order => ++$order, test_class => [qw(forking mod_copy)], }, caseignore_site_cpfr_cpto_overwrite => { order => ++$order, test_class => [qw(forking mod_copy)], }, caseignore_site_cpfr_cpto_filenames_with_spaces => { order => ++$order, test_class => [qw(forking mod_copy)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } # Support functions sub create_test_dir { my $setup = shift; my $sub_dir = shift; mkpath($sub_dir); # Make sure that, if we're running as root, that the sub directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $sub_dir)) { die("Can't set perms on $sub_dir to 0755: $!"); } unless (chown($setup->{uid}, $setup->{gid}, $sub_dir)) { die("Can't set owner of $sub_dir to $setup->{uid}/$setup->{gid}: $!"); } } } sub create_test_file { my $setup = shift; my $test_file = shift; 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 (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } } # Test cases sub caseignore_site_copy { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $src_file = File::Spec->rel2abs("$setup->{home_dir}/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$setup->{home_dir}/src.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->site('COPY', 'SrC.txt', 'dst.txt'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "SITE COPY command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); unless (-f $dst_file) { die("File $dst_file does not exist as expected"); } }; 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->{log_file}, $ex); } sub caseignore_site_copy_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/TeSt.D/sUb.d"); create_test_dir($setup, $test_dir); my $src_file = File::Spec->rel2abs("$test_dir/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$test_dir/dst.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->site('COPY', 'test.d/sub.d/SrC.txt', 'test.d/sub.d/dst.txt'); my $expected = 200; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "SITE COPY command successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); unless (-f $dst_file) { die("File $dst_file does not exist as expected"); } }; 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->{log_file}, $ex); } sub caseignore_site_cpfr_cpto { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $src_file = File::Spec->rel2abs("$setup->{home_dir}/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$setup->{home_dir}/src.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->site('CPFR', 'SrC.txt'); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "File or directory exists, ready for destination name"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->site('CPTO', 'dst.txt'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Copy successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); unless (-f $dst_file) { die("File $dst_file does not exist as expected"); } }; 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->{log_file}, $ex); } sub caseignore_site_cpfr_cpto_overwrite { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $src_file = File::Spec->rel2abs("$setup->{home_dir}/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$setup->{home_dir}/dst.txt"); create_test_file($setup, $dst_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->site('CPFR', 'SrC.tXt'); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "File or directory exists, ready for destination name"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->site('CPTO', 'DsT.tXt'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Copy successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); unless (-f $dst_file) { die("File $dst_file does not exist as expected"); } }; 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->{log_file}, $ex); } sub caseignore_site_cpfr_cpto_filenames_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $src_file = File::Spec->rel2abs("$setup->{home_dir}/src file.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$setup->{home_dir}/dst file.txt"); create_test_file($setup, $dst_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, }, }; 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 { my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port); $client->login($setup->{user}, $setup->{passwd}); my ($resp_code, $resp_msg) = $client->site('CPFR', 'SrC fIlE.tXt'); my $expected = 350; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "File or directory exists, ready for destination name"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); ($resp_code, $resp_msg) = $client->site('CPTO', 'DsT fIlE.tXt'); $expected = 250; $self->assert($expected == $resp_code, test_msg("Expected response code $expected, got $resp_code")); $expected = "Copy successful"; $self->assert($expected eq $resp_msg, test_msg("Expected response message '$expected', got '$resp_msg'")); unless (-f $dst_file) { die("File $dst_file does not exist as expected"); } }; 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->{log_file}, $ex); } 1; proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/mod_case/sftp.pm000066400000000000000000002037561476171011700250160ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_case::sftp; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use POSIX qw(:fcntl_h); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { caseignore_sftp_realpath => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_lstat => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_setstat => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_opendir => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_stat => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_readlink => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_symlink_src => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_symlink_src_issue5 => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_symlink_dst => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_symlink_dst_issue5 => { order => ++$order, test_class => [qw(bug forking mod_sftp sftp)], }, caseignore_sftp_download => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_upload => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_mkdir => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_rmdir => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_remove => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_rename => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_rename_overwrite => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, caseignore_sftp_rename_filenames_with_spaces => { order => ++$order, test_class => [qw(forking mod_sftp sftp)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { 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}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/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: $!"); } } # Support functions sub create_test_dir { my $setup = shift; my $sub_dir = shift; mkpath($sub_dir); # Make sure that, if we're running as root, that the sub directory has # permissions/privs set for the account we create if ($< == 0) { unless (chmod(0755, $sub_dir)) { die("Can't set perms on $sub_dir to 0755: $!"); } unless (chown($setup->{uid}, $setup->{gid}, $sub_dir)) { die("Can't set owner of $sub_dir to $setup->{uid}/$setup->{gid}: $!"); } } } sub create_test_file { my $setup = shift; my $test_file = shift; 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 (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } } # Test cases sub caseignore_sftp_realpath { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $sub_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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 $cwd = $sftp->realpath('TeSt.D'); unless ($cwd) { my ($err_code, $err_name) = $sftp->error(); die("Can't get real path for 'TeSt.D': [$err_name] ($err_code)"); } if ($^O eq 'darwin') { # MacOSX-specific hack dealing with their tmp filesystem handling $sub_dir = ('/private' . $sub_dir); } my $expected = $sub_dir; $self->assert($expected eq $cwd, test_msg("Expected cwd '$expected', got '$cwd'")); $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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_lstat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $test_size = (stat($test_file))[7]; my $test_symlink = File::Spec->rel2abs("$tmpdir/test.lnk"); unless (symlink($test_file, $test_symlink)) { die("Can't symlink $test_symlink to $test_file: $!"); } my $test_symlink_size = (lstat($test_symlink))[7]; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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 $attrs = $sftp->stat('TeSt.LnK', 0); unless ($attrs) { my ($err_code, $err_name) = $sftp->error(); die("FXP_LSTAT TeSt.LnK failed: [$err_name] ($err_code)"); } my $expected = $test_symlink_size; my $file_size = $attrs->{size}; $self->assert(qr/\d+/, $file_size, test_msg("Expected symlink size '$expected', got '$file_size'")); $expected = $setup->{uid}; my $file_uid = $attrs->{uid}; $self->assert($expected == $file_uid, test_msg("Expected UID '$expected', got '$file_uid'")); $expected = $setup->{gid}; my $file_gid = $attrs->{gid}; $self->assert($expected == $file_gid, test_msg("Expected GID '$expected', got '$file_gid'")); $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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_setstat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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.TxT', atime => 0, mtime => 0, ); unless ($res) { my ($err_code, $err_name) = $sftp->error(); die("Can't setstat TeSt.TxT: [$err_name] ($err_code)"); } my $attrs = $sftp->stat('test.txt'); unless ($attrs) { my ($err_code, $err_name) = $sftp->error(); die("Can't stat test.txt: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); my $expected = 0; my $file_atime = $attrs->{atime}; $self->assert($expected == $file_atime, test_msg("Expected atime '$expected', got '$file_atime'")); my $file_mtime = $attrs->{mtime}; $self->assert($expected == $file_mtime, test_msg("Expected mtime '$expected', got '$file_mtime'")); }; 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->{log_file}, $ex); } sub caseignore_sftp_opendir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $sub_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $sub_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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.D'); unless ($dir) { my ($err_code, $err_name) = $sftp->error(); die("Can't open directory 'TeSt.D': [$err_name] ($err_code)"); } my $res = {}; my $file = $dir->read(); while ($file) { $res->{$file->{name}} = $file; $file = $dir->read(); } my $expected = { '.' => 1, '..' => 1, }; # To issue the FXP_CLOSE, we have to explicitly destroy the dirhandle $dir = undef; $sftp = undef; $ssh2->disconnect(); my $ok = 1; my $mismatch = ''; my $seen = []; foreach my $name (keys(%$res)) { push(@$seen, $name); unless (defined($expected->{$name})) { $mismatch = $name; $ok = 0; last; } } unless ($ok) { die("Unexpected name '$mismatch' appeared in READDIR data") } # Now remove from $expected all of the paths we saw; if there are # any entries remaining in $expected, something went wrong. foreach my $name (@$seen) { delete($expected->{$name}); } my $remaining = scalar(keys(%$expected)); $self->assert(0 == $remaining, test_msg("Expected 0 remaining, got $remaining")); }; 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->{log_file}, $ex); } sub caseignore_sftp_stat { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $test_size = (stat($test_file))[7]; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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 $attrs = $sftp->stat('TeSt.TxT', 1); unless ($attrs) { my ($err_code, $err_name) = $sftp->error(); die("Can't stat TeSt.TxT: [$err_name] ($err_code)"); } my $expected = $test_size; my $file_size = $attrs->{size}; $self->assert($expected == $file_size, test_msg("Expected size '$expected', got '$file_size'")); $expected = $setup->{uid}; my $file_uid = $attrs->{uid}; $self->assert($expected == $file_uid, test_msg("Expected UID '$expected', got '$file_uid'")); $expected = $setup->{gid}; my $file_gid = $attrs->{gid}; $self->assert($expected == $file_gid, test_msg("Expected GID '$expected', got '$file_gid'")); $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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_readlink { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $test_symlink = File::Spec->rel2abs("$tmpdir/test.lnk"); unless (symlink($test_file, $test_symlink)) { die("Can't symlink $test_symlink to $test_file: $!"); } my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { my $ssh2 = Net::SSH2->new(); sleep(1); 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 $path = $sftp->readlink('TeSt.LnK'); unless ($path) { my ($err_code, $err_name) = $sftp->error(); die("Can't readlink TeSt.LnK: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert($test_file eq $path, test_msg("Expected path '$test_file', got '$path'")); }; 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->{log_file}, $ex); } sub caseignore_sftp_symlink_src { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $test_symlink = File::Spec->rel2abs("$tmpdir/test.lnk"); # Not sure why, but Net::SSH2::SFTP seems to need a little more time # for this test case. my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', TimeoutIdle => 5, IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('TeSt.TxT', 'test.lnk'); unless (defined($res)) { my ($err_code, $err_name) = $sftp->error(); die("Can't symlink test.lnk to TeSt.TxT: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert(-l $test_symlink, test_msg("Symlink $test_symlink does not exist as expected")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 1) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_symlink_src_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_dir = File::Spec->rel2abs("$tmpdir/tEsT.d/SuB.d"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/test.txt"); create_test_file($setup, $test_file); my $test_symlink = File::Spec->rel2abs("$test_dir/test.lnk"); # Not sure why, but Net::SSH2::SFTP seems to need a little more time # for this test case. my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', TimeoutIdle => 5, IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('test.d/sub.d/TeSt.TxT', 'test.d/sub.d/test.lnk'); unless (defined($res)) { my ($err_code, $err_name) = $sftp->error(); die("Can't symlink test.lnk to TeSt.TxT: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert(-l $test_symlink, test_msg("Symlink $test_symlink does not exist as expected")); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 1) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_symlink_dst { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); create_test_file($setup, $test_file); my $test_symlink = File::Spec->rel2abs("$tmpdir/test.lnk"); if (open(my $fh, "> $test_symlink")) { close($fh); # 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 (chown($setup->{uid}, $setup->{gid}, $test_symlink)) { die("Can't set owner of $test_symlink to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_symlink: $!"); } # Not sure why, but Net::SSH2::SFTP seems to need a little more time # for this test case. my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', TimeoutIdle => 5, IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('test.txt', 'TeSt.LnK'); if ($res) { die("Symlink of TeSt.LnK to test.txt succeeded unexpectedly"); } $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 1) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_symlink_dst_issue5 { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_dir = File::Spec->rel2abs("$tmpdir/tEsT.d/SuB.d"); create_test_dir($setup, $test_dir); my $test_file = File::Spec->rel2abs("$test_dir/test.txt"); create_test_file($setup, $test_file); my $test_symlink = File::Spec->rel2abs("$test_dir/test.lnk"); if (open(my $fh, "> $test_symlink")) { close($fh); # 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 (chown($setup->{uid}, $setup->{gid}, $test_symlink)) { die("Can't set owner of $test_symlink to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_symlink: $!"); } # Not sure why, but Net::SSH2::SFTP seems to need a little more time # for this test case. my $timeout_idle = 15; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', TimeoutIdle => 5, IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('test.d/sub.d/test.txt', 'test.d/sub.d/TeSt.LnK'); if ($res) { die("Symlink of TeSt.LnK to test.txt succeeded unexpectedly"); } $sftp = undef; $ssh2->disconnect(); }; if ($@) { $ex = $@; } $wfh->print("done\n"); $wfh->flush(); } else { eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 1) }; if ($@) { warn($@); exit 1; } exit 0; } # Stop server server_stop($setup->{pid_file}); $self->assert_child_ok($pid); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_download { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $test_sz = -s $test_file; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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.TxT', O_RDONLY); unless ($fh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open TeSt.TxT: [$err_name] ($err_code)"); } my $buf; my $size = 0; my $res = $fh->read($buf, 8192); while ($res) { $size += $res; $res = $fh->read($buf, 8192); } # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $fh = undef; $sftp = undef; $ssh2->disconnect(); $self->assert($test_sz == $size, test_msg("Expected size $test_sz, got $size")); }; 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->{log_file}, $ex); } sub caseignore_sftp_upload { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); if (open(my $fh, "> $test_file")) { close($fh); # 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 (chown($setup->{uid}, $setup->{gid}, $test_file)) { die("Can't set owner of $test_file to $setup->{uid}/$setup->{gid}: $!"); } } } else { die("Can't open $test_file: $!"); } my $test_sz = 32; my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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.TxT', O_WRONLY|O_CREAT, 0644); unless ($fh) { my ($err_code, $err_name) = $sftp->error(); die("Can't open TeSt.TxT: [$err_name] ($err_code)"); } print $fh "ABCD" x 8; # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle $fh = undef; $sftp = undef; $ssh2->disconnect(); my $size = -s $test_file; $self->assert($size == $test_sz, test_msg("Expected size $test_sz, got $size")); }; 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->{log_file}, $ex); } sub caseignore_sftp_mkdir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $test_dir); my $bad_dir = File::Spec->rel2abs("$setup->{home_dir}/TeSt.D"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AuthOrder => 'mod_auth_file.c', AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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"); } # Due to Issue #1639, ProFTPD now treats EEXIST errors for MKDIR # requests as success. my $res = $sftp->mkdir('TeSt.D'); unless ($res) { my ($err_code, $err_name) = $sftp->error(); die("Can't mkdir TeSt.D: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); if ($^O ne 'darwin') { # Avoid this check on Mac OSX, due to its default case-insensitve # (but case-preserving) filesystem. Yuck. $self->assert(!-d $bad_dir, test_msg("Directory $bad_dir exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_rmdir { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d"); create_test_dir($setup, $test_dir); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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.D'); unless ($res) { my ($err_code, $err_name) = $sftp->error(); die("Can't rmdir TeSt.D: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert(!-d $test_dir, test_msg("Directory $test_dir exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_remove { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt"); create_test_file($setup, $test_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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.TxT'); unless ($res) { my ($err_code, $err_name) = $sftp->error(); die("Can't remove TeSt.TxT: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert(!-f $test_file, test_msg("File $test_file exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_rename { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $src_file = File::Spec->rel2abs("$tmpdir/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$tmpdir/dst.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20 sftp:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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 $flags = (0x01|0x02|0x04); my $res = $sftp->rename('SrC.tXt', 'dst.txt', $flags); unless (defined($res)) { my ($err_code, $err_name) = $sftp->error(); die("Can't rename SrC.tXt to dst.txt: [$err_name] ($err_code)"); } $sftp = undef; $ssh2->disconnect(); $self->assert(-f $dst_file, test_msg("File '$dst_file' does not exist as expected")); $self->assert(!-f $src_file, test_msg("File '$src_file' exists 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); test_cleanup($setup->{log_file}, $ex); } sub caseignore_sftp_rename_overwrite { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $src_file = File::Spec->rel2abs("$tmpdir/src.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$tmpdir/dst.txt"); create_test_file($setup, $dst_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('SrC.tXt', 'dSt.TxT'); if ($res) { # We expect this to fail because libssh2 uses SFTP version 3, and that # version does not specify RENAME flags for overwriting an existing # file. die("RENAME of 'SrC.tXt' to 'dSt.TxT' succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_FILE_ALREADY_EXISTS'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); }; 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->{log_file}, $ex); } sub caseignore_sftp_rename_filenames_with_spaces { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $rsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_rsa_key"); my $dsa_host_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/ssh_host_dsa_key"); my $src_file = File::Spec->rel2abs("$tmpdir/src file.txt"); create_test_file($setup, $src_file); my $dst_file = File::Spec->rel2abs("$tmpdir/dst file.txt"); create_test_file($setup, $dst_file); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, '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 { my $ssh2 = Net::SSH2->new(); sleep(1); 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('SrC fIlE.tXt', 'dSt FiLe.TxT'); if ($res) { # We expect this to fail because libssh2 uses SFTP version 3, and that # version does not specify RENAME flags for overwriting an existing # file. die("RENAME of 'SrC fIlE.tXt' to 'dSt FiLe.TxT' succeeded unexpectedly"); } my ($err_code, $err_name) = $sftp->error(); my $expected = 'SSH_FX_FILE_ALREADY_EXISTS'; $self->assert($expected eq $err_name, test_msg("Expected error name '$expected', got '$err_name'")); }; 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->{log_file}, $ex); } 1; proftpd-mod_case-0.9.1/t/lib/ProFTPD/Tests/Modules/mod_case/tls.pm000066400000000000000000000066641476171011700246430ustar00rootroot00000000000000package ProFTPD::Tests::Modules::mod_case::tls; use lib qw(t/lib); use base qw(ProFTPD::TestSuite::Child); use strict; use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; use POSIX qw(:fcntl_h); use ProFTPD::TestSuite::FTP; use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite); $| = 1; my $order = 0; my $TESTS = { caseignore_tls_retr => { order => ++$order, test_class => [qw(forking mod_tls)], }, }; sub new { return shift()->SUPER::new(@_); } sub list_tests { return testsuite_get_runnable_tests($TESTS); } sub caseignore_tls_retr { my $self = shift; my $tmpdir = $self->{tmpdir}; my $setup = test_setup($tmpdir, 'case'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_tls/ca-cert.pem"); my $dst_file = File::Spec->rel2abs("$tmpdir/dst.txt"); my $config = { PidFile => $setup->{pid_file}, ScoreboardFile => $setup->{scoreboard_file}, SystemLog => $setup->{log_file}, TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 case:20', AuthUserFile => $setup->{auth_user_file}, AuthGroupFile => $setup->{auth_group_file}, AllowOverwrite => 'on', AllowStoreRestart => 'on', IfModules => { 'mod_case.c' => { CaseEngine => 'on', CaseIgnore => 'on', CaseLog => $setup->{log_file}, }, 'mod_delay.c' => { DelayEngine => 'off', }, 'mod_tls.c' => { TLSEngine => 'on', TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, TLSOptions => 'NoSessionReuseRequired', }, }, }; 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::FTPSSL; my $ex; # Fork child $self->handle_sigchld(); defined(my $pid = fork()) or die("Can't fork: $!"); if ($pid) { eval { # Give the server a chance to start up sleep(1); my $client = Net::FTPSSL->new('127.0.0.1', Encryption => 'E', Port => $port, ); unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } unless ($client->binary()) { die("Can't set transfer mode to binary: " . $client->last_message()); } unless ($client->get('CaSe.CoNf', $dst_file)) { die("Can't download 'CaSe.CoNf' to '$dst_file': " . $client->last_message()); } $client->quit(); $self->assert(-f $dst_file, test_msg("File $dst_file does not exist as expected")); }; 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->{log_file}, $ex); } 1; proftpd-mod_case-0.9.1/t/modules/000077500000000000000000000000001476171011700166675ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/modules/mod_case.t000066400000000000000000000002641476171011700206300ustar00rootroot00000000000000#!/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_case"); proftpd-mod_case-0.9.1/t/modules/mod_case/000077500000000000000000000000001476171011700204415ustar00rootroot00000000000000proftpd-mod_case-0.9.1/t/modules/mod_case/copy.t000066400000000000000000000002721476171011700216010ustar00rootroot00000000000000#!/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_case::copy"); proftpd-mod_case-0.9.1/t/modules/mod_case/sftp.t000066400000000000000000000002721476171011700216030ustar00rootroot00000000000000#!/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_case::sftp"); proftpd-mod_case-0.9.1/t/modules/mod_case/tls.t000066400000000000000000000002711476171011700214300ustar00rootroot00000000000000#!/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_case::tls"); proftpd-mod_case-0.9.1/tests.pl000066400000000000000000000054371476171011700164640ustar00rootroot00000000000000#!/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}/tests/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_case.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_case/copy.t' => { order => ++$order, test_class => [qw(mod_case mod_copy)], }, 't/modules/mod_case/sftp.t' => { order => ++$order, test_class => [qw(mod_case mod_sftp)], }, 't/modules/mod_case/tls.t' => { order => ++$order, test_class => [qw(mod_case mod_tls)], }, }; 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 <