1package ProFTPD::Tests::Modules::mod_ldap;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use File::Path qw(mkpath rmtree);
8use File::Spec;
9use IO::Handle;
10
11use Net::LDAP qw(LDAP_NO_SUCH_OBJECT);
12use Net::LDAP::Entry;
13
14use ProFTPD::TestSuite::FTP;
15use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
16
17$| = 1;
18
19my $order = 0;
20
21# FIXME: we probably can't test these directives without depending
22# on OpenLDAP's slapd being installed, and starting our own directory
23# instance that we can configure and start/stop as we wish.
24#
25# LDAPAttr
26# LDAPAuthBinds
27# LDAPProtocolVersion
28# LDAPUseTLS
29
30my $TESTS = {
31  ldap_users_authallowed => {
32    order => ++$order,
33    test_class => [qw(forking)],
34  },
35
36  ldap_users_authdenied => {
37    order => ++$order,
38    test_class => [qw(forking)],
39  },
40
41  ldap_genhomedir_with_username => {
42    order => ++$order,
43    test_class => [qw(forking)],
44  },
45
46  ldap_genhomedir_without_username => {
47    order => ++$order,
48    test_class => [qw(forking)],
49  },
50
51  ldap_genhomedir_forcegenhdir => {
52    order => ++$order,
53    test_class => [qw(forking)],
54  },
55
56  ldap_groups_authdenied => {
57    order => ++$order,
58    test_class => [qw(forking)],
59  },
60
61  ldap_groups_authallowed => {
62    order => ++$order,
63    test_class => [qw(forking)],
64  },
65
66  ldap_quota_on_user => {
67    order => ++$order,
68    test_class => [qw(forking)],
69  },
70
71  ldap_quota_default => {
72    order => ++$order,
73    test_class => [qw(forking)],
74  },
75
76  ldap_default_uid => {
77    order => ++$order,
78    test_class => [qw(forking)],
79  },
80
81  ldap_default_gid => {
82    order => ++$order,
83    test_class => [qw(forking)],
84  },
85
86  ldap_default_force_uid => {
87    order => ++$order,
88    test_class => [qw(forking)],
89  },
90
91  ldap_default_force_gid => {
92    order => ++$order,
93    test_class => [qw(forking)],
94  },
95
96  ldap_alias_dereference_off => {
97    order => ++$order,
98    test_class => [qw(forking)],
99  },
100
101  ldap_alias_dereference_on => {
102    order => ++$order,
103    test_class => [qw(forking)],
104  },
105
106  ldap_scope_base => {
107    order => ++$order,
108    test_class => [qw(forking)],
109  },
110
111  ldap_scope_sub => {
112    order => ++$order,
113    test_class => [qw(forking)],
114  },
115
116  ldap_default_auth_scheme => {
117    order => ++$order,
118    test_class => [qw(forking)],
119  },
120
121};
122
123sub new {
124  return shift()->SUPER::new(@_);
125}
126
127sub list_tests {
128  return testsuite_get_runnable_tests($TESTS);
129}
130
131sub ldap_auth {
132  my $self = shift;
133  my $allow_auth = shift;
134  my $tmpdir = $self->{tmpdir};
135
136  my $config_file = "$tmpdir/ldap.conf";
137  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
138  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
139
140  my $log_file = File::Spec->rel2abs('tests.log');
141
142  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
143  my $bind_dn = $ENV{LDAP_BIND_DN};
144  my $bind_pass = $ENV{LDAP_BIND_PASS};
145  my $ldap_base = $ENV{LDAP_USER_BASE};
146  my $user = 'proftpdtest' . int(rand(4294967296));
147  my $passwd = 'foobar';
148  my $uid = 1000;
149  my $gid = 1000;
150  my $home_dir = File::Spec->rel2abs($tmpdir);
151
152  my $ld = Net::LDAP->new([$server]);
153  $self->assert($ld);
154  $self->assert($ld->bind($bind_dn, password => $bind_pass));
155
156  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
157  $entry->delete();
158  my $msg = $entry->update($ld);
159  if ($msg->is_error()) {
160    $self->annotate($msg->error());
161  }
162  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
163
164  $entry = Net::LDAP::Entry->new(
165    "uid=$user,$ldap_base",
166    objectClass => ['posixAccount', 'account'],
167    uid => $user,
168    userPassword => $passwd,
169    uidNumber => $uid,
170    gidNumber => $gid,
171    homeDirectory => $home_dir,
172    cn => 'ProFTPD Test',
173  );
174  $msg = $entry->update($ld);
175  $self->assert(!$msg->is_error());
176
177  # Make sure that, if we're running as root, that the home directory has
178  # permissions/privs set for the account we create
179  if ($< == 0) {
180    unless (chmod(0755, $home_dir)) {
181      die("Can't set perms on $home_dir to 0755: $!");
182    }
183
184    unless (chown($uid, $gid, $home_dir)) {
185      die("Can't set owner of $home_dir to $uid/$gid: $!");
186    }
187  }
188
189  my $config = {
190    PidFile => $pid_file,
191    ScoreboardFile => $scoreboard_file,
192    SystemLog => $log_file,
193    TraceLog => $log_file,
194    Trace => 'auth:10',
195
196    IfModules => {
197      'mod_delay.c' => {
198        DelayEngine => 'off',
199      },
200
201      'mod_ldap.c' => {
202        LDAPServer => $server,
203        LDAPBindDN => "$bind_dn $bind_pass",
204        LDAPUsers => "$ldap_base (uid=%u)",
205      },
206    },
207  };
208
209  my ($port, $config_user, $config_group) = config_write($config_file, $config);
210
211  # Open pipes, for use between the parent and child processes.  Specifically,
212  # the child will indicate when it's done with its test by writing a message
213  # to the parent.
214  my ($rfh, $wfh);
215  unless (pipe($rfh, $wfh)) {
216    die("Can't open pipe: $!");
217  }
218
219  my $ex;
220
221  # Fork child
222  $self->handle_sigchld();
223  defined(my $pid = fork()) or die("Can't fork: $!");
224  if ($pid) {
225    eval {
226      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
227      if ($allow_auth) {
228        $client->login($user, $passwd);
229      } else {
230        eval { $client->login($user, $passwd . '-') };
231        unless ($@) {
232          die("Login succeeded unexpectedly");
233        }
234      }
235
236      my ($resp_code, $resp_msg);
237      $resp_code = $client->response_code();
238      $resp_msg = $client->response_msg();
239
240      my $expected;
241
242      if ($allow_auth) {
243        $expected = 230;
244        $self->assert($expected == $resp_code,
245          test_msg("Expected $expected, got $resp_code"));
246
247        # FIXME: assert() -> assert_str_equals()
248        $expected = "User $user logged in";
249        $self->assert($expected eq $resp_msg,
250          test_msg("Expected '$expected', got '$resp_msg'"));
251      } else {
252          $expected = 530;
253          $self->assert($expected == $resp_code,
254            test_msg("Expected $expected, got $resp_code"));
255
256          $expected = 'Login incorrect.';
257          $self->assert($expected eq $resp_msg,
258            test_msg("Expected '$expected', got '$resp_msg'"));
259      }
260    };
261
262    if ($@) {
263      $ex = $@;
264    }
265
266    $wfh->print("done\n");
267    $wfh->flush();
268
269  } else {
270    eval { server_wait($config_file, $rfh) };
271    if ($@) {
272      warn($@);
273      exit 1;
274    }
275
276    exit 0;
277  }
278
279  server_stop($pid_file);
280
281  $self->assert_child_ok($pid);
282
283  if ($ex) {
284    die($ex);
285  }
286
287  unlink($log_file);
288}
289
290sub ldap_users_authallowed {
291  my $self = shift;
292
293  ldap_auth($self, 1);
294}
295
296sub ldap_users_authdenied {
297  my $self = shift;
298
299  ldap_auth($self, 0);
300}
301
302sub ldap_genhomedir {
303  my $self = shift;
304  my $with_username = shift;
305  my $force_genhdir = shift;
306  my $tmpdir = $self->{tmpdir};
307
308  my $config_file = "$tmpdir/ldap.conf";
309  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
310  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
311
312  my $log_file = File::Spec->rel2abs('tests.log');
313
314  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
315  my $bind_dn = $ENV{LDAP_BIND_DN};
316  my $bind_pass = $ENV{LDAP_BIND_PASS};
317  my $ldap_base = $ENV{LDAP_USER_BASE};
318  my $user = 'proftpdtest' . int(rand(4294967296));
319  my $passwd = 'foobar';
320  my $uid = 1000;
321  my $gid = 1000;
322
323  my $home_dir;
324  if ($with_username) {
325    $home_dir = File::Spec->rel2abs($tmpdir) . "/$user";
326  } else {
327    $home_dir = File::Spec->rel2abs($tmpdir);
328  }
329
330  if ($with_username) {
331    mkdir($home_dir);
332  }
333
334  my $ld = Net::LDAP->new([$server]);
335  $self->assert($ld);
336  $self->assert($ld->bind($bind_dn, password => $bind_pass));
337
338  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
339  $entry->delete();
340  my $msg = $entry->update($ld);
341  if ($msg->is_error()) {
342    $self->annotate($msg->error());
343  }
344  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
345
346  if ($force_genhdir) {
347    $entry = Net::LDAP::Entry->new(
348      "uid=$user,$ldap_base",
349      objectClass => ['posixAccount', 'account'],
350      uid => $user,
351      userPassword => $passwd,
352      uidNumber => $uid,
353      gidNumber => $gid,
354      homeDirectory => '/nonexistent',
355      cn => 'ProFTPD Test',
356    );
357  } else {
358    $entry = Net::LDAP::Entry->new(
359      "uid=$user,$ldap_base",
360      objectClass => ['posixAccount', 'account'],
361      uid => $user,
362      userPassword => $passwd,
363      uidNumber => $uid,
364      gidNumber => $gid,
365      homeDirectory => $home_dir,
366      cn => 'ProFTPD Test',
367    );
368  }
369  $msg = $entry->update($ld);
370  $self->assert(!$msg->is_error());
371
372  # Make sure that, if we're running as root, that the home directory has
373  # permissions/privs set for the account we create
374  if ($< == 0) {
375    unless (chmod(0755, $home_dir)) {
376      die("Can't set perms on $home_dir to 0755: $!");
377    }
378
379    unless (chown($uid, $gid, $home_dir)) {
380      die("Can't set owner of $home_dir to $uid/$gid: $!");
381    }
382  }
383
384  my $config = {
385    PidFile => $pid_file,
386    ScoreboardFile => $scoreboard_file,
387    SystemLog => $log_file,
388    TraceLog => $log_file,
389    Trace => 'auth:10',
390
391    IfModules => {
392      'mod_delay.c' => {
393        DelayEngine => 'off',
394      },
395
396      'mod_ldap.c' => {
397        LDAPServer => $server,
398        LDAPBindDN => "$bind_dn $bind_pass",
399        LDAPUsers => "$ldap_base (uid=%u)",
400        LDAPGenerateHomedir => 'on',
401        LDAPGenerateHomedirPrefix => $tmpdir,
402        LDAPGenerateHomedirPrefixNoUsername =>
403          $with_username ? 'off' : 'on',
404        LDAPForceGeneratedHomedir =>
405          $force_genhdir ? 'on' : 'off',
406      },
407    },
408  };
409
410  open(HDIR_FILE, ">$home_dir/testfile") ||
411    die("Unable to open $home_dir/testfile for writing: $!");
412  print HDIR_FILE "test file in generated homedir\n";
413  close(HDIR_FILE);
414
415  my ($port, $config_user, $config_group) = config_write($config_file, $config);
416
417  # Open pipes, for use between the parent and child processes.  Specifically,
418  # the child will indicate when it's done with its test by writing a message
419  # to the parent.
420  my ($rfh, $wfh);
421  unless (pipe($rfh, $wfh)) {
422    die("Can't open pipe: $!");
423  }
424
425  my $ex;
426
427  # Fork child
428  $self->handle_sigchld();
429  defined(my $pid = fork()) or die("Can't fork: $!");
430  if ($pid) {
431    eval {
432      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
433      $client->login($user, $passwd);
434
435      my ($resp_code, $resp_msg);
436      $resp_code = $client->response_code();
437      $resp_msg = $client->response_msg();
438
439      my $expected;
440
441      $expected = 230;
442      $self->assert($expected == $resp_code,
443        test_msg("Expected $expected, got $resp_code"));
444
445      $expected = "User $user logged in";
446      $self->assert($expected eq $resp_msg,
447        test_msg("Expected '$expected', got '$resp_msg'"));
448
449      $client->retr('testfile');
450    };
451
452    if ($@) {
453      $ex = $@;
454    }
455
456    $wfh->print("done\n");
457    $wfh->flush();
458
459  } else {
460    eval { server_wait($config_file, $rfh) };
461    if ($@) {
462      warn($@);
463      exit 1;
464    }
465
466    exit 0;
467  }
468
469  server_stop($pid_file);
470
471  $self->assert_child_ok($pid);
472
473  if ($ex) {
474    die($ex);
475  }
476
477  unlink($log_file);
478}
479
480sub ldap_genhomedir_with_username {
481  my $self = shift;
482
483  ldap_genhomedir($self, 1, 0);
484}
485
486sub ldap_genhomedir_without_username {
487  my $self = shift;
488
489  ldap_genhomedir($self, 0, 0);
490}
491
492sub ldap_genhomedir_forcegenhdir {
493  my $self = shift;
494
495  ldap_genhomedir($self, 0, 1);
496}
497
498sub ldap_groups_auth {
499  my $self = shift;
500  my $allow_auth = shift;
501  my $tmpdir = $self->{tmpdir};
502
503  my $config_file = "$tmpdir/ldap.conf";
504  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
505  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
506
507  my $log_file = File::Spec->rel2abs('tests.log');
508
509  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
510  my $bind_dn = $ENV{LDAP_BIND_DN};
511  my $bind_pass = $ENV{LDAP_BIND_PASS};
512  my $ldap_base = $ENV{LDAP_USER_BASE};
513  my $user = 'proftpdtest' . int(rand(4294967296));
514  my $passwd = 'foobar';
515  my $uid = 1000;
516  my $gid = 1000;
517  my $home_dir = File::Spec->rel2abs($tmpdir);
518
519  my $group = 'proftpdtestgroup' . int(rand(4294967296));
520  my $groupid = 10000;
521
522  my $ld = Net::LDAP->new([$server]);
523  $self->assert($ld);
524  $self->assert($ld->bind($bind_dn, password => $bind_pass));
525
526  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
527  $entry->delete();
528  my $msg = $entry->update($ld);
529  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
530
531  $entry = Net::LDAP::Entry->new("cn=$group,$ldap_base");
532  $entry->delete();
533  $msg = $entry->update($ld);
534  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
535
536  $entry = Net::LDAP::Entry->new(
537    "uid=$user,$ldap_base",
538    objectClass => ['posixAccount', 'account'],
539    uid => $user,
540    userPassword => $passwd,
541    uidNumber => $uid,
542    gidNumber => $gid,
543    homeDirectory => $home_dir,
544    cn => 'ProFTPD Test',
545  );
546  $msg = $entry->update($ld);
547  $self->assert(!$msg->is_error());
548
549  if ($allow_auth) {
550    $entry = Net::LDAP::Entry->new(
551      "cn=$group,$ldap_base",
552      objectClass => 'posixGroup',
553      cn => $group,
554      gidNumber => $groupid,
555      memberUid => $user,
556    );
557  } else {
558    $entry = Net::LDAP::Entry->new(
559      "cn=$group,$ldap_base",
560      objectClass => 'posixGroup',
561      cn => $group,
562      gidNumber => $groupid,
563    );
564  }
565  $msg = $entry->update($ld);
566  $self->assert(!$msg->is_error());
567
568  # Make sure that, if we're running as root, that the home directory has
569  # permissions/privs set for the account we create
570  if ($< == 0) {
571    unless (chmod(0755, $home_dir)) {
572      die("Can't set perms on $home_dir to 0755: $!");
573    }
574
575    unless (chown($uid, $gid, $home_dir)) {
576      die("Can't set owner of $home_dir to $uid/$gid: $!");
577    }
578  }
579
580  my $config = {
581    PidFile => $pid_file,
582    ScoreboardFile => $scoreboard_file,
583    SystemLog => $log_file,
584    TraceLog => $log_file,
585    Trace => 'auth:10',
586
587    Limit => {
588      LOGIN => {
589        AllowGroup => $group,
590        DenyAll => '',
591      }
592    },
593
594    IfModules => {
595      'mod_delay.c' => {
596        DelayEngine => 'off',
597      },
598
599      'mod_ldap.c' => {
600        LDAPServer => $server,
601        LDAPBindDN => "$bind_dn $bind_pass",
602        LDAPUsers => "$ldap_base (uid=%u)",
603        LDAPGroups => "$ldap_base (uid=%u)",
604      },
605    },
606  };
607
608  my ($port, $config_user, $config_group) = config_write($config_file, $config);
609
610  # Open pipes, for use between the parent and child processes.  Specifically,
611  # the child will indicate when it's done with its test by writing a message
612  # to the parent.
613  my ($rfh, $wfh);
614  unless (pipe($rfh, $wfh)) {
615    die("Can't open pipe: $!");
616  }
617
618  my $ex;
619
620  # Fork child
621  $self->handle_sigchld();
622  defined(my $pid = fork()) or die("Can't fork: $!");
623  if ($pid) {
624    eval {
625      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
626      if ($allow_auth) {
627        $client->login($user, $passwd);
628      } else {
629        eval { $client->login($user, $passwd) };
630        unless ($@) {
631          die("Login succeeded unexpectedly");
632        }
633      }
634
635      my ($resp_code, $resp_msg);
636      $resp_code = $client->response_code();
637      $resp_msg = $client->response_msg();
638
639      my $expected;
640
641      if ($allow_auth) {
642        $expected = 230;
643        $self->assert($expected == $resp_code,
644          test_msg("Expected $expected, got $resp_code"));
645
646        $expected = "User $user logged in";
647        $self->assert($expected eq $resp_msg,
648          test_msg("Expected '$expected', got '$resp_msg'"));
649      } else {
650        $expected = 530;
651        $self->assert($expected == $resp_code,
652          test_msg("Expected $expected, got $resp_code"));
653
654        $expected = 'Login incorrect.';
655        $self->assert($expected eq $resp_msg,
656          test_msg("Expected '$expected', got '$resp_msg'"));
657      }
658    };
659
660    if ($@) {
661      $ex = $@;
662    }
663
664    $wfh->print("done\n");
665    $wfh->flush();
666
667  } else {
668    eval { server_wait($config_file, $rfh) };
669    if ($@) {
670      warn($@);
671      exit 1;
672    }
673
674    exit 0;
675  }
676
677  server_stop($pid_file);
678
679  $self->assert_child_ok($pid);
680
681  if ($ex) {
682    die($ex);
683  }
684
685  unlink($log_file);
686}
687
688sub ldap_groups_authdenied {
689  my $self = shift;
690
691  ldap_groups_auth($self, 0);
692}
693
694sub ldap_groups_authallowed {
695  my $self = shift;
696
697  ldap_groups_auth($self, 1);
698}
699
700sub ldap_quota {
701  my $self = shift;
702  my $default_quota = shift;
703  my $tmpdir = $self->{tmpdir};
704  my $abs_tmpdir = File::Spec->rel2abs($self->{tmpdir});
705
706  my $config_file = "$tmpdir/ldap.conf";
707  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
708  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
709
710  my $log_file = File::Spec->rel2abs('tests.log');
711
712  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
713  my $bind_dn = $ENV{LDAP_BIND_DN};
714  my $bind_pass = $ENV{LDAP_BIND_PASS};
715  my $ldap_base = $ENV{LDAP_USER_BASE};
716  my $user = 'proftpdtest' . int(rand(4294967296));
717  my $passwd = 'foobar';
718  my $uid = 1000;
719  my $gid = 1000;
720  my $home_dir = File::Spec->rel2abs($tmpdir);
721
722  my $ftpquota_path;
723  if ($ENV{PROFTPD_TEST_PATH}) {
724    $ftpquota_path = "$ENV{PROFTPD_TEST_PATH}/../contrib/ftpquota";
725  } else {
726    $ftpquota_path = '../contrib/ftpquota';
727  }
728
729  my $ld = Net::LDAP->new([$server]);
730  $self->assert($ld);
731  $self->assert($ld->bind($bind_dn, password => $bind_pass));
732
733  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
734  $entry->delete();
735  my $msg = $entry->update($ld);
736  if ($msg->is_error()) {
737    $self->annotate($msg->error());
738  }
739  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
740
741  if ($default_quota) {
742    $entry = Net::LDAP::Entry->new(
743      "uid=$user,$ldap_base",
744      objectClass => ['posixAccount', 'account'],
745      uid => $user,
746      userPassword => $passwd,
747      uidNumber => $uid,
748      gidNumber => $gid,
749      homeDirectory => $home_dir,
750      cn => 'ProFTPD Test',
751    );
752  } else {
753    $entry = Net::LDAP::Entry->new(
754      "uid=$user,$ldap_base",
755      objectClass => ['posixAccount', 'account', 'proftpdQuota'],
756      uid => $user,
757      userPassword => $passwd,
758      uidNumber => $uid,
759      gidNumber => $gid,
760      homeDirectory => $home_dir,
761      cn => 'ProFTPD Test',
762      ftpQuota => 'false,hard,1048576,0,0,0,0,0',
763    );
764  }
765  $msg = $entry->update($ld);
766  $self->assert(!$msg->is_error());
767
768  # Make sure that, if we're running as root, that the home directory has
769  # permissions/privs set for the account we create
770  if ($< == 0) {
771    unless (chmod(0755, $home_dir)) {
772      die("Can't set perms on $home_dir to 0755: $!");
773    }
774
775    unless (chown($uid, $gid, $home_dir)) {
776      die("Can't set owner of $home_dir to $uid/$gid: $!");
777    }
778  }
779
780  my $config = {
781    PidFile => $pid_file,
782    ScoreboardFile => $scoreboard_file,
783    SystemLog => $log_file,
784    TraceLog => $log_file,
785    Trace => 'auth:10',
786
787    IfModules => {
788      'mod_delay.c' => {
789        DelayEngine => 'off',
790      },
791
792      'mod_ldap.c' => {
793        LDAPServer => $server,
794        LDAPBindDN => "$bind_dn $bind_pass",
795        LDAPUsers => "$ldap_base (uid=%u)",
796        LDAPDefaultQuota => 'false,hard,10485760,0,0,0,0,0',
797      },
798
799      'mod_quotatab.c' => {
800        QuotaEngine => 'on',
801        QuotaShowQuotas => 'on',
802        QuotaDisplayUnits => 'Mb',
803        QuotaLimitTable => 'ldap:',
804        QuotaTallyTable => "file:$abs_tmpdir/quota-tally",
805        QuotaLog => '/var/log/quota',
806      },
807    },
808  };
809
810  # FIXME: error checking?
811  system("$ftpquota_path --type tally --create-table --table-path \"$tmpdir/quota-tally\"");
812
813  my ($port, $config_user, $config_group) = config_write($config_file, $config);
814
815  # Open pipes, for use between the parent and child processes.  Specifically,
816  # the child will indicate when it's done with its test by writing a message
817  # to the parent.
818  my ($rfh, $wfh);
819  unless (pipe($rfh, $wfh)) {
820    die("Can't open pipe: $!");
821  }
822
823  my $ex;
824
825  # Fork child
826  $self->handle_sigchld();
827  defined(my $pid = fork()) or die("Can't fork: $!");
828  if ($pid) {
829    eval {
830      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
831      $client->login($user, $passwd);
832
833      my ($resp_code, $resp_msg);
834      $resp_code = $client->response_code();
835      $resp_msg = $client->response_msg();
836
837      my $expected;
838
839      $expected = 230;
840      $self->assert($expected == $resp_code,
841        test_msg("Expected $expected, got $resp_code"));
842
843      $expected = "User $user logged in";
844      $self->assert($expected eq $resp_msg,
845        test_msg("Expected '$expected', got '$resp_msg'"));
846
847
848      $client->quote('SITE QUOTA');
849      $resp_code = $client->response_code();
850      $resp_msg = join("\n", @{$client->response_msgs()});
851
852      $self->assert_matches(qr/Quota Type:\s*User/mi, $resp_msg,
853        test_msg("Expected User quota type, got '$resp_msg'"));
854      if ($default_quota) {
855        $self->assert_matches(qr/Uploaded Mb:\s*0\.00\/10\.00/mi, $resp_msg,
856          test_msg("Expected 0.0/1.0 Mb uploaded, got '$resp_msg'"));
857      } else {
858        $self->assert_matches(qr/Uploaded Mb:\s*0\.00\/1\.00/mi, $resp_msg,
859          test_msg("Expected 0.0/1.0 Mb uploaded, got '$resp_msg'"));
860      }
861
862      $client->quit();
863    };
864
865    if ($@) {
866      $ex = $@;
867    }
868
869    $wfh->print("done\n");
870    $wfh->flush();
871
872  } else {
873    eval { server_wait($config_file, $rfh) };
874    if ($@) {
875      warn($@);
876      exit 1;
877    }
878
879    exit 0;
880  }
881
882  server_stop($pid_file);
883
884  $self->assert_child_ok($pid);
885
886  if ($ex) {
887    die($ex);
888  }
889
890  unlink($log_file);
891}
892
893sub ldap_quota_on_user {
894  my $self = shift;
895
896  ldap_quota($self, 0);
897}
898
899sub ldap_quota_default {
900  my $self = shift;
901
902  ldap_quota($self, 1);
903}
904
905sub ldap_default_uid_gid {
906  my $self = shift;
907  my $uid_or_gid = shift;
908  my $force_default = shift;
909  my $tmpdir = $self->{tmpdir};
910
911  my $config_file = "$tmpdir/ldap.conf";
912  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
913  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
914
915  my $log_file = File::Spec->rel2abs('tests.log');
916
917  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
918  my $bind_dn = $ENV{LDAP_BIND_DN};
919  my $bind_pass = $ENV{LDAP_BIND_PASS};
920  my $ldap_base = $ENV{LDAP_USER_BASE};
921  my $user = 'proftpdtest' . int(rand(4294967296));
922  my $passwd = 'foobar';
923  my $ldap_uid = 1000;
924  my $ldap_gid = 1000;
925  my $home_dir = File::Spec->rel2abs($tmpdir) . "/$user";
926
927  mkdir($home_dir);
928
929  my $ld = Net::LDAP->new([$server]);
930  $self->assert($ld);
931  $self->assert($ld->bind($bind_dn, password => $bind_pass));
932
933  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
934  $entry->delete();
935  my $msg = $entry->update($ld);
936  if ($msg->is_error()) {
937    $self->annotate($msg->error());
938  }
939  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
940
941  # FIXME: posixAccount requires [ug]idNumber, so we can't check
942  # LDAPDefault[UG]ID behavior when uidNumber/gidNumber are absent.
943  # We'll have to declare a dependency on a different (local)
944  # objectClass, or have the mod_ldap test suite depend on OpenLDAP's
945  # slapd being installed, and start our own directory instance.
946  $entry = Net::LDAP::Entry->new(
947    "uid=$user,$ldap_base",
948    objectClass => ['posixAccount', 'account'],
949    uid => $user,
950    userPassword => $passwd,
951    uidNumber => $ldap_uid,
952    gidNumber => $ldap_gid,
953    homeDirectory => $home_dir,
954    cn => 'ProFTPD Test',
955  );
956  $msg = $entry->update($ld);
957  $self->assert(!$msg->is_error());
958
959  my $config = {
960    PidFile => $pid_file,
961    ScoreboardFile => $scoreboard_file,
962    SystemLog => $log_file,
963    TraceLog => $log_file,
964    Trace => 'auth:10',
965
966    IfModules => {
967      'mod_delay.c' => {
968        DelayEngine => 'off',
969      },
970
971      'mod_ldap.c' => {
972        LDAPServer => $server,
973        LDAPBindDN => "$bind_dn $bind_pass",
974        LDAPUsers => "$ldap_base (uid=%u)",
975        LDAPGenerateHomedir => 'on',
976        LDAPGenerateHomedirPrefix => $tmpdir,
977      },
978    },
979  };
980  # If we're forcing the UID/GID, force a *different* UID/GID so
981  # we can determine whether the forcing is in effect.
982  my $proftpd_uid = $ldap_uid;
983  my $proftpd_gid = $ldap_gid;
984  if ($uid_or_gid eq 'uid') {
985    if ($force_default) {
986      $proftpd_uid = $ldap_uid + 1;
987      $config->{IfModules}->{'mod_ldap.c'}->{LDAPForceDefaultUID} = 'on';
988    }
989    $config->{IfModules}->{'mod_ldap.c'}->{LDAPDefaultUID} = $proftpd_uid;
990  } elsif ($uid_or_gid eq 'gid') {
991    if ($force_default) {
992      $proftpd_gid = $ldap_gid + 1;
993      $config->{IfModules}->{'mod_ldap.c'}->{LDAPForceDefaultGID} = 'on';
994    }
995    $config->{IfModules}->{'mod_ldap.c'}->{LDAPDefaultGID} = $proftpd_gid;
996  }
997
998  # Make sure that, if we're running as root, that the home directory has
999  # permissions/privs set for the account we create
1000  if ($< == 0) {
1001    unless (chmod(0755, $home_dir)) {
1002      die("Can't set perms on $home_dir to 0755: $!");
1003    }
1004
1005    unless (chown($proftpd_uid, $proftpd_gid, $home_dir)) {
1006      die("Can't set owner of $home_dir to $proftpd_uid/$proftpd_gid: $!");
1007    }
1008  }
1009
1010  open(HDIR_FILE, ">$home_dir/testfile") ||
1011    die("Unable to open $home_dir/testfile for writing: $!");
1012  print HDIR_FILE "test file\n";
1013  close(HDIR_FILE);
1014  unless (chown($proftpd_uid, $proftpd_gid, "$home_dir/testfile")) {
1015    die("Can't set owner of $home_dir/testfile to $ldap_uid/$ldap_gid: $!");
1016  }
1017  unless (chmod(0700, "$home_dir/testfile")) {
1018    die("Can't set file permission bits on $home_dir/testfile to 0700: $!");
1019  }
1020
1021  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1022
1023  # Open pipes, for use between the parent and child processes.  Specifically,
1024  # the child will indicate when it's done with its test by writing a message
1025  # to the parent.
1026  my ($rfh, $wfh);
1027  unless (pipe($rfh, $wfh)) {
1028    die("Can't open pipe: $!");
1029  }
1030
1031  my $ex;
1032
1033  # Fork child
1034  $self->handle_sigchld();
1035  defined(my $pid = fork()) or die("Can't fork: $!");
1036  if ($pid) {
1037    eval {
1038      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1039      $client->login($user, $passwd);
1040
1041      my ($resp_code, $resp_msg);
1042      $resp_code = $client->response_code();
1043      $resp_msg = $client->response_msg();
1044
1045      my $expected;
1046
1047      $expected = 230;
1048      $self->assert($expected == $resp_code,
1049        test_msg("Expected $expected, got $resp_code"));
1050
1051      $expected = "User $user logged in";
1052      $self->assert($expected eq $resp_msg,
1053        test_msg("Expected '$expected', got '$resp_msg'"));
1054
1055      $client->retr('testfile');
1056    };
1057
1058    if ($@) {
1059      $ex = $@;
1060    }
1061
1062    $wfh->print("done\n");
1063    $wfh->flush();
1064
1065  } else {
1066    eval { server_wait($config_file, $rfh) };
1067    if ($@) {
1068      warn($@);
1069      exit 1;
1070    }
1071
1072    exit 0;
1073  }
1074
1075  server_stop($pid_file);
1076
1077  $self->assert_child_ok($pid);
1078
1079  if ($ex) {
1080    die($ex);
1081  }
1082
1083  unlink($log_file);
1084}
1085
1086sub ldap_default_uid {
1087  my $self = shift;
1088
1089  ldap_default_uid_gid($self, 'uid', 0);
1090}
1091
1092sub ldap_default_gid {
1093  my $self = shift;
1094
1095  ldap_default_uid_gid($self, 'gid', 0);
1096}
1097
1098sub ldap_default_force_uid {
1099  my $self = shift;
1100
1101  ldap_default_uid_gid($self, 'uid', 1);
1102}
1103
1104sub ldap_default_force_gid {
1105  my $self = shift;
1106
1107  ldap_default_uid_gid($self, 'gid', 1);
1108}
1109
1110sub ldap_alias_dereference {
1111  my $self = shift;
1112  my $dereference_enabled = shift;
1113  my $tmpdir = $self->{tmpdir};
1114  my $abs_tmpdir = File::Spec->rel2abs($self->{tmpdir});
1115
1116  my $config_file = "$tmpdir/ldap.conf";
1117  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
1118  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
1119
1120  my $log_file = File::Spec->rel2abs('tests.log');
1121
1122  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
1123  my $bind_dn = $ENV{LDAP_BIND_DN};
1124  my $bind_pass = $ENV{LDAP_BIND_PASS};
1125  my $ldap_base = $ENV{LDAP_USER_BASE};
1126  my $ldap_base_alias = 'ou=proftpdtest' . int(rand(4294967296)) . ',' .
1127    $ldap_base;
1128  my $user = 'proftpdtest' . int(rand(4294967296));
1129  my $passwd = 'foobar';
1130  my $uid = 1000;
1131  my $gid = 1000;
1132  my $home_dir = File::Spec->rel2abs($tmpdir);
1133
1134  my $ld = Net::LDAP->new([$server]);
1135  $self->assert($ld);
1136  $self->assert($ld->bind($bind_dn, password => $bind_pass));
1137
1138  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
1139  $entry->delete();
1140  my $msg = $entry->update($ld);
1141  if ($msg->is_error()) {
1142    $self->annotate($msg->error());
1143  }
1144  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
1145
1146  $entry = Net::LDAP::Entry->new(
1147    $ldap_base_alias,
1148    objectClass => ['alias', 'extensibleObject'],
1149    aliasedObjectName => $ldap_base,
1150  );
1151  $msg = $entry->update($ld);
1152  $self->assert(!$msg->is_error());
1153
1154  $entry = Net::LDAP::Entry->new(
1155    "uid=$user,$ldap_base",
1156    objectClass => ['posixAccount', 'account'],
1157    uid => $user,
1158    userPassword => $passwd,
1159    uidNumber => $uid,
1160    gidNumber => $gid,
1161    homeDirectory => $home_dir,
1162    cn => 'ProFTPD Test',
1163  );
1164  $msg = $entry->update($ld);
1165  $self->assert(!$msg->is_error());
1166
1167  # Make sure that, if we're running as root, that the home directory has
1168  # permissions/privs set for the account we create
1169  if ($< == 0) {
1170    unless (chmod(0755, $home_dir)) {
1171      die("Can't set perms on $home_dir to 0755: $!");
1172    }
1173
1174    unless (chown($uid, $gid, $home_dir)) {
1175      die("Can't set owner of $home_dir to $uid/$gid: $!");
1176    }
1177  }
1178
1179  my $config = {
1180    PidFile => $pid_file,
1181    ScoreboardFile => $scoreboard_file,
1182    SystemLog => $log_file,
1183    TraceLog => $log_file,
1184    Trace => 'auth:10',
1185
1186    IfModules => {
1187      'mod_delay.c' => {
1188        DelayEngine => 'off',
1189      },
1190
1191      'mod_ldap.c' => {
1192        LDAPServer => $server,
1193        LDAPBindDN => "$bind_dn $bind_pass",
1194        LDAPUsers => "$ldap_base_alias (uid=%u)",
1195      },
1196
1197      'mod_quotatab.c' => {
1198        QuotaEngine => 'on',
1199        QuotaShowQuotas => 'on',
1200        QuotaDisplayUnits => 'Mb',
1201        QuotaLimitTable => 'ldap:',
1202        QuotaTallyTable => "file:$abs_tmpdir/quota-tally",
1203        QuotaLog => '/var/log/quota',
1204      },
1205    },
1206  };
1207  if ($dereference_enabled) {
1208    $config->{IfModules}->{'mod_ldap.c'}->{LDAPAliasDereference} = 'always';
1209  }
1210
1211  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1212
1213  # Open pipes, for use between the parent and child processes.  Specifically,
1214  # the child will indicate when it's done with its test by writing a message
1215  # to the parent.
1216  my ($rfh, $wfh);
1217  unless (pipe($rfh, $wfh)) {
1218    die("Can't open pipe: $!");
1219  }
1220
1221  my $ex;
1222
1223  # Fork child
1224  $self->handle_sigchld();
1225  defined(my $pid = fork()) or die("Can't fork: $!");
1226  if ($pid) {
1227    eval {
1228      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1229      if ($dereference_enabled) {
1230        $client->login($user, $passwd);
1231      } else {
1232        eval { $client->login($user, $passwd) };
1233        unless ($@) {
1234          die("Login succeeded unexpectedly");
1235        }
1236      }
1237
1238      my ($resp_code, $resp_msg);
1239      $resp_code = $client->response_code();
1240      $resp_msg = $client->response_msg();
1241
1242      my $expected;
1243
1244      if ($dereference_enabled) {
1245        $expected = 230;
1246        $self->assert($expected == $resp_code,
1247          test_msg("Expected $expected, got $resp_code"));
1248
1249        $expected = "User $user logged in";
1250        $self->assert($expected eq $resp_msg,
1251          test_msg("Expected '$expected', got '$resp_msg'"));
1252      } else {
1253        $expected = 530;
1254        $self->assert($expected == $resp_code,
1255          test_msg("Expected $expected, got $resp_code"));
1256
1257        $expected = 'Login incorrect.';
1258        $self->assert($expected eq $resp_msg,
1259          test_msg("Expected '$expected', got '$resp_msg'"));
1260      }
1261
1262      $client->quit();
1263    };
1264
1265    if ($@) {
1266      $ex = $@;
1267    }
1268
1269    $wfh->print("done\n");
1270    $wfh->flush();
1271
1272  } else {
1273    eval { server_wait($config_file, $rfh) };
1274    if ($@) {
1275      warn($@);
1276      exit 1;
1277    }
1278
1279    exit 0;
1280  }
1281
1282  server_stop($pid_file);
1283
1284  $self->assert_child_ok($pid);
1285
1286  if ($ex) {
1287    die($ex);
1288  }
1289
1290  unlink($log_file);
1291}
1292
1293sub ldap_alias_dereference_off {
1294  my $self = shift;
1295
1296  ldap_alias_dereference($self, 0);
1297}
1298
1299sub ldap_alias_dereference_on {
1300  my $self = shift;
1301
1302  ldap_alias_dereference($self, 1);
1303}
1304
1305sub ldap_scope {
1306  my $self = shift;
1307  my $scope = shift;
1308  my $tmpdir = $self->{tmpdir};
1309  my $abs_tmpdir = File::Spec->rel2abs($self->{tmpdir});
1310
1311  my $config_file = "$tmpdir/ldap.conf";
1312  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
1313  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
1314
1315  my $log_file = File::Spec->rel2abs('tests.log');
1316
1317  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
1318  my $bind_dn = $ENV{LDAP_BIND_DN};
1319  my $bind_pass = $ENV{LDAP_BIND_PASS};
1320  my $ldap_base = $ENV{LDAP_USER_BASE};
1321  my $user = 'proftpdtest' . int(rand(4294967296));
1322  my $passwd = 'foobar';
1323  my $uid = 1000;
1324  my $gid = 1000;
1325  my $home_dir = File::Spec->rel2abs($tmpdir);
1326
1327  my $ld = Net::LDAP->new([$server]);
1328  $self->assert($ld);
1329  $self->assert($ld->bind($bind_dn, password => $bind_pass));
1330
1331  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
1332  $entry->delete();
1333  my $msg = $entry->update($ld);
1334  if ($msg->is_error()) {
1335    $self->annotate($msg->error());
1336  }
1337  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
1338
1339  $entry = Net::LDAP::Entry->new(
1340    "uid=$user,$ldap_base",
1341    objectClass => ['posixAccount', 'account'],
1342    uid => $user,
1343    userPassword => $passwd,
1344    uidNumber => $uid,
1345    gidNumber => $gid,
1346    homeDirectory => $home_dir,
1347    cn => 'ProFTPD Test',
1348  );
1349  $msg = $entry->update($ld);
1350  $self->assert(!$msg->is_error());
1351
1352  # Make sure that, if we're running as root, that the home directory has
1353  # permissions/privs set for the account we create
1354  if ($< == 0) {
1355    unless (chmod(0755, $home_dir)) {
1356      die("Can't set perms on $home_dir to 0755: $!");
1357    }
1358
1359    unless (chown($uid, $gid, $home_dir)) {
1360      die("Can't set owner of $home_dir to $uid/$gid: $!");
1361    }
1362  }
1363
1364  my $config = {
1365    PidFile => $pid_file,
1366    ScoreboardFile => $scoreboard_file,
1367    SystemLog => $log_file,
1368    TraceLog => $log_file,
1369    Trace => 'auth:10',
1370
1371    IfModules => {
1372      'mod_delay.c' => {
1373        DelayEngine => 'off',
1374      },
1375
1376      'mod_ldap.c' => {
1377        LDAPServer => $server,
1378        LDAPBindDN => "$bind_dn $bind_pass",
1379        LDAPUsers => "$ldap_base (uid=%u)",
1380        LDAPSearchScope => $scope,
1381      },
1382
1383      'mod_quotatab.c' => {
1384        QuotaEngine => 'on',
1385        QuotaShowQuotas => 'on',
1386        QuotaDisplayUnits => 'Mb',
1387        QuotaLimitTable => 'ldap:',
1388        QuotaTallyTable => "file:$abs_tmpdir/quota-tally",
1389        QuotaLog => '/var/log/quota',
1390      },
1391    },
1392  };
1393
1394  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1395
1396  # Open pipes, for use between the parent and child processes.  Specifically,
1397  # the child will indicate when it's done with its test by writing a message
1398  # to the parent.
1399  my ($rfh, $wfh);
1400  unless (pipe($rfh, $wfh)) {
1401    die("Can't open pipe: $!");
1402  }
1403
1404  my $ex;
1405
1406  # Fork child
1407  $self->handle_sigchld();
1408  defined(my $pid = fork()) or die("Can't fork: $!");
1409  if ($pid) {
1410    eval {
1411      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1412      if ($scope eq 'subtree') {
1413        $client->login($user, $passwd);
1414      } else {
1415        eval { $client->login($user, $passwd) };
1416        unless ($@) {
1417          die("Login succeeded unexpectedly");
1418        }
1419      }
1420
1421      my ($resp_code, $resp_msg);
1422      $resp_code = $client->response_code();
1423      $resp_msg = $client->response_msg();
1424
1425      my $expected;
1426
1427      if ($scope eq 'subtree') {
1428        $expected = 230;
1429        $self->assert($expected == $resp_code,
1430          test_msg("Expected $expected, got $resp_code"));
1431
1432        $expected = "User $user logged in";
1433        $self->assert($expected eq $resp_msg,
1434          test_msg("Expected '$expected', got '$resp_msg'"));
1435      } else {
1436        $expected = 530;
1437        $self->assert($expected == $resp_code,
1438          test_msg("Expected $expected, got $resp_code"));
1439
1440        $expected = 'Login incorrect.';
1441        $self->assert($expected eq $resp_msg,
1442          test_msg("Expected '$expected', got '$resp_msg'"));
1443      }
1444
1445      $client->quit();
1446    };
1447
1448    if ($@) {
1449      $ex = $@;
1450    }
1451
1452    $wfh->print("done\n");
1453    $wfh->flush();
1454
1455  } else {
1456    eval { server_wait($config_file, $rfh) };
1457    if ($@) {
1458      warn($@);
1459      exit 1;
1460    }
1461
1462    exit 0;
1463  }
1464
1465  server_stop($pid_file);
1466
1467  $self->assert_child_ok($pid);
1468
1469  if ($ex) {
1470    die($ex);
1471  }
1472
1473  unlink($log_file);
1474}
1475
1476sub ldap_scope_base {
1477  my $self = shift;
1478
1479  ldap_scope($self, 'base');
1480}
1481
1482sub ldap_scope_sub {
1483  my $self = shift;
1484
1485  ldap_scope($self, 'subtree');
1486}
1487
1488sub ldap_default_auth_scheme {
1489  my $self = shift;
1490  my $tmpdir = $self->{tmpdir};
1491  my $abs_tmpdir = File::Spec->rel2abs($self->{tmpdir});
1492
1493  my $config_file = "$tmpdir/ldap.conf";
1494  my $pid_file = File::Spec->rel2abs("$tmpdir/ldap.pid");
1495  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/ldap.scoreboard");
1496
1497  my $log_file = File::Spec->rel2abs('tests.log');
1498
1499  my $server = $ENV{LDAP_SERVER} ? $ENV{LDAP_SERVER} : 'localhost';
1500  my $bind_dn = $ENV{LDAP_BIND_DN};
1501  my $bind_pass = $ENV{LDAP_BIND_PASS};
1502  my $ldap_base = $ENV{LDAP_USER_BASE};
1503  my $user = 'proftpdtest' . int(rand(4294967296));
1504  my $passwd = 'foobar';
1505  my $uid = 1000;
1506  my $gid = 1000;
1507  my $home_dir = File::Spec->rel2abs($tmpdir);
1508
1509  my $ld = Net::LDAP->new([$server]);
1510  $self->assert($ld);
1511  $self->assert($ld->bind($bind_dn, password => $bind_pass));
1512
1513  my $entry = Net::LDAP::Entry->new("uid=$user,$ldap_base");
1514  $entry->delete();
1515  my $msg = $entry->update($ld);
1516  if ($msg->is_error()) {
1517    $self->annotate($msg->error());
1518  }
1519  $self->assert(!$msg->is_error() || $msg->code() == LDAP_NO_SUCH_OBJECT);
1520
1521  $entry = Net::LDAP::Entry->new(
1522    "uid=$user,$ldap_base",
1523    objectClass => ['posixAccount', 'account'],
1524    uid => $user,
1525    userPassword => crypt($passwd,
1526      join '', ('.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z')[rand 64, rand 64]),
1527    uidNumber => $uid,
1528    gidNumber => $gid,
1529    homeDirectory => $home_dir,
1530    cn => 'ProFTPD Test',
1531  );
1532  $msg = $entry->update($ld);
1533  $self->assert(!$msg->is_error());
1534
1535  # Make sure that, if we're running as root, that the home directory has
1536  # permissions/privs set for the account we create
1537  if ($< == 0) {
1538    unless (chmod(0755, $home_dir)) {
1539      die("Can't set perms on $home_dir to 0755: $!");
1540    }
1541
1542    unless (chown($uid, $gid, $home_dir)) {
1543      die("Can't set owner of $home_dir to $uid/$gid: $!");
1544    }
1545  }
1546
1547  my $config = {
1548    PidFile => $pid_file,
1549    ScoreboardFile => $scoreboard_file,
1550    SystemLog => $log_file,
1551    TraceLog => $log_file,
1552    Trace => 'auth:10',
1553
1554    IfModules => {
1555      'mod_delay.c' => {
1556        DelayEngine => 'off',
1557      },
1558
1559      'mod_ldap.c' => {
1560        LDAPServer => $server,
1561        LDAPBindDN => "$bind_dn $bind_pass",
1562        LDAPUsers => "$ldap_base (uid=%u)",
1563        LDAPAuthBinds => 'off',
1564        LDAPDefaultAuthScheme => 'crypt',
1565      },
1566
1567      'mod_quotatab.c' => {
1568        QuotaEngine => 'on',
1569        QuotaShowQuotas => 'on',
1570        QuotaDisplayUnits => 'Mb',
1571        QuotaLimitTable => 'ldap:',
1572        QuotaTallyTable => "file:$abs_tmpdir/quota-tally",
1573        QuotaLog => '/var/log/quota',
1574      },
1575    },
1576  };
1577
1578  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1579
1580  # Open pipes, for use between the parent and child processes.  Specifically,
1581  # the child will indicate when it's done with its test by writing a message
1582  # to the parent.
1583  my ($rfh, $wfh);
1584  unless (pipe($rfh, $wfh)) {
1585    die("Can't open pipe: $!");
1586  }
1587
1588  my $ex;
1589
1590  # Fork child
1591  $self->handle_sigchld();
1592  defined(my $pid = fork()) or die("Can't fork: $!");
1593  if ($pid) {
1594    eval {
1595      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1596      $client->login($user, $passwd);
1597
1598      my ($resp_code, $resp_msg);
1599      $resp_code = $client->response_code();
1600      $resp_msg = $client->response_msg();
1601
1602      my $expected;
1603
1604      $expected = 230;
1605      $self->assert($expected == $resp_code,
1606        test_msg("Expected $expected, got $resp_code"));
1607
1608      $expected = "User $user logged in";
1609      $self->assert($expected eq $resp_msg,
1610        test_msg("Expected '$expected', got '$resp_msg'"));
1611
1612      $client->quit();
1613    };
1614
1615    if ($@) {
1616      $ex = $@;
1617    }
1618
1619    $wfh->print("done\n");
1620    $wfh->flush();
1621
1622  } else {
1623    eval { server_wait($config_file, $rfh) };
1624    if ($@) {
1625      warn($@);
1626      exit 1;
1627    }
1628
1629    exit 0;
1630  }
1631
1632  server_stop($pid_file);
1633
1634  $self->assert_child_ok($pid);
1635
1636  if ($ex) {
1637    die($ex);
1638  }
1639
1640  unlink($log_file);
1641}
1642
16431;
1644