Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2: Implement a ProxyProtocolIgnore directive, which can be u… #21

Merged
merged 1 commit into from
Jun 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion mod_proxy_protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,26 @@ MODRET set_proxyprotocolengine(cmd_rec *cmd) {
return PR_HANDLED(cmd);
}

/* usage: ProxyProtocolIgnore on|off */
MODRET set_proxyprotocolignore(cmd_rec *cmd) {
int ignore = 0;
config_rec *c;

CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

ignore = get_boolean(cmd, 1);
if (ignore == -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]) = ignore;

return PR_HANDLED(cmd);
}

/* usage: ProxyProtocolOptions opt1 ... */
MODRET set_proxyprotocoloptions(cmd_rec *cmd) {
register unsigned int i;
Expand Down Expand Up @@ -1334,7 +1354,7 @@ MODRET set_proxyprotocolversion(cmd_rec *cmd) {

static int proxy_protocol_sess_init(void) {
config_rec *c;
int engine = 0, res = 0, timerno = -1, xerrno;
int engine = 0, ignore = FALSE, res = 0, timerno = -1, xerrno;
const pr_netaddr_t *proxied_src_addr = NULL, *proxied_dst_addr = NULL;
unsigned int proxied_src_port = 0, proxied_dst_port = 0;
const char *remote_ip = NULL, *remote_name = NULL;
Expand All @@ -1349,6 +1369,12 @@ static int proxy_protocol_sess_init(void) {
return 0;
}

/* ProxyProtocolIgnore */
c = find_config(main_server->conf, CONF_PARAM, "ProxyProtocolIgnore", FALSE);
if (c != NULL) {
ignore = *((int *) c->argv[0]);
}

/* ProxyProtocolOptions */
c = find_config(main_server->conf, CONF_PARAM, "ProxyProtocolOptions", FALSE);
while (c != NULL) {
Expand Down Expand Up @@ -1419,6 +1445,13 @@ static int proxy_protocol_sess_init(void) {
return -1;
}

if (ignore == TRUE) {
pr_log_debug(DEBUG10, MOD_PROXY_PROTOCOL_VERSION
": ProxyProtocolIgnore is in effect, ignoring proxied source "
"address '%s'", pr_netaddr_get_ipstr(proxied_src_addr));
return 0;
}

if (proxied_src_addr != NULL) {
remote_ip = pstrdup(session.pool,
pr_netaddr_get_ipstr(pr_netaddr_get_sess_remote_addr()));
Expand Down Expand Up @@ -1539,6 +1572,7 @@ static int proxy_protocol_sess_init(void) {

static conftable proxy_protocol_conftab[] = {
{ "ProxyProtocolEngine", set_proxyprotocolengine, NULL },
{ "ProxyProtocolIgnore", set_proxyprotocolignore, NULL },
{ "ProxyProtocolOptions", set_proxyprotocoloptions, NULL },
{ "ProxyProtocolTimeout", set_proxyprotocoltimeout, NULL },
{ "ProxyProtocolVersion", set_proxyprotocolversion, NULL },
Expand Down
37 changes: 37 additions & 0 deletions mod_proxy_protocol.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h2>Author</h2>
<h2>Directives</h2>
<ul>
<li><a href="#ProxyProtocolEngine">ProxyProtocolEngine</a>
<li><a href="#ProxyProtocolIgnore">ProxyProtocolIgnore</a>
<li><a href="#ProxyProtocolOptions">ProxyProtocolOptions</a>
<li><a href="#ProxyProtocolTimeout">ProxyProtocolTimeout</a>
<li><a href="#ProxyProtocolVersion">ProxyProtocolVersion</a>
Expand All @@ -65,6 +66,42 @@ <h2><a name="ProxyProtocolEngine">ProxyProtocolEngine</a></h2>
and handling of protocols which provide information on proxied connections;
support for these protocols is provided by <code>mod_proxy_protocol</code>.

<p>
<hr>
<h2><a name="ProxyProtocolIgnore">ProxyProtocolIgnore</a></h2>
<strong>Syntax:</strong> ProxyProtocolIgnore <em>on|off</em><br>
<strong>Default:</strong> off<br>
<strong>Context:</strong> server config, <code>&lt;VirtualHost&gt;</code>, <code>&lt;Global&gt;</code><br>
<strong>Module:</strong> mod_proxy_protocol<br>
<strong>Compatibility:</strong> 1.3.5rc4 and later

<p>
The <code>ProxyProtocolIgnore</code> directive determines whether the
<code>mod_proxy_protocol</code> module will <em>use</em> the information it
reads, in the PROXY protocol data that may be sent by a client.

<p>
This is useful for cases where clients may be sending/using the PROXY protocol,
but you may not want <code>mod_proxy_protocol</code> to use the data from some
clients. For example:
<pre>
&lt;Class untrusted-senders&gt;
From 1.2.3.4
From 5.6.7.8
&lt;/Class&gt;

&lt;IfModule mod_proxy_protocol.c&gt;
# We always to read PROXY protocol headers from clients...
ProxyProtocolEngine on

# ...but we don't always trust the senders
&lt;IfClass untrusted-senders&gt;
ProxyProtocolIgnore on
&lt;/IfClass&gt;
&lt;/IfModule&gt;
</pre>


<p>
<hr>
<h2><a name="ProxyProtocolOptions">ProxyProtocolOptions</a></h3>
Expand Down
261 changes: 261 additions & 0 deletions t/lib/ProFTPD/Tests/Modules/mod_proxy_protocol/ifsession.pm
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ my $TESTS = {
test_class => [qw(forking mod_ifsession mod_proxy_protocol)],
},

proxy_protocol_config_ignore_ifsess_matching_class_issue2 => {
order => ++$order,
test_class => [qw(forking mod_ifsession mod_proxy_protocol)],
},

proxy_protocol_config_ignore_ifsess_mismatched_class_issue2 => {
order => ++$order,
test_class => [qw(forking mod_ifsession mod_proxy_protocol)],
},

};

sub new {
Expand Down Expand Up @@ -223,4 +233,255 @@ EOC
test_cleanup($setup->{log_file}, $ex);
}

sub proxy_protocol_config_ignore_ifsess_matching_class_issue2 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'ifsess');

my $proxied_src_addr = '1.1.1.1';

my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},
TraceLog => $setup->{log_file},
Trace => 'proxy_protocol:20',

AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},

IfModules => {
'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 <<EOC;
<Class expect-proxy-protocol>
From 127.0.0.1
</Class>

<IfModule mod_proxy_protocol.c>
ProxyProtocolEngine on
<IfClass expect-proxy-protocol>
ProxyProtocolIgnore off
</IfClass>
</IfModule>
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 {
sleep(2);

my $client = ProFTPD::TestSuite::ProxiedFTP->new('127.0.0.1', $port,
['TCP4', $proxied_src_addr, '2.2.2.2', 111, 222]);
$client->login($setup->{user}, $setup->{passwd});
$client->quit();
};
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);

eval {
if (open(my $fh, "< $setup->{log_file}")) {
my $ok = 0;

while (my $line = <$fh>) {
chomp($line);

if ($ENV{TEST_VERBOSE}) {
print STDERR "# $line\n";
}

if ($line =~ /using proxied source address: /) {
$ok = 1;
last;
}
}

close($fh);

$self->assert($ok, test_msg("Did not see expected debug log messages"));

} else {
die("Can't read $setup->{log_file}: $!");
}
};
if ($@) {
$ex = $@;
}

test_cleanup($setup->{log_file}, $ex);
}

sub proxy_protocol_config_ignore_ifsess_mismatched_class_issue2 {
my $self = shift;
my $tmpdir = $self->{tmpdir};
my $setup = test_setup($tmpdir, 'ifsess');

my $proxied_src_addr = '1.1.1.1';

my $config = {
PidFile => $setup->{pid_file},
ScoreboardFile => $setup->{scoreboard_file},
SystemLog => $setup->{log_file},

AuthUserFile => $setup->{auth_user_file},
AuthGroupFile => $setup->{auth_group_file},

IfModules => {
'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 <<EOC;
<Class expect-proxy-protocol>
From 127.0.0.1
</Class>

<IfModule mod_proxy_protocol.c>
ProxyProtocolEngine on
<IfClass expect-proxy-protocol>
ProxyProtocolIgnore on
</IfClass>
</IfModule>
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 {
sleep(2);

my $client = ProFTPD::TestSuite::ProxiedFTP->new('127.0.0.1', $port,
['TCP4', $proxied_src_addr, '2.2.2.2', 111, 222]);
eval { $client->user($setup->{user}) };
unless ($@) {
die('USER command succeeded unexpectedly');
}

my $resp_code = $client->response();
my $expected = 5;
$self->assert($resp_code == $expected,
test_msg("Expected response code $expected, got $resp_code"));
};
if ($@) {
$ex = $@;
}

$wfh->print("done\n");
$wfh->flush();

} else {
eval { server_wait($setup->{config_file}, $rfh, 10) };
if ($@) {
warn($@);
exit 1;
}

exit 0;
}

# Stop server
server_stop($setup->{pid_file});
$self->assert_child_ok($pid);

eval {
if (open(my $fh, "< $setup->{log_file}")) {
my $ok = 1;

while (my $line = <$fh>) {
chomp($line);

if ($ENV{TEST_VERBOSE}) {
print STDERR "# $line\n";
}

if ($line =~ /using proxied source address: /) {
$ok = 0;
last;
}
}

close($fh);

$self->assert($ok, test_msg("Saw unexpected debug log messages"));

} else {
die("Can't read $setup->{log_file}: $!");
}
};
if ($@) {
$ex = $@;
}

test_cleanup($setup->{log_file}, $ex);
}

1;