source: src/router/proftpd/tests/t/lib/ProFTPD/Tests/Config/ShowSymlinks.pm @ 17880

Last change on this file since 17880 was 17880, checked in by BrainSlayer, 19 months ago

update

File size: 95.8 KB
Line 
1package ProFTPD::Tests::Config::ShowSymlinks;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use Cwd;
8use File::Path qw(mkpath);
9use File::Spec;
10use IO::Handle;
11
12use ProFTPD::TestSuite::FTP;
13use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
14
15$| = 1;
16
17my $order = 0;
18
19my $TESTS = {
20  # XXX All of these tests use relative symlink paths.  Also need
21  # tests which use absolute symlink paths, in both chrooted and non-chrooted
22  # flavors.
23
24  showsymlinks_off_list_rel_symlinked_file => {
25    order => ++$order,
26    test_class => [qw(forking)],
27  },
28
29  showsymlinks_on_list_rel_symlinked_file => {
30    order => ++$order,
31    test_class => [qw(forking)],
32  },
33
34  showsymlinks_off_list_rel_symlinked_dir => {
35    order => ++$order,
36    test_class => [qw(forking)],
37  },
38
39  showsymlinks_on_list_rel_symlinked_dir => {
40    order => ++$order,
41    test_class => [qw(forking)],
42  },
43
44  showsymlinks_off_nlst_rel_symlinked_file => {
45    order => ++$order,
46    test_class => [qw(forking)],
47  },
48
49  showsymlinks_on_nlst_rel_symlinked_file => {
50    order => ++$order,
51    test_class => [qw(forking)],
52  },
53
54  showsymlinks_off_nlst_rel_symlinked_dir => {
55    order => ++$order,
56    test_class => [qw(forking)],
57  },
58
59  showsymlinks_on_nlst_rel_symlinked_dir => {
60    order => ++$order,
61    test_class => [qw(forking)],
62  },
63
64  showsymlinks_off_mlsd_rel_symlinked_file => {
65    order => ++$order,
66    test_class => [qw(forking)],
67  },
68
69  showsymlinks_on_mlsd_rel_symlinked_file => {
70    order => ++$order,
71    test_class => [qw(forking)],
72  },
73
74  showsymlinks_off_mlsd_rel_symlinked_dir => {
75    order => ++$order,
76    test_class => [qw(forking)],
77  },
78
79  showsymlinks_on_mlsd_rel_symlinked_dir => {
80    order => ++$order,
81    test_class => [qw(forking)],
82  },
83
84  showsymlinks_off_mlst_rel_symlinked_file => {
85    order => ++$order,
86    test_class => [qw(forking)],
87  },
88
89  showsymlinks_off_mlst_rel_symlinked_file_chrooted => {
90    order => ++$order,
91    test_class => [qw(forking rootprivs)],
92  },
93
94  showsymlinks_on_mlst_rel_symlinked_file => {
95    order => ++$order,
96    test_class => [qw(forking)],
97  },
98
99  showsymlinks_on_mlst_rel_symlinked_file_chrooted => {
100    order => ++$order,
101    test_class => [qw(forking rootprivs)],
102  },
103
104  showsymlinks_off_mlst_rel_symlinked_dir => {
105    order => ++$order,
106    test_class => [qw(forking)],
107  },
108
109  showsymlinks_off_mlst_rel_symlinked_dir_chrooted => {
110    order => ++$order,
111    test_class => [qw(forking rootprivs)],
112  },
113
114  showsymlinks_on_mlst_rel_symlinked_dir => {
115    order => ++$order,
116    test_class => [qw(forking)],
117  },
118
119  showsymlinks_on_mlst_rel_symlinked_dir_chrooted => {
120    order => ++$order,
121    test_class => [qw(forking rootprivs)],
122  },
123
124  showsymlinks_off_stat_rel_symlinked_file => {
125    order => ++$order,
126    test_class => [qw(forking)],
127  },
128
129  showsymlinks_on_stat_rel_symlinked_file => {
130    order => ++$order,
131    test_class => [qw(forking)],
132  },
133
134  showsymlinks_off_stat_rel_symlinked_dir => {
135    order => ++$order,
136    test_class => [qw(forking)],
137  },
138
139  showsymlinks_on_stat_rel_symlinked_dir => {
140    order => ++$order,
141    test_class => [qw(forking)],
142  },
143
144};
145
146sub new {
147  return shift()->SUPER::new(@_);
148}
149
150sub list_tests {
151  return testsuite_get_runnable_tests($TESTS);
152}
153
154sub showsymlinks_off_list_rel_symlinked_file {
155  my $self = shift;
156  my $tmpdir = $self->{tmpdir};
157
158  my $config_file = "$tmpdir/config.conf";
159  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
160  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
161
162  my $log_file = File::Spec->rel2abs('tests.log');
163
164  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
165  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
166
167  my $user = 'proftpd';
168  my $passwd = 'test';
169  my $group = 'ftpd';
170  my $home_dir = File::Spec->rel2abs($tmpdir);
171  my $uid = 500;
172  my $gid = 500;
173
174  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
175  mkpath($foo_dir);
176
177  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
178  if (open(my $fh, "> $test_file")) {
179    print $fh "Hello, World!\n";
180    unless (close($fh)) {
181      die("Can't write $test_file: $!");
182    }
183
184  } else {
185    die("Can't open $test_file: $!");
186  }
187
188  # Change to the 'foo' directory in order to create a relative path in the
189  # symlink we need
190
191  my $cwd = getcwd();
192  unless (chdir("$foo_dir")) {
193    die("Can't chdir to $foo_dir: $!");
194  }
195
196  unless (symlink('test.txt', 'test.lnk')) {
197    die("Can't symlink 'test.txt' to 'test.lnk': $!");
198  }
199
200  unless (chdir($cwd)) {
201    die("Can't chdir to $cwd: $!");
202  }
203
204  # Make sure that, if we're running as root, that the home directory has
205  # permissions/privs set for the account we create
206  if ($< == 0) {
207    unless (chmod(0755, $home_dir, $foo_dir)) {
208      die("Can't set perms on $home_dir to 0755: $!");
209    }
210
211    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
212      die("Can't set owner of $home_dir to $uid/$gid: $!");
213    }
214  }
215
216  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
217    '/bin/bash');
218  auth_group_write($auth_group_file, $group, $gid, $user);
219
220  my $config = {
221    PidFile => $pid_file,
222    ScoreboardFile => $scoreboard_file,
223    SystemLog => $log_file,
224
225    AuthUserFile => $auth_user_file,
226    AuthGroupFile => $auth_group_file,
227    ShowSymlinks => 'off',
228
229    IfModules => {
230      'mod_delay.c' => {
231        DelayEngine => 'off',
232      },
233    },
234  };
235
236  my ($port, $config_user, $config_group) = config_write($config_file, $config);
237
238  # Open pipes, for use between the parent and child processes.  Specifically,
239  # the child will indicate when it's done with its test by writing a message
240  # to the parent.
241  my ($rfh, $wfh);
242  unless (pipe($rfh, $wfh)) {
243    die("Can't open pipe: $!");
244  }
245
246  my $ex;
247
248  # Fork child
249  $self->handle_sigchld();
250  defined(my $pid = fork()) or die("Can't fork: $!");
251  if ($pid) {
252    eval {
253      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
254      $client->login($user, $passwd);
255
256      my $conn = $client->list_raw('-F foo');
257      unless ($conn) {
258        die("LIST failed: " . $client->response_code() . " " .
259          $client->response_msg());
260      }
261
262      my $buf;
263      $conn->read($buf, 8192, 25);
264      eval { $conn->close() };
265
266      # We have to be careful of the fact that readdir returns directory
267      # entries in an unordered fashion.
268      my $res = {};
269      my $lines = [split(/\n/, $buf)];
270      foreach my $line (@$lines) {
271        if ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
272          $res->{$2} = $1;
273        }
274      }
275
276      my $expected = {
277        'test.lnk' => '-rw-r--r--',
278        'test.txt' => '-rw-r--r--',
279      };
280
281      my $ok = 1;
282      my $mismatch = '';
283      foreach my $name (keys(%$res)) {
284        unless (defined($expected->{$name})) {
285          $mismatch = $name;
286          $ok = 0;
287          last;
288        }
289
290        unless ($res->{$name} eq $expected->{$name}) {
291          $mismatch = $name;
292          $ok = 0;
293          last;
294        }
295      }
296
297      $self->assert($ok,
298        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
299
300      my $count = scalar(keys(%$res));
301      $expected = 2;
302      unless ($count == $expected) {
303        die("LIST returned wrong number of entries (expected $expected, got $count)");
304      }
305    };
306
307    if ($@) {
308      $ex = $@;
309    }
310
311    $wfh->print("done\n");
312    $wfh->flush();
313
314  } else {
315    eval { server_wait($config_file, $rfh) };
316    if ($@) {
317      warn($@);
318      exit 1;
319    }
320
321    exit 0;
322  }
323
324  # Stop server
325  server_stop($pid_file);
326
327  $self->assert_child_ok($pid);
328
329  if ($ex) {
330    die($ex);
331  }
332
333  unlink($log_file);
334}
335
336sub showsymlinks_on_list_rel_symlinked_file {
337  my $self = shift;
338  my $tmpdir = $self->{tmpdir};
339
340  my $config_file = "$tmpdir/config.conf";
341  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
342  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
343
344  my $log_file = File::Spec->rel2abs('tests.log');
345
346  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
347  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
348
349  my $user = 'proftpd';
350  my $passwd = 'test';
351  my $group = 'ftpd';
352  my $home_dir = File::Spec->rel2abs($tmpdir);
353  my $uid = 500;
354  my $gid = 500;
355
356  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
357  mkpath($foo_dir);
358
359  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
360  if (open(my $fh, "> $test_file")) {
361    print $fh "Hello, World!\n";
362    unless (close($fh)) {
363      die("Can't write $test_file: $!");
364    }
365
366  } else {
367    die("Can't open $test_file: $!");
368  }
369
370  # Change to the 'foo' directory in order to create a relative path in the
371  # symlink we need
372
373  my $cwd = getcwd();
374  unless (chdir("$foo_dir")) {
375    die("Can't chdir to $foo_dir: $!");
376  }
377
378  unless (symlink('test.txt', 'test.lnk')) {
379    die("Can't symlink 'test.txt' to 'test.lnk': $!");
380  }
381
382  unless (chdir($cwd)) {
383    die("Can't chdir to $cwd: $!");
384  }
385
386  # Make sure that, if we're running as root, that the home directory has
387  # permissions/privs set for the account we create
388  if ($< == 0) {
389    unless (chmod(0755, $home_dir, $foo_dir)) {
390      die("Can't set perms on $home_dir to 0755: $!");
391    }
392
393    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
394      die("Can't set owner of $home_dir to $uid/$gid: $!");
395    }
396  }
397
398  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
399    '/bin/bash');
400  auth_group_write($auth_group_file, $group, $gid, $user);
401
402  my $config = {
403    PidFile => $pid_file,
404    ScoreboardFile => $scoreboard_file,
405    SystemLog => $log_file,
406
407    AuthUserFile => $auth_user_file,
408    AuthGroupFile => $auth_group_file,
409    ShowSymlinks => 'on',
410
411    IfModules => {
412      'mod_delay.c' => {
413        DelayEngine => 'off',
414      },
415    },
416  };
417
418  my ($port, $config_user, $config_group) = config_write($config_file, $config);
419
420  # Open pipes, for use between the parent and child processes.  Specifically,
421  # the child will indicate when it's done with its test by writing a message
422  # to the parent.
423  my ($rfh, $wfh);
424  unless (pipe($rfh, $wfh)) {
425    die("Can't open pipe: $!");
426  }
427
428  my $ex;
429
430  # Fork child
431  $self->handle_sigchld();
432  defined(my $pid = fork()) or die("Can't fork: $!");
433  if ($pid) {
434    eval {
435      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
436      $client->login($user, $passwd);
437
438      my $conn = $client->list_raw('-F foo');
439      unless ($conn) {
440        die("LIST failed: " . $client->response_code() . " " .
441          $client->response_msg());
442      }
443
444      my $buf;
445      $conn->read($buf, 8192, 25);
446      eval { $conn->close() };
447
448      # We have to be careful of the fact that readdir returns directory
449      # entries in an unordered fashion.
450      my $res = {};
451      my $lines = [split(/\n/, $buf)];
452      foreach my $line (@$lines) {
453        if ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(\S+)$/) {
454          $res->{$2} = $1;
455
456        } elsif ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(.*)?$/) {
457          $res->{$2} = $1;
458        }
459      }
460
461      my $expected = {
462        'test.lnk -> test.txt' => 'lrwxrwxrwx',
463        'test.txt' => '-rw-r--r--',
464      };
465
466      my $ok = 1;
467      my $mismatch = '';
468      foreach my $name (keys(%$res)) {
469        unless (defined($expected->{$name})) {
470          $mismatch = $name;
471          $ok = 0;
472          last;
473        }
474
475        unless ($res->{$name} eq $expected->{$name}) {
476          $mismatch = $name;
477          $ok = 0;
478          last;
479        }
480      }
481
482      $self->assert($ok,
483        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
484
485      my $count = scalar(keys(%$res));
486      $expected = 2;
487      unless ($count == $expected) {
488        die("LIST returned wrong number of entries (expected $expected, got $count)");
489      }
490    };
491
492    if ($@) {
493      $ex = $@;
494    }
495
496    $wfh->print("done\n");
497    $wfh->flush();
498
499  } else {
500    eval { server_wait($config_file, $rfh) };
501    if ($@) {
502      warn($@);
503      exit 1;
504    }
505
506    exit 0;
507  }
508
509  # Stop server
510  server_stop($pid_file);
511
512  $self->assert_child_ok($pid);
513
514  if ($ex) {
515    die($ex);
516  }
517
518  unlink($log_file);
519}
520
521sub showsymlinks_off_list_rel_symlinked_dir {
522  my $self = shift;
523  my $tmpdir = $self->{tmpdir};
524
525  my $config_file = "$tmpdir/config.conf";
526  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
527  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
528
529  my $log_file = File::Spec->rel2abs('tests.log');
530
531  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
532  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
533
534  my $user = 'proftpd';
535  my $passwd = 'test';
536  my $group = 'ftpd';
537  my $home_dir = File::Spec->rel2abs($tmpdir);
538  my $uid = 500;
539  my $gid = 500;
540
541  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
542  mkpath($foo_dir);
543
544  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
545  mkpath($test_dir);
546
547  # Change to the 'foo' directory in order to create a relative path in the
548  # symlink we need
549
550  my $cwd = getcwd();
551  unless (chdir("$foo_dir")) {
552    die("Can't chdir to $foo_dir: $!");
553  }
554
555  unless (symlink('test.d', 'test.lnk')) {
556    die("Can't symlink 'test.d' to 'test.lnk': $!");
557  }
558
559  unless (chdir($cwd)) {
560    die("Can't chdir to $cwd: $!");
561  }
562
563  # Make sure that, if we're running as root, that the home directory has
564  # permissions/privs set for the account we create
565  if ($< == 0) {
566    unless (chmod(0755, $home_dir, $foo_dir, $test_dir)) {
567      die("Can't set perms on $home_dir to 0755: $!");
568    }
569
570    unless (chown($uid, $gid, $home_dir, $foo_dir, $test_dir)) {
571      die("Can't set owner of $home_dir to $uid/$gid: $!");
572    }
573  }
574
575  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
576    '/bin/bash');
577  auth_group_write($auth_group_file, $group, $gid, $user);
578
579  my $config = {
580    PidFile => $pid_file,
581    ScoreboardFile => $scoreboard_file,
582    SystemLog => $log_file,
583
584    AuthUserFile => $auth_user_file,
585    AuthGroupFile => $auth_group_file,
586    ShowSymlinks => 'off',
587
588    IfModules => {
589      'mod_delay.c' => {
590        DelayEngine => 'off',
591      },
592    },
593  };
594
595  my ($port, $config_user, $config_group) = config_write($config_file, $config);
596
597  # Open pipes, for use between the parent and child processes.  Specifically,
598  # the child will indicate when it's done with its test by writing a message
599  # to the parent.
600  my ($rfh, $wfh);
601  unless (pipe($rfh, $wfh)) {
602    die("Can't open pipe: $!");
603  }
604
605  my $ex;
606
607  # Fork child
608  $self->handle_sigchld();
609  defined(my $pid = fork()) or die("Can't fork: $!");
610  if ($pid) {
611    eval {
612      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
613      $client->login($user, $passwd);
614
615      my $conn = $client->list_raw('-F foo');
616      unless ($conn) {
617        die("LIST failed: " . $client->response_code() . " " .
618          $client->response_msg());
619      }
620
621      my $buf;
622      $conn->read($buf, 8192, 25);
623      eval { $conn->close() };
624
625      # We have to be careful of the fact that readdir returns directory
626      # entries in an unordered fashion.
627      my $res = {};
628      my $lines = [split(/\n/, $buf)];
629      foreach my $line (@$lines) {
630        if ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
631          $res->{$2} = $1;
632        }
633      }
634
635      my $expected = {
636        'test.lnk/' => 'drwxr-xr-x',
637        'test.d/' => 'drwxr-xr-x',
638      };
639
640      my $ok = 1;
641      my $mismatch = '';
642      foreach my $name (keys(%$res)) {
643        unless (defined($expected->{$name})) {
644          $mismatch = $name;
645          $ok = 0;
646          last;
647        }
648
649        unless ($res->{$name} eq $expected->{$name}) {
650          $mismatch = $name;
651          $ok = 0;
652          last;
653        }
654      }
655
656      $self->assert($ok,
657        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
658
659      my $count = scalar(keys(%$res));
660      $expected = 2;
661      unless ($count == $expected) {
662        die("LIST returned wrong number of entries (expected $expected, got $count)");
663      }
664    };
665
666    if ($@) {
667      $ex = $@;
668    }
669
670    $wfh->print("done\n");
671    $wfh->flush();
672
673  } else {
674    eval { server_wait($config_file, $rfh) };
675    if ($@) {
676      warn($@);
677      exit 1;
678    }
679
680    exit 0;
681  }
682
683  # Stop server
684  server_stop($pid_file);
685
686  $self->assert_child_ok($pid);
687
688  if ($ex) {
689    die($ex);
690  }
691
692  unlink($log_file);
693}
694
695sub showsymlinks_on_list_rel_symlinked_dir {
696  my $self = shift;
697  my $tmpdir = $self->{tmpdir};
698
699  my $config_file = "$tmpdir/config.conf";
700  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
701  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
702
703  my $log_file = File::Spec->rel2abs('tests.log');
704
705  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
706  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
707
708  my $user = 'proftpd';
709  my $passwd = 'test';
710  my $group = 'ftpd';
711  my $home_dir = File::Spec->rel2abs($tmpdir);
712  my $uid = 500;
713  my $gid = 500;
714
715  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
716  mkpath($foo_dir);
717
718  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
719  mkpath($test_dir);
720
721  # Change to the 'foo' directory in order to create a relative path in the
722  # symlink we need
723
724  my $cwd = getcwd();
725  unless (chdir("$foo_dir")) {
726    die("Can't chdir to $foo_dir: $!");
727  }
728
729  unless (symlink('test.d', 'test.lnk')) {
730    die("Can't symlink 'test.d' to 'test.lnk': $!");
731  }
732
733  unless (chdir($cwd)) {
734    die("Can't chdir to $cwd: $!");
735  }
736
737  # Make sure that, if we're running as root, that the home directory has
738  # permissions/privs set for the account we create
739  if ($< == 0) {
740    unless (chmod(0755, $home_dir, $foo_dir, $test_dir)) {
741      die("Can't set perms on $home_dir to 0755: $!");
742    }
743
744    unless (chown($uid, $gid, $home_dir, $foo_dir, $test_dir)) {
745      die("Can't set owner of $home_dir to $uid/$gid: $!");
746    }
747  }
748
749  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
750    '/bin/bash');
751  auth_group_write($auth_group_file, $group, $gid, $user);
752
753  my $config = {
754    PidFile => $pid_file,
755    ScoreboardFile => $scoreboard_file,
756    SystemLog => $log_file,
757
758    AuthUserFile => $auth_user_file,
759    AuthGroupFile => $auth_group_file,
760    ShowSymlinks => 'on',
761
762    IfModules => {
763      'mod_delay.c' => {
764        DelayEngine => 'off',
765      },
766    },
767  };
768
769  my ($port, $config_user, $config_group) = config_write($config_file, $config);
770
771  # Open pipes, for use between the parent and child processes.  Specifically,
772  # the child will indicate when it's done with its test by writing a message
773  # to the parent.
774  my ($rfh, $wfh);
775  unless (pipe($rfh, $wfh)) {
776    die("Can't open pipe: $!");
777  }
778
779  my $ex;
780
781  # Fork child
782  $self->handle_sigchld();
783  defined(my $pid = fork()) or die("Can't fork: $!");
784  if ($pid) {
785    eval {
786      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
787      $client->login($user, $passwd);
788
789      my $conn = $client->list_raw('-F foo');
790      unless ($conn) {
791        die("LIST failed: " . $client->response_code() . " " .
792          $client->response_msg());
793      }
794
795      my $buf;
796      $conn->read($buf, 8192, 25);
797      eval { $conn->close() };
798
799      # We have to be careful of the fact that readdir returns directory
800      # entries in an unordered fashion.
801      my $res = {};
802      my $lines = [split(/\n/, $buf)];
803      foreach my $line (@$lines) {
804        if ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(\S+)$/) {
805          $res->{$2} = $1;
806
807        } elsif ($line =~ /(^\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(.*)?$/) {
808          $res->{$2} = $1;
809        }
810      }
811
812      my $expected = {
813        'test.lnk -> test.d/' => 'lrwxrwxrwx',
814        'test.d/' => 'drwxr-xr-x',
815      };
816
817      my $ok = 1;
818      my $mismatch = '';
819      foreach my $name (keys(%$res)) {
820        unless (defined($expected->{$name})) {
821          $mismatch = $name;
822          $ok = 0;
823          last;
824        }
825
826        unless ($res->{$name} eq $expected->{$name}) {
827          $mismatch = $name;
828          $ok = 0;
829          last;
830        }
831      }
832
833      $self->assert($ok,
834        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
835
836      my $count = scalar(keys(%$res));
837      $expected = 2;
838      unless ($count == $expected) {
839        die("LIST returned wrong number of entries (expected $expected, got $count)");
840      }
841    };
842
843    if ($@) {
844      $ex = $@;
845    }
846
847    $wfh->print("done\n");
848    $wfh->flush();
849
850  } else {
851    eval { server_wait($config_file, $rfh) };
852    if ($@) {
853      warn($@);
854      exit 1;
855    }
856
857    exit 0;
858  }
859
860  # Stop server
861  server_stop($pid_file);
862
863  $self->assert_child_ok($pid);
864
865  if ($ex) {
866    die($ex);
867  }
868
869  unlink($log_file);
870}
871
872sub showsymlinks_off_nlst_rel_symlinked_file {
873  my $self = shift;
874  my $tmpdir = $self->{tmpdir};
875
876  my $config_file = "$tmpdir/config.conf";
877  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
878  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
879
880  my $log_file = File::Spec->rel2abs('tests.log');
881
882  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
883  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
884
885  my $user = 'proftpd';
886  my $passwd = 'test';
887  my $group = 'ftpd';
888  my $home_dir = File::Spec->rel2abs($tmpdir);
889  my $uid = 500;
890  my $gid = 500;
891
892  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
893  mkpath($foo_dir);
894
895  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
896  if (open(my $fh, "> $test_file")) {
897    print $fh "Hello, World!\n";
898    unless (close($fh)) {
899      die("Can't write $test_file: $!");
900    }
901
902  } else {
903    die("Can't open $test_file: $!");
904  }
905
906  # Change to the 'foo' directory in order to create a relative path in the
907  # symlink we need
908
909  my $cwd = getcwd();
910  unless (chdir("$foo_dir")) {
911    die("Can't chdir to $foo_dir: $!");
912  }
913
914  unless (symlink('test.txt', 'test.lnk')) {
915    die("Can't symlink 'test.txt' to 'test.lnk': $!");
916  }
917
918  unless (chdir($cwd)) {
919    die("Can't chdir to $cwd: $!");
920  }
921
922  # Make sure that, if we're running as root, that the home directory has
923  # permissions/privs set for the account we create
924  if ($< == 0) {
925    unless (chmod(0755, $home_dir, $foo_dir)) {
926      die("Can't set perms on $home_dir to 0755: $!");
927    }
928
929    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
930      die("Can't set owner of $home_dir to $uid/$gid: $!");
931    }
932  }
933
934  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
935    '/bin/bash');
936  auth_group_write($auth_group_file, $group, $gid, $user);
937
938  my $config = {
939    PidFile => $pid_file,
940    ScoreboardFile => $scoreboard_file,
941    SystemLog => $log_file,
942
943    AuthUserFile => $auth_user_file,
944    AuthGroupFile => $auth_group_file,
945    ShowSymlinks => 'off',
946
947    IfModules => {
948      'mod_delay.c' => {
949        DelayEngine => 'off',
950      },
951    },
952  };
953
954  my ($port, $config_user, $config_group) = config_write($config_file, $config);
955
956  # Open pipes, for use between the parent and child processes.  Specifically,
957  # the child will indicate when it's done with its test by writing a message
958  # to the parent.
959  my ($rfh, $wfh);
960  unless (pipe($rfh, $wfh)) {
961    die("Can't open pipe: $!");
962  }
963
964  my $ex;
965
966  # Fork child
967  $self->handle_sigchld();
968  defined(my $pid = fork()) or die("Can't fork: $!");
969  if ($pid) {
970    eval {
971      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
972      $client->login($user, $passwd);
973
974      my $conn = $client->nlst_raw('foo');
975      unless ($conn) {
976        die("NLST failed: " . $client->response_code() . " " .
977          $client->response_msg());
978      }
979
980      my $buf;
981      $conn->read($buf, 8192, 25);
982      eval { $conn->close() };
983
984      # We have to be careful of the fact that readdir returns directory
985      # entries in an unordered fashion.
986      my $res = {};
987      my $lines = [split(/\n/, $buf)];
988      foreach my $line (@$lines) {
989        $res->{$line} = 1;
990      }
991
992      my $expected = {
993        'foo/test.lnk' => 1,
994        'foo/test.txt' => 1,
995      };
996
997      my $ok = 1;
998      my $mismatch = '';
999      foreach my $name (keys(%$res)) {
1000        unless (defined($expected->{$name})) {
1001          $mismatch = $name;
1002          $ok = 0;
1003          last;
1004        }
1005      }
1006
1007      $self->assert($ok,
1008        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
1009
1010      my $count = scalar(keys(%$res));
1011      $expected = 2;
1012      unless ($count == $expected) {
1013        die("LIST returned wrong number of entries (expected $expected, got $count)");
1014      }
1015    };
1016
1017    if ($@) {
1018      $ex = $@;
1019    }
1020
1021    $wfh->print("done\n");
1022    $wfh->flush();
1023
1024  } else {
1025    eval { server_wait($config_file, $rfh) };
1026    if ($@) {
1027      warn($@);
1028      exit 1;
1029    }
1030
1031    exit 0;
1032  }
1033
1034  # Stop server
1035  server_stop($pid_file);
1036
1037  $self->assert_child_ok($pid);
1038
1039  if ($ex) {
1040    die($ex);
1041  }
1042
1043  unlink($log_file);
1044}
1045
1046sub showsymlinks_on_nlst_rel_symlinked_file {
1047  my $self = shift;
1048  my $tmpdir = $self->{tmpdir};
1049
1050  my $config_file = "$tmpdir/config.conf";
1051  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1052  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1053
1054  my $log_file = File::Spec->rel2abs('tests.log');
1055
1056  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1057  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1058
1059  my $user = 'proftpd';
1060  my $passwd = 'test';
1061  my $group = 'ftpd';
1062  my $home_dir = File::Spec->rel2abs($tmpdir);
1063  my $uid = 500;
1064  my $gid = 500;
1065
1066  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1067  mkpath($foo_dir);
1068
1069  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
1070  if (open(my $fh, "> $test_file")) {
1071    print $fh "Hello, World!\n";
1072    unless (close($fh)) {
1073      die("Can't write $test_file: $!");
1074    }
1075
1076  } else {
1077    die("Can't open $test_file: $!");
1078  }
1079
1080  # Change to the 'foo' directory in order to create a relative path in the
1081  # symlink we need
1082
1083  my $cwd = getcwd();
1084  unless (chdir("$foo_dir")) {
1085    die("Can't chdir to $foo_dir: $!");
1086  }
1087
1088  unless (symlink('test.txt', 'test.lnk')) {
1089    die("Can't symlink 'test.txt' to 'test.lnk': $!");
1090  }
1091
1092  unless (chdir($cwd)) {
1093    die("Can't chdir to $cwd: $!");
1094  }
1095
1096  # Make sure that, if we're running as root, that the home directory has
1097  # permissions/privs set for the account we create
1098  if ($< == 0) {
1099    unless (chmod(0755, $home_dir, $foo_dir)) {
1100      die("Can't set perms on $home_dir to 0755: $!");
1101    }
1102
1103    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1104      die("Can't set owner of $home_dir to $uid/$gid: $!");
1105    }
1106  }
1107
1108  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1109    '/bin/bash');
1110  auth_group_write($auth_group_file, $group, $gid, $user);
1111
1112  my $config = {
1113    PidFile => $pid_file,
1114    ScoreboardFile => $scoreboard_file,
1115    SystemLog => $log_file,
1116
1117    AuthUserFile => $auth_user_file,
1118    AuthGroupFile => $auth_group_file,
1119    ShowSymlinks => 'on',
1120
1121    IfModules => {
1122      'mod_delay.c' => {
1123        DelayEngine => 'off',
1124      },
1125    },
1126  };
1127
1128  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1129
1130  # Open pipes, for use between the parent and child processes.  Specifically,
1131  # the child will indicate when it's done with its test by writing a message
1132  # to the parent.
1133  my ($rfh, $wfh);
1134  unless (pipe($rfh, $wfh)) {
1135    die("Can't open pipe: $!");
1136  }
1137
1138  my $ex;
1139
1140  # Fork child
1141  $self->handle_sigchld();
1142  defined(my $pid = fork()) or die("Can't fork: $!");
1143  if ($pid) {
1144    eval {
1145      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1146      $client->login($user, $passwd);
1147
1148      my $conn = $client->nlst_raw('foo');
1149      unless ($conn) {
1150        die("NLST failed: " . $client->response_code() . " " .
1151          $client->response_msg());
1152      }
1153
1154      my $buf;
1155      $conn->read($buf, 8192, 25);
1156      eval { $conn->close() };
1157
1158      # We have to be careful of the fact that readdir returns directory
1159      # entries in an unordered fashion.
1160      my $res = {};
1161      my $lines = [split(/\n/, $buf)];
1162      foreach my $line (@$lines) {
1163        $res->{$line} = 1;
1164      }
1165
1166      my $expected = {
1167        'foo/test.lnk' => 1,
1168        'foo/test.txt' => 1,
1169      };
1170
1171      my $ok = 1;
1172      my $mismatch = '';
1173      foreach my $name (keys(%$res)) {
1174        unless (defined($expected->{$name})) {
1175          $mismatch = $name;
1176          $ok = 0;
1177          last;
1178        }
1179      }
1180
1181      $self->assert($ok,
1182        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
1183
1184      my $count = scalar(keys(%$res));
1185      $expected = 2;
1186      unless ($count == $expected) {
1187        die("LIST returned wrong number of entries (expected $expected, got $count)");
1188      }
1189    };
1190
1191    if ($@) {
1192      $ex = $@;
1193    }
1194
1195    $wfh->print("done\n");
1196    $wfh->flush();
1197
1198  } else {
1199    eval { server_wait($config_file, $rfh) };
1200    if ($@) {
1201      warn($@);
1202      exit 1;
1203    }
1204
1205    exit 0;
1206  }
1207
1208  # Stop server
1209  server_stop($pid_file);
1210
1211  $self->assert_child_ok($pid);
1212
1213  if ($ex) {
1214    die($ex);
1215  }
1216
1217  unlink($log_file);
1218}
1219
1220sub showsymlinks_off_nlst_rel_symlinked_dir {
1221  my $self = shift;
1222  my $tmpdir = $self->{tmpdir};
1223
1224  my $config_file = "$tmpdir/config.conf";
1225  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1226  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1227
1228  my $log_file = File::Spec->rel2abs('tests.log');
1229
1230  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1231  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1232
1233  my $user = 'proftpd';
1234  my $passwd = 'test';
1235  my $group = 'ftpd';
1236  my $home_dir = File::Spec->rel2abs($tmpdir);
1237  my $uid = 500;
1238  my $gid = 500;
1239
1240  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1241  mkpath($foo_dir);
1242
1243  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
1244  mkpath($test_dir);
1245
1246  # Change to the 'foo' directory in order to create a relative path in the
1247  # symlink we need
1248
1249  my $cwd = getcwd();
1250  unless (chdir("$foo_dir")) {
1251    die("Can't chdir to $foo_dir: $!");
1252  }
1253
1254  unless (symlink('test.d', 'test.lnk')) {
1255    die("Can't symlink 'test.d' to 'test.lnk': $!");
1256  }
1257
1258  unless (chdir($cwd)) {
1259    die("Can't chdir to $cwd: $!");
1260  }
1261
1262  # Make sure that, if we're running as root, that the home directory has
1263  # permissions/privs set for the account we create
1264  if ($< == 0) {
1265    unless (chmod(0755, $home_dir, $foo_dir)) {
1266      die("Can't set perms on $home_dir to 0755: $!");
1267    }
1268
1269    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1270      die("Can't set owner of $home_dir to $uid/$gid: $!");
1271    }
1272  }
1273
1274  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1275    '/bin/bash');
1276  auth_group_write($auth_group_file, $group, $gid, $user);
1277
1278  my $config = {
1279    PidFile => $pid_file,
1280    ScoreboardFile => $scoreboard_file,
1281    SystemLog => $log_file,
1282
1283    AuthUserFile => $auth_user_file,
1284    AuthGroupFile => $auth_group_file,
1285    ShowSymlinks => 'off',
1286
1287    IfModules => {
1288      'mod_delay.c' => {
1289        DelayEngine => 'off',
1290      },
1291    },
1292  };
1293
1294  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1295
1296  # Open pipes, for use between the parent and child processes.  Specifically,
1297  # the child will indicate when it's done with its test by writing a message
1298  # to the parent.
1299  my ($rfh, $wfh);
1300  unless (pipe($rfh, $wfh)) {
1301    die("Can't open pipe: $!");
1302  }
1303
1304  my $ex;
1305
1306  # Fork child
1307  $self->handle_sigchld();
1308  defined(my $pid = fork()) or die("Can't fork: $!");
1309  if ($pid) {
1310    eval {
1311      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1312      $client->login($user, $passwd);
1313
1314      my $conn = $client->nlst_raw('foo');
1315      unless ($conn) {
1316        die("NLST failed: " . $client->response_code() . " " .
1317          $client->response_msg());
1318      }
1319
1320      my $buf;
1321      $conn->read($buf, 8192, 25);
1322      eval { $conn->close() };
1323
1324      # We have to be careful of the fact that readdir returns directory
1325      # entries in an unordered fashion.
1326      my $res = {};
1327      my $lines = [split(/\n/, $buf)];
1328      foreach my $line (@$lines) {
1329        $res->{$line} = 1;
1330      }
1331
1332      my $expected = {
1333        'foo/test.lnk' => 1,
1334        'foo/test.d' => 1,
1335      };
1336
1337      my $ok = 1;
1338      my $mismatch = '';
1339      foreach my $name (keys(%$res)) {
1340        unless (defined($expected->{$name})) {
1341          $mismatch = $name;
1342          $ok = 0;
1343          last;
1344        }
1345      }
1346
1347      $self->assert($ok,
1348        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
1349
1350      my $count = scalar(keys(%$res));
1351      $expected = 2;
1352      unless ($count == $expected) {
1353        die("LIST returned wrong number of entries (expected $expected, got $count)");
1354      }
1355    };
1356
1357    if ($@) {
1358      $ex = $@;
1359    }
1360
1361    $wfh->print("done\n");
1362    $wfh->flush();
1363
1364  } else {
1365    eval { server_wait($config_file, $rfh) };
1366    if ($@) {
1367      warn($@);
1368      exit 1;
1369    }
1370
1371    exit 0;
1372  }
1373
1374  # Stop server
1375  server_stop($pid_file);
1376
1377  $self->assert_child_ok($pid);
1378
1379  if ($ex) {
1380    die($ex);
1381  }
1382
1383  unlink($log_file);
1384}
1385
1386sub showsymlinks_on_nlst_rel_symlinked_dir {
1387  my $self = shift;
1388  my $tmpdir = $self->{tmpdir};
1389
1390  my $config_file = "$tmpdir/config.conf";
1391  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1392  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1393
1394  my $log_file = File::Spec->rel2abs('tests.log');
1395
1396  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1397  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1398
1399  my $user = 'proftpd';
1400  my $passwd = 'test';
1401  my $group = 'ftpd';
1402  my $home_dir = File::Spec->rel2abs($tmpdir);
1403  my $uid = 500;
1404  my $gid = 500;
1405
1406  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1407  mkpath($foo_dir);
1408
1409  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
1410  mkpath($test_dir);
1411
1412  # Change to the 'foo' directory in order to create a relative path in the
1413  # symlink we need
1414
1415  my $cwd = getcwd();
1416  unless (chdir("$foo_dir")) {
1417    die("Can't chdir to $foo_dir: $!");
1418  }
1419
1420  unless (symlink('test.d', 'test.lnk')) {
1421    die("Can't symlink 'test.d' to 'test.lnk': $!");
1422  }
1423
1424  unless (chdir($cwd)) {
1425    die("Can't chdir to $cwd: $!");
1426  }
1427
1428  # Make sure that, if we're running as root, that the home directory has
1429  # permissions/privs set for the account we create
1430  if ($< == 0) {
1431    unless (chmod(0755, $home_dir, $foo_dir)) {
1432      die("Can't set perms on $home_dir to 0755: $!");
1433    }
1434
1435    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1436      die("Can't set owner of $home_dir to $uid/$gid: $!");
1437    }
1438  }
1439
1440  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1441    '/bin/bash');
1442  auth_group_write($auth_group_file, $group, $gid, $user);
1443
1444  my $config = {
1445    PidFile => $pid_file,
1446    ScoreboardFile => $scoreboard_file,
1447    SystemLog => $log_file,
1448
1449    AuthUserFile => $auth_user_file,
1450    AuthGroupFile => $auth_group_file,
1451    ShowSymlinks => 'on',
1452
1453    IfModules => {
1454      'mod_delay.c' => {
1455        DelayEngine => 'off',
1456      },
1457    },
1458  };
1459
1460  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1461
1462  # Open pipes, for use between the parent and child processes.  Specifically,
1463  # the child will indicate when it's done with its test by writing a message
1464  # to the parent.
1465  my ($rfh, $wfh);
1466  unless (pipe($rfh, $wfh)) {
1467    die("Can't open pipe: $!");
1468  }
1469
1470  my $ex;
1471
1472  # Fork child
1473  $self->handle_sigchld();
1474  defined(my $pid = fork()) or die("Can't fork: $!");
1475  if ($pid) {
1476    eval {
1477      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1478      $client->login($user, $passwd);
1479
1480      my $conn = $client->nlst_raw('foo');
1481      unless ($conn) {
1482        die("NLST failed: " . $client->response_code() . " " .
1483          $client->response_msg());
1484      }
1485
1486      my $buf;
1487      $conn->read($buf, 8192, 25);
1488      eval { $conn->close() };
1489
1490      # We have to be careful of the fact that readdir returns directory
1491      # entries in an unordered fashion.
1492      my $res = {};
1493      my $lines = [split(/\n/, $buf)];
1494      foreach my $line (@$lines) {
1495        $res->{$line} = 1;
1496      }
1497
1498      my $expected = {
1499        'foo/test.lnk' => 1,
1500        'foo/test.d' => 1,
1501      };
1502
1503      my $ok = 1;
1504      my $mismatch = '';
1505      foreach my $name (keys(%$res)) {
1506        unless (defined($expected->{$name})) {
1507          $mismatch = $name;
1508          $ok = 0;
1509          last;
1510        }
1511      }
1512
1513      $self->assert($ok,
1514        test_msg("Unexpected name '$mismatch' appeared in LIST data"));
1515
1516      my $count = scalar(keys(%$res));
1517      $expected = 2;
1518      unless ($count == $expected) {
1519        die("LIST returned wrong number of entries (expected $expected, got $count)");
1520      }
1521    };
1522
1523    if ($@) {
1524      $ex = $@;
1525    }
1526
1527    $wfh->print("done\n");
1528    $wfh->flush();
1529
1530  } else {
1531    eval { server_wait($config_file, $rfh) };
1532    if ($@) {
1533      warn($@);
1534      exit 1;
1535    }
1536
1537    exit 0;
1538  }
1539
1540  # Stop server
1541  server_stop($pid_file);
1542
1543  $self->assert_child_ok($pid);
1544
1545  if ($ex) {
1546    die($ex);
1547  }
1548
1549  unlink($log_file);
1550}
1551
1552sub showsymlinks_off_mlsd_rel_symlinked_file {
1553  my $self = shift;
1554  my $tmpdir = $self->{tmpdir};
1555
1556  my $config_file = "$tmpdir/config.conf";
1557  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1558  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1559
1560  my $log_file = File::Spec->rel2abs('tests.log');
1561
1562  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1563  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1564
1565  my $user = 'proftpd';
1566  my $passwd = 'test';
1567  my $group = 'ftpd';
1568  my $home_dir = File::Spec->rel2abs($tmpdir);
1569  my $uid = 500;
1570  my $gid = 500;
1571
1572  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1573  mkpath($foo_dir);
1574
1575  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
1576  if (open(my $fh, "> $test_file")) {
1577    print $fh "Hello, World!\n";
1578    unless (close($fh)) {
1579      die("Can't write $test_file: $!");
1580    }
1581
1582  } else {
1583    die("Can't open $test_file: $!");
1584  }
1585
1586  # Change to the 'foo' directory in order to create a relative path in the
1587  # symlink we need
1588
1589  my $cwd = getcwd();
1590  unless (chdir("$foo_dir")) {
1591    die("Can't chdir to $foo_dir: $!");
1592  }
1593
1594  unless (symlink('test.txt', 'test.lnk')) {
1595    die("Can't symlink 'test.txt' to 'test.lnk': $!");
1596  }
1597
1598  unless (chdir($cwd)) {
1599    die("Can't chdir to $cwd: $!");
1600  }
1601
1602  # Make sure that, if we're running as root, that the home directory has
1603  # permissions/privs set for the account we create
1604  if ($< == 0) {
1605    unless (chmod(0755, $home_dir, $foo_dir)) {
1606      die("Can't set perms on $home_dir to 0755: $!");
1607    }
1608
1609    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1610      die("Can't set owner of $home_dir to $uid/$gid: $!");
1611    }
1612  }
1613
1614  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1615    '/bin/bash');
1616  auth_group_write($auth_group_file, $group, $gid, $user);
1617
1618  my $config = {
1619    PidFile => $pid_file,
1620    ScoreboardFile => $scoreboard_file,
1621    SystemLog => $log_file,
1622
1623    AuthUserFile => $auth_user_file,
1624    AuthGroupFile => $auth_group_file,
1625    ShowSymlinks => 'off',
1626
1627    IfModules => {
1628      'mod_delay.c' => {
1629        DelayEngine => 'off',
1630      },
1631    },
1632  };
1633
1634  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1635
1636  # Open pipes, for use between the parent and child processes.  Specifically,
1637  # the child will indicate when it's done with its test by writing a message
1638  # to the parent.
1639  my ($rfh, $wfh);
1640  unless (pipe($rfh, $wfh)) {
1641    die("Can't open pipe: $!");
1642  }
1643
1644  my $ex;
1645
1646  # Fork child
1647  $self->handle_sigchld();
1648  defined(my $pid = fork()) or die("Can't fork: $!");
1649  if ($pid) {
1650    eval {
1651      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1652      $client->login($user, $passwd);
1653
1654      my $conn = $client->mlsd_raw('foo');
1655      unless ($conn) {
1656        die("MLSD failed: " . $client->response_code() . " " .
1657          $client->response_msg());
1658      }
1659
1660      my $buf;
1661      $conn->read($buf, 8192, 25);
1662      eval { $conn->close() };
1663
1664      my $res = {};
1665      my $lines = [split(/\n/, $buf)];
1666      foreach my $line (@$lines) {
1667        if ($line =~ /^modify=\S+;perm=\S+;type=\S+;unique=(\S+);UNIX\.group=\d+;UNIX\.mode=\d+;UNIX.owner=\d+; (.*?)$/) {
1668          $res->{$2} = $1;
1669        }
1670      }
1671
1672      my $count = scalar(keys(%$res));
1673      unless ($count == 4) {
1674        die("MLSD returned wrong number of entries (expected 4, got $count)");
1675      }
1676
1677      # test.lnk is a symlink to test.txt.  According to RFC3659, the unique
1678      # fact for both of these should thus be the same, since they are the
1679      # same underlying object.
1680
1681      my $expected = $res->{'test.txt'};
1682      my $got = $res->{'test.lnk'};
1683      $self->assert($expected eq $got,
1684        test_msg("Expected '$expected', got '$got'"));
1685    };
1686
1687    if ($@) {
1688      $ex = $@;
1689    }
1690
1691    $wfh->print("done\n");
1692    $wfh->flush();
1693
1694  } else {
1695    eval { server_wait($config_file, $rfh) };
1696    if ($@) {
1697      warn($@);
1698      exit 1;
1699    }
1700
1701    exit 0;
1702  }
1703
1704  # Stop server
1705  server_stop($pid_file);
1706
1707  $self->assert_child_ok($pid);
1708
1709  if ($ex) {
1710    die($ex);
1711  }
1712
1713  unlink($log_file);
1714}
1715
1716sub showsymlinks_on_mlsd_rel_symlinked_file {
1717  my $self = shift;
1718  my $tmpdir = $self->{tmpdir};
1719
1720  my $config_file = "$tmpdir/config.conf";
1721  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1722  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1723
1724  my $log_file = File::Spec->rel2abs('tests.log');
1725
1726  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1727  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1728
1729  my $user = 'proftpd';
1730  my $passwd = 'test';
1731  my $group = 'ftpd';
1732  my $home_dir = File::Spec->rel2abs($tmpdir);
1733  my $uid = 500;
1734  my $gid = 500;
1735
1736  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1737  mkpath($foo_dir);
1738
1739  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
1740  if (open(my $fh, "> $test_file")) {
1741    print $fh "Hello, World!\n";
1742    unless (close($fh)) {
1743      die("Can't write $test_file: $!");
1744    }
1745
1746  } else {
1747    die("Can't open $test_file: $!");
1748  }
1749
1750  # Change to the 'foo' directory in order to create a relative path in the
1751  # symlink we need
1752
1753  my $cwd = getcwd();
1754  unless (chdir("$foo_dir")) {
1755    die("Can't chdir to $foo_dir: $!");
1756  }
1757
1758  unless (symlink('test.txt', 'test.lnk')) {
1759    die("Can't symlink 'test.txt' to 'test.lnk': $!");
1760  }
1761
1762  unless (chdir($cwd)) {
1763    die("Can't chdir to $cwd: $!");
1764  }
1765
1766  # Make sure that, if we're running as root, that the home directory has
1767  # permissions/privs set for the account we create
1768  if ($< == 0) {
1769    unless (chmod(0755, $home_dir, $foo_dir)) {
1770      die("Can't set perms on $home_dir to 0755: $!");
1771    }
1772
1773    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1774      die("Can't set owner of $home_dir to $uid/$gid: $!");
1775    }
1776  }
1777
1778  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1779    '/bin/bash');
1780  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
1781
1782  my $config = {
1783    PidFile => $pid_file,
1784    ScoreboardFile => $scoreboard_file,
1785    SystemLog => $log_file,
1786
1787    AuthUserFile => $auth_user_file,
1788    AuthGroupFile => $auth_group_file,
1789    ShowSymlinks => 'on',
1790
1791    IfModules => {
1792      'mod_delay.c' => {
1793        DelayEngine => 'off',
1794      },
1795    },
1796  };
1797
1798  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1799
1800  # Open pipes, for use between the parent and child processes.  Specifically,
1801  # the child will indicate when it's done with its test by writing a message
1802  # to the parent.
1803  my ($rfh, $wfh);
1804  unless (pipe($rfh, $wfh)) {
1805    die("Can't open pipe: $!");
1806  }
1807
1808  my $ex;
1809
1810  # Fork child
1811  $self->handle_sigchld();
1812  defined(my $pid = fork()) or die("Can't fork: $!");
1813  if ($pid) {
1814    eval {
1815      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1816      $client->login($user, $passwd);
1817
1818      my $conn = $client->mlsd_raw('foo');
1819      unless ($conn) {
1820        die("MLSD failed: " . $client->response_code() . " " .
1821          $client->response_msg());
1822      }
1823
1824      my $buf;
1825      $conn->read($buf, 8192, 25);
1826      eval { $conn->close() };
1827
1828      my $res = {};
1829      my $lines = [split(/\n/, $buf)];
1830      foreach my $line (@$lines) {
1831        if ($line =~ /^modify=\S+;perm=\S+;type=(\S+);unique=(\S+);UNIX\.group=\d+;UNIX\.mode=\d+;UNIX.owner=\d+; (.*?)$/) {
1832          $res->{$3} = { type => $1, unique => $2 };
1833        }
1834      }
1835
1836      my $count = scalar(keys(%$res));
1837      unless ($count == 4) {
1838        die("MLSD returned wrong number of entries (expected 4, got $count)");
1839      }
1840
1841      # test.lnk is a symlink to test.txt.  According to RFC3659, the unique
1842      # fact for both of these should thus be the same, since they are the
1843      # same underlying object.
1844
1845      my $expected = $res->{'test.txt'}->{unique};
1846      my $got = $res->{'test.lnk'}->{unique};
1847      $self->assert($expected eq $got,
1848        test_msg("Expected '$expected', got '$got'"));
1849
1850      # Since ShowSymlinks is on, the type for test.lnk should indicate that
1851      # it's a symlink
1852      $expected = 'OS.unix=symlink';
1853      $got = $res->{'test.lnk'}->{type};
1854      $self->assert(qr/$expected/i, $got,
1855        test_msg("Expected '$expected', got '$got'"));
1856    };
1857
1858    if ($@) {
1859      $ex = $@;
1860    }
1861
1862    $wfh->print("done\n");
1863    $wfh->flush();
1864
1865  } else {
1866    eval { server_wait($config_file, $rfh) };
1867    if ($@) {
1868      warn($@);
1869      exit 1;
1870    }
1871
1872    exit 0;
1873  }
1874
1875  # Stop server
1876  server_stop($pid_file);
1877
1878  $self->assert_child_ok($pid);
1879
1880  if ($ex) {
1881    die($ex);
1882  }
1883
1884  unlink($log_file);
1885}
1886
1887sub showsymlinks_off_mlsd_rel_symlinked_dir {
1888  my $self = shift;
1889  my $tmpdir = $self->{tmpdir};
1890
1891  my $config_file = "$tmpdir/config.conf";
1892  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
1893  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
1894
1895  my $log_file = File::Spec->rel2abs('tests.log');
1896
1897  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
1898  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
1899
1900  my $user = 'proftpd';
1901  my $passwd = 'test';
1902  my $group = 'ftpd';
1903  my $home_dir = File::Spec->rel2abs($tmpdir);
1904  my $uid = 500;
1905  my $gid = 500;
1906
1907  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
1908  mkpath($foo_dir);
1909
1910  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
1911  mkpath($test_dir);
1912
1913  # Change to the 'foo' directory in order to create a relative path in the
1914  # symlink we need
1915
1916  my $cwd = getcwd();
1917  unless (chdir("$foo_dir")) {
1918    die("Can't chdir to $foo_dir: $!");
1919  }
1920
1921  unless (symlink('test.d', 'test.lnk')) {
1922    die("Can't symlink 'test.d' to 'test.lnk': $!");
1923  }
1924
1925  unless (chdir($cwd)) {
1926    die("Can't chdir to $cwd: $!");
1927  }
1928
1929  # Make sure that, if we're running as root, that the home directory has
1930  # permissions/privs set for the account we create
1931  if ($< == 0) {
1932    unless (chmod(0755, $home_dir, $foo_dir)) {
1933      die("Can't set perms on $home_dir to 0755: $!");
1934    }
1935
1936    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
1937      die("Can't set owner of $home_dir to $uid/$gid: $!");
1938    }
1939  }
1940
1941  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1942    '/bin/bash');
1943  auth_group_write($auth_group_file, $group, $gid, $user);
1944
1945  my $config = {
1946    PidFile => $pid_file,
1947    ScoreboardFile => $scoreboard_file,
1948    SystemLog => $log_file,
1949
1950    AuthUserFile => $auth_user_file,
1951    AuthGroupFile => $auth_group_file,
1952    ShowSymlinks => 'off',
1953
1954    IfModules => {
1955      'mod_delay.c' => {
1956        DelayEngine => 'off',
1957      },
1958    },
1959  };
1960
1961  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1962
1963  # Open pipes, for use between the parent and child processes.  Specifically,
1964  # the child will indicate when it's done with its test by writing a message
1965  # to the parent.
1966  my ($rfh, $wfh);
1967  unless (pipe($rfh, $wfh)) {
1968    die("Can't open pipe: $!");
1969  }
1970
1971  my $ex;
1972
1973  # Fork child
1974  $self->handle_sigchld();
1975  defined(my $pid = fork()) or die("Can't fork: $!");
1976  if ($pid) {
1977    eval {
1978      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1979      $client->login($user, $passwd);
1980
1981      my $conn = $client->mlsd_raw('foo');
1982      unless ($conn) {
1983        die("MLSD failed: " . $client->response_code() . " " .
1984          $client->response_msg());
1985      }
1986
1987      my $buf;
1988      $conn->read($buf, 8192, 25);
1989      eval { $conn->close() };
1990
1991      my $res = {};
1992      my $lines = [split(/\n/, $buf)];
1993      foreach my $line (@$lines) {
1994        if ($line =~ /^modify=\S+;perm=\S+;type=\S+;unique=(\S+);UNIX\.group=\d+;UNIX\.mode=\d+;UNIX.owner=\d+; (.*?)$/) {
1995          $res->{$2} = $1;
1996        }
1997      }
1998
1999      my $count = scalar(keys(%$res));
2000      unless ($count == 4) {
2001        die("MLSD returned wrong number of entries (expected 4, got $count)");
2002      }
2003
2004      # test.lnk is a symlink to test.d.  According to RFC3659, the unique
2005      # fact for both of these should thus be the same, since they are the
2006      # same underlying object.
2007
2008      my $expected = $res->{'test.d'};
2009      my $got = $res->{'test.lnk'};
2010      $self->assert($expected eq $got,
2011        test_msg("Expected '$expected', got '$got'"));
2012    };
2013
2014    if ($@) {
2015      $ex = $@;
2016    }
2017
2018    $wfh->print("done\n");
2019    $wfh->flush();
2020
2021  } else {
2022    eval { server_wait($config_file, $rfh) };
2023    if ($@) {
2024      warn($@);
2025      exit 1;
2026    }
2027
2028    exit 0;
2029  }
2030
2031  # Stop server
2032  server_stop($pid_file);
2033
2034  $self->assert_child_ok($pid);
2035
2036  if ($ex) {
2037    die($ex);
2038  }
2039
2040  unlink($log_file);
2041}
2042
2043sub showsymlinks_on_mlsd_rel_symlinked_dir {
2044  my $self = shift;
2045  my $tmpdir = $self->{tmpdir};
2046
2047  my $config_file = "$tmpdir/config.conf";
2048  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2049  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2050
2051  my $log_file = File::Spec->rel2abs('tests.log');
2052
2053  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2054  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2055
2056  my $user = 'proftpd';
2057  my $passwd = 'test';
2058  my $group = 'ftpd';
2059  my $home_dir = File::Spec->rel2abs($tmpdir);
2060  my $uid = 500;
2061  my $gid = 500;
2062
2063  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2064  mkpath($foo_dir);
2065
2066  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
2067  mkpath($test_dir);
2068
2069  # Change to the 'foo' directory in order to create a relative path in the
2070  # symlink we need
2071
2072  my $cwd = getcwd();
2073  unless (chdir("$foo_dir")) {
2074    die("Can't chdir to $foo_dir: $!");
2075  }
2076
2077  unless (symlink('test.d', 'test.lnk')) {
2078    die("Can't symlink 'test.d' to 'test.lnk': $!");
2079  }
2080
2081  unless (chdir($cwd)) {
2082    die("Can't chdir to $cwd: $!");
2083  }
2084
2085  # Make sure that, if we're running as root, that the home directory has
2086  # permissions/privs set for the account we create
2087  if ($< == 0) {
2088    unless (chmod(0755, $home_dir, $foo_dir)) {
2089      die("Can't set perms on $home_dir to 0755: $!");
2090    }
2091
2092    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2093      die("Can't set owner of $home_dir to $uid/$gid: $!");
2094    }
2095  }
2096
2097  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2098    '/bin/bash');
2099  auth_group_write($auth_group_file, 'ftpd', $gid, $user);
2100
2101  my $config = {
2102    PidFile => $pid_file,
2103    ScoreboardFile => $scoreboard_file,
2104    SystemLog => $log_file,
2105
2106    AuthUserFile => $auth_user_file,
2107    AuthGroupFile => $auth_group_file,
2108    ShowSymlinks => 'on',
2109
2110    IfModules => {
2111      'mod_delay.c' => {
2112        DelayEngine => 'off',
2113      },
2114    },
2115  };
2116
2117  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2118
2119  # Open pipes, for use between the parent and child processes.  Specifically,
2120  # the child will indicate when it's done with its test by writing a message
2121  # to the parent.
2122  my ($rfh, $wfh);
2123  unless (pipe($rfh, $wfh)) {
2124    die("Can't open pipe: $!");
2125  }
2126
2127  my $ex;
2128
2129  # Fork child
2130  $self->handle_sigchld();
2131  defined(my $pid = fork()) or die("Can't fork: $!");
2132  if ($pid) {
2133    eval {
2134      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2135      $client->login($user, $passwd);
2136
2137      my $conn = $client->mlsd_raw('foo');
2138      unless ($conn) {
2139        die("MLSD failed: " . $client->response_code() . " " .
2140          $client->response_msg());
2141      }
2142
2143      my $buf;
2144      $conn->read($buf, 8192, 25);
2145      eval { $conn->close() };
2146
2147      my $res = {};
2148      my $lines = [split(/\n/, $buf)];
2149      foreach my $line (@$lines) {
2150        if ($line =~ /^modify=\S+;perm=\S+;type=(\S+);unique=(\S+);UNIX\.group=\d+;UNIX\.mode=\d+;UNIX.owner=\d+; (.*?)$/) {
2151          $res->{$3} = { type => $1, unique => $2 };
2152        }
2153      }
2154
2155      my $count = scalar(keys(%$res));
2156      unless ($count == 4) {
2157        die("MLSD returned wrong number of entries (expected 4, got $count)");
2158      }
2159
2160      # test.lnk is a symlink to test.d.  According to RFC3659, the unique
2161      # fact for both of these should thus be the same, since they are the
2162      # same underlying object.
2163
2164      my $expected = $res->{'test.d'}->{unique};
2165      my $got = $res->{'test.lnk'}->{unique};
2166      $self->assert($expected eq $got,
2167        test_msg("Expected '$expected', got '$got'"));
2168
2169      # Since ShowSymlinks is on, the type for test.lnk should indicate that
2170      # it's a symlink
2171      $expected = 'OS.unix=symlink';
2172      $got = $res->{'test.lnk'}->{type};
2173      $self->assert(qr/$expected/i, $got,
2174        test_msg("Expected '$expected', got '$got'"));
2175    };
2176
2177    if ($@) {
2178      $ex = $@;
2179    }
2180
2181    $wfh->print("done\n");
2182    $wfh->flush();
2183
2184  } else {
2185    eval { server_wait($config_file, $rfh) };
2186    if ($@) {
2187      warn($@);
2188      exit 1;
2189    }
2190
2191    exit 0;
2192  }
2193
2194  # Stop server
2195  server_stop($pid_file);
2196
2197  $self->assert_child_ok($pid);
2198
2199  if ($ex) {
2200    die($ex);
2201  }
2202
2203  unlink($log_file);
2204}
2205
2206sub showsymlinks_off_mlst_rel_symlinked_file {
2207  my $self = shift;
2208  my $tmpdir = $self->{tmpdir};
2209
2210  my $config_file = "$tmpdir/config.conf";
2211  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2212  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2213
2214  my $log_file = File::Spec->rel2abs('tests.log');
2215
2216  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2217  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2218
2219  my $user = 'proftpd';
2220  my $passwd = 'test';
2221  my $group = 'ftpd';
2222  my $home_dir = File::Spec->rel2abs($tmpdir);
2223  my $uid = 500;
2224  my $gid = 500;
2225
2226  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2227  mkpath($foo_dir);
2228
2229  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
2230  if (open(my $fh, "> $test_file")) {
2231    print $fh "Hello, World!\n";
2232    unless (close($fh)) {
2233      die("Can't write $test_file: $!");
2234    }
2235
2236  } else {
2237    die("Can't open $test_file: $!");
2238  }
2239
2240  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
2241
2242  # Change to the 'foo' directory in order to create a relative path in the
2243  # symlink we need
2244
2245  my $cwd = getcwd();
2246  unless (chdir("$foo_dir")) {
2247    die("Can't chdir to $foo_dir: $!");
2248  }
2249
2250  unless (symlink('test.txt', 'test.lnk')) {
2251    die("Can't symlink 'test.txt' to 'test.lnk': $!");
2252  }
2253
2254  unless (chdir($cwd)) {
2255    die("Can't chdir to $cwd: $!");
2256  }
2257
2258  # Make sure that, if we're running as root, that the home directory has
2259  # permissions/privs set for the account we create
2260  if ($< == 0) {
2261    unless (chmod(0755, $home_dir, $foo_dir)) {
2262      die("Can't set perms on $home_dir to 0755: $!");
2263    }
2264
2265    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2266      die("Can't set owner of $home_dir to $uid/$gid: $!");
2267    }
2268  }
2269
2270  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2271    '/bin/bash');
2272  auth_group_write($auth_group_file, $group, $gid, $user);
2273
2274  my $config = {
2275    PidFile => $pid_file,
2276    ScoreboardFile => $scoreboard_file,
2277    SystemLog => $log_file,
2278
2279    AuthUserFile => $auth_user_file,
2280    AuthGroupFile => $auth_group_file,
2281    ShowSymlinks => 'off',
2282
2283    IfModules => {
2284      'mod_delay.c' => {
2285        DelayEngine => 'off',
2286      },
2287    },
2288  };
2289
2290  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2291
2292  # Open pipes, for use between the parent and child processes.  Specifically,
2293  # the child will indicate when it's done with its test by writing a message
2294  # to the parent.
2295  my ($rfh, $wfh);
2296  unless (pipe($rfh, $wfh)) {
2297    die("Can't open pipe: $!");
2298  }
2299
2300  my $ex;
2301
2302  # Fork child
2303  $self->handle_sigchld();
2304  defined(my $pid = fork()) or die("Can't fork: $!");
2305  if ($pid) {
2306    eval {
2307      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2308      $client->login($user, $passwd);
2309
2310      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
2311
2312      my $expected;
2313
2314      $expected = 250;
2315      $self->assert($expected == $resp_code,
2316        test_msg("Expected $expected, got $resp_code"));
2317
2318      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
2319      $self->assert(qr/$expected/, $resp_msg,
2320        test_msg("Expected '$expected', got '$resp_msg'"));
2321    };
2322
2323    if ($@) {
2324      $ex = $@;
2325    }
2326
2327    $wfh->print("done\n");
2328    $wfh->flush();
2329
2330  } else {
2331    eval { server_wait($config_file, $rfh) };
2332    if ($@) {
2333      warn($@);
2334      exit 1;
2335    }
2336
2337    exit 0;
2338  }
2339
2340  # Stop server
2341  server_stop($pid_file);
2342
2343  $self->assert_child_ok($pid);
2344
2345  if ($ex) {
2346    die($ex);
2347  }
2348
2349  unlink($log_file);
2350}
2351
2352sub showsymlinks_off_mlst_rel_symlinked_file_chrooted {
2353  my $self = shift;
2354  my $tmpdir = $self->{tmpdir};
2355
2356  my $config_file = "$tmpdir/config.conf";
2357  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2358  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2359
2360  my $log_file = File::Spec->rel2abs('tests.log');
2361
2362  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2363  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2364
2365  my $user = 'proftpd';
2366  my $passwd = 'test';
2367  my $group = 'ftpd';
2368  my $home_dir = File::Spec->rel2abs($tmpdir);
2369  my $uid = 500;
2370  my $gid = 500;
2371
2372  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2373  mkpath($foo_dir);
2374
2375  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
2376  if (open(my $fh, "> $test_file")) {
2377    print $fh "Hello, World!\n";
2378    unless (close($fh)) {
2379      die("Can't write $test_file: $!");
2380    }
2381
2382  } else {
2383    die("Can't open $test_file: $!");
2384  }
2385
2386  # Since we're chrooting the session, we don't expect to see the real
2387  # absolute path.
2388  my $test_symlink = "/foo/test.lnk";
2389
2390  # Change to the 'foo' directory in order to create a relative path in the
2391  # symlink we need
2392
2393  my $cwd = getcwd();
2394  unless (chdir("$foo_dir")) {
2395    die("Can't chdir to $foo_dir: $!");
2396  }
2397
2398  unless (symlink('test.txt', 'test.lnk')) {
2399    die("Can't symlink 'test.txt' to 'test.lnk': $!");
2400  }
2401
2402  unless (chdir($cwd)) {
2403    die("Can't chdir to $cwd: $!");
2404  }
2405
2406  # Make sure that, if we're running as root, that the home directory has
2407  # permissions/privs set for the account we create
2408  if ($< == 0) {
2409    unless (chmod(0755, $home_dir, $foo_dir)) {
2410      die("Can't set perms on $home_dir to 0755: $!");
2411    }
2412
2413    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2414      die("Can't set owner of $home_dir to $uid/$gid: $!");
2415    }
2416  }
2417
2418  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2419    '/bin/bash');
2420  auth_group_write($auth_group_file, $group, $gid, $user);
2421
2422  my $config = {
2423    PidFile => $pid_file,
2424    ScoreboardFile => $scoreboard_file,
2425    SystemLog => $log_file,
2426
2427    AuthUserFile => $auth_user_file,
2428    AuthGroupFile => $auth_group_file,
2429    ShowSymlinks => 'off',
2430
2431    DefaultRoot => '~',
2432
2433    IfModules => {
2434      'mod_delay.c' => {
2435        DelayEngine => 'off',
2436      },
2437    },
2438  };
2439
2440  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2441
2442  # Open pipes, for use between the parent and child processes.  Specifically,
2443  # the child will indicate when it's done with its test by writing a message
2444  # to the parent.
2445  my ($rfh, $wfh);
2446  unless (pipe($rfh, $wfh)) {
2447    die("Can't open pipe: $!");
2448  }
2449
2450  my $ex;
2451
2452  # Fork child
2453  $self->handle_sigchld();
2454  defined(my $pid = fork()) or die("Can't fork: $!");
2455  if ($pid) {
2456    eval {
2457      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2458      $client->login($user, $passwd);
2459
2460      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
2461
2462      my $expected;
2463
2464      $expected = 250;
2465      $self->assert($expected == $resp_code,
2466        test_msg("Expected $expected, got $resp_code"));
2467
2468      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
2469      $self->assert(qr/$expected/, $resp_msg,
2470        test_msg("Expected '$expected', got '$resp_msg'"));
2471    };
2472
2473    if ($@) {
2474      $ex = $@;
2475    }
2476
2477    $wfh->print("done\n");
2478    $wfh->flush();
2479
2480  } else {
2481    eval { server_wait($config_file, $rfh) };
2482    if ($@) {
2483      warn($@);
2484      exit 1;
2485    }
2486
2487    exit 0;
2488  }
2489
2490  # Stop server
2491  server_stop($pid_file);
2492
2493  $self->assert_child_ok($pid);
2494
2495  if ($ex) {
2496    die($ex);
2497  }
2498
2499  unlink($log_file);
2500}
2501
2502sub showsymlinks_on_mlst_rel_symlinked_file {
2503  my $self = shift;
2504  my $tmpdir = $self->{tmpdir};
2505
2506  my $config_file = "$tmpdir/config.conf";
2507  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2508  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2509
2510  my $log_file = File::Spec->rel2abs('tests.log');
2511
2512  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2513  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2514
2515  my $user = 'proftpd';
2516  my $passwd = 'test';
2517  my $group = 'ftpd';
2518  my $home_dir = File::Spec->rel2abs($tmpdir);
2519  my $uid = 500;
2520  my $gid = 500;
2521
2522  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2523  mkpath($foo_dir);
2524
2525  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
2526  if (open(my $fh, "> $test_file")) {
2527    print $fh "Hello, World!\n";
2528    unless (close($fh)) {
2529      die("Can't write $test_file: $!");
2530    }
2531
2532  } else {
2533    die("Can't open $test_file: $!");
2534  }
2535
2536  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
2537
2538  # Change to the 'foo' directory in order to create a relative path in the
2539  # symlink we need
2540
2541  my $cwd = getcwd();
2542  unless (chdir("$foo_dir")) {
2543    die("Can't chdir to $foo_dir: $!");
2544  }
2545
2546  unless (symlink('test.txt', 'test.lnk')) {
2547    die("Can't symlink 'test.txt' to 'test.lnk': $!");
2548  }
2549
2550  unless (chdir($cwd)) {
2551    die("Can't chdir to $cwd: $!");
2552  }
2553
2554  # Make sure that, if we're running as root, that the home directory has
2555  # permissions/privs set for the account we create
2556  if ($< == 0) {
2557    unless (chmod(0755, $home_dir, $foo_dir)) {
2558      die("Can't set perms on $home_dir to 0755: $!");
2559    }
2560
2561    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2562      die("Can't set owner of $home_dir to $uid/$gid: $!");
2563    }
2564  }
2565
2566  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2567    '/bin/bash');
2568  auth_group_write($auth_group_file, $group, $gid, $user);
2569
2570  my $config = {
2571    PidFile => $pid_file,
2572    ScoreboardFile => $scoreboard_file,
2573    SystemLog => $log_file,
2574
2575    AuthUserFile => $auth_user_file,
2576    AuthGroupFile => $auth_group_file,
2577    ShowSymlinks => 'on',
2578
2579    IfModules => {
2580      'mod_delay.c' => {
2581        DelayEngine => 'off',
2582      },
2583    },
2584  };
2585
2586  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2587
2588  # Open pipes, for use between the parent and child processes.  Specifically,
2589  # the child will indicate when it's done with its test by writing a message
2590  # to the parent.
2591  my ($rfh, $wfh);
2592  unless (pipe($rfh, $wfh)) {
2593    die("Can't open pipe: $!");
2594  }
2595
2596  my $ex;
2597
2598  # Fork child
2599  $self->handle_sigchld();
2600  defined(my $pid = fork()) or die("Can't fork: $!");
2601  if ($pid) {
2602    eval {
2603      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2604      $client->login($user, $passwd);
2605
2606      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
2607
2608      my $expected;
2609
2610      $expected = 250;
2611      $self->assert($expected == $resp_code,
2612        test_msg("Expected $expected, got $resp_code"));
2613
2614      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=OS.unix=symlink;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_file . '$';
2615      $self->assert(qr/$expected/, $resp_msg,
2616        test_msg("Expected '$expected', got '$resp_msg'"));
2617    };
2618
2619    if ($@) {
2620      $ex = $@;
2621    }
2622
2623    $wfh->print("done\n");
2624    $wfh->flush();
2625
2626  } else {
2627    eval { server_wait($config_file, $rfh) };
2628    if ($@) {
2629      warn($@);
2630      exit 1;
2631    }
2632
2633    exit 0;
2634  }
2635
2636  # Stop server
2637  server_stop($pid_file);
2638
2639  $self->assert_child_ok($pid);
2640
2641  if ($ex) {
2642    die($ex);
2643  }
2644
2645  unlink($log_file);
2646}
2647
2648sub showsymlinks_on_mlst_rel_symlinked_file_chrooted {
2649  my $self = shift;
2650  my $tmpdir = $self->{tmpdir};
2651
2652  my $config_file = "$tmpdir/config.conf";
2653  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2654  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2655
2656  my $log_file = File::Spec->rel2abs('tests.log');
2657
2658  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2659  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2660
2661  my $user = 'proftpd';
2662  my $passwd = 'test';
2663  my $group = 'ftpd';
2664  my $home_dir = File::Spec->rel2abs($tmpdir);
2665  my $uid = 500;
2666  my $gid = 500;
2667
2668  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2669  mkpath($foo_dir);
2670
2671  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
2672  if (open(my $fh, "> $test_file")) {
2673    print $fh "Hello, World!\n";
2674    unless (close($fh)) {
2675      die("Can't write $test_file: $!");
2676    }
2677
2678  } else {
2679    die("Can't open $test_file: $!");
2680  }
2681
2682  # Since we're chrooting the session, we don't expect to see the real
2683  # absolute path.
2684  my $test_symlink = "/foo/test.txt";
2685
2686  # Change to the 'foo' directory in order to create a relative path in the
2687  # symlink we need
2688
2689  my $cwd = getcwd();
2690  unless (chdir("$foo_dir")) {
2691    die("Can't chdir to $foo_dir: $!");
2692  }
2693
2694  unless (symlink('test.txt', 'test.lnk')) {
2695    die("Can't symlink 'test.txt' to 'test.lnk': $!");
2696  }
2697
2698  unless (chdir($cwd)) {
2699    die("Can't chdir to $cwd: $!");
2700  }
2701
2702  # Make sure that, if we're running as root, that the home directory has
2703  # permissions/privs set for the account we create
2704  if ($< == 0) {
2705    unless (chmod(0755, $home_dir, $foo_dir)) {
2706      die("Can't set perms on $home_dir to 0755: $!");
2707    }
2708
2709    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2710      die("Can't set owner of $home_dir to $uid/$gid: $!");
2711    }
2712  }
2713
2714  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2715    '/bin/bash');
2716  auth_group_write($auth_group_file, $group, $gid, $user);
2717
2718  my $config = {
2719    PidFile => $pid_file,
2720    ScoreboardFile => $scoreboard_file,
2721    SystemLog => $log_file,
2722
2723    AuthUserFile => $auth_user_file,
2724    AuthGroupFile => $auth_group_file,
2725    ShowSymlinks => 'on',
2726
2727    DefaultRoot => '~',
2728
2729    IfModules => {
2730      'mod_delay.c' => {
2731        DelayEngine => 'off',
2732      },
2733    },
2734  };
2735
2736  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2737
2738  # Open pipes, for use between the parent and child processes.  Specifically,
2739  # the child will indicate when it's done with its test by writing a message
2740  # to the parent.
2741  my ($rfh, $wfh);
2742  unless (pipe($rfh, $wfh)) {
2743    die("Can't open pipe: $!");
2744  }
2745
2746  my $ex;
2747
2748  # Fork child
2749  $self->handle_sigchld();
2750  defined(my $pid = fork()) or die("Can't fork: $!");
2751  if ($pid) {
2752    eval {
2753      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2754      $client->login($user, $passwd);
2755
2756      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
2757
2758      my $expected;
2759
2760      $expected = 250;
2761      $self->assert($expected == $resp_code,
2762        test_msg("Expected $expected, got $resp_code"));
2763
2764      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=OS.unix=symlink;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
2765      $self->assert(qr/$expected/, $resp_msg,
2766        test_msg("Expected '$expected', got '$resp_msg'"));
2767    };
2768
2769    if ($@) {
2770      $ex = $@;
2771    }
2772
2773    $wfh->print("done\n");
2774    $wfh->flush();
2775
2776  } else {
2777    eval { server_wait($config_file, $rfh) };
2778    if ($@) {
2779      warn($@);
2780      exit 1;
2781    }
2782
2783    exit 0;
2784  }
2785
2786  # Stop server
2787  server_stop($pid_file);
2788
2789  $self->assert_child_ok($pid);
2790
2791  if ($ex) {
2792    die($ex);
2793  }
2794
2795  unlink($log_file);
2796}
2797
2798sub showsymlinks_off_mlst_rel_symlinked_dir {
2799  my $self = shift;
2800  my $tmpdir = $self->{tmpdir};
2801
2802  my $config_file = "$tmpdir/config.conf";
2803  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2804  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2805
2806  my $log_file = File::Spec->rel2abs('tests.log');
2807
2808  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2809  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2810
2811  my $user = 'proftpd';
2812  my $passwd = 'test';
2813  my $group = 'ftpd';
2814  my $home_dir = File::Spec->rel2abs($tmpdir);
2815  my $uid = 500;
2816  my $gid = 500;
2817
2818  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2819  mkpath($foo_dir);
2820
2821  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
2822  mkpath($test_dir);
2823
2824  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
2825
2826  # Change to the 'foo' directory in order to create a relative path in the
2827  # symlink we need
2828
2829  my $cwd = getcwd();
2830  unless (chdir("$foo_dir")) {
2831    die("Can't chdir to $foo_dir: $!");
2832  }
2833
2834  unless (symlink('test.d', 'test.lnk')) {
2835    die("Can't symlink 'test.d' to 'test.lnk': $!");
2836  }
2837
2838  unless (chdir($cwd)) {
2839    die("Can't chdir to $cwd: $!");
2840  }
2841
2842  # Make sure that, if we're running as root, that the home directory has
2843  # permissions/privs set for the account we create
2844  if ($< == 0) {
2845    unless (chmod(0755, $home_dir, $foo_dir)) {
2846      die("Can't set perms on $home_dir to 0755: $!");
2847    }
2848
2849    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2850      die("Can't set owner of $home_dir to $uid/$gid: $!");
2851    }
2852  }
2853
2854  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2855    '/bin/bash');
2856  auth_group_write($auth_group_file, $group, $gid, $user);
2857
2858  my $config = {
2859    PidFile => $pid_file,
2860    ScoreboardFile => $scoreboard_file,
2861    SystemLog => $log_file,
2862
2863    AuthUserFile => $auth_user_file,
2864    AuthGroupFile => $auth_group_file,
2865    ShowSymlinks => 'off',
2866
2867    IfModules => {
2868      'mod_delay.c' => {
2869        DelayEngine => 'off',
2870      },
2871    },
2872  };
2873
2874  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2875
2876  # Open pipes, for use between the parent and child processes.  Specifically,
2877  # the child will indicate when it's done with its test by writing a message
2878  # to the parent.
2879  my ($rfh, $wfh);
2880  unless (pipe($rfh, $wfh)) {
2881    die("Can't open pipe: $!");
2882  }
2883
2884  my $ex;
2885
2886  # Fork child
2887  $self->handle_sigchld();
2888  defined(my $pid = fork()) or die("Can't fork: $!");
2889  if ($pid) {
2890    eval {
2891      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2892      $client->login($user, $passwd);
2893
2894      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
2895
2896      my $expected;
2897
2898      $expected = 250;
2899      $self->assert($expected == $resp_code,
2900        test_msg("Expected $expected, got $resp_code"));
2901
2902      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
2903      $self->assert(qr/$expected/, $resp_msg,
2904        test_msg("Expected '$expected', got '$resp_msg'"));
2905    };
2906
2907    if ($@) {
2908      $ex = $@;
2909    }
2910
2911    $wfh->print("done\n");
2912    $wfh->flush();
2913
2914  } else {
2915    eval { server_wait($config_file, $rfh) };
2916    if ($@) {
2917      warn($@);
2918      exit 1;
2919    }
2920
2921    exit 0;
2922  }
2923
2924  # Stop server
2925  server_stop($pid_file);
2926
2927  $self->assert_child_ok($pid);
2928
2929  if ($ex) {
2930    die($ex);
2931  }
2932
2933  unlink($log_file);
2934}
2935
2936sub showsymlinks_off_mlst_rel_symlinked_dir_chrooted {
2937  my $self = shift;
2938  my $tmpdir = $self->{tmpdir};
2939
2940  my $config_file = "$tmpdir/config.conf";
2941  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
2942  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
2943
2944  my $log_file = File::Spec->rel2abs('tests.log');
2945
2946  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
2947  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
2948
2949  my $user = 'proftpd';
2950  my $passwd = 'test';
2951  my $group = 'ftpd';
2952  my $home_dir = File::Spec->rel2abs($tmpdir);
2953  my $uid = 500;
2954  my $gid = 500;
2955
2956  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
2957  mkpath($foo_dir);
2958
2959  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
2960  mkpath($test_dir);
2961
2962  # Since we're chrooting the session, we don't expect to see the real
2963  # absolute path.
2964  my $test_symlink = "/foo/test.lnk";
2965
2966  # Change to the 'foo' directory in order to create a relative path in the
2967  # symlink we need
2968
2969  my $cwd = getcwd();
2970  unless (chdir("$foo_dir")) {
2971    die("Can't chdir to $foo_dir: $!");
2972  }
2973
2974  unless (symlink('test.d', 'test.lnk')) {
2975    die("Can't symlink 'test.d' to 'test.lnk': $!");
2976  }
2977
2978  unless (chdir($cwd)) {
2979    die("Can't chdir to $cwd: $!");
2980  }
2981
2982  # Make sure that, if we're running as root, that the home directory has
2983  # permissions/privs set for the account we create
2984  if ($< == 0) {
2985    unless (chmod(0755, $home_dir, $foo_dir)) {
2986      die("Can't set perms on $home_dir to 0755: $!");
2987    }
2988
2989    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
2990      die("Can't set owner of $home_dir to $uid/$gid: $!");
2991    }
2992  }
2993
2994  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2995    '/bin/bash');
2996  auth_group_write($auth_group_file, $group, $gid, $user);
2997
2998  my $config = {
2999    PidFile => $pid_file,
3000    ScoreboardFile => $scoreboard_file,
3001    SystemLog => $log_file,
3002
3003    AuthUserFile => $auth_user_file,
3004    AuthGroupFile => $auth_group_file,
3005    ShowSymlinks => 'off',
3006
3007    DefaultRoot => '~',
3008
3009    IfModules => {
3010      'mod_delay.c' => {
3011        DelayEngine => 'off',
3012      },
3013    },
3014  };
3015
3016  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3017
3018  # Open pipes, for use between the parent and child processes.  Specifically,
3019  # the child will indicate when it's done with its test by writing a message
3020  # to the parent.
3021  my ($rfh, $wfh);
3022  unless (pipe($rfh, $wfh)) {
3023    die("Can't open pipe: $!");
3024  }
3025
3026  my $ex;
3027
3028  # Fork child
3029  $self->handle_sigchld();
3030  defined(my $pid = fork()) or die("Can't fork: $!");
3031  if ($pid) {
3032    eval {
3033      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3034      $client->login($user, $passwd);
3035
3036      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
3037
3038      my $expected;
3039
3040      $expected = 250;
3041      $self->assert($expected == $resp_code,
3042        test_msg("Expected $expected, got $resp_code"));
3043
3044      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=file;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
3045      $self->assert(qr/$expected/, $resp_msg,
3046        test_msg("Expected '$expected', got '$resp_msg'"));
3047    };
3048
3049    if ($@) {
3050      $ex = $@;
3051    }
3052
3053    $wfh->print("done\n");
3054    $wfh->flush();
3055
3056  } else {
3057    eval { server_wait($config_file, $rfh) };
3058    if ($@) {
3059      warn($@);
3060      exit 1;
3061    }
3062
3063    exit 0;
3064  }
3065
3066  # Stop server
3067  server_stop($pid_file);
3068
3069  $self->assert_child_ok($pid);
3070
3071  if ($ex) {
3072    die($ex);
3073  }
3074
3075  unlink($log_file);
3076}
3077
3078sub showsymlinks_on_mlst_rel_symlinked_dir {
3079  my $self = shift;
3080  my $tmpdir = $self->{tmpdir};
3081
3082  my $config_file = "$tmpdir/config.conf";
3083  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3084  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3085
3086  my $log_file = File::Spec->rel2abs('tests.log');
3087
3088  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3089  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3090
3091  my $user = 'proftpd';
3092  my $passwd = 'test';
3093  my $group = 'ftpd';
3094  my $home_dir = File::Spec->rel2abs($tmpdir);
3095  my $uid = 500;
3096  my $gid = 500;
3097
3098  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3099  mkpath($foo_dir);
3100
3101  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
3102  mkpath($test_dir);
3103
3104  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
3105
3106  # Change to the 'foo' directory in order to create a relative path in the
3107  # symlink we need
3108
3109  my $cwd = getcwd();
3110  unless (chdir("$foo_dir")) {
3111    die("Can't chdir to $foo_dir: $!");
3112  }
3113
3114  unless (symlink('test.d', 'test.lnk')) {
3115    die("Can't symlink 'test.d' to 'test.lnk': $!");
3116  }
3117
3118  unless (chdir($cwd)) {
3119    die("Can't chdir to $cwd: $!");
3120  }
3121
3122  # Make sure that, if we're running as root, that the home directory has
3123  # permissions/privs set for the account we create
3124  if ($< == 0) {
3125    unless (chmod(0755, $home_dir, $foo_dir)) {
3126      die("Can't set perms on $home_dir to 0755: $!");
3127    }
3128
3129    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3130      die("Can't set owner of $home_dir to $uid/$gid: $!");
3131    }
3132  }
3133
3134  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3135    '/bin/bash');
3136  auth_group_write($auth_group_file, $group, $gid, $user);
3137
3138  my $config = {
3139    PidFile => $pid_file,
3140    ScoreboardFile => $scoreboard_file,
3141    SystemLog => $log_file,
3142
3143    AuthUserFile => $auth_user_file,
3144    AuthGroupFile => $auth_group_file,
3145    ShowSymlinks => 'on',
3146
3147    IfModules => {
3148      'mod_delay.c' => {
3149        DelayEngine => 'off',
3150      },
3151    },
3152  };
3153
3154  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3155
3156  # Open pipes, for use between the parent and child processes.  Specifically,
3157  # the child will indicate when it's done with its test by writing a message
3158  # to the parent.
3159  my ($rfh, $wfh);
3160  unless (pipe($rfh, $wfh)) {
3161    die("Can't open pipe: $!");
3162  }
3163
3164  my $ex;
3165
3166  # Fork child
3167  $self->handle_sigchld();
3168  defined(my $pid = fork()) or die("Can't fork: $!");
3169  if ($pid) {
3170    eval {
3171      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3172      $client->login($user, $passwd);
3173
3174      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
3175
3176      my $expected;
3177
3178      $expected = 250;
3179      $self->assert($expected == $resp_code,
3180        test_msg("Expected $expected, got $resp_code"));
3181
3182      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=OS.unix=symlink;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_dir . '$';
3183      $self->assert(qr/$expected/, $resp_msg,
3184        test_msg("Expected '$expected', got '$resp_msg'"));
3185    };
3186
3187    if ($@) {
3188      $ex = $@;
3189    }
3190
3191    $wfh->print("done\n");
3192    $wfh->flush();
3193
3194  } else {
3195    eval { server_wait($config_file, $rfh) };
3196    if ($@) {
3197      warn($@);
3198      exit 1;
3199    }
3200
3201    exit 0;
3202  }
3203
3204  # Stop server
3205  server_stop($pid_file);
3206
3207  $self->assert_child_ok($pid);
3208
3209  if ($ex) {
3210    die($ex);
3211  }
3212
3213  unlink($log_file);
3214}
3215
3216sub showsymlinks_on_mlst_rel_symlinked_dir_chrooted {
3217  my $self = shift;
3218  my $tmpdir = $self->{tmpdir};
3219
3220  my $config_file = "$tmpdir/config.conf";
3221  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3222  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3223
3224  my $log_file = File::Spec->rel2abs('tests.log');
3225
3226  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3227  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3228
3229  my $user = 'proftpd';
3230  my $passwd = 'test';
3231  my $group = 'ftpd';
3232  my $home_dir = File::Spec->rel2abs($tmpdir);
3233  my $uid = 500;
3234  my $gid = 500;
3235
3236  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3237  mkpath($foo_dir);
3238
3239  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
3240  mkpath($test_dir);
3241
3242  # Since we're chrooting the session, we don't expect to see the real
3243  # absolute path
3244  my $test_symlink = "/foo/test.d";
3245
3246  # Change to the 'foo' directory in order to create a relative path in the
3247  # symlink we need
3248
3249  my $cwd = getcwd();
3250  unless (chdir("$foo_dir")) {
3251    die("Can't chdir to $foo_dir: $!");
3252  }
3253
3254  unless (symlink('test.d', 'test.lnk')) {
3255    die("Can't symlink 'test.d' to 'test.lnk': $!");
3256  }
3257
3258  unless (chdir($cwd)) {
3259    die("Can't chdir to $cwd: $!");
3260  }
3261
3262  # Make sure that, if we're running as root, that the home directory has
3263  # permissions/privs set for the account we create
3264  if ($< == 0) {
3265    unless (chmod(0755, $home_dir, $foo_dir)) {
3266      die("Can't set perms on $home_dir to 0755: $!");
3267    }
3268
3269    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3270      die("Can't set owner of $home_dir to $uid/$gid: $!");
3271    }
3272  }
3273
3274  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3275    '/bin/bash');
3276  auth_group_write($auth_group_file, $group, $gid, $user);
3277
3278  my $config = {
3279    PidFile => $pid_file,
3280    ScoreboardFile => $scoreboard_file,
3281    SystemLog => $log_file,
3282
3283    AuthUserFile => $auth_user_file,
3284    AuthGroupFile => $auth_group_file,
3285    ShowSymlinks => 'on',
3286
3287    DefaultRoot => '~',
3288
3289    IfModules => {
3290      'mod_delay.c' => {
3291        DelayEngine => 'off',
3292      },
3293    },
3294  };
3295
3296  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3297
3298  # Open pipes, for use between the parent and child processes.  Specifically,
3299  # the child will indicate when it's done with its test by writing a message
3300  # to the parent.
3301  my ($rfh, $wfh);
3302  unless (pipe($rfh, $wfh)) {
3303    die("Can't open pipe: $!");
3304  }
3305
3306  my $ex;
3307
3308  # Fork child
3309  $self->handle_sigchld();
3310  defined(my $pid = fork()) or die("Can't fork: $!");
3311  if ($pid) {
3312    eval {
3313      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3314      $client->login($user, $passwd);
3315
3316      my ($resp_code, $resp_msg) = $client->mlst('foo/test.lnk');
3317
3318      my $expected;
3319
3320      $expected = 250;
3321      $self->assert($expected == $resp_code,
3322        test_msg("Expected $expected, got $resp_code"));
3323
3324      $expected = 'modify=\d+;perm=adfr(w)?;size=\d+;type=OS.unix=symlink;unique=\S+;UNIX.group=\d+;UNIX.mode=\d+;UNIX.owner=\d+; ' . $test_symlink . '$';
3325      $self->assert(qr/$expected/, $resp_msg,
3326        test_msg("Expected '$expected', got '$resp_msg'"));
3327    };
3328
3329    if ($@) {
3330      $ex = $@;
3331    }
3332
3333    $wfh->print("done\n");
3334    $wfh->flush();
3335
3336  } else {
3337    eval { server_wait($config_file, $rfh) };
3338    if ($@) {
3339      warn($@);
3340      exit 1;
3341    }
3342
3343    exit 0;
3344  }
3345
3346  # Stop server
3347  server_stop($pid_file);
3348
3349  $self->assert_child_ok($pid);
3350
3351  if ($ex) {
3352    die($ex);
3353  }
3354
3355  unlink($log_file);
3356}
3357
3358sub showsymlinks_off_stat_rel_symlinked_file {
3359  my $self = shift;
3360  my $tmpdir = $self->{tmpdir};
3361
3362  my $config_file = "$tmpdir/config.conf";
3363  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3364  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3365
3366  my $log_file = File::Spec->rel2abs('tests.log');
3367
3368  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3369  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3370
3371  my $user = 'proftpd';
3372  my $passwd = 'test';
3373  my $group = 'ftpd';
3374  my $home_dir = File::Spec->rel2abs($tmpdir);
3375  my $uid = 500;
3376  my $gid = 500;
3377
3378  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3379  mkpath($foo_dir);
3380
3381  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
3382  if (open(my $fh, "> $test_file")) {
3383    print $fh "Hello, World!\n";
3384    unless (close($fh)) {
3385      die("Can't write $test_file: $!");
3386    }
3387
3388  } else {
3389    die("Can't open $test_file: $!");
3390  }
3391
3392  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
3393
3394  # Change to the 'foo' directory in order to create a relative path in the
3395  # symlink we need
3396
3397  my $cwd = getcwd();
3398  unless (chdir("$foo_dir")) {
3399    die("Can't chdir to $foo_dir: $!");
3400  }
3401
3402  unless (symlink('test.txt', 'test.lnk')) {
3403    die("Can't symlink 'test.txt' to 'test.lnk': $!");
3404  }
3405
3406  unless (chdir($cwd)) {
3407    die("Can't chdir to $cwd: $!");
3408  }
3409
3410  # Make sure that, if we're running as root, that the home directory has
3411  # permissions/privs set for the account we create
3412  if ($< == 0) {
3413    unless (chmod(0755, $home_dir, $foo_dir)) {
3414      die("Can't set perms on $home_dir to 0755: $!");
3415    }
3416
3417    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3418      die("Can't set owner of $home_dir to $uid/$gid: $!");
3419    }
3420  }
3421
3422  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3423    '/bin/bash');
3424  auth_group_write($auth_group_file, $group, $gid, $user);
3425
3426  my $config = {
3427    PidFile => $pid_file,
3428    ScoreboardFile => $scoreboard_file,
3429    SystemLog => $log_file,
3430
3431    AuthUserFile => $auth_user_file,
3432    AuthGroupFile => $auth_group_file,
3433    ShowSymlinks => 'off',
3434
3435    IfModules => {
3436      'mod_delay.c' => {
3437        DelayEngine => 'off',
3438      },
3439    },
3440  };
3441
3442  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3443
3444  # Open pipes, for use between the parent and child processes.  Specifically,
3445  # the child will indicate when it's done with its test by writing a message
3446  # to the parent.
3447  my ($rfh, $wfh);
3448  unless (pipe($rfh, $wfh)) {
3449    die("Can't open pipe: $!");
3450  }
3451
3452  my $ex;
3453
3454  # Fork child
3455  $self->handle_sigchld();
3456  defined(my $pid = fork()) or die("Can't fork: $!");
3457  if ($pid) {
3458    eval {
3459      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3460      $client->login($user, $passwd);
3461
3462      my $name = 'foo/test.lnk';
3463      my ($resp_code, $resp_msg) = $client->stat("-F $name");
3464
3465      my $expected;
3466
3467      $expected = 211;
3468      $self->assert($expected == $resp_code,
3469        test_msg("Expected $expected, got $resp_code"));
3470
3471      unless ($resp_msg =~ /^\s+(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
3472        die("Response '$resp_msg' does not match expected pattern");
3473      }
3474
3475      my $info = $1;
3476      my $path = $2;
3477
3478      $expected = '-rw-r--r--';
3479      $self->assert($expected eq $info,
3480        test_msg("Expected '$expected', got '$info'"));
3481
3482      $expected = $name;
3483      $self->assert($expected eq $path,
3484        test_msg("Expected '$expected', got '$path'"));
3485    };
3486
3487    if ($@) {
3488      $ex = $@;
3489    }
3490
3491    $wfh->print("done\n");
3492    $wfh->flush();
3493
3494  } else {
3495    eval { server_wait($config_file, $rfh) };
3496    if ($@) {
3497      warn($@);
3498      exit 1;
3499    }
3500
3501    exit 0;
3502  }
3503
3504  # Stop server
3505  server_stop($pid_file);
3506
3507  $self->assert_child_ok($pid);
3508
3509  if ($ex) {
3510    die($ex);
3511  }
3512
3513  unlink($log_file);
3514}
3515
3516sub showsymlinks_on_stat_rel_symlinked_file {
3517  my $self = shift;
3518  my $tmpdir = $self->{tmpdir};
3519
3520  my $config_file = "$tmpdir/config.conf";
3521  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3522  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3523
3524  my $log_file = File::Spec->rel2abs('tests.log');
3525
3526  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3527  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3528
3529  my $user = 'proftpd';
3530  my $passwd = 'test';
3531  my $group = 'ftpd';
3532  my $home_dir = File::Spec->rel2abs($tmpdir);
3533  my $uid = 500;
3534  my $gid = 500;
3535
3536  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3537  mkpath($foo_dir);
3538
3539  my $test_file = File::Spec->rel2abs("$tmpdir/foo/test.txt");
3540  if (open(my $fh, "> $test_file")) {
3541    print $fh "Hello, World!\n";
3542    unless (close($fh)) {
3543      die("Can't write $test_file: $!");
3544    }
3545
3546  } else {
3547    die("Can't open $test_file: $!");
3548  }
3549
3550  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
3551
3552  # Change to the 'foo' directory in order to create a relative path in the
3553  # symlink we need
3554
3555  my $cwd = getcwd();
3556  unless (chdir("$foo_dir")) {
3557    die("Can't chdir to $foo_dir: $!");
3558  }
3559
3560  unless (symlink('test.txt', 'test.lnk')) {
3561    die("Can't symlink 'test.txt' to 'test.lnk': $!");
3562  }
3563
3564  unless (chdir($cwd)) {
3565    die("Can't chdir to $cwd: $!");
3566  }
3567
3568  # Make sure that, if we're running as root, that the home directory has
3569  # permissions/privs set for the account we create
3570  if ($< == 0) {
3571    unless (chmod(0755, $home_dir, $foo_dir)) {
3572      die("Can't set perms on $home_dir to 0755: $!");
3573    }
3574
3575    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3576      die("Can't set owner of $home_dir to $uid/$gid: $!");
3577    }
3578  }
3579
3580  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3581    '/bin/bash');
3582  auth_group_write($auth_group_file, $group, $gid, $user);
3583
3584  my $config = {
3585    PidFile => $pid_file,
3586    ScoreboardFile => $scoreboard_file,
3587    SystemLog => $log_file,
3588
3589    AuthUserFile => $auth_user_file,
3590    AuthGroupFile => $auth_group_file,
3591    ShowSymlinks => 'on',
3592
3593    IfModules => {
3594      'mod_delay.c' => {
3595        DelayEngine => 'off',
3596      },
3597    },
3598  };
3599
3600  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3601
3602  # Open pipes, for use between the parent and child processes.  Specifically,
3603  # the child will indicate when it's done with its test by writing a message
3604  # to the parent.
3605  my ($rfh, $wfh);
3606  unless (pipe($rfh, $wfh)) {
3607    die("Can't open pipe: $!");
3608  }
3609
3610  my $ex;
3611
3612  # Fork child
3613  $self->handle_sigchld();
3614  defined(my $pid = fork()) or die("Can't fork: $!");
3615  if ($pid) {
3616    eval {
3617      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3618      $client->login($user, $passwd);
3619
3620      my $name = 'foo/test.lnk';
3621      my ($resp_code, $resp_msg) = $client->stat("-F $name");
3622
3623      my $expected;
3624
3625      $expected = 211;
3626      $self->assert($expected == $resp_code,
3627        test_msg("Expected $expected, got $resp_code"));
3628
3629      unless ($resp_msg =~ /^\s+(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(.*)?$/) {
3630        die("Response '$resp_msg' does not match expected pattern");
3631      }
3632
3633      my $info = $1;
3634      my $path = $2;
3635
3636      $expected = 'lrwxrwxrwx';
3637      $self->assert($expected eq $info,
3638        test_msg("Expected '$expected', got '$info'"));
3639
3640      # XXX Possible bug; this should look like:
3641      #
3642      #  foo/test.lnk -> foo/test.txt
3643      #
3644      # instead of:
3645      #
3646      #  foo/test.lnk -> test.txt
3647
3648      $expected = "$name -> test.txt";
3649      $self->assert($expected eq $path,
3650        test_msg("Expected '$expected', got '$path'"));
3651    };
3652
3653    if ($@) {
3654      $ex = $@;
3655    }
3656
3657    $wfh->print("done\n");
3658    $wfh->flush();
3659
3660  } else {
3661    eval { server_wait($config_file, $rfh) };
3662    if ($@) {
3663      warn($@);
3664      exit 1;
3665    }
3666
3667    exit 0;
3668  }
3669
3670  # Stop server
3671  server_stop($pid_file);
3672
3673  $self->assert_child_ok($pid);
3674
3675  if ($ex) {
3676    die($ex);
3677  }
3678
3679  unlink($log_file);
3680}
3681
3682sub showsymlinks_off_stat_rel_symlinked_dir {
3683  my $self = shift;
3684  my $tmpdir = $self->{tmpdir};
3685
3686  my $config_file = "$tmpdir/config.conf";
3687  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3688  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3689
3690  my $log_file = File::Spec->rel2abs('tests.log');
3691
3692  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3693  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3694
3695  my $user = 'proftpd';
3696  my $passwd = 'test';
3697  my $group = 'ftpd';
3698  my $home_dir = File::Spec->rel2abs($tmpdir);
3699  my $uid = 500;
3700  my $gid = 500;
3701
3702  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3703  mkpath($foo_dir);
3704
3705  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
3706  mkpath($test_dir);
3707
3708  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
3709
3710  # Change to the 'foo' directory in order to create a relative path in the
3711  # symlink we need
3712
3713  my $cwd = getcwd();
3714  unless (chdir("$foo_dir")) {
3715    die("Can't chdir to $foo_dir: $!");
3716  }
3717
3718  unless (symlink('test.d', 'test.lnk')) {
3719    die("Can't symlink 'test.d' to 'test.lnk': $!");
3720  }
3721
3722  unless (chdir($cwd)) {
3723    die("Can't chdir to $cwd: $!");
3724  }
3725
3726  # Make sure that, if we're running as root, that the home directory has
3727  # permissions/privs set for the account we create
3728  if ($< == 0) {
3729    unless (chmod(0755, $home_dir, $foo_dir)) {
3730      die("Can't set perms on $home_dir to 0755: $!");
3731    }
3732
3733    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3734      die("Can't set owner of $home_dir to $uid/$gid: $!");
3735    }
3736  }
3737
3738  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3739    '/bin/bash');
3740  auth_group_write($auth_group_file, $group, $gid, $user);
3741
3742  my $config = {
3743    PidFile => $pid_file,
3744    ScoreboardFile => $scoreboard_file,
3745    SystemLog => $log_file,
3746
3747    AuthUserFile => $auth_user_file,
3748    AuthGroupFile => $auth_group_file,
3749    ShowSymlinks => 'off',
3750
3751    IfModules => {
3752      'mod_delay.c' => {
3753        DelayEngine => 'off',
3754      },
3755    },
3756  };
3757
3758  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3759
3760  # Open pipes, for use between the parent and child processes.  Specifically,
3761  # the child will indicate when it's done with its test by writing a message
3762  # to the parent.
3763  my ($rfh, $wfh);
3764  unless (pipe($rfh, $wfh)) {
3765    die("Can't open pipe: $!");
3766  }
3767
3768  my $ex;
3769
3770  # Fork child
3771  $self->handle_sigchld();
3772  defined(my $pid = fork()) or die("Can't fork: $!");
3773  if ($pid) {
3774    eval {
3775      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3776      $client->login($user, $passwd);
3777
3778      my $name = 'foo/test.d';
3779      my ($resp_code, $resp_msg) = $client->stat("-F $name");
3780
3781      my $expected;
3782
3783      $expected = 211;
3784      $self->assert($expected == $resp_code,
3785        test_msg("Expected $expected, got $resp_code"));
3786
3787      unless ($resp_msg =~ /^\s+(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+(\S+)$/) {
3788        die("Response '$resp_msg' does not match expected pattern");
3789      }
3790
3791      my $info = $1;
3792      my $path = $2;
3793
3794      $expected = 'drwxr-xr-x';
3795      $self->assert($expected eq $info,
3796        test_msg("Expected '$expected', got '$info'"));
3797
3798      $expected = ".";
3799      $self->assert($expected eq $path,
3800        test_msg("Expected '$expected', got '$path'"));
3801    };
3802
3803    if ($@) {
3804      $ex = $@;
3805    }
3806
3807    $wfh->print("done\n");
3808    $wfh->flush();
3809
3810  } else {
3811    eval { server_wait($config_file, $rfh) };
3812    if ($@) {
3813      warn($@);
3814      exit 1;
3815    }
3816
3817    exit 0;
3818  }
3819
3820  # Stop server
3821  server_stop($pid_file);
3822
3823  $self->assert_child_ok($pid);
3824
3825  if ($ex) {
3826    die($ex);
3827  }
3828
3829  unlink($log_file);
3830}
3831
3832sub showsymlinks_on_stat_rel_symlinked_dir {
3833  my $self = shift;
3834  my $tmpdir = $self->{tmpdir};
3835
3836  my $config_file = "$tmpdir/config.conf";
3837  my $pid_file = File::Spec->rel2abs("$tmpdir/config.pid");
3838  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/config.scoreboard");
3839
3840  my $log_file = File::Spec->rel2abs('tests.log');
3841
3842  my $auth_user_file = File::Spec->rel2abs("$tmpdir/config.passwd");
3843  my $auth_group_file = File::Spec->rel2abs("$tmpdir/config.group");
3844
3845  my $user = 'proftpd';
3846  my $passwd = 'test';
3847  my $group = 'ftpd';
3848  my $home_dir = File::Spec->rel2abs($tmpdir);
3849  my $uid = 500;
3850  my $gid = 500;
3851
3852  my $foo_dir = File::Spec->rel2abs("$tmpdir/foo");
3853  mkpath($foo_dir);
3854
3855  my $test_dir = File::Spec->rel2abs("$tmpdir/foo/test.d");
3856  mkpath($test_dir);
3857
3858  my $test_symlink = File::Spec->rel2abs("$tmpdir/foo/test.lnk");
3859
3860  # Change to the 'foo' directory in order to create a relative path in the
3861  # symlink we need
3862
3863  my $cwd = getcwd();
3864  unless (chdir("$foo_dir")) {
3865    die("Can't chdir to $foo_dir: $!");
3866  }
3867
3868  unless (symlink('test.d', 'test.lnk')) {
3869    die("Can't symlink 'test.d' to 'test.lnk': $!");
3870  }
3871
3872  unless (chdir($cwd)) {
3873    die("Can't chdir to $cwd: $!");
3874  }
3875
3876  # Make sure that, if we're running as root, that the home directory has
3877  # permissions/privs set for the account we create
3878  if ($< == 0) {
3879    unless (chmod(0755, $home_dir, $foo_dir)) {
3880      die("Can't set perms on $home_dir to 0755: $!");
3881    }
3882
3883    unless (chown($uid, $gid, $home_dir, $foo_dir)) {
3884      die("Can't set owner of $home_dir to $uid/$gid: $!");
3885    }
3886  }
3887
3888  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
3889    '/bin/bash');
3890  auth_group_write($auth_group_file, $group, $gid, $user);
3891
3892  my $config = {
3893    PidFile => $pid_file,
3894    ScoreboardFile => $scoreboard_file,
3895    SystemLog => $log_file,
3896
3897    AuthUserFile => $auth_user_file,
3898    AuthGroupFile => $auth_group_file,
3899    ShowSymlinks => 'on',
3900
3901    IfModules => {
3902      'mod_delay.c' => {
3903        DelayEngine => 'off',
3904      },
3905    },
3906  };
3907
3908  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3909
3910  # Open pipes, for use between the parent and child processes.  Specifically,
3911  # the child will indicate when it's done with its test by writing a message
3912  # to the parent.
3913  my ($rfh, $wfh);
3914  unless (pipe($rfh, $wfh)) {
3915    die("Can't open pipe: $!");
3916  }
3917
3918  my $ex;
3919
3920  # Fork child
3921  $self->handle_sigchld();
3922  defined(my $pid = fork()) or die("Can't fork: $!");
3923  if ($pid) {
3924    eval {
3925      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3926      $client->login($user, $passwd);
3927
3928      my $name = 'foo/test.d';
3929      my ($resp_code, $resp_msg) = $client->stat("-F $name");
3930
3931      my $expected;
3932
3933      $expected = 211;
3934      $self->assert($expected == $resp_code,
3935        test_msg("Expected $expected, got $resp_code"));
3936
3937      # XXX Possible bug; this should return info on the symlink, rather
3938      # than trying to list the contents of the symlink target directory.
3939
3940      unless ($resp_msg =~ /^\s+(\S+)\s+\d+\s+\S+\s+\S+\s+.*?\s+\d{2}:\d{2}\s+(.*)?$/) {
3941        die("Response '$resp_msg' does not match expected pattern");
3942      }
3943
3944      my $info = $1;
3945      my $path = $2;
3946
3947      $expected = 'drwxr-xr-x';
3948      $self->assert($expected eq $info,
3949        test_msg("Expected '$expected', got '$info'"));
3950
3951      $expected = ".";
3952      $self->assert($expected eq $path,
3953        test_msg("Expected '$expected', got '$path'"));
3954    };
3955
3956    if ($@) {
3957      $ex = $@;
3958    }
3959
3960    $wfh->print("done\n");
3961    $wfh->flush();
3962
3963  } else {
3964    eval { server_wait($config_file, $rfh) };
3965    if ($@) {
3966      warn($@);
3967      exit 1;
3968    }
3969
3970    exit 0;
3971  }
3972
3973  # Stop server
3974  server_stop($pid_file);
3975
3976  $self->assert_child_ok($pid);
3977
3978  if ($ex) {
3979    die($ex);
3980  }
3981
3982  unlink($log_file);
3983}
3984
39851;
Note: See TracBrowser for help on using the repository browser.