1package ProFTPD::Tests::Config::PassivePorts;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use File::Spec;
8use IO::Handle;
9
10use ProFTPD::TestSuite::FTP;
11use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
12
13$| = 1;
14
15my $order = 0;
16
17my $TESTS = {
18  pasv_ports_server_config => {
19    order => ++$order,
20    test_class => [qw(forking)],
21  },
22
23  pasv_ports_global => {
24    order => ++$order,
25    test_class => [qw(forking)],
26  },
27
28  pasv_ports_vhost => {
29    order => ++$order,
30    test_class => [qw(forking)],
31  },
32
33};
34
35sub new {
36  return shift()->SUPER::new(@_);
37}
38
39sub list_tests {
40  return testsuite_get_runnable_tests($TESTS);
41}
42
43sub pasv_ports_server_config {
44  my $self = shift;
45  my $tmpdir = $self->{tmpdir};
46
47  my $config_file = "$tmpdir/config.conf";
48  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
49  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
50
51  my $log_file = test_get_logfile();
52
53  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
54  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
55
56  my $user = 'proftpd';
57  my $passwd = 'test';
58  my $group = 'ftpd';
59  my $home_dir = File::Spec->rel2abs($tmpdir);
60  my $uid = 500;
61  my $gid = 500;
62
63  # Make sure that, if we're running as root, that the home directory has
64  # permissions/privs set for the account we create
65  if ($< == 0) {
66    unless (chmod(0755, $home_dir)) {
67      die("Can't set perms on $home_dir to 0755: $!");
68    }
69
70    unless (chown($uid, $gid, $home_dir)) {
71      die("Can't set owner of $home_dir to $uid/$gid: $!");
72    }
73  }
74
75  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
76    '/bin/bash');
77  auth_group_write($auth_group_file, $group, $gid, $user);
78
79  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
80  if (open(my $fh, "> $test_file")) {
81    print $fh "ABCD" x 8192;
82    unless (close($fh)) {
83      die("Can't write $test_file: $!");
84    }
85
86  } else {
87    die("Can't open $test_file: $!");
88  }
89
90  my $min_port = 40100;
91  my $max_port = 40200;
92
93  my $config = {
94    PidFile => $pid_file,
95    ScoreboardFile => $scoreboard_file,
96    SystemLog => $log_file,
97    TraceLog => $log_file,
98    Trace => 'DEFAULT:0 data:10',
99
100    AuthUserFile => $auth_user_file,
101    AuthGroupFile => $auth_group_file,
102    PassivePorts => "$min_port $max_port",
103
104    IfModules => {
105      'mod_delay.c' => {
106        DelayEngine => 'off',
107      },
108    },
109  };
110
111  my ($port, $config_user, $config_group) = config_write($config_file, $config);
112
113  # Open pipes, for use between the parent and child processes.  Specifically,
114  # the child will indicate when it's done with its test by writing a message
115  # to the parent.
116  my ($rfh, $wfh);
117  unless (pipe($rfh, $wfh)) {
118    die("Can't open pipe: $!");
119  }
120
121  my $ex;
122
123  # Fork child
124  $self->handle_sigchld();
125  defined(my $pid = fork()) or die("Can't fork: $!");
126  if ($pid) {
127    eval {
128      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
129      $client->login($user, $passwd);
130
131      my ($resp_code, $resp_msg) = $client->pasv();
132      $client->quit();
133
134      my $expected;
135
136      $expected = 227;
137      $self->assert($expected == $resp_code,
138        test_msg("Expected response code $expected, got $resp_code"));
139
140      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
141      $self->assert(qr/$expected/, $resp_msg,
142        test_msg("Expected response message '$expected', got '$resp_msg'"));
143
144      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
145        die("Response '$resp_msg' does not match expected pattern");
146      }
147
148      my $pasv_port = ($1 * 256) + $2;
149
150      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
151        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
152    };
153
154    if ($@) {
155      $ex = $@;
156    }
157
158    $wfh->print("done\n");
159    $wfh->flush();
160
161  } else {
162    eval { server_wait($config_file, $rfh) };
163    if ($@) {
164      warn($@);
165      exit 1;
166    }
167
168    exit 0;
169  }
170
171  # Stop server
172  server_stop($pid_file);
173
174  $self->assert_child_ok($pid);
175
176  if ($ex) {
177    test_append_logfile($log_file, $ex);
178    unlink($log_file);
179
180    die($ex);
181  }
182
183  unlink($log_file);
184}
185
186sub pasv_ports_global {
187  my $self = shift;
188  my $tmpdir = $self->{tmpdir};
189
190  my $config_file = "$tmpdir/config.conf";
191  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
192  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
193
194  my $log_file = test_get_logfile();
195
196  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
197  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
198
199  my $user = 'proftpd';
200  my $passwd = 'test';
201  my $group = 'ftpd';
202  my $home_dir = File::Spec->rel2abs($tmpdir);
203  my $uid = 500;
204  my $gid = 500;
205
206  # Make sure that, if we're running as root, that the home directory has
207  # permissions/privs set for the account we create
208  if ($< == 0) {
209    unless (chmod(0755, $home_dir)) {
210      die("Can't set perms on $home_dir to 0755: $!");
211    }
212
213    unless (chown($uid, $gid, $home_dir)) {
214      die("Can't set owner of $home_dir to $uid/$gid: $!");
215    }
216  }
217
218  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
219    '/bin/bash');
220  auth_group_write($auth_group_file, $group, $gid, $user);
221
222  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
223  if (open(my $fh, "> $test_file")) {
224    print $fh "ABCD" x 8192;
225    unless (close($fh)) {
226      die("Can't write $test_file: $!");
227    }
228
229  } else {
230    die("Can't open $test_file: $!");
231  }
232
233  my $min_port = 40100;
234  my $max_port = 40200;
235
236  my $config = {
237    PidFile => $pid_file,
238    ScoreboardFile => $scoreboard_file,
239    SystemLog => $log_file,
240    TraceLog => $log_file,
241    Trace => 'DEFAULT:0 data:10',
242
243    AuthUserFile => $auth_user_file,
244    AuthGroupFile => $auth_group_file,
245
246    Global => {
247      PassivePorts => "$min_port $max_port",
248    },
249
250    IfModules => {
251      'mod_delay.c' => {
252        DelayEngine => 'off',
253      },
254    },
255  };
256
257  my ($port, $config_user, $config_group) = config_write($config_file, $config);
258
259  # Open pipes, for use between the parent and child processes.  Specifically,
260  # the child will indicate when it's done with its test by writing a message
261  # to the parent.
262  my ($rfh, $wfh);
263  unless (pipe($rfh, $wfh)) {
264    die("Can't open pipe: $!");
265  }
266
267  my $ex;
268
269  # Fork child
270  $self->handle_sigchld();
271  defined(my $pid = fork()) or die("Can't fork: $!");
272  if ($pid) {
273    eval {
274      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
275      $client->login($user, $passwd);
276
277      my ($resp_code, $resp_msg) = $client->pasv();
278      $client->quit();
279
280      my $expected;
281
282      $expected = 227;
283      $self->assert($expected == $resp_code,
284        test_msg("Expected response code $expected, got $resp_code"));
285
286      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
287      $self->assert(qr/$expected/, $resp_msg,
288        test_msg("Expected response message '$expected', got '$resp_msg'"));
289
290      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
291        die("Response '$resp_msg' does not match expected pattern");
292      }
293
294      my $pasv_port = ($1 * 256) + $2;
295
296      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
297        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
298    };
299
300    if ($@) {
301      $ex = $@;
302    }
303
304    $wfh->print("done\n");
305    $wfh->flush();
306
307  } else {
308    eval { server_wait($config_file, $rfh) };
309    if ($@) {
310      warn($@);
311      exit 1;
312    }
313
314    exit 0;
315  }
316
317  # Stop server
318  server_stop($pid_file);
319
320  $self->assert_child_ok($pid);
321
322  if ($ex) {
323    test_append_logfile($log_file, $ex);
324    unlink($log_file);
325
326    die($ex);
327  }
328
329  unlink($log_file);
330}
331
332sub pasv_ports_vhost {
333  my $self = shift;
334  my $tmpdir = $self->{tmpdir};
335
336  my $config_file = "$tmpdir/config.conf";
337  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
338  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
339
340  my $log_file = test_get_logfile();
341
342  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
343  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
344
345  my $user = 'proftpd';
346  my $passwd = 'test';
347  my $group = 'ftpd';
348  my $home_dir = File::Spec->rel2abs($tmpdir);
349  my $uid = 500;
350  my $gid = 500;
351
352  # Make sure that, if we're running as root, that the home directory has
353  # permissions/privs set for the account we create
354  if ($< == 0) {
355    unless (chmod(0755, $home_dir)) {
356      die("Can't set perms on $home_dir to 0755: $!");
357    }
358
359    unless (chown($uid, $gid, $home_dir)) {
360      die("Can't set owner of $home_dir to $uid/$gid: $!");
361    }
362  }
363
364  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
365    '/bin/bash');
366  auth_group_write($auth_group_file, $group, $gid, $user);
367
368  my $test_file = File::Spec->rel2abs("$tmpdir/test.txt");
369  if (open(my $fh, "> $test_file")) {
370    print $fh "ABCD" x 8192;
371    unless (close($fh)) {
372      die("Can't write $test_file: $!");
373    }
374
375  } else {
376    die("Can't open $test_file: $!");
377  }
378
379  my $min_port = 40100;
380  my $max_port = 40200;
381
382  my $config = {
383    PidFile => $pid_file,
384    ScoreboardFile => $scoreboard_file,
385    SystemLog => $log_file,
386    TraceLog => $log_file,
387    Trace => 'DEFAULT:0 data:10',
388
389    AuthUserFile => $auth_user_file,
390    AuthGroupFile => $auth_group_file,
391    Port => '0',
392    SocketBindTight => 'on',
393
394    IfModules => {
395      'mod_delay.c' => {
396        DelayEngine => 'off',
397      },
398    },
399  };
400
401  my ($port, $config_user, $config_group) = config_write($config_file, $config);
402
403  my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port();
404
405  if (open(my $fh, ">> $config_file")) {
406    print $fh <<EOC;
407<VirtualHost 127.0.0.1>
408  ServerName "Vhost"
409  Port $vhost_port
410  AuthUserFile $auth_user_file
411  AuthGroupFile $auth_group_file
412  PassivePorts $min_port $max_port
413</VirtualHost>
414EOC
415    unless (close($fh)) {
416      die("Can't write $config_file: $!");
417    }
418
419  } else {
420    die("Can't open $config_file: $!");
421  }
422
423  # Open pipes, for use between the parent and child processes.  Specifically,
424  # the child will indicate when it's done with its test by writing a message
425  # to the parent.
426  my ($rfh, $wfh);
427  unless (pipe($rfh, $wfh)) {
428    die("Can't open pipe: $!");
429  }
430
431  my $ex;
432
433  # Fork child
434  $self->handle_sigchld();
435  defined(my $pid = fork()) or die("Can't fork: $!");
436  if ($pid) {
437    eval {
438      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $vhost_port);
439      $client->login($user, $passwd);
440
441      my ($resp_code, $resp_msg) = $client->pasv();
442      $client->quit();
443
444      my $expected;
445
446      $expected = 227;
447      $self->assert($expected == $resp_code,
448        test_msg("Expected response code $expected, got $resp_code"));
449
450      $expected = '\(\d+,\d+,\d+,\d+,\d+,\d+\)';
451      $self->assert(qr/$expected/, $resp_msg,
452        test_msg("Expected response message '$expected', got '$resp_msg'"));
453
454      unless ($resp_msg =~ /\(\d+,\d+,\d+,\d+,(\d+),(\d+)\)/) {
455        die("Response '$resp_msg' does not match expected pattern");
456      }
457
458      my $pasv_port = ($1 * 256) + $2;
459
460      $self->assert($min_port <= $pasv_port && $max_port >= $pasv_port,
461        test_msg("Expected port from $min_port to $max_port, got $pasv_port"));
462    };
463
464    if ($@) {
465      $ex = $@;
466    }
467
468    $wfh->print("done\n");
469    $wfh->flush();
470
471  } else {
472    eval { server_wait($config_file, $rfh) };
473    if ($@) {
474      warn($@);
475      exit 1;
476    }
477
478    exit 0;
479  }
480
481  # Stop server
482  server_stop($pid_file);
483
484  $self->assert_child_ok($pid);
485
486  if ($ex) {
487    test_append_logfile($log_file, $ex);
488    unlink($log_file);
489
490    die($ex);
491  }
492
493  unlink($log_file);
494}
495
4961;
497