--- contrib/mod_copy.c +++ contrib/mod_copy.c @@ -31,7 +31,7 @@ #include "conf.h" -#define MOD_COPY_VERSION "mod_copy/0.4" +#define MOD_COPY_VERSION "mod_copy/0.5" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001030401 @@ -40,6 +40,8 @@ extern pr_response_t *resp_list, *resp_err_list; +static int copy_engine = TRUE; + static const char *trace_channel = "copy"; /* These are copied largely from src/mkhome.c */ @@ -471,10 +473,37 @@ static int copy_paths(pool *p, const cha return 0; } +/* Configuration handlers + */ + +/* usage: CopyEngine on|off */ +MODRET set_copyengine(cmd_rec *cmd) { + int engine = -1; + config_rec *c; + + CHECK_ARGS(cmd, 1); + CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); + + engine = get_boolean(cmd, 1); + if (engine == -1) { + CONF_ERROR(cmd, "expected Boolean parameter"); + } + + c = add_config_param(cmd->argv[0], 1, NULL); + c->argv[0] = palloc(c->pool, sizeof(int)); + *((int *) c->argv[0]) = engine; + + return PR_HANDLED(cmd); +} + /* Command handlers */ MODRET copy_copy(cmd_rec *cmd) { + if (copy_engine == FALSE) { + return PR_DECLINED(cmd); + } + if (cmd->argc < 2) { return PR_DECLINED(cmd); } @@ -539,12 +568,26 @@ MODRET copy_cpfr(cmd_rec *cmd) { register unsigned int i; int res; char *path = ""; + unsigned char *authenticated = NULL; + + if (copy_engine == FALSE) { + return PR_DECLINED(cmd); + } if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPFR", 5) != 0) { return PR_DECLINED(cmd); } + authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); + if (authenticated == NULL || + *authenticated == FALSE) { + pr_response_add_err(R_530, _("Please login with USER and PASS")); + + errno = EPERM; + return PR_ERROR(cmd); + } + CHECK_CMD_MIN_ARGS(cmd, 3); /* Construct the target file name by concatenating all the parameters after @@ -594,12 +637,26 @@ MODRET copy_cpfr(cmd_rec *cmd) { MODRET copy_cpto(cmd_rec *cmd) { register unsigned int i; char *from, *to = ""; + unsigned char *authenticated = NULL; + + if (copy_engine == FALSE) { + return PR_DECLINED(cmd); + } if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPTO", 5) != 0) { return PR_DECLINED(cmd); } + authenticated = get_param_ptr(cmd->server->conf, "authenticated", FALSE); + if (authenticated == NULL || + *authenticated == FALSE) { + pr_response_add_err(R_530, _("Please login with USER and PASS")); + + errno = EPERM; + return PR_ERROR(cmd); + } + CHECK_CMD_MIN_ARGS(cmd, 3); from = pr_table_get(session.notes, "mod_copy.cpfr-path", NULL); @@ -632,6 +689,10 @@ MODRET copy_cpto(cmd_rec *cmd) { } MODRET copy_log_site(cmd_rec *cmd) { + if (copy_engine == FALSE) { + return PR_DECLINED(cmd); + } + if (cmd->argc < 3 || strncasecmp(cmd->argv[1], "CPTO", 5) != 0) { return PR_DECLINED(cmd); @@ -643,23 +704,58 @@ MODRET copy_log_site(cmd_rec *cmd) { return PR_DECLINED(cmd); } +MODRET copy_post_pass(cmd_rec *cmd) { + config_rec *c; + + if (copy_engine == FALSE) { + return PR_DECLINED(cmd); + } + + /* The CopyEngine directive may have been changed for this user by + * e.g. mod_ifsession, thus we check again. + */ + c = find_config(main_server->conf, CONF_PARAM, "CopyEngine", FALSE); + if (c != NULL) { + copy_engine = *((int *) c->argv[0]); + } + + return PR_DECLINED(cmd); +} + /* Initialization functions */ static int copy_sess_init(void) { + config_rec *c; + + c = find_config(main_server->conf, CONF_PARAM, "CopyEngine", FALSE); + if (c != NULL) { + copy_engine = *((int *) c->argv[0]); + } + + if (copy_engine == FALSE) { + return 0; + } + /* Advertise support for the SITE command */ pr_feat_add("SITE COPY"); - return 0; } /* Module API tables */ +static conftable copy_conftab[] = { + { "CopyEngine", set_copyengine, NULL }, + + { NULL } +}; + static cmdtable copy_cmdtab[] = { { CMD, C_SITE, G_WRITE, copy_copy, FALSE, FALSE, CL_MISC }, { CMD, C_SITE, G_DIRS, copy_cpfr, FALSE, FALSE, CL_MISC }, { CMD, C_SITE, G_WRITE, copy_cpto, FALSE, FALSE, CL_MISC }, + { POST_CMD, C_PASS, G_NONE, copy_post_pass, FALSE, FALSE }, { LOG_CMD, C_SITE, G_NONE, copy_log_site, FALSE, FALSE }, { LOG_CMD_ERR, C_SITE, G_NONE, copy_log_site, FALSE, FALSE }, @@ -676,7 +772,7 @@ module copy_module = { "copy", /* Module configuration handler table */ - NULL, + copy_conftab, /* Module command handler table */ copy_cmdtab, --- doc/contrib/mod_copy.html +++ doc/contrib/mod_copy.html @@ -27,22 +27,40 @@ ProFTPD 1.3.x, and is not compile instructions are discussed here.

-The most current version of mod_copy can be found at: -

