1package ProFTPD::Tests::Config::ListOptions;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use File::Path qw(mkpath);
8use File::Spec;
9use IO::Handle;
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  listoptions_opt_t => {
20    order => ++$order,
21    test_class => [qw(forking)],
22  },
23
24  listoptions_opt_1_list => {
25    order => ++$order,
26    test_class => [qw(bug forking)],
27  },
28
29  listoptions_opt_1_nlst => {
30    order => ++$order,
31    test_class => [qw(bug forking)],
32  },
33
34  listoptions_opt_1_nlst_simple_glob => {
35    order => ++$order,
36    test_class => [qw(bug forking)],
37  },
38
39  listoptions_opt_1_nlst_complex_glob => {
40    order => ++$order,
41    test_class => [qw(bug forking rootprivs)],
42  },
43
44  listoptions_listonly => {
45    order => ++$order,
46    test_class => [qw(bug forking)],
47  },
48
49  listoptions_nlstonly => {
50    order => ++$order,
51    test_class => [qw(bug forking)],
52  },
53
54  listoptions_sortednlst_bug4267 => {
55    order => ++$order,
56    test_class => [qw(bug forking)],
57  },
58
59  listoptions_maxfiles => {
60    order => ++$order,
61    test_class => [qw(forking slow)],
62  },
63
64  listoptions_nlstnamesonly_issue251 => {
65    order => ++$order,
66    test_class => [qw(bug forking)],
67  },
68
69};
70
71sub new {
72  return shift()->SUPER::new(@_);
73}
74
75sub list_tests {
76  return testsuite_get_runnable_tests($TESTS);
77}
78
79sub listoptions_opt_t {
80  my $self = shift;
81  my $tmpdir = $self->{tmpdir};
82
83  my $config_file = "$tmpdir/config.conf";
84  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
85  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
86
87  my $log_file = test_get_logfile();
88
89  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
90  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
91
92  my $user = 'proftpd';
93  my $passwd = 'test';
94  my $group = 'ftpd';
95  my $home_dir = File::Spec->rel2abs($tmpdir);
96  my $uid = 500;
97  my $gid = 500;
98
99  # Make sure that, if we're running as root, that the home directory has
100  # permissions/privs set for the account we create
101  if ($< == 0) {
102    unless (chmod(0755, $home_dir)) {
103      die("Can't set perms on $home_dir to 0755: $!");
104    }
105
106    unless (chown($uid, $gid, $home_dir)) {
107      die("Can't set owner of $home_dir to $uid/$gid: $!");
108    }
109  }
110
111  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
112    '/bin/bash');
113  auth_group_write($auth_group_file, $group, $gid, $user);
114
115  # Create three files, and use utime() to modify the last-mod time of
116  # each.
117  my $test_files = [qw(
118    a.txt
119    b.txt
120    c.txt
121    d.txt
122    e.txt
123    f.txt
124    g.txt
125    h.txt
126    i.txt
127  )];
128
129  my $count = scalar(@$test_files);
130  foreach my $test_file (sort { $a cmp $b } @$test_files) {
131    $count--;
132
133    my $path = File::Spec->rel2abs("$tmpdir/$test_file");
134    if (open(my $fh, "> $path")) {
135      print $fh "Hello, World!\n";
136      unless (close($fh)) {
137        die("Can't write $path: $!");
138      }
139
140      my $mtime = (time() - ($count * 10));
141      unless (utime(undef, $mtime, $path)) {
142        die("Can't change mtime for $path: $!");
143      }
144
145    } else {
146      die("Can't open $path: $!");
147    }
148  }
149
150  my $config = {
151    PidFile => $pid_file,
152    ScoreboardFile => $scoreboard_file,
153    SystemLog => $log_file,
154
155    AuthUserFile => $auth_user_file,
156    AuthGroupFile => $auth_group_file,
157
158    ListOptions => '-t',
159
160    IfModules => {
161      'mod_delay.c' => {
162        DelayEngine => 'off',
163      },
164    },
165  };
166
167  my ($port, $config_user, $config_group) = config_write($config_file, $config);
168
169  # Open pipes, for use between the parent and child processes.  Specifically,
170  # the child will indicate when it's done with its test by writing a message
171  # to the parent.
172  my ($rfh, $wfh);
173  unless (pipe($rfh, $wfh)) {
174    die("Can't open pipe: $!");
175  }
176
177  my $ex;
178
179  # Fork child
180  $self->handle_sigchld();
181  defined(my $pid = fork()) or die("Can't fork: $!");
182  if ($pid) {
183    eval {
184      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
185      $client->login($user, $passwd);
186
187      my $conn = $client->list_raw("*.txt");
188      unless ($conn) {
189        die("Failed to LIST: " . $client->response_code() . " " .
190          $client->response_msg());
191      }
192
193      my $buf = '';
194      my $tmp;
195      while ($conn->read($tmp, 8192, 25)) {
196        $buf .= $tmp;
197      }
198      eval { $conn->close() };
199
200      my $resp_code = $client->response_code();
201      my $resp_msg = $client->response_msg();
202
203      my $expected;
204
205      $expected = 226;
206      $self->assert($expected == $resp_code,
207        test_msg("Expected $expected, got $resp_code"));
208
209      $expected = "Transfer complete";
210      $self->assert($expected eq $resp_msg,
211        test_msg("Expected '$expected', got '$resp_msg'"));
212
213      $client->quit();
214
215      # We have to be careful of the fact that readdir returns directory
216      # entries in an unordered fashion.
217      my $res = [];
218      my $lines = [split(/\n/, $buf)];
219      foreach my $line (@$lines) {
220        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
221          push(@$res, $1);
222        }
223      }
224
225      $expected = [reverse(@$test_files)];
226
227      my $nexpected = scalar(@$expected);
228      my $nres = scalar(@$res);
229
230      $self->assert($nexpected == $nres,
231        test_msg("Expected $nexpected items, got $nres"));
232      for (my $i = 0; $i < $nexpected; $i++) {
233        $self->assert($expected->[$i] eq $res->[$i],
234          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
235      }
236    };
237
238    if ($@) {
239      $ex = $@;
240    }
241
242    $wfh->print("done\n");
243    $wfh->flush();
244
245  } else {
246    eval { server_wait($config_file, $rfh) };
247    if ($@) {
248      warn($@);
249      exit 1;
250    }
251
252    exit 0;
253  }
254
255  # Stop server
256  server_stop($pid_file);
257
258  $self->assert_child_ok($pid);
259
260  if ($ex) {
261    test_append_logfile($log_file, $ex);
262    unlink($log_file);
263
264    die($ex);
265  }
266
267  unlink($log_file);
268}
269
270sub listoptions_opt_1_list {
271  my $self = shift;
272  my $tmpdir = $self->{tmpdir};
273  my $setup = test_setup($tmpdir, 'config');
274
275  my $test_files = [qw(
276    a.txt
277    b.txt
278    c.txt
279    d.txt
280    e.txt
281    f.txt
282    g.txt
283    h.txt
284    i.txt
285  )];
286
287  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
288  mkpath($test_dir);
289
290  my $count = scalar(@$test_files);
291  foreach my $test_file (@$test_files) {
292    my $path = File::Spec->rel2abs("$test_dir/$test_file");
293    if (open(my $fh, "> $path")) {
294      print $fh "Hello, World!\n";
295      unless (close($fh)) {
296        die("Can't write $path: $!");
297      }
298
299    } else {
300      die("Can't open $path: $!");
301    }
302  }
303
304  my $config = {
305    PidFile => $setup->{pid_file},
306    ScoreboardFile => $setup->{scoreboard_file},
307    SystemLog => $setup->{log_file},
308
309    AuthUserFile => $setup->{auth_user_file},
310    AuthGroupFile => $setup->{auth_group_file},
311
312    ListOptions => '"-A -1" strict',
313
314    IfModules => {
315      'mod_delay.c' => {
316        DelayEngine => 'off',
317      },
318    },
319  };
320
321  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
322    $config);
323
324  # Open pipes, for use between the parent and child processes.  Specifically,
325  # the child will indicate when it's done with its test by writing a message
326  # to the parent.
327  my ($rfh, $wfh);
328  unless (pipe($rfh, $wfh)) {
329    die("Can't open pipe: $!");
330  }
331
332  my $ex;
333
334  # Fork child
335  $self->handle_sigchld();
336  defined(my $pid = fork()) or die("Can't fork: $!");
337  if ($pid) {
338    eval {
339      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
340      $client->login($setup->{user}, $setup->{passwd});
341
342      my $conn = $client->list_raw('test.d');
343      unless ($conn) {
344        die("Failed to LIST: " . $client->response_code() . " " .
345          $client->response_msg());
346      }
347
348      my $buf;
349      $conn->read($buf, 16384, 25);
350      eval { $conn->close() };
351
352      my $resp_code = $client->response_code();
353      my $resp_msg = $client->response_msg();
354      $self->assert_transfer_ok($resp_code, $resp_msg);
355
356      $client->quit();
357
358      if ($ENV{TEST_VERBOSE}) {
359        print STDERR "Data:\n$buf\n";
360      }
361
362      # We have to be careful of the fact that readdir returns directory
363      # entries in an unordered fashion.
364      my $res = [];
365      my $lines = [split(/\n/, $buf)];
366      foreach my $line (@$lines) {
367        push(@$res, $line);
368      }
369
370      my $expected = [@$test_files];
371      my $nexpected = scalar(@$expected);
372      my $nres = scalar(@$res);
373
374      $self->assert($nexpected == $nres,
375        "Expected $nexpected items, got $nres");
376      for (my $i = 0; $i < $nexpected; $i++) {
377        $self->assert($expected->[$i] eq $res->[$i],
378          "Expected '$expected->[$i]' at index $i, got '$res->[$i]'");
379      }
380    };
381    if ($@) {
382      $ex = $@;
383    }
384
385    $wfh->print("done\n");
386    $wfh->flush();
387
388  } else {
389    eval { server_wait($setup->{config_file}, $rfh) };
390    if ($@) {
391      warn($@);
392      exit 1;
393    }
394
395    exit 0;
396  }
397
398  # Stop server
399  server_stop($setup->{pid_file});
400  $self->assert_child_ok($pid);
401
402  test_cleanup($setup->{log_file}, $ex);
403}
404
405sub listoptions_opt_1_nlst {
406  my $self = shift;
407  my $tmpdir = $self->{tmpdir};
408  my $setup = test_setup($tmpdir, 'config');
409
410  my $test_files = [qw(
411    a.txt
412    b.txt
413    c.txt
414    d.txt
415    e.txt
416    f.txt
417    g.txt
418    h.txt
419    i.txt
420  )];
421
422  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
423  mkpath($test_dir);
424
425  my $count = scalar(@$test_files);
426  foreach my $test_file (@$test_files) {
427    my $path = File::Spec->rel2abs("$test_dir/$test_file");
428    if (open(my $fh, "> $path")) {
429      print $fh "Hello, World!\n";
430      unless (close($fh)) {
431        die("Can't write $path: $!");
432      }
433
434    } else {
435      die("Can't open $path: $!");
436    }
437  }
438
439  my $config = {
440    PidFile => $setup->{pid_file},
441    ScoreboardFile => $setup->{scoreboard_file},
442    SystemLog => $setup->{log_file},
443
444    AuthUserFile => $setup->{auth_user_file},
445    AuthGroupFile => $setup->{auth_group_file},
446
447    ListOptions => '"-A -1" strict',
448
449    IfModules => {
450      'mod_delay.c' => {
451        DelayEngine => 'off',
452      },
453    },
454  };
455
456  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
457    $config);
458
459  # Open pipes, for use between the parent and child processes.  Specifically,
460  # the child will indicate when it's done with its test by writing a message
461  # to the parent.
462  my ($rfh, $wfh);
463  unless (pipe($rfh, $wfh)) {
464    die("Can't open pipe: $!");
465  }
466
467  my $ex;
468
469  # Fork child
470  $self->handle_sigchld();
471  defined(my $pid = fork()) or die("Can't fork: $!");
472  if ($pid) {
473    eval {
474      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
475      $client->login($setup->{user}, $setup->{passwd});
476
477      my $conn = $client->nlst_raw('test.d');
478      unless ($conn) {
479        die("Failed to NLST: " . $client->response_code() . " " .
480          $client->response_msg());
481      }
482
483      my $buf;
484      $conn->read($buf, 16384, 25);
485      eval { $conn->close() };
486
487      my $resp_code = $client->response_code();
488      my $resp_msg = $client->response_msg();
489      $self->assert_transfer_ok($resp_code, $resp_msg);
490
491      $client->quit();
492
493      if ($ENV{TEST_VERBOSE}) {
494        print STDERR "Data:\n$buf\n";
495      }
496
497      # We have to be careful of the fact that readdir returns directory
498      # entries in an unordered fashion.
499      my $res = [];
500      my $lines = [split(/\n/, $buf)];
501      foreach my $line (@$lines) {
502        push(@$res, $line);
503      }
504
505      # Sort the results, so that they match the expected list.
506      $res = [sort { $a cmp $b } @$res];
507
508      my $expected = [@$test_files];
509      my $nexpected = scalar(@$expected);
510      my $nres = scalar(@$res);
511
512      $self->assert($nexpected == $nres,
513        "Expected $nexpected items, got $nres");
514
515      for (my $i = 0; $i < $nexpected; $i++) {
516        $self->assert($expected->[$i] eq $res->[$i],
517          "Expected '$expected->[$i]' at index $i, got '$res->[$i]'");
518      }
519    };
520    if ($@) {
521      $ex = $@;
522    }
523
524    $wfh->print("done\n");
525    $wfh->flush();
526
527  } else {
528    eval { server_wait($setup->{config_file}, $rfh) };
529    if ($@) {
530      warn($@);
531      exit 1;
532    }
533
534    exit 0;
535  }
536
537  # Stop server
538  server_stop($setup->{pid_file});
539  $self->assert_child_ok($pid);
540
541  test_cleanup($setup->{log_file}, $ex);
542}
543
544sub listoptions_opt_1_nlst_simple_glob {
545  my $self = shift;
546  my $tmpdir = $self->{tmpdir};
547
548  my $config_file = "$tmpdir/config.conf";
549  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
550  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
551
552  my $log_file = test_get_logfile();
553
554  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
555  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
556
557  my $user = 'proftpd';
558  my $passwd = 'test';
559  my $group = 'ftpd';
560  my $home_dir = File::Spec->rel2abs($tmpdir);
561  my $uid = 500;
562  my $gid = 500;
563
564  # Make sure that, if we're running as root, that the home directory has
565  # permissions/privs set for the account we create
566  if ($< == 0) {
567    unless (chmod(0755, $home_dir)) {
568      die("Can't set perms on $home_dir to 0755: $!");
569    }
570
571    unless (chown($uid, $gid, $home_dir)) {
572      die("Can't set owner of $home_dir to $uid/$gid: $!");
573    }
574  }
575
576  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
577    '/bin/bash');
578  auth_group_write($auth_group_file, $group, $gid, $user);
579
580  my $test_files = [qw(
581    a.txt
582    b.txt
583    c.txt
584    d.txt
585    e.txt
586    f.txt
587    g.txt
588    h.txt
589    i.txt
590  )];
591
592  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
593  mkpath($test_dir);
594
595  my $count = scalar(@$test_files);
596  foreach my $test_file (@$test_files) {
597    my $path = File::Spec->rel2abs("$test_dir/$test_file");
598    if (open(my $fh, "> $path")) {
599      print $fh "Hello, World!\n";
600      unless (close($fh)) {
601        die("Can't write $path: $!");
602      }
603
604    } else {
605      die("Can't open $path: $!");
606    }
607  }
608
609  my $config = {
610    PidFile => $pid_file,
611    ScoreboardFile => $scoreboard_file,
612    SystemLog => $log_file,
613
614    AuthUserFile => $auth_user_file,
615    AuthGroupFile => $auth_group_file,
616
617    ListOptions => '"-A -1" strict',
618
619    IfModules => {
620      'mod_delay.c' => {
621        DelayEngine => 'off',
622      },
623    },
624  };
625
626  my ($port, $config_user, $config_group) = config_write($config_file, $config);
627
628  # Open pipes, for use between the parent and child processes.  Specifically,
629  # the child will indicate when it's done with its test by writing a message
630  # to the parent.
631  my ($rfh, $wfh);
632  unless (pipe($rfh, $wfh)) {
633    die("Can't open pipe: $!");
634  }
635
636  my $ex;
637
638  # Fork child
639  $self->handle_sigchld();
640  defined(my $pid = fork()) or die("Can't fork: $!");
641  if ($pid) {
642    eval {
643      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
644      $client->login($user, $passwd);
645
646      my $conn = $client->nlst_raw('test.d/*');
647      unless ($conn) {
648        die("Failed to NLST: " . $client->response_code() . " " .
649          $client->response_msg());
650      }
651
652      my $buf;
653      $conn->read($buf, 16384, 25);
654      eval { $conn->close() };
655
656      my $resp_code = $client->response_code();
657      my $resp_msg = $client->response_msg();
658      $client->quit();
659
660      $self->assert_transfer_ok($resp_code, $resp_msg);
661
662      # We have to be careful of the fact that readdir returns directory
663      # entries in an unordered fashion.
664      my $res = [];
665      my $lines = [split(/\n/, $buf)];
666      foreach my $line (@$lines) {
667        push(@$res, $line);
668      }
669
670      # Sort the results, so that they match the expected list.
671      $res = [sort { $a cmp $b } @$res];
672
673      my $expected = [@$test_files];
674      my $nexpected = scalar(@$expected);
675      my $nres = scalar(@$res);
676
677      $self->assert($nexpected == $nres,
678        test_msg("Expected $nexpected items, got $nres"));
679
680      for (my $i = 0; $i < $nexpected; $i++) {
681        $self->assert($expected->[$i] eq $res->[$i],
682          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
683      }
684    };
685
686    if ($@) {
687      $ex = $@;
688    }
689
690    $wfh->print("done\n");
691    $wfh->flush();
692
693  } else {
694    eval { server_wait($config_file, $rfh) };
695    if ($@) {
696      warn($@);
697      exit 1;
698    }
699
700    exit 0;
701  }
702
703  # Stop server
704  server_stop($pid_file);
705
706  $self->assert_child_ok($pid);
707
708  if ($ex) {
709    test_append_logfile($log_file, $ex);
710    unlink($log_file);
711
712    die($ex);
713  }
714
715  unlink($log_file);
716}
717
718sub listoptions_opt_1_nlst_complex_glob {
719  my $self = shift;
720  my $tmpdir = $self->{tmpdir};
721
722  my $config_file = "$tmpdir/config.conf";
723  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
724  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
725
726  my $log_file = test_get_logfile();
727
728  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
729  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
730
731  my $user = 'proftpd';
732  my $passwd = 'test';
733  my $group = 'ftpd';
734  my $home_dir = File::Spec->rel2abs($tmpdir);
735  my $uid = 500;
736  my $gid = 500;
737
738  # Make sure that, if we're running as root, that the home directory has
739  # permissions/privs set for the account we create
740  if ($< == 0) {
741    unless (chmod(0755, $home_dir)) {
742      die("Can't set perms on $home_dir to 0755: $!");
743    }
744
745    unless (chown($uid, $gid, $home_dir)) {
746      die("Can't set owner of $home_dir to $uid/$gid: $!");
747    }
748  }
749
750  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
751    '/bin/bash');
752  auth_group_write($auth_group_file, $group, $gid, $user);
753
754  my $test_files = [qw(
755    2a-a.txt
756    2b-b.txt
757    2c-c.txt
758    2d-d.txt
759    2e-e.txt
760    2f-f.txt
761    2g-g.txt
762    2h-h.txt
763    2i-i.txt
764  )];
765
766  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d/sub.d");
767  mkpath($test_dir);
768
769  my $count = scalar(@$test_files);
770  foreach my $test_file (@$test_files) {
771    my $path = File::Spec->rel2abs("$test_dir/$test_file");
772    if (open(my $fh, "> $path")) {
773      print $fh "Hello, World!\n";
774      unless (close($fh)) {
775        die("Can't write $path: $!");
776      }
777
778    } else {
779      die("Can't open $path: $!");
780    }
781  }
782
783  my $config = {
784    PidFile => $pid_file,
785    ScoreboardFile => $scoreboard_file,
786    SystemLog => $log_file,
787
788    AuthUserFile => $auth_user_file,
789    AuthGroupFile => $auth_group_file,
790
791    DefaultRoot => '~',
792    ListOptions => '"-A -1" strict',
793
794    IfModules => {
795      'mod_delay.c' => {
796        DelayEngine => 'off',
797      },
798    },
799  };
800
801  my ($port, $config_user, $config_group) = config_write($config_file, $config);
802
803  # Open pipes, for use between the parent and child processes.  Specifically,
804  # the child will indicate when it's done with its test by writing a message
805  # to the parent.
806  my ($rfh, $wfh);
807  unless (pipe($rfh, $wfh)) {
808    die("Can't open pipe: $!");
809  }
810
811  my $ex;
812
813  # Fork child
814  $self->handle_sigchld();
815  defined(my $pid = fork()) or die("Can't fork: $!");
816  if ($pid) {
817    eval {
818      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
819      $client->login($user, $passwd);
820
821      my $conn = $client->nlst_raw('/test.d/sub.d/2?-*.txt');
822      unless ($conn) {
823        die("Failed to NLST: " . $client->response_code() . " " .
824          $client->response_msg());
825      }
826
827      my $buf;
828      $conn->read($buf, 16384, 25);
829      eval { $conn->close() };
830
831      my $resp_code = $client->response_code();
832      my $resp_msg = $client->response_msg();
833      $client->quit();
834
835      $self->assert_transfer_ok($resp_code, $resp_msg);
836
837      # We have to be careful of the fact that readdir returns directory
838      # entries in an unordered fashion.
839      my $res = [];
840      my $lines = [split(/\n/, $buf)];
841      foreach my $line (@$lines) {
842        push(@$res, $line);
843      }
844
845      # Sort the results, so that they match the expected list.
846      $res = [sort { $a cmp $b } @$res];
847
848      my $expected = [@$test_files];
849      my $nexpected = scalar(@$expected);
850      my $nres = scalar(@$res);
851
852      $self->assert($nexpected == $nres,
853        test_msg("Expected $nexpected items, got $nres"));
854
855      for (my $i = 0; $i < $nexpected; $i++) {
856        $self->assert($expected->[$i] eq $res->[$i],
857          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
858      }
859    };
860
861    if ($@) {
862      $ex = $@;
863    }
864
865    $wfh->print("done\n");
866    $wfh->flush();
867
868  } else {
869    eval { server_wait($config_file, $rfh) };
870    if ($@) {
871      warn($@);
872      exit 1;
873    }
874
875    exit 0;
876  }
877
878  # Stop server
879  server_stop($pid_file);
880
881  $self->assert_child_ok($pid);
882
883  if ($ex) {
884    test_append_logfile($log_file, $ex);
885    unlink($log_file);
886
887    die($ex);
888  }
889
890  unlink($log_file);
891}
892
893sub listoptions_listonly {
894  my $self = shift;
895  my $tmpdir = $self->{tmpdir};
896
897  my $config_file = "$tmpdir/config.conf";
898  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
899  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
900
901  my $log_file = test_get_logfile();
902
903  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
904  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
905
906  my $user = 'proftpd';
907  my $passwd = 'test';
908  my $group = 'ftpd';
909  my $home_dir = File::Spec->rel2abs($tmpdir);
910  my $uid = 500;
911  my $gid = 500;
912
913  # Make sure that, if we're running as root, that the home directory has
914  # permissions/privs set for the account we create
915  if ($< == 0) {
916    unless (chmod(0755, $home_dir)) {
917      die("Can't set perms on $home_dir to 0755: $!");
918    }
919
920    unless (chown($uid, $gid, $home_dir)) {
921      die("Can't set owner of $home_dir to $uid/$gid: $!");
922    }
923  }
924
925  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
926    '/bin/bash');
927  auth_group_write($auth_group_file, $group, $gid, $user);
928
929  my $test_files = [qw(
930    a.txt
931    b.txt
932    c.txt
933    d.txt
934    e.txt
935    f.txt
936    g.txt
937    h.txt
938    i.txt
939  )];
940
941  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
942  mkpath($test_dir);
943
944  my $count = scalar(@$test_files);
945  foreach my $test_file (@$test_files) {
946    my $path = File::Spec->rel2abs("$test_dir/$test_file");
947    if (open(my $fh, "> $path")) {
948      print $fh "Hello, World!\n";
949      unless (close($fh)) {
950        die("Can't write $path: $!");
951      }
952
953    } else {
954      die("Can't open $path: $!");
955    }
956  }
957
958  my $config = {
959    PidFile => $pid_file,
960    ScoreboardFile => $scoreboard_file,
961    SystemLog => $log_file,
962
963    AuthUserFile => $auth_user_file,
964    AuthGroupFile => $auth_group_file,
965    TimeoutLinger => 1,
966
967    ListOptions => '"-A -1" LISTOnly',
968
969    IfModules => {
970      'mod_delay.c' => {
971        DelayEngine => 'off',
972      },
973    },
974  };
975
976  my ($port, $config_user, $config_group) = config_write($config_file, $config);
977
978  # Open pipes, for use between the parent and child processes.  Specifically,
979  # the child will indicate when it's done with its test by writing a message
980  # to the parent.
981  my ($rfh, $wfh);
982  unless (pipe($rfh, $wfh)) {
983    die("Can't open pipe: $!");
984  }
985
986  my $ex;
987
988  # Fork child
989  $self->handle_sigchld();
990  defined(my $pid = fork()) or die("Can't fork: $!");
991  if ($pid) {
992    eval {
993      # First, connect, do a LIST, examine the results
994      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
995      $client->login($user, $passwd);
996
997      # First, do a LIST and see what we get
998      my $conn = $client->list_raw('test.d');
999      unless ($conn) {
1000        die("Failed to LIST: " . $client->response_code() . " " .
1001          $client->response_msg());
1002      }
1003
1004      my $buf;
1005      $conn->read($buf, 16384, 25);
1006      eval { $conn->close() };
1007      sleep(2);
1008
1009      my $resp_code = $client->response_code();
1010      my $resp_msg = $client->response_msg();
1011      $self->assert_transfer_ok($resp_code, $resp_msg);
1012      $client->quit();
1013
1014      # We have to be careful of the fact that readdir returns directory
1015      # entries in an unordered fashion.
1016      my $res = [];
1017      my $lines = [split(/\n/, $buf)];
1018      foreach my $line (@$lines) {
1019        push(@$res, $line);
1020      }
1021
1022      my $expected = [@$test_files];
1023      my $nexpected = scalar(@$expected);
1024      my $nres = scalar(@$res);
1025
1026      $self->assert($nexpected == $nres,
1027        test_msg("Expected $nexpected items, got $nres"));
1028      for (my $i = 0; $i < $nexpected; $i++) {
1029        $self->assert($expected->[$i] eq $res->[$i],
1030          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
1031      }
1032
1033      # Next, connect, do a NLST, examine the results
1034      $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1035      $client->login($user, $passwd);
1036
1037      $conn = $client->nlst_raw('test.d');
1038      unless ($conn) {
1039        die("Failed to NLST: " . $client->response_code() . " " .
1040          $client->response_msg());
1041      }
1042
1043      $buf = '';
1044      $conn->read($buf, 16384, 25);
1045      eval { $conn->close() };
1046      sleep(2);
1047
1048      $resp_code = $client->response_code();
1049      $resp_msg = $client->response_msg();
1050      $self->assert_transfer_ok($resp_code, $resp_msg);
1051      $client->quit();
1052
1053      # We have to be careful of the fact that readdir returns directory
1054      # entries in an unordered fashion.
1055      $res = [];
1056      $lines = [split(/\n/, $buf)];
1057      foreach my $line (@$lines) {
1058        push(@$res, $line);
1059      }
1060
1061      # Sort the results, so that they match the expected list.
1062      $res = [sort { $a cmp $b } @$res];
1063
1064      $expected = [map { "test.d/$_" } @$test_files];
1065      $nexpected = scalar(@$expected);
1066      $nres = scalar(@$res);
1067
1068      $self->assert($nexpected == $nres,
1069        test_msg("Expected $nexpected items, got $nres"));
1070      for (my $i = 0; $i < $nexpected; $i++) {
1071        $self->assert($expected->[$i] eq $res->[$i],
1072          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
1073      }
1074    };
1075
1076    if ($@) {
1077      $ex = $@;
1078    }
1079
1080    $wfh->print("done\n");
1081    $wfh->flush();
1082
1083  } else {
1084    eval { server_wait($config_file, $rfh, 30) };
1085    if ($@) {
1086      warn($@);
1087      exit 1;
1088    }
1089
1090    exit 0;
1091  }
1092
1093  # Stop server
1094  server_stop($pid_file);
1095
1096  $self->assert_child_ok($pid);
1097
1098  if ($ex) {
1099    test_append_logfile($log_file, $ex);
1100    unlink($log_file);
1101
1102    die($ex);
1103  }
1104
1105  unlink($log_file);
1106}
1107
1108sub listoptions_nlstonly {
1109  my $self = shift;
1110  my $tmpdir = $self->{tmpdir};
1111
1112  my $config_file = "$tmpdir/config.conf";
1113  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1114  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1115
1116  my $log_file = test_get_logfile();
1117
1118  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1119  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1120
1121  my $user = 'proftpd';
1122  my $passwd = 'test';
1123  my $group = 'ftpd';
1124  my $home_dir = File::Spec->rel2abs($tmpdir);
1125  my $uid = 500;
1126  my $gid = 500;
1127
1128  # Make sure that, if we're running as root, that the home directory has
1129  # permissions/privs set for the account we create
1130  if ($< == 0) {
1131    unless (chmod(0755, $home_dir)) {
1132      die("Can't set perms on $home_dir to 0755: $!");
1133    }
1134
1135    unless (chown($uid, $gid, $home_dir)) {
1136      die("Can't set owner of $home_dir to $uid/$gid: $!");
1137    }
1138  }
1139
1140  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1141    '/bin/bash');
1142  auth_group_write($auth_group_file, $group, $gid, $user);
1143
1144  my $test_files = [qw(
1145    a.txt
1146    b.txt
1147    c.txt
1148    d.txt
1149    e.txt
1150    f.txt
1151    g.txt
1152    h.txt
1153    i.txt
1154  )];
1155
1156  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
1157  mkpath($test_dir);
1158
1159  my $count = scalar(@$test_files);
1160  foreach my $test_file (@$test_files) {
1161    my $path = File::Spec->rel2abs("$test_dir/$test_file");
1162    if (open(my $fh, "> $path")) {
1163      print $fh "Hello, World!\n";
1164      unless (close($fh)) {
1165        die("Can't write $path: $!");
1166      }
1167
1168    } else {
1169      die("Can't open $path: $!");
1170    }
1171  }
1172
1173  my $config = {
1174    PidFile => $pid_file,
1175    ScoreboardFile => $scoreboard_file,
1176    SystemLog => $log_file,
1177
1178    AuthUserFile => $auth_user_file,
1179    AuthGroupFile => $auth_group_file,
1180    TimeoutLinger => 1,
1181
1182    ListOptions => '"-A -1" NLSTOnly',
1183
1184    IfModules => {
1185      'mod_delay.c' => {
1186        DelayEngine => 'off',
1187      },
1188    },
1189  };
1190
1191  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1192
1193  # Open pipes, for use between the parent and child processes.  Specifically,
1194  # the child will indicate when it's done with its test by writing a message
1195  # to the parent.
1196  my ($rfh, $wfh);
1197  unless (pipe($rfh, $wfh)) {
1198    die("Can't open pipe: $!");
1199  }
1200
1201  my $ex;
1202
1203  # Fork child
1204  $self->handle_sigchld();
1205  defined(my $pid = fork()) or die("Can't fork: $!");
1206  if ($pid) {
1207    eval {
1208      # First, connect, do a LIST, examine the results
1209      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1210      $client->login($user, $passwd);
1211
1212      # First, do a LIST and see what we get
1213      my $conn = $client->list_raw('test.d');
1214      unless ($conn) {
1215        die("Failed to LIST: " . $client->response_code() . " " .
1216          $client->response_msg());
1217      }
1218
1219      my $buf;
1220      $conn->read($buf, 16384, 25);
1221      eval { $conn->close() };
1222      sleep(2);
1223
1224      my $resp_code = $client->response_code();
1225      my $resp_msg = $client->response_msg();
1226      $self->assert_transfer_ok($resp_code, $resp_msg);
1227      $client->quit();
1228
1229      # We have to be careful of the fact that readdir returns directory
1230      # entries in an unordered fashion.
1231      my $res = [];
1232      my $lines = [split(/\n/, $buf)];
1233      foreach my $line (@$lines) {
1234        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
1235          push(@$res, $1);
1236        }
1237      }
1238
1239      my $expected = [@$test_files];
1240      my $nexpected = scalar(@$expected);
1241      my $nres = scalar(@$res);
1242
1243      $self->assert($nexpected == $nres,
1244        test_msg("Expected $nexpected items, got $nres"));
1245      for (my $i = 0; $i < $nexpected; $i++) {
1246        $self->assert($expected->[$i] eq $res->[$i],
1247          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
1248      }
1249
1250      # Next, connect, do a NLST, examine the results
1251      $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1252      $client->login($user, $passwd);
1253
1254      $conn = $client->nlst_raw('test.d');
1255      unless ($conn) {
1256        die("Failed to NLST: " . $client->response_code() . " " .
1257          $client->response_msg());
1258      }
1259
1260      $buf = '';
1261      $conn->read($buf, 16384, 25);
1262      eval { $conn->close() };
1263      sleep(2);
1264
1265      $resp_code = $client->response_code();
1266      $resp_msg = $client->response_msg();
1267      $self->assert_transfer_ok($resp_code, $resp_msg);
1268      $client->quit();
1269
1270      # We have to be careful of the fact that readdir returns directory
1271      # entries in an unordered fashion.
1272      $res = [];
1273      $lines = [split(/\n/, $buf)];
1274      foreach my $line (@$lines) {
1275        push(@$res, $line);
1276      }
1277
1278      # Sort the results, so that they match the expected list.
1279      $res = [sort { $a cmp $b } @$res];
1280
1281      $expected = [@$test_files];
1282      $nexpected = scalar(@$expected);
1283      $nres = scalar(@$res);
1284
1285      $self->assert($nexpected == $nres,
1286        test_msg("Expected $nexpected items, got $nres"));
1287      for (my $i = 0; $i < $nexpected; $i++) {
1288        $self->assert($expected->[$i] eq $res->[$i],
1289          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
1290      }
1291    };
1292
1293    if ($@) {
1294      $ex = $@;
1295    }
1296
1297    $wfh->print("done\n");
1298    $wfh->flush();
1299
1300  } else {
1301    eval { server_wait($config_file, $rfh, 30) };
1302    if ($@) {
1303      warn($@);
1304      exit 1;
1305    }
1306
1307    exit 0;
1308  }
1309
1310  # Stop server
1311  server_stop($pid_file);
1312
1313  $self->assert_child_ok($pid);
1314
1315  if ($ex) {
1316    test_append_logfile($log_file, $ex);
1317    unlink($log_file);
1318
1319    die($ex);
1320  }
1321
1322  unlink($log_file);
1323}
1324
1325sub listoptions_sortednlst_bug4267 {
1326  my $self = shift;
1327  my $tmpdir = $self->{tmpdir};
1328  my $setup = test_setup($tmpdir, 'config');
1329
1330  my $test_files = [];
1331  my $nfiles = 1000;
1332  for (my $i = 0; $i < $nfiles; $i++) {
1333    my $fileno = sprintf("%04d", $i);
1334    push(@$test_files, "$fileno.dat");
1335  }
1336
1337  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
1338  mkpath($test_dir);
1339
1340  if ($ENV{TEST_VERBOSE}) {
1341    print STDERR "# Writing out files..."
1342  }
1343
1344  my $count = scalar(@$test_files);
1345  foreach my $test_file (@$test_files) {
1346    my $path = File::Spec->rel2abs("$test_dir/$test_file");
1347    if (open(my $fh, "> $path")) {
1348      print $fh "Hello, World!\n";
1349      unless (close($fh)) {
1350        die("Can't write $path: $!");
1351      }
1352
1353    } else {
1354      die("Can't open $path: $!");
1355    }
1356  }
1357
1358  if ($ENV{TEST_VERBOSE}) {
1359    print STDERR "done\n";
1360  }
1361
1362  my $config = {
1363    PidFile => $setup->{pid_file},
1364    ScoreboardFile => $setup->{scoreboard_file},
1365    SystemLog => $setup->{log_file},
1366
1367    AuthUserFile => $setup->{auth_user_file},
1368    AuthGroupFile => $setup->{auth_group_file},
1369    TimeoutLinger => 1,
1370
1371    ListOptions => '"" SortedNLST',
1372
1373    IfModules => {
1374      'mod_delay.c' => {
1375        DelayEngine => 'off',
1376      },
1377    },
1378  };
1379
1380  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1381    $config);
1382
1383  # Open pipes, for use between the parent and child processes.  Specifically,
1384  # the child will indicate when it's done with its test by writing a message
1385  # to the parent.
1386  my ($rfh, $wfh);
1387  unless (pipe($rfh, $wfh)) {
1388    die("Can't open pipe: $!");
1389  }
1390
1391  my $ex;
1392
1393  # Fork child
1394  $self->handle_sigchld();
1395  defined(my $pid = fork()) or die("Can't fork: $!");
1396  if ($pid) {
1397    eval {
1398      sleep(1);
1399
1400      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
1401      $client->login($setup->{user}, $setup->{passwd});
1402      $client->cwd('test.d');
1403
1404      my $conn = $client->nlst_raw();
1405      unless ($conn) {
1406        die("Failed to NLST: " . $client->response_code() . " " .
1407          $client->response_msg());
1408      }
1409
1410      my ($buf, $tmp);
1411      my $res = $conn->read($tmp, 8192, 5);
1412      while ($res) {
1413        $buf .= $tmp;
1414        $tmp = '';
1415        $res = $conn->read($tmp, 8192, 5);
1416      }
1417      eval { $conn->close() };
1418      sleep(2);
1419
1420      my $resp_code = $client->response_code();
1421      my $resp_msg = $client->response_msg();
1422      $self->assert_transfer_ok($resp_code, $resp_msg);
1423
1424      if ($ENV{TEST_VERBOSE}) {
1425        print STDERR "buf:\n$buf\n";
1426      }
1427
1428      # Do NOT sort the results; we expect them to match the expected list.
1429      my $res = [];
1430      my $lines = [split(/\n/, $buf)];
1431      foreach my $line (@$lines) {
1432        push(@$res, $line);
1433      }
1434
1435      my $expected = [@$test_files];
1436      my $nexpected = scalar(@$expected);
1437      my $nres = scalar(@$res);
1438
1439      $self->assert($nexpected == $nres,
1440        test_msg("Expected $nexpected items, got $nres"));
1441      for (my $i = 0; $i < $nexpected; $i++) {
1442        $self->assert($expected->[$i] eq $res->[$i],
1443          test_msg("Expected '$expected->[$i]' at index $i, got '$res->[$i]'"));
1444      }
1445
1446      $client->quit();
1447    };
1448
1449    if ($@) {
1450      $ex = $@;
1451    }
1452
1453    $wfh->print("done\n");
1454    $wfh->flush();
1455
1456  } else {
1457    eval { server_wait($setup->{config_file}, $rfh, 180) };
1458    if ($@) {
1459      warn($@);
1460      exit 1;
1461    }
1462
1463    exit 0;
1464  }
1465
1466  # Stop server
1467  server_stop($setup->{pid_file});
1468  $self->assert_child_ok($pid);
1469
1470  test_cleanup($setup->{log_file}, $ex);
1471}
1472
1473sub listoptions_maxfiles {
1474  my $self = shift;
1475  my $tmpdir = $self->{tmpdir};
1476
1477  my $config_file = "$tmpdir/config.conf";
1478  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1479  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1480
1481  my $log_file = test_get_logfile();
1482
1483  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1484  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1485
1486  my $user = 'proftpd';
1487  my $passwd = 'test';
1488  my $group = 'ftpd';
1489  my $home_dir = File::Spec->rel2abs($tmpdir);
1490  my $uid = 500;
1491  my $gid = 500;
1492
1493  # Make sure that, if we're running as root, that the home directory has
1494  # permissions/privs set for the account we create
1495  if ($< == 0) {
1496    unless (chmod(0755, $home_dir)) {
1497      die("Can't set perms on $home_dir to 0755: $!");
1498    }
1499
1500    unless (chown($uid, $gid, $home_dir)) {
1501      die("Can't set owner of $home_dir to $uid/$gid: $!");
1502    }
1503  }
1504
1505  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1506    '/bin/bash');
1507  auth_group_write($auth_group_file, $group, $gid, $user);
1508
1509  # For this test, we need to create MANY (i.e. 110K) files in the
1510  # home directory.
1511  my $test_file_prefix = File::Spec->rel2abs("$tmpdir/test_");
1512
1513  my $max_files = 100000;
1514
1515  my $count = $max_files + 10000;
1516  print STDOUT "# Creating $count files in $tmpdir\n";
1517  for (my $i = 1; $i <= $count; $i++) {
1518    my $test_file = 'test_' . sprintf("%07s", $i);
1519    my $test_path = "$home_dir/$test_file";
1520
1521    if (open(my $fh, "> $test_path")) {
1522      close($fh);
1523
1524    } else {
1525      die("Can't open $test_path: $!");
1526    }
1527
1528    if ($i % 10000 == 0) {
1529      print STDOUT "# Created file $test_file\n";
1530    }
1531  }
1532
1533  $max_files = 100000;
1534  my $timeout = 900;
1535
1536  my $config = {
1537    PidFile => $pid_file,
1538    ScoreboardFile => $scoreboard_file,
1539    SystemLog => $log_file,
1540
1541    AuthUserFile => $auth_user_file,
1542    AuthGroupFile => $auth_group_file,
1543
1544    TimeoutIdle => $timeout + 15,
1545    TimeoutNoTransfer => $timeout + 15,
1546
1547    ListOptions => "-al maxfiles $max_files",
1548
1549    IfModules => {
1550      'mod_delay.c' => {
1551        DelayEngine => 'off',
1552      },
1553    },
1554  };
1555
1556  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1557
1558  # Open pipes, for use between the parent and child processes.  Specifically,
1559  # the child will indicate when it's done with its test by writing a message
1560  # to the parent.
1561  my ($rfh, $wfh);
1562  unless (pipe($rfh, $wfh)) {
1563    die("Can't open pipe: $!");
1564  }
1565
1566  my $ex;
1567
1568  # Fork child
1569  $self->handle_sigchld();
1570  defined(my $pid = fork()) or die("Can't fork: $!");
1571  if ($pid) {
1572    eval {
1573      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1574      $client->login($user, $passwd);
1575
1576      my $conn = $client->list_raw();
1577      unless ($conn) {
1578        die("Failed to LIST: " . $client->response_code() . " " .
1579          $client->response_msg());
1580      }
1581
1582      my $buf;
1583
1584      my $tmp;
1585      while ($conn->read($tmp, 16384, 25)) {
1586        $buf .= $tmp;
1587      }
1588
1589      eval { $conn->close() };
1590
1591      my $resp_code = $client->response_code();
1592      my $resp_msg = $client->response_msg();
1593
1594      $self->assert_transfer_ok($resp_code, $resp_msg);
1595      $client->quit();
1596
1597      # We have to be careful of the fact that readdir returns directory
1598      # entries in an unordered fashion.
1599      my $res = [];
1600      my $lines = [split(/\n/, $buf)];
1601      foreach my $line (@$lines) {
1602        if ($line =~ /^\S+\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
1603          push(@$res, $1);
1604        }
1605      }
1606
1607      my $nexpected = $max_files;
1608      my $nres = scalar(@$res);
1609
1610      $self->assert($nexpected == $nres,
1611        test_msg("Expected $nexpected items, got $nres"));
1612    };
1613
1614    if ($@) {
1615      $ex = $@;
1616    }
1617
1618    $wfh->print("done\n");
1619    $wfh->flush();
1620
1621  } else {
1622    eval { server_wait($config_file, $rfh, $timeout) };
1623    if ($@) {
1624      warn($@);
1625      exit 1;
1626    }
1627
1628    exit 0;
1629  }
1630
1631  # Stop server
1632  server_stop($pid_file);
1633
1634  $self->assert_child_ok($pid);
1635
1636  if ($ex) {
1637    test_append_logfile($log_file, $ex);
1638    unlink($log_file);
1639
1640    die($ex);
1641  }
1642
1643  unlink($log_file);
1644}
1645
1646sub listoptions_nlstnamesonly_issue251 {
1647  my $self = shift;
1648  my $tmpdir = $self->{tmpdir};
1649  my $setup = test_setup($tmpdir, 'config');
1650
1651  my $test_files = [qw(
1652    a.txt
1653    b.txt
1654    c.txt
1655    d.txt
1656    e.txt
1657    f.txt
1658    g.txt
1659    h.txt
1660    i.txt
1661  )];
1662
1663  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
1664  mkpath($test_dir);
1665
1666  my $count = scalar(@$test_files);
1667  foreach my $test_file (@$test_files) {
1668    my $path = File::Spec->rel2abs("$test_dir/$test_file");
1669    if (open(my $fh, "> $path")) {
1670      print $fh "Hello, World!\n";
1671      unless (close($fh)) {
1672        die("Can't write $path: $!");
1673      }
1674
1675    } else {
1676      die("Can't open $path: $!");
1677    }
1678  }
1679
1680  my $config = {
1681    PidFile => $setup->{pid_file},
1682    ScoreboardFile => $setup->{scoreboard_file},
1683    SystemLog => $setup->{log_file},
1684
1685    AuthUserFile => $setup->{auth_user_file},
1686    AuthGroupFile => $setup->{auth_group_file},
1687
1688    ListOptions => '"-A -1 NLSTOnly"',
1689
1690    IfModules => {
1691      'mod_delay.c' => {
1692        DelayEngine => 'off',
1693      },
1694    },
1695  };
1696
1697  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
1698    $config);
1699
1700  # Open pipes, for use between the parent and child processes.  Specifically,
1701  # the child will indicate when it's done with its test by writing a message
1702  # to the parent.
1703  my ($rfh, $wfh);
1704  unless (pipe($rfh, $wfh)) {
1705    die("Can't open pipe: $!");
1706  }
1707
1708  my $ex;
1709
1710  # Fork child
1711  $self->handle_sigchld();
1712  defined(my $pid = fork()) or die("Can't fork: $!");
1713  if ($pid) {
1714    eval {
1715      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1716      $client->login($setup->{user}, $setup->{passwd});
1717
1718      my $conn = $client->nlst_raw('test.d');
1719      unless ($conn) {
1720        die("Failed to NLST: " . $client->response_code() . " " .
1721          $client->response_msg());
1722      }
1723
1724      my $buf;
1725      $conn->read($buf, 16384, 25);
1726      eval { $conn->close() };
1727
1728      my $resp_code = $client->response_code();
1729      my $resp_msg = $client->response_msg();
1730      $self->assert_transfer_ok($resp_code, $resp_msg);
1731
1732      $client->quit();
1733
1734      if ($ENV{TEST_VERBOSE}) {
1735        print STDERR "Data:\n$buf\n";
1736      }
1737
1738      # We have to be careful of the fact that readdir returns directory
1739      # entries in an unordered fashion.
1740      my $res = [];
1741      my $lines = [split(/\n/, $buf)];
1742      foreach my $line (@$lines) {
1743        push(@$res, $line);
1744      }
1745
1746      # Sort the results, so that they match the expected list.
1747      $res = [sort { $a cmp $b } @$res];
1748
1749      my $expected = [@$test_files];
1750      my $nexpected = scalar(@$expected);
1751      my $nres = scalar(@$res);
1752
1753      $self->assert($nexpected == $nres,
1754        "Expected $nexpected items, got $nres");
1755
1756      for (my $i = 0; $i < $nexpected; $i++) {
1757        $self->assert($expected->[$i] eq $res->[$i],
1758          "Expected '$expected->[$i]' at index $i, got '$res->[$i]'");
1759      }
1760    };
1761    if ($@) {
1762      $ex = $@;
1763    }
1764
1765    $wfh->print("done\n");
1766    $wfh->flush();
1767
1768  } else {
1769    eval { server_wait($setup->{config_file}, $rfh) };
1770    if ($@) {
1771      warn($@);
1772      exit 1;
1773    }
1774
1775    exit 0;
1776  }
1777
1778  # Stop server
1779  server_stop($setup->{pid_file});
1780  $self->assert_child_ok($pid);
1781
1782  test_cleanup($setup->{log_file}, $ex);
1783}
1784
17851;
1786