.
*
* -----DO NOT EDIT BELOW THIS LINE-----
* $Archive: mod_vroot.a $
*/
#include "mod_vroot.h"
#include "privs.h"
#include "alias.h"
#include "path.h"
#include "fsio.h"
int vroot_logfd = -1;
unsigned int vroot_opts = 0;
module vroot_module;
static int vroot_engine = FALSE;
static const char *trace_channel = "vroot";
#if PROFTPD_VERSION_NUMBER >= 0x0001030407
static int vroot_use_mkdtemp = FALSE;
#endif /* ProFTPD 1.3.4c or later */
static int handle_vrootaliases(void) {
config_rec *c;
pool *tmp_pool = NULL;
/* Handle any VRootAlias settings. */
tmp_pool = make_sub_pool(session.pool);
pr_pool_tag(tmp_pool, "VRootAlias pool");
c = find_config(main_server->conf, CONF_PARAM, "VRootAlias", FALSE);
while (c != NULL) {
char src_path[PR_TUNABLE_PATH_MAX+1], dst_path[PR_TUNABLE_PATH_MAX+1];
const char *ptr;
pr_signals_handle();
/* XXX Note that by using vroot_path_lookup(), we assume a POST_CMD
* invocation. Looks like VRootAlias might end up being incompatible
* with VRootServerRoot.
*/
memset(src_path, '\0', sizeof(src_path));
ptr = c->argv[0];
/* Check for any expandable variables. */
ptr = path_subst_uservar(tmp_pool, &ptr);
sstrncpy(src_path, ptr, sizeof(src_path)-1);
vroot_path_clean(src_path);
ptr = c->argv[1];
/* Check for any expandable variables. */
ptr = path_subst_uservar(tmp_pool, &ptr);
ptr = dir_best_path(tmp_pool, ptr);
vroot_path_lookup(NULL, dst_path, sizeof(dst_path)-1, ptr,
VROOT_LOOKUP_FL_NO_ALIAS, NULL);
if (vroot_alias_add(dst_path, src_path) < 0) {
/* Make a slightly better log message when there is an alias collision. */
if (errno == EEXIST) {
(void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
"VRootAlias already configured for '%s', ignoring bad alias",
(char *) c->argv[1]);
} else {
(void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
"error stashing VRootAlias '%s': %s", dst_path, strerror(errno));
}
} else {
(void) pr_log_writefile(vroot_logfd, MOD_VROOT_VERSION,
"aliased '%s' to real path '%s'", dst_path, src_path);
}
c = find_config_next(c, c->next, CONF_PARAM, "VRootAlias", FALSE);
}
destroy_pool(tmp_pool);
return 0;
}
/* Configuration handlers
*/
/* usage: VRootAlias src-path dst-path */
MODRET set_vrootalias(cmd_rec *cmd) {
config_rec *c;
CHECK_ARGS(cmd, 2);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (pr_fs_valid_path(cmd->argv[1]) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "source path '", cmd->argv[1],
"' is not an absolute path", NULL));
}
c = add_config_param_str(cmd->argv[0], 2, cmd->argv[1], cmd->argv[2]);
/* Set this flag in order to allow mod_ifsession to work properly with
* multiple VRootAlias directives.
*/
c->flags |= CF_MERGEDOWN_MULTI;
return PR_HANDLED(cmd);
}
/* usage: VRootEngine on|off */
MODRET set_vrootengine(cmd_rec *cmd) {
int engine = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
engine = get_boolean(cmd, 1);
if (engine == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
}
/* usage: VRootLog path|"none" */
MODRET set_vrootlog(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (pr_fs_valid_path(cmd->argv[1]) < 0) {
CONF_ERROR(cmd, "must be an absolute path");
}
(void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return PR_HANDLED(cmd);
}
/* usage: VRootOptions opt1 opt2 ... optN */
MODRET set_vrootoptions(cmd_rec *cmd) {
config_rec *c = NULL;
register unsigned int i;
unsigned int opts = 0U;
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
c = add_config_param(cmd->argv[0], 1, NULL);
for (i = 1; i < cmd->argc; i++) {
if (strcasecmp(cmd->argv[i], "AllowSymlinks") == 0) {
opts |= VROOT_OPT_ALLOW_SYMLINKS;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown VRootOption: '",
cmd->argv[i], "'", NULL));
}
}
c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
*((unsigned int *) c->argv[0]) = opts;
return PR_HANDLED(cmd);
}
/* usage: VRootServerRoot path */
MODRET set_vrootserverroot(cmd_rec *cmd) {
struct stat st;
config_rec *c;
char *path;
size_t pathlen;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
path = cmd->argv[1];
if (pr_fs_valid_path(path) < 0) {
CONF_ERROR(cmd, "must be an absolute path");
}
if (stat(path, &st) < 0) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "error checking '", path, "': ",
strerror(errno), NULL));
}
if (!S_ISDIR(st.st_mode)) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", path, "' is not a directory",
NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
/* Make sure the configured path has a trailing path separater ('/').
* This is important.
*/
pathlen = strlen(path);
if (path[pathlen - 1] != '/') {
c->argv[0] = pstrcat(c->pool, path, "/", NULL);
} else {
c->argv[0] = pstrdup(c->pool, path);
}
return PR_HANDLED(cmd);
}
/* Command handlers
*/
static const char *vroot_cmd_fixup_path(cmd_rec *cmd, const char *key,
int use_best_path) {
const char *path;
char *real_path = NULL;
path = pr_table_get(cmd->notes, key, NULL);
if (path != NULL) {
if (use_best_path == TRUE) {
/* Only needed for mod_sftp sessions, to do what mod_xfer does for FTP
* commands, but in a way that does not require mod_sftp changes.
* Probably too clever.
*/
path = dir_best_path(cmd->pool, path);
}
if (*path == '/') {
const char *base_path;
base_path = vroot_path_get_base(cmd->tmp_pool, NULL);
real_path = pdircat(cmd->pool, base_path, path, NULL);
vroot_path_clean(real_path);
} else {
real_path = vroot_realpath(cmd->pool, path, VROOT_REALPATH_FL_ABS_PATH);
}
pr_trace_msg(trace_channel, 17,
"fixed up '%s' path in command %s; was '%s', now '%s'", key,
(char *) cmd->argv[0], path, real_path);
pr_table_set(cmd->notes, key, real_path, 0);
}
return real_path;
}
MODRET vroot_pre_scp_retr(cmd_rec *cmd) {
const char *key, *proto, *real_path;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a PRE_CMD handler, we only run for SCP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "scp") != 0) {
return PR_DECLINED(cmd);
}
/* Unlike SFTP sessions, mod_sftp does NOT set these cmd->notes for SCP
* sessions before doing the PRE_CMD dispatching. So we do it ourselves,
* pre-emptively, before using our other machinery.
*/
key = "mod_xfer.retr-path";
(void) pr_table_add(cmd->notes, key, pstrdup(cmd->pool, cmd->arg), 0);
real_path = vroot_cmd_fixup_path(cmd, key, TRUE);
if (real_path != NULL) {
/* In addition, for SCP sessions, we modify cmd->arg as well, for
* mod_sftp's benefit.
*/
cmd->arg = (char *) real_path;
}
return PR_DECLINED(cmd);
}
MODRET vroot_pre_sftp_retr(cmd_rec *cmd) {
const char *key, *proto, *real_path;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a PRE_CMD handler, we only run for SFTP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "sftp") != 0) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.retr-path";
real_path = vroot_cmd_fixup_path(cmd, key, TRUE);
if (real_path != NULL) {
/* In addition, for SFTP sessions, we modify cmd->arg as well, for
* mod_sftp's benefit.
*/
cmd->arg = (char *) real_path;
}
return PR_DECLINED(cmd);
}
MODRET vroot_post_sftp_retr(cmd_rec *cmd) {
const char *key, *path, *proto;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a POST_CMD handler, we only run for SFTP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "sftp") != 0) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.retr-path";
path = pr_table_get(cmd->notes, key, NULL);
if (path != NULL) {
/* In addition, for SFTP sessions, we modify session.xfer.path as well,
* for mod_xfer's benefit in TransferLog entries.
*/
session.xfer.path = pstrdup(session.xfer.p, path);
}
return PR_DECLINED(cmd);
}
MODRET vroot_log_retr(cmd_rec *cmd) {
const char *key;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.retr-path";
(void) vroot_cmd_fixup_path(cmd, key, FALSE);
return PR_DECLINED(cmd);
}
MODRET vroot_pre_scp_stor(cmd_rec *cmd) {
const char *key, *proto, *real_path;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a PRE_CMD handler, we only run for SCP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "scp") != 0) {
return PR_DECLINED(cmd);
}
/* Unlike SFTP sessions, mod_sftp does NOT set these cmd->notes for SCP
* sessions before doing the PRE_CMD dispatching. So we do it ourselves,
* pre-emptively, before using our other machinery.
*/
key = "mod_xfer.store-path";
(void) pr_table_add(cmd->notes, key, pstrdup(cmd->pool, cmd->arg), 0);
real_path = vroot_cmd_fixup_path(cmd, key, TRUE);
if (real_path != NULL) {
/* In addition, for SCP sessions, we modify cmd->arg as well, for
* mod_sftp's benefit.
*/
cmd->arg = (char *) real_path;
}
return PR_DECLINED(cmd);
}
MODRET vroot_pre_sftp_stor(cmd_rec *cmd) {
const char *key, *proto, *real_path;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a PRE_CMD handler, we only run for SFTP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "sftp") != 0) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.store-path";
real_path = vroot_cmd_fixup_path(cmd, key, TRUE);
if (real_path != NULL) {
/* In addition, for SFTP sessions, we modify cmd->arg as well, for
* mod_sftp's benefit.
*/
cmd->arg = (char *) real_path;
}
return PR_DECLINED(cmd);
}
MODRET vroot_post_sftp_stor(cmd_rec *cmd) {
const char *key, *path, *proto;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
/* As a POST_CMD handler, we only run for SFTP sessions. */
proto = pr_session_get_protocol(0);
if (strcmp(proto, "sftp") != 0) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.store-path";
path = pr_table_get(cmd->notes, key, NULL);
if (path != NULL) {
/* In addition, for SFTP sessions, we modify session.xfer.path as well,
* for mod_xfer's benefit in TransferLog entries.
*/
session.xfer.path = pstrdup(session.xfer.p, path);
}
return PR_DECLINED(cmd);
}
MODRET vroot_log_stor(cmd_rec *cmd) {
const char *key;
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
key = "mod_xfer.store-path";
(void) vroot_cmd_fixup_path(cmd, key, FALSE);
return PR_DECLINED(cmd);
}
MODRET vroot_pre_mkd(cmd_rec *cmd) {
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
#if PROFTPD_VERSION_NUMBER >= 0x0001030407
vroot_use_mkdtemp = pr_fsio_set_use_mkdtemp(FALSE);
#endif /* ProFTPD 1.3.4c or later */
return PR_DECLINED(cmd);
}
MODRET vroot_post_mkd(cmd_rec *cmd) {
if (vroot_engine == FALSE ||
session.chroot_path == NULL) {
return PR_DECLINED(cmd);
}
#if PROFTPD_VERSION_NUMBER >= 0x0001030407
pr_fsio_set_use_mkdtemp(vroot_use_mkdtemp);
#endif /* ProFTPD 1.3.4c or later */
return PR_DECLINED(cmd);
}
MODRET vroot_post_pass(cmd_rec *cmd) {
if (vroot_engine == FALSE) {
return PR_DECLINED(cmd);
}
/* If not chrooted, umount our vroot FS. */
if (session.chroot_path == NULL) {
pr_fs_t *fs;
fs = pr_unmount_fs("/", "vroot");
if (fs != NULL) {
destroy_pool(fs->fs_pool);
pr_log_debug(DEBUG5, MOD_VROOT_VERSION ": vroot unmounted");
pr_fs_setcwd(pr_fs_getvwd());
pr_fs_clear_cache();
} else {
pr_log_debug(DEBUG2, MOD_VROOT_VERSION
": error unmounting vroot: %s", strerror(errno));
}
} else {
config_rec *c;
/* Otherwise, lookup and process any VRootOptions. */
c = find_config(main_server->conf, CONF_PARAM, "VRootOptions", FALSE);
if (c != NULL) {
vroot_opts = *((unsigned int *) c->argv[0]);
}
/* XXX This needs to be in the PRE_CMD PASS handler, as when
* VRootServer is used, so that a real chroot(2) occurs.
*/
handle_vrootaliases();
}
return PR_DECLINED(cmd);
}
/* Event listeners
*/
static void vroot_chroot_ev(const void *event_data, void *user_data) {
pr_fs_t *fs = NULL;
int *use_vroot = NULL;
use_vroot = get_param_ptr(main_server->conf, "VRootEngine", FALSE);
if (use_vroot == NULL ||
*use_vroot == FALSE) {
vroot_engine = FALSE;
return;
}
/* First, make sure that we have not already registered our FS object. */
fs = pr_unmount_fs("/", "vroot");
if (fs != NULL) {
destroy_pool(fs->fs_pool);
}
fs = pr_register_fs(main_server->pool, "vroot", "/");
if (fs == NULL) {
pr_log_debug(DEBUG3, MOD_VROOT_VERSION ": error registering fs: %s",
strerror(errno));
return;
}
pr_log_debug(DEBUG5, MOD_VROOT_VERSION ": vroot registered");
/* Add the module's custom FS callbacks here. This module does not
* provide callbacks for the following (as they are unnecessary):
* close(), read(), write(), and lseek().
*/
fs->stat = vroot_fsio_stat;
fs->lstat = vroot_fsio_lstat;
fs->rename = vroot_fsio_rename;
fs->unlink = vroot_fsio_unlink;
fs->open = vroot_fsio_open;
#if PROFTPD_VERSION_NUMBER < 0x0001030603
fs->creat = vroot_fsio_creat;
#endif /* ProFTPD 1.3.6rc2 or earlier */
fs->link = vroot_fsio_link;
fs->readlink = vroot_fsio_readlink;
fs->symlink = vroot_fsio_symlink;
fs->truncate = vroot_fsio_truncate;
fs->chmod = vroot_fsio_chmod;
fs->chown = vroot_fsio_chown;
#if PROFTPD_VERSION_NUMBER >= 0x0001030407
fs->lchown = vroot_fsio_lchown;
#endif /* ProFTPD 1.3.4c or later */
fs->chdir = vroot_fsio_chdir;
fs->chroot = vroot_fsio_chroot;
fs->utimes = vroot_fsio_utimes;
fs->opendir = vroot_fsio_opendir;
fs->readdir = vroot_fsio_readdir;
fs->closedir = vroot_fsio_closedir;
fs->mkdir = vroot_fsio_mkdir;
fs->rmdir = vroot_fsio_rmdir;
vroot_engine = TRUE;
}
static void vroot_exit_ev(const void *event_data, void *user_data) {
vroot_alias_free();
vroot_fsio_free();
}
/* Initialization routines
*/
static int vroot_sess_init(void) {
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "VRootLog", FALSE);
if (c != NULL) {
const char *path;
path = c->argv[0];
if (strcasecmp(path, "none") != 0) {
int res, xerrno;
PRIVS_ROOT
res = pr_log_openfile(path, &vroot_logfd, 0660);
xerrno = errno;
PRIVS_RELINQUISH
switch (res) {
case 0:
break;
case -1:
pr_log_debug(DEBUG1, MOD_VROOT_VERSION
": unable to open VRootLog '%s': %s", path, strerror(xerrno));
break;
case PR_LOG_SYMLINK:
pr_log_debug(DEBUG1, MOD_VROOT_VERSION
": unable to open VRootLog '%s': %s", path, "is a symlink");
break;
case PR_LOG_WRITABLE_DIR:
pr_log_debug(DEBUG1, MOD_VROOT_VERSION
": unable to open VRootLog '%s': %s", path,
"parent directory is world-writable");
break;
}
}
}
vroot_alias_init(session.pool);
vroot_fsio_init(session.pool);
pr_event_register(&vroot_module, "core.chroot", vroot_chroot_ev, NULL);
pr_event_register(&vroot_module, "core.exit", vroot_exit_ev, NULL);
return 0;
}
/* Module API tables
*/
static conftable vroot_conftab[] = {
{ "VRootAlias", set_vrootalias, NULL },
{ "VRootEngine", set_vrootengine, NULL },
{ "VRootLog", set_vrootlog, NULL },
{ "VRootOptions", set_vrootoptions, NULL },
{ "VRootServerRoot", set_vrootserverroot, NULL },
{ NULL }
};
static cmdtable vroot_cmdtab[] = {
{ POST_CMD, C_PASS, G_NONE, vroot_post_pass, FALSE, FALSE },
{ PRE_CMD, C_MKD, G_NONE, vroot_pre_mkd, FALSE, FALSE },
{ POST_CMD, C_MKD, G_NONE, vroot_post_mkd, FALSE, FALSE },
{ POST_CMD_ERR, C_MKD, G_NONE, vroot_post_mkd, FALSE, FALSE },
{ PRE_CMD, C_XMKD, G_NONE, vroot_pre_mkd, FALSE, FALSE },
{ POST_CMD, C_XMKD, G_NONE, vroot_post_mkd, FALSE, FALSE },
{ POST_CMD_ERR, C_XMKD, G_NONE, vroot_post_mkd, FALSE, FALSE },
/* These command handlers are for manipulating cmd->notes, to get
* paths properly logged.
*
* Ideally these POST_CMD handlers would be LOG_CMD/LOG_CMD_ERR phase
* handlers. HOWEVER, we need to transform things before the cmd is
* dispatched to mod_log, and mod_log uses a C_ANY handler for logging.
* And when dispatching, C_ANY handlers are run before named handlers.
* This means that using * LOG_CMD/LOG_CMD_ERR handlers would be run AFTER
* mod_log's handler, even though we appear BEFORE mod_log in the module
* load order.
*
* Thus to do the transformation, we actually use CMD/POST_CMD_ERR phase
* handlers here. The reason to use CMD, rather than POST_CMD, is the
* the TransferLog entries are written by mod_xfer, in its CMD handlers.
* Given this, you might be tempted to change these to PRE_CMD handlers.
* That will not work, either, as the necessary cmd->notes keys are
* populated by PRE_CMD handlers in mod_xfer, one of the last modules to
* run.
*/
{ CMD, C_APPE, G_NONE, vroot_log_stor, FALSE, FALSE, CL_WRITE },
{ POST_CMD_ERR, C_APPE, G_NONE, vroot_log_stor, FALSE, FALSE },
{ CMD, C_RETR, G_NONE, vroot_log_retr, FALSE, FALSE, CL_READ },
{ POST_CMD_ERR, C_RETR, G_NONE, vroot_log_retr, FALSE, FALSE },
{ CMD, C_STOR, G_NONE, vroot_log_stor, FALSE, FALSE, CL_WRITE },
{ POST_CMD_ERR, C_STOR, G_NONE, vroot_log_stor, FALSE, FALSE },
/* To make this more complicated, we DO actually want these handlers to
* run as PRE_CMD handlers, but only for mod_sftp sessions. Why? The
* mod_sftp module does not use the normal CMD handlers; it handles
* dispatching on its own. And we do still want mod_vroot to fix up
* the paths properly for SFTP/SCP sessions, too.
*/
{ PRE_CMD, C_APPE, G_NONE, vroot_pre_sftp_stor, FALSE, FALSE, CL_WRITE },
{ POST_CMD, C_APPE, G_NONE, vroot_post_sftp_stor, FALSE, FALSE },
{ PRE_CMD, C_RETR, G_NONE, vroot_pre_sftp_retr, FALSE, FALSE, CL_READ },
{ POST_CMD, C_RETR, G_NONE, vroot_post_sftp_retr, FALSE, FALSE },
{ PRE_CMD, C_STOR, G_NONE, vroot_pre_sftp_stor, FALSE, FALSE, CL_WRITE },
{ POST_CMD, C_STOR, G_NONE, vroot_post_sftp_stor, FALSE, FALSE },
{ PRE_CMD, C_RETR, G_NONE, vroot_pre_scp_retr, FALSE, FALSE, CL_READ },
{ PRE_CMD, C_STOR, G_NONE, vroot_pre_scp_stor, FALSE, FALSE, CL_WRITE },
{ 0, NULL }
};
module vroot_module = {
NULL, NULL,
/* Module API version 2.0 */
0x20,
/* Module name */
"vroot",
/* Module configuration handler table */
vroot_conftab,
/* Module command handler table */
vroot_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization function */
NULL,
/* Session initialization function */
vroot_sess_init,
/* Module version */
MOD_VROOT_VERSION
};
proftpd-mod_vroot-0.9.11/mod_vroot.h.in 0000664 0000000 0000000 00000002646 14334560322 0020066 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot
* Copyright (c) 2016-2022 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_VROOT_H
#define MOD_VROOT_H
#include "conf.h"
#define MOD_VROOT_VERSION "mod_vroot/0.9.11"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030602
# error "ProFTPD 1.3.6rc2 or later required"
#endif
extern int vroot_logfd;
extern unsigned int vroot_opts;
/* VRootOptions */
#define VROOT_OPT_ALLOW_SYMLINKS 0x0001
#endif /* MOD_VROOT_H */
proftpd-mod_vroot-0.9.11/mod_vroot.html 0000664 0000000 0000000 00000016005 14334560322 0020170 0 ustar 00root root 0000000 0000000
ProFTPD module mod_vroot
ProFTPD module mod_vroot
This module is contained in the mod_vroot.c
file for
ProFTPD 1.3.x, and is not compiled by default. Installation
instructions are discussed here.
The purpose of this module to is to implement a virtual chroot capability
that does not require root privileges. The mod_vroot
module
provides this capability by using ProFTPD's FS API, available as of 1.2.8rc1.
The most current version of mod_vroot
can be found at:
https://github.com/Castaglia/proftpd-mod_vroot.git
Author
Please contact TJ Saunders <tj at castaglia.org> with any
questions, concerns, or suggestions regarding this module.
Thanks
2003-08-26: Thanks to Oskar Liljeblad for the elegant patch that added
symlink support.
Directives
Syntax: VRootAlias src-path dst-path
Default: None
Context: server config, <VirtualHost>
, <Global>
Module: mod_vroot
Compatibility: 1.3.2 and later
The VRootAlias
directive is used to create an "alias" of a
directory outside of the chroot area into the chroot. The dst-path
parameter is a relative path, relative to the chroot area (i.e.
the directory in which the session starts). The src-path parameter,
on the other hand, is an absolute path, and may be to a file or
directory.
For example, you might map a shared upload directory into a user's home
directory using:
<IfModule mod_vroot.c>
VRootEngine on
DefaultRoot ~
VRootAlias /var/ftp/upload ~/upload
</IfModule>
This will automatically create an "upload" directory to appear in the
chroot area (in this case, the user's home directory).
The VRootAlias
directive is only needed for files/directories
that are going to be accessed by remote clients. It is not needed
for configuration files (e.g. PAM configuration files like pam_env.conf
) needed by libraries. Using the VRootAlias
for
such library configuration files is pointless and wasteful.
Note that this directive will not work if the
VRootServerRoot
is used.
Syntax: VRootEngine on|off
Default: None
Context: server config, <VirtualHost>
, <Global>
Module: mod_vroot
Compatibility: 1.2.8rc1 and later
The VRootEngine
directive enables the virtual chroot engine
implemented by mod_vroot
. If enabled, the virtual chroot will
be used in place of the operating system's chroot(2)
. This
directive affects any DefaultRoot
directives and any
<Anonymous>
contexts within the server context in which
the VRootEngine
directive appears.
Syntax: VRootLog file
Default: None
Context: server config, <VirtualHost>
, <Global>
Module: mod_vroot
Compatibility: 1.3.0rc1 and later
The VRootLog
directive is used to specify a log file for
mod_vroot
's reporting on a per-server basis. The file
parameter given must be the full path to the file to use for logging.
Syntax: VRootOptions opt1 ...
Default: None
Context: "server config" <VirtualHost>
, <Global>
Module: mod_vroot
Compatibility: 1.2.9rc2 and later
The VRootOptions
directive is used to configure various optional
behavior of mod_vroot
.
Example:
VRootOptions allowSymlinks
The currently implemented options are:
allowSymlinks
Normally, any symlinks that point outside of the vroot area simply do
not work. When the allowSymlinks
option is enabled, these
symlinks will be allowed. Note that by enabling symlinks, the efficacy
of the vroot "jail" is reduced.
Syntax: VRootServerRoot path
Default: None
Context: "server config" <VirtualHost>
, <Global>
Module: mod_vroot
Compatibility: 1.3.2rc1 and later
The VRootServerRoot
directive is used to configure a directory
to which the mod_vroot
module will perform a real chroot.
The idea is that each <VirtualHost>
can have its own
directory to which a real chroot(2)
system call is made;
the user-specific home directories will be virtual roots underneath this
directory. Thus some measure of security, via the chroot(2)
system call, is provided by the kernel, while still allowing symlinked shared
folders among users of this <VirtualHost>
.
For example:
<VirtualHost a.b.c.d>
VRootEngine on
VRootServerRoot /etc/ftpd/a.b.c.d/
VRootOptions allowSymlinks
DefaultRoot ~
...
</VirtualHost>
See also: VRootOptions
To install mod_vroot
, go to the third-party module area in
the proftpd source code and unpack the mod_vroot
source tarball:
$ cd proftpd-dir/contrib/
$ tar zxvf /path/to/mod_vroot-version.tar.gz
after unpacking the latest proftpd-1.3.x source code. For including
mod_vroot
as a staticly linked module:
$ ./configure --with-modules=mod_vroot:...
To build mod_vroot
as a DSO module:
$ ./configure --enable-dso --with-shared=mod_vroot:...
Then follow the usual steps:
$ make
$ make install
© Copyright 2000-2016 TJ Saunders
All Rights Reserved
proftpd-mod_vroot-0.9.11/path.c 0000664 0000000 0000000 00000023624 14334560322 0016377 0 ustar 00root root 0000000 0000000 /*
* ProFTPD: mod_vroot Path API
* Copyright (c) 2016-2022 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.
*
* As a special exemption, the respective copyright holders give permission
* to link this program with OpenSSL, and distribute the resulting
* executable, without including the source code for OpenSSL in the source
* distribution.
*/
#include "path.h"
#include "alias.h"
static char vroot_base[PR_TUNABLE_PATH_MAX + 1];
static size_t vroot_baselen = 0;
static const char *trace_channel = "vroot.path";
/* Support routines note: some of these support functions are borrowed from
* pure-ftpd.
*/
static void strmove(register char *dst, register const char *src) {
if (dst == NULL ||
src == NULL) {
return;
}
while (*src != 0) {
*dst++ = *src++;
}
*dst = 0;
}
int vroot_path_have_base(void) {
if (*vroot_base == '\0') {
return FALSE;
}
return TRUE;
}
const char *vroot_path_get_base(pool *p, size_t *baselen) {
if (p == NULL) {
errno = EINVAL;
return NULL;
}
if (baselen != NULL) {
*baselen = vroot_baselen;
}
return pstrdup(p, vroot_base);
}
int vroot_path_set_base(const char *base, size_t baselen) {
if (base == NULL ||
baselen >= sizeof(vroot_base)) {
errno = EINVAL;
return -1;
}
memset(vroot_base, '\0', sizeof(vroot_base));
if (baselen > 0) {
memcpy(vroot_base, base, baselen);
vroot_base[sizeof(vroot_base)-1] = '\0';
}
vroot_baselen = baselen;
return 0;
}
/* Note that we do in-place modifications of the given `path` buffer here,
* which means that it MUST be writable; no constant strings, please.
*/
void vroot_path_clean(char *path) {
char *ptr = NULL;
if (path == NULL ||
*path == 0) {
return;
}
ptr = strstr(path, "//");
while (ptr != NULL) {
pr_signals_handle();
strmove(ptr, ptr + 1);
ptr = strstr(path, "//");
}
ptr = strstr(path, "/./");
while (ptr != NULL) {
pr_signals_handle();
strmove(ptr, ptr + 2);
ptr = strstr(path, "/./");
}
while (strncmp(path, "../", 3) == 0) {
pr_signals_handle();
path += 3;
}
ptr = strstr(path, "/../");
if (ptr != NULL) {
if (ptr == path) {
while (strncmp(path, "/../", 4) == 0) {
pr_signals_handle();
strmove(path, path + 3);
}
ptr = strstr(path, "/../");
}
while (ptr != NULL) {
char *next_elem;
pr_signals_handle();
next_elem = ptr + 4;
if (ptr != path &&
*ptr == '/') {
ptr--;
}
while (ptr != path &&
*ptr != '/') {
ptr--;
}
if (*ptr == '/') {
ptr++;
}
strmove(ptr, next_elem);
ptr = strstr(path, "/../");
}
}
ptr = path;
if (*ptr == '.') {
ptr++;
if (*ptr == '\0') {
return;
}
if (*ptr == '/') {
while (*ptr == '/') {
ptr++;
}
strmove(path, ptr);
}
}
if (*ptr == '\0') {
return;
}
ptr = path + strlen(path) - 1;
if (*ptr != '.' ||
ptr == path) {
return;
}
ptr--;
if (*ptr == '/' ||
ptr == path) {
ptr[1] = '\0';
return;
}
if (*ptr != '.' ||
ptr == path) {
return;
}
ptr--;
if (*ptr != '/') {
return;
}
*ptr = '\0';
ptr = strrchr(path, '/');
if (ptr == NULL) {
*path = '/';
path[1] = '\0';
return;
}
ptr[1] = '\0';
}
char *vroot_realpath(pool *p, const char *path, int flags) {
char *real_path = NULL;
size_t real_pathlen;
if (p == NULL ||
path == NULL) {
errno = EINVAL;
return NULL;
}
/* If not an absolute path, prepend the current location. */
if (*path != '/' &&
(flags & VROOT_REALPATH_FL_ABS_PATH)) {
real_path = pdircat(p, pr_fs_getvwd(), path, NULL);
} else {
real_path = pstrdup(p, path);
}
vroot_path_clean(real_path);
/* If the given path ends in a slash, remove it. The handling of
* VRootAliases is sensitive to such things.
*/
real_pathlen = strlen(real_path);
if (real_pathlen > 1 &&
real_path[real_pathlen-1] == '/') {
real_path[real_pathlen-1] = '\0';
real_pathlen--;
}
return real_path;
}
/* The given `vpath` buffer is the looked-up path for the given `path`. */
int vroot_path_lookup(pool *p, char *vpath, size_t vpathsz, const char *path,
int flags, char **alias_path) {
char buf[PR_TUNABLE_PATH_MAX + 1], *bufp = NULL;
const char *cwd;
if (vpath == NULL ||
path == NULL) {
errno = EINVAL;
return -1;
}
memset(buf, '\0', sizeof(buf));
if (vpath != NULL &&
vpathsz > 0) {
memset(vpath, '\0', vpathsz);
}
cwd = pr_fs_getcwd();
if (strcmp(path, ".") != 0) {
sstrncpy(buf, path, sizeof(buf));
} else {
sstrncpy(buf, cwd, sizeof(buf));
}
vroot_path_clean(buf);
bufp = buf;
if (strncmp(bufp, vroot_base, vroot_baselen) == 0) {
size_t len;
/* Attempt to handle cases like "/base/base" and "/base/basefoo", where
* the base is just "/base".
* See https://github.com/proftpd/proftpd/issues/1491
*/
len = strlen(bufp);
if (len > vroot_baselen &&
bufp[vroot_baselen] == '/') {
bufp += vroot_baselen;
}
}
loop:
pr_signals_handle();
if (bufp[0] == '.' &&
bufp[1] == '.' &&
(bufp[2] == '\0' ||
bufp[2] == '/')) {
char *ptr = NULL;
ptr = strrchr(vpath, '/');
if (ptr != NULL) {
*ptr = '\0';
} else {
*vpath = '\0';
}
if (strncmp(vpath, vroot_base, vroot_baselen) == 0 ||
vpath[vroot_baselen] != '/') {
snprintf(vpath, vpathsz, "%s/", vroot_base);
}
if (bufp[0] == '.' &&
bufp[1] == '.' &&
bufp[2] == '/') {
bufp += 3;
goto loop;
}
} else if (*bufp == '/') {
snprintf(vpath, vpathsz, "%s/", vroot_base);
bufp += 1;
goto loop;
} else if (*bufp != '\0') {
size_t buflen, tmplen;
char *ptr = NULL;
ptr = strstr(bufp, "..");
if (ptr != NULL) {
size_t ptrlen;
/* We need to watch for path components/filenames which legitimately
* contain two or more periods in addition to other characters.
*/
ptrlen = strlen(ptr);
if (ptrlen >= 3) {
/* If this ".." occurrence is the start of the buffer AND the next
* character after the ".." is a slash, then deny it.
*/
if (ptr == bufp &&
ptr[2] == '/') {
errno = EPERM;
return -1;
}
/* If this ".." occurrence is NOT the start of the buffer AND the
* characters preceeding and following the ".." are slashes, then
* deny it.
*/
if (ptr != bufp &&
ptr[-1] == '/' &&
ptr[2] == '/') {
errno = EPERM;
return -1;
}
}
}
buflen = strlen(bufp) + 1;
tmplen = strlen(vpath);
if (tmplen + buflen >= vpathsz) {
errno = ENAMETOOLONG;
return -1;
}
vpath[tmplen] = '/';
memcpy(vpath + tmplen + 1, bufp, buflen);
}
/* Clean any unnecessary characters added by the above processing. */
vroot_path_clean(vpath);
if (!(flags & VROOT_LOOKUP_FL_NO_ALIAS)) {
int alias_count;
/* Check to see if this path is an alias; if so, return the real path. */
alias_count = vroot_alias_count();
if (alias_count > 0) {
char *start_ptr = NULL, *end_ptr = NULL;
const char *src_path = NULL;
/* buf is used here for storing the "suffix", to be appended later when
* aliases are found.
*/
bufp = buf;
start_ptr = vpath;
while (start_ptr != NULL) {
char *ptr = NULL;
pr_signals_handle();
pr_trace_msg(trace_channel, 15, "checking for alias for '%s'",
start_ptr);
src_path = vroot_alias_get(start_ptr);
if (src_path != NULL) {
pr_trace_msg(trace_channel, 15, "found '%s' for alias '%s'", src_path,
start_ptr);
/* If the caller provided a pointer for wanting to know the full
* alias path (not the true path), then fill that pointer.
*/
if (alias_path != NULL) {
if (end_ptr != NULL) {
*alias_path = pdircat(p, start_ptr, end_ptr + 1, NULL);
} else {
*alias_path = pstrdup(p, start_ptr);
}
pr_trace_msg(trace_channel, 19, "using alias path '%s' for '%s'",
*alias_path, start_ptr);
}
sstrncpy(vpath, src_path, vpathsz);
if (end_ptr != NULL) {
/* Now tack on our suffix from the scratchpad. */
sstrcat(vpath, bufp, vpathsz);
}
break;
}
ptr = strrchr(start_ptr, '/');
if (end_ptr != NULL) {
*end_ptr = '/';
}
if (ptr == NULL) {
break;
}
/* If this is the start of the path, we're done. */
if (ptr == start_ptr) {
break;
}
/* Store the suffix in the buf scratchpad. */
sstrncpy(buf, ptr, sizeof(buf));
end_ptr = ptr;
*end_ptr = '\0';
}
}
}
/* Note that logging the session.chroot_path here will not help; mod_vroot
* deliberately always sets that to just "/".
*/
pr_trace_msg(trace_channel, 19,
"lookup: path = '%s', cwd = '%s', base = '%s', vpath = '%s'", path, cwd,
vroot_base, vpath);
return 0;
}
proftpd-mod_vroot-0.9.11/path.h 0000664 0000000 0000000 00000003070 14334560322 0016375 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot Path API
* Copyright (c) 2016 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#ifndef MOD_VROOT_PATH_H
#define MOD_VROOT_PATH_H
#include "mod_vroot.h"
int vroot_path_have_base(void);
const char *vroot_path_get_base(pool *p, size_t *baselen);
int vroot_path_set_base(const char *base, size_t baselen);
void vroot_path_clean(char *path);
int vroot_path_lookup(pool *p, char *path, size_t pathlen, const char *dir,
int flags, char **alias_path);
#define VROOT_LOOKUP_FL_NO_ALIAS 0x001
char *vroot_realpath(pool *p, const char *path, int flags);
#define VROOT_REALPATH_FL_ABS_PATH 0x001
#endif /* MOD_VROOT_PATH_H */
proftpd-mod_vroot-0.9.11/t/ 0000775 0000000 0000000 00000000000 14334560322 0015533 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/Makefile.in 0000664 0000000 0000000 00000003336 14334560322 0017605 0 ustar 00root root 0000000 0000000 CC=@CC@
@SET_MAKE@
top_builddir=../../..
top_srcdir=../../..
module_srcdir=..
srcdir=@srcdir@
VPATH=@srcdir@
include $(top_srcdir)/Make.rules
# Necessary redefinitions
INCLUDES=-I. -I.. -I$(module_srcdir)/include -I../../.. -I../../../include @INCLUDES@
TEST_CPPFLAGS=$(ADDL_CPPFLAGS) -DHAVE_CONFIG_H $(DEFAULT_PATHS) $(PLATFORM) $(INCLUDES)
TEST_LDFLAGS=-L$(top_srcdir)/lib @LIBDIRS@
EXEEXT=@EXEEXT@
TEST_API_DEPS=\
$(top_srcdir)/lib/prbase.a \
$(top_srcdir)/src/pool.o \
$(top_srcdir)/src/privs.o \
$(top_srcdir)/src/str.o \
$(top_srcdir)/src/sets.o \
$(top_srcdir)/src/table.o \
$(top_srcdir)/src/netacl.o \
$(top_srcdir)/src/class.o \
$(top_srcdir)/src/event.o \
$(top_srcdir)/src/timers.o \
$(top_srcdir)/src/stash.o \
$(top_srcdir)/src/modules.o \
$(top_srcdir)/src/cmd.o \
$(top_srcdir)/src/configdb.o \
$(top_srcdir)/src/parser.o \
$(top_srcdir)/src/regexp.o \
$(top_srcdir)/src/fsio.o \
$(top_srcdir)/src/netio.o \
$(top_srcdir)/src/inet.o \
$(top_srcdir)/src/netaddr.o \
$(top_srcdir)/src/response.o \
$(top_srcdir)/src/auth.o \
$(top_srcdir)/src/env.o \
$(top_srcdir)/src/trace.o \
$(top_srcdir)/src/support.o \
$(top_srcdir)/src/error.o \
$(module_srcdir)/alias.o \
$(module_srcdir)/path.o \
$(module_srcdir)/fsio.o
TEST_API_LIBS=-lcheck -lm
TEST_API_OBJS=\
api/alias.o \
api/path.o \
api/fsio.o \
api/stubs.o \
api/tests.o
dummy:
api/.c.o:
$(CC) $(CPPFLAGS) $(TEST_CPPFLAGS) $(CFLAGS) -c $<
api-tests$(EXEEXT): $(TEST_API_OBJS) $(TEST_API_DEPS)
$(LIBTOOL) --mode=link --tag=CC $(CC) $(LDFLAGS) $(TEST_LDFLAGS) -o $@ $(TEST_API_DEPS) $(TEST_API_OBJS) $(TEST_API_LIBS) $(LIBS)
./$@
clean:
$(LIBTOOL) --mode=clean $(RM) *.o api/*.o api-tests$(EXEEXT) api-tests.log
proftpd-mod_vroot-0.9.11/t/api/ 0000775 0000000 0000000 00000000000 14334560322 0016304 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/api/alias.c 0000664 0000000 0000000 00000010153 14334560322 0017541 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot testsuite
* Copyright (c) 2016-2022 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Alias tests. */
#include "tests.h"
#include "alias.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = make_sub_pool(NULL);
}
vroot_alias_init(p);
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.alias", 1, 20);
}
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.alias", 0, 0);
}
vroot_alias_free();
if (p) {
destroy_pool(p);
p = NULL;
}
}
START_TEST (alias_init_test) {
int res;
res = vroot_alias_init(NULL);
ck_assert_msg(res < 0, "Failed to handle null pool");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
START_TEST (alias_count_test) {
unsigned int res;
res = vroot_alias_count();
ck_assert_msg(res == 0, "Expected 0, got %u", res);
}
END_TEST
START_TEST (alias_exists_test) {
int res;
const char *path;
res = vroot_alias_exists(NULL);
ck_assert_msg(res == FALSE, "Failed to handle null path");
path = "/foo/bar";
res = vroot_alias_exists(path);
ck_assert_msg(res == FALSE, "Expected FALSE for path '%s', got TRUE", path);
}
END_TEST
START_TEST (alias_add_test) {
int res;
const char *dst, *src;
res = vroot_alias_add(NULL, NULL);
ck_assert_msg(res < 0, "Failed to handle null dst");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
dst = "foo";
res = vroot_alias_add(dst, NULL);
ck_assert_msg(res < 0, "Failed to handle null src");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
src = "bar";
res = vroot_alias_add(dst, src);
ck_assert_msg(res == 0, "Failed to add alias '%s => %s': %s", src, dst,
strerror(errno));
}
END_TEST
START_TEST (alias_get_test) {
const char *alias, *path;
alias = vroot_alias_get(NULL);
ck_assert_msg(alias == NULL, "Failed to handle null path");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
path = "/foo/bar";
alias = vroot_alias_get(path);
ck_assert_msg(alias == NULL, "Expected null for path '%s', got '%s'", path,
alias);
ck_assert_msg(errno == ENOENT, "Expected ENOENT (%d), got %s (%d)", ENOENT,
strerror(errno), errno);
}
END_TEST
START_TEST (alias_do_test) {
int res;
res = vroot_alias_do(NULL, NULL);
ck_assert_msg(res < 0, "Failed to handle null callback");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got %s (%d)", EINVAL,
strerror(errno), errno);
}
END_TEST
Suite *tests_get_alias_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("alias");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, alias_init_test);
tcase_add_test(testcase, alias_count_test);
tcase_add_test(testcase, alias_exists_test);
tcase_add_test(testcase, alias_add_test);
tcase_add_test(testcase, alias_get_test);
tcase_add_test(testcase, alias_do_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_vroot-0.9.11/t/api/fsio.c 0000664 0000000 0000000 00000003711 14334560322 0017412 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot testsuite
* Copyright (c) 2016 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* FSIO tests. */
#include "tests.h"
#include "fsio.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = make_sub_pool(NULL);
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.fsio", 1, 20);
}
}
static void tear_down(void) {
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.fsio", 0, 0);
}
if (p) {
destroy_pool(p);
p = NULL;
}
}
/* TODO: Fill in these FSIO API tests, once the Path API unit tests are
* fleshed out more completely, as the FSIO API heavily relies on the Path API.
*/
START_TEST (fsio_stat_test) {
}
END_TEST
Suite *tests_get_fsio_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("fsio");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, fsio_stat_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_vroot-0.9.11/t/api/path.c 0000664 0000000 0000000 00000030401 14334560322 0017402 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot testsuite
* Copyright (c) 2016-2022 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* vroot tests. */
#include "tests.h"
#include "path.h"
static pool *p = NULL;
static void set_up(void) {
if (p == NULL) {
p = make_sub_pool(NULL);
}
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.path", 1, 20);
}
}
static void tear_down(void) {
(void) vroot_path_set_base("", 0);
if (getenv("TEST_VERBOSE") != NULL) {
pr_trace_set_levels("vroot.path", 0, 0);
}
if (p != NULL) {
destroy_pool(p);
p = NULL;
}
}
START_TEST (path_have_base_test) {
int res;
mark_point();
res = vroot_path_have_base();
ck_assert_msg(res == FALSE, "Have vroot base unexpectedly");
}
END_TEST
START_TEST (path_get_base_test) {
const char *res;
mark_point();
res = vroot_path_get_base(NULL, NULL);
ck_assert_msg(res == NULL, "Failed to handle null pool");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = vroot_path_get_base(p, NULL);
ck_assert_msg(res != NULL, "Failed to get base: %s", strerror(errno));
ck_assert_msg(strcmp(res, "") == 0, "Expected '', got '%s'", res);
}
END_TEST
START_TEST (path_set_base_test) {
int res;
const char *path, *ptr;
size_t pathlen, len;
mark_point();
res = vroot_path_set_base(NULL, 0);
ck_assert_msg(res < 0, "Failed to handle missing path");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
path = "/foo";
pathlen = (PR_TUNABLE_PATH_MAX * 4);
res = vroot_path_set_base("foo", pathlen);
ck_assert_msg(res < 0, "Failed to handle too-long pathlen");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
path = "/foo";
pathlen = strlen(path);
res = vroot_path_set_base(path, pathlen);
ck_assert_msg(res == 0, "Failed to set base '%s': %s", path, strerror(errno));
mark_point();
res = vroot_path_have_base();
ck_assert_msg(res == TRUE, "Have base is unexpectedly false");
mark_point();
ptr = vroot_path_get_base(p, &len);
ck_assert_msg(ptr != NULL, "Failed to get base: %s", strerror(errno));
ck_assert_msg(len == pathlen, "Expected %lu, got %lu",
(unsigned long) pathlen, (unsigned long) len);
/* Clear the base, using an empty string. */
mark_point();
path = "";
res = vroot_path_set_base(path, 0);
ck_assert_msg(res == 0, "Failed to set empty path as base: %s",
strerror(errno));
mark_point();
res = vroot_path_have_base();
ck_assert_msg(res == FALSE, "Have base is unexpectedly true");
}
END_TEST
START_TEST (path_clean_test) {
char *path, *expected;
mark_point();
vroot_path_clean(NULL);
mark_point();
path = pstrdup(p, "//");
expected = "/";
vroot_path_clean(path);
ck_assert_msg(strcmp(path, expected) == 0, "Expected '%s', got '%s'",
expected, path);
mark_point();
path = pstrdup(p, "/foo/./bar//");
expected = "/foo/bar/";
vroot_path_clean(path);
ck_assert_msg(strcmp(path, expected) == 0, "Expected '%s', got '%s'",
expected, path);
mark_point();
path = pstrdup(p, "/foo/../bar//");
expected = "/bar/";
vroot_path_clean(path);
ck_assert_msg(strcmp(path, expected) == 0, "Expected '%s', got '%s'",
expected, path);
mark_point();
path = pstrdup(p, "/./.././.././bar/./");
expected = "/bar/";
vroot_path_clean(path);
ck_assert_msg(strcmp(path, expected) == 0, "Expected '%s', got '%s'",
expected, path);
mark_point();
path = pstrdup(p, ".");
expected = ".";
vroot_path_clean(path);
ck_assert_msg(strcmp(path, expected) == 0, "Expected '%s', got '%s'",
expected, path);
}
END_TEST
START_TEST (realpath_test) {
char *res, *path, *expected;
mark_point();
res = vroot_realpath(NULL, NULL, 0);
ck_assert_msg(res == NULL, "Failed to handle null pool");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = vroot_realpath(p, NULL, 0);
ck_assert_msg(res == NULL, "Failed to handle null path");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
path = pstrdup(p, "/foo");
expected = "/foo";
res = vroot_realpath(p, path, 0);
ck_assert_msg(res != NULL, "Failed to handle path: '%s'", strerror(errno));
ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'",
expected, res);
mark_point();
path = pstrdup(p, "/foo/");
expected = "/foo";
res = vroot_realpath(p, path, 0);
ck_assert_msg(res != NULL, "Failed to handle path: '%s'", strerror(errno));
ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'",
expected, res);
mark_point();
path = pstrdup(p, "/foo//");
expected = "/foo";
res = vroot_realpath(p, path, 0);
ck_assert_msg(res != NULL, "Failed to handle path: '%s'", strerror(errno));
ck_assert_msg(strcmp(res, expected) == 0, "Expected '%s', got '%s'",
expected, res);
}
END_TEST
START_TEST (path_lookup_test) {
int res;
char *vpath = NULL;
size_t vpathsz = 1024;
const char *path;
mark_point();
vpath = pcalloc(p, vpathsz);
res = vroot_path_lookup(p, vpath, vpathsz, NULL, 0, NULL);
ck_assert_msg(res < 0, "Failed to handle null path");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
path = "/";
res = vroot_path_lookup(NULL, NULL, 0, path, 0, NULL);
ck_assert_msg(res < 0, "Failed to handle null vpath");
ck_assert_msg(errno == EINVAL, "Expected EINVAL (%d), got '%s' (%d)", EINVAL,
strerror(errno), errno);
mark_point();
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
mark_point();
path = ".";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
}
END_TEST
/* See: https://github.com/proftpd/proftpd/issues/1491 */
START_TEST (path_lookup_issue1491_test) {
int res;
char *vpath = NULL;
size_t vpathsz = 1024, baselen;
const char *base, *path, *expected;
vpath = pcalloc(p, vpathsz);
base = "/store";
baselen = strlen(base);
/* Set the base. */
mark_point();
res = vroot_path_set_base(base, baselen);
ck_assert_msg(res == 0, "Failed to set base '%s': %s", base, strerror(errno));
/* Start with an absolute path that matches the base. */
mark_point();
path = base;
/* NOTE: Yes, this is a surprising expectation; it has to do with the
* necessary fixes for Issue #1491. Sigh.
*/
expected = "/store/store";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Then try a relative path whose name matches the base, sans the leading
* path delimiter.
*/
mark_point();
path = "store";
expected = base;
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Next, try a relative path for a file whose name starts with that of
* the base.
*/
mark_point();
path = "storetest";
expected = "/storetest";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Next, use an absolute path for a file whose name starts with that of the
* base; this appears to be the root of Issue #1491.
*/
mark_point();
path = "/storetest";
/* NOTE: Yes, this is a surprising expectation; it has to do with the
* necessary fixes for Issue #1491. Sigh.
*/
expected = "/store/storetest";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Set the new base. */
base = "/store/store";
baselen = strlen(base);
mark_point();
res = vroot_path_set_base(base, baselen);
ck_assert_msg(res == 0, "Failed to set base '%s': %s", base, strerror(errno));
/* Start with an absolute path that matches the base. */
mark_point();
path = base;
/* NOTE: Yes, this is a surprising expectation; it has to do with the
* necessary fixes for Issue #1491. Sigh. This is starting to look a little
* ridiculous.
*/
expected = "/store/store/store/store";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Then try a relative path whose name matches the base, sans the leading
* path delimiter.
*/
mark_point();
path = "store";
expected = "/store";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Next, try a relative path for a file whose name starts with that of
* the base.
*/
mark_point();
path = "storetest";
expected = "/storetest";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Next, use an absolute path for a file whose name starts with that of the
* base; this appears to be the root of Issue #1491.
*/
mark_point();
path = "/storetest";
/* NOTE: Yes, this is a surprising expectation; it has to do with the
* necessary fixes for Issue #1491. Sigh.
*/
expected = "/store/store/storetest";
res = vroot_path_lookup(p, vpath, vpathsz, path, 0, NULL);
ck_assert_msg(res >= 0, "Failed to lookup vpath for '%s': %s", path,
strerror(errno));
ck_assert_msg(strcmp(vpath, expected) == 0, "Expected '%s', got '%s'",
expected, vpath);
/* Clear the base, using an empty string. */
mark_point();
path = "";
res = vroot_path_set_base(path, 0);
ck_assert_msg(res == 0, "Failed to set empty path as base: %s",
strerror(errno));
}
END_TEST
/* TODO */
START_TEST (path_lookup_with_alias_test) {
}
END_TEST
Suite *tests_get_path_suite(void) {
Suite *suite;
TCase *testcase;
suite = suite_create("path");
testcase = tcase_create("base");
tcase_add_checked_fixture(testcase, set_up, tear_down);
tcase_add_test(testcase, path_have_base_test);
tcase_add_test(testcase, path_get_base_test);
tcase_add_test(testcase, path_set_base_test);
tcase_add_test(testcase, path_clean_test);
tcase_add_test(testcase, realpath_test);
tcase_add_test(testcase, path_lookup_test);
tcase_add_test(testcase, path_lookup_issue1491_test);
tcase_add_test(testcase, path_lookup_with_alias_test);
suite_add_tcase(suite, testcase);
return suite;
}
proftpd-mod_vroot-0.9.11/t/api/stubs.c 0000664 0000000 0000000 00000007414 14334560322 0017616 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot API testsuite
* Copyright (c) 2016-2020 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "tests.h"
/* Stubs */
session_t session;
int ServerUseReverseDNS = FALSE;
server_rec *main_server = NULL;
pid_t mpid = 1;
unsigned char is_master = TRUE;
volatile unsigned int recvd_signal_flags = 0;
module *static_modules[] = { NULL };
module *loaded_modules = NULL;
xaset_t *server_list = NULL;
int vroot_logfd = -1;
pool *vroot_pool = NULL;
unsigned int vroot_opts = 0;
int login_check_limits(xaset_t *set, int recurse, int and, int *found) {
return TRUE;
}
int xferlog_open(const char *path) {
return 0;
}
int pr_cmd_read(cmd_rec **cmd) {
errno = ENOENT;
*cmd = NULL;
return -1;
}
int pr_config_get_server_xfer_bufsz(int direction) {
int bufsz = -1;
switch (direction) {
case PR_NETIO_IO_RD:
bufsz = PR_TUNABLE_DEFAULT_RCVBUFSZ;
break;
case PR_NETIO_IO_WR:
bufsz = PR_TUNABLE_DEFAULT_SNDBUFSZ;
break;
default:
errno = EINVAL;
return -1;
}
return bufsz;
}
void pr_log_auth(int priority, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "AUTH: ");
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
void pr_log_debug(int level, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "DEBUG%d: ", level);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level,
const char *log_msg, size_t log_msglen) {
errno = ENOSYS;
return -1;
}
int pr_log_event_listening(unsigned int log_type) {
return FALSE;
}
int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) {
*log_fd = STDERR_FILENO;
return 0;
}
void pr_log_pri(int prio, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "PRI%d: ", prio);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
}
void pr_log_stacktrace(int fd, const char *name) {
}
int pr_log_writefile(int fd, const char *name, const char *fmt, ...) {
if (getenv("TEST_VERBOSE") != NULL) {
va_list msg;
fprintf(stderr, "%s: ", name);
va_start(msg, fmt);
vfprintf(stderr, fmt, msg);
va_end(msg);
fprintf(stderr, "\n");
}
return 0;
}
int pr_scoreboard_entry_update(pid_t pid, ...) {
return 0;
}
void pr_session_disconnect(module *m, int reason_code, const char *details) {
}
void pr_session_end(int flags) {
}
const char *pr_session_get_protocol(int flags) {
return "ftp";
}
int pr_session_set_protocol(const char *proto) {
return 0;
}
void pr_signals_handle(void) {
}
/* Module-specific stubs */
proftpd-mod_vroot-0.9.11/t/api/tests.c 0000664 0000000 0000000 00000006671 14334560322 0017624 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot API testsuite
* Copyright (c) 2016 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
#include "tests.h"
struct testsuite_info {
const char *name;
Suite *(*get_suite)(void);
};
static struct testsuite_info suites[] = {
{ "path", tests_get_path_suite },
{ "alias", tests_get_alias_suite },
{ "fsio", tests_get_fsio_suite },
{ NULL, NULL }
};
static Suite *tests_get_suite(const char *suite) {
register unsigned int i;
for (i = 0; suites[i].name != NULL; i++) {
if (strcmp(suite, suites[i].name) == 0) {
return (*suites[i].get_suite)();
}
}
errno = ENOENT;
return NULL;
}
int main(int argc, char *argv[]) {
const char *log_file = "api-tests.log";
int nfailed = 0;
SRunner *runner = NULL;
char *requested = NULL;
runner = srunner_create(NULL);
/* XXX This log name should be set outside this code, e.g. via environment
* variable or command-line option.
*/
srunner_set_log(runner, log_file);
requested = getenv("VROOT_TEST_SUITE");
if (requested) {
Suite *suite;
suite = tests_get_suite(requested);
if (suite) {
srunner_add_suite(runner, suite);
} else {
fprintf(stderr,
"No such test suite ('%s') requested via VROOT_TEST_SUITE\n",
requested);
return EXIT_FAILURE;
}
} else {
register unsigned int i;
for (i = 0; suites[i].name; i++) {
Suite *suite;
suite = (suites[i].get_suite)();
if (suite) {
srunner_add_suite(runner, suite);
}
}
}
/* Configure the Trace API to write to stderr. */
pr_trace_use_stderr(TRUE);
requested = getenv("VROOT_TEST_NOFORK");
if (requested) {
srunner_set_fork_status(runner, CK_NOFORK);
} else {
requested = getenv("CK_DEFAULT_TIMEOUT");
if (requested == NULL) {
setenv("CK_DEFAULT_TIMEOUT", "60", 1);
}
}
srunner_run_all(runner, CK_NORMAL);
nfailed = srunner_ntests_failed(runner);
if (runner)
srunner_free(runner);
if (nfailed != 0) {
fprintf(stderr, "-------------------------------------------------\n");
fprintf(stderr, " FAILED %d %s\n\n", nfailed,
nfailed != 1 ? "tests" : "test");
fprintf(stderr, " Please send email to:\n\n");
fprintf(stderr, " tj@castaglia.org\n\n");
fprintf(stderr, " containing the `%s' file (in the t/ directory)\n", log_file);
fprintf(stderr, " and the output from running `proftpd -V'\n");
fprintf(stderr, "-------------------------------------------------\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
proftpd-mod_vroot-0.9.11/t/api/tests.h 0000664 0000000 0000000 00000003012 14334560322 0017613 0 ustar 00root root 0000000 0000000 /*
* ProFTPD - mod_vroot API testsuite
* Copyright (c) 2016-2020 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.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*/
/* Testsuite management */
#ifndef MOD_VROOT_TESTS_H
#define MOD_VROOT_TESTS_H
#include "mod_vroot.h"
#ifdef HAVE_CHECK_H
# include
#else
# error "Missing Check installation; necessary for ProFTPD testsuite"
#endif
Suite *tests_get_path_suite(void);
Suite *tests_get_alias_suite(void);
Suite *tests_get_fsio_suite(void);
extern volatile unsigned int recvd_signal_flags;
extern pid_t mpid;
extern server_rec *main_server;
#endif /* MOD_VROOT_TESTS_H */
proftpd-mod_vroot-0.9.11/t/lib/ 0000775 0000000 0000000 00000000000 14334560322 0016301 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/ 0000775 0000000 0000000 00000000000 14334560322 0017517 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/Tests/ 0000775 0000000 0000000 00000000000 14334560322 0020621 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/Tests/Modules/ 0000775 0000000 0000000 00000000000 14334560322 0022231 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/Tests/Modules/mod_vroot.pm 0000664 0000000 0000000 00001116630 14334560322 0024607 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_vroot;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Cwd;
use Digest::MD5;
use File::Path qw(mkpath rmtree);
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 = {
vroot_engine => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_anon => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_anon_limit_write_allow_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_symlink => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_symlink_eloop => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_opt_allow_symlinks_file => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_opt_allow_symlinks_dir_retr => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_opt_allow_symlinks_dir_stor_no_overwrite => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_opt_allow_symlinks_dir_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_opt_allow_symlinks_dir_cwd => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_dir_mkd => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_server_root => {
order => ++$order,
test_class => [qw(forking rootprivs)],
},
vroot_server_root_mkd => {
order => ++$order,
test_class => [qw(bug forking rootprivs)],
},
vroot_alias_file_list => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_list_multi => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_retr => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_stor_no_overwrite => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_dele => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_file_mlsd => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_file_mlst => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dup_same_name => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dup_colliding_aliases => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_delete_source => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_no_source => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_list_no_trailing_slash => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_list_with_trailing_slash => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_list_from_above => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_cwd_list => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_cwd_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_cwd_cdup => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_mkd => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_rmd => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_dir_cwd_mlsd => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_outside_root_cwd_mlsd => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_mlsd_from_above => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_mlst => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_list_multi_issue22 => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_dir_mlsd_multi_issue22 => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_symlink_list => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_symlink_retr => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_symlink_stor_no_overwrite => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_symlink_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_symlink_mlsd => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_symlink_mlst => {
order => ++$order,
test_class => [qw(forking mod_facts)],
},
vroot_alias_ifuser => {
order => ++$order,
test_class => [qw(forking mod_ifsession)],
},
vroot_alias_ifgroup => {
order => ++$order,
test_class => [qw(forking mod_ifsession)],
},
vroot_alias_ifgroup_list_stor => {
order => ++$order,
test_class => [qw(forking mod_ifsession)],
},
vroot_alias_ifclass => {
order => ++$order,
test_class => [qw(forking mod_ifsession)],
},
vroot_showsymlinks_on => {
order => ++$order,
test_class => [qw(bug forking)],
},
vroot_hiddenstores_on_double_dot => {
order => ++$order,
test_class => [qw(bug forking)],
},
vroot_mfmt => {
order => ++$order,
test_class => [qw(bug forking)],
},
vroot_log_extlog_retr => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_log_extlog_stor => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_log_xferlog_retr => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_log_xferlog_stor => {
order => ++$order,
test_class => [qw(forking)],
},
# XXX Currently does not work due to matching logic, and to
# mod_vroot's session.chroot_path machinations.
# vroot_config_limit_write => {
# order => ++$order,
# test_class => [qw(bug forking)],
# },
vroot_config_deleteabortedstores_conn_aborted => {
order => ++$order,
test_class => [qw(bug forking)],
},
vroot_config_deleteabortedstores_cmd_aborted => {
order => ++$order,
test_class => [qw(bug forking)],
},
vroot_alias_var_u_file => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_var_u_dir => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_var_u_dir_with_stor_mff => {
order => ++$order,
test_class => [qw(forking)],
},
vroot_alias_var_u_symlink_dir => {
order => ++$order,
test_class => [qw(forking)],
},
# See:
# https://github.com/Castaglia/proftpd-mod_vroot/issues/4
vroot_alias_bad_src_dst_check_bug4 => {
order => ++$order,
test_class => [qw(bug forking)],
},
# See:
# https://github.com/Castaglia/proftpd-mod_vroot/issues/5
vroot_alias_bad_alias_dirscan_bug5 => {
order => ++$order,
test_class => [qw(bug forking)],
},
# See: https://github.com/proftpd/proftpd/issues/59
vroot_alias_enametoolong_bug59 => {
order => ++$order,
test_class => [qw(bug forking)],
},
# See: https://github.com/proftpd/proftpd/issues/1491
vroot_root_paths_hidden_issue1491 => {
order => ++$order,
test_class => [qw(bug forking)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
return testsuite_get_runnable_tests($TESTS);
# XXX test file aliases where the alias includes directories which do not
# exist. Should we allow traversal of these kinds of aliases (if so, what
# real directory do we use for perms, ownership? What would a CWD into
# such a path component mean?), or only allow retrieval/storage to that
# alias but not traversal?
}
# 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: $!");
}
}
sub prep_test_symlink {
my $setup = shift;
my $symlink_file = shift;
my $mode = shift;
$mode = 0644 unless defined($mode);
# Make sure that, if we're running as root, the symlink has permissions/privs
# set for the account we create.
#
# NOTE: Perl does NOT support lchmod(2), lchown(2), so...this may not always
# do what we want.
if ($< == 0) {
unless (chmod($mode, $symlink_file)) {
die("Can't set perms on $symlink_file to $mode: $!");
}
unless (chown($setup->{uid}, $setup->{gid}, $symlink_file)) {
die("Can't set owner of $symlink_file to $setup->{uid}/$setup->{gid}: $!");
}
}
}
# Test cases
sub vroot_engine {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => $setup->{home_dir},
},
'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->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to change up and out of the vroot
($resp_code, $resp_msg) = $client->quote("CWD", "..");
$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"));
$expected = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
($resp_code, $resp_msg) = $client->cdup();
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'CDUP 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 = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$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 vroot_anon {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
if (open(my $fh, ">> $setup->{config_file}")) {
print $fh <
User $setup->{user}
Group $setup->{group}
EOC
unless (close($fh)) {
die("Can't write $setup->{config_file}: $!");
}
} else {
die("Can't open $setup->{config_file}: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to change up and out of the vroot
($resp_code, $resp_msg) = $client->quote("CWD", "..");
$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"));
$expected = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
($resp_code, $resp_msg) = $client->cdup();
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'CDUP 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 = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$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 vroot_anon_limit_write_allow_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $anon_dir = File::Spec->rel2abs($tmpdir);
my $uploads_dir = File::Spec->rel2abs("$anon_dir/uploads");
create_test_dir($setup, $uploads_dir);
my $test_file = File::Spec->rel2abs("$uploads_dir/test.txt");
# Test this config:
#
#
#
# DenyAll
#
#
#
#
# AllowAll
#
#
#
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',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
if (open(my $fh, ">> $setup->{config_file}")) {
print $fh <
User $setup->{user}
Group $setup->{group}
RequireValidShell off
DenyAll
# Ideally there would be no leading slash here, but because of how
# mod_vroot alters things (see Issue #1), the leading slash makes
# the test succeed.
AllowAll
EOC
unless (close($fh)) {
die("Can't write $setup->{config_file}: $!");
}
} else {
die("Can't open $setup->{config_file}: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow 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->stor_raw('uploads/test.txt');
unless ($conn) {
die("STOR uploads/test.txt failed:" . $client->response_code() . " " .
$client->response_msg());
}
my $buf = "Hello, World!\n";
$conn->write($buf, length($buf), 15);
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();
$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 vroot_symlink {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
# Create a symlink to a file that is outside of the vroot
my $test_file = File::Spec->rel2abs("$tmpdir/bar.txt");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'foo.txt';
unless (symlink("../bar.txt", $symlink_file)) {
die("Can't symlink '../bar.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot.fsio:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => $home_dir,
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
sleep(1);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# Response:\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;
}
}
if (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly not empty");
}
# Try to download from the symlink
$conn = $client->retr_raw('foo.txt');
if ($conn) {
die("RETR test.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
$client->quit();
my $expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'foo.txt: (No such file or directory|Not a regular file)';
$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, 15) };
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 vroot_symlink_eloop {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
# Create a symlink to a file that is outside of the vroot
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
create_test_file($setup, $test_file);
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'test.txt';
unless (symlink("../test.txt", $symlink_file)) {
die("Can't symlink '../test.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => $home_dir,
},
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
sleep(1);
eval { $conn->close() };
# 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;
}
}
if (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly not empty");
}
# Try to download from the symlink
$conn = $client->retr_raw('test.txt');
if ($conn) {
die("RETR test.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
# We expect this because the "../test.txt" -> "test.txt" symlink
# causes mod_vroot to handle "../test.txt" as "test.txt", which is
# a symlink -- hence the loop.
$expected = 'test.txt: (Too many levels of symbolic links|Not a regular file)';
$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 vroot_opt_allow_symlinks_file {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
# Create a symlink to a file that is outside of the vroot
my $test_file = File::Spec->rel2abs("$tmpdir/bar.txt");
create_test_file($setup, $test_file);
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'foo.txt';
unless (symlink("../bar.txt", $symlink_file)) {
die("Can't symlink '../bar.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => $home_dir,
},
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
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);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'foo.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to download from the symlink
$conn = $client->retr_raw('foo.txt');
unless ($conn) {
die("RETR foo.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
sleep(1);
eval { $conn->close() };
$resp_code = $client->response_code();
$resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg);
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_opt_allow_symlinks_dir_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
my $public_dir = File::Spec->rel2abs("$tmpdir/public");
create_test_dir($setup, $public_dir);
# Create a symlink to a directory that is outside of the vroot
my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
create_test_file($setup, $test_file);
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'public';
unless (symlink("../public", $symlink_file)) {
die("Can't symlink '../public' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file, 0755);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20 vroot.fsio:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => $home_dir,
},
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 15);
sleep(1);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'public' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to download from the symlink
$conn = $client->retr_raw('public/test.txt');
unless ($conn) {
die("RETR public/test.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 15);
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 vroot_opt_allow_symlinks_dir_stor_no_overwrite {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $uid = 500;
my $gid = 500;
mkpath($home_dir);
my $public_dir = File::Spec->rel2abs("$tmpdir/public");
mkpath($public_dir);
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir, $public_dir)) {
die("Can't set perms on $home_dir, $public_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir, $public_dir)) {
die("Can't set owner of $home_dir, $public_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
# Create a symlink to a directory that is outside of the vroot
my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("../public", "public")) {
die("Can't symlink '../public' to 'public': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'AllowSymlinks',
DefaultRoot => $home_dir,
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'public' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to upload to the symlink
$conn = $client->stor_raw('public/test.txt');
if ($conn) {
die("STOR public/test.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "public/test.txt: Overwrite permission denied";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_opt_allow_symlinks_dir_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
my $public_dir = File::Spec->rel2abs("$tmpdir/public");
create_test_dir($setup, $public_dir);
# Create a symlink to a directory that is outside of the vroot
my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
create_test_file($setup, $test_file);
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'public';
unless (symlink("../public", $symlink_file)) {
die("Can't symlink '../public' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file, 0755);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot.fsio:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => $home_dir,
},
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 15);
sleep(1);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'public' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to upload to the symlink
$conn = $client->stor_raw('public/test.txt');
unless ($conn) {
die("STOR public/test.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = "Farewell cruel world!";
$conn->write($buf, length($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 vroot_opt_allow_symlinks_dir_cwd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $uid = 500;
my $gid = 500;
mkpath($home_dir);
my $public_dir = File::Spec->rel2abs("$tmpdir/public");
mkpath($public_dir);
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir, $public_dir)) {
die("Can't set perms on $home_dir, $public_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir, $public_dir)) {
die("Can't set owner of $home_dir, $public_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
# Create a symlink to a directory that is outside of the vroot
my $test_file = File::Spec->rel2abs("$public_dir/test.txt");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("../public", "public")) {
die("Can't symlink '../public' to 'public': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'AllowSymlinks',
DefaultRoot => $home_dir,
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'public' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to change into the symlink'd directory
my ($resp_code, $resp_msg) = $client->cwd('public');
$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'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'test.txt' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
($resp_code, $resp_msg) = $client->pwd();
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = '"/public" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
# Go up to the parent directory
($resp_code, $resp_msg) = $client->cdup();
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "CDUP 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 = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_dir_mkd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $sub_dir = File::Spec->rel2abs("$tmpdir/foo.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 => 'fsio:10 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
Directory => {
# BUG: This should be $sub_dir. But due to how mod_vroot currently
# works, the path has to be modified to match the
# mod_vroot. (I.e. for the purposes of this test, just '/foo.d').
# Sigh.
# $sub_dir => {
'/foo.d' => {
# Test the UserOwner directive in the setting
UserOwner => 'root',
},
},
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->cwd('foo.d');
$client->mkd('bar.d');
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/foo.d/bar.d\" - Directory successfully created";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $setup->{log_file}")) {
# Look for the 'smkdir' fsio channel trace message; it will tell us
# whether the UserOwner directive from the section was
# successfully found.
my $have_smkdir_line = 0;
my $line;
while ($line = <$fh>) {
chomp($line);
if ($line =~ /smkdir/) {
$have_smkdir_line = 1;
last;
}
}
close($fh);
$self->assert($have_smkdir_line,
test_msg("Did not find expected 'fsio' channel TraceLog line in $setup->{log_file}"));
if ($line =~ /UID (\S+),/) {
my $smkdir_uid = $1;
if ($< == 0) {
$self->assert($smkdir_uid == 0 || $smkdir_uid == -1,
test_msg("Expected UID 0 or -1, got $smkdir_uid"));
}
} else {
die("Unexpectedly formatted 'fsio' channel TraceLog line '$line'");
}
} else {
die("Can't read $setup->{log_file}: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_server_root {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $uid = 500;
my $gid = 500;
mkpath($home_dir);
my $abs_tmpdir = File::Spec->rel2abs($tmpdir);
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootServerRoot => $abs_tmpdir,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
if (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly not empty");
}
# Try to change up and out of the vroot
($resp_code, $resp_msg) = $client->quote("CWD", "..");
$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"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
if (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly not empty");
}
($resp_code, $resp_msg) = $client->cdup();
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'CDUP 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 = '"/" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
if (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly not empty");
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_server_root_mkd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $uid = 500;
my $gid = 500;
mkpath($home_dir);
my $abs_tmpdir = File::Spec->rel2abs($tmpdir);
my $test_dir = File::Spec->rel2abs("$home_dir/test.d");
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootServerRoot => $abs_tmpdir,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->mkd('test.d');
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/test.d\" - Directory successfully created";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->cwd('test.d');
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = 'CWD command successful';
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->pwd();
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/test.d\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_list {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_list_multi {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file1 = '~/bar.txt';
my $dst_file2 = 'baz.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $log_file",
'DefaultRoot ~',
"VRootAlias $src_file $dst_file1",
"VRootAlias $src_file $dst_file2",
"VRootAlias $src_file /tmp/foo/bar/baz",
],
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
'baz.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
create_test_dir($setup, $test_dir);
my $src_file = File::Spec->rel2abs("$test_dir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to download the aliased file
my $conn = $client->retr_raw('bar.txt');
unless ($conn) {
die("RETR bar.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
my $count = $conn->read($buf, 8192, 5);
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();
my $expected = 14;
$self->assert($expected == $count,
test_msg("Expected size $expected, got $count"));
};
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 vroot_alias_file_stor_no_overwrite {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to upload to the aliased file
my $conn = $client->stor_raw('bar.txt');
if ($conn) {
die("STOR bar.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'bar.txt: Overwrite permission denied';
$self->assert($expected eq $resp_msg,
test_msg("Expected '$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 vroot_alias_file_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to upload to the aliased file
my $conn = $client->stor_raw('bar.txt');
unless ($conn) {
die("STOR bar.txt failed: " . $client->response_code() . ' ' .
$client->response_msg());
}
my $buf = "Farewell, cruel world";
$conn->write($buf, length($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 vroot_alias_file_dele {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
# Try to delete the aliased file
eval { $client->dele('bar.txt') };
unless ($@) {
die("DELE bar.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = 'bar.txt: Permission denied';
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_mlsd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = 'bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->mlsd_raw('bar.txt');
if ($conn) {
die("MLSD bar.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "'bar.txt' is not a directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_mlst {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = 'bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->mlst('bar.txt');
my $expected;
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; \/bar.txt$';
$self->assert(qr/$expected/, $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dup_same_name {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/foo.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dup_colliding_aliases {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $log_file",
'DefaultRoot ~',
"VRootAlias $src_file $dst_file",
"VRootAlias $auth_user_file $dst_file",
],
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_delete_source {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
# Delete the source of the alias, and make sure that neither source
# nor alias appear in the directory listing.
($resp_code, $resp_msg) = $client->dele('foo.txt');
$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'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_no_source {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_list_no_trailing_slash {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.d' => 1,
'bar.d' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_list_with_trailing_slash {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir/ $dst_dir/",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.d' => 1,
'bar.d' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_list_from_above {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw('bar.d');
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_cwd_list {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->cwd('bar.d');
$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"));
$expected = "\"/bar.d\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_cwd_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_dir = File::Spec->rel2abs("$tmpdir/sub1.d/sub2.d/foo.d");
create_test_dir($setup, $src_dir);
my $dst_dir = '~/bar.d';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->cwd('bar.d');
$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"));
$expected = "\"/bar.d\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->stor_raw('test.txt');
unless ($conn) {
die("Failed to STOR: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = "Hello, World!";
$conn->write($buf, length($buf), 5);
sleep(1);
eval { $conn->close() };
$resp_code = $client->response_code();
$resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg);
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_alias_dir_cwd_cdup {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->cwd('bar.d');
$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"));
$expected = "\"/bar.d\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
($resp_code, $resp_msg) = $client->cdup();
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "CDUP 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 = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_mkd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = 'bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
eval { $client->mkd('bar.d') };
unless ($@) {
die("MKD bar.d succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "bar.d: File exists";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_rmd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = 'bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
eval { $client->rmd('bar.d') };
unless ($@) {
die("RMD bar.d succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "bar.d: Permission denied";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_cwd_mlsd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->cwd('bar.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'"));
my $conn = $client->mlsd_raw();
unless ($conn) {
die("Failed to MLSD: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
$expected = {
'.' => 1,
'..' => 1,
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_mlsd_from_above {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->mlsd_raw('bar.d');
unless ($conn) {
die("Failed to MLSD bar.d: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
my $expected = {
'.' => 1,
'..' => 1,
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_outside_root_cwd_mlsd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/baz");
mkpath($home_dir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 table:20 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->cwd('bar.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'"));
my $conn = $client->mlsd_raw();
unless ($conn) {
die("Failed to MLSD: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
$expected = {
'.' => 1,
'..' => 1,
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_outside_root_cwd_mlsd_cwd_ls {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/baz");
mkpath($home_dir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
my $sub_dir = File::Spec->rel2abs("$tmpdir/foo.d/subdir");
mkpath($sub_dir);
my $test_file4 = File::Spec->rel2abs("$tmpdir/foo.d/subdir/test.txt");
if (open(my $fh, "> $test_file4")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file4: $!");
}
} else {
die("Can't open $test_file4: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 table:20 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->cwd('bar.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'"));
my $conn = $client->mlsd_raw();
unless ($conn) {
die("Failed to MLSD: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
$expected = {
'.' => 1,
'..' => 1,
'subdir' => 1,
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
($resp_code, $resp_msg) = $client->cwd('subdir');
$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'"));
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'test.txt' => 1,
};
$ok = 1;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
($resp_code, $resp_msg) = $client->pwd();
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = '"/bar.d/subdir" is the current directory';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_mlst {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
create_test_dir($setup, $src_dir);
my $dst_dir = 'bar.d';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->mlst('bar.d');
my $expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'modify=\d+;perm=flcdmpe;type=dir;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; \/bar\.d$';
$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 vroot_alias_dir_list_multi_issue22 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_dir1 = File::Spec->rel2abs("$tmpdir/foo1.d");
create_test_dir($setup, $src_dir1);
my $src_file1 = File::Spec->rel2abs("$src_dir1/test.txt");
create_test_file($setup, $src_file1);
my $src_dir2 = File::Spec->rel2abs("$tmpdir/foo2.d");
create_test_dir($setup, $src_dir2);
my $dst_dir1 = 'bar1.d';
create_test_dir($setup, File::Spec->rel2abs("$tmpdir/$dst_dir1"));
my $dst_dir2 = 'bar2.d';
my $dst_dir3 = 'bar3.d';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20 vroot.alias:20 vroot.fsio:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $setup->{log_file}",
'DefaultRoot ~',
"VRootAlias $src_dir1 $dst_dir1/$dst_dir2",
"VRootAlias $src_dir1 $dst_dir2",
"VRootAlias $src_dir2 $dst_dir3",
],
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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+)$/) {
# Watch for unexpected duplicates
if (exists($res->{$1})) {
die("LIST data already contains $1");
}
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'bar1.d' => 1,
'bar2.d' => 1,
'bar3.d' => 1,
'foo1.d' => 1,
'foo2.d' => 1,
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch = '';
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->cwd('bar1.d/bar2.d');
$conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\n$buf\n";
}
# We have to be careful of the fact that readdir returns directory
# entries in an unordered fashion.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
# Watch for unexpected duplicates
if (exists($res->{$1})) {
die("LIST data already contains $1");
}
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'test.txt' => 1,
};
$ok = 1;
$mismatch = '';
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$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 vroot_alias_dir_mlsd_multi_issue22 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_dir1 = File::Spec->rel2abs("$tmpdir/foo1.d");
create_test_dir($setup, $src_dir1);
my $src_dir2 = File::Spec->rel2abs("$tmpdir/foo2.d");
create_test_dir($setup, $src_dir2);
my $dst_dir1 = 'bar1.d';
my $dst_dir2 = 'bar2.d';
my $dst_dir3 = 'bar3.d';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20 vroot.fsio:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $setup->{log_file}",
'DefaultRoot ~',
"VRootAlias $src_dir1 $dst_dir1/$dst_dir2",
"VRootAlias $src_dir1 $dst_dir2",
"VRootAlias $src_dir2 $dst_dir3",
],
'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 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->mlsd_raw();
unless ($conn) {
die("Failed to MLSD: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
# Watch for unexpected duplicates
if (exists($res->{$1})) {
die("MLSD data already contains $1");
}
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
my $expected = {
'.' => 1,
'..' => 1,
'bar2.d' => 1,
'bar3.d' => 1,
'foo1.d' => 1,
'foo2.d' => 1,
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
my $ok = 1;
my $mismatch = '';
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
$client->mlst('bar1.d/bar2.d');
$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 vroot_alias_symlink_list {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'foo.lnk' => 1,
'bar.lnk' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_symlink_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $cwd = getcwd();
unless (chdir($setup->{home_dir})) {
die("Can't chdir to $setup->{home_dir}: $!");
}
my $symlink_file = 'foo.lnk';
unless (symlink("./foo.txt", $symlink_file)) {
die("Can't symlink './foo.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to download the aliased file
my $conn = $client->retr_raw('bar.lnk');
unless ($conn) {
die("RETR bar.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
my $count = $conn->read($buf, 8192, 5);
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();
my $expected = 14;
$self->assert($expected == $count,
test_msg("Expected size $expected, got $count"));
};
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 vroot_alias_symlink_stor_no_overwrite {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $cwd = getcwd();
unless (chdir($setup->{home_dir})) {
die("Can't chdir to $setup->{home_dir}: $!");
}
my $symlink_file = 'foo.lnk';
unless (symlink("./foo.txt", $symlink_file)) {
die("Can't symlink './foo.txt' to '$symlink_file': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to upload to the aliased symlink
my $conn = $client->stor_raw('bar.lnk');
if ($conn) {
die("STOR bar.lnk succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = 'bar.lnk: Overwrite permission denied';
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_alias_symlink_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $cwd = getcwd();
unless (chdir($setup->{home_dir})) {
die("Can't chdir to $setup->{home_dir}: $!");
}
my $symlink_file = 'foo.lnk';
unless (symlink("./foo.txt", $symlink_file)) {
die("Can't symlink './foo.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# Try to upload to the aliased symlink
my $conn = $client->stor_raw('bar.lnk');
unless ($conn) {
die("STOR bar.lnk failed: " . $client->response_code() . ' ' .
$client->response_msg());
}
my $buf = "Farewell, cruel world";
$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);
}
sub vroot_alias_symlink_mlsd {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->mlsd_raw('bar.lnk');
if ($conn) {
die("MLSD bar.lnk succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "'bar.lnk' is not a directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_symlink_mlst {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'AllowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->mlst('bar.lnk');
my $expected;
$expected = 250;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; \/bar\.lnk$';
$self->assert(qr/$expected/, $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_ifuser {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file1 = '~/bar.txt';
my $dst_file2 = '~/baz.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
if (open(my $fh, ">> $config_file")) {
print $fh <
VRootAlias $src_file $dst_file1
VRootAlias $src_file $dst_file2
EOC
unless (close($fh)) {
die("Can't write $config_file: $!");
}
} else {
die("Can't open $config_file: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_ifgroup {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file1 = '~/bar.txt';
my $dst_file2 = '~/baz.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
if (open(my $fh, ">> $config_file")) {
print $fh <
VRootAlias $src_file $dst_file1
VRootAlias $src_file $dst_file2
EOC
unless (close($fh)) {
die("Can't write $config_file: $!");
}
} else {
die("Can't open $config_file: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_ifgroup_list_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
mkpath($home_dir);
my $uid = 500;
my $gid = 500;
my $shared_dir = File::Spec->rel2abs("$tmpdir/shared");
mkpath($shared_dir);
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir, $shared_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir, $shared_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $test_file = File::Spec->rel2abs("$shared_dir/test.txt");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
if (open(my $fh, ">> $config_file")) {
print $fh <
VRootAlias $shared_dir ~/shared
EOC
unless (close($fh)) {
die("Can't write $config_file: $!");
}
} else {
die("Can't open $config_file: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
$resp_code = $client->response_code();
$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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'shared' => 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"));
($resp_code, $resp_msg) = $client->cwd('shared');
$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'"));
$conn = $client->list_raw('-al');
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = '';
$conn->read($buf, 8192, 5);
eval { $conn->close() };
$resp_code = $client->response_code();
$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.
$res = {};
$lines = [split(/\n/, $buf)];
foreach my $line (@$lines) {
if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'.' => 1,
'..' => 1,
'test.txt' => 1,
};
$ok = 1;
$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"));
$conn = $client->stor_raw('bar.txt');
unless ($conn) {
die("Failed to STOR bar.txt: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = "Farewell, cruel world!\n";
$conn->write($buf, length($buf), 5);
eval { $conn->close() };
$resp_code = $client->response_code();
$resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg);
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_ifclass {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file1 = '~/bar.txt';
my $dst_file2 = '~/baz.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
if (open(my $fh, ">> $config_file")) {
print $fh <
From 127.0.0.1
VRootAlias $src_file $dst_file1
VRootAlias $src_file $dst_file2
EOC
unless (close($fh)) {
die("Can't write $config_file: $!");
}
} else {
die("Can't open $config_file: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'foo.txt' => 1,
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_showsymlinks_on {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/home");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
create_test_dir($setup, $home_dir);
# See:
#
# http://forums.proftpd.org/smf/index.php/topic,5207.0.html
# Create a symlink to a file that is outside of the vroot
my $outside_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $outside_file);
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
my $symlink_file = 'foo.lnk';
unless (symlink("../foo.txt", $symlink_file)) {
die("Can't symlink '../foo.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
# Now create a symlink which points inside the vroot
my $inside_file = File::Spec->rel2abs("$tmpdir/home/bar.txt");
create_test_file($setup, $inside_file);
$cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
$symlink_file = 'bar.lnk';
unless (symlink("./bar.txt", $symlink_file)) {
die("Can't symlink './bar.txt' to '$symlink_file': $!");
}
prep_test_symlink($setup, $symlink_file);
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
# ShowSymlinks is on by default, but explicitly list it here for
# completeness
ShowSymlinks => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => $home_dir,
},
'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 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->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
sleep(1);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'bar.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
# Try to download from the symlink
$conn = $client->retr_raw('bar.txt');
unless ($conn) {
die("RETR bar.txt failed: " . $client->response_code() . " " .
$client->response_msg());
}
$conn->read($buf, 8192, 5);
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 vroot_hiddenstores_on_double_dot {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
HiddenStores => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
# Try to upload a file whose name starts with a period
my $conn = $client->stor_raw('.foo');
unless ($conn) {
die("STOR .foo failed: " . $client->response_code() . ' ' .
$client->response_msg());
}
my $buf = "Farewell, cruel world";
$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($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_mfmt {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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 => 'fsio:10 vroot:20 vroot.fsio:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
# First try MFMT using relative paths
my $path = './test.txt';
my ($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
my $expected = 213;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "Modify=20020717210715; $path";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$path = "test.txt";
($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
$expected = 213;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "Modify=20020717210715; $path";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
# Next try an absolute path (from client perspective)
$path = "/test.txt";
($resp_code, $resp_msg) = $client->mfmt('20020717210715', $path);
$expected = 213;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "Modify=20020717210715; $path";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_log_extlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
create_test_file($setup, $test_file);
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 => 'fsio:10 vroot.fsio:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log READ custom",
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
'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 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->retr_raw('test.txt');
unless ($conn) {
die("Failed to RETR test.txt: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
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);
# Now, read in the ExtendedLog, and see whether the %f variable was
# properly written out.
eval {
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_log_extlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
my $ext_log = File::Spec->rel2abs("$tmpdir/custom.log");
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 jot:20 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log WRITE custom",
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->stor_raw('test.txt');
unless ($conn) {
die("Failed to STOR test.txt: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = "Hello, World!";
$conn->write($buf, length($buf), 5);
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($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
# Now, read in the ExtendedLog, and see whether the %f variable was
# properly written out.
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_log_xferlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
create_test_file($setup, $test_file);
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
$client->type('binary');
my $conn = $client->retr_raw('test.txt');
unless ($conn) {
die("Failed to RETR test.txt: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
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);
eval {
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# $line\n";
}
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line,
test_msg("Expected '$expected', got '$line'"));
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
test_msg("Expected '$expected', got '$remote_host'"));
$expected = -s $test_file;
$self->assert($expected == $filesz,
test_msg("Expected '$expected', got '$filesz'"));
$expected = $test_file;
$self->assert($expected eq $filename,
test_msg("Expected '$expected', got '$filename'"));
$expected = 'b';
$self->assert($expected eq $xfer_type,
test_msg("Expected '$expected', got '$xfer_type'"));
$expected = $setup->{user};
$self->assert($expected eq $user_name,
test_msg("Expected '$expected', got '$user_name'"));
}
} else {
die("Can't read $xfer_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_log_xferlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
$client->type('binary');
my $conn = $client->stor_raw('test.txt');
unless ($conn) {
die("Failed to STOR test.txt: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf = "Hello, World!";
$conn->write($buf, length($buf), 5);
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($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+ftp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line,
test_msg("Expected '$expected', got '$line'"));
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
test_msg("Expected '$expected', got '$remote_host'"));
$expected = -s $test_file;
$self->assert($expected == $filesz,
test_msg("Expected '$expected', got '$filesz'"));
$expected = $test_file;
$self->assert($expected eq $filename,
test_msg("Expected '$expected', got '$filename'"));
$expected = 'b';
$self->assert($expected eq $xfer_type,
test_msg("Expected '$expected', got '$xfer_type'"));
$expected = $user;
$self->assert($expected eq $user_name,
test_msg("Expected '$expected', got '$user_name'"));
}
} else {
die("Can't read $xfer_log: $!");
}
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_config_limit_write {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'directory:20 fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($config_file, $config);
if (open(my $fh, ">> $config_file")) {
print $fh <
DenyAll
EOC
unless (close($fh)) {
die("Can't write $config_file: $!");
}
} else {
die("Can't open $config_file: $!");
}
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($user, $passwd);
my $conn = $client->stor_raw('test.txt');
if ($conn) {
eval { $conn->close() };
die("STOR test.txt succeeded unexpectedly");
}
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
my $expected;
$expected = 550;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "test.txt: Permission denied";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_config_deleteabortedstores_conn_aborted {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
HiddenStores => 'on',
DeleteAbortedStores => 'on',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my $conn = $client->stor_raw('test.txt');
unless ($conn) {
die("Failed to STOR test.txt: " . $client->response_code() . ' ' .
$client->response_msg());
}
my $buf = "Hello, World!";
$conn->write($buf, length($buf), 5);
unless (-f $hidden_file) {
die("File $hidden_file does not exist as expected");
}
eval { $conn->abort() };
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg, 1);
if (-f $test_file) {
die("File $test_file exists unexpectedly");
}
if (-f $hidden_file) {
die("File $hidden_file exists unexpectedly");
}
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_config_deleteabortedstores_cmd_aborted {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $hidden_file = File::Spec->rel2abs("$tmpdir/.in.test.txt.");
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
# There's a heisenbug lurking here, that only comes out in the GitHub
# CI workflow environment. To keep it at bay, most times, we enable
# verbose output programmatically.
$ENV{TEST_VERBOSE} = 1;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
HiddenStores => 'on',
DeleteAbortedStores => 'on',
TimeoutLinger => 2,
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
'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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 3);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->stor_raw('test.txt');
unless ($conn) {
die("Failed to STOR test.txt: " . $client->response_code() . ' ' .
$client->response_msg());
}
my $buf = "Hello, World!";
$conn->write($buf, length($buf), 15);
unless (-f $hidden_file) {
die("File $hidden_file does not exist as expected");
}
eval { $client->quote('ABOR') };
sleep(1);
my $resp_code = $client->response_code();
my $resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg, 1) if $resp_code != 421;
# No need to close our data conn here; it was closed by the ABOR.
$client->quit();
if (-f $test_file) {
die("File $test_file exists unexpectedly");
}
if (-f $hidden_file) {
die("File $hidden_file exists unexpectedly");
}
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh, 10) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
test_cleanup($setup->{log_file}, $ex);
delete($ENV{TEST_VERBOSE});
}
sub vroot_alias_var_u_file {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
mkpath($sub_dir);
my $src_path = File::Spec->rel2abs("$sub_dir/foo.txt");
if (open(my $fh, "> $src_path")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_path: $!");
}
} else {
die("Can't open $src_path: $!");
}
my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.txt';
my $dst_file = '~/bar.txt';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'bar.txt' => 1,
'proftpd' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = '-rw-r--r--';
$self->assert($mode eq $res->{'bar.txt'},
test_msg("Expected '$mode' for 'bar.txt', got '$res->{'bar.txt'}'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_var_u_dir {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
mkpath($sub_dir);
my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
mkpath($src_path);
my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.d';
my $dst_file = '~/bar.d';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'bar.d' => 1,
'proftpd' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = 'drwxr-xr-x';
$self->assert($mode eq $res->{'bar.d'},
test_msg("Expected '$mode' for 'bar.d', got '$res->{'bar.d'}'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_var_u_dir_with_stor_mff {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $sub_dir = File::Spec->rel2abs("$tmpdir/sub.d");
create_test_dir($setup, $sub_dir);
my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
create_test_dir($setup, $src_path);
my $src_file = File::Spec->rel2abs($tmpdir) . '/sub.d/foo.d/';
my $dst_file = '/%u';
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'DEFAULT:10 vroot:20 fileperms:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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 server to start up
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
sleep(1);
$resp_code = $client->response_code();
$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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'sub.d' => 1,
'proftpd' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = 'drwxr-xr-x';
$self->assert($mode eq $res->{'proftpd'},
test_msg("Expected '$mode' for 'proftpd', got '$res->{'proftpd'}'"));
# Now change into the aliased directory, and upload a file there
($resp_code, $resp_msg) = $client->cwd('proftpd');
my $file = 'test.txt';
$conn = $client->stor_raw($file);
unless ($conn) {
die("STOR $file failed: " . $client->response_code() . " " .
$client->response_msg());
}
$buf = "Hello, World!\n";
$conn->write($buf, length($buf), 25);
eval { $conn->close() };
sleep(1);
$resp_code = $client->response_code();
$resp_msg = $client->response_msg();
$self->assert_transfer_ok($resp_code, $resp_msg);
my $facts = 'Modify=20020717210715;Create=20120807064710';
($resp_code, $resp_msg) = $client->mff($facts, $file);
$expected = 213;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "$facts $file";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh, 45) };
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 vroot_alias_var_u_symlink_dir {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $sub_dir = File::Spec->rel2abs("$tmpdir/$user");
mkpath($sub_dir);
my $src_path = File::Spec->rel2abs("$sub_dir/foo.d");
mkpath($src_path);
my $cwd = getcwd();
unless (chdir($sub_dir)) {
die("Can't chdir to $sub_dir: $!");
}
unless (symlink('foo.d', "./foo.lnk")) {
die("Can't symlink 'foo.d' to './foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_file = File::Spec->rel2abs($tmpdir) . '/%u/foo.lnk';
my $dst_file = '/%u.lnk';
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
ShowSymlinks => 'off',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg);
($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
# 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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
'proftpd.lnk' => 1,
'proftpd' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = 'drwxr-xr-x';
$self->assert($mode eq $res->{'proftpd.lnk'},
test_msg("Expected '$mode' for 'proftpd.lnk', got '$res->{'proftpd.lnk'}'"));
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_alias_bad_src_dst_check_bug4 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
# In order for the real /tmp/vroot.d directory to be visible, via
# VRootAlias, within the vroot, the leading /tmp directory needs to
# actually exist with the vroot. In other words, the path needs to be
# real, even if the leaf is virtual.
my $user_tmpdir = File::Spec->rel2abs("$setup->{home_dir}/tmp");
mkpath($user_tmpdir);
my $test_dir = File::Spec->rel2abs("/tmp/vroot.d");
mkpath($test_dir);
my $test_file = File::Spec->rel2abs("$test_dir/test.txt");
if (open(my $fh, "> $test_file")) {
close($fh);
} else {
die("Can't open $test_file: $!");
}
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $user_tmpdir, $test_dir)) {
die("Can't set perms on $user_tmpdir to 0755: $!");
}
unless (chown($setup->{uid}, $setup->{gid}, $user_tmpdir, $test_dir)) {
die("Can't set owner of $user_tmpdir to $setup->{uid}/$setup->{gid}: $!");
}
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
ShowSymlinks => 'off',
IfModules => {
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$test_dir ~/tmp/vroot.d",
},
'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 for server startup
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my ($resp_code, $resp_msg) = $client->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->cwd('/tmp/vroot.d');
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'test.txt' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = '-rw-r--r--';
$self->assert($mode eq $res->{'test.txt'},
test_msg("Expected '$mode' for 'test.txt', got '$res->{'test.txt'}'"));
$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 vroot_alias_bad_alias_dirscan_bug5 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $home_dir = File::Spec->rel2abs("$tmpdir/proftpd");
my $setup = test_setup($tmpdir, 'vroot', undef, undef, undef, undef, undef,
$home_dir);
# In order for the real /tmp/vroot.d directory to be visible, via
# VRootAlias, within the vroot, the leading /tmp directory needs to
# actually exist with the vroot. In other words, the path needs to be
# real, even if the leaf is virtual.
my $user_tmpdir = File::Spec->rel2abs("$home_dir/tmp");
create_test_dir($setup, $user_tmpdir);
my $test_dir = File::Spec->rel2abs("$tmpdir/vroot.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 => 'fsio:20 vroot:20 vroot.fsio:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
ShowSymlinks => 'off',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $setup->{log_file}",
'DefaultRoot ~',
"VRootAlias $test_dir ~/vroot.d",
"VRootAlias $test_dir ~/tmp/vroot.d",
],
'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->pwd();
my $expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected response code $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected response message '$expected', got '$resp_msg'"));
$client->cwd('/tmp');
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 5);
eval { $conn->close() };
if ($ENV{TEST_VERBOSE}) {
print STDERR "# response:\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->{$2} = $1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
$expected = {
'vroot.d' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
my $mode = 'drwxr-xr-x';
$self->assert($mode eq $res->{'vroot.d'},
test_msg("Expected '$mode' for 'vroot.d', got '$res->{'vroot.d'}'"));
$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 vroot_alias_enametoolong_bug59 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = test_get_logfile();
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $group = 'ftpd';
my $home_dir = File::Spec->rel2abs("$tmpdir/$user");
mkpath($home_dir);
my $uid = 500;
my $gid = 500;
my $test_dir = File::Spec->rel2abs("/tmp/vroot.d/0001a/10001a/encoding/input");
mkpath($test_dir);
my $test_file = File::Spec->rel2abs("$test_dir/asgard");
if (open(my $fh, "> $test_file")) {
close($fh);
} else {
die("Can't open $test_file: $!");
}
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir, $test_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir, $test_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, $group, $gid, $user);
my $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:20 vroot:20',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
ShowSymlinks => 'off',
IfModules => {
'mod_vroot.c' => [
'VRootEngine on',
"VRootLog $log_file",
'DefaultRoot ~',
"VRootAlias /tmp ~/tmp-vroot-alias",
"VRootAlias $test_dir ~/0001a-input",
],
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $passwd);
my ($resp_code, $resp_msg) = $client->pwd();
my $expected;
$expected = 257;
$self->assert($expected == $resp_code,
test_msg("Expected $expected, got $resp_code"));
$expected = "\"/\" is the current directory";
$self->assert($expected eq $resp_msg,
test_msg("Expected '$expected', got '$resp_msg'"));
my $conn = $client->mlsd_raw('0001a-input');
unless ($conn) {
die("Failed to MLSD: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 30);
eval { $conn->close() };
# 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 =~ /^modify=\S+;perm=\S+;type=\S+;unique=\S+;UNIX\.group=\d+;UNIX\.groupname=\S+;UNIX\.mode=\d+;UNIX\.owner=\d+;UNIX\.ownername=\S+; (.*?)$/) {
$res->{$1} = 1;
}
}
unless (scalar(keys(%$res)) > 0) {
die("MLSD data unexpectedly empty");
}
$expected = {
'.' => 1,
'..' => 1,
'asgard' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in MLSD data")
}
$client->quit();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
test_append_logfile($log_file, $ex);
unlink($log_file);
die($ex);
}
unlink($log_file);
}
sub vroot_root_paths_hidden_issue1491 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
# Note: the actual reproduction recipe for this issue requires the use
# of a single-component root, e.g. "/store". However, I use the
# normal automatically generated temporary directory (of multiple path
# components) here, for the rest of the machinery; the `use_opt` variable
# can be used in the future to run this test using the short `/opt` directory
# as the DefaultRoot. Doing so require that that `/opt` directory be
# created (and populated!) manually.
my $use_opt = 0;
my $root_dir;
if ($use_opt) {
$root_dir = File::Spec->rel2abs('/opt');
} else {
$root_dir = File::Spec->rel2abs("$tmpdir/opt");
mkpath($root_dir);
my $root_files = [qw(
not-opt
opt
optagain
opttest
)];
foreach my $root_file (@$root_files) {
my $path = File::Spec->rel2abs("$root_dir/$root_file");
next if -f $path;
if (open(my $fh, "> $path")) {
close($fh);
} else {
die("Can't open $path: $!");
}
}
if ($< == 0) {
unless (chmod(0755, $root_dir)) {
die("Can't set perms on $root_dir to 0755: $!");
}
unless (chown($setup->{uid}, $setup->{gid}, $root_dir)) {
die("Can't set owner of $root_dir to $setup->{uid}/$setup->{gid}: $!");
}
}
}
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:20 vroot:20 vroot.path:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
ShowSymlinks => 'off',
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => $root_dir,
},
},
};
my ($port, $config_user, $config_group) = config_write($setup->{config_file},
$config);
# Open pipes, for use between the parent and child processes. Specifically,
# the child will indicate when it's done with its test by writing a message
# to the parent.
my ($rfh, $wfh);
unless (pipe($rfh, $wfh)) {
die("Can't open pipe: $!");
}
my $ex;
# Fork child
$self->handle_sigchld();
defined(my $pid = fork()) or die("Can't fork: $!");
if ($pid) {
eval {
# Allow for server startup
sleep(1);
my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
$client->login($setup->{user}, $setup->{passwd});
my $conn = $client->list_raw();
unless ($conn) {
die("Failed to LIST: " . $client->response_code() . " " .
$client->response_msg());
}
my $buf;
$conn->read($buf, 8192, 30);
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 ($ENV{TEST_VERBOSE}) {
print STDERR "# data:\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;
}
}
unless (scalar(keys(%$res)) > 0) {
die("LIST data unexpectedly empty");
}
my $expected = {
'not-opt' => 1,
'opt' => 1,
'optagain' => 1,
'opttest' => 1,
};
my $ok = 1;
my $mismatch;
foreach my $name (keys(%$res)) {
unless (defined($expected->{$name})) {
$mismatch = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$mismatch' appeared in LIST data")
}
$ok = 1;
my $missing;
foreach my $name (keys(%$expected)) {
unless (defined($res->{$name})) {
$missing = $name;
$ok = 0;
last;
}
}
unless ($ok) {
die("Unexpected name '$missing' missing from 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);
}
1;
proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/Tests/Modules/mod_vroot/ 0000775 0000000 0000000 00000000000 14334560322 0024241 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/lib/ProFTPD/Tests/Modules/mod_vroot/sftp.pm 0000664 0000000 0000000 00000311361 14334560322 0025560 0 ustar 00root root 0000000 0000000 package ProFTPD::Tests::Modules::mod_vroot::sftp;
use lib qw(t/lib);
use base qw(ProFTPD::TestSuite::Child);
use strict;
use Cwd;
use Digest::MD5;
use File::Copy;
use File::Path qw(mkpath rmtree);
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 = {
vroot_alias_file_sftp_read => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_write_no_overwrite => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_write => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_stat => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_lstat => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_realpath => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_sftp_remove => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_dir_sftp_readdir => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_dir_sftp_rmdir => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_symlink_sftp_stat => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_symlink_sftp_lstat => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_symlink_sftp_realpath => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
vroot_alias_file_scp_download => {
order => ++$order,
test_class => [qw(forking mod_sftp scp)],
},
vroot_alias_file_scp_upload => {
order => ++$order,
test_class => [qw(forking mod_sftp scp)],
},
vroot_sftp_log_extlog_retr => {
order => ++$order,
test_class => [qw(bug forking mod_sftp sftp)],
},
vroot_sftp_log_xferlog_retr => {
order => ++$order,
test_class => [qw(bug forking mod_sftp sftp)],
},
vroot_sftp_log_extlog_stor => {
order => ++$order,
test_class => [qw(bug forking mod_sftp sftp)],
},
vroot_sftp_log_xferlog_stor => {
order => ++$order,
test_class => [qw(bug forking mod_sftp sftp)],
},
vroot_scp_log_extlog_retr => {
order => ++$order,
test_class => [qw(bug forking mod_sftp scp)],
},
vroot_scp_log_xferlog_retr => {
order => ++$order,
test_class => [qw(bug forking mod_sftp scp)],
},
vroot_scp_log_extlog_stor => {
order => ++$order,
test_class => [qw(bug forking mod_sftp scp)],
},
vroot_scp_log_xferlog_stor => {
order => ++$order,
test_class => [qw(bug forking mod_sftp scp)],
},
vroot_alias_dir_sftp_publickey_issue30 => {
order => ++$order,
test_class => [qw(forking mod_sftp sftp)],
},
};
sub new {
return shift()->SUPER::new(@_);
}
sub list_tests {
return testsuite_get_runnable_tests($TESTS);
# XXX test file aliases where the alias includes directories which do not
# exist. Should we allow traversal of these kinds of aliases (if so, what
# real directory do we use for perms, ownership? What would a CWD into
# such a path component mean?), or only allow retrieval/storage to that
# alias but not traversal?
}
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 routines
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 vroot_alias_file_sftp_read {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.txt', O_RDONLY);
unless ($fh) {
my ($err_code, $err_name) = $sftp->error();
die("Can't open bar.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 explicit destroy the filehandle
$fh = undef;
my $expected = 14;
$self->assert($expected == $size,
test_msg("Expected $expected, got $size"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_sftp_write_no_overwrite {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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: $!");
}
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('bar.txt', O_WRONLY|O_TRUNC, 0644);
if ($fh) {
die("OPEN bar.txt succeeded unexpectedly");
}
my ($err_code, $err_name) = $sftp->error();
my $expected = 'SSH_FX_PERMISSION_DENIED';
$self->assert($expected eq $err_name,
test_msg("Expected '$expected', got '$err_name'"));
$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 vroot_alias_file_sftp_write {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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: $!");
}
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('bar.txt', O_WRONLY|O_TRUNC, 0644);
unless ($fh) {
my ($err_code, $err_name) = $sftp->error();
die("Can't open bar.txt: [$err_name] ($err_code)");
}
my $count = 20;
for (my $i = 0; $i < $count; $i++) {
print $fh "ABCD" x 4096;
}
# To issue the FXP_CLOSE, we have to explicit destroy the filehandle
$fh = 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 vroot_alias_file_sftp_stat {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.txt', 1);
unless ($attrs) {
my ($err_code, $err_name) = $sftp->error();
die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
}
my $expected = 14;
my $file_size = $attrs->{size};
$self->assert($expected == $file_size,
test_msg("Expected $expected, got $file_size"));
$expected = $<;
my $file_uid = $attrs->{uid};
$self->assert($expected == $file_uid,
test_msg("Expected '$expected', got '$file_uid'"));
$expected = $(;
my $file_gid = $attrs->{gid};
$self->assert($expected == $file_gid,
test_msg("Expected '$expected', got '$file_gid'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_sftp_lstat {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.txt', 0);
unless ($attrs) {
my ($err_code, $err_name) = $sftp->error();
die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
}
my $expected = 14;
my $file_size = $attrs->{size};
$self->assert($expected == $file_size,
test_msg("Expected $expected, got $file_size"));
$expected = $<;
my $file_uid = $attrs->{uid};
$self->assert($expected == $file_uid,
test_msg("Expected '$expected', got '$file_uid'"));
$expected = $(;
my $file_gid = $attrs->{gid};
$self->assert($expected == $file_gid,
test_msg("Expected '$expected', got '$file_gid'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_sftp_realpath {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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 $real_path = $sftp->realpath('bar.txt');
unless ($real_path) {
my ($err_code, $err_name) = $sftp->error();
die("Can't get real path for 'bar.txt': [$err_name] ($err_code)");
}
my $expected;
$expected = '/bar.txt';
$self->assert($expected eq $real_path,
test_msg("Expected '$expected', got '$real_path'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_sftp_remove {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $dst_file = '~/bar.txt';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_file $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.txt');
if ($res) {
die("REMOVE bar.txt succeeded unexpectedly");
}
my ($err_code, $err_name) = $sftp->error();
my $expected = 'SSH_FX_PERMISSION_DENIED';
$self->assert($expected eq $err_name,
test_msg("Expected '$expected', got '$err_name'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_sftp_readdir {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
if (open(my $fh, "> $test_file1")) {
close($fh);
} else {
die("Can't open $test_file1: $!");
}
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
if (open(my $fh, "> $test_file2")) {
close($fh);
} else {
die("Can't open $test_file2: $!");
}
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
if (open(my $fh, "> $test_file3")) {
close($fh);
} else {
die("Can't open $test_file3: $!");
}
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.d');
unless ($dir) {
my ($err_code, $err_name) = $sftp->error();
die("Can't open directory 'bar.d': [$err_name] ($err_code)");
}
my $res = {};
my $file = $dir->read();
while ($file) {
$res->{$file->{name}} = $file;
$file = $dir->read();
}
my $expected = {
'.' => 1,
'..' => 1,
'a.txt' => 1,
'b.txt' => 1,
'c.txt' => 1,
};
# To issue the FXP_CLOSE, we have to explicit destroy the dirhandle
$dir = 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, got $remaining"));
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_dir_sftp_rmdir {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
mkpath($src_dir);
my $dst_dir = '~/bar.d';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('/bar.d');
if ($res) {
die("RMDIR bar.d succeeded unexpectedly");
}
my ($err_code, $err_name) = $sftp->error();
my $expected = 'SSH_FX_PERMISSION_DENIED';
$self->assert($expected eq $err_name,
test_msg("Expected '$expected', got '$err_name'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_symlink_sftp_stat {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'allowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.lnk', 1);
unless ($attrs) {
my ($err_code, $err_name) = $sftp->error();
die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
}
my $expected = 14;
my $file_size = $attrs->{size};
$self->assert($expected == $file_size,
test_msg("Expected $expected, got $file_size"));
$expected = $<;
my $file_uid = $attrs->{uid};
$self->assert($expected == $file_uid,
test_msg("Expected '$expected', got '$file_uid'"));
$expected = $(;
my $file_gid = $attrs->{gid};
$self->assert($expected == $file_gid,
test_msg("Expected '$expected', got '$file_gid'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_symlink_sftp_lstat {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'allowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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('bar.lnk', 0);
unless ($attrs) {
my ($err_code, $err_name) = $sftp->error();
die("FXP_STAT bar.txt failed: [$err_name] ($err_code)");
}
my $expected = 14;
my $file_size = $attrs->{size};
$self->assert($expected == $file_size,
test_msg("Expected $expected, got $file_size"));
$expected = $<;
my $file_uid = $attrs->{uid};
$self->assert($expected == $file_uid,
test_msg("Expected '$expected', got '$file_uid'"));
$expected = $(;
my $file_gid = $attrs->{gid};
$self->assert($expected == $file_gid,
test_msg("Expected '$expected', got '$file_gid'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_symlink_sftp_realpath {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $config_file = "$tmpdir/vroot.conf";
my $pid_file = File::Spec->rel2abs("$tmpdir/vroot.pid");
my $scoreboard_file = File::Spec->rel2abs("$tmpdir/vroot.scoreboard");
my $log_file = File::Spec->rel2abs('tests.log');
my $auth_user_file = File::Spec->rel2abs("$tmpdir/vroot.passwd");
my $auth_group_file = File::Spec->rel2abs("$tmpdir/vroot.group");
my $user = 'proftpd';
my $passwd = 'test';
my $home_dir = File::Spec->rel2abs($tmpdir);
my $uid = 500;
my $gid = 500;
# Make sure that, if we're running as root, that the home directory has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chmod(0755, $home_dir)) {
die("Can't set perms on $home_dir to 0755: $!");
}
unless (chown($uid, $gid, $home_dir)) {
die("Can't set owner of $home_dir to $uid/$gid: $!");
}
}
auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
'/bin/bash');
auth_group_write($auth_group_file, 'ftpd', $gid, $user);
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
if (open(my $fh, "> $src_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $src_file: $!");
}
} else {
die("Can't open $src_file: $!");
}
my $cwd = getcwd();
unless (chdir($home_dir)) {
die("Can't chdir to $home_dir: $!");
}
unless (symlink("./foo.txt", "foo.lnk")) {
die("Can't symlink './foo.txt' to 'foo.lnk': $!");
}
unless (chdir($cwd)) {
die("Can't chdir to $cwd: $!");
}
my $src_symlink = File::Spec->rel2abs("$tmpdir/foo.lnk");
my $dst_file = '~/bar.lnk';
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 $config = {
PidFile => $pid_file,
ScoreboardFile => $scoreboard_file,
SystemLog => $log_file,
TraceLog => $log_file,
Trace => 'fsio:10',
AuthUserFile => $auth_user_file,
AuthGroupFile => $auth_group_file,
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $log_file",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $log_file,
VRootOptions => 'allowSymlinks',
DefaultRoot => '~',
VRootAlias => "$src_symlink $dst_file",
},
'mod_delay.c' => {
DelayEngine => 'off',
},
},
};
my ($port, $config_user, $config_group) = config_write($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($user, $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 $real_path = $sftp->realpath('bar.lnk');
unless ($real_path) {
my ($err_code, $err_name) = $sftp->error();
die("FXP_REALPATH bar.lnk failed: [$err_name] ($err_code)");
}
my $expected = '/bar.lnk';
$self->assert($expected eq $real_path,
test_msg("Expected '$expected', got '$real_path'"));
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($config_file, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($pid_file);
$self->assert_child_ok($pid);
if ($ex) {
die($ex);
}
unlink($log_file);
}
sub vroot_alias_file_scp_download {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
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 $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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: $!");
}
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 $res = $ssh2->scp_get('/bar.txt', $test_file);
unless ($res) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't download bar.txt from server: [$err_name] ($err_code) $err_str");
}
$ssh2->disconnect();
unless (-f $test_file) {
die("$test_file 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 vroot_alias_file_scp_upload {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_file = File::Spec->rel2abs("$tmpdir/foo.txt");
create_test_file($setup, $src_file);
my $dst_file = '~/bar.txt';
my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
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 $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
AllowOverwrite => 'on',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_file $dst_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: $!");
}
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 $res = $ssh2->scp_put($setup->{config_file}, '/bar.txt');
unless ($res) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't upload bar.txt to server: [$err_name] ($err_code) $err_str");
}
$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 vroot_sftp_log_extlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
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 => 'command:20 event:20 extlog:20 fsio:10 jot:20 sftp:20 scp:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log READ custom",
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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 explicit destroy the filehandle
$fh = undef;
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
close($fh);
chomp($line);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_sftp_log_xferlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 sftp:20 scp:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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 explicit destroy the filehandle
$fh = undef;
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+sftp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line, "Expected '$expected', got '$line'");
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
"Expected '$expected', got '$remote_host'");
$expected = -s $test_file;
$self->assert($expected == $filesz,
"Expected '$expected', got '$filesz'");
$expected = $test_file;
$self->assert($expected eq $filename,
"Expected '$expected', got '$filename'");
$expected = 'b';
$self->assert($expected eq $xfer_type,
"Expected '$expected', got '$xfer_type'");
$expected = $setup->{user};
$self->assert($expected eq $user_name,
"Expected '$expected', got '$user_name'");
}
} else {
die("Can't read $xfer_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_sftp_log_extlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 jot:20 sftp:20 scp:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log WRITE custom",
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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)");
}
my $buf = "Hello, World!\n";
print $fh $buf;
# To issue the FXP_CLOSE, we have to explicit destroy the filehandle
$fh = undef;
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
close($fh);
chomp($line);
if ($ENV{TEST_VERBOSE}) {
print STDERR "# $line\n";
}
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_sftp_log_xferlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 jot:20 sftp:20 scp:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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)");
}
my $buf = "Hello, World!\n";
print $fh $buf;
# To issue the FXP_CLOSE, we have to explicit destroy the filehandle
$fh = undef;
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+sftp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line, "Expected '$expected', got '$line'");
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
"Expected '$expected', got '$remote_host'");
$expected = -s $test_file;
$self->assert($expected == $filesz,
"Expected '$expected', got '$filesz'");
$expected = $test_file;
$self->assert($expected eq $filename,
"Expected '$expected', got '$filename'");
$expected = 'b';
$self->assert($expected eq $xfer_type,
"Expected '$expected', got '$xfer_type'");
$expected = $setup->{user};
$self->assert($expected eq $user_name,
"Expected '$expected', got '$user_name'");
}
} else {
die("Can't read $xfer_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_scp_log_extlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 sftp:20 scp:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log READ custom",
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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");
}
unless ($ssh2->scp_get('test.txt', '/dev/null')) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't download 'test.txt' from server: [$err_name] ($err_code) $err_str");
}
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_scp_log_xferlog_retr {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
if (open(my $fh, "> $test_file")) {
print $fh "Hello, World!\n";
unless (close($fh)) {
die("Can't write $test_file: $!");
}
} else {
die("Can't open $test_file: $!");
}
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 sftp:20 scp:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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");
}
unless ($ssh2->scp_get('test.txt', '/dev/null')) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't download 'test.txt' from server: [$err_name] ($err_code) $err_str");
}
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+o\s+r\s+(\S+)\s+scp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line, "Expected '$expected', got '$line'");
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
"Expected '$expected', got '$remote_host'");
$expected = -s $test_file;
$self->assert($expected == $filesz,
"Expected '$expected', got '$filesz'");
$expected = $test_file;
$self->assert($expected eq $filename,
"Expected '$expected', got '$filename'");
$expected = 'b';
$self->assert($expected eq $xfer_type,
"Expected '$expected', got '$xfer_type'");
$expected = $setup->{user};
$self->assert($expected eq $user_name,
"Expected '$expected', got '$user_name'");
}
} else {
die("Can't read $xfer_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_scp_log_extlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
my $ext_log = File::Spec->rel2abs("$tmpdir/ext.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 sftp:20 scp:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
LogFormat => 'custom "%f"',
ExtendedLog => "$ext_log WRITE custom",
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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");
}
unless ($ssh2->scp_put($setup->{config_file}, 'test.txt')) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't upload $setup->{config_file} to server: [$err_name] ($err_code) $err_str");
}
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $ext_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$self->assert($test_file eq $line,
test_msg("Expected '$test_file', got '$line'"));
} else {
die("Can't read $ext_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_scp_log_xferlog_stor {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
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");
my $xfer_log = File::Spec->rel2abs("$tmpdir/xfer.log");
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'fsio:10 sftp:20 scp:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
TransferLog => $xfer_log,
IfModules => {
'mod_delay.c' => {
DelayEngine => 'off',
},
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
},
},
};
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;
require Net::SSH2;
# 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");
}
unless ($ssh2->scp_put($setup->{config_file}, 'test.txt')) {
my ($err_code, $err_name, $err_str) = $ssh2->error();
die("Can't upload $setup->{config_file} to server: [$err_name] ($err_code) $err_str");
}
$ssh2->disconnect();
};
if ($@) {
$ex = $@;
}
$wfh->print("done\n");
$wfh->flush();
} else {
eval { server_wait($setup->{config_file}, $rfh) };
if ($@) {
warn($@);
exit 1;
}
exit 0;
}
# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);
eval {
if (open(my $fh, "< $xfer_log")) {
my $line = <$fh>;
chomp($line);
close($fh);
if ($ENV{TEST_VERBOSE}) {
print STDERR "$line\n";
}
my $expected = '^\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+\s+\d+\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+_\s+i\s+r\s+(\S+)\s+scp\s+0\s+\*\s+c$';
$self->assert(qr/$expected/, $line, "Expected '$expected', got '$line'");
if ($line =~ /$expected/) {
my $remote_host = $1;
my $filesz = $2;
my $filename = $3;
my $xfer_type = $4;
my $user_name = $5;
if ($^O eq 'darwin') {
# MacOSX-specific hack, due to how it handles tmp files
$test_file = ('/private' . $test_file);
}
$expected = '127.0.0.1';
$self->assert($expected eq $remote_host,
"Expected '$expected', got '$remote_host'");
$expected = -s $test_file;
$self->assert($expected == $filesz,
"Expected '$expected', got '$filesz'");
$expected = $test_file;
$self->assert($expected eq $filename,
"Expected '$expected', got '$filename'");
$expected = 'b';
$self->assert($expected eq $xfer_type,
"Expected '$expected', got '$xfer_type'");
$expected = $setup->{user};
$self->assert($expected eq $user_name,
"Expected '$expected', got '$user_name'");
}
} else {
die("Can't read $xfer_log: $!");
}
};
if ($@) {
$ex = $@;
}
test_cleanup($setup->{log_file}, $ex);
}
sub vroot_alias_dir_sftp_publickey_issue30 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'vroot');
my $src_dir = File::Spec->rel2abs("$tmpdir/foo.d");
create_test_dir($setup, $src_dir);
my $dst_dir = '~/bar.d';
my $test_file1 = File::Spec->rel2abs("$tmpdir/foo.d/a.txt");
create_test_file($setup, $test_file1);
my $test_file2 = File::Spec->rel2abs("$tmpdir/foo.d/b.txt");
create_test_file($setup, $test_file2);
my $test_file3 = File::Spec->rel2abs("$tmpdir/foo.d/c.txt");
create_test_file($setup, $test_file3);
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 $rsa_priv_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/test_rsa_key");
my $rsa_pub_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/test_rsa_key.pub");
my $rsa_rfc4716_key = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/tests/t/etc/modules/mod_sftp/authorized_rsa_keys2");
my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys");
unless (copy($rsa_rfc4716_key, $authorized_keys)) {
die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!");
}
# Make sure that, if we're running as root, that the authorized_keys file has
# permissions/privs set for the account we create
if ($< == 0) {
unless (chown($setup->{uid}, $setup->{gid}, $authorized_keys)) {
die("Can't set owner of $authorized_keys to $setup->{uid}/$setup->{gid}: $!");
}
}
$ENV{TEST_VERBOSE} = 1;
my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'auth:20 fsio:20 ssh2:20 vroot:20',
AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},
AuthOrder => 'mod_auth_file.c',
IfModules => {
'mod_sftp.c' => [
"SFTPEngine on",
"SFTPLog $setup->{log_file}",
"SFTPHostKey $rsa_host_key",
"SFTPHostKey $dsa_host_key",
"SFTPAuthorizedUserKeys file:~/.authorized_keys",
],
'mod_vroot.c' => {
VRootEngine => 'on',
VRootLog => $setup->{log_file},
DefaultRoot => '~',
VRootAlias => "$src_dir $dst_dir",
},
'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: $!");
}
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_publickey($setup->{user}, $rsa_pub_key,
$rsa_priv_key)) {
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('/');
unless ($dir) {
my ($err_code, $err_name) = $sftp->error();
die("Can't open directory '.': [$err_name] ($err_code)");
}
my $res = {};
my $file = $dir->read();
while ($file) {
if ($ENV{TEST_VERBOSE}) {
print STDERR "# READDIR: $file->{name}\n";
}
$res->{$file->{name}} = $file;
$file = $dir->read();
}
my $expected = {
'.' => 1,
'..' => 1,
'.authorized_keys' => 1,
'foo.d' => 1,
'bar.d' => 1,
'vroot.conf' => 1,
'vroot.group' => 1,
'vroot.passwd' => 1,
'vroot.pid' => 1,
'vroot.scoreboard' => 1,
'vroot.scoreboard.lck' => 1,
};
# To issue the FXP_CLOSE, we have to explicitly destroy the dirhandle
$dir = 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, 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);
delete($ENV{TEST_VERBOSE});
}
1;
proftpd-mod_vroot-0.9.11/t/modules/ 0000775 0000000 0000000 00000000000 14334560322 0017203 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/modules/mod_vroot.t 0000664 0000000 0000000 00000000265 14334560322 0021403 0 ustar 00root root 0000000 0000000 #!/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_vroot");
proftpd-mod_vroot-0.9.11/t/modules/mod_vroot/ 0000775 0000000 0000000 00000000000 14334560322 0021213 5 ustar 00root root 0000000 0000000 proftpd-mod_vroot-0.9.11/t/modules/mod_vroot/sftp.t 0000664 0000000 0000000 00000000273 14334560322 0022356 0 ustar 00root root 0000000 0000000 #!/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_vroot::sftp");
proftpd-mod_vroot-0.9.11/tests.pl 0000664 0000000 0000000 00000005076 14334560322 0016777 0 ustar 00root root 0000000 0000000 #!/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_vroot.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_vroot/sftp.t' => {
order => ++$order,
test_class => [qw(mod_sftp mod_vroot)],
},
};
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 <