-  http://www.castaglia.org/proftpd/
-
+The most current version of mod_copy is distributed with the +ProFTPD source code.

Author

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

Directives

+ +

SITE Commands

+

+


+

CopyEngine

+Syntax: CopyEngine on|off
+Default: CopyEngine on
+Context: server config, <VirtualHost>, <Global>
+Module: mod_radius
+Compatibility: 1.3.6rc1 and later + +

+The CopyEngine directive enables or disables the module's +handling of SITE COPY et al commands. If it is set to +off this module ignores these commands. + +


SITE CPFR

This SITE command specifies the source file/directory to use @@ -118,13 +136,8 @@ your existing server:



-Author: $Author: castaglia $
-Last Updated: $Date: 2010/03/10 19:20:43 $
- -

- -© Copyright 2009-2010 TJ Saunders
+© Copyright 2009-2015 TJ Saunders
All Rights Reserved
--- RELEASE_NOTES +++ RELEASE_NOTES @@ -6,6 +6,14 @@ This file contains a description of the releases. More information on these changes can be found in the NEWS and ChangeLog files. +Upcoming 1.3.5a +--------------- + + + New Configuration Directives + + CopyEngine (Bug#4169) + + 1.3.5 --------- --- tests/t/lib/ProFTPD/Tests/Modules/mod_copy.pm +++ tests/t/lib/ProFTPD/Tests/Modules/mod_copy.pm @@ -21,6 +21,11 @@ my $TESTS = { test_class => [qw(forking)], }, + copy_file_no_login => { + order => ++$order, + test_class => [qw(bug forking)], + }, + copy_dir => { order => ++$order, test_class => [qw(forking)], @@ -86,6 +91,11 @@ my $TESTS = { test_class => [qw(forking)], }, + copy_cpfr_cpto_no_login => { + order => ++$order, + test_class => [qw(bug forking)], + }, + copy_cpto_no_cpfr => { order => ++$order, test_class => [qw(forking)], @@ -263,6 +273,137 @@ sub copy_file { unlink($log_file); } +sub copy_file_no_login { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + + my $config_file = "$tmpdir/copy.conf"; + my $pid_file = File::Spec->rel2abs("$tmpdir/copy.pid"); + my $scoreboard_file = File::Spec->rel2abs("$tmpdir/copy.scoreboard"); + + my $log_file = File::Spec->rel2abs('tests.log'); + + my $auth_user_file = File::Spec->rel2abs("$tmpdir/copy.passwd"); + my $auth_group_file = File::Spec->rel2abs("$tmpdir/copy.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("$home_dir/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 = File::Spec->rel2abs("$home_dir/bar.txt"); + + my $config = { + PidFile => $pid_file, + ScoreboardFile => $scoreboard_file, + SystemLog => $log_file, + + AuthUserFile => $auth_user_file, + AuthGroupFile => $auth_group_file, + + IfModules => { + '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); + + eval { $client->site('COPY', 'foo.txt', 'bar.txt') }; + unless ($@) { + die("SITE COPY succeeded unexpectedly"); + } + + my $resp_code = $client->response_code(); + my $resp_msg = $client->response_msg(); + + my $expected; + $expected = 530; + $self->assert($expected == $resp_code, + test_msg("Expected response code $expected, got $resp_code")); + + $expected = "Please login with USER and PASS"; + $self->assert($expected eq $resp_msg, + test_msg("Expected response message '$expected', got '$resp_msg'")); + }; + + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($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 copy_dir { my $self = shift; my $tmpdir = $self->{tmpdir}; @@ -2578,6 +2719,153 @@ sub copy_cpfr_cpto { }; 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 copy_cpfr_cpto_no_login { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + + my $config_file = "$tmpdir/copy.conf"; + my $pid_file = File::Spec->rel2abs("$tmpdir/copy.pid"); + my $scoreboard_file = File::Spec->rel2abs("$tmpdir/copy.scoreboard"); + + my $log_file = File::Spec->rel2abs('tests.log'); + + my $auth_user_file = File::Spec->rel2abs("$tmpdir/copy.passwd"); + my $auth_group_file = File::Spec->rel2abs("$tmpdir/copy.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("$home_dir/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 = File::Spec->rel2abs("$home_dir/bar.txt"); + + my $config = { + PidFile => $pid_file, + ScoreboardFile => $scoreboard_file, + SystemLog => $log_file, + + AuthUserFile => $auth_user_file, + AuthGroupFile => $auth_group_file, + + IfModules => { + '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); + + eval { $client->site('CPFR', 'foo.txt') }; + unless ($@) { + die("SITE CPFR succeeded unexpectedly"); + } + + my $resp_code = $client->response_code(); + my $resp_msg = $client->response_msg(); + + my $expected; + $expected = 530; + $self->assert($expected == $resp_code, + test_msg("Expected response code $expected, got $resp_code")); + + $expected = "Please login with USER and PASS"; + $self->assert($expected eq $resp_msg, + test_msg("Expected response message '$expected', got '$resp_msg'")); + + eval { $client->site('CPTO', 'bar.txt') }; + unless ($@) { + die("SITE CPTO succeeded unexpectedly"); + } + + $resp_code = $client->response_code(); + $resp_msg = $client->response_msg(); + + $expected = 530; + $self->assert($expected == $resp_code, + test_msg("Expected response code $expected, got $resp_code")); + + $expected = "Please login with USER and PASS"; + $self->assert($expected eq $resp_msg, + test_msg("Expected response message '$expected', got '$resp_msg'")); + }; + + if ($@) { $ex = $@; }