1package ProFTPD::Tests::Config::Directory::Limits;
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
19# A fun collection of various <Directory> and <Limit> configurations
20
21my $TESTS = {
22  limits_with_glob_then_nonglob_dirs_for_same_path => {
23    order => ++$order,
24    test_class => [qw(forking)],
25  },
26
27  limits_with_nonglob_then_glob_dirs_for_same_path => {
28    order => ++$order,
29    test_class => [qw(forking)],
30  },
31
32  limits_with_glob_denied_delete_bug3146 => {
33    order => ++$order,
34    test_class => [qw(bug forking)],
35  },
36
37  limits_without_glob_denied_delete_bug3146 => {
38    order => ++$order,
39    test_class => [qw(bug forking)],
40  },
41
42  limits_commands_comma_space_delimited_deferred_paths_bug3147 => {
43    order => ++$order,
44    test_class => [qw(bug forking)],
45  },
46
47  limits_commands_comma_delimited_deferred_paths_bug3147 => {
48    order => ++$order,
49    test_class => [qw(bug forking)],
50  },
51
52  limits_commands_no_commas_deferred_paths_bug3147 => {
53    order => ++$order,
54    test_class => [qw(bug forking)],
55  },
56
57  limits_rename_dir_ok_write_denied => {
58    order => ++$order,
59    test_class => [qw(forking)],
60  },
61
62  limits_rename_dir_failed_rnfr_denied => {
63    order => ++$order,
64    test_class => [qw(forking)],
65  },
66
67  limits_one_char_dir_bug3337 => {
68    order => ++$order,
69    test_class => [qw(bug forking)],
70  },
71
72  limits_symlink_dir_bug3166 => {
73    order => ++$order,
74    test_class => [qw(bug forking rootprivs)],
75  },
76
77  limits_anon_dir_abs_path_bug3283 => {
78    order => ++$order,
79    test_class => [qw(bug forking rootprivs)],
80  },
81
82  limits_with_multi_globs_denied_delete => {
83    order => ++$order,
84    test_class => [qw(forking)],
85  },
86
87  limits_retr_bug3915 => {
88    order => ++$order,
89    test_class => [qw(bug forking)],
90  },
91
92  limits_retr_bug3915_chrooted => {
93    order => ++$order,
94    test_class => [qw(bug forking rootprivs)],
95  },
96
97  limits_stor_with_multiple_groups_chrooted => {
98    order => ++$order,
99    test_class => [qw(bug forking rootprivs)],
100  },
101
102};
103
104sub new {
105  return shift()->SUPER::new(@_);
106}
107
108sub list_tests {
109  return testsuite_get_runnable_tests($TESTS);
110}
111
112sub limits_with_glob_then_nonglob_dirs_for_same_path {
113  my $self = shift;
114  my $tmpdir = $self->{tmpdir};
115
116  my $config_file = "$tmpdir/dir.conf";
117  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
118  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
119
120  my $log_file = test_get_logfile();
121
122  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
123  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
124
125  my $user = 'proftpd';
126  my $passwd = 'test';
127  my $group = 'ftpd';
128  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
129  my $uid = 500;
130  my $gid = 500;
131
132  mkpath($home_dir);
133
134  # Make sure that, if we're running as root, that the home directory has
135  # permissions/privs set for the account we create
136  if ($< == 0) {
137    unless (chmod(0755, $home_dir)) {
138      die("Can't set perms on $home_dir to 0755: $!");
139    }
140
141    unless (chown($uid, $gid, $home_dir)) {
142      die("Can't set owner of $home_dir to $uid/$gid: $!");
143    }
144  }
145
146  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
147    '/bin/bash');
148  auth_group_write($auth_group_file, $group, $gid, $user);
149
150  my $abs_tmp_dir = File::Spec->rel2abs($tmpdir);
151
152  my $config = {
153    PidFile => $pid_file,
154    ScoreboardFile => $scoreboard_file,
155    SystemLog => $log_file,
156    TraceLog => $log_file,
157    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
158
159    AuthUserFile => $auth_user_file,
160    AuthGroupFile => $auth_group_file,
161
162    DefaultChdir => '~',
163
164    IfModules => {
165      'mod_delay.c' => {
166        DelayEngine => 'off',
167      },
168    },
169  };
170
171  my ($port, $config_user, $config_group) = config_write($config_file, $config);
172
173  # In order to ensure that the <Directory> sections appear in the desired
174  # order in the config file, we write them out here.  The config_write()
175  # function, whilst useful, uses hashrefs, which means the ordering of the
176  # keys is not necessarily predictable.
177  if (open(my $fh, ">> $config_file")) {
178    print $fh <<EOD;
179<Directory $abs_tmp_dir/home/*/proftpd>
180  <Limit ALL>
181    DenyAll
182  </Limit>
183</Directory>
184<Directory $abs_tmp_dir/home/users/proftpd>
185  <Limit ALL>
186    AllowAll
187  </Limit>
188</Directory>
189EOD
190    close($fh);
191
192  } else {
193    die("Can't read $config_file: $!");
194  }
195
196  # Open pipes, for use between the parent and child processes.  Specifically,
197  # the child will indicate when it's done with its test by writing a message
198  # to the parent.
199  my ($rfh, $wfh);
200  unless (pipe($rfh, $wfh)) {
201    die("Can't open pipe: $!");
202  }
203
204  my $ex;
205
206  # Fork child
207  $self->handle_sigchld();
208  defined(my $pid = fork()) or die("Can't fork: $!");
209  if ($pid) {
210    eval {
211      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
212      $client->login($user, $passwd);
213
214      my ($resp_code, $resp_msg);
215
216      my $conn = $client->stor_raw('test.txt');
217      unless ($conn) {
218        die("Failed to STOR test.txt: " . $client->response_code() . " " .
219          $client->response_msg());
220      };
221
222      my $buf = "Hello, World\n";
223      $conn->write($buf, length($buf));
224      $conn->close();
225
226      $resp_code = $client->response_code();
227      $resp_msg = $client->response_msg();
228
229      my $expected;
230
231      $expected = 226;
232      $self->assert($expected == $resp_code,
233        test_msg("Expected $expected, got $resp_code"));
234
235      $expected = "Transfer complete";
236      $self->assert($expected eq $resp_msg,
237        test_msg("Expected '$expected', got '$resp_msg'"));
238    };
239
240    if ($@) {
241      $ex = $@;
242    }
243
244    $wfh->print("done\n");
245    $wfh->flush();
246
247  } else {
248    eval { server_wait($config_file, $rfh) };
249    if ($@) {
250      warn($@);
251      exit 1;
252    }
253
254    exit 0;
255  }
256
257  # Stop server
258  server_stop($pid_file);
259
260  $self->assert_child_ok($pid);
261
262  if ($ex) {
263    test_append_logfile($log_file, $ex);
264    unlink($log_file);
265
266    die($ex);
267  }
268
269  unlink($log_file);
270}
271
272sub limits_with_nonglob_then_glob_dirs_for_same_path {
273  my $self = shift;
274  my $tmpdir = $self->{tmpdir};
275
276  my $config_file = "$tmpdir/dir.conf";
277  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
278  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
279
280  my $log_file = test_get_logfile();
281
282  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
283  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
284
285  my $user = 'proftpd';
286  my $passwd = 'test';
287  my $group = 'ftpd';
288  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
289  my $uid = 500;
290  my $gid = 500;
291
292  mkpath($home_dir);
293
294  # Make sure that, if we're running as root, that the home directory has
295  # permissions/privs set for the account we create
296  if ($< == 0) {
297    unless (chmod(0755, $home_dir)) {
298      die("Can't set perms on $home_dir to 0755: $!");
299    }
300
301    unless (chown($uid, $gid, $home_dir)) {
302      die("Can't set owner of $home_dir to $uid/$gid: $!");
303    }
304  }
305
306  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
307    '/bin/bash');
308  auth_group_write($auth_group_file, $group, $gid, $user);
309
310  my $abs_tmp_dir = File::Spec->rel2abs($tmpdir);
311
312  my $config = {
313    PidFile => $pid_file,
314    ScoreboardFile => $scoreboard_file,
315    SystemLog => $log_file,
316    TraceLog => $log_file,
317    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
318
319    AuthUserFile => $auth_user_file,
320    AuthGroupFile => $auth_group_file,
321
322    DefaultChdir => '~',
323
324    IfModules => {
325      'mod_delay.c' => {
326        DelayEngine => 'off',
327      },
328    },
329  };
330
331  my ($port, $config_user, $config_group) = config_write($config_file, $config);
332
333  # In order to ensure that the <Directory> sections appear in the desired
334  # order in the config file, we write them out here.  The config_write()
335  # function, whilst useful, uses hashrefs, which means the ordering of the
336  # keys is not necessarily predictable.
337  if (open(my $fh, ">> $config_file")) {
338    print $fh <<EOD;
339<Directory $abs_tmp_dir/home/users/proftpd>
340  <Limit ALL>
341    AllowAll
342  </Limit>
343</Directory>
344<Directory $abs_tmp_dir/home/*/proftpd>
345  <Limit ALL>
346    DenyAll
347  </Limit>
348</Directory>
349EOD
350    close($fh);
351
352  } else {
353    die("Can't read $config_file: $!");
354  }
355
356  # Open pipes, for use between the parent and child processes.  Specifically,
357  # the child will indicate when it's done with its test by writing a message
358  # to the parent.
359  my ($rfh, $wfh);
360  unless (pipe($rfh, $wfh)) {
361    die("Can't open pipe: $!");
362  }
363
364  my $ex;
365
366  # Fork child
367  $self->handle_sigchld();
368  defined(my $pid = fork()) or die("Can't fork: $!");
369  if ($pid) {
370    eval {
371      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
372      $client->login($user, $passwd);
373
374      my ($resp_code, $resp_msg);
375
376      my $conn = $client->stor_raw('test.txt');
377      if ($conn) {
378        die("STOR test.txt succeeded unexpectedly");
379      };
380
381      $resp_code = $client->response_code();
382      $resp_msg = $client->response_msg();
383
384      my $expected;
385
386      $expected = 550;
387      $self->assert($expected == $resp_code,
388        test_msg("Expected $expected, got $resp_code"));
389
390      $expected = "test.txt: Operation not permitted";
391      $self->assert($expected eq $resp_msg,
392        test_msg("Expected '$expected', got '$resp_msg'"));
393    };
394
395    if ($@) {
396      $ex = $@;
397    }
398
399    $wfh->print("done\n");
400    $wfh->flush();
401
402  } else {
403    eval { server_wait($config_file, $rfh) };
404    if ($@) {
405      warn($@);
406      exit 1;
407    }
408
409    exit 0;
410  }
411
412  # Stop server
413  server_stop($pid_file);
414
415  $self->assert_child_ok($pid);
416
417  if ($ex) {
418    test_append_logfile($log_file, $ex);
419    unlink($log_file);
420
421    die($ex);
422  }
423
424  unlink($log_file);
425}
426
427sub limits_with_glob_denied_delete_bug3146 {
428  my $self = shift;
429  my $tmpdir = $self->{tmpdir};
430
431  # This config is from:
432  #
433  #  http://forums.proftpd.org/smf/index.php/topic,3491.0
434  #  http://bugs.proftpd.org/show_bug.cgi?id=3146
435
436  my $config_file = "$tmpdir/dir.conf";
437  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
438  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
439
440  my $log_file = test_get_logfile();
441
442  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
443  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
444
445  my $user = 'proftpd';
446  my $passwd = 'test';
447  my $group = 'ftpd';
448  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
449  my $uid = 500;
450  my $gid = 500;
451
452  mkpath($home_dir);
453
454  my $test_file = File::Spec->rel2abs("$home_dir/test.txt");
455  if (open(my $fh, "> $test_file")) {
456    print $fh "Hello, World!\n";
457
458    unless (close($fh)) {
459      die("Can't write $test_file: $!");
460    }
461
462  } else {
463    die("Can't open $test_file: $!");
464  }
465
466  # Make sure that, if we're running as root, that the home directory has
467  # permissions/privs set for the account we create
468  if ($< == 0) {
469    unless (chmod(0755, $home_dir)) {
470      die("Can't set perms on $home_dir to 0755: $!");
471    }
472
473    unless (chown($uid, $gid, $home_dir)) {
474      die("Can't set owner of $home_dir to $uid/$gid: $!");
475    }
476  }
477
478  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
479    '/bin/bash');
480  auth_group_write($auth_group_file, $group, $gid, $user);
481
482  my $abs_tmp_dir = File::Spec->rel2abs($tmpdir);
483
484  my $config = {
485    PidFile => $pid_file,
486    ScoreboardFile => $scoreboard_file,
487    SystemLog => $log_file,
488    TraceLog => $log_file,
489    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
490
491    AuthUserFile => $auth_user_file,
492    AuthGroupFile => $auth_group_file,
493
494    DefaultChdir => '~',
495
496    Directory => {
497      "$abs_tmp_dir/home/*/proftpd" => {
498        Limit => {
499          'ALL' => {
500            DenyAll => '',
501          },
502
503          'CDUP CWD XCWD XCUP LIST NLST' => {
504            AllowAll => '',
505          },
506
507          'STOR STOU' => {
508            AllowAll => '',
509          },
510        },
511      },
512    },
513
514    IfModules => {
515      'mod_delay.c' => {
516        DelayEngine => 'off',
517      },
518    },
519  };
520
521  my ($port, $config_user, $config_group) = config_write($config_file, $config);
522
523  # Open pipes, for use between the parent and child processes.  Specifically,
524  # the child will indicate when it's done with its test by writing a message
525  # to the parent.
526  my ($rfh, $wfh);
527  unless (pipe($rfh, $wfh)) {
528    die("Can't open pipe: $!");
529  }
530
531  my $ex;
532
533  # Fork child
534  $self->handle_sigchld();
535  defined(my $pid = fork()) or die("Can't fork: $!");
536  if ($pid) {
537    eval {
538      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
539      $client->login($user, $passwd);
540
541      my ($resp_code, $resp_msg);
542      eval { $client->dele('test.txt') };
543      unless ($@) {
544        die("DELE succeeded unexpectedly");
545
546      } else {
547        $resp_code = $client->response_code();
548        $resp_msg = $client->response_msg();
549      }
550
551      my $expected;
552
553      $expected = 550;
554      $self->assert($expected == $resp_code,
555        test_msg("Expected $expected, got $resp_code"));
556
557      $expected = "test.txt: Operation not permitted";
558      $self->assert($expected eq $resp_msg,
559        test_msg("Expected '$expected', got '$resp_msg'"));
560    };
561
562    if ($@) {
563      $ex = $@;
564    }
565
566    $wfh->print("done\n");
567    $wfh->flush();
568
569  } else {
570    eval { server_wait($config_file, $rfh) };
571    if ($@) {
572      warn($@);
573      exit 1;
574    }
575
576    exit 0;
577  }
578
579  # Stop server
580  server_stop($pid_file);
581
582  $self->assert_child_ok($pid);
583
584  if ($ex) {
585    test_append_logfile($log_file, $ex);
586    unlink($log_file);
587
588    die($ex);
589  }
590
591  unlink($log_file);
592}
593
594sub limits_without_glob_denied_delete_bug3146 {
595  my $self = shift;
596  my $tmpdir = $self->{tmpdir};
597
598  # This config is from:
599  #
600  #  http://forums.proftpd.org/smf/index.php/topic,3491.0
601  #
602  # only without the glob pattern, to check for regressions.
603
604  my $config_file = "$tmpdir/dir.conf";
605  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
606  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
607
608  my $log_file = test_get_logfile();
609
610  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
611  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
612
613  my $user = 'proftpd';
614  my $passwd = 'test';
615  my $group = 'ftpd';
616  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
617  my $uid = 500;
618  my $gid = 500;
619
620  mkpath($home_dir);
621
622  my $test_file = File::Spec->rel2abs("$home_dir/test.txt");
623  if (open(my $fh, "> $test_file")) {
624    print $fh "Hello, World!\n";
625
626    unless (close($fh)) {
627      die("Can't write $test_file: $!");
628    }
629
630  } else {
631    die("Can't open $test_file: $!");
632  }
633
634  # Make sure that, if we're running as root, that the home directory has
635  # permissions/privs set for the account we create
636  if ($< == 0) {
637    unless (chmod(0755, $home_dir)) {
638      die("Can't set perms on $home_dir to 0755: $!");
639    }
640
641    unless (chown($uid, $gid, $home_dir)) {
642      die("Can't set owner of $home_dir to $uid/$gid: $!");
643    }
644  }
645
646  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
647    '/bin/bash');
648  auth_group_write($auth_group_file, $group, $gid, $user);
649
650  my $abs_tmp_dir = File::Spec->rel2abs($tmpdir);
651
652  my $config = {
653    PidFile => $pid_file,
654    ScoreboardFile => $scoreboard_file,
655    SystemLog => $log_file,
656    TraceLog => $log_file,
657    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
658
659    AuthUserFile => $auth_user_file,
660    AuthGroupFile => $auth_group_file,
661
662    DefaultChdir => '~',
663
664    Directory => {
665      "$abs_tmp_dir/home/users/proftpd" => {
666        Limit => {
667          'ALL' => {
668            DenyAll => '',
669          },
670
671          'CDUP CWD XCWD XCUP LIST NLST' => {
672            AllowAll => '',
673          },
674
675          'STOR STOU' => {
676            AllowAll => '',
677          },
678        },
679      },
680    },
681
682    IfModules => {
683      'mod_delay.c' => {
684        DelayEngine => 'off',
685      },
686    },
687  };
688
689  my ($port, $config_user, $config_group) = config_write($config_file, $config);
690
691  # Open pipes, for use between the parent and child processes.  Specifically,
692  # the child will indicate when it's done with its test by writing a message
693  # to the parent.
694  my ($rfh, $wfh);
695  unless (pipe($rfh, $wfh)) {
696    die("Can't open pipe: $!");
697  }
698
699  my $ex;
700
701  # Fork child
702  $self->handle_sigchld();
703  defined(my $pid = fork()) or die("Can't fork: $!");
704  if ($pid) {
705    eval {
706      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
707      $client->login($user, $passwd);
708
709      my ($resp_code, $resp_msg);
710      eval { $client->dele('test.txt') };
711      unless ($@) {
712        die("DELE succeeded unexpectedly");
713
714      } else {
715        $resp_code = $client->response_code();
716        $resp_msg = $client->response_msg();
717      }
718
719      my $expected;
720
721      $expected = 550;
722      $self->assert($expected == $resp_code,
723        test_msg("Expected $expected, got $resp_code"));
724
725      $expected = "test.txt: Operation not permitted";
726      $self->assert($expected eq $resp_msg,
727        test_msg("Expected '$expected', got '$resp_msg'"));
728    };
729
730    if ($@) {
731      $ex = $@;
732    }
733
734    $wfh->print("done\n");
735    $wfh->flush();
736
737  } else {
738    eval { server_wait($config_file, $rfh) };
739    if ($@) {
740      warn($@);
741      exit 1;
742    }
743
744    exit 0;
745  }
746
747  # Stop server
748  server_stop($pid_file);
749
750  $self->assert_child_ok($pid);
751
752  if ($ex) {
753    test_append_logfile($log_file, $ex);
754    unlink($log_file);
755
756    die($ex);
757  }
758
759  unlink($log_file);
760}
761
762sub limits_commands_comma_space_delimited_deferred_paths_bug3147 {
763  my $self = shift;
764  my $tmpdir = $self->{tmpdir};
765
766  # This config is from:
767  #
768  #  http://forums.proftpd.org/smf/index.php/topic,3648.0
769  #  http://bugs.proftpd.org/show_bug.cgi?id=3147
770
771  my $config_file = "$tmpdir/dir.conf";
772  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
773  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
774
775  my $log_file = test_get_logfile();
776
777  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
778  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
779
780  my $user = 'proftpd';
781  my $passwd = 'test';
782  my $group = 'ftpd';
783  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
784  my $uid = 500;
785  my $gid = 500;
786
787  mkpath($home_dir);
788
789  my $sub_dir = File::Spec->rel2abs("$tmpdir/home/$user/upload");
790  mkpath($sub_dir);
791
792  # Make sure that, if we're running as root, that the home directory has
793  # permissions/privs set for the account we create
794  if ($< == 0) {
795    unless (chmod(0755, $home_dir, $sub_dir)) {
796      die("Can't set perms on $home_dir, $sub_dir to 0755: $!");
797    }
798
799    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
800      die("Can't set owner of $home_dir, $sub_dir to $uid/$gid: $!");
801    }
802  }
803
804  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
805    '/bin/bash');
806  auth_group_write($auth_group_file, $group, $gid, $user);
807
808  my $config = {
809    PidFile => $pid_file,
810    ScoreboardFile => $scoreboard_file,
811    SystemLog => $log_file,
812    TraceLog => $log_file,
813    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
814
815    AuthUserFile => $auth_user_file,
816    AuthGroupFile => $auth_group_file,
817
818    DefaultChdir => '~',
819
820    Directory => {
821      "~$user" => {
822        Limit => {
823          'WRITE' => {
824            DenyAll => '',
825          },
826        },
827      },
828
829      # Bug#3147 happened because proftpd's parsing of the list of commands
830      # in the <Limit> section was not properly handling commas after the
831      # command names.
832
833      "~$user/upload" => {
834        Limit => {
835          'STOR, APPE, MKD, RMD, RNTO, STOU, XMKD, XRMD' => {
836            AllowAll => '',
837          },
838        },
839      },
840    },
841
842    IfModules => {
843      'mod_delay.c' => {
844        DelayEngine => 'off',
845      },
846    },
847  };
848
849  my ($port, $config_user, $config_group) = config_write($config_file, $config);
850
851  # Open pipes, for use between the parent and child processes.  Specifically,
852  # the child will indicate when it's done with its test by writing a message
853  # to the parent.
854  my ($rfh, $wfh);
855  unless (pipe($rfh, $wfh)) {
856    die("Can't open pipe: $!");
857  }
858
859  my $ex;
860
861  # Fork child
862  $self->handle_sigchld();
863  defined(my $pid = fork()) or die("Can't fork: $!");
864  if ($pid) {
865    eval {
866      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
867      $client->login($user, $passwd);
868
869      my ($resp_code, $resp_msg);
870
871      my $conn = $client->stor_raw('upload/test.txt');
872      unless ($conn) {
873        die("Failed to STOR upload/test.txt: " . $client->response_code() .
874          " " . $client->response_msg());
875      }
876
877      my $buf = "Hello, World!\n";
878      $conn->write($buf, length($buf));
879      $conn->close();
880
881      $resp_code = $client->response_code();
882      $resp_msg = $client->response_msg();
883
884      my $expected;
885
886      $expected = 226;
887      $self->assert($expected == $resp_code,
888        test_msg("Expected $expected, got $resp_code"));
889
890      $expected = "Transfer complete";
891      $self->assert($expected eq $resp_msg,
892        test_msg("Expected '$expected', got '$resp_msg'"));
893    };
894
895    if ($@) {
896      $ex = $@;
897    }
898
899    $wfh->print("done\n");
900    $wfh->flush();
901
902  } else {
903    eval { server_wait($config_file, $rfh) };
904    if ($@) {
905      warn($@);
906      exit 1;
907    }
908
909    exit 0;
910  }
911
912  # Stop server
913  server_stop($pid_file);
914
915  $self->assert_child_ok($pid);
916
917  if ($ex) {
918    test_append_logfile($log_file, $ex);
919    unlink($log_file);
920
921    die($ex);
922  }
923
924  unlink($log_file);
925}
926
927sub limits_commands_comma_delimited_deferred_paths_bug3147 {
928  my $self = shift;
929  my $tmpdir = $self->{tmpdir};
930
931  # This config is from:
932  #
933  #  http://forums.proftpd.org/smf/index.php/topic,3648.0
934  #  http://bugs.proftpd.org/show_bug.cgi?id=3147
935
936  my $config_file = "$tmpdir/dir.conf";
937  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
938  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
939
940  my $log_file = test_get_logfile();
941
942  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
943  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
944
945  my $user = 'proftpd';
946  my $passwd = 'test';
947  my $group = 'ftpd';
948  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
949  my $uid = 500;
950  my $gid = 500;
951
952  mkpath($home_dir);
953
954  my $sub_dir = File::Spec->rel2abs("$tmpdir/home/$user/upload");
955  mkpath($sub_dir);
956
957  # Make sure that, if we're running as root, that the home directory has
958  # permissions/privs set for the account we create
959  if ($< == 0) {
960    unless (chmod(0755, $home_dir, $sub_dir)) {
961      die("Can't set perms on $home_dir, $sub_dir to 0755: $!");
962    }
963
964    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
965      die("Can't set owner of $home_dir, $sub_dir to $uid/$gid: $!");
966    }
967  }
968
969  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
970    '/bin/bash');
971  auth_group_write($auth_group_file, $group, $gid, $user);
972
973  my $config = {
974    PidFile => $pid_file,
975    ScoreboardFile => $scoreboard_file,
976    SystemLog => $log_file,
977    TraceLog => $log_file,
978    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
979
980    AuthUserFile => $auth_user_file,
981    AuthGroupFile => $auth_group_file,
982
983    DefaultChdir => '~',
984
985    Directory => {
986      "~$user" => {
987        Limit => {
988          'WRITE' => {
989            DenyAll => '',
990          },
991        },
992      },
993
994      # Bug#3147 happened because proftpd's parsing of the list of commands
995      # in the <Limit> section was not properly handling commas after the
996      # command names.
997
998      "~$user/upload" => {
999        Limit => {
1000          'STOR,APPE,MKD,RMD,RNTO,STOU,XMKD,XRMD' => {
1001            AllowAll => '',
1002          },
1003        },
1004      },
1005    },
1006
1007    IfModules => {
1008      'mod_delay.c' => {
1009        DelayEngine => 'off',
1010      },
1011    },
1012  };
1013
1014  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1015
1016  # Open pipes, for use between the parent and child processes.  Specifically,
1017  # the child will indicate when it's done with its test by writing a message
1018  # to the parent.
1019  my ($rfh, $wfh);
1020  unless (pipe($rfh, $wfh)) {
1021    die("Can't open pipe: $!");
1022  }
1023
1024  my $ex;
1025
1026  # Fork child
1027  $self->handle_sigchld();
1028  defined(my $pid = fork()) or die("Can't fork: $!");
1029  if ($pid) {
1030    eval {
1031      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1032      $client->login($user, $passwd);
1033
1034      my ($resp_code, $resp_msg);
1035
1036      my $conn = $client->stor_raw('upload/test.txt');
1037      unless ($conn) {
1038        die("Failed to STOR upload/test.txt: " . $client->response_code() .
1039          " " . $client->response_msg());
1040      }
1041
1042      my $buf = "Hello, World!\n";
1043      $conn->write($buf, length($buf));
1044      $conn->close();
1045
1046      $resp_code = $client->response_code();
1047      $resp_msg = $client->response_msg();
1048
1049      my $expected;
1050
1051      $expected = 226;
1052      $self->assert($expected == $resp_code,
1053        test_msg("Expected $expected, got $resp_code"));
1054
1055      $expected = "Transfer complete";
1056      $self->assert($expected eq $resp_msg,
1057        test_msg("Expected '$expected', got '$resp_msg'"));
1058    };
1059
1060    if ($@) {
1061      $ex = $@;
1062    }
1063
1064    $wfh->print("done\n");
1065    $wfh->flush();
1066
1067  } else {
1068    eval { server_wait($config_file, $rfh) };
1069    if ($@) {
1070      warn($@);
1071      exit 1;
1072    }
1073
1074    exit 0;
1075  }
1076
1077  # Stop server
1078  server_stop($pid_file);
1079
1080  $self->assert_child_ok($pid);
1081
1082  if ($ex) {
1083    test_append_logfile($log_file, $ex);
1084    unlink($log_file);
1085
1086    die($ex);
1087  }
1088
1089  unlink($log_file);
1090}
1091
1092sub limits_commands_no_commas_deferred_paths_bug3147 {
1093  my $self = shift;
1094  my $tmpdir = $self->{tmpdir};
1095
1096  # This config is from:
1097  #
1098  #  http://forums.proftpd.org/smf/index.php/topic,3648.0
1099  #  http://bugs.proftpd.org/show_bug.cgi?id=3147
1100
1101  my $config_file = "$tmpdir/dir.conf";
1102  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1103  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1104
1105  my $log_file = test_get_logfile();
1106
1107  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1108  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1109
1110  my $user = 'proftpd';
1111  my $passwd = 'test';
1112  my $group = 'ftpd';
1113  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
1114  my $uid = 500;
1115  my $gid = 500;
1116
1117  mkpath($home_dir);
1118
1119  my $sub_dir = File::Spec->rel2abs("$tmpdir/home/$user/upload");
1120  mkpath($sub_dir);
1121
1122  # Make sure that, if we're running as root, that the home directory has
1123  # permissions/privs set for the account we create
1124  if ($< == 0) {
1125    unless (chmod(0755, $home_dir, $sub_dir)) {
1126      die("Can't set perms on $home_dir, $sub_dir to 0755: $!");
1127    }
1128
1129    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
1130      die("Can't set owner of $home_dir, $sub_dir to $uid/$gid: $!");
1131    }
1132  }
1133
1134  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1135    '/bin/bash');
1136  auth_group_write($auth_group_file, $group, $gid, $user);
1137
1138  my $config = {
1139    PidFile => $pid_file,
1140    ScoreboardFile => $scoreboard_file,
1141    SystemLog => $log_file,
1142    TraceLog => $log_file,
1143    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1144
1145    AuthUserFile => $auth_user_file,
1146    AuthGroupFile => $auth_group_file,
1147
1148    DefaultChdir => '~',
1149
1150    Directory => {
1151      "~$user" => {
1152        Limit => {
1153          'WRITE' => {
1154            DenyAll => '',
1155          },
1156        },
1157      },
1158
1159      # Bug#3147 happened because proftpd's parsing of the list of commands
1160      # in the <Limit> section was not properly handling commas after the
1161      # command names.  Make sure the fix for Bug#3147 doesn't break command
1162      # lists which DO NOT use commas.
1163
1164      "~$user/upload" => {
1165        Limit => {
1166          'STOR APPE MKD RMD RNTO STOU XMKD XRMD' => {
1167            AllowAll => '',
1168          },
1169        },
1170      },
1171    },
1172
1173    IfModules => {
1174      'mod_delay.c' => {
1175        DelayEngine => 'off',
1176      },
1177    },
1178  };
1179
1180  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1181
1182  # Open pipes, for use between the parent and child processes.  Specifically,
1183  # the child will indicate when it's done with its test by writing a message
1184  # to the parent.
1185  my ($rfh, $wfh);
1186  unless (pipe($rfh, $wfh)) {
1187    die("Can't open pipe: $!");
1188  }
1189
1190  my $ex;
1191
1192  # Fork child
1193  $self->handle_sigchld();
1194  defined(my $pid = fork()) or die("Can't fork: $!");
1195  if ($pid) {
1196    eval {
1197      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1198      $client->login($user, $passwd);
1199
1200      my ($resp_code, $resp_msg);
1201
1202      my $conn = $client->stor_raw('upload/test.txt');
1203      unless ($conn) {
1204        die("Failed to STOR upload/test.txt: " . $client->response_code() .
1205          " " . $client->response_msg());
1206      }
1207
1208      my $buf = "Hello, World!\n";
1209      $conn->write($buf, length($buf));
1210      $conn->close();
1211
1212      $resp_code = $client->response_code();
1213      $resp_msg = $client->response_msg();
1214
1215      my $expected;
1216
1217      $expected = 226;
1218      $self->assert($expected == $resp_code,
1219        test_msg("Expected $expected, got $resp_code"));
1220
1221      $expected = "Transfer complete";
1222      $self->assert($expected eq $resp_msg,
1223        test_msg("Expected '$expected', got '$resp_msg'"));
1224    };
1225
1226    if ($@) {
1227      $ex = $@;
1228    }
1229
1230    $wfh->print("done\n");
1231    $wfh->flush();
1232
1233  } else {
1234    eval { server_wait($config_file, $rfh) };
1235    if ($@) {
1236      warn($@);
1237      exit 1;
1238    }
1239
1240    exit 0;
1241  }
1242
1243  # Stop server
1244  server_stop($pid_file);
1245
1246  $self->assert_child_ok($pid);
1247
1248  if ($ex) {
1249    test_append_logfile($log_file, $ex);
1250    unlink($log_file);
1251
1252    die($ex);
1253  }
1254
1255  unlink($log_file);
1256}
1257
1258sub limits_rename_dir_ok_write_denied {
1259  my $self = shift;
1260  my $tmpdir = $self->{tmpdir};
1261
1262  my $config_file = "$tmpdir/dir.conf";
1263  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1264  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1265
1266  my $log_file = test_get_logfile();
1267
1268  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1269  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1270
1271  my $user = 'proftpd';
1272  my $passwd = 'test';
1273  my $group = 'ftpd';
1274  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
1275  my $uid = 500;
1276  my $gid = 500;
1277
1278  mkpath($home_dir);
1279
1280  my $sub_dir = File::Spec->rel2abs("$tmpdir/home/$user/upload");
1281  mkpath($sub_dir);
1282
1283  # Make sure that, if we're running as root, that the home directory has
1284  # permissions/privs set for the account we create
1285  if ($< == 0) {
1286    unless (chmod(0755, $home_dir, $sub_dir)) {
1287      die("Can't set perms on $home_dir, $sub_dir to 0755: $!");
1288    }
1289
1290    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
1291      die("Can't set owner of $home_dir, $sub_dir to $uid/$gid: $!");
1292    }
1293  }
1294
1295  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1296    '/bin/bash');
1297  auth_group_write($auth_group_file, $group, $gid, $user);
1298
1299  my $config = {
1300    PidFile => $pid_file,
1301    ScoreboardFile => $scoreboard_file,
1302    SystemLog => $log_file,
1303    TraceLog => $log_file,
1304    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1305
1306    AuthUserFile => $auth_user_file,
1307    AuthGroupFile => $auth_group_file,
1308
1309    DefaultChdir => '~',
1310
1311    Directory => {
1312      "~$user/*" => {
1313        Limit => {
1314          'CWD XCWD RNFR RNTO' => {
1315            AllowAll => '',
1316          },
1317
1318          'ALL' => {
1319            DenyAll => '',
1320          },
1321        },
1322      },
1323
1324      "~$user/upload" => {
1325        Limit => {
1326          'WRITE' => {
1327            DenyAll => '',
1328          },
1329        },
1330      },
1331    },
1332
1333    IfModules => {
1334      'mod_delay.c' => {
1335        DelayEngine => 'off',
1336      },
1337    },
1338  };
1339
1340  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1341
1342  # Open pipes, for use between the parent and child processes.  Specifically,
1343  # the child will indicate when it's done with its test by writing a message
1344  # to the parent.
1345  my ($rfh, $wfh);
1346  unless (pipe($rfh, $wfh)) {
1347    die("Can't open pipe: $!");
1348  }
1349
1350  my $ex;
1351
1352  # Fork child
1353  $self->handle_sigchld();
1354  defined(my $pid = fork()) or die("Can't fork: $!");
1355  if ($pid) {
1356    eval {
1357      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1358      $client->login($user, $passwd);
1359
1360      my ($resp_code, $resp_msg);
1361
1362      ($resp_code, $resp_msg) = $client->rnfr('upload');
1363
1364      my $expected;
1365
1366      $expected = 350;
1367      $self->assert($expected == $resp_code,
1368        test_msg("Expected $expected, got $resp_code"));
1369
1370      $expected = "File or directory exists, ready for destination name";
1371      $self->assert($expected eq $resp_msg,
1372        test_msg("Expected '$expected', got '$resp_msg'"));
1373
1374      ($resp_code, $resp_msg) = $client->rnto('upload2');
1375
1376      $expected = 250;
1377      $self->assert($expected == $resp_code,
1378        test_msg("Expected $expected, got $resp_code"));
1379
1380      $expected = "Rename successful";
1381      $self->assert($expected eq $resp_msg,
1382        test_msg("Expected '$expected', got '$resp_msg'"));
1383    };
1384
1385    if ($@) {
1386      $ex = $@;
1387    }
1388
1389    $wfh->print("done\n");
1390    $wfh->flush();
1391
1392  } else {
1393    eval { server_wait($config_file, $rfh) };
1394    if ($@) {
1395      warn($@);
1396      exit 1;
1397    }
1398
1399    exit 0;
1400  }
1401
1402  # Stop server
1403  server_stop($pid_file);
1404
1405  $self->assert_child_ok($pid);
1406
1407  if ($ex) {
1408    test_append_logfile($log_file, $ex);
1409    unlink($log_file);
1410
1411    die($ex);
1412  }
1413
1414  unlink($log_file);
1415}
1416
1417sub limits_rename_dir_failed_rnfr_denied {
1418  my $self = shift;
1419  my $tmpdir = $self->{tmpdir};
1420
1421  my $config_file = "$tmpdir/dir.conf";
1422  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1423  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1424
1425  my $log_file = test_get_logfile();
1426
1427  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1428  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1429
1430  my $user = 'proftpd';
1431  my $passwd = 'test';
1432  my $group = 'ftpd';
1433  my $home_dir = File::Spec->rel2abs("$tmpdir/home/$user");
1434  my $uid = 500;
1435  my $gid = 500;
1436
1437  mkpath($home_dir);
1438
1439  my $sub_dir = File::Spec->rel2abs("$tmpdir/home/$user/upload");
1440  mkpath($sub_dir);
1441
1442  # Make sure that, if we're running as root, that the home directory has
1443  # permissions/privs set for the account we create
1444  if ($< == 0) {
1445    unless (chmod(0755, $home_dir, $sub_dir)) {
1446      die("Can't set perms on $home_dir, $sub_dir to 0755: $!");
1447    }
1448
1449    unless (chown($uid, $gid, $home_dir, $sub_dir)) {
1450      die("Can't set owner of $home_dir, $sub_dir to $uid/$gid: $!");
1451    }
1452  }
1453
1454  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1455    '/bin/bash');
1456  auth_group_write($auth_group_file, $group, $gid, $user);
1457
1458  my $config = {
1459    PidFile => $pid_file,
1460    ScoreboardFile => $scoreboard_file,
1461    SystemLog => $log_file,
1462    TraceLog => $log_file,
1463    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1464
1465    AuthUserFile => $auth_user_file,
1466    AuthGroupFile => $auth_group_file,
1467
1468    DefaultChdir => '~',
1469
1470    Directory => {
1471      "~$user/*" => {
1472        Limit => {
1473          'CWD XCWD RNFR RNTO' => {
1474            AllowAll => '',
1475          },
1476
1477          'ALL' => {
1478            DenyAll => '',
1479          },
1480        },
1481      },
1482
1483      "~$user/upload" => {
1484        Limit => {
1485          'RNFR' => {
1486            DenyAll => '',
1487          },
1488        },
1489      },
1490    },
1491
1492    IfModules => {
1493      'mod_delay.c' => {
1494        DelayEngine => 'off',
1495      },
1496    },
1497  };
1498
1499  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1500
1501  # Open pipes, for use between the parent and child processes.  Specifically,
1502  # the child will indicate when it's done with its test by writing a message
1503  # to the parent.
1504  my ($rfh, $wfh);
1505  unless (pipe($rfh, $wfh)) {
1506    die("Can't open pipe: $!");
1507  }
1508
1509  my $ex;
1510
1511  # Fork child
1512  $self->handle_sigchld();
1513  defined(my $pid = fork()) or die("Can't fork: $!");
1514  if ($pid) {
1515    eval {
1516      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1517      $client->login($user, $passwd);
1518
1519      my ($resp_code, $resp_msg);
1520
1521      eval { $client->rnfr('upload') };
1522      unless ($@) {
1523        die("RNFR upload succeeded unexpectedly");
1524
1525      } else {
1526        $resp_code = $client->response_code();
1527        $resp_msg = $client->response_msg();
1528      }
1529
1530      my $expected;
1531
1532      $expected = 550;
1533      $self->assert($expected == $resp_code,
1534        test_msg("Expected $expected, got $resp_code"));
1535
1536      $expected = "upload: Operation not permitted";
1537      $self->assert($expected eq $resp_msg,
1538        test_msg("Expected '$expected', got '$resp_msg'"));
1539    };
1540
1541    if ($@) {
1542      $ex = $@;
1543    }
1544
1545    $wfh->print("done\n");
1546    $wfh->flush();
1547
1548  } else {
1549    eval { server_wait($config_file, $rfh) };
1550    if ($@) {
1551      warn($@);
1552      exit 1;
1553    }
1554
1555    exit 0;
1556  }
1557
1558  # Stop server
1559  server_stop($pid_file);
1560
1561  $self->assert_child_ok($pid);
1562
1563  if ($ex) {
1564    test_append_logfile($log_file, $ex);
1565    unlink($log_file);
1566
1567    die($ex);
1568  }
1569
1570  unlink($log_file);
1571}
1572
1573sub limits_one_char_dir_bug3337 {
1574  my $self = shift;
1575  my $tmpdir = $self->{tmpdir};
1576
1577  my $config_file = "$tmpdir/dir.conf";
1578  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1579  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1580
1581  my $log_file = test_get_logfile();
1582
1583  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1584  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1585
1586  my $user = 'proftpd';
1587  my $passwd = 'test';
1588  my $group = 'ftpd';
1589  my $home_dir = File::Spec->rel2abs($tmpdir);
1590  my $uid = 500;
1591  my $gid = 500;
1592
1593  my $a_dir = File::Spec->rel2abs("$home_dir/a");
1594  mkpath($a_dir);
1595
1596  my $ab_dir = File::Spec->rel2abs("$home_dir/ab");
1597  mkpath($ab_dir);
1598
1599  # Make sure that, if we're running as root, that the home directory has
1600  # permissions/privs set for the account we create
1601  if ($< == 0) {
1602    unless (chmod(0755, $home_dir, $a_dir, $ab_dir)) {
1603      die("Can't set perms on $home_dir to 0755: $!");
1604    }
1605
1606    unless (chown($uid, $gid, $home_dir, $a_dir, $ab_dir)) {
1607      die("Can't set owner of $home_dir to $uid/$gid: $!");
1608    }
1609  }
1610
1611  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1612    '/bin/bash');
1613  auth_group_write($auth_group_file, $group, $gid, $user);
1614
1615  # Reported in
1616  #
1617  #  http://forums.proftpd.org/smf/index.php/topic,4170.0.html
1618
1619  my $config = {
1620    PidFile => $pid_file,
1621    ScoreboardFile => $scoreboard_file,
1622    SystemLog => $log_file,
1623    TraceLog => $log_file,
1624    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1625
1626    AuthUserFile => $auth_user_file,
1627    AuthGroupFile => $auth_group_file,
1628    DefaultChdir => '~',
1629
1630    Directory => {
1631      "$home_dir" => {
1632        Limit => {
1633          WRITE => {
1634            DenyUser => $user,
1635          },
1636        },
1637      },
1638
1639      "$a_dir" => {
1640        Limit => {
1641          WRITE => {
1642            AllowUser => $user,
1643          },
1644        },
1645      },
1646
1647      "$ab_dir" => {
1648        Limit => {
1649          WRITE => {
1650            AllowUser => $user,
1651          },
1652        },
1653      },
1654    },
1655
1656    IfModules => {
1657      'mod_delay.c' => {
1658        DelayEngine => 'off',
1659      },
1660    },
1661  };
1662
1663  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1664
1665  # Open pipes, for use between the parent and child processes.  Specifically,
1666  # the child will indicate when it's done with its test by writing a message
1667  # to the parent.
1668  my ($rfh, $wfh);
1669  unless (pipe($rfh, $wfh)) {
1670    die("Can't open pipe: $!");
1671  }
1672
1673  my $ex;
1674
1675  # Fork child
1676  $self->handle_sigchld();
1677  defined(my $pid = fork()) or die("Can't fork: $!");
1678  if ($pid) {
1679    eval {
1680      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1681      $client->login($user, $passwd);
1682
1683      $client->cwd('ab');
1684      my $conn = $client->stor_raw('test.txt');
1685      unless ($conn) {
1686        die("STOR test.txt failed: " . $client->response_code() . " " .
1687          $client->response_msg());
1688      }
1689
1690      $conn->close();
1691
1692      my $resp_code = $client->response_code();
1693      my $resp_msg = $client->response_msg();
1694
1695      my $expected;
1696
1697      $expected = 226;
1698      $self->assert($expected == $resp_code,
1699        test_msg("Expected $expected, got $resp_code"));
1700
1701      $expected = "Transfer complete";
1702      $self->assert($expected eq $resp_msg,
1703        test_msg("Expected '$expected', got '$resp_msg'"));
1704
1705      $client->cwd('../a');
1706      $conn = $client->stor_raw('test.txt');
1707      unless ($conn) {
1708        die("STOR test.txt failed: " . $client->response_code() . " " .
1709          $client->response_msg());
1710      }
1711
1712      $conn->close();
1713
1714      $resp_code = $client->response_code();
1715      $resp_msg = $client->response_msg();
1716
1717      $expected = 226;
1718      $self->assert($expected == $resp_code,
1719        test_msg("Expected $expected, got $resp_code"));
1720
1721      $expected = "Transfer complete";
1722      $self->assert($expected eq $resp_msg,
1723        test_msg("Expected '$expected', got '$resp_msg'"));
1724    };
1725
1726    if ($@) {
1727      $ex = $@;
1728    }
1729
1730    $wfh->print("done\n");
1731    $wfh->flush();
1732
1733  } else {
1734    eval { server_wait($config_file, $rfh) };
1735    if ($@) {
1736      warn($@);
1737      exit 1;
1738    }
1739
1740    exit 0;
1741  }
1742
1743  # Stop server
1744  server_stop($pid_file);
1745
1746  $self->assert_child_ok($pid);
1747
1748  if ($ex) {
1749    test_append_logfile($log_file, $ex);
1750    unlink($log_file);
1751
1752    die($ex);
1753  }
1754
1755  unlink($log_file);
1756}
1757
1758sub limits_symlink_dir_bug3166 {
1759  my $self = shift;
1760  my $tmpdir = $self->{tmpdir};
1761
1762  my $config_file = "$tmpdir/dir.conf";
1763  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1764  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1765
1766  my $log_file = test_get_logfile();
1767
1768  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1769  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1770
1771  my $user = 'proftpd';
1772  my $passwd = 'test';
1773  my $group = 'ftpd';
1774  my $home_dir = File::Spec->rel2abs($tmpdir);
1775  my $uid = 500;
1776  my $gid = 500;
1777
1778  my $src_dir = File::Spec->rel2abs("$tmpdir/dispatch/$user");
1779  mkpath($src_dir);
1780
1781  my $dst_dir = File::Spec->rel2abs("$tmpdir/writable/dir1");
1782  mkpath($dst_dir);
1783
1784  # Change to the $src_dir in order to create a symlink with a relative
1785  # path (so that it will work in the chroot).
1786
1787  my $cwd = getcwd();
1788  unless (chdir($src_dir)) {
1789    die("Can't chdir to $src_dir: $!");
1790  }
1791
1792  unless (symlink("../../writable/dir1", "dir1")) {
1793    die("Can't symlink '../../writable/dir1' to 'dir1': $!");
1794  }
1795
1796  unless (chdir($cwd)) {
1797    die("Can't chdir to $cwd: $!");
1798  }
1799
1800  # Make sure that, if we're running as root, that the home directory has
1801  # permissions/privs set for the account we create
1802  if ($< == 0) {
1803    unless (chmod(0755, $home_dir, $src_dir, $dst_dir)) {
1804      die("Can't set perms on $home_dir to 0755: $!");
1805    }
1806
1807    unless (chown($uid, $gid, $home_dir, $src_dir, $dst_dir)) {
1808      die("Can't set owner of $home_dir to $uid/$gid: $!");
1809    }
1810  }
1811
1812  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
1813    '/bin/bash');
1814  auth_group_write($auth_group_file, $group, $gid, $user);
1815
1816  my $config = {
1817    PidFile => $pid_file,
1818    ScoreboardFile => $scoreboard_file,
1819    SystemLog => $log_file,
1820    TraceLog => $log_file,
1821    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1822
1823    AuthUserFile => $auth_user_file,
1824    AuthGroupFile => $auth_group_file,
1825    DefaultRoot => '~',
1826
1827    IfModules => {
1828      'mod_delay.c' => {
1829        DelayEngine => 'off',
1830      },
1831    },
1832  };
1833
1834  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1835
1836  if (open(my $fh, ">> $config_file")) {
1837    print $fh <<EOD;
1838<Directory $src_dir>
1839  <Limit ALL>
1840    Order allow, deny
1841    AllowUser $user
1842    DenyAll
1843  </Limit>
1844</Directory>
1845
1846<Directory $home_dir/writable>
1847  <Limit ALL>
1848    Order allow, deny
1849    AllowUser admin
1850    DenyAll
1851  </Limit>
1852
1853  <Limit DIRS PORT PASV EPRT EPSV>
1854    Order allow, deny
1855    AllowUser admin
1856    AllowUser test
1857    DenyAll
1858  </Limit>
1859</Directory>
1860
1861<Directory $dst_dir>
1862
1863  # Bug#3166 was caused by using just ALL in this limit; the parent
1864  # directory's limit included the DIRS command group, which took precedence
1865  # over ALL.  By adding DIRS to the limit (which includes the test user),
1866  # the desired behavior is achieved.
1867
1868  <Limit ALL DIRS>
1869    Order allow, deny
1870    AllowUser admin
1871    AllowUser $user
1872    DenyAll
1873  </Limit>
1874</Directory>
1875EOD
1876    close($fh);
1877
1878  } else {
1879    die("Can't read $config_file: $!");
1880  }
1881
1882  # Open pipes, for use between the parent and child processes.  Specifically,
1883  # the child will indicate when it's done with its test by writing a message
1884  # to the parent.
1885  my ($rfh, $wfh);
1886  unless (pipe($rfh, $wfh)) {
1887    die("Can't open pipe: $!");
1888  }
1889
1890  my $ex;
1891
1892  # Fork child
1893  $self->handle_sigchld();
1894  defined(my $pid = fork()) or die("Can't fork: $!");
1895  if ($pid) {
1896    eval {
1897      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1898      $client->login($user, $passwd);
1899
1900      $client->cwd('dispatch');
1901      $client->nlst();
1902
1903      $client->cwd($user);
1904      $client->nlst();
1905
1906      $client->cwd('dir1');
1907
1908      my ($resp_code, $resp_msg) = $client->pwd();
1909
1910      my $expected;
1911
1912      $expected = 257;
1913      $self->assert($expected == $resp_code,
1914        test_msg("Expected $expected, got $resp_code"));
1915
1916      $expected = '"/writable/dir1" is the current directory';
1917      $self->assert($expected eq $resp_msg,
1918        test_msg("Expected '$expected', got '$resp_msg'"));
1919    };
1920
1921    if ($@) {
1922      $ex = $@;
1923    }
1924
1925    $wfh->print("done\n");
1926    $wfh->flush();
1927
1928  } else {
1929    eval { server_wait($config_file, $rfh) };
1930    if ($@) {
1931      warn($@);
1932      exit 1;
1933    }
1934
1935    exit 0;
1936  }
1937
1938  # Stop server
1939  server_stop($pid_file);
1940
1941  $self->assert_child_ok($pid);
1942
1943  if ($ex) {
1944    test_append_logfile($log_file, $ex);
1945    unlink($log_file);
1946
1947    die($ex);
1948  }
1949
1950  unlink($log_file);
1951}
1952
1953sub limits_anon_dir_abs_path_bug3283 {
1954  my $self = shift;
1955  my $tmpdir = $self->{tmpdir};
1956
1957  my $config_file = "$tmpdir/dir.conf";
1958  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
1959  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
1960
1961  my $log_file = test_get_logfile();
1962
1963  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
1964  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
1965
1966  my ($user, $group) = config_get_identity();
1967
1968  my $passwd = 'test';
1969  my $anon_dir = File::Spec->rel2abs($tmpdir);
1970  my $uid = 500;
1971  my $gid = 500;
1972
1973  my $hide_dir = File::Spec->rel2abs("$tmpdir/hide");
1974  mkpath($hide_dir);
1975
1976  # Make sure that, if we're running as root, that the home directory has
1977  # permissions/privs set for the account we create
1978  if ($< == 0) {
1979    unless (chmod(0755, $anon_dir, $hide_dir)) {
1980      die("Can't set perms on $anon_dir to 0755: $!");
1981    }
1982
1983    unless (chown($uid, $gid, $anon_dir, $hide_dir)) {
1984      die("Can't set owner of $anon_dir to $uid/$gid: $!");
1985    }
1986  }
1987
1988  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, '/tmp',
1989    '/bin/bash');
1990  auth_group_write($auth_group_file, $group, $gid, $user);
1991
1992  my $config = {
1993    PidFile => $pid_file,
1994    ScoreboardFile => $scoreboard_file,
1995    SystemLog => $log_file,
1996    TraceLog => $log_file,
1997    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
1998
1999    AuthUserFile => $auth_user_file,
2000    AuthGroupFile => $auth_group_file,
2001
2002    IfModules => {
2003      'mod_delay.c' => {
2004        DelayEngine => 'off',
2005      },
2006    },
2007  };
2008
2009  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2010
2011  if (open(my $fh, ">> $config_file")) {
2012    print $fh <<EOD;
2013<Anonymous $anon_dir>
2014  User $user
2015  Group $group
2016  UserAlias anonymous $user
2017  RequireValidShell off
2018
2019  <Directory $hide_dir>
2020    <Limit ALL>
2021      DenyAll
2022    </Limit>
2023  </Directory>
2024</Anonymous>
2025EOD
2026    close($fh);
2027
2028  } else {
2029    die("Can't read $config_file: $!");
2030  }
2031
2032  # Open pipes, for use between the parent and child processes.  Specifically,
2033  # the child will indicate when it's done with its test by writing a message
2034  # to the parent.
2035  my ($rfh, $wfh);
2036  unless (pipe($rfh, $wfh)) {
2037    die("Can't open pipe: $!");
2038  }
2039
2040  my $ex;
2041
2042  # Fork child
2043  $self->handle_sigchld();
2044  defined(my $pid = fork()) or die("Can't fork: $!");
2045  if ($pid) {
2046    eval {
2047      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2048      $client->login($user, $passwd);
2049
2050      my $conn = $client->list_raw();
2051      unless ($conn) {
2052        die("LIST failed: " . $client->response_code() . " " .
2053          $client->response_msg());
2054      }
2055
2056      my $buf;
2057      $conn->read($buf, 32768, 30);
2058      $conn->close();
2059
2060      my $lines = [split(/\n/, $buf)];
2061
2062      foreach my $line (@$lines) {
2063        if ($line =~ /\s+(\S+)$/) {
2064          my $dir_name = $1;
2065
2066          # The <Directory> with <Limit ALL>DenyAll should not appear in
2067          # the returned list
2068
2069          my $unexpected = 'hide';
2070          $self->assert($unexpected ne $dir_name,
2071            test_msg("Unexpectedly saw '$dir_name' in LIST"));
2072        }
2073      }
2074
2075      $client->quit();
2076    };
2077
2078    if ($@) {
2079      $ex = $@;
2080    }
2081
2082    $wfh->print("done\n");
2083    $wfh->flush();
2084
2085  } else {
2086    eval { server_wait($config_file, $rfh) };
2087    if ($@) {
2088      warn($@);
2089      exit 1;
2090    }
2091
2092    exit 0;
2093  }
2094
2095  # Stop server
2096  server_stop($pid_file);
2097
2098  $self->assert_child_ok($pid);
2099
2100  if ($ex) {
2101    test_append_logfile($log_file, $ex);
2102    unlink($log_file);
2103
2104    die($ex);
2105  }
2106
2107  unlink($log_file);
2108}
2109
2110sub limits_with_multi_globs_denied_delete {
2111  my $self = shift;
2112  my $tmpdir = $self->{tmpdir};
2113
2114  # This config is from:
2115  #
2116  #  http://forums.proftpd.org/smf/index.php/topic,6807.0.html
2117
2118  my $config_file = "$tmpdir/dir.conf";
2119  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
2120  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
2121
2122  my $log_file = test_get_logfile();
2123
2124  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
2125  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
2126
2127  my $user = 'proftpd';
2128  my $passwd = 'test';
2129  my $group = 'ftpd';
2130  my $users_dir = File::Spec->rel2abs("$tmpdir/home/users");
2131  my $home_dir = File::Spec->rel2abs("$users_dir/$user");
2132  my $uid = 500;
2133  my $gid = 500;
2134
2135  mkpath($home_dir);
2136
2137  my $test_file = File::Spec->rel2abs("$home_dir/hiddenfile_foo.txt");
2138  if (open(my $fh, "> $test_file")) {
2139    print $fh "Hello, World!\n";
2140
2141    unless (close($fh)) {
2142      die("Can't write $test_file: $!");
2143    }
2144
2145  } else {
2146    die("Can't open $test_file: $!");
2147  }
2148
2149  # Make sure that, if we're running as root, that the home directory has
2150  # permissions/privs set for the account we create
2151  if ($< == 0) {
2152    unless (chmod(0755, $home_dir)) {
2153      die("Can't set perms on $home_dir to 0755: $!");
2154    }
2155
2156    unless (chown($uid, $gid, $home_dir)) {
2157      die("Can't set owner of $home_dir to $uid/$gid: $!");
2158    }
2159  }
2160
2161  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2162    '/bin/bash');
2163  auth_group_write($auth_group_file, $group, $gid, $user);
2164
2165  my $abs_tmp_dir = File::Spec->rel2abs($tmpdir);
2166
2167  my $glob_expr = "$users_dir/*/hiddenfile_*";
2168  if ($^O eq 'darwin') {
2169    # MacOSX-specific hack/tweak
2170    $glob_expr = '/private' . $glob_expr;
2171  }
2172
2173  my $config = {
2174    PidFile => $pid_file,
2175    ScoreboardFile => $scoreboard_file,
2176    SystemLog => $log_file,
2177    TraceLog => $log_file,
2178    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
2179
2180    AuthUserFile => $auth_user_file,
2181    AuthGroupFile => $auth_group_file,
2182
2183    DefaultChdir => '~',
2184
2185    Directory => {
2186      $glob_expr => {
2187        Limit => {
2188          'ALL' => {
2189            DenyAll => '',
2190          },
2191        },
2192      },
2193    },
2194
2195    IfModules => {
2196      'mod_delay.c' => {
2197        DelayEngine => 'off',
2198      },
2199    },
2200  };
2201
2202  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2203
2204  # Open pipes, for use between the parent and child processes.  Specifically,
2205  # the child will indicate when it's done with its test by writing a message
2206  # to the parent.
2207  my ($rfh, $wfh);
2208  unless (pipe($rfh, $wfh)) {
2209    die("Can't open pipe: $!");
2210  }
2211
2212  my $ex;
2213
2214  # Fork child
2215  $self->handle_sigchld();
2216  defined(my $pid = fork()) or die("Can't fork: $!");
2217  if ($pid) {
2218    eval {
2219      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2220      $client->login($user, $passwd);
2221
2222      my $filename = 'hiddenfile_foo.txt';
2223      eval { $client->dele($filename) };
2224      unless ($@) {
2225        die("DELE succeeded unexpectedly");
2226      }
2227
2228      my $resp_code = $client->response_code();
2229      my $resp_msg = $client->response_msg();
2230
2231      my $expected;
2232
2233      $expected = 550;
2234      $self->assert($expected == $resp_code,
2235        test_msg("Expected $expected, got $resp_code"));
2236
2237      $expected = "$filename: Operation not permitted";
2238      $self->assert($expected eq $resp_msg,
2239        test_msg("Expected '$expected', got '$resp_msg'"));
2240    };
2241
2242    if ($@) {
2243      $ex = $@;
2244    }
2245
2246    $wfh->print("done\n");
2247    $wfh->flush();
2248
2249  } else {
2250    eval { server_wait($config_file, $rfh) };
2251    if ($@) {
2252      warn($@);
2253      exit 1;
2254    }
2255
2256    exit 0;
2257  }
2258
2259  # Stop server
2260  server_stop($pid_file);
2261
2262  $self->assert_child_ok($pid);
2263
2264  if ($ex) {
2265    test_append_logfile($log_file, $ex);
2266    unlink($log_file);
2267
2268    die($ex);
2269  }
2270
2271  unlink($log_file);
2272}
2273
2274sub limits_retr_bug3915 {
2275  my $self = shift;
2276  my $tmpdir = $self->{tmpdir};
2277
2278  my $config_file = "$tmpdir/dir.conf";
2279  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
2280  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
2281
2282  my $log_file = test_get_logfile();
2283
2284  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
2285  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
2286
2287  my $user = 'proftpd';
2288  my $passwd = 'test';
2289  my $group = 'ftpd';
2290  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
2291  my $uid = 500;
2292  my $gid = 500;
2293
2294  mkpath($home_dir);
2295
2296  # Make sure that, if we're running as root, that the home directory has
2297  # permissions/privs set for the account we create
2298  if ($< == 0) {
2299    unless (chmod(0755, $home_dir)) {
2300      die("Can't set perms on $home_dir to 0755: $!");
2301    }
2302
2303    unless (chown($uid, $gid, $home_dir)) {
2304      die("Can't set owner of $home_dir to $uid/$gid: $!");
2305    }
2306  }
2307
2308  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2309    '/bin/bash');
2310  auth_group_write($auth_group_file, $group, $gid, $user);
2311
2312  my $test_file = File::Spec->rel2abs("$home_dir/test.txt");
2313  if (open(my $fh, "> $test_file")) {
2314    print $fh "Hello, World!\n";
2315
2316    unless (close($fh)) {
2317      die("Can't write $test_file: $!");
2318    }
2319
2320  } else {
2321    die("Can't open $test_file: $!");
2322  }
2323
2324  my $config = {
2325    PidFile => $pid_file,
2326    ScoreboardFile => $scoreboard_file,
2327    SystemLog => $log_file,
2328    TraceLog => $log_file,
2329    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
2330
2331    AuthUserFile => $auth_user_file,
2332    AuthGroupFile => $auth_group_file,
2333
2334    DefaultChdir => '~',
2335
2336    IfModules => {
2337      'mod_delay.c' => {
2338        DelayEngine => 'off',
2339      },
2340    },
2341  };
2342
2343  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2344
2345  if (open(my $fh, ">> $config_file")) {
2346    print $fh <<EOC;
2347<Directory ~$user>
2348  <Limit RETR>
2349    DenyAll
2350  </Limit>
2351</Directory>
2352EOC
2353    close($fh);
2354
2355  } else {
2356    die("Can't read $config_file: $!");
2357  }
2358
2359  # Open pipes, for use between the parent and child processes.  Specifically,
2360  # the child will indicate when it's done with its test by writing a message
2361  # to the parent.
2362  my ($rfh, $wfh);
2363  unless (pipe($rfh, $wfh)) {
2364    die("Can't open pipe: $!");
2365  }
2366
2367  my $ex;
2368
2369  # Fork child
2370  $self->handle_sigchld();
2371  defined(my $pid = fork()) or die("Can't fork: $!");
2372  if ($pid) {
2373    eval {
2374      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2375      $client->login($user, $passwd);
2376
2377      my ($resp_code, $resp_msg);
2378
2379      my $conn = $client->retr_raw('test.txt');
2380      if ($conn) {
2381        die("RETR test.txt succeeded unexpectedly");
2382      }
2383
2384      $resp_code = $client->response_code();
2385      $resp_msg = $client->response_msg();
2386
2387      my $expected;
2388
2389      $expected = 550;
2390      $self->assert($expected == $resp_code,
2391        test_msg("Expected response code $expected, got $resp_code"));
2392
2393      $expected = "test.txt: Operation not permitted";
2394      $self->assert($expected eq $resp_msg,
2395        test_msg("Expected response message '$expected', got '$resp_msg'"));
2396    };
2397
2398    if ($@) {
2399      $ex = $@;
2400    }
2401
2402    $wfh->print("done\n");
2403    $wfh->flush();
2404
2405  } else {
2406    eval { server_wait($config_file, $rfh) };
2407    if ($@) {
2408      warn($@);
2409      exit 1;
2410    }
2411
2412    exit 0;
2413  }
2414
2415  # Stop server
2416  server_stop($pid_file);
2417
2418  $self->assert_child_ok($pid);
2419
2420  if ($ex) {
2421    test_append_logfile($log_file, $ex);
2422    unlink($log_file);
2423
2424    die($ex);
2425  }
2426
2427  unlink($log_file);
2428}
2429
2430sub limits_retr_bug3915_chrooted {
2431  my $self = shift;
2432  my $tmpdir = $self->{tmpdir};
2433
2434  my $config_file = "$tmpdir/dir.conf";
2435  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
2436  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
2437
2438  my $log_file = test_get_logfile();
2439
2440  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
2441  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
2442
2443  my $user = 'proftpd';
2444  my $passwd = 'test';
2445  my $group = 'ftpd';
2446  my $home_dir = File::Spec->rel2abs("$tmpdir/home/users/$user");
2447  my $uid = 500;
2448  my $gid = 500;
2449
2450  mkpath($home_dir);
2451
2452  # Make sure that, if we're running as root, that the home directory has
2453  # permissions/privs set for the account we create
2454  if ($< == 0) {
2455    unless (chmod(0755, $home_dir)) {
2456      die("Can't set perms on $home_dir to 0755: $!");
2457    }
2458
2459    unless (chown($uid, $gid, $home_dir)) {
2460      die("Can't set owner of $home_dir to $uid/$gid: $!");
2461    }
2462  }
2463
2464  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2465    '/bin/bash');
2466  auth_group_write($auth_group_file, $group, $gid, $user);
2467
2468  my $test_file = File::Spec->rel2abs("$home_dir/test.txt");
2469  if (open(my $fh, "> $test_file")) {
2470    print $fh "Hello, World!\n";
2471
2472    unless (close($fh)) {
2473      die("Can't write $test_file: $!");
2474    }
2475
2476  } else {
2477    die("Can't open $test_file: $!");
2478  }
2479
2480  my $config = {
2481    PidFile => $pid_file,
2482    ScoreboardFile => $scoreboard_file,
2483    SystemLog => $log_file,
2484    TraceLog => $log_file,
2485    Trace => 'DEFAULT:10 fsio:0 directory:10 lock:0',
2486
2487    AuthUserFile => $auth_user_file,
2488    AuthGroupFile => $auth_group_file,
2489
2490    DefaultRoot => '~',
2491
2492    IfModules => {
2493      'mod_delay.c' => {
2494        DelayEngine => 'off',
2495      },
2496    },
2497  };
2498
2499  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2500
2501  if (open(my $fh, ">> $config_file")) {
2502    print $fh <<EOC;
2503<Directory ~$user>
2504  <Limit RETR>
2505    DenyAll
2506  </Limit>
2507</Directory>
2508EOC
2509    close($fh);
2510
2511  } else {
2512    die("Can't read $config_file: $!");
2513  }
2514
2515  # Open pipes, for use between the parent and child processes.  Specifically,
2516  # the child will indicate when it's done with its test by writing a message
2517  # to the parent.
2518  my ($rfh, $wfh);
2519  unless (pipe($rfh, $wfh)) {
2520    die("Can't open pipe: $!");
2521  }
2522
2523  my $ex;
2524
2525  # Fork child
2526  $self->handle_sigchld();
2527  defined(my $pid = fork()) or die("Can't fork: $!");
2528  if ($pid) {
2529    eval {
2530      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2531      $client->login($user, $passwd);
2532
2533      my ($resp_code, $resp_msg);
2534
2535      my $conn = $client->retr_raw('test.txt');
2536      if ($conn) {
2537        die("RETR test.txt succeeded unexpectedly");
2538      }
2539
2540      $resp_code = $client->response_code();
2541      $resp_msg = $client->response_msg();
2542
2543      my $expected;
2544
2545      $expected = 550;
2546      $self->assert($expected == $resp_code,
2547        test_msg("Expected response code $expected, got $resp_code"));
2548
2549      $expected = "test.txt: Operation not permitted";
2550      $self->assert($expected eq $resp_msg,
2551        test_msg("Expected response message '$expected', got '$resp_msg'"));
2552    };
2553
2554    if ($@) {
2555      $ex = $@;
2556    }
2557
2558    $wfh->print("done\n");
2559    $wfh->flush();
2560
2561  } else {
2562    eval { server_wait($config_file, $rfh) };
2563    if ($@) {
2564      warn($@);
2565      exit 1;
2566    }
2567
2568    exit 0;
2569  }
2570
2571  # Stop server
2572  server_stop($pid_file);
2573
2574  $self->assert_child_ok($pid);
2575
2576  if ($ex) {
2577    test_append_logfile($log_file, $ex);
2578    unlink($log_file);
2579
2580    die($ex);
2581  }
2582
2583  unlink($log_file);
2584}
2585
2586sub limits_stor_with_multiple_groups_chrooted {
2587  my $self = shift;
2588  my $tmpdir = $self->{tmpdir};
2589
2590  my $config_file = "$tmpdir/dir.conf";
2591  my $pid_file = File::Spec->rel2abs("$tmpdir/dir.pid");
2592  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/dir.scoreboard");
2593
2594  my $log_file = test_get_logfile();
2595
2596  my $auth_user_file = File::Spec->rel2abs("$tmpdir/dir.passwd");
2597  my $auth_group_file = File::Spec->rel2abs("$tmpdir/dir.group");
2598
2599  my $user = 'proftpd';
2600  my $passwd = 'test';
2601  my $group = 'ftpd';
2602  my $home_dir = File::Spec->rel2abs($tmpdir);
2603  my $uid = 500;
2604  my $gid = 500;
2605
2606  my $read_group = 'ftpd_read';
2607  my $read_gid = 501;
2608  my $write_group = 'ftpd_write';
2609  my $write_gid = 502;
2610
2611  my $test_dir = File::Spec->rel2abs("$tmpdir/test.d");
2612  mkpath($test_dir);
2613
2614  my $sub_dir = File::Spec->rel2abs("$test_dir/sub.d");
2615  mkpath($sub_dir);
2616
2617  # Make sure that, if we're running as root, that the home directory has
2618  # permissions/privs set for the account we create
2619  if ($< == 0) {
2620    unless (chmod(0755, $home_dir)) {
2621      die("Can't set perms on $home_dir to 0755: $!");
2622    }
2623
2624    unless (chown($uid, $gid, $home_dir)) {
2625      die("Can't set owner of $home_dir to $uid/$gid: $!");
2626    }
2627
2628    unless(chmod(0775, $test_dir, $sub_dir)) {
2629      die("Can't set perms on $test_dir to 0775: $!");
2630    }
2631
2632    unless (chown($uid, $write_gid, $test_dir, $sub_dir)) {
2633      die("Can't set owner of $test_dir to $uid/$write_gid: $!");
2634    }
2635
2636  }
2637
2638  auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir,
2639    '/bin/bash');
2640  auth_group_write($auth_group_file, $group, $gid, $user);
2641  auth_group_write($auth_group_file, $read_group, $read_gid, $user);
2642  auth_group_write($auth_group_file, $write_group, $write_gid, $user);
2643
2644  # See:
2645  #  https://forums.proftpd.org/smf/index.php/topic,11304.0.html
2646
2647  my $config = {
2648    PidFile => $pid_file,
2649    ScoreboardFile => $scoreboard_file,
2650    SystemLog => $log_file,
2651    TraceLog => $log_file,
2652    Trace => 'DEFAULT:10 fileperms:10 fsio:0 directory:10 lock:0',
2653
2654    AuthUserFile => $auth_user_file,
2655    AuthGroupFile => $auth_group_file,
2656
2657    DefaultRoot => '~',
2658
2659    IfModules => {
2660      'mod_delay.c' => {
2661        DelayEngine => 'off',
2662      },
2663    },
2664  };
2665
2666  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2667
2668  if (open(my $fh, ">> $config_file")) {
2669    print $fh <<EOD;
2670<Directory $test_dir>
2671  <Limit DIRS READ>
2672    AllowGroup OR $read_group,$write_group
2673  </Limit>
2674
2675  <Limit ALL>
2676    DenyAll
2677  </Limit>
2678</Directory>
2679
2680<Directory $sub_dir>
2681  <Limit DIRS READ>
2682    AllowGroup OR $read_group,$write_group
2683  </Limit>
2684
2685  <Limit ALL>
2686    AllowGroup $write_group
2687    DenyAll
2688  </Limit>
2689</Directory>
2690EOD
2691    close($fh);
2692
2693  } else {
2694    die("Can't read $config_file: $!");
2695  }
2696
2697  # Open pipes, for use between the parent and child processes.  Specifically,
2698  # the child will indicate when it's done with its test by writing a message
2699  # to the parent.
2700  my ($rfh, $wfh);
2701  unless (pipe($rfh, $wfh)) {
2702    die("Can't open pipe: $!");
2703  }
2704
2705  my $ex;
2706
2707  # Fork child
2708  $self->handle_sigchld();
2709  defined(my $pid = fork()) or die("Can't fork: $!");
2710  if ($pid) {
2711    eval {
2712      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2713      $client->login($user, $passwd);
2714      $client->cwd('test.d/sub.d');
2715
2716      my $conn = $client->stor_raw('test.txt');
2717      unless ($conn) {
2718        die("Failed to STOR test.txt: " . $client->response_code() . " " .
2719          $client->response_msg());
2720      };
2721
2722      my $buf = "Hello, World\n";
2723      $conn->write($buf, length($buf), 25);
2724      eval { $conn->close() };
2725
2726      my $resp_code = $client->response_code();
2727      my $resp_msg = $client->response_msg();
2728      $self->assert_transfer_ok($resp_code, $resp_msg);
2729
2730      $client->quit();
2731    };
2732
2733    if ($@) {
2734      $ex = $@;
2735    }
2736
2737    $wfh->print("done\n");
2738    $wfh->flush();
2739
2740  } else {
2741    eval { server_wait($config_file, $rfh) };
2742    if ($@) {
2743      warn($@);
2744      exit 1;
2745    }
2746
2747    exit 0;
2748  }
2749
2750  # Stop server
2751  server_stop($pid_file);
2752
2753  $self->assert_child_ok($pid);
2754
2755  if ($ex) {
2756    test_append_logfile($log_file, $ex);
2757    unlink($log_file);
2758
2759    die($ex);
2760  }
2761
2762  unlink($log_file);
2763}
2764
27651;
2766