From owner-svn-ports-branches@FreeBSD.ORG Wed May 20 19:16:19 2015 Return-Path: Delivered-To: svn-ports-branches@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 16A27E03; Wed, 20 May 2015 19:16:19 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 0480E1931; Wed, 20 May 2015 19:16:19 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t4KJGIuE048432; Wed, 20 May 2015 19:16:18 GMT (envelope-from delphij@FreeBSD.org) Received: (from delphij@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t4KJGIhe048429; Wed, 20 May 2015 19:16:18 GMT (envelope-from delphij@FreeBSD.org) Message-Id: <201505201916.t4KJGIhe048429@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: delphij set sender to delphij@FreeBSD.org using -f From: Xin LI Date: Wed, 20 May 2015 19:16:18 +0000 (UTC) To: ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-branches@freebsd.org Subject: svn commit: r386882 - in branches/2015Q2/ftp/proftpd: . files X-SVN-Group: ports-branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-ports-branches@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: SVN commit messages for all the branches of the ports tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 20 May 2015 19:16:19 -0000 Author: delphij Date: Wed May 20 19:16:18 2015 New Revision: 386882 URL: https://svnweb.freebsd.org/changeset/ports/386882 Log: MFH: r386881 Fix proftpd unauthenticated copying of files via SITE CPFR/CPTO vulnerability. Security: CVE-2015-3306 Approved by: ports-secteam (self) Added: branches/2015Q2/ftp/proftpd/files/patch-CVE-2015-3306 - copied unchanged from r386881, head/ftp/proftpd/files/patch-CVE-2015-3306 Modified: branches/2015Q2/ftp/proftpd/Makefile Directory Properties: branches/2015Q2/ (props changed) Modified: branches/2015Q2/ftp/proftpd/Makefile ============================================================================== --- branches/2015Q2/ftp/proftpd/Makefile Wed May 20 19:15:24 2015 (r386881) +++ branches/2015Q2/ftp/proftpd/Makefile Wed May 20 19:16:18 2015 (r386882) @@ -5,7 +5,7 @@ PORTNAME?= proftpd .if !defined(DISTVERSION) PORTVERSION?= ${PROFTPD_VERSION} .endif -PORTREVISION?= 6 +PORTREVISION?= 7 CATEGORIES?= ftp MASTER_SITES= ftp://ftp.proftpd.org/distrib/source/ \ https://github.com/downloads/proftpd/proftpd.github.com/ \ Copied: branches/2015Q2/ftp/proftpd/files/patch-CVE-2015-3306 (from r386881, head/ftp/proftpd/files/patch-CVE-2015-3306) ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ branches/2015Q2/ftp/proftpd/files/patch-CVE-2015-3306 Wed May 20 19:16:18 2015 (r386882, copy of r386881, head/ftp/proftpd/files/patch-CVE-2015-3306) @@ -0,0 +1,610 @@ +Index: contrib/mod_copy.c +=================================================================== +--- contrib/mod_copy.c ++++ contrib/mod_copy.c +@@ -2,7 +2,7 @@ + * ProFTPD: mod_copy -- a module supporting copying of files on the server + * without transferring the data to the client and back + * +- * Copyright (c) 2009-2012 TJ Saunders ++ * Copyright (c) 2009-2015 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 +@@ -25,13 +25,11 @@ + * + * This is mod_copy, contrib software for proftpd 1.3.x and above. + * For more information contact TJ Saunders . +- * +- * $Id: mod_copy.c,v 1.8 2012/12/27 22:31:29 castaglia Exp $ + */ + + #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 +38,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 */ +@@ -165,7 +165,7 @@ static int copy_symlink(pool *p, const c + src_path, strerror(xerrno)); + + errno = xerrno; +- return -1; ++ return -1; + } + link_path[len] = '\0'; + +@@ -471,10 +471,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 +566,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 +635,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 +687,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 +702,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 +770,7 @@ module copy_module = { + "copy", + + /* Module configuration handler table */ +- NULL, ++ copy_conftab, + + /* Module command handler table */ + copy_cmdtab, +Index: doc/contrib/mod_copy.html +=================================================================== +--- doc/contrib/mod_copy.html ++++ doc/contrib/mod_copy.html +@@ -1,5 +1,5 @@ +- +- ++ ++ + + + +@@ -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
+
+ +Index: tests/t/lib/ProFTPD/Tests/Modules/mod_copy.pm +=================================================================== +--- 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 = $@; + } +