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