1package ProFTPD::Tests::Commands::HOST;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use File::Spec;
8use IO::Handle;
9use Sys::HostAddr;
10
11use ProFTPD::TestSuite::FTP;
12use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
13
14$| = 1;
15
16my $order = 0;
17
18my $TESTS = {
19  host_after_login_fails => {
20    order => ++$order,
21    test_class => [qw(forking)],
22  },
23
24  host_login_succeeds => {
25    order => ++$order,
26    test_class => [qw(forking)],
27  },
28
29  # TODO:
30  #  host_default_server_login_succeeds
31  #    where we do NOT send HOST, and mark an aliased name-based host with
32  #    DefaultServer
33
34  host_literal_ipv6_fails_useipv6_off => {
35    order => ++$order,
36    test_class => [qw(feature_ipv6 forking)],
37  },
38
39  host_literal_ipv6_with_port_fails => {
40    order => ++$order,
41    test_class => [qw(feature_ipv6 forking)],
42  },
43
44  host_invalid_ipv4_fails => {
45    order => ++$order,
46    test_class => [qw(forking)],
47  },
48
49  host_ipv4_with_port_fails => {
50    order => ++$order,
51    test_class => [qw(forking)],
52  },
53
54  host_unknown_host_fails => {
55    order => ++$order,
56    test_class => [qw(forking)],
57  },
58
59  host_known_ipv4_same_host_ok => {
60    order => ++$order,
61    test_class => [qw(forking)],
62  },
63
64  host_known_ipv6_same_host_ok => {
65    order => ++$order,
66    test_class => [qw(feature_ipv6 forking)],
67  },
68
69  host_known_ipv4_different_host_fails => {
70    order => ++$order,
71    test_class => [qw(forking)],
72  },
73
74  host_known_ipv6_different_host_fails => {
75    order => ++$order,
76    test_class => [qw(feature_ipv6 forking)],
77  },
78
79  host_known_dns_ok => {
80    order => ++$order,
81    test_class => [qw(forking)],
82  },
83
84  host_before_feat_ok => {
85    order => ++$order,
86    test_class => [qw(forking mod_tls)],
87  },
88
89  host_after_feat_ok => {
90    order => ++$order,
91    test_class => [qw(forking mod_tls)],
92  },
93
94  # Various config situations
95
96  # 2-vhost config, 2 vhost (same addr, different DNS) config,
97
98  host_config_limit_denied => {
99    order => ++$order,
100    test_class => [qw(forking)],
101  },
102};
103
104sub new {
105  return shift()->SUPER::new(@_);
106}
107
108sub list_tests {
109  return testsuite_get_runnable_tests($TESTS);
110}
111
112sub host_login_succeeds {
113  my $self = shift;
114  my $tmpdir = $self->{tmpdir};
115  my $setup = test_setup($tmpdir, 'cmds');
116
117  my $config = {
118    PidFile => $setup->{pid_file},
119    ScoreboardFile => $setup->{scoreboard_file},
120    SystemLog => $setup->{log_file},
121    TraceLog => $setup->{log_file},
122    Trace => 'DEFAULT:20',
123
124    AuthOrder => 'mod_auth_pam.c mod_auth_unix.c',
125    DefaultServer => 'on',
126
127    IfModules => {
128      'mod_delay.c' => {
129        DelayEngine => 'off',
130      },
131    },
132  };
133
134  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
135    $config);
136
137  if (open(my $fh, ">> $setup->{config_file}")) {
138    print $fh <<EOC;
139<VirtualHost 127.0.0.1>
140  ServerAlias localhost
141  Port $port
142
143  AuthUserFile $setup->{auth_user_file}
144  AuthGroupFile $setup->{auth_group_file}
145  AuthOrder mod_auth_file.c
146
147  <IfModule mod_delay.c>
148    DelayEngine off
149  </IfModule>
150</VirtualHost>
151EOC
152    unless (close($fh)) {
153      die("Can't write $setup->{config_file}: $!");
154    }
155
156  } else {
157    die("Can't open $setup->{config_file}: $!");
158  }
159
160  # Open pipes, for use between the parent and child processes.  Specifically,
161  # the child will indicate when it's done with its test by writing a message
162  # to the parent.
163  my ($rfh, $wfh);
164  unless (pipe($rfh, $wfh)) {
165    die("Can't open pipe: $!");
166  }
167
168  my $ex;
169
170  # Fork child
171  $self->handle_sigchld();
172  defined(my $pid = fork()) or die("Can't fork: $!");
173  if ($pid) {
174    eval {
175      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
176      $client->host('localhost');
177      $client->login($setup->{user}, $setup->{passwd});
178
179      my $resp_code = $client->response_code();
180      my $resp_msg = $client->response_msg();
181
182      my $expected;
183
184      $expected = 230;
185      $self->assert($expected == $resp_code,
186        test_msg("Expected response code $expected, got $resp_code"));
187
188      $expected = "User $setup->{user} logged in";
189      $self->assert($expected eq $resp_msg,
190        test_msg("Expected response message '$expected', got '$resp_msg'"));
191    };
192
193    if ($@) {
194      $ex = $@;
195    }
196
197    $wfh->print("done\n");
198    $wfh->flush();
199
200  } else {
201    eval { server_wait($setup->{config_file}, $rfh) };
202    if ($@) {
203      warn($@);
204      exit 1;
205    }
206
207    exit 0;
208  }
209
210  # Stop server
211  server_stop($setup->{pid_file});
212
213  $self->assert_child_ok($pid);
214
215  test_cleanup($setup->{log_file}, $ex);
216}
217
218sub host_after_login_fails {
219  my $self = shift;
220  my $tmpdir = $self->{tmpdir};
221  my $setup = test_setup($tmpdir, 'cmds');
222
223  my $config = {
224    PidFile => $setup->{pid_file},
225    ScoreboardFile => $setup->{scoreboard_file},
226    SystemLog => $setup->{log_file},
227    TraceLog => $setup->{log_file},
228    Trace => 'DEFAULT:20',
229
230    AuthUserFile => $setup->{auth_user_file},
231    AuthGroupFile => $setup->{auth_group_file},
232
233    IfModules => {
234      'mod_delay.c' => {
235        DelayEngine => 'off',
236      },
237    },
238  };
239
240  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
241    $config);
242
243  # Open pipes, for use between the parent and child processes.  Specifically,
244  # the child will indicate when it's done with its test by writing a message
245  # to the parent.
246  my ($rfh, $wfh);
247  unless (pipe($rfh, $wfh)) {
248    die("Can't open pipe: $!");
249  }
250
251  my $ex;
252
253  # Fork child
254  $self->handle_sigchld();
255  defined(my $pid = fork()) or die("Can't fork: $!");
256  if ($pid) {
257    eval {
258      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
259      $client->login($setup->{user}, $setup->{passwd});
260
261      eval { $client->host('localhost') };
262      unless ($@) {
263        die("HOST after login succeeded unexpectedly");
264      }
265
266      my $resp_code = $client->response_code();
267      my $resp_msg = $client->response_msg();
268
269      my $expected;
270
271      $expected = 503;
272      $self->assert($expected == $resp_code,
273        test_msg("Expected response code $expected, got $resp_code"));
274
275      $expected = "Bad sequence of commands";
276      $self->assert($expected eq $resp_msg,
277        test_msg("Expected response message '$expected', got '$resp_msg'"));
278    };
279
280    if ($@) {
281      $ex = $@;
282    }
283
284    $wfh->print("done\n");
285    $wfh->flush();
286
287  } else {
288    eval { server_wait($setup->{config_file}, $rfh) };
289    if ($@) {
290      warn($@);
291      exit 1;
292    }
293
294    exit 0;
295  }
296
297  # Stop server
298  server_stop($setup->{pid_file});
299
300  $self->assert_child_ok($pid);
301
302  test_cleanup($setup->{log_file}, $ex);
303}
304
305sub host_literal_ipv6_fails_useipv6_off {
306  my $self = shift;
307  my $tmpdir = $self->{tmpdir};
308  my $setup = test_setup($tmpdir, 'cmds');
309
310  my $config = {
311    PidFile => $setup->{pid_file},
312    ScoreboardFile => $setup->{scoreboard_file},
313    SystemLog => $setup->{log_file},
314
315    AuthUserFile => $setup->{auth_user_file},
316    AuthGroupFile => $setup->{auth_group_file},
317    UseIPv6 => 'off',
318
319    IfModules => {
320      'mod_delay.c' => {
321        DelayEngine => 'off',
322      },
323    },
324  };
325
326  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
327    $config);
328
329  # Open pipes, for use between the parent and child processes.  Specifically,
330  # the child will indicate when it's done with its test by writing a message
331  # to the parent.
332  my ($rfh, $wfh);
333  unless (pipe($rfh, $wfh)) {
334    die("Can't open pipe: $!");
335  }
336
337  my $ex;
338
339  # Fork child
340  $self->handle_sigchld();
341  defined(my $pid = fork()) or die("Can't fork: $!");
342  if ($pid) {
343    eval {
344      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
345
346      my $host = '[::1]';
347      eval { $client->host($host) };
348      unless ($@) {
349        die("HOST after login succeeded unexpectedly");
350      }
351
352      my $resp_code = $client->response_code();
353      my $resp_msg = $client->response_msg();
354
355      my $expected;
356
357      $expected = 501;
358      $self->assert($expected == $resp_code,
359        test_msg("Expected response code $expected, got $resp_code"));
360
361      $expected = "$host: Invalid hostname provided";
362      $self->assert($expected eq $resp_msg,
363        test_msg("Expected response message '$expected', got '$resp_msg'"));
364    };
365
366    if ($@) {
367      $ex = $@;
368    }
369
370    $wfh->print("done\n");
371    $wfh->flush();
372
373  } else {
374    eval { server_wait($setup->{config_file}, $rfh) };
375    if ($@) {
376      warn($@);
377      exit 1;
378    }
379
380    exit 0;
381  }
382
383  # Stop server
384  server_stop($setup->{pid_file});
385
386  $self->assert_child_ok($pid);
387
388  test_cleanup($setup->{log_file}, $ex);
389}
390
391sub host_literal_ipv6_with_port_fails {
392  my $self = shift;
393  my $tmpdir = $self->{tmpdir};
394  my $setup = test_setup($tmpdir, 'cmds');
395
396  my $config = {
397    PidFile => $setup->{pid_file},
398    ScoreboardFile => $setup->{scoreboard_file},
399    SystemLog => $setup->{log_file},
400
401    AuthUserFile => $setup->{auth_user_file},
402    AuthGroupFile => $setup->{auth_group_file},
403    UseIPv6 => 'on',
404
405    IfModules => {
406      'mod_delay.c' => {
407        DelayEngine => 'off',
408      },
409    },
410  };
411
412  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
413    $config);
414
415  # Open pipes, for use between the parent and child processes.  Specifically,
416  # the child will indicate when it's done with its test by writing a message
417  # to the parent.
418  my ($rfh, $wfh);
419  unless (pipe($rfh, $wfh)) {
420    die("Can't open pipe: $!");
421  }
422
423  my $ex;
424
425  # Fork child
426  $self->handle_sigchld();
427  defined(my $pid = fork()) or die("Can't fork: $!");
428  if ($pid) {
429    eval {
430      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
431
432      my $host = "[::1]:$port";
433      eval { $client->host($host) };
434      unless ($@) {
435        die("HOST after login succeeded unexpectedly");
436      }
437
438      my $resp_code = $client->response_code();
439      my $resp_msg = $client->response_msg();
440
441      my $expected;
442
443      $expected = 501;
444      $self->assert($expected == $resp_code,
445        test_msg("Expected response code $expected, got $resp_code"));
446
447      $expected = "$host: Invalid IPv6 address provided";
448      $self->assert($expected eq $resp_msg,
449        test_msg("Expected response message '$expected', got '$resp_msg'"));
450    };
451
452    if ($@) {
453      $ex = $@;
454    }
455
456    $wfh->print("done\n");
457    $wfh->flush();
458
459  } else {
460    eval { server_wait($setup->{config_file}, $rfh) };
461    if ($@) {
462      warn($@);
463      exit 1;
464    }
465
466    exit 0;
467  }
468
469  # Stop server
470  server_stop($setup->{pid_file});
471
472  $self->assert_child_ok($pid);
473
474  test_cleanup($setup->{log_file}, $ex);
475}
476
477sub host_invalid_ipv4_fails {
478  my $self = shift;
479  my $tmpdir = $self->{tmpdir};
480  my $setup = test_setup($tmpdir, 'cmds');
481
482  my $config = {
483    PidFile => $setup->{pid_file},
484    ScoreboardFile => $setup->{scoreboard_file},
485    SystemLog => $setup->{log_file},
486
487    AuthUserFile => $setup->{auth_user_file},
488    AuthGroupFile => $setup->{auth_group_file},
489
490    IfModules => {
491      'mod_delay.c' => {
492        DelayEngine => 'off',
493      },
494    },
495  };
496
497  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
498    $config);
499
500  # Open pipes, for use between the parent and child processes.  Specifically,
501  # the child will indicate when it's done with its test by writing a message
502  # to the parent.
503  my ($rfh, $wfh);
504  unless (pipe($rfh, $wfh)) {
505    die("Can't open pipe: $!");
506  }
507
508  my $ex;
509
510  # Fork child
511  $self->handle_sigchld();
512  defined(my $pid = fork()) or die("Can't fork: $!");
513  if ($pid) {
514    eval {
515      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
516
517      my $host = "300.400.500.600";
518      eval { $client->host($host) };
519      unless ($@) {
520        die("HOST after login succeeded unexpectedly");
521      }
522
523      my $resp_code = $client->response_code();
524      my $resp_msg = $client->response_msg();
525
526      my $expected;
527
528      $expected = 504;
529      $self->assert($expected == $resp_code,
530        test_msg("Expected response code $expected, got $resp_code"));
531
532      $expected = "$host: Unknown hostname provided";
533      $self->assert($expected eq $resp_msg,
534        test_msg("Expected response message '$expected', got '$resp_msg'"));
535    };
536
537    if ($@) {
538      $ex = $@;
539    }
540
541    $wfh->print("done\n");
542    $wfh->flush();
543
544  } else {
545    eval { server_wait($setup->{config_file}, $rfh) };
546    if ($@) {
547      warn($@);
548      exit 1;
549    }
550
551    exit 0;
552  }
553
554  # Stop server
555  server_stop($setup->{pid_file});
556
557  $self->assert_child_ok($pid);
558
559  test_cleanup($setup->{log_file}, $ex);
560}
561
562sub host_ipv4_with_port_fails {
563  my $self = shift;
564  my $tmpdir = $self->{tmpdir};
565  my $setup = test_setup($tmpdir, 'cmds');
566
567  my $config = {
568    PidFile => $setup->{pid_file},
569    ScoreboardFile => $setup->{scoreboard_file},
570    SystemLog => $setup->{log_file},
571
572    AuthUserFile => $setup->{auth_user_file},
573    AuthGroupFile => $setup->{auth_group_file},
574
575    IfModules => {
576      'mod_delay.c' => {
577        DelayEngine => 'off',
578      },
579    },
580  };
581
582  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
583    $config);
584
585  # Open pipes, for use between the parent and child processes.  Specifically,
586  # the child will indicate when it's done with its test by writing a message
587  # to the parent.
588  my ($rfh, $wfh);
589  unless (pipe($rfh, $wfh)) {
590    die("Can't open pipe: $!");
591  }
592
593  my $ex;
594
595  # Fork child
596  $self->handle_sigchld();
597  defined(my $pid = fork()) or die("Can't fork: $!");
598  if ($pid) {
599    eval {
600      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
601
602      my $host = "127.0.0.1:$port";
603      eval { $client->host($host) };
604      unless ($@) {
605        die("HOST after login succeeded unexpectedly");
606      }
607
608      my $resp_code = $client->response_code();
609      my $resp_msg = $client->response_msg();
610
611      my $expected;
612
613      $expected = 501;
614      $self->assert($expected == $resp_code,
615        test_msg("Expected response code $expected, got $resp_code"));
616
617      $expected = "$host: Invalid hostname provided";
618      $self->assert($expected eq $resp_msg,
619        test_msg("Expected response message '$expected', got '$resp_msg'"));
620    };
621
622    if ($@) {
623      $ex = $@;
624    }
625
626    $wfh->print("done\n");
627    $wfh->flush();
628
629  } else {
630    eval { server_wait($setup->{config_file}, $rfh) };
631    if ($@) {
632      warn($@);
633      exit 1;
634    }
635
636    exit 0;
637  }
638
639  # Stop server
640  server_stop($setup->{pid_file});
641
642  $self->assert_child_ok($pid);
643
644  test_cleanup($setup->{log_file}, $ex);
645}
646
647sub host_unknown_host_fails {
648  my $self = shift;
649  my $tmpdir = $self->{tmpdir};
650  my $setup = test_setup($tmpdir, 'cmds');
651
652  my $config = {
653    PidFile => $setup->{pid_file},
654    ScoreboardFile => $setup->{scoreboard_file},
655    SystemLog => $setup->{log_file},
656
657    AuthUserFile => $setup->{auth_user_file},
658    AuthGroupFile => $setup->{auth_group_file},
659
660    IfModules => {
661      'mod_delay.c' => {
662        DelayEngine => 'off',
663      },
664    },
665  };
666
667  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
668    $config);
669
670  # Open pipes, for use between the parent and child processes.  Specifically,
671  # the child will indicate when it's done with its test by writing a message
672  # to the parent.
673  my ($rfh, $wfh);
674  unless (pipe($rfh, $wfh)) {
675    die("Can't open pipe: $!");
676  }
677
678  my $ex;
679
680  # Fork child
681  $self->handle_sigchld();
682  defined(my $pid = fork()) or die("Can't fork: $!");
683  if ($pid) {
684    eval {
685      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
686      my $host = "nosuchname.proftpd.org";
687
688      eval { $client->host($host) };
689      unless ($@) {
690        die("HOST $host succeeded unexpectedly");
691      }
692
693      my $resp_code = $client->response_code();
694      my $resp_msg = $client->response_msg();
695
696      my $expected;
697
698      $expected = 504;
699      $self->assert($expected == $resp_code,
700        test_msg("Expected response code $expected, got $resp_code"));
701
702      $expected = "$host: Unknown hostname provided";
703      $self->assert($expected eq $resp_msg,
704        test_msg("Expected response message '$expected', got '$resp_msg'"));
705    };
706
707    if ($@) {
708      $ex = $@;
709    }
710
711    $wfh->print("done\n");
712    $wfh->flush();
713
714  } else {
715    eval { server_wait($setup->{config_file}, $rfh) };
716    if ($@) {
717      warn($@);
718      exit 1;
719    }
720
721    exit 0;
722  }
723
724  # Stop server
725  server_stop($setup->{pid_file});
726
727  $self->assert_child_ok($pid);
728
729  test_cleanup($setup->{log_file}, $ex);
730}
731
732sub host_known_ipv4_same_host_ok {
733  my $self = shift;
734  my $tmpdir = $self->{tmpdir};
735  my $setup = test_setup($tmpdir, 'cmds');
736
737  my $config = {
738    PidFile => $setup->{pid_file},
739    ScoreboardFile => $setup->{scoreboard_file},
740    SystemLog => $setup->{log_file},
741
742    AuthUserFile => $setup->{auth_user_file},
743    AuthGroupFile => $setup->{auth_group_file},
744
745    IfModules => {
746      'mod_delay.c' => {
747        DelayEngine => 'off',
748      },
749    },
750  };
751
752  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
753    $config);
754
755  # Open pipes, for use between the parent and child processes.  Specifically,
756  # the child will indicate when it's done with its test by writing a message
757  # to the parent.
758  my ($rfh, $wfh);
759  unless (pipe($rfh, $wfh)) {
760    die("Can't open pipe: $!");
761  }
762
763  my $ex;
764
765  # Fork child
766  $self->handle_sigchld();
767  defined(my $pid = fork()) or die("Can't fork: $!");
768  if ($pid) {
769    eval {
770      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
771      my $host = "127.0.0.1";
772
773      $client->host($host);
774      my $resp_code = $client->response_code();
775      my $resp_msg = $client->response_msg();
776
777      my $expected;
778
779      $expected = 220;
780      $self->assert($expected == $resp_code,
781        test_msg("Expected response code $expected, got $resp_code"));
782
783      $expected = "HOST command successful";
784      $self->assert($expected eq $resp_msg,
785        test_msg("Expected response message '$expected', got '$resp_msg'"));
786    };
787
788    if ($@) {
789      $ex = $@;
790    }
791
792    $wfh->print("done\n");
793    $wfh->flush();
794
795  } else {
796    eval { server_wait($setup->{config_file}, $rfh) };
797    if ($@) {
798      warn($@);
799      exit 1;
800    }
801
802    exit 0;
803  }
804
805  # Stop server
806  server_stop($setup->{pid_file});
807
808  $self->assert_child_ok($pid);
809
810  test_cleanup($setup->{log_file}, $ex);
811}
812
813sub host_known_ipv6_same_host_ok {
814  my $self = shift;
815  my $tmpdir = $self->{tmpdir};
816  my $setup = test_setup($tmpdir, 'cmds');
817
818  my $config = {
819    PidFile => $setup->{pid_file},
820    ScoreboardFile => $setup->{scoreboard_file},
821    SystemLog => $setup->{log_file},
822
823    AuthUserFile => $setup->{auth_user_file},
824    AuthGroupFile => $setup->{auth_group_file},
825    UseIPv6 => 'on',
826    DefaultAddress => '::1',
827
828    IfModules => {
829      'mod_delay.c' => {
830        DelayEngine => 'off',
831      },
832    },
833  };
834
835  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
836    $config);
837
838  # Open pipes, for use between the parent and child processes.  Specifically,
839  # the child will indicate when it's done with its test by writing a message
840  # to the parent.
841  my ($rfh, $wfh);
842  unless (pipe($rfh, $wfh)) {
843    die("Can't open pipe: $!");
844  }
845
846  my $ex;
847
848  # Fork child
849  $self->handle_sigchld();
850  defined(my $pid = fork()) or die("Can't fork: $!");
851  if ($pid) {
852    eval {
853      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
854      my $host = "[::ffff:127.0.0.1]";
855
856      $client->host($host);
857
858      my $resp_code = $client->response_code();
859      my $resp_msg = $client->response_msg();
860
861      my $expected;
862
863      $expected = 220;
864      $self->assert($expected == $resp_code,
865        test_msg("Expected response code $expected, got $resp_code"));
866
867      $expected = "HOST command successful";
868      $self->assert($expected eq $resp_msg,
869        test_msg("Expected response message '$expected', got '$resp_msg'"));
870    };
871
872    if ($@) {
873      $ex = $@;
874    }
875
876    $wfh->print("done\n");
877    $wfh->flush();
878
879  } else {
880    eval { server_wait($setup->{config_file}, $rfh) };
881    if ($@) {
882      warn($@);
883      exit 1;
884    }
885
886    exit 0;
887  }
888
889  # Stop server
890  server_stop($setup->{pid_file});
891
892  $self->assert_child_ok($pid);
893
894  test_cleanup($setup->{log_file}, $ex);
895}
896
897sub host_known_ipv4_different_host_fails {
898  my $self = shift;
899  my $tmpdir = $self->{tmpdir};
900  my $setup = test_setup($tmpdir, 'cmds');
901
902  my $ipv4_addr = Sys::HostAddr->new();
903  my $v4_addrs = $ipv4_addr->addresses();
904
905  my $vhost_addr;
906  foreach my $v4_addr (@$v4_addrs) {
907    if ($v4_addr ne '127.0.0.1') {
908      $vhost_addr = $v4_addr;
909      last;
910    }
911  }
912
913  if (!defined($vhost_addr)) {
914    print STDERR "+ Unable to find additional IPv4 addresses, skipping test\n";
915    return;
916  }
917
918  my $config = {
919    PidFile => $setup->{pid_file},
920    ScoreboardFile => $setup->{scoreboard_file},
921    SystemLog => $setup->{log_file},
922
923    AuthUserFile => $setup->{auth_user_file},
924    AuthGroupFile => $setup->{auth_group_file},
925
926    IfModules => {
927      'mod_delay.c' => {
928        DelayEngine => 'off',
929      },
930    },
931  };
932
933  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
934    $config);
935
936  if (open(my $fh, ">> $setup->{config_file}")) {
937    print $fh <<EOC;
938<VirtualHost $vhost_addr>
939  Port $port
940  ServerName "Other Virtual Host"
941</VirtualHost>
942EOC
943    unless (close($fh)) {
944      die("Can't write $setup->{config_file}: $!");
945    }
946
947  } else {
948    die("Can't open $setup->{config_file}: $!");
949  }
950
951  # Open pipes, for use between the parent and child processes.  Specifically,
952  # the child will indicate when it's done with its test by writing a message
953  # to the parent.
954  my ($rfh, $wfh);
955  unless (pipe($rfh, $wfh)) {
956    die("Can't open pipe: $!");
957  }
958
959  my $ex;
960
961  # Fork child
962  $self->handle_sigchld();
963  defined(my $pid = fork()) or die("Can't fork: $!");
964  if ($pid) {
965    eval {
966      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
967      my $host = $vhost_addr;
968
969      eval { $client->host($host) };
970      unless ($@) {
971        die("HOST $host succeeded unexpectedly");
972      }
973
974      my $resp_code = $client->response_code();
975      my $resp_msg = $client->response_msg();
976
977      my $expected;
978
979      $expected = 504;
980      $self->assert($expected == $resp_code,
981        test_msg("Expected response code $expected, got $resp_code"));
982
983      $expected = "$host: Unknown hostname provided";
984      $self->assert($expected eq $resp_msg,
985        test_msg("Expected response message '$expected', got '$resp_msg'"));
986    };
987
988    if ($@) {
989      $ex = $@;
990    }
991
992    $wfh->print("done\n");
993    $wfh->flush();
994
995  } else {
996    eval { server_wait($setup->{config_file}, $rfh) };
997    if ($@) {
998      warn($@);
999      exit 1;
1000    }
1001
1002    exit 0;
1003  }
1004
1005  # Stop server
1006  server_stop($setup->{pid_file});
1007
1008  $self->assert_child_ok($pid);
1009
1010  test_cleanup($setup->{log_file}, $ex);
1011}
1012
1013sub host_known_ipv6_different_host_fails {
1014  my $self = shift;
1015  my $tmpdir = $self->{tmpdir};
1016  my $setup = test_setup($tmpdir, 'cmds');
1017
1018  my $ipv6_addr = Sys::HostAddr->new(ipv => 6);
1019  my $v6_addrs = $ipv6_addr->addresses();
1020
1021  my $vhost_addr;
1022  foreach my $v6_addr (@$v6_addrs) {
1023    if ($v6_addr ne '::1') {
1024      $vhost_addr = $v6_addr;
1025
1026      # Trim off the link-local scope, if present
1027      # $vhost_addr =~ s/\%(.*)?$//;
1028
1029      last;
1030    }
1031  }
1032
1033  if (!defined($vhost_addr)) {
1034    print STDERR "+ Unable to find additional IPv6 addresses, skipping test\n";
1035    return;
1036  }
1037
1038  my $config = {
1039    PidFile => $setup->{pid_file},
1040    ScoreboardFile => $setup->{scoreboard_file},
1041    SystemLog => $setup->{log_file},
1042
1043    AuthUserFile => $setup->{auth_user_file},
1044    AuthGroupFile => $setup->{auth_group_file},
1045    UseIPv6 => 'on',
1046    DefaultAddress => '::1',
1047
1048    IfModules => {
1049      'mod_delay.c' => {
1050        DelayEngine => 'off',
1051      },
1052    },
1053  };
1054
1055  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1056    $config);
1057
1058  if (open(my $fh, ">> $setup->{config_file}")) {
1059    print $fh <<EOC;
1060<VirtualHost $vhost_addr>
1061  Port $port
1062  ServerName "Other Virtual Host"
1063</VirtualHost>
1064EOC
1065    unless (close($fh)) {
1066      die("Can't write $setup->{config_file}: $!");
1067    }
1068
1069  } else {
1070    die("Can't open $setup->{config_file}: $!");
1071  }
1072
1073  # Open pipes, for use between the parent and child processes.  Specifically,
1074  # the child will indicate when it's done with its test by writing a message
1075  # to the parent.
1076  my ($rfh, $wfh);
1077  unless (pipe($rfh, $wfh)) {
1078    die("Can't open pipe: $!");
1079  }
1080
1081  my $ex;
1082
1083  # Fork child
1084  $self->handle_sigchld();
1085  defined(my $pid = fork()) or die("Can't fork: $!");
1086  if ($pid) {
1087    eval {
1088      my $client = ProFTPD::TestSuite::FTP->new('localhost', $port);
1089      my $host = ('[' . $vhost_addr . ']');
1090
1091      eval { $client->host($host) };
1092      unless ($@) {
1093        die("HOST $host succeeded unexpectedly");
1094      }
1095
1096      my $resp_code = $client->response_code();
1097      my $resp_msg = $client->response_msg();
1098
1099      my $expected;
1100
1101      $expected = 504;
1102      $self->assert($expected == $resp_code,
1103        test_msg("Expected response code $expected, got $resp_code"));
1104
1105      $expected = "$host: Unknown hostname provided";
1106      $self->assert($expected eq $resp_msg,
1107        test_msg("Expected response message '$expected', got '$resp_msg'"));
1108    };
1109
1110    if ($@) {
1111      $ex = $@;
1112    }
1113
1114    $wfh->print("done\n");
1115    $wfh->flush();
1116
1117  } else {
1118    eval { server_wait($setup->{config_file}, $rfh) };
1119    if ($@) {
1120      warn($@);
1121      exit 1;
1122    }
1123
1124    exit 0;
1125  }
1126
1127  # Stop server
1128  server_stop($setup->{pid_file});
1129
1130  $self->assert_child_ok($pid);
1131
1132  test_cleanup($setup->{log_file}, $ex);
1133}
1134
1135sub host_known_dns_ok {
1136  my $self = shift;
1137  my $tmpdir = $self->{tmpdir};
1138  my $setup = test_setup($tmpdir, 'cmds');
1139
1140  my $config = {
1141    PidFile => $setup->{pid_file},
1142    ScoreboardFile => $setup->{scoreboard_file},
1143    SystemLog => $setup->{log_file},
1144    TraceLog => $setup->{log_file},
1145    Trace => 'binding:20',
1146
1147    AuthUserFile => $setup->{auth_user_file},
1148    AuthGroupFile => $setup->{auth_group_file},
1149
1150    IfModules => {
1151      'mod_delay.c' => {
1152        DelayEngine => 'off',
1153      },
1154    },
1155  };
1156
1157  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1158    $config);
1159
1160  if (open(my $fh, ">> $setup->{config_file}")) {
1161    my $dns_name = 'localhost';
1162
1163    print $fh <<EOC;
1164<VirtualHost $dns_name>
1165  Port $port
1166  ServerName $dns_name
1167  ServerIdent off
1168</VirtualHost>
1169EOC
1170    unless (close($fh)) {
1171      die("Can't write $setup->{config_file}: $!");
1172    }
1173
1174  } else {
1175    die("Can't open $setup->{config_file}: $!");
1176  }
1177
1178  # Open pipes, for use between the parent and child processes.  Specifically,
1179  # the child will indicate when it's done with its test by writing a message
1180  # to the parent.
1181  my ($rfh, $wfh);
1182  unless (pipe($rfh, $wfh)) {
1183    die("Can't open pipe: $!");
1184  }
1185
1186  my $ex;
1187
1188  # Fork child
1189  $self->handle_sigchld();
1190  defined(my $pid = fork()) or die("Can't fork: $!");
1191  if ($pid) {
1192    eval {
1193      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1194      my $host = "localhost";
1195
1196      $client->host($host);
1197      my $resp_code = $client->response_code();
1198      my $resp_msg = $client->response_msg();
1199
1200      my $expected;
1201
1202      $expected = 220;
1203      $self->assert($expected == $resp_code,
1204        test_msg("Expected response code $expected, got $resp_code"));
1205
1206      $expected = '127.0.0.1 FTP server ready';
1207      $self->assert($expected eq $resp_msg,
1208        test_msg("Expected response message '$expected', got '$resp_msg'"));
1209    };
1210
1211    if ($@) {
1212      $ex = $@;
1213    }
1214
1215    $wfh->print("done\n");
1216    $wfh->flush();
1217
1218  } else {
1219    eval { server_wait($setup->{config_file}, $rfh) };
1220    if ($@) {
1221      warn($@);
1222      exit 1;
1223    }
1224
1225    exit 0;
1226  }
1227
1228  # Stop server
1229  server_stop($setup->{pid_file});
1230
1231  $self->assert_child_ok($pid);
1232
1233  test_cleanup($setup->{log_file}, $ex);
1234}
1235
1236sub host_config_limit_denied {
1237  my $self = shift;
1238  my $tmpdir = $self->{tmpdir};
1239  my $setup = test_setup($tmpdir, 'cmds');
1240
1241  my $config = {
1242    PidFile => $setup->{pid_file},
1243    ScoreboardFile => $setup->{scoreboard_file},
1244    SystemLog => $setup->{log_file},
1245
1246    AuthUserFile => $setup->{auth_user_file},
1247    AuthGroupFile => $setup->{auth_group_file},
1248
1249    IfModules => {
1250      'mod_delay.c' => {
1251        DelayEngine => 'off',
1252      },
1253    },
1254  };
1255
1256  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1257    $config);
1258
1259  if (open(my $fh, ">> $setup->{config_file}")) {
1260    print $fh <<EOC;
1261<Limit HOST>
1262  DenyAll
1263</Limit>
1264EOC
1265    unless (close($fh)) {
1266      die("Can't write $setup->{config_file}: $!");
1267    }
1268
1269  } else {
1270    die("Can't open $setup->{config_file}: $!");
1271  }
1272
1273  # Open pipes, for use between the parent and child processes.  Specifically,
1274  # the child will indicate when it's done with its test by writing a message
1275  # to the parent.
1276  my ($rfh, $wfh);
1277  unless (pipe($rfh, $wfh)) {
1278    die("Can't open pipe: $!");
1279  }
1280
1281  my $ex;
1282
1283  # Fork child
1284  $self->handle_sigchld();
1285  defined(my $pid = fork()) or die("Can't fork: $!");
1286  if ($pid) {
1287    eval {
1288      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1289
1290      my $host = "127.0.0.1";
1291      eval { $client->host($host) };
1292      unless ($@) {
1293        die("HOST after login succeeded unexpectedly");
1294      }
1295
1296      my $resp_code = $client->response_code();
1297      my $resp_msg = $client->response_msg();
1298
1299      my $expected;
1300
1301      $expected = 504;
1302      $self->assert($expected == $resp_code,
1303        test_msg("Expected response code $expected, got $resp_code"));
1304
1305      $expected = "$host: Permission denied";
1306      $self->assert($expected eq $resp_msg,
1307        test_msg("Expected response message '$expected', got '$resp_msg'"));
1308    };
1309
1310    if ($@) {
1311      $ex = $@;
1312    }
1313
1314    $wfh->print("done\n");
1315    $wfh->flush();
1316
1317  } else {
1318    eval { server_wait($setup->{config_file}, $rfh) };
1319    if ($@) {
1320      warn($@);
1321      exit 1;
1322    }
1323
1324    exit 0;
1325  }
1326
1327  # Stop server
1328  server_stop($setup->{pid_file});
1329
1330  $self->assert_child_ok($pid);
1331
1332  test_cleanup($setup->{log_file}, $ex);
1333}
1334
1335sub host_before_feat_ok {
1336  my $self = shift;
1337  my $tmpdir = $self->{tmpdir};
1338  my $setup = test_setup($tmpdir, 'cmds');
1339
1340  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
1341  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
1342
1343  my $server_alias = 'ftp.nospam.org';
1344  my $server_name = 'Other Virtual Host';
1345
1346  my $config = {
1347    PidFile => $setup->{pid_file},
1348    ScoreboardFile => $setup->{scoreboard_file},
1349    SystemLog => $setup->{log_file},
1350
1351    AuthUserFile => $setup->{auth_user_file},
1352    AuthGroupFile => $setup->{auth_group_file},
1353
1354    IfModules => {
1355      'mod_delay.c' => {
1356        DelayEngine => 'off',
1357      },
1358    },
1359  };
1360
1361  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1362    $config);
1363
1364  if (open(my $fh, ">> $setup->{config_file}")) {
1365    print $fh <<EOC;
1366<VirtualHost 127.0.0.1>
1367  Port $port
1368  ServerName "$server_name"
1369  ServerAlias $server_alias
1370
1371  TLSEngine on
1372  TLSLog $setup->{log_file}
1373  TLSRSACertificateFile $cert_file
1374  TLSCACertificateFile $ca_file
1375</VirtualHost>
1376EOC
1377    unless (close($fh)) {
1378      die("Can't write $setup->{config_file}: $!");
1379    }
1380
1381  } else {
1382    die("Can't open $setup->{config_file}: $!");
1383  }
1384
1385  # Open pipes, for use between the parent and child processes.  Specifically,
1386  # the child will indicate when it's done with its test by writing a message
1387  # to the parent.
1388  my ($rfh, $wfh);
1389  unless (pipe($rfh, $wfh)) {
1390    die("Can't open pipe: $!");
1391  }
1392
1393  my $ex;
1394
1395  # Fork child
1396  $self->handle_sigchld();
1397  defined(my $pid = fork()) or die("Can't fork: $!");
1398  if ($pid) {
1399    eval {
1400      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1401      $client->host($server_alias);
1402      my $resp_code = $client->response_code();
1403      my $resp_msg = $client->response_msg();
1404
1405      my $expected;
1406
1407      $expected = 220;
1408      $self->assert($expected == $resp_code,
1409        test_msg("Expected response code $expected, got $resp_code"));
1410
1411      $expected = $server_name;
1412      $self->assert(qr/$expected/, $resp_msg,
1413        test_msg("Expected response message '$expected', got '$resp_msg'"));
1414
1415      $client->feat();
1416      $resp_code = $client->response_code();
1417      my $resp_msgs = $client->response_msgs();
1418
1419      $expected = 211;
1420      $self->assert($expected == $resp_code,
1421        test_msg("Expected response code $expected, got $resp_code"));
1422
1423      my $saw_auth_tls = 0;
1424      foreach my $feat (@$resp_msgs) {
1425        if ($feat =~ /AUTH TLS/) {
1426          $saw_auth_tls = 1;
1427          last;
1428        }
1429      }
1430
1431      $self->assert($saw_auth_tls,
1432        test_msg("Did not see expected 'AUTH TLS' feature listed via FEAT"));
1433
1434      $client->quit();
1435    };
1436
1437    if ($@) {
1438      $ex = $@;
1439    }
1440
1441    $wfh->print("done\n");
1442    $wfh->flush();
1443
1444  } else {
1445    eval { server_wait($setup->{config_file}, $rfh) };
1446    if ($@) {
1447      warn($@);
1448      exit 1;
1449    }
1450
1451    exit 0;
1452  }
1453
1454  # Stop server
1455  server_stop($setup->{pid_file});
1456
1457  $self->assert_child_ok($pid);
1458
1459  test_cleanup($setup->{log_file}, $ex);
1460}
1461
1462sub host_after_feat_ok {
1463  my $self = shift;
1464  my $tmpdir = $self->{tmpdir};
1465  my $setup = test_setup($tmpdir, 'cmds');
1466
1467  my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem');
1468  my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem');
1469
1470  my $server_alias = 'ftp.nospam.org';
1471  my $server_name = 'Other Virtual Host';
1472
1473  my $config = {
1474    PidFile => $setup->{pid_file},
1475    ScoreboardFile => $setup->{scoreboard_file},
1476    SystemLog => $setup->{log_file},
1477
1478    AuthUserFile => $setup->{auth_user_file},
1479    AuthGroupFile => $setup->{auth_group_file},
1480
1481    IfModules => {
1482      'mod_delay.c' => {
1483        DelayEngine => 'off',
1484      },
1485    },
1486  };
1487
1488  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1489    $config);
1490
1491  if (open(my $fh, ">> $setup->{config_file}")) {
1492    print $fh <<EOC;
1493<VirtualHost 127.0.0.1>
1494  Port $port
1495  ServerName "$server_name"
1496  ServerAlias $server_alias
1497
1498  TLSEngine on
1499  TLSLog $setup->{log_file}
1500  TLSRSACertificateFile $cert_file
1501  TLSCACertificateFile $ca_file
1502</VirtualHost>
1503EOC
1504    unless (close($fh)) {
1505      die("Can't write $setup->{config_file}: $!");
1506    }
1507
1508  } else {
1509    die("Can't open $setup->{config_file}: $!");
1510  }
1511
1512  # Open pipes, for use between the parent and child processes.  Specifically,
1513  # the child will indicate when it's done with its test by writing a message
1514  # to the parent.
1515  my ($rfh, $wfh);
1516  unless (pipe($rfh, $wfh)) {
1517    die("Can't open pipe: $!");
1518  }
1519
1520  my $ex;
1521
1522  # Fork child
1523  $self->handle_sigchld();
1524  defined(my $pid = fork()) or die("Can't fork: $!");
1525  if ($pid) {
1526    eval {
1527      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1528      $client->feat();
1529
1530      my $resp_code = $client->response_code();
1531      my $resp_msgs = $client->response_msgs();
1532
1533      my $expected;
1534
1535      $expected = 211;
1536      $self->assert($expected == $resp_code,
1537        test_msg("Expected response code $expected, got $resp_code"));
1538
1539      my $saw_auth_tls = 0;
1540      foreach my $feat (@$resp_msgs) {
1541        if ($feat =~ /AUTH TLS/) {
1542          $saw_auth_tls = 1;
1543          last;
1544        }
1545      }
1546
1547      $self->assert(!$saw_auth_tls,
1548        test_msg("Saw 'AUTH TLS' feature listed via FEAT unexpectedly"));
1549
1550      $client->host($server_alias);
1551      $resp_code = $client->response_code();
1552      my $resp_msg = $client->response_msg();
1553
1554      $expected = 220;
1555      $self->assert($expected == $resp_code,
1556        test_msg("Expected response code $expected, got $resp_code"));
1557
1558      $expected = $server_name;
1559      $self->assert(qr/$expected/, $resp_msg,
1560        test_msg("Expected response message '$expected', got '$resp_msg'"));
1561
1562      $client->quit();
1563    };
1564
1565    if ($@) {
1566      $ex = $@;
1567    }
1568
1569    $wfh->print("done\n");
1570    $wfh->flush();
1571
1572  } else {
1573    eval { server_wait($setup->{config_file}, $rfh) };
1574    if ($@) {
1575      warn($@);
1576      exit 1;
1577    }
1578
1579    exit 0;
1580  }
1581
1582  # Stop server
1583  server_stop($setup->{pid_file});
1584
1585  $self->assert_child_ok($pid);
1586
1587  test_cleanup($setup->{log_file}, $ex);
1588}
1589
15901;
1591