1package ProFTPD::Tests::Modules::mod_sql_passwd;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use File::Spec;
8use IO::Handle;
9
10use ProFTPD::TestSuite::FTP;
11use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
12
13$| = 1;
14
15my $order = 0;
16
17my $TESTS = {
18  sql_passwd_host => {
19    order => ++$order,
20    test_class => [qw(forking)],
21  },
22
23  sql_passwd_md5_base64 => {
24    order => ++$order,
25    test_class => [qw(forking)],
26  },
27
28  sql_passwd_md5_hex_lc => {
29    order => ++$order,
30    test_class => [qw(forking)],
31  },
32
33  sql_passwd_md5_hex_uc => {
34    order => ++$order,
35    test_class => [qw(forking)],
36  },
37
38  sql_passwd_sha1_base64 => {
39    order => ++$order,
40    test_class => [qw(forking)],
41  },
42
43  sql_passwd_sha1_hex_lc => {
44    order => ++$order,
45    test_class => [qw(forking)],
46  },
47
48  sql_passwd_sha1_hex_uc => {
49    order => ++$order,
50    test_class => [qw(forking)],
51  },
52
53  sql_passwd_engine_off => {
54    order => ++$order,
55    test_class => [qw(forking)],
56  },
57
58  sql_passwd_salt_file => {
59    order => ++$order,
60    test_class => [qw(forking)],
61  },
62
63  sql_passwd_salt_file_trailing_newline => {
64    order => ++$order,
65    test_class => [qw(forking)],
66  },
67
68  sql_passwd_salt_file_prepend => {
69    order => ++$order,
70    test_class => [qw(forking)],
71  },
72
73  sql_passwd_sha256_base64_bug3344 => {
74    order => ++$order,
75    test_class => [qw(forking)],
76  },
77
78  sql_passwd_sha256_hex_lc_bug3344 => {
79    order => ++$order,
80    test_class => [qw(forking)],
81  },
82
83  sql_passwd_sha256_hex_uc_bug3344 => {
84    order => ++$order,
85    test_class => [qw(forking)],
86  },
87
88  sql_passwd_sha512_base64_bug3344 => {
89    order => ++$order,
90    test_class => [qw(forking)],
91  },
92
93  sql_passwd_sha512_hex_lc_bug3344 => {
94    order => ++$order,
95    test_class => [qw(forking)],
96  },
97
98  sql_passwd_sha512_hex_uc_bug3344 => {
99    order => ++$order,
100    test_class => [qw(forking)],
101  },
102
103  sql_passwd_user_salt_name => {
104    order => ++$order,
105    test_class => [qw(forking)],
106  },
107
108  sql_passwd_user_salt_sql => {
109    order => ++$order,
110    test_class => [qw(forking)],
111  },
112
113  sql_passwd_md5_hash_encode_salt_password_bug3500 => {
114    order => ++$order,
115    test_class => [qw(bug forking)],
116  },
117
118  sql_passwd_md5_hash_encode_password_bug3500 => {
119    order => ++$order,
120    test_class => [qw(bug forking)],
121  },
122
123  sql_passwd_md5_rounds_bug3500 => {
124    order => ++$order,
125    test_class => [qw(bug forking)],
126  },
127
128  sql_passwd_md5_rounds_hash_encode_salt_password_bug3500 => {
129    order => ++$order,
130    test_class => [qw(bug forking)],
131  },
132
133  sql_passwd_md5_hash_password => {
134    order => ++$order,
135    test_class => [qw(forking)],
136  },
137
138  sql_passwd_md5_hash_salt => {
139    order => ++$order,
140    test_class => [qw(forking)],
141  },
142
143  sql_passwd_sha1_encode_salt_hash_encode_password => {
144    order => ++$order,
145    test_class => [qw(forking)],
146  },
147
148  sql_passwd_pbkdf2_sha1_base64 => {
149    order => ++$order,
150    test_class => [qw(forking)],
151  },
152
153  sql_passwd_pbkdf2_sha1_hex_lc => {
154    order => ++$order,
155    test_class => [qw(forking)],
156  },
157
158  sql_passwd_pbkdf2_sha1_hex_uc => {
159    order => ++$order,
160    test_class => [qw(forking)],
161  },
162
163  sql_passwd_pbkdf2_sha512_hex_lc => {
164    order => ++$order,
165    test_class => [qw(forking)],
166  },
167
168  sql_passwd_pbkdf2_per_user_bug4052 => {
169    order => ++$order,
170    test_class => [qw(forking bug)],
171  },
172
173  sql_passwd_salt_file_with_user_salt => {
174    order => ++$order,
175    test_class => [qw(forking)],
176  },
177
178  sql_passwd_user_salt_sql_base64_bug4138 => {
179    order => ++$order,
180    test_class => [qw(bug forking)],
181  },
182
183  sql_passwd_user_salt_sql_hex_lc_bug4138 => {
184    order => ++$order,
185    test_class => [qw(bug forking)],
186  },
187
188  sql_passwd_user_salt_sql_hex_uc_bug4138 => {
189    order => ++$order,
190    test_class => [qw(bug forking)],
191  },
192
193  sql_passwd_salt_file_base64_bug4138 => {
194    order => ++$order,
195    test_class => [qw(bug forking)],
196  },
197
198  sql_passwd_salt_file_hex_lc_bug4138 => {
199    order => ++$order,
200    test_class => [qw(bug forking)],
201  },
202
203  sql_passwd_salt_file_hex_uc_bug4138 => {
204    order => ++$order,
205    test_class => [qw(bug forking)],
206  },
207
208  sql_passwd_scrypt_base64_interactive_cost => {
209    order => ++$order,
210    test_class => [qw(feature_sodium forking)],
211  },
212
213  sql_passwd_scrypt_hex_lc_interactive_cost => {
214    order => ++$order,
215    test_class => [qw(feature_sodium forking)],
216  },
217
218  sql_passwd_scrypt_hex_uc_interactive_cost => {
219    order => ++$order,
220    test_class => [qw(feature_sodium forking)],
221  },
222
223  sql_passwd_scrypt_base64_sensitive_cost => {
224    order => ++$order,
225    test_class => [qw(feature_sodium forking)],
226  },
227
228  sql_passwd_scrypt_hex_lc_sensitive_cost => {
229    order => ++$order,
230    test_class => [qw(feature_sodium forking)],
231  },
232
233  sql_passwd_scrypt_hex_uc_sensitive_cost => {
234    order => ++$order,
235    test_class => [qw(feature_sodium forking)],
236  },
237
238  sql_passwd_scrypt_hex_uc_sensitive_cost_len_64 => {
239    order => ++$order,
240    test_class => [qw(feature_sodium forking)],
241  },
242
243  sql_passwd_argon2_base64_interactive_cost => {
244    order => ++$order,
245    test_class => [qw(feature_sodium forking)],
246  },
247
248  sql_passwd_argon2_hex_lc_interactive_cost => {
249    order => ++$order,
250    test_class => [qw(feature_sodium forking)],
251  },
252
253  sql_passwd_argon2_hex_uc_interactive_cost => {
254    order => ++$order,
255    test_class => [qw(feature_sodium forking)],
256  },
257
258  sql_passwd_argon2_base64_interactive_cost => {
259    order => ++$order,
260    test_class => [qw(feature_sodium forking)],
261  },
262
263  sql_passwd_argon2_hex_lc_sensitive_cost => {
264    order => ++$order,
265    test_class => [qw(feature_sodium forking)],
266  },
267
268  sql_passwd_argon2_hex_uc_sensitive_cost => {
269    order => ++$order,
270    test_class => [qw(feature_sodium forking)],
271  },
272
273  sql_passwd_argon2_hex_uc_sensitive_cost_len_64 => {
274    order => ++$order,
275    test_class => [qw(feature_sodium forking)],
276  },
277
278  sql_passwd_bcrypt => {
279    order => ++$order,
280    test_class => [qw(forking)],
281  },
282
283  sql_passwd_bcrypt_php_variant => {
284    order => ++$order,
285    test_class => [qw(forking)],
286  },
287
288};
289
290sub new {
291  return shift()->SUPER::new(@_);
292}
293
294sub list_tests {
295  return testsuite_get_runnable_tests($TESTS);
296}
297
298sub sql_passwd_host {
299  my $self = shift;
300  my $tmpdir = $self->{tmpdir};
301
302  # I used:
303  #
304  #  `/bin/echo -n "test" | openssl dgst -binary -md5 | openssl enc -base64`
305  #
306  # to generate this password.
307  my $passwd = 'CY9rzUYh03PK3k6DJie09g==';
308
309  my $setup = test_setup($tmpdir, 'sql_passwd', undef, $passwd);
310
311  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
312
313  # Build up sqlite3 command to create users, groups tables and populate them
314  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
315
316  if (open(my $fh, "> $db_script")) {
317    print $fh <<EOS;
318CREATE TABLE users (
319  userid TEXT,
320  passwd TEXT,
321  uid INTEGER,
322  gid INTEGER,
323  homedir TEXT,
324  shell TEXT
325);
326INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$setup->{user}', '$passwd', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
327
328CREATE TABLE groups (
329  groupname TEXT,
330  gid INTEGER,
331  members TEXT
332);
333INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
334EOS
335
336    unless (close($fh)) {
337      die("Can't write $db_script: $!");
338    }
339
340  } else {
341    die("Can't open $db_script: $!");
342  }
343
344  my $cmd = "sqlite3 $db_file < $db_script";
345
346  if ($ENV{TEST_VERBOSE}) {
347    print STDERR "Executing sqlite3: $cmd\n";
348  }
349
350  my @output = `$cmd`;
351  if (scalar(@output) &&
352      $ENV{TEST_VERBOSE}) {
353    print STDERR "Output: ", join('', @output), "\n";
354  }
355
356  my $config = {
357    PidFile => $setup->{pid_file},
358    ScoreboardFile => $setup->{scoreboard_file},
359    SystemLog => $setup->{log_file},
360
361    IfModules => {
362      'mod_delay.c' => {
363        DelayEngine => 'off',
364      },
365
366      'mod_sql.c' => {
367        SQLEngine => 'off',
368      },
369
370      'mod_sql_passwd.c' => {
371        SQLPasswordEngine => 'off',
372      },
373    },
374  };
375
376  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
377    $config);
378
379  my $host = 'localhost';
380
381  if (open(my $fh, ">> $setup->{config_file}")) {
382    print $fh <<EOC;
383# This virtual host is name-based
384<VirtualHost 127.0.0.1>
385  Port $port
386  ServerAlias $host
387  ServerName "SQL Passwd Server"
388
389  <IfModule mod_delay.c>
390    DelayEngine off
391  </IfModule>
392
393  <IfModule mod_sql.c>
394    SQLAuthTypes md5
395    SQLBackend sqlite3
396    SQLConnectInfo $db_file
397    SQLLogFile $setup->{log_file}
398  </IfModule>
399
400  <IfModule mod_sql_passwd.c>
401    SQLPasswordEngine on
402    SQLPasswordEncoding base64
403  </IfModule>
404</VirtualHost>
405EOC
406    unless (close($fh)) {
407      die("Can't write $setup->{config_file}: $!");
408    }
409
410  } else {
411    die("Can't open $setup->{config_file}: $!");
412  }
413
414  # Open pipes, for use between the parent and child processes.  Specifically,
415  # the child will indicate when it's done with its test by writing a message
416  # to the parent.
417  my ($rfh, $wfh);
418  unless (pipe($rfh, $wfh)) {
419    die("Can't open pipe: $!");
420  }
421
422  my $ex;
423
424  # Fork child
425  $self->handle_sigchld();
426  defined(my $pid = fork()) or die("Can't fork: $!");
427  if ($pid) {
428    eval {
429      sleep(1);
430      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
431      $client->host($host);
432      $client->login($setup->{user}, "test");
433
434      my $resp_msgs = $client->response_msgs();
435      my $nmsgs = scalar(@$resp_msgs);
436
437      my $expected = 1;
438      $self->assert($expected == $nmsgs,
439        test_msg("Expected $expected, got $nmsgs"));
440
441      $expected = "User $setup->{user} logged in";
442      $self->assert($expected eq $resp_msgs->[0],
443        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
444    };
445    if ($@) {
446      $ex = $@;
447    }
448
449    $wfh->print("done\n");
450    $wfh->flush();
451
452  } else {
453    eval { server_wait($setup->{config_file}, $rfh) };
454    if ($@) {
455      warn($@);
456      exit 1;
457    }
458
459    exit 0;
460  }
461
462  # Stop server
463  server_stop($setup->{pid_file});
464  $self->assert_child_ok($pid);
465
466  test_cleanup($setup->{log_file}, $ex);
467}
468
469sub sql_passwd_md5_base64 {
470  my $self = shift;
471  my $tmpdir = $self->{tmpdir};
472
473  # I used:
474  #
475  #  `/bin/echo -n "test" | openssl dgst -binary -md5 | openssl enc -base64`
476  #
477  # to generate this password.
478  my $passwd = 'CY9rzUYh03PK3k6DJie09g==';
479
480  my $setup = test_setup($tmpdir, 'sql_passwd', undef, $passwd);
481
482  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
483
484  # Build up sqlite3 command to create users, groups tables and populate them
485  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
486
487  if (open(my $fh, "> $db_script")) {
488    print $fh <<EOS;
489CREATE TABLE users (
490  userid TEXT,
491  passwd TEXT,
492  uid INTEGER,
493  gid INTEGER,
494  homedir TEXT,
495  shell TEXT
496);
497INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$setup->{user}', '$passwd', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
498
499CREATE TABLE groups (
500  groupname TEXT,
501  gid INTEGER,
502  members TEXT
503);
504INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
505EOS
506
507    unless (close($fh)) {
508      die("Can't write $db_script: $!");
509    }
510
511  } else {
512    die("Can't open $db_script: $!");
513  }
514
515  my $cmd = "sqlite3 $db_file < $db_script";
516
517  if ($ENV{TEST_VERBOSE}) {
518    print STDERR "Executing sqlite3: $cmd\n";
519  }
520
521  my @output = `$cmd`;
522  if (scalar(@output) &&
523      $ENV{TEST_VERBOSE}) {
524    print STDERR "Output: ", join('', @output), "\n";
525  }
526
527  my $config = {
528    PidFile => $setup->{pid_file},
529    ScoreboardFile => $setup->{scoreboard_file},
530    SystemLog => $setup->{log_file},
531
532    IfModules => {
533      'mod_delay.c' => {
534        DelayEngine => 'off',
535      },
536
537      'mod_sql.c' => {
538        SQLAuthTypes => 'md5',
539        SQLBackend => 'sqlite3',
540        SQLConnectInfo => $db_file,
541        SQLLogFile => $setup->{log_file},
542      },
543
544      'mod_sql_passwd.c' => {
545        SQLPasswordEngine => 'on',
546        SQLPasswordEncoding => 'base64',
547      },
548    },
549  };
550
551  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
552    $config);
553
554  # Open pipes, for use between the parent and child processes.  Specifically,
555  # the child will indicate when it's done with its test by writing a message
556  # to the parent.
557  my ($rfh, $wfh);
558  unless (pipe($rfh, $wfh)) {
559    die("Can't open pipe: $!");
560  }
561
562  my $ex;
563
564  # Fork child
565  $self->handle_sigchld();
566  defined(my $pid = fork()) or die("Can't fork: $!");
567  if ($pid) {
568    eval {
569      sleep(1);
570      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
571      $client->login($setup->{user}, "test");
572
573      my $resp_msgs = $client->response_msgs();
574      my $nmsgs = scalar(@$resp_msgs);
575
576      my $expected = 1;
577      $self->assert($expected == $nmsgs,
578        test_msg("Expected $expected, got $nmsgs"));
579
580      $expected = "User $setup->{user} logged in";
581      $self->assert($expected eq $resp_msgs->[0],
582        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
583    };
584    if ($@) {
585      $ex = $@;
586    }
587
588    $wfh->print("done\n");
589    $wfh->flush();
590
591  } else {
592    eval { server_wait($setup->{config_file}, $rfh) };
593    if ($@) {
594      warn($@);
595      exit 1;
596    }
597
598    exit 0;
599  }
600
601  # Stop server
602  server_stop($setup->{pid_file});
603  $self->assert_child_ok($pid);
604
605  test_cleanup($setup->{log_file}, $ex);
606}
607
608sub sql_passwd_md5_hex_lc {
609  my $self = shift;
610  my $tmpdir = $self->{tmpdir};
611
612  my $config_file = "$tmpdir/sqlpasswd.conf";
613  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
614  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
615
616  my $log_file = test_get_logfile();
617
618  my $user = 'proftpd';
619  my $group = 'ftpd';
620
621  # I used:
622  #
623  #  `/bin/echo -n "test" | openssl dgst -hex -md5`
624  #
625  # to generate this password.
626  my $passwd = '098f6bcd4621d373cade4e832627b4f6';
627
628  my $home_dir = File::Spec->rel2abs($tmpdir);
629  my $uid = 500;
630  my $gid = 500;
631
632  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
633
634  # Build up sqlite3 command to create users, groups tables and populate them
635  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
636
637  if (open(my $fh, "> $db_script")) {
638    print $fh <<EOS;
639CREATE TABLE users (
640  userid TEXT,
641  passwd TEXT,
642  uid INTEGER,
643  gid INTEGER,
644  homedir TEXT,
645  shell TEXT
646);
647INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
648
649CREATE TABLE groups (
650  groupname TEXT,
651  gid INTEGER,
652  members TEXT
653);
654INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
655EOS
656
657    unless (close($fh)) {
658      die("Can't write $db_script: $!");
659    }
660
661  } else {
662    die("Can't open $db_script: $!");
663  }
664
665  my $cmd = "sqlite3 $db_file < $db_script";
666
667  if ($ENV{TEST_VERBOSE}) {
668    print STDERR "Executing sqlite3: $cmd\n";
669  }
670
671  my @output = `$cmd`;
672  if (scalar(@output) &&
673      $ENV{TEST_VERBOSE}) {
674    print STDERR "Output: ", join('', @output), "\n";
675  }
676
677  my $config = {
678    PidFile => $pid_file,
679    ScoreboardFile => $scoreboard_file,
680    SystemLog => $log_file,
681
682    IfModules => {
683      'mod_delay.c' => {
684        DelayEngine => 'off',
685      },
686
687      'mod_sql.c' => {
688        SQLAuthTypes => 'md5',
689        SQLBackend => 'sqlite3',
690        SQLConnectInfo => $db_file,
691        SQLLogFile => $log_file,
692      },
693
694      'mod_sql_passwd.c' => {
695        SQLPasswordEngine => 'on',
696        SQLPasswordEncoding => 'hex',
697      },
698    },
699  };
700
701  my ($port, $config_user, $config_group) = config_write($config_file, $config);
702
703  # Open pipes, for use between the parent and child processes.  Specifically,
704  # the child will indicate when it's done with its test by writing a message
705  # to the parent.
706  my ($rfh, $wfh);
707  unless (pipe($rfh, $wfh)) {
708    die("Can't open pipe: $!");
709  }
710
711  my $ex;
712
713  # Fork child
714  $self->handle_sigchld();
715  defined(my $pid = fork()) or die("Can't fork: $!");
716  if ($pid) {
717    eval {
718      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
719      $client->login($user, "test");
720
721      my $resp_msgs = $client->response_msgs();
722      my $nmsgs = scalar(@$resp_msgs);
723
724      my $expected;
725
726      $expected = 1;
727      $self->assert($expected == $nmsgs,
728        test_msg("Expected $expected, got $nmsgs"));
729
730      $expected = "User proftpd logged in";
731      $self->assert($expected eq $resp_msgs->[0],
732        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
733
734    };
735
736    if ($@) {
737      $ex = $@;
738    }
739
740    $wfh->print("done\n");
741    $wfh->flush();
742
743  } else {
744    eval { server_wait($config_file, $rfh) };
745    if ($@) {
746      warn($@);
747      exit 1;
748    }
749
750    exit 0;
751  }
752
753  # Stop server
754  server_stop($pid_file);
755
756  $self->assert_child_ok($pid);
757
758  if ($ex) {
759    test_append_logfile($log_file, $ex);
760    unlink($log_file);
761
762    die($ex);
763  }
764
765  unlink($log_file);
766}
767
768sub sql_passwd_md5_hex_uc {
769  my $self = shift;
770  my $tmpdir = $self->{tmpdir};
771
772  my $config_file = "$tmpdir/sqlpasswd.conf";
773  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
774  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
775
776  my $log_file = test_get_logfile();
777
778  my $user = 'proftpd';
779  my $group = 'ftpd';
780
781  # I used:
782  #
783  #  `/bin/echo -n "test" | openssl dgst -hex -md5`
784  #
785  # to generate this password.  Then I manually made all of the letters be
786  # in uppercase.  Tedious.
787  my $passwd = '098F6BCD4621D373CADE4E832627B4F6';
788
789  my $home_dir = File::Spec->rel2abs($tmpdir);
790  my $uid = 500;
791  my $gid = 500;
792
793  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
794
795  # Build up sqlite3 command to create users, groups tables and populate them
796  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
797
798  if (open(my $fh, "> $db_script")) {
799    print $fh <<EOS;
800CREATE TABLE users (
801  userid TEXT,
802  passwd TEXT,
803  uid INTEGER,
804  gid INTEGER,
805  homedir TEXT,
806  shell TEXT
807);
808INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
809
810CREATE TABLE groups (
811  groupname TEXT,
812  gid INTEGER,
813  members TEXT
814);
815INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
816EOS
817
818    unless (close($fh)) {
819      die("Can't write $db_script: $!");
820    }
821
822  } else {
823    die("Can't open $db_script: $!");
824  }
825
826  my $cmd = "sqlite3 $db_file < $db_script";
827
828  if ($ENV{TEST_VERBOSE}) {
829    print STDERR "Executing sqlite3: $cmd\n";
830  }
831
832  my @output = `$cmd`;
833  if (scalar(@output) &&
834      $ENV{TEST_VERBOSE}) {
835    print STDERR "Output: ", join('', @output), "\n";
836  }
837
838  my $config = {
839    PidFile => $pid_file,
840    ScoreboardFile => $scoreboard_file,
841    SystemLog => $log_file,
842
843    IfModules => {
844      'mod_delay.c' => {
845        DelayEngine => 'off',
846      },
847
848      'mod_sql.c' => {
849        SQLAuthTypes => 'md5',
850        SQLBackend => 'sqlite3',
851        SQLConnectInfo => $db_file,
852        SQLLogFile => $log_file,
853      },
854
855      'mod_sql_passwd.c' => {
856        SQLPasswordEngine => 'on',
857        SQLPasswordEncoding => 'HEX',
858      },
859    },
860  };
861
862  my ($port, $config_user, $config_group) = config_write($config_file, $config);
863
864  # Open pipes, for use between the parent and child processes.  Specifically,
865  # the child will indicate when it's done with its test by writing a message
866  # to the parent.
867  my ($rfh, $wfh);
868  unless (pipe($rfh, $wfh)) {
869    die("Can't open pipe: $!");
870  }
871
872  my $ex;
873
874  # Fork child
875  $self->handle_sigchld();
876  defined(my $pid = fork()) or die("Can't fork: $!");
877  if ($pid) {
878    eval {
879      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
880      $client->login($user, "test");
881
882      my $resp_msgs = $client->response_msgs();
883      my $nmsgs = scalar(@$resp_msgs);
884
885      my $expected;
886
887      $expected = 1;
888      $self->assert($expected == $nmsgs,
889        test_msg("Expected $expected, got $nmsgs"));
890
891      $expected = "User proftpd logged in";
892      $self->assert($expected eq $resp_msgs->[0],
893        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
894
895    };
896
897    if ($@) {
898      $ex = $@;
899    }
900
901    $wfh->print("done\n");
902    $wfh->flush();
903
904  } else {
905    eval { server_wait($config_file, $rfh) };
906    if ($@) {
907      warn($@);
908      exit 1;
909    }
910
911    exit 0;
912  }
913
914  # Stop server
915  server_stop($pid_file);
916
917  $self->assert_child_ok($pid);
918
919  if ($ex) {
920    test_append_logfile($log_file, $ex);
921    unlink($log_file);
922
923    die($ex);
924  }
925
926  unlink($log_file);
927}
928
929sub sql_passwd_sha1_base64 {
930  my $self = shift;
931  my $tmpdir = $self->{tmpdir};
932
933  my $config_file = "$tmpdir/sqlpasswd.conf";
934  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
935  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
936
937  my $log_file = test_get_logfile();
938
939  my $user = 'proftpd';
940  my $group = 'ftpd';
941
942  # I used:
943  #
944  #  `/bin/echo -n "test" | openssl dgst -binary -sha1 | openssl enc -base64`
945  #
946  # to generate this password.
947  my $passwd = 'qUqP5cyxm6YcTAhz05Hph5gvu9M=';
948
949  my $home_dir = File::Spec->rel2abs($tmpdir);
950  my $uid = 500;
951  my $gid = 500;
952
953  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
954
955  # Build up sqlite3 command to create users, groups tables and populate them
956  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
957
958  if (open(my $fh, "> $db_script")) {
959    print $fh <<EOS;
960CREATE TABLE users (
961  userid TEXT,
962  passwd TEXT,
963  uid INTEGER,
964  gid INTEGER,
965  homedir TEXT,
966  shell TEXT
967);
968INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
969
970CREATE TABLE groups (
971  groupname TEXT,
972  gid INTEGER,
973  members TEXT
974);
975INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
976EOS
977
978    unless (close($fh)) {
979      die("Can't write $db_script: $!");
980    }
981
982  } else {
983    die("Can't open $db_script: $!");
984  }
985
986  my $cmd = "sqlite3 $db_file < $db_script";
987
988  if ($ENV{TEST_VERBOSE}) {
989    print STDERR "Executing sqlite3: $cmd\n";
990  }
991
992  my @output = `$cmd`;
993  if (scalar(@output) &&
994      $ENV{TEST_VERBOSE}) {
995    print STDERR "Output: ", join('', @output), "\n";
996  }
997
998  my $config = {
999    PidFile => $pid_file,
1000    ScoreboardFile => $scoreboard_file,
1001    SystemLog => $log_file,
1002
1003    IfModules => {
1004      'mod_delay.c' => {
1005        DelayEngine => 'off',
1006      },
1007
1008      'mod_sql.c' => {
1009        SQLAuthTypes => 'sha1',
1010        SQLBackend => 'sqlite3',
1011        SQLConnectInfo => $db_file,
1012        SQLLogFile => $log_file,
1013      },
1014
1015      'mod_sql_passwd.c' => {
1016        SQLPasswordEngine => 'on',
1017        SQLPasswordEncoding => 'base64',
1018      },
1019    },
1020  };
1021
1022  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1023
1024  # Open pipes, for use between the parent and child processes.  Specifically,
1025  # the child will indicate when it's done with its test by writing a message
1026  # to the parent.
1027  my ($rfh, $wfh);
1028  unless (pipe($rfh, $wfh)) {
1029    die("Can't open pipe: $!");
1030  }
1031
1032  my $ex;
1033
1034  # Fork child
1035  $self->handle_sigchld();
1036  defined(my $pid = fork()) or die("Can't fork: $!");
1037  if ($pid) {
1038    eval {
1039      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1040      $client->login($user, "test");
1041
1042      my $resp_msgs = $client->response_msgs();
1043      my $nmsgs = scalar(@$resp_msgs);
1044
1045      my $expected;
1046
1047      $expected = 1;
1048      $self->assert($expected == $nmsgs,
1049        test_msg("Expected $expected, got $nmsgs"));
1050
1051      $expected = "User proftpd logged in";
1052      $self->assert($expected eq $resp_msgs->[0],
1053        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
1054
1055    };
1056
1057    if ($@) {
1058      $ex = $@;
1059    }
1060
1061    $wfh->print("done\n");
1062    $wfh->flush();
1063
1064  } else {
1065    eval { server_wait($config_file, $rfh) };
1066    if ($@) {
1067      warn($@);
1068      exit 1;
1069    }
1070
1071    exit 0;
1072  }
1073
1074  # Stop server
1075  server_stop($pid_file);
1076
1077  $self->assert_child_ok($pid);
1078
1079  if ($ex) {
1080    test_append_logfile($log_file, $ex);
1081    unlink($log_file);
1082
1083    die($ex);
1084  }
1085
1086  unlink($log_file);
1087}
1088
1089sub sql_passwd_sha1_hex_lc {
1090  my $self = shift;
1091  my $tmpdir = $self->{tmpdir};
1092
1093  my $config_file = "$tmpdir/sqlpasswd.conf";
1094  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1095  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1096
1097  my $log_file = test_get_logfile();
1098
1099  my $user = 'proftpd';
1100  my $group = 'ftpd';
1101
1102  # I used:
1103  #
1104  #  `/bin/echo -n "test" | openssl dgst -hex -sha1`
1105  #
1106  # to generate this password.
1107  my $passwd = 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3';
1108
1109  my $home_dir = File::Spec->rel2abs($tmpdir);
1110  my $uid = 500;
1111  my $gid = 500;
1112
1113  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1114
1115  # Build up sqlite3 command to create users, groups tables and populate them
1116  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1117
1118  if (open(my $fh, "> $db_script")) {
1119    print $fh <<EOS;
1120CREATE TABLE users (
1121  userid TEXT,
1122  passwd TEXT,
1123  uid INTEGER,
1124  gid INTEGER,
1125  homedir TEXT,
1126  shell TEXT
1127);
1128INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1129
1130CREATE TABLE groups (
1131  groupname TEXT,
1132  gid INTEGER,
1133  members TEXT
1134);
1135INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1136EOS
1137
1138    unless (close($fh)) {
1139      die("Can't write $db_script: $!");
1140    }
1141
1142  } else {
1143    die("Can't open $db_script: $!");
1144  }
1145
1146  my $cmd = "sqlite3 $db_file < $db_script";
1147
1148  if ($ENV{TEST_VERBOSE}) {
1149    print STDERR "Executing sqlite3: $cmd\n";
1150  }
1151
1152  my @output = `$cmd`;
1153  if (scalar(@output) &&
1154      $ENV{TEST_VERBOSE}) {
1155    print STDERR "Output: ", join('', @output), "\n";
1156  }
1157
1158  my $config = {
1159    PidFile => $pid_file,
1160    ScoreboardFile => $scoreboard_file,
1161    SystemLog => $log_file,
1162
1163    IfModules => {
1164      'mod_delay.c' => {
1165        DelayEngine => 'off',
1166      },
1167
1168      'mod_sql.c' => {
1169        SQLAuthTypes => 'sha1',
1170        SQLBackend => 'sqlite3',
1171        SQLConnectInfo => $db_file,
1172        SQLLogFile => $log_file,
1173      },
1174
1175      'mod_sql_passwd.c' => {
1176        SQLPasswordEngine => 'on',
1177        SQLPasswordEncoding => 'hex',
1178      },
1179    },
1180  };
1181
1182  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1183
1184  # Open pipes, for use between the parent and child processes.  Specifically,
1185  # the child will indicate when it's done with its test by writing a message
1186  # to the parent.
1187  my ($rfh, $wfh);
1188  unless (pipe($rfh, $wfh)) {
1189    die("Can't open pipe: $!");
1190  }
1191
1192  my $ex;
1193
1194  # Fork child
1195  $self->handle_sigchld();
1196  defined(my $pid = fork()) or die("Can't fork: $!");
1197  if ($pid) {
1198    eval {
1199      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1200      $client->login($user, "test");
1201
1202      my $resp_msgs = $client->response_msgs();
1203      my $nmsgs = scalar(@$resp_msgs);
1204
1205      my $expected;
1206
1207      $expected = 1;
1208      $self->assert($expected == $nmsgs,
1209        test_msg("Expected $expected, got $nmsgs"));
1210
1211      $expected = "User proftpd logged in";
1212      $self->assert($expected eq $resp_msgs->[0],
1213        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
1214
1215    };
1216
1217    if ($@) {
1218      $ex = $@;
1219    }
1220
1221    $wfh->print("done\n");
1222    $wfh->flush();
1223
1224  } else {
1225    eval { server_wait($config_file, $rfh) };
1226    if ($@) {
1227      warn($@);
1228      exit 1;
1229    }
1230
1231    exit 0;
1232  }
1233
1234  # Stop server
1235  server_stop($pid_file);
1236
1237  $self->assert_child_ok($pid);
1238
1239  if ($ex) {
1240    test_append_logfile($log_file, $ex);
1241    unlink($log_file);
1242
1243    die($ex);
1244  }
1245
1246  unlink($log_file);
1247}
1248
1249sub sql_passwd_sha1_hex_uc {
1250  my $self = shift;
1251  my $tmpdir = $self->{tmpdir};
1252
1253  my $config_file = "$tmpdir/sqlpasswd.conf";
1254  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1255  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1256
1257  my $log_file = test_get_logfile();
1258
1259  my $user = 'proftpd';
1260  my $group = 'ftpd';
1261
1262  # I used:
1263  #
1264  #  `/bin/echo -n "test" | openssl dgst -hex -sha1`
1265  #
1266  # to generate this password.  Then I manually made all of the letters be
1267  # in uppercase.  Tedious.
1268  my $passwd = 'A94A8FE5CCB19BA61C4C0873D391E987982FBBD3';
1269
1270  my $home_dir = File::Spec->rel2abs($tmpdir);
1271  my $uid = 500;
1272  my $gid = 500;
1273
1274  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1275
1276  # Build up sqlite3 command to create users, groups tables and populate them
1277  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1278
1279  if (open(my $fh, "> $db_script")) {
1280    print $fh <<EOS;
1281CREATE TABLE users (
1282  userid TEXT,
1283  passwd TEXT,
1284  uid INTEGER,
1285  gid INTEGER,
1286  homedir TEXT,
1287  shell TEXT
1288);
1289INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1290
1291CREATE TABLE groups (
1292  groupname TEXT,
1293  gid INTEGER,
1294  members TEXT
1295);
1296INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1297EOS
1298
1299    unless (close($fh)) {
1300      die("Can't write $db_script: $!");
1301    }
1302
1303  } else {
1304    die("Can't open $db_script: $!");
1305  }
1306
1307  my $cmd = "sqlite3 $db_file < $db_script";
1308
1309  if ($ENV{TEST_VERBOSE}) {
1310    print STDERR "Executing sqlite3: $cmd\n";
1311  }
1312
1313  my @output = `$cmd`;
1314  if (scalar(@output) &&
1315      $ENV{TEST_VERBOSE}) {
1316    print STDERR "Output: ", join('', @output), "\n";
1317  }
1318
1319  my $config = {
1320    PidFile => $pid_file,
1321    ScoreboardFile => $scoreboard_file,
1322    SystemLog => $log_file,
1323
1324    IfModules => {
1325      'mod_delay.c' => {
1326        DelayEngine => 'off',
1327      },
1328
1329      'mod_sql.c' => {
1330        SQLAuthTypes => 'sha1',
1331        SQLBackend => 'sqlite3',
1332        SQLConnectInfo => $db_file,
1333        SQLLogFile => $log_file,
1334      },
1335
1336      'mod_sql_passwd.c' => {
1337        SQLPasswordEngine => 'on',
1338        SQLPasswordEncoding => 'HEX',
1339      },
1340    },
1341  };
1342
1343  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1344
1345  # Open pipes, for use between the parent and child processes.  Specifically,
1346  # the child will indicate when it's done with its test by writing a message
1347  # to the parent.
1348  my ($rfh, $wfh);
1349  unless (pipe($rfh, $wfh)) {
1350    die("Can't open pipe: $!");
1351  }
1352
1353  my $ex;
1354
1355  # Fork child
1356  $self->handle_sigchld();
1357  defined(my $pid = fork()) or die("Can't fork: $!");
1358  if ($pid) {
1359    eval {
1360      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1361      $client->login($user, "test");
1362
1363      my $resp_msgs = $client->response_msgs();
1364      my $nmsgs = scalar(@$resp_msgs);
1365
1366      my $expected;
1367
1368      $expected = 1;
1369      $self->assert($expected == $nmsgs,
1370        test_msg("Expected $expected, got $nmsgs"));
1371
1372      $expected = "User proftpd logged in";
1373      $self->assert($expected eq $resp_msgs->[0],
1374        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
1375
1376    };
1377
1378    if ($@) {
1379      $ex = $@;
1380    }
1381
1382    $wfh->print("done\n");
1383    $wfh->flush();
1384
1385  } else {
1386    eval { server_wait($config_file, $rfh) };
1387    if ($@) {
1388      warn($@);
1389      exit 1;
1390    }
1391
1392    exit 0;
1393  }
1394
1395  # Stop server
1396  server_stop($pid_file);
1397
1398  $self->assert_child_ok($pid);
1399
1400  if ($ex) {
1401    test_append_logfile($log_file, $ex);
1402    unlink($log_file);
1403
1404    die($ex);
1405  }
1406
1407  unlink($log_file);
1408}
1409
1410sub sql_passwd_engine_off {
1411  my $self = shift;
1412  my $tmpdir = $self->{tmpdir};
1413
1414  my $config_file = "$tmpdir/sqlpasswd.conf";
1415  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1416  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1417
1418  my $log_file = test_get_logfile();
1419
1420  my $user = 'proftpd';
1421  my $group = 'ftpd';
1422
1423  # I used:
1424  #
1425  #  `/bin/echo -n "test" | openssl dgst -binary -md5 | openssl enc -base64`
1426  #
1427  # to generate this password.
1428  my $passwd = 'CY9rzUYh03PK3k6DJie09g==';
1429
1430  my $home_dir = File::Spec->rel2abs($tmpdir);
1431  my $uid = 500;
1432  my $gid = 500;
1433
1434  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1435
1436  # Build up sqlite3 command to create users, groups tables and populate them
1437  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1438
1439  if (open(my $fh, "> $db_script")) {
1440    print $fh <<EOS;
1441CREATE TABLE users (
1442  userid TEXT,
1443  passwd TEXT,
1444  uid INTEGER,
1445  gid INTEGER,
1446  homedir TEXT,
1447  shell TEXT
1448);
1449INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1450
1451CREATE TABLE groups (
1452  groupname TEXT,
1453  gid INTEGER,
1454  members TEXT
1455);
1456INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1457EOS
1458
1459    unless (close($fh)) {
1460      die("Can't write $db_script: $!");
1461    }
1462
1463  } else {
1464    die("Can't open $db_script: $!");
1465  }
1466
1467  my $cmd = "sqlite3 $db_file < $db_script";
1468
1469  if ($ENV{TEST_VERBOSE}) {
1470    print STDERR "Executing sqlite3: $cmd\n";
1471  }
1472
1473  my @output = `$cmd`;
1474  if (scalar(@output) &&
1475      $ENV{TEST_VERBOSE}) {
1476    print STDERR "Output: ", join('', @output), "\n";
1477  }
1478
1479  my $config = {
1480    PidFile => $pid_file,
1481    ScoreboardFile => $scoreboard_file,
1482    SystemLog => $log_file,
1483
1484    IfModules => {
1485      'mod_delay.c' => {
1486        DelayEngine => 'off',
1487      },
1488
1489      'mod_sql.c' => {
1490        SQLAuthTypes => 'md5',
1491        SQLBackend => 'sqlite3',
1492        SQLConnectInfo => $db_file,
1493        SQLLogFile => $log_file,
1494      },
1495
1496      'mod_sql_passwd.c' => {
1497        SQLPasswordEngine => 'off',
1498        SQLPasswordEncoding => 'base64',
1499      },
1500    },
1501  };
1502
1503  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1504
1505  # Open pipes, for use between the parent and child processes.  Specifically,
1506  # the child will indicate when it's done with its test by writing a message
1507  # to the parent.
1508  my ($rfh, $wfh);
1509  unless (pipe($rfh, $wfh)) {
1510    die("Can't open pipe: $!");
1511  }
1512
1513  my $ex;
1514
1515  # Fork child
1516  $self->handle_sigchld();
1517  defined(my $pid = fork()) or die("Can't fork: $!");
1518  if ($pid) {
1519    eval {
1520      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1521      eval { $client->login($user, "test") };
1522      unless ($@) {
1523        die("Login succeeded unexpectedly");
1524      }
1525
1526      my $resp_code = $client->response_code();
1527      my $resp_msg = $client->response_msg();
1528
1529      my $expected;
1530
1531      $expected = 530;
1532      $self->assert($expected == $resp_code,
1533        test_msg("Expected $expected, got $resp_code"));
1534
1535      $expected = "Login incorrect.";
1536      $self->assert($expected eq $resp_msg,
1537        test_msg("Expected '$expected', got '$resp_msg'"));
1538    };
1539
1540    if ($@) {
1541      $ex = $@;
1542    }
1543
1544    $wfh->print("done\n");
1545    $wfh->flush();
1546
1547  } else {
1548    eval { server_wait($config_file, $rfh) };
1549    if ($@) {
1550      warn($@);
1551      exit 1;
1552    }
1553
1554    exit 0;
1555  }
1556
1557  # Stop server
1558  server_stop($pid_file);
1559
1560  $self->assert_child_ok($pid);
1561
1562  if ($ex) {
1563    test_append_logfile($log_file, $ex);
1564    unlink($log_file);
1565
1566    die($ex);
1567  }
1568
1569  unlink($log_file);
1570}
1571
1572sub sql_passwd_salt_file {
1573  my $self = shift;
1574  my $tmpdir = $self->{tmpdir};
1575
1576  my $config_file = "$tmpdir/sqlpasswd.conf";
1577  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1578  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1579
1580  my $log_file = test_get_logfile();
1581
1582  my $salt = '8Hkqr7bnPaZ52j81VvuoWdOEuq6EeXwpiIw5Q679xzvEqwe128';
1583
1584  my $user = 'proftpd';
1585  my $group = 'ftpd';
1586
1587  # I used:
1588  #
1589  #  Digest::SHA1::sha1_hex((lc("password")) . $salt);
1590  #
1591  # to generate this password.
1592  my $passwd = '975838a6aebc87d384535df6f7226274813353aa';
1593
1594  my $home_dir = File::Spec->rel2abs($tmpdir);
1595  my $uid = 500;
1596  my $gid = 500;
1597
1598  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1599
1600  # Build up sqlite3 command to create users, groups tables and populate them
1601  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1602
1603  if (open(my $fh, "> $db_script")) {
1604    print $fh <<EOS;
1605CREATE TABLE users (
1606  userid TEXT,
1607  passwd TEXT,
1608  uid INTEGER,
1609  gid INTEGER,
1610  homedir TEXT,
1611  shell TEXT
1612);
1613INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1614
1615CREATE TABLE groups (
1616  groupname TEXT,
1617  gid INTEGER,
1618  members TEXT
1619);
1620INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1621EOS
1622
1623    unless (close($fh)) {
1624      die("Can't write $db_script: $!");
1625    }
1626
1627  } else {
1628    die("Can't open $db_script: $!");
1629  }
1630
1631  my $cmd = "sqlite3 $db_file < $db_script";
1632
1633  if ($ENV{TEST_VERBOSE}) {
1634    print STDERR "Executing sqlite3: $cmd\n";
1635  }
1636
1637  my @output = `$cmd`;
1638  if (scalar(@output) &&
1639      $ENV{TEST_VERBOSE}) {
1640    print STDERR "Output: ", join('', @output), "\n";
1641  }
1642
1643  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
1644  if (open(my $fh, "> $salt_file")) {
1645    binmode($fh);
1646    print $fh $salt;
1647
1648    unless (close($fh)) {
1649      die("Can't write $salt_file: $!");
1650    }
1651
1652  } else {
1653    die("Can't open $salt_file: $!");
1654  }
1655
1656  my $config = {
1657    PidFile => $pid_file,
1658    ScoreboardFile => $scoreboard_file,
1659    SystemLog => $log_file,
1660
1661    IfModules => {
1662      'mod_delay.c' => {
1663        DelayEngine => 'off',
1664      },
1665
1666      'mod_sql.c' => {
1667        SQLAuthTypes => 'sha1',
1668        SQLBackend => 'sqlite3',
1669        SQLConnectInfo => $db_file,
1670        SQLLogFile => $log_file,
1671      },
1672
1673      'mod_sql_passwd.c' => {
1674        SQLPasswordEngine => 'on',
1675        SQLPasswordEncoding => 'hex',
1676        SQLPasswordSaltFile => $salt_file,
1677      },
1678    },
1679  };
1680
1681  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1682
1683  # Open pipes, for use between the parent and child processes.  Specifically,
1684  # the child will indicate when it's done with its test by writing a message
1685  # to the parent.
1686  my ($rfh, $wfh);
1687  unless (pipe($rfh, $wfh)) {
1688    die("Can't open pipe: $!");
1689  }
1690
1691  my $ex;
1692
1693  # Fork child
1694  $self->handle_sigchld();
1695  defined(my $pid = fork()) or die("Can't fork: $!");
1696  if ($pid) {
1697    eval {
1698      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1699      $client->login($user, "password");
1700
1701      my $resp_msgs = $client->response_msgs();
1702      my $nmsgs = scalar(@$resp_msgs);
1703
1704      my $expected;
1705
1706      $expected = 1;
1707      $self->assert($expected == $nmsgs,
1708        test_msg("Expected $expected, got $nmsgs"));
1709
1710      $expected = "User proftpd logged in";
1711      $self->assert($expected eq $resp_msgs->[0],
1712        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
1713
1714    };
1715
1716    if ($@) {
1717      $ex = $@;
1718    }
1719
1720    $wfh->print("done\n");
1721    $wfh->flush();
1722
1723  } else {
1724    eval { server_wait($config_file, $rfh) };
1725    if ($@) {
1726      warn($@);
1727      exit 1;
1728    }
1729
1730    exit 0;
1731  }
1732
1733  # Stop server
1734  server_stop($pid_file);
1735
1736  $self->assert_child_ok($pid);
1737
1738  if ($ex) {
1739    test_append_logfile($log_file, $ex);
1740    unlink($log_file);
1741
1742    die($ex);
1743  }
1744
1745  unlink($log_file);
1746}
1747
1748sub sql_passwd_salt_file_trailing_newline {
1749  my $self = shift;
1750  my $tmpdir = $self->{tmpdir};
1751
1752  my $config_file = "$tmpdir/sqlpasswd.conf";
1753  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1754  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1755
1756  my $log_file = test_get_logfile();
1757
1758  my $salt = '8Hkqr7bnPaZ52j81VvuoWdOEuq6EeXwpiIw5Q679xzvEqwe128';
1759
1760  my $user = 'proftpd';
1761  my $group = 'ftpd';
1762
1763  # I used:
1764  #
1765  #  Digest::SHA1::sha1_hex((lc("password")) . $salt);
1766  #
1767  # to generate this password.
1768  my $passwd = '975838a6aebc87d384535df6f7226274813353aa';
1769
1770  my $home_dir = File::Spec->rel2abs($tmpdir);
1771  my $uid = 500;
1772  my $gid = 500;
1773
1774  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1775
1776  # Build up sqlite3 command to create users, groups tables and populate them
1777  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1778
1779  if (open(my $fh, "> $db_script")) {
1780    print $fh <<EOS;
1781CREATE TABLE users (
1782  userid TEXT,
1783  passwd TEXT,
1784  uid INTEGER,
1785  gid INTEGER,
1786  homedir TEXT,
1787  shell TEXT
1788);
1789INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1790
1791CREATE TABLE groups (
1792  groupname TEXT,
1793  gid INTEGER,
1794  members TEXT
1795);
1796INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1797EOS
1798
1799    unless (close($fh)) {
1800      die("Can't write $db_script: $!");
1801    }
1802
1803  } else {
1804    die("Can't open $db_script: $!");
1805  }
1806
1807  my $cmd = "sqlite3 $db_file < $db_script";
1808
1809  if ($ENV{TEST_VERBOSE}) {
1810    print STDERR "Executing sqlite3: $cmd\n";
1811  }
1812
1813  my @output = `$cmd`;
1814  if (scalar(@output) &&
1815      $ENV{TEST_VERBOSE}) {
1816    print STDERR "Output: ", join('', @output), "\n";
1817  }
1818
1819  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
1820  if (open(my $fh, "> $salt_file")) {
1821    binmode($fh);
1822
1823    # In this case, we deliberately write a trailing newline with the salt,
1824    # to make sure that mod_sql_passwd handles it.
1825    print $fh "$salt\n";
1826
1827    unless (close($fh)) {
1828      die("Can't write $salt_file: $!");
1829    }
1830
1831  } else {
1832    die("Can't open $salt_file: $!");
1833  }
1834
1835  my $config = {
1836    PidFile => $pid_file,
1837    ScoreboardFile => $scoreboard_file,
1838    SystemLog => $log_file,
1839
1840    IfModules => {
1841      'mod_delay.c' => {
1842        DelayEngine => 'off',
1843      },
1844
1845      'mod_sql.c' => {
1846        SQLAuthTypes => 'sha1',
1847        SQLBackend => 'sqlite3',
1848        SQLConnectInfo => $db_file,
1849        SQLLogFile => $log_file,
1850      },
1851
1852      'mod_sql_passwd.c' => {
1853        SQLPasswordEngine => 'on',
1854        SQLPasswordEncoding => 'hex',
1855        SQLPasswordSaltFile => $salt_file,
1856      },
1857    },
1858  };
1859
1860  my ($port, $config_user, $config_group) = config_write($config_file, $config);
1861
1862  # Open pipes, for use between the parent and child processes.  Specifically,
1863  # the child will indicate when it's done with its test by writing a message
1864  # to the parent.
1865  my ($rfh, $wfh);
1866  unless (pipe($rfh, $wfh)) {
1867    die("Can't open pipe: $!");
1868  }
1869
1870  my $ex;
1871
1872  # Fork child
1873  $self->handle_sigchld();
1874  defined(my $pid = fork()) or die("Can't fork: $!");
1875  if ($pid) {
1876    eval {
1877      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
1878      $client->login($user, "password");
1879
1880      my $resp_msgs = $client->response_msgs();
1881      my $nmsgs = scalar(@$resp_msgs);
1882
1883      my $expected;
1884
1885      $expected = 1;
1886      $self->assert($expected == $nmsgs,
1887        test_msg("Expected $expected, got $nmsgs"));
1888
1889      $expected = "User proftpd logged in";
1890      $self->assert($expected eq $resp_msgs->[0],
1891        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
1892
1893    };
1894
1895    if ($@) {
1896      $ex = $@;
1897    }
1898
1899    $wfh->print("done\n");
1900    $wfh->flush();
1901
1902  } else {
1903    eval { server_wait($config_file, $rfh) };
1904    if ($@) {
1905      warn($@);
1906      exit 1;
1907    }
1908
1909    exit 0;
1910  }
1911
1912  # Stop server
1913  server_stop($pid_file);
1914
1915  $self->assert_child_ok($pid);
1916
1917  if ($ex) {
1918    test_append_logfile($log_file, $ex);
1919    unlink($log_file);
1920
1921    die($ex);
1922  }
1923
1924  unlink($log_file);
1925}
1926
1927sub sql_passwd_salt_file_prepend {
1928  my $self = shift;
1929  my $tmpdir = $self->{tmpdir};
1930
1931  my $config_file = "$tmpdir/sqlpasswd.conf";
1932  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
1933  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
1934
1935  my $log_file = test_get_logfile();
1936
1937  my $salt = '8Hkqr7bnPaZ52j81VvuoWdOEuq6EeXwpiIw5Q679xzvEqwe128';
1938
1939  my $user = 'proftpd';
1940  my $group = 'ftpd';
1941
1942  # I used:
1943  #
1944  #  Digest::SHA1::sha1_hex($salt . lc("password"));
1945  #
1946  # to generate this password.
1947  my $passwd = 'c16542a729162ec1228919a21b36775d63391b78';
1948
1949  my $home_dir = File::Spec->rel2abs($tmpdir);
1950  my $uid = 500;
1951  my $gid = 500;
1952
1953  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
1954
1955  # Build up sqlite3 command to create users, groups tables and populate them
1956  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
1957
1958  if (open(my $fh, "> $db_script")) {
1959    print $fh <<EOS;
1960CREATE TABLE users (
1961  userid TEXT,
1962  passwd TEXT,
1963  uid INTEGER,
1964  gid INTEGER,
1965  homedir TEXT,
1966  shell TEXT
1967);
1968INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
1969
1970CREATE TABLE groups (
1971  groupname TEXT,
1972  gid INTEGER,
1973  members TEXT
1974);
1975INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
1976EOS
1977
1978    unless (close($fh)) {
1979      die("Can't write $db_script: $!");
1980    }
1981
1982  } else {
1983    die("Can't open $db_script: $!");
1984  }
1985
1986  my $cmd = "sqlite3 $db_file < $db_script";
1987
1988  if ($ENV{TEST_VERBOSE}) {
1989    print STDERR "Executing sqlite3: $cmd\n";
1990  }
1991
1992  my @output = `$cmd`;
1993  if (scalar(@output) &&
1994      $ENV{TEST_VERBOSE}) {
1995    print STDERR "Output: ", join('', @output), "\n";
1996  }
1997
1998  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
1999  if (open(my $fh, "> $salt_file")) {
2000    binmode($fh);
2001    print $fh $salt;
2002
2003    unless (close($fh)) {
2004      die("Can't write $salt_file: $!");
2005    }
2006
2007  } else {
2008    die("Can't open $salt_file: $!");
2009  }
2010
2011  my $config = {
2012    PidFile => $pid_file,
2013    ScoreboardFile => $scoreboard_file,
2014    SystemLog => $log_file,
2015
2016    IfModules => {
2017      'mod_delay.c' => {
2018        DelayEngine => 'off',
2019      },
2020
2021      'mod_sql.c' => {
2022        SQLAuthTypes => 'sha1',
2023        SQLBackend => 'sqlite3',
2024        SQLConnectInfo => $db_file,
2025        SQLLogFile => $log_file,
2026      },
2027
2028      'mod_sql_passwd.c' => {
2029        SQLPasswordEngine => 'on',
2030        SQLPasswordEncoding => 'hex',
2031        SQLPasswordSaltFile => "$salt_file Prepend",
2032      },
2033    },
2034  };
2035
2036  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2037
2038  # Open pipes, for use between the parent and child processes.  Specifically,
2039  # the child will indicate when it's done with its test by writing a message
2040  # to the parent.
2041  my ($rfh, $wfh);
2042  unless (pipe($rfh, $wfh)) {
2043    die("Can't open pipe: $!");
2044  }
2045
2046  my $ex;
2047
2048  # Fork child
2049  $self->handle_sigchld();
2050  defined(my $pid = fork()) or die("Can't fork: $!");
2051  if ($pid) {
2052    eval {
2053      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2054      $client->login($user, "password");
2055
2056      my $resp_msgs = $client->response_msgs();
2057      my $nmsgs = scalar(@$resp_msgs);
2058
2059      my $expected;
2060
2061      $expected = 1;
2062      $self->assert($expected == $nmsgs,
2063        test_msg("Expected $expected, got $nmsgs"));
2064
2065      $expected = "User proftpd logged in";
2066      $self->assert($expected eq $resp_msgs->[0],
2067        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2068
2069    };
2070
2071    if ($@) {
2072      $ex = $@;
2073    }
2074
2075    $wfh->print("done\n");
2076    $wfh->flush();
2077
2078  } else {
2079    eval { server_wait($config_file, $rfh) };
2080    if ($@) {
2081      warn($@);
2082      exit 1;
2083    }
2084
2085    exit 0;
2086  }
2087
2088  # Stop server
2089  server_stop($pid_file);
2090
2091  $self->assert_child_ok($pid);
2092
2093  if ($ex) {
2094    test_append_logfile($log_file, $ex);
2095    unlink($log_file);
2096
2097    die($ex);
2098  }
2099
2100  unlink($log_file);
2101}
2102
2103sub sql_passwd_sha256_base64_bug3344 {
2104  my $self = shift;
2105  my $tmpdir = $self->{tmpdir};
2106
2107  my $config_file = "$tmpdir/sqlpasswd.conf";
2108  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2109  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2110
2111  my $log_file = test_get_logfile();
2112
2113  my $user = 'proftpd';
2114  my $group = 'ftpd';
2115
2116  # I used:
2117  #
2118  #  `/bin/echo -n "test" | openssl dgst -binary -sha256 | openssl enc -base64`
2119  #
2120  # to generate this password.
2121  my $passwd = 'n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=';
2122
2123  my $home_dir = File::Spec->rel2abs($tmpdir);
2124  my $uid = 500;
2125  my $gid = 500;
2126
2127  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2128
2129  # Build up sqlite3 command to create users, groups tables and populate them
2130  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2131
2132  if (open(my $fh, "> $db_script")) {
2133    print $fh <<EOS;
2134CREATE TABLE users (
2135  userid TEXT,
2136  passwd TEXT,
2137  uid INTEGER,
2138  gid INTEGER,
2139  homedir TEXT,
2140  shell TEXT
2141);
2142INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2143
2144CREATE TABLE groups (
2145  groupname TEXT,
2146  gid INTEGER,
2147  members TEXT
2148);
2149INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2150EOS
2151
2152    unless (close($fh)) {
2153      die("Can't write $db_script: $!");
2154    }
2155
2156  } else {
2157    die("Can't open $db_script: $!");
2158  }
2159
2160  my $cmd = "sqlite3 $db_file < $db_script";
2161
2162  if ($ENV{TEST_VERBOSE}) {
2163    print STDERR "Executing sqlite3: $cmd\n";
2164  }
2165
2166  my @output = `$cmd`;
2167  if (scalar(@output) &&
2168      $ENV{TEST_VERBOSE}) {
2169    print STDERR "Output: ", join('', @output), "\n";
2170  }
2171
2172  my $config = {
2173    PidFile => $pid_file,
2174    ScoreboardFile => $scoreboard_file,
2175    SystemLog => $log_file,
2176
2177    IfModules => {
2178      'mod_delay.c' => {
2179        DelayEngine => 'off',
2180      },
2181
2182      'mod_sql.c' => {
2183        SQLAuthTypes => 'sha256',
2184        SQLBackend => 'sqlite3',
2185        SQLConnectInfo => $db_file,
2186        SQLLogFile => $log_file,
2187      },
2188
2189      'mod_sql_passwd.c' => {
2190        SQLPasswordEngine => 'on',
2191        SQLPasswordEncoding => 'base64',
2192      },
2193    },
2194  };
2195
2196  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2197
2198  # Open pipes, for use between the parent and child processes.  Specifically,
2199  # the child will indicate when it's done with its test by writing a message
2200  # to the parent.
2201  my ($rfh, $wfh);
2202  unless (pipe($rfh, $wfh)) {
2203    die("Can't open pipe: $!");
2204  }
2205
2206  my $ex;
2207
2208  # Fork child
2209  $self->handle_sigchld();
2210  defined(my $pid = fork()) or die("Can't fork: $!");
2211  if ($pid) {
2212    eval {
2213      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2214      $client->login($user, "test");
2215
2216      my $resp_msgs = $client->response_msgs();
2217      my $nmsgs = scalar(@$resp_msgs);
2218
2219      my $expected;
2220
2221      $expected = 1;
2222      $self->assert($expected == $nmsgs,
2223        test_msg("Expected $expected, got $nmsgs"));
2224
2225      $expected = "User proftpd logged in";
2226      $self->assert($expected eq $resp_msgs->[0],
2227        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2228
2229    };
2230
2231    if ($@) {
2232      $ex = $@;
2233    }
2234
2235    $wfh->print("done\n");
2236    $wfh->flush();
2237
2238  } else {
2239    eval { server_wait($config_file, $rfh) };
2240    if ($@) {
2241      warn($@);
2242      exit 1;
2243    }
2244
2245    exit 0;
2246  }
2247
2248  # Stop server
2249  server_stop($pid_file);
2250
2251  $self->assert_child_ok($pid);
2252
2253  if ($ex) {
2254    test_append_logfile($log_file, $ex);
2255    unlink($log_file);
2256
2257    die($ex);
2258  }
2259
2260  unlink($log_file);
2261}
2262
2263sub sql_passwd_sha256_hex_lc_bug3344 {
2264  my $self = shift;
2265  my $tmpdir = $self->{tmpdir};
2266
2267  my $config_file = "$tmpdir/sqlpasswd.conf";
2268  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2269  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2270
2271  my $log_file = test_get_logfile();
2272
2273  my $user = 'proftpd';
2274  my $group = 'ftpd';
2275
2276  # I used:
2277  #
2278  #  `/bin/echo -n "test" | openssl dgst -hex -sha256`
2279  #
2280  # to generate this password.
2281  my $passwd = '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
2282
2283  my $home_dir = File::Spec->rel2abs($tmpdir);
2284  my $uid = 500;
2285  my $gid = 500;
2286
2287  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2288
2289  # Build up sqlite3 command to create users, groups tables and populate them
2290  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2291
2292  if (open(my $fh, "> $db_script")) {
2293    print $fh <<EOS;
2294CREATE TABLE users (
2295  userid TEXT,
2296  passwd TEXT,
2297  uid INTEGER,
2298  gid INTEGER,
2299  homedir TEXT,
2300  shell TEXT
2301);
2302INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2303
2304CREATE TABLE groups (
2305  groupname TEXT,
2306  gid INTEGER,
2307  members TEXT
2308);
2309INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2310EOS
2311
2312    unless (close($fh)) {
2313      die("Can't write $db_script: $!");
2314    }
2315
2316  } else {
2317    die("Can't open $db_script: $!");
2318  }
2319
2320  my $cmd = "sqlite3 $db_file < $db_script";
2321
2322  if ($ENV{TEST_VERBOSE}) {
2323    print STDERR "Executing sqlite3: $cmd\n";
2324  }
2325
2326  my @output = `$cmd`;
2327  if (scalar(@output) &&
2328      $ENV{TEST_VERBOSE}) {
2329    print STDERR "Output: ", join('', @output), "\n";
2330  }
2331
2332  my $config = {
2333    PidFile => $pid_file,
2334    ScoreboardFile => $scoreboard_file,
2335    SystemLog => $log_file,
2336
2337    IfModules => {
2338      'mod_delay.c' => {
2339        DelayEngine => 'off',
2340      },
2341
2342      'mod_sql.c' => {
2343        SQLAuthTypes => 'sha256',
2344        SQLBackend => 'sqlite3',
2345        SQLConnectInfo => $db_file,
2346        SQLLogFile => $log_file,
2347      },
2348
2349      'mod_sql_passwd.c' => {
2350        SQLPasswordEngine => 'on',
2351        SQLPasswordEncoding => 'hex',
2352      },
2353    },
2354  };
2355
2356  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2357
2358  # Open pipes, for use between the parent and child processes.  Specifically,
2359  # the child will indicate when it's done with its test by writing a message
2360  # to the parent.
2361  my ($rfh, $wfh);
2362  unless (pipe($rfh, $wfh)) {
2363    die("Can't open pipe: $!");
2364  }
2365
2366  my $ex;
2367
2368  # Fork child
2369  $self->handle_sigchld();
2370  defined(my $pid = fork()) or die("Can't fork: $!");
2371  if ($pid) {
2372    eval {
2373      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2374      $client->login($user, "test");
2375
2376      my $resp_msgs = $client->response_msgs();
2377      my $nmsgs = scalar(@$resp_msgs);
2378
2379      my $expected;
2380
2381      $expected = 1;
2382      $self->assert($expected == $nmsgs,
2383        test_msg("Expected $expected, got $nmsgs"));
2384
2385      $expected = "User proftpd logged in";
2386      $self->assert($expected eq $resp_msgs->[0],
2387        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2388
2389    };
2390
2391    if ($@) {
2392      $ex = $@;
2393    }
2394
2395    $wfh->print("done\n");
2396    $wfh->flush();
2397
2398  } else {
2399    eval { server_wait($config_file, $rfh) };
2400    if ($@) {
2401      warn($@);
2402      exit 1;
2403    }
2404
2405    exit 0;
2406  }
2407
2408  # Stop server
2409  server_stop($pid_file);
2410
2411  $self->assert_child_ok($pid);
2412
2413  if ($ex) {
2414    test_append_logfile($log_file, $ex);
2415    unlink($log_file);
2416
2417    die($ex);
2418  }
2419
2420  unlink($log_file);
2421}
2422
2423sub sql_passwd_sha256_hex_uc_bug3344 {
2424  my $self = shift;
2425  my $tmpdir = $self->{tmpdir};
2426
2427  my $config_file = "$tmpdir/sqlpasswd.conf";
2428  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2429  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2430
2431  my $log_file = test_get_logfile();
2432
2433  my $user = 'proftpd';
2434  my $group = 'ftpd';
2435
2436  # I used:
2437  #
2438  #  `/bin/echo -n "test" | openssl dgst -hex -sha256`
2439  #
2440  # to generate this password.  Then I manually made all of the letters be
2441  # in uppercase.  Tedious.
2442  my $passwd = '9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08';
2443
2444  my $home_dir = File::Spec->rel2abs($tmpdir);
2445  my $uid = 500;
2446  my $gid = 500;
2447
2448  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2449
2450  # Build up sqlite3 command to create users, groups tables and populate them
2451  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2452
2453  if (open(my $fh, "> $db_script")) {
2454    print $fh <<EOS;
2455CREATE TABLE users (
2456  userid TEXT,
2457  passwd TEXT,
2458  uid INTEGER,
2459  gid INTEGER,
2460  homedir TEXT,
2461  shell TEXT
2462);
2463INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2464
2465CREATE TABLE groups (
2466  groupname TEXT,
2467  gid INTEGER,
2468  members TEXT
2469);
2470INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2471EOS
2472
2473    unless (close($fh)) {
2474      die("Can't write $db_script: $!");
2475    }
2476
2477  } else {
2478    die("Can't open $db_script: $!");
2479  }
2480
2481  my $cmd = "sqlite3 $db_file < $db_script";
2482
2483  if ($ENV{TEST_VERBOSE}) {
2484    print STDERR "Executing sqlite3: $cmd\n";
2485  }
2486
2487  my @output = `$cmd`;
2488  if (scalar(@output) &&
2489      $ENV{TEST_VERBOSE}) {
2490    print STDERR "Output: ", join('', @output), "\n";
2491  }
2492
2493  my $config = {
2494    PidFile => $pid_file,
2495    ScoreboardFile => $scoreboard_file,
2496    SystemLog => $log_file,
2497
2498    IfModules => {
2499      'mod_delay.c' => {
2500        DelayEngine => 'off',
2501      },
2502
2503      'mod_sql.c' => {
2504        SQLAuthTypes => 'sha256',
2505        SQLBackend => 'sqlite3',
2506        SQLConnectInfo => $db_file,
2507        SQLLogFile => $log_file,
2508      },
2509
2510      'mod_sql_passwd.c' => {
2511        SQLPasswordEngine => 'on',
2512        SQLPasswordEncoding => 'HEX',
2513      },
2514    },
2515  };
2516
2517  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2518
2519  # Open pipes, for use between the parent and child processes.  Specifically,
2520  # the child will indicate when it's done with its test by writing a message
2521  # to the parent.
2522  my ($rfh, $wfh);
2523  unless (pipe($rfh, $wfh)) {
2524    die("Can't open pipe: $!");
2525  }
2526
2527  my $ex;
2528
2529  # Fork child
2530  $self->handle_sigchld();
2531  defined(my $pid = fork()) or die("Can't fork: $!");
2532  if ($pid) {
2533    eval {
2534      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2535      $client->login($user, "test");
2536
2537      my $resp_msgs = $client->response_msgs();
2538      my $nmsgs = scalar(@$resp_msgs);
2539
2540      my $expected;
2541
2542      $expected = 1;
2543      $self->assert($expected == $nmsgs,
2544        test_msg("Expected $expected, got $nmsgs"));
2545
2546      $expected = "User proftpd logged in";
2547      $self->assert($expected eq $resp_msgs->[0],
2548        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2549
2550    };
2551
2552    if ($@) {
2553      $ex = $@;
2554    }
2555
2556    $wfh->print("done\n");
2557    $wfh->flush();
2558
2559  } else {
2560    eval { server_wait($config_file, $rfh) };
2561    if ($@) {
2562      warn($@);
2563      exit 1;
2564    }
2565
2566    exit 0;
2567  }
2568
2569  # Stop server
2570  server_stop($pid_file);
2571
2572  $self->assert_child_ok($pid);
2573
2574  if ($ex) {
2575    test_append_logfile($log_file, $ex);
2576    unlink($log_file);
2577
2578    die($ex);
2579  }
2580
2581  unlink($log_file);
2582}
2583
2584sub sql_passwd_sha512_base64_bug3344 {
2585  my $self = shift;
2586  my $tmpdir = $self->{tmpdir};
2587
2588  my $config_file = "$tmpdir/sqlpasswd.conf";
2589  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2590  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2591
2592  my $log_file = test_get_logfile();
2593
2594  my $user = 'proftpd';
2595  my $group = 'ftpd';
2596
2597  # I used:
2598  #
2599  #  `/bin/echo -n "test" | openssl dgst -binary -sha512 | openssl enc -base64 -A`
2600  #
2601  # to generate this password.
2602  my $passwd = '7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==';
2603
2604  my $home_dir = File::Spec->rel2abs($tmpdir);
2605  my $uid = 500;
2606  my $gid = 500;
2607
2608  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2609
2610  # Build up sqlite3 command to create users, groups tables and populate them
2611  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2612
2613  if (open(my $fh, "> $db_script")) {
2614    print $fh <<EOS;
2615CREATE TABLE users (
2616  userid TEXT,
2617  passwd TEXT,
2618  uid INTEGER,
2619  gid INTEGER,
2620  homedir TEXT,
2621  shell TEXT
2622);
2623INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2624
2625CREATE TABLE groups (
2626  groupname TEXT,
2627  gid INTEGER,
2628  members TEXT
2629);
2630INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2631EOS
2632
2633    unless (close($fh)) {
2634      die("Can't write $db_script: $!");
2635    }
2636
2637  } else {
2638    die("Can't open $db_script: $!");
2639  }
2640
2641  my $cmd = "sqlite3 $db_file < $db_script";
2642
2643  if ($ENV{TEST_VERBOSE}) {
2644    print STDERR "Executing sqlite3: $cmd\n";
2645  }
2646
2647  my @output = `$cmd`;
2648  if (scalar(@output) &&
2649      $ENV{TEST_VERBOSE}) {
2650    print STDERR "Output: ", join('', @output), "\n";
2651  }
2652
2653  my $config = {
2654    PidFile => $pid_file,
2655    ScoreboardFile => $scoreboard_file,
2656    SystemLog => $log_file,
2657
2658    IfModules => {
2659      'mod_delay.c' => {
2660        DelayEngine => 'off',
2661      },
2662
2663      'mod_sql.c' => {
2664        SQLAuthTypes => 'sha512',
2665        SQLBackend => 'sqlite3',
2666        SQLConnectInfo => $db_file,
2667        SQLLogFile => $log_file,
2668      },
2669
2670      'mod_sql_passwd.c' => {
2671        SQLPasswordEngine => 'on',
2672        SQLPasswordEncoding => 'base64',
2673      },
2674    },
2675  };
2676
2677  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2678
2679  # Open pipes, for use between the parent and child processes.  Specifically,
2680  # the child will indicate when it's done with its test by writing a message
2681  # to the parent.
2682  my ($rfh, $wfh);
2683  unless (pipe($rfh, $wfh)) {
2684    die("Can't open pipe: $!");
2685  }
2686
2687  my $ex;
2688
2689  # Fork child
2690  $self->handle_sigchld();
2691  defined(my $pid = fork()) or die("Can't fork: $!");
2692  if ($pid) {
2693    eval {
2694      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2695      $client->login($user, "test");
2696
2697      my $resp_msgs = $client->response_msgs();
2698      my $nmsgs = scalar(@$resp_msgs);
2699
2700      my $expected;
2701
2702      $expected = 1;
2703      $self->assert($expected == $nmsgs,
2704        test_msg("Expected $expected, got $nmsgs"));
2705
2706      $expected = "User proftpd logged in";
2707      $self->assert($expected eq $resp_msgs->[0],
2708        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2709
2710    };
2711
2712    if ($@) {
2713      $ex = $@;
2714    }
2715
2716    $wfh->print("done\n");
2717    $wfh->flush();
2718
2719  } else {
2720    eval { server_wait($config_file, $rfh) };
2721    if ($@) {
2722      warn($@);
2723      exit 1;
2724    }
2725
2726    exit 0;
2727  }
2728
2729  # Stop server
2730  server_stop($pid_file);
2731
2732  $self->assert_child_ok($pid);
2733
2734  if ($ex) {
2735    test_append_logfile($log_file, $ex);
2736    unlink($log_file);
2737
2738    die($ex);
2739  }
2740
2741  unlink($log_file);
2742}
2743
2744sub sql_passwd_sha512_hex_lc_bug3344 {
2745  my $self = shift;
2746  my $tmpdir = $self->{tmpdir};
2747
2748  my $config_file = "$tmpdir/sqlpasswd.conf";
2749  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2750  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2751
2752  my $log_file = test_get_logfile();
2753
2754  my $user = 'proftpd';
2755  my $group = 'ftpd';
2756
2757  # I used:
2758  #
2759  #  `/bin/echo -n "test" | openssl dgst -hex -sha512`
2760  #
2761  # to generate this password.
2762  my $passwd = 'ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff';
2763
2764  my $home_dir = File::Spec->rel2abs($tmpdir);
2765  my $uid = 500;
2766  my $gid = 500;
2767
2768  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2769
2770  # Build up sqlite3 command to create users, groups tables and populate them
2771  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2772
2773  if (open(my $fh, "> $db_script")) {
2774    print $fh <<EOS;
2775CREATE TABLE users (
2776  userid TEXT,
2777  passwd TEXT,
2778  uid INTEGER,
2779  gid INTEGER,
2780  homedir TEXT,
2781  shell TEXT
2782);
2783INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2784
2785CREATE TABLE groups (
2786  groupname TEXT,
2787  gid INTEGER,
2788  members TEXT
2789);
2790INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2791EOS
2792
2793    unless (close($fh)) {
2794      die("Can't write $db_script: $!");
2795    }
2796
2797  } else {
2798    die("Can't open $db_script: $!");
2799  }
2800
2801  my $cmd = "sqlite3 $db_file < $db_script";
2802
2803  if ($ENV{TEST_VERBOSE}) {
2804    print STDERR "Executing sqlite3: $cmd\n";
2805  }
2806
2807  my @output = `$cmd`;
2808  if (scalar(@output) &&
2809      $ENV{TEST_VERBOSE}) {
2810    print STDERR "Output: ", join('', @output), "\n";
2811  }
2812
2813  my $config = {
2814    PidFile => $pid_file,
2815    ScoreboardFile => $scoreboard_file,
2816    SystemLog => $log_file,
2817
2818    IfModules => {
2819      'mod_delay.c' => {
2820        DelayEngine => 'off',
2821      },
2822
2823      'mod_sql.c' => {
2824        SQLAuthTypes => 'sha512',
2825        SQLBackend => 'sqlite3',
2826        SQLConnectInfo => $db_file,
2827        SQLLogFile => $log_file,
2828      },
2829
2830      'mod_sql_passwd.c' => {
2831        SQLPasswordEngine => 'on',
2832        SQLPasswordEncoding => 'hex',
2833      },
2834    },
2835  };
2836
2837  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2838
2839  # Open pipes, for use between the parent and child processes.  Specifically,
2840  # the child will indicate when it's done with its test by writing a message
2841  # to the parent.
2842  my ($rfh, $wfh);
2843  unless (pipe($rfh, $wfh)) {
2844    die("Can't open pipe: $!");
2845  }
2846
2847  my $ex;
2848
2849  # Fork child
2850  $self->handle_sigchld();
2851  defined(my $pid = fork()) or die("Can't fork: $!");
2852  if ($pid) {
2853    eval {
2854      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
2855      $client->login($user, "test");
2856
2857      my $resp_msgs = $client->response_msgs();
2858      my $nmsgs = scalar(@$resp_msgs);
2859
2860      my $expected;
2861
2862      $expected = 1;
2863      $self->assert($expected == $nmsgs,
2864        test_msg("Expected $expected, got $nmsgs"));
2865
2866      $expected = "User proftpd logged in";
2867      $self->assert($expected eq $resp_msgs->[0],
2868        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
2869
2870    };
2871
2872    if ($@) {
2873      $ex = $@;
2874    }
2875
2876    $wfh->print("done\n");
2877    $wfh->flush();
2878
2879  } else {
2880    eval { server_wait($config_file, $rfh) };
2881    if ($@) {
2882      warn($@);
2883      exit 1;
2884    }
2885
2886    exit 0;
2887  }
2888
2889  # Stop server
2890  server_stop($pid_file);
2891
2892  $self->assert_child_ok($pid);
2893
2894  if ($ex) {
2895    test_append_logfile($log_file, $ex);
2896    unlink($log_file);
2897
2898    die($ex);
2899  }
2900
2901  unlink($log_file);
2902}
2903
2904sub sql_passwd_sha512_hex_uc_bug3344 {
2905  my $self = shift;
2906  my $tmpdir = $self->{tmpdir};
2907
2908  my $config_file = "$tmpdir/sqlpasswd.conf";
2909  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
2910  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
2911
2912  my $log_file = test_get_logfile();
2913
2914  my $user = 'proftpd';
2915  my $group = 'ftpd';
2916
2917  # I used:
2918  #
2919  #  `/bin/echo -n "test" | openssl dgst -hex -sha512`
2920  #
2921  # to generate this password.  Then I manually made all of the letters be
2922  # in uppercase.  Tedious.
2923  my $passwd = 'EE26B0DD4AF7E749AA1A8EE3C10AE9923F618980772E473F8819A5D4940E0DB27AC185F8A0E1D5F84F88BC887FD67B143732C304CC5FA9AD8E6F57F50028A8FF';
2924
2925  my $home_dir = File::Spec->rel2abs($tmpdir);
2926  my $uid = 500;
2927  my $gid = 500;
2928
2929  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
2930
2931  # Build up sqlite3 command to create users, groups tables and populate them
2932  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
2933
2934  if (open(my $fh, "> $db_script")) {
2935    print $fh <<EOS;
2936CREATE TABLE users (
2937  userid TEXT,
2938  passwd TEXT,
2939  uid INTEGER,
2940  gid INTEGER,
2941  homedir TEXT,
2942  shell TEXT
2943);
2944INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
2945
2946CREATE TABLE groups (
2947  groupname TEXT,
2948  gid INTEGER,
2949  members TEXT
2950);
2951INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
2952EOS
2953
2954    unless (close($fh)) {
2955      die("Can't write $db_script: $!");
2956    }
2957
2958  } else {
2959    die("Can't open $db_script: $!");
2960  }
2961
2962  my $cmd = "sqlite3 $db_file < $db_script";
2963
2964  if ($ENV{TEST_VERBOSE}) {
2965    print STDERR "Executing sqlite3: $cmd\n";
2966  }
2967
2968  my @output = `$cmd`;
2969  if (scalar(@output) &&
2970      $ENV{TEST_VERBOSE}) {
2971    print STDERR "Output: ", join('', @output), "\n";
2972  }
2973
2974  my $config = {
2975    PidFile => $pid_file,
2976    ScoreboardFile => $scoreboard_file,
2977    SystemLog => $log_file,
2978
2979    IfModules => {
2980      'mod_delay.c' => {
2981        DelayEngine => 'off',
2982      },
2983
2984      'mod_sql.c' => {
2985        SQLAuthTypes => 'sha512',
2986        SQLBackend => 'sqlite3',
2987        SQLConnectInfo => $db_file,
2988        SQLLogFile => $log_file,
2989      },
2990
2991      'mod_sql_passwd.c' => {
2992        SQLPasswordEngine => 'on',
2993        SQLPasswordEncoding => 'HEX',
2994      },
2995    },
2996  };
2997
2998  my ($port, $config_user, $config_group) = config_write($config_file, $config);
2999
3000  # Open pipes, for use between the parent and child processes.  Specifically,
3001  # the child will indicate when it's done with its test by writing a message
3002  # to the parent.
3003  my ($rfh, $wfh);
3004  unless (pipe($rfh, $wfh)) {
3005    die("Can't open pipe: $!");
3006  }
3007
3008  my $ex;
3009
3010  # Fork child
3011  $self->handle_sigchld();
3012  defined(my $pid = fork()) or die("Can't fork: $!");
3013  if ($pid) {
3014    eval {
3015      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3016      $client->login($user, "test");
3017
3018      my $resp_msgs = $client->response_msgs();
3019      my $nmsgs = scalar(@$resp_msgs);
3020
3021      my $expected;
3022
3023      $expected = 1;
3024      $self->assert($expected == $nmsgs,
3025        test_msg("Expected $expected, got $nmsgs"));
3026
3027      $expected = "User proftpd logged in";
3028      $self->assert($expected eq $resp_msgs->[0],
3029        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3030
3031    };
3032
3033    if ($@) {
3034      $ex = $@;
3035    }
3036
3037    $wfh->print("done\n");
3038    $wfh->flush();
3039
3040  } else {
3041    eval { server_wait($config_file, $rfh) };
3042    if ($@) {
3043      warn($@);
3044      exit 1;
3045    }
3046
3047    exit 0;
3048  }
3049
3050  # Stop server
3051  server_stop($pid_file);
3052
3053  $self->assert_child_ok($pid);
3054
3055  if ($ex) {
3056    test_append_logfile($log_file, $ex);
3057    unlink($log_file);
3058
3059    die($ex);
3060  }
3061
3062  unlink($log_file);
3063}
3064
3065sub sql_passwd_user_salt_name {
3066  my $self = shift;
3067  my $tmpdir = $self->{tmpdir};
3068
3069  my $config_file = "$tmpdir/sqlpasswd.conf";
3070  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3071  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3072
3073  my $log_file = test_get_logfile();
3074
3075  my $user = 'proftpd';
3076  my $group = 'ftpd';
3077
3078  # I used:
3079  #
3080  #  Digest::SHA1::sha1_hex((lc("password")) . $user);
3081  #
3082  # to generate this password.
3083  my $passwd = '0934e2799b96d7f93fdfaaa13853dfa291e09cb1';
3084
3085  my $home_dir = File::Spec->rel2abs($tmpdir);
3086  my $uid = 500;
3087  my $gid = 500;
3088
3089  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3090
3091  # Build up sqlite3 command to create users, groups tables and populate them
3092  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3093
3094  if (open(my $fh, "> $db_script")) {
3095    print $fh <<EOS;
3096CREATE TABLE users (
3097  userid TEXT,
3098  passwd TEXT,
3099  uid INTEGER,
3100  gid INTEGER,
3101  homedir TEXT,
3102  shell TEXT
3103);
3104INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
3105
3106CREATE TABLE groups (
3107  groupname TEXT,
3108  gid INTEGER,
3109  members TEXT
3110);
3111INSERT INTO groups (groupname, gid, members) VALUES ('ftpd', $gid, '$user');
3112EOS
3113
3114    unless (close($fh)) {
3115      die("Can't write $db_script: $!");
3116    }
3117
3118  } else {
3119    die("Can't open $db_script: $!");
3120  }
3121
3122  my $cmd = "sqlite3 $db_file < $db_script";
3123
3124  if ($ENV{TEST_VERBOSE}) {
3125    print STDERR "Executing sqlite3: $cmd\n";
3126  }
3127
3128  my @output = `$cmd`;
3129  if (scalar(@output) &&
3130      $ENV{TEST_VERBOSE}) {
3131    print STDERR "Output: ", join('', @output), "\n";
3132  }
3133
3134  my $config = {
3135    PidFile => $pid_file,
3136    ScoreboardFile => $scoreboard_file,
3137    SystemLog => $log_file,
3138
3139    IfModules => {
3140      'mod_delay.c' => {
3141        DelayEngine => 'off',
3142      },
3143
3144      'mod_sql.c' => {
3145        SQLAuthTypes => 'sha1',
3146        SQLBackend => 'sqlite3',
3147        SQLConnectInfo => $db_file,
3148        SQLLogFile => $log_file,
3149      },
3150
3151      'mod_sql_passwd.c' => {
3152        SQLPasswordEngine => 'on',
3153        SQLPasswordEncoding => 'hex',
3154        SQLPasswordUserSalt => 'name',
3155      },
3156    },
3157  };
3158
3159  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3160
3161  # Open pipes, for use between the parent and child processes.  Specifically,
3162  # the child will indicate when it's done with its test by writing a message
3163  # to the parent.
3164  my ($rfh, $wfh);
3165  unless (pipe($rfh, $wfh)) {
3166    die("Can't open pipe: $!");
3167  }
3168
3169  my $ex;
3170
3171  # Fork child
3172  $self->handle_sigchld();
3173  defined(my $pid = fork()) or die("Can't fork: $!");
3174  if ($pid) {
3175    eval {
3176      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3177      $client->login($user, "password");
3178
3179      my $resp_msgs = $client->response_msgs();
3180      my $nmsgs = scalar(@$resp_msgs);
3181
3182      my $expected;
3183
3184      $expected = 1;
3185      $self->assert($expected == $nmsgs,
3186        test_msg("Expected $expected, got $nmsgs"));
3187
3188      $expected = "User proftpd logged in";
3189      $self->assert($expected eq $resp_msgs->[0],
3190        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3191
3192    };
3193
3194    if ($@) {
3195      $ex = $@;
3196    }
3197
3198    $wfh->print("done\n");
3199    $wfh->flush();
3200
3201  } else {
3202    eval { server_wait($config_file, $rfh) };
3203    if ($@) {
3204      warn($@);
3205      exit 1;
3206    }
3207
3208    exit 0;
3209  }
3210
3211  # Stop server
3212  server_stop($pid_file);
3213
3214  $self->assert_child_ok($pid);
3215
3216  if ($ex) {
3217    test_append_logfile($log_file, $ex);
3218    unlink($log_file);
3219
3220    die($ex);
3221  }
3222
3223  unlink($log_file);
3224}
3225
3226sub sql_passwd_user_salt_sql {
3227  my $self = shift;
3228  my $tmpdir = $self->{tmpdir};
3229
3230  my $config_file = "$tmpdir/sqlpasswd.conf";
3231  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3232  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3233
3234  my $log_file = test_get_logfile();
3235
3236  my $user = 'proftpd';
3237
3238  my $salt = 'MyS00p3r$3kr3t$@lt';
3239
3240  # I used:
3241  #
3242  #  Digest::SHA1::sha1_hex((lc("password")) . $salt);
3243  #
3244  # to generate this password.
3245  my $passwd = 'cbaae8ec99dad240e86b64c66d31272b39a87e2e';
3246
3247  my $home_dir = File::Spec->rel2abs($tmpdir);
3248  my $uid = 500;
3249  my $gid = 500;
3250
3251  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3252
3253  # Build up sqlite3 command to create users, groups tables and populate them
3254  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3255
3256  if (open(my $fh, "> $db_script")) {
3257    print $fh <<EOS;
3258CREATE TABLE users (
3259  userid TEXT,
3260  passwd TEXT,
3261  uid INTEGER,
3262  gid INTEGER,
3263  homedir TEXT,
3264  shell TEXT
3265);
3266INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
3267
3268CREATE TABLE groups (
3269  groupname TEXT,
3270  gid INTEGER,
3271  members TEXT
3272);
3273INSERT INTO groups (groupname, gid, members) VALUES ('ftpd', $gid, '$user');
3274
3275CREATE TABLE user_salts (
3276  userid TEXT,
3277  salt TEXT
3278);
3279INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
3280EOS
3281
3282    unless (close($fh)) {
3283      die("Can't write $db_script: $!");
3284    }
3285
3286  } else {
3287    die("Can't open $db_script: $!");
3288  }
3289
3290  my $cmd = "sqlite3 $db_file < $db_script";
3291
3292  if ($ENV{TEST_VERBOSE}) {
3293    print STDERR "Executing sqlite3: $cmd\n";
3294  }
3295
3296  my @output = `$cmd`;
3297  if (scalar(@output) &&
3298      $ENV{TEST_VERBOSE}) {
3299    print STDERR "Output: ", join('', @output), "\n";
3300  }
3301
3302  my $config = {
3303    PidFile => $pid_file,
3304    ScoreboardFile => $scoreboard_file,
3305    SystemLog => $log_file,
3306
3307    IfModules => {
3308      'mod_delay.c' => {
3309        DelayEngine => 'off',
3310      },
3311
3312      'mod_sql.c' => {
3313        SQLAuthTypes => 'sha1',
3314        SQLBackend => 'sqlite3',
3315        SQLConnectInfo => $db_file,
3316        SQLLogFile => $log_file,
3317        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
3318      },
3319
3320      'mod_sql_passwd.c' => {
3321        SQLPasswordEngine => 'on',
3322        SQLPasswordEncoding => 'hex',
3323        SQLPasswordUserSalt => 'sql:/get-user-salt',
3324      },
3325    },
3326  };
3327
3328  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3329
3330  # Open pipes, for use between the parent and child processes.  Specifically,
3331  # the child will indicate when it's done with its test by writing a message
3332  # to the parent.
3333  my ($rfh, $wfh);
3334  unless (pipe($rfh, $wfh)) {
3335    die("Can't open pipe: $!");
3336  }
3337
3338  my $ex;
3339
3340  # Fork child
3341  $self->handle_sigchld();
3342  defined(my $pid = fork()) or die("Can't fork: $!");
3343  if ($pid) {
3344    eval {
3345      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3346      $client->login($user, "password");
3347
3348      my $resp_msgs = $client->response_msgs();
3349      my $nmsgs = scalar(@$resp_msgs);
3350
3351      my $expected;
3352
3353      $expected = 1;
3354      $self->assert($expected == $nmsgs,
3355        test_msg("Expected $expected, got $nmsgs"));
3356
3357      $expected = "User proftpd logged in";
3358      $self->assert($expected eq $resp_msgs->[0],
3359        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3360
3361    };
3362
3363    if ($@) {
3364      $ex = $@;
3365    }
3366
3367    $wfh->print("done\n");
3368    $wfh->flush();
3369
3370  } else {
3371    eval { server_wait($config_file, $rfh) };
3372    if ($@) {
3373      warn($@);
3374      exit 1;
3375    }
3376
3377    exit 0;
3378  }
3379
3380  # Stop server
3381  server_stop($pid_file);
3382
3383  $self->assert_child_ok($pid);
3384
3385  if ($ex) {
3386    test_append_logfile($log_file, $ex);
3387    unlink($log_file);
3388
3389    die($ex);
3390  }
3391
3392  unlink($log_file);
3393}
3394
3395sub sql_passwd_md5_hash_encode_salt_password_bug3500 {
3396  my $self = shift;
3397  my $tmpdir = $self->{tmpdir};
3398
3399  my $config_file = "$tmpdir/sqlpasswd.conf";
3400  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3401  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3402
3403  my $log_file = test_get_logfile();
3404
3405  my $user = 'proftpd';
3406
3407  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
3408
3409  my $passwd = 'password';
3410  my $salt = ':(Km-';
3411
3412  # I used:
3413  #
3414  #  Digest::MD5::md5_hex(Digest::MD5::md5_hex($salt) .
3415  #                       Digest::MD5::md5_hex($passwd));
3416  #
3417  # to generate this password.
3418  my $db_passwd = 'e434ada7d8d3db4924d3b2bfe3bf1ce4';
3419
3420  my $group = 'ftpd';
3421  my $home_dir = File::Spec->rel2abs($tmpdir);
3422  my $uid = 500;
3423  my $gid = 500;
3424
3425  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3426
3427  # Build up sqlite3 command to create users, groups tables and populate them
3428  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3429
3430  if (open(my $fh, "> $db_script")) {
3431    print $fh <<EOS;
3432CREATE TABLE users (
3433  userid TEXT,
3434  passwd TEXT,
3435  uid INTEGER,
3436  gid INTEGER,
3437  homedir TEXT,
3438  shell TEXT
3439);
3440INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
3441
3442CREATE TABLE groups (
3443  groupname TEXT,
3444  gid INTEGER,
3445  members TEXT
3446);
3447INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
3448
3449CREATE TABLE user_salts (
3450  userid TEXT,
3451  salt TEXT
3452);
3453INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
3454EOS
3455
3456    unless (close($fh)) {
3457      die("Can't write $db_script: $!");
3458    }
3459
3460  } else {
3461    die("Can't open $db_script: $!");
3462  }
3463
3464  my $cmd = "sqlite3 $db_file < $db_script";
3465
3466  if ($ENV{TEST_VERBOSE}) {
3467    print STDERR "Executing sqlite3: $cmd\n";
3468  }
3469
3470  my @output = `$cmd`;
3471  if (scalar(@output) &&
3472      $ENV{TEST_VERBOSE}) {
3473    print STDERR "Output: ", join('', @output), "\n";
3474  }
3475
3476  my $config = {
3477    PidFile => $pid_file,
3478    ScoreboardFile => $scoreboard_file,
3479    SystemLog => $log_file,
3480
3481    IfModules => {
3482      'mod_delay.c' => {
3483        DelayEngine => 'off',
3484      },
3485
3486      'mod_sql.c' => {
3487        SQLAuthTypes => 'md5',
3488        SQLBackend => 'sqlite3',
3489        SQLConnectInfo => $db_file,
3490        SQLLogFile => $log_file,
3491        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
3492        SQLMinID => '100',
3493      },
3494
3495      'mod_sql_passwd.c' => {
3496        SQLPasswordEngine => 'on',
3497        SQLPasswordEncoding => 'hex',
3498        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
3499
3500        # Tell mod_sql_passwd to transform both the salt and the password
3501        # before transforming the combination of them.
3502        SQLPasswordOptions => 'HashEncodeSalt HashEncodePassword',
3503      },
3504    },
3505  };
3506
3507  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3508
3509  # Open pipes, for use between the parent and child processes.  Specifically,
3510  # the child will indicate when it's done with its test by writing a message
3511  # to the parent.
3512  my ($rfh, $wfh);
3513  unless (pipe($rfh, $wfh)) {
3514    die("Can't open pipe: $!");
3515  }
3516
3517  my $ex;
3518
3519  # Fork child
3520  $self->handle_sigchld();
3521  defined(my $pid = fork()) or die("Can't fork: $!");
3522  if ($pid) {
3523    eval {
3524      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3525      $client->login($user, $passwd);
3526
3527      my $resp_msgs = $client->response_msgs();
3528      my $nmsgs = scalar(@$resp_msgs);
3529
3530      my $expected;
3531
3532      $expected = 1;
3533      $self->assert($expected == $nmsgs,
3534        test_msg("Expected $expected, got $nmsgs"));
3535
3536      $expected = "User proftpd logged in";
3537      $self->assert($expected eq $resp_msgs->[0],
3538        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3539
3540    };
3541
3542    if ($@) {
3543      $ex = $@;
3544    }
3545
3546    $wfh->print("done\n");
3547    $wfh->flush();
3548
3549  } else {
3550    eval { server_wait($config_file, $rfh) };
3551    if ($@) {
3552      warn($@);
3553      exit 1;
3554    }
3555
3556    exit 0;
3557  }
3558
3559  # Stop server
3560  server_stop($pid_file);
3561
3562  $self->assert_child_ok($pid);
3563
3564  if ($ex) {
3565    test_append_logfile($log_file, $ex);
3566    unlink($log_file);
3567
3568    die($ex);
3569  }
3570
3571  unlink($log_file);
3572}
3573
3574sub sql_passwd_md5_hash_encode_password_bug3500 {
3575  my $self = shift;
3576  my $tmpdir = $self->{tmpdir};
3577
3578  my $config_file = "$tmpdir/sqlpasswd.conf";
3579  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3580  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3581
3582  my $log_file = test_get_logfile();
3583
3584  my $user = 'proftpd';
3585
3586  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
3587
3588  my $passwd = 'password';
3589
3590  # I used:
3591  #
3592  #  Digest::MD5::md5_hex($passwd)
3593  #
3594  # to generate this password.
3595  my $db_passwd = '5f4dcc3b5aa765d61d8327deb882cf99';
3596
3597  my $group = 'ftpd';
3598  my $home_dir = File::Spec->rel2abs($tmpdir);
3599  my $uid = 500;
3600  my $gid = 500;
3601
3602  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3603
3604  # Build up sqlite3 command to create users, groups tables and populate them
3605  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3606
3607  if (open(my $fh, "> $db_script")) {
3608    print $fh <<EOS;
3609CREATE TABLE users (
3610  userid TEXT,
3611  passwd TEXT,
3612  uid INTEGER,
3613  gid INTEGER,
3614  homedir TEXT,
3615  shell TEXT
3616);
3617INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
3618
3619CREATE TABLE groups (
3620  groupname TEXT,
3621  gid INTEGER,
3622  members TEXT
3623);
3624INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
3625
3626EOS
3627
3628    unless (close($fh)) {
3629      die("Can't write $db_script: $!");
3630    }
3631
3632  } else {
3633    die("Can't open $db_script: $!");
3634  }
3635
3636  my $cmd = "sqlite3 $db_file < $db_script";
3637
3638  if ($ENV{TEST_VERBOSE}) {
3639    print STDERR "Executing sqlite3: $cmd\n";
3640  }
3641
3642  my @output = `$cmd`;
3643  if (scalar(@output) &&
3644      $ENV{TEST_VERBOSE}) {
3645    print STDERR "Output: ", join('', @output), "\n";
3646  }
3647
3648  my $config = {
3649    PidFile => $pid_file,
3650    ScoreboardFile => $scoreboard_file,
3651    SystemLog => $log_file,
3652
3653    IfModules => {
3654      'mod_delay.c' => {
3655        DelayEngine => 'off',
3656      },
3657
3658      'mod_sql.c' => {
3659        SQLAuthTypes => 'md5',
3660        SQLBackend => 'sqlite3',
3661        SQLConnectInfo => $db_file,
3662        SQLLogFile => $log_file,
3663        SQLMinID => '100',
3664      },
3665
3666      'mod_sql_passwd.c' => {
3667        SQLPasswordEngine => 'on',
3668        SQLPasswordEncoding => 'hex',
3669
3670        # Tell mod_sql_passwd to transform the password
3671        # before transforming the combination of them.
3672        SQLPasswordOptions => 'HashEncodePassword',
3673      },
3674    },
3675  };
3676
3677  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3678
3679  # Open pipes, for use between the parent and child processes.  Specifically,
3680  # the child will indicate when it's done with its test by writing a message
3681  # to the parent.
3682  my ($rfh, $wfh);
3683  unless (pipe($rfh, $wfh)) {
3684    die("Can't open pipe: $!");
3685  }
3686
3687  my $ex;
3688
3689  # Fork child
3690  $self->handle_sigchld();
3691  defined(my $pid = fork()) or die("Can't fork: $!");
3692  if ($pid) {
3693    eval {
3694      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3695      $client->login($user, $passwd);
3696
3697      my $resp_msgs = $client->response_msgs();
3698      my $nmsgs = scalar(@$resp_msgs);
3699
3700      my $expected;
3701
3702      $expected = 1;
3703      $self->assert($expected == $nmsgs,
3704        test_msg("Expected $expected, got $nmsgs"));
3705
3706      $expected = "User proftpd logged in";
3707      $self->assert($expected eq $resp_msgs->[0],
3708        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3709
3710    };
3711
3712    if ($@) {
3713      $ex = $@;
3714    }
3715
3716    $wfh->print("done\n");
3717    $wfh->flush();
3718
3719  } else {
3720    eval { server_wait($config_file, $rfh) };
3721    if ($@) {
3722      warn($@);
3723      exit 1;
3724    }
3725
3726    exit 0;
3727  }
3728
3729  # Stop server
3730  server_stop($pid_file);
3731
3732  $self->assert_child_ok($pid);
3733
3734  if ($ex) {
3735    test_append_logfile($log_file, $ex);
3736    unlink($log_file);
3737
3738    die($ex);
3739  }
3740
3741  unlink($log_file);
3742}
3743
3744sub sql_passwd_md5_rounds_bug3500 {
3745  my $self = shift;
3746  my $tmpdir = $self->{tmpdir};
3747
3748  my $config_file = "$tmpdir/sqlpasswd.conf";
3749  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3750  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3751
3752  my $log_file = test_get_logfile();
3753
3754  my $user = 'proftpd';
3755
3756  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
3757
3758  my $passwd = 'Password';
3759  my $salt = 'Sav0ryS4lt';
3760
3761  # I used:
3762  #
3763  #  Digest::MD5::md5_hex(
3764  #    Digest::MD5::md5_hex(
3765  #      Digest::MD5::md5_hex($salt . $passwd)));
3766  #
3767  # to generate this password.
3768  my $db_passwd = 'ca71e8bd1170c941633124ea4e424320';
3769
3770  my $group = 'ftpd';
3771  my $home_dir = File::Spec->rel2abs($tmpdir);
3772  my $uid = 500;
3773  my $gid = 500;
3774
3775  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3776
3777  # Build up sqlite3 command to create users, groups tables and populate them
3778  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3779
3780  if (open(my $fh, "> $db_script")) {
3781    print $fh <<EOS;
3782CREATE TABLE users (
3783  userid TEXT,
3784  passwd TEXT,
3785  uid INTEGER,
3786  gid INTEGER,
3787  homedir TEXT,
3788  shell TEXT
3789);
3790INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
3791
3792CREATE TABLE groups (
3793  groupname TEXT,
3794  gid INTEGER,
3795  members TEXT
3796);
3797INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
3798
3799CREATE TABLE user_salts (
3800  userid TEXT,
3801  salt TEXT
3802);
3803INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
3804EOS
3805
3806    unless (close($fh)) {
3807      die("Can't write $db_script: $!");
3808    }
3809
3810  } else {
3811    die("Can't open $db_script: $!");
3812  }
3813
3814  my $cmd = "sqlite3 $db_file < $db_script";
3815
3816  if ($ENV{TEST_VERBOSE}) {
3817    print STDERR "Executing sqlite3: $cmd\n";
3818  }
3819
3820  my @output = `$cmd`;
3821  if (scalar(@output) &&
3822      $ENV{TEST_VERBOSE}) {
3823    print STDERR "Output: ", join('', @output), "\n";
3824  }
3825
3826  my $config = {
3827    PidFile => $pid_file,
3828    ScoreboardFile => $scoreboard_file,
3829    SystemLog => $log_file,
3830
3831    IfModules => {
3832      'mod_delay.c' => {
3833        DelayEngine => 'off',
3834      },
3835
3836      'mod_sql.c' => {
3837        SQLAuthTypes => 'md5',
3838        SQLBackend => 'sqlite3',
3839        SQLConnectInfo => $db_file,
3840        SQLLogFile => $log_file,
3841        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
3842        SQLMinID => '100',
3843      },
3844
3845      'mod_sql_passwd.c' => {
3846        SQLPasswordEngine => 'on',
3847        SQLPasswordEncoding => 'hex',
3848        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
3849
3850        # Tell mod_sql_passwd to use three rounds of transformation
3851        SQLPasswordRounds => '3',
3852      },
3853    },
3854  };
3855
3856  my ($port, $config_user, $config_group) = config_write($config_file, $config);
3857
3858  # Open pipes, for use between the parent and child processes.  Specifically,
3859  # the child will indicate when it's done with its test by writing a message
3860  # to the parent.
3861  my ($rfh, $wfh);
3862  unless (pipe($rfh, $wfh)) {
3863    die("Can't open pipe: $!");
3864  }
3865
3866  my $ex;
3867
3868  # Fork child
3869  $self->handle_sigchld();
3870  defined(my $pid = fork()) or die("Can't fork: $!");
3871  if ($pid) {
3872    eval {
3873      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
3874      $client->login($user, $passwd);
3875
3876      my $resp_msgs = $client->response_msgs();
3877      my $nmsgs = scalar(@$resp_msgs);
3878
3879      my $expected;
3880
3881      $expected = 1;
3882      $self->assert($expected == $nmsgs,
3883        test_msg("Expected $expected, got $nmsgs"));
3884
3885      $expected = "User proftpd logged in";
3886      $self->assert($expected eq $resp_msgs->[0],
3887        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
3888
3889    };
3890
3891    if ($@) {
3892      $ex = $@;
3893    }
3894
3895    $wfh->print("done\n");
3896    $wfh->flush();
3897
3898  } else {
3899    eval { server_wait($config_file, $rfh) };
3900    if ($@) {
3901      warn($@);
3902      exit 1;
3903    }
3904
3905    exit 0;
3906  }
3907
3908  # Stop server
3909  server_stop($pid_file);
3910
3911  $self->assert_child_ok($pid);
3912
3913  if ($ex) {
3914    test_append_logfile($log_file, $ex);
3915    unlink($log_file);
3916
3917    die($ex);
3918  }
3919
3920  unlink($log_file);
3921}
3922
3923sub sql_passwd_md5_rounds_hash_encode_salt_password_bug3500 {
3924  my $self = shift;
3925  my $tmpdir = $self->{tmpdir};
3926
3927  my $config_file = "$tmpdir/sqlpasswd.conf";
3928  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
3929  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
3930
3931  my $log_file = test_get_logfile();
3932
3933  my $user = 'proftpd';
3934
3935  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
3936
3937  my $passwd = 'Password';
3938  my $salt = 'Sav0ryS4lt';
3939
3940  # I used:
3941  #
3942  #  Digest::MD5::md5_hex(
3943  #    Digest::MD5::md5_hex(
3944  #      Digest::MD5::md5_hex(
3945  #        Digest::MD5::md5_hex($salt) . Digest::MD5::md5_hex($passwd))));
3946  #
3947  # to generate this password.
3948  my $db_passwd = 'b317dcd1738a96a608c27607dd8179c0';
3949
3950  my $group = 'ftpd';
3951  my $home_dir = File::Spec->rel2abs($tmpdir);
3952  my $uid = 500;
3953  my $gid = 500;
3954
3955  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
3956
3957  # Build up sqlite3 command to create users, groups tables and populate them
3958  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
3959
3960  if (open(my $fh, "> $db_script")) {
3961    print $fh <<EOS;
3962CREATE TABLE users (
3963  userid TEXT,
3964  passwd TEXT,
3965  uid INTEGER,
3966  gid INTEGER,
3967  homedir TEXT,
3968  shell TEXT
3969);
3970INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
3971
3972CREATE TABLE groups (
3973  groupname TEXT,
3974  gid INTEGER,
3975  members TEXT
3976);
3977INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
3978
3979CREATE TABLE user_salts (
3980  userid TEXT,
3981  salt TEXT
3982);
3983INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
3984EOS
3985
3986    unless (close($fh)) {
3987      die("Can't write $db_script: $!");
3988    }
3989
3990  } else {
3991    die("Can't open $db_script: $!");
3992  }
3993
3994  my $cmd = "sqlite3 $db_file < $db_script";
3995
3996  if ($ENV{TEST_VERBOSE}) {
3997    print STDERR "Executing sqlite3: $cmd\n";
3998  }
3999
4000  my @output = `$cmd`;
4001  if (scalar(@output) &&
4002      $ENV{TEST_VERBOSE}) {
4003    print STDERR "Output: ", join('', @output), "\n";
4004  }
4005
4006  my $config = {
4007    PidFile => $pid_file,
4008    ScoreboardFile => $scoreboard_file,
4009    SystemLog => $log_file,
4010
4011    IfModules => {
4012      'mod_delay.c' => {
4013        DelayEngine => 'off',
4014      },
4015
4016      'mod_sql.c' => {
4017        SQLAuthTypes => 'md5',
4018        SQLBackend => 'sqlite3',
4019        SQLConnectInfo => $db_file,
4020        SQLLogFile => $log_file,
4021        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
4022        SQLMinID => '100',
4023      },
4024
4025      'mod_sql_passwd.c' => {
4026        SQLPasswordEngine => 'on',
4027        SQLPasswordEncoding => 'hex',
4028        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
4029
4030        # Tell mod_sql_passwd to transform salt and password
4031        SQLPasswordOptions => 'HashEncodePassword HashEncodeSalt',
4032
4033        # Tell mod_sql_passwd to use three rounds of transformation
4034        SQLPasswordRounds => '3',
4035      },
4036    },
4037  };
4038
4039  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4040
4041  # Open pipes, for use between the parent and child processes.  Specifically,
4042  # the child will indicate when it's done with its test by writing a message
4043  # to the parent.
4044  my ($rfh, $wfh);
4045  unless (pipe($rfh, $wfh)) {
4046    die("Can't open pipe: $!");
4047  }
4048
4049  my $ex;
4050
4051  # Fork child
4052  $self->handle_sigchld();
4053  defined(my $pid = fork()) or die("Can't fork: $!");
4054  if ($pid) {
4055    eval {
4056      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4057      $client->login($user, $passwd);
4058
4059      my $resp_msgs = $client->response_msgs();
4060      my $nmsgs = scalar(@$resp_msgs);
4061
4062      my $expected;
4063
4064      $expected = 1;
4065      $self->assert($expected == $nmsgs,
4066        test_msg("Expected $expected, got $nmsgs"));
4067
4068      $expected = "User proftpd logged in";
4069      $self->assert($expected eq $resp_msgs->[0],
4070        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4071
4072    };
4073
4074    if ($@) {
4075      $ex = $@;
4076    }
4077
4078    $wfh->print("done\n");
4079    $wfh->flush();
4080
4081  } else {
4082    eval { server_wait($config_file, $rfh) };
4083    if ($@) {
4084      warn($@);
4085      exit 1;
4086    }
4087
4088    exit 0;
4089  }
4090
4091  # Stop server
4092  server_stop($pid_file);
4093
4094  $self->assert_child_ok($pid);
4095
4096  if ($ex) {
4097    test_append_logfile($log_file, $ex);
4098    unlink($log_file);
4099
4100    die($ex);
4101  }
4102
4103  unlink($log_file);
4104}
4105
4106sub sql_passwd_md5_hash_password {
4107  my $self = shift;
4108  my $tmpdir = $self->{tmpdir};
4109
4110  my $config_file = "$tmpdir/sqlpasswd.conf";
4111  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
4112  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
4113
4114  my $log_file = test_get_logfile();
4115
4116  my $user = 'proftpd';
4117
4118  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
4119
4120  my $passwd = 'password';
4121
4122  # I used:
4123  #
4124  #  Digest::MD5::md5_hex(Digest::MD5::md5($passwd));
4125  #
4126  # to generate this password.
4127  my $db_passwd = '9bf4b3611c53176f5c649aa4fc1ff6b2';
4128
4129  my $group = 'ftpd';
4130  my $home_dir = File::Spec->rel2abs($tmpdir);
4131  my $uid = 500;
4132  my $gid = 500;
4133
4134  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
4135
4136  # Build up sqlite3 command to create users, groups tables and populate them
4137  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
4138
4139  if (open(my $fh, "> $db_script")) {
4140    print $fh <<EOS;
4141CREATE TABLE users (
4142  userid TEXT,
4143  passwd TEXT,
4144  uid INTEGER,
4145  gid INTEGER,
4146  homedir TEXT,
4147  shell TEXT
4148);
4149INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
4150
4151CREATE TABLE groups (
4152  groupname TEXT,
4153  gid INTEGER,
4154  members TEXT
4155);
4156INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
4157
4158EOS
4159
4160    unless (close($fh)) {
4161      die("Can't write $db_script: $!");
4162    }
4163
4164  } else {
4165    die("Can't open $db_script: $!");
4166  }
4167
4168  my $cmd = "sqlite3 $db_file < $db_script";
4169
4170  if ($ENV{TEST_VERBOSE}) {
4171    print STDERR "Executing sqlite3: $cmd\n";
4172  }
4173
4174  my @output = `$cmd`;
4175  if (scalar(@output) &&
4176      $ENV{TEST_VERBOSE}) {
4177    print STDERR "Output: ", join('', @output), "\n";
4178  }
4179
4180  my $config = {
4181    PidFile => $pid_file,
4182    ScoreboardFile => $scoreboard_file,
4183    SystemLog => $log_file,
4184
4185    IfModules => {
4186      'mod_delay.c' => {
4187        DelayEngine => 'off',
4188      },
4189
4190      'mod_sql.c' => {
4191        SQLAuthTypes => 'md5',
4192        SQLBackend => 'sqlite3',
4193        SQLConnectInfo => $db_file,
4194        SQLLogFile => $log_file,
4195        SQLMinID => '100',
4196      },
4197
4198      'mod_sql_passwd.c' => {
4199        SQLPasswordEngine => 'on',
4200        SQLPasswordEncoding => 'hex',
4201
4202        SQLPasswordOptions => 'HashPassword',
4203      },
4204    },
4205  };
4206
4207  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4208
4209  # Open pipes, for use between the parent and child processes.  Specifically,
4210  # the child will indicate when it's done with its test by writing a message
4211  # to the parent.
4212  my ($rfh, $wfh);
4213  unless (pipe($rfh, $wfh)) {
4214    die("Can't open pipe: $!");
4215  }
4216
4217  my $ex;
4218
4219  # Fork child
4220  $self->handle_sigchld();
4221  defined(my $pid = fork()) or die("Can't fork: $!");
4222  if ($pid) {
4223    eval {
4224      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4225      $client->login($user, $passwd);
4226
4227      my $resp_msgs = $client->response_msgs();
4228      my $nmsgs = scalar(@$resp_msgs);
4229
4230      my $expected;
4231
4232      $expected = 1;
4233      $self->assert($expected == $nmsgs,
4234        test_msg("Expected $expected, got $nmsgs"));
4235
4236      $expected = "User proftpd logged in";
4237      $self->assert($expected eq $resp_msgs->[0],
4238        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4239
4240    };
4241
4242    if ($@) {
4243      $ex = $@;
4244    }
4245
4246    $wfh->print("done\n");
4247    $wfh->flush();
4248
4249  } else {
4250    eval { server_wait($config_file, $rfh) };
4251    if ($@) {
4252      warn($@);
4253      exit 1;
4254    }
4255
4256    exit 0;
4257  }
4258
4259  # Stop server
4260  server_stop($pid_file);
4261
4262  $self->assert_child_ok($pid);
4263
4264  if ($ex) {
4265    test_append_logfile($log_file, $ex);
4266    unlink($log_file);
4267
4268    die($ex);
4269  }
4270
4271  unlink($log_file);
4272}
4273
4274sub sql_passwd_md5_hash_salt {
4275  my $self = shift;
4276  my $tmpdir = $self->{tmpdir};
4277
4278  my $config_file = "$tmpdir/sqlpasswd.conf";
4279  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
4280  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
4281
4282  my $log_file = test_get_logfile();
4283
4284  my $user = 'proftpd';
4285
4286  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
4287
4288  my $passwd = 'password';
4289  my $salt = ':(Km-';
4290
4291  # I used:
4292  #
4293  #  Digest::MD5::md5_hex(Digest::MD5::md5($salt) . $passwd);
4294  #
4295  # to generate this password.
4296  my $db_passwd = '6d829eb1782d1295a04a983da3d1286d';
4297
4298  my $group = 'ftpd';
4299  my $home_dir = File::Spec->rel2abs($tmpdir);
4300  my $uid = 500;
4301  my $gid = 500;
4302
4303  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
4304
4305  # Build up sqlite3 command to create users, groups tables and populate them
4306  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
4307
4308  if (open(my $fh, "> $db_script")) {
4309    print $fh <<EOS;
4310CREATE TABLE users (
4311  userid TEXT,
4312  passwd TEXT,
4313  uid INTEGER,
4314  gid INTEGER,
4315  homedir TEXT,
4316  shell TEXT
4317);
4318INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
4319
4320CREATE TABLE groups (
4321  groupname TEXT,
4322  gid INTEGER,
4323  members TEXT
4324);
4325INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
4326
4327CREATE TABLE user_salts (
4328  userid TEXT,
4329  salt TEXT
4330);
4331INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
4332EOS
4333
4334    unless (close($fh)) {
4335      die("Can't write $db_script: $!");
4336    }
4337
4338  } else {
4339    die("Can't open $db_script: $!");
4340  }
4341
4342  my $cmd = "sqlite3 $db_file < $db_script";
4343
4344  if ($ENV{TEST_VERBOSE}) {
4345    print STDERR "Executing sqlite3: $cmd\n";
4346  }
4347
4348  my @output = `$cmd`;
4349  if (scalar(@output) &&
4350      $ENV{TEST_VERBOSE}) {
4351    print STDERR "Output: ", join('', @output), "\n";
4352  }
4353
4354  my $config = {
4355    PidFile => $pid_file,
4356    ScoreboardFile => $scoreboard_file,
4357    SystemLog => $log_file,
4358
4359    IfModules => {
4360      'mod_delay.c' => {
4361        DelayEngine => 'off',
4362      },
4363
4364      'mod_sql.c' => {
4365        SQLAuthTypes => 'md5',
4366        SQLBackend => 'sqlite3',
4367        SQLConnectInfo => $db_file,
4368        SQLLogFile => $log_file,
4369        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
4370        SQLMinID => '100',
4371      },
4372
4373      'mod_sql_passwd.c' => {
4374        SQLPasswordEngine => 'on',
4375        SQLPasswordEncoding => 'hex',
4376        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
4377
4378        SQLPasswordOptions => 'HashSalt',
4379      },
4380    },
4381  };
4382
4383  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4384
4385  # Open pipes, for use between the parent and child processes.  Specifically,
4386  # the child will indicate when it's done with its test by writing a message
4387  # to the parent.
4388  my ($rfh, $wfh);
4389  unless (pipe($rfh, $wfh)) {
4390    die("Can't open pipe: $!");
4391  }
4392
4393  my $ex;
4394
4395  # Fork child
4396  $self->handle_sigchld();
4397  defined(my $pid = fork()) or die("Can't fork: $!");
4398  if ($pid) {
4399    eval {
4400      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4401      $client->login($user, $passwd);
4402
4403      my $resp_msgs = $client->response_msgs();
4404      my $nmsgs = scalar(@$resp_msgs);
4405
4406      my $expected;
4407
4408      $expected = 1;
4409      $self->assert($expected == $nmsgs,
4410        test_msg("Expected $expected, got $nmsgs"));
4411
4412      $expected = "User proftpd logged in";
4413      $self->assert($expected eq $resp_msgs->[0],
4414        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4415
4416    };
4417
4418    if ($@) {
4419      $ex = $@;
4420    }
4421
4422    $wfh->print("done\n");
4423    $wfh->flush();
4424
4425  } else {
4426    eval { server_wait($config_file, $rfh) };
4427    if ($@) {
4428      warn($@);
4429      exit 1;
4430    }
4431
4432    exit 0;
4433  }
4434
4435  # Stop server
4436  server_stop($pid_file);
4437
4438  $self->assert_child_ok($pid);
4439
4440  if ($ex) {
4441    test_append_logfile($log_file, $ex);
4442    unlink($log_file);
4443
4444    die($ex);
4445  }
4446
4447  unlink($log_file);
4448}
4449
4450sub sql_passwd_md5_encode_salt {
4451  my $self = shift;
4452  my $tmpdir = $self->{tmpdir};
4453
4454  my $config_file = "$tmpdir/sqlpasswd.conf";
4455  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
4456  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
4457
4458  my $log_file = test_get_logfile();
4459
4460  my $user = 'proftpd';
4461
4462  # See http://bugs.proftpd.org/show_bug.cgi?id=3500 for more details
4463
4464  my $passwd = 'password';
4465  my $salt = ':(Km-';
4466
4467  # I used:
4468  #
4469  #  Digest::MD5::md5_hex(Digest::MD5::md5_hex($salt) .
4470  #                       Digest::MD5::md5_hex($passwd));
4471  #
4472  # to generate this password.
4473  my $db_passwd = 'e434ada7d8d3db4924d3b2bfe3bf1ce4';
4474
4475  my $group = 'ftpd';
4476  my $home_dir = File::Spec->rel2abs($tmpdir);
4477  my $uid = 500;
4478  my $gid = 500;
4479
4480  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
4481
4482  # Build up sqlite3 command to create users, groups tables and populate them
4483  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
4484
4485  if (open(my $fh, "> $db_script")) {
4486    print $fh <<EOS;
4487CREATE TABLE users (
4488  userid TEXT,
4489  passwd TEXT,
4490  uid INTEGER,
4491  gid INTEGER,
4492  homedir TEXT,
4493  shell TEXT
4494);
4495INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
4496
4497CREATE TABLE groups (
4498  groupname TEXT,
4499  gid INTEGER,
4500  members TEXT
4501);
4502INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
4503
4504CREATE TABLE user_salts (
4505  userid TEXT,
4506  salt TEXT
4507);
4508INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
4509EOS
4510
4511    unless (close($fh)) {
4512      die("Can't write $db_script: $!");
4513    }
4514
4515  } else {
4516    die("Can't open $db_script: $!");
4517  }
4518
4519  my $cmd = "sqlite3 $db_file < $db_script";
4520
4521  if ($ENV{TEST_VERBOSE}) {
4522    print STDERR "Executing sqlite3: $cmd\n";
4523  }
4524
4525  my @output = `$cmd`;
4526  if (scalar(@output) &&
4527      $ENV{TEST_VERBOSE}) {
4528    print STDERR "Output: ", join('', @output), "\n";
4529  }
4530
4531  my $config = {
4532    PidFile => $pid_file,
4533    ScoreboardFile => $scoreboard_file,
4534    SystemLog => $log_file,
4535
4536    IfModules => {
4537      'mod_delay.c' => {
4538        DelayEngine => 'off',
4539      },
4540
4541      'mod_sql.c' => {
4542        SQLAuthTypes => 'md5',
4543        SQLBackend => 'sqlite3',
4544        SQLConnectInfo => $db_file,
4545        SQLLogFile => $log_file,
4546        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
4547        SQLMinID => '100',
4548      },
4549
4550      'mod_sql_passwd.c' => {
4551        SQLPasswordEngine => 'on',
4552        SQLPasswordEncoding => 'hex',
4553        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
4554
4555        # Tell mod_sql_passwd to transform both the salt and the password
4556        # before transforming the combination of them.
4557        SQLPasswordOptions => 'HashEncodeSalt HashEncodePassword',
4558      },
4559    },
4560  };
4561
4562  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4563
4564  # Open pipes, for use between the parent and child processes.  Specifically,
4565  # the child will indicate when it's done with its test by writing a message
4566  # to the parent.
4567  my ($rfh, $wfh);
4568  unless (pipe($rfh, $wfh)) {
4569    die("Can't open pipe: $!");
4570  }
4571
4572  my $ex;
4573
4574  # Fork child
4575  $self->handle_sigchld();
4576  defined(my $pid = fork()) or die("Can't fork: $!");
4577  if ($pid) {
4578    eval {
4579      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4580      $client->login($user, $passwd);
4581
4582      my $resp_msgs = $client->response_msgs();
4583      my $nmsgs = scalar(@$resp_msgs);
4584
4585      my $expected;
4586
4587      $expected = 1;
4588      $self->assert($expected == $nmsgs,
4589        test_msg("Expected $expected, got $nmsgs"));
4590
4591      $expected = "User proftpd logged in";
4592      $self->assert($expected eq $resp_msgs->[0],
4593        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4594
4595    };
4596
4597    if ($@) {
4598      $ex = $@;
4599    }
4600
4601    $wfh->print("done\n");
4602    $wfh->flush();
4603
4604  } else {
4605    eval { server_wait($config_file, $rfh) };
4606    if ($@) {
4607      warn($@);
4608      exit 1;
4609    }
4610
4611    exit 0;
4612  }
4613
4614  # Stop server
4615  server_stop($pid_file);
4616
4617  $self->assert_child_ok($pid);
4618
4619  if ($ex) {
4620    test_append_logfile($log_file, $ex);
4621    unlink($log_file);
4622
4623    die($ex);
4624  }
4625
4626  unlink($log_file);
4627}
4628
4629sub sql_passwd_sha1_encode_salt_hash_encode_password {
4630  my $self = shift;
4631  my $tmpdir = $self->{tmpdir};
4632
4633  my $config_file = "$tmpdir/sqlpasswd.conf";
4634  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
4635  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
4636
4637  my $log_file = test_get_logfile();
4638
4639  my $user = 'proftpd';
4640
4641  # See http://forums.proftpd.org/smf/index.php/topic,3733.0.html for more
4642  # details
4643
4644  my $passwd = 'test1234';
4645  my $salt = '48d84dc8792accc3770117c804f1b5ce';
4646
4647  # I used:
4648  #
4649  #  Digest::SHA1::sha1_hex($salt . Digest::SHA1::sha1_hex($passwd))
4650  #
4651  # to generate this password.
4652  my $db_passwd = '03cb508a6c32e33a21695ed139e6f7cb4e479a76';
4653
4654  my $group = 'ftpd';
4655  my $home_dir = File::Spec->rel2abs($tmpdir);
4656  my $uid = 500;
4657  my $gid = 500;
4658
4659  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
4660
4661  # Build up sqlite3 command to create users, groups tables and populate them
4662  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
4663
4664  if (open(my $fh, "> $db_script")) {
4665    print $fh <<EOS;
4666CREATE TABLE users (
4667  userid TEXT,
4668  passwd TEXT,
4669  uid INTEGER,
4670  gid INTEGER,
4671  homedir TEXT,
4672  shell TEXT
4673);
4674INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$db_passwd', $uid, $gid, '$home_dir', '/bin/bash');
4675
4676CREATE TABLE groups (
4677  groupname TEXT,
4678  gid INTEGER,
4679  members TEXT
4680);
4681INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
4682
4683CREATE TABLE user_salts (
4684  userid TEXT,
4685  salt TEXT
4686);
4687INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
4688
4689EOS
4690
4691    unless (close($fh)) {
4692      die("Can't write $db_script: $!");
4693    }
4694
4695  } else {
4696    die("Can't open $db_script: $!");
4697  }
4698
4699  my $cmd = "sqlite3 $db_file < $db_script";
4700
4701  if ($ENV{TEST_VERBOSE}) {
4702    print STDERR "Executing sqlite3: $cmd\n";
4703  }
4704
4705  my @output = `$cmd`;
4706  if (scalar(@output) &&
4707      $ENV{TEST_VERBOSE}) {
4708    print STDERR "Output: ", join('', @output), "\n";
4709  }
4710
4711  my $config = {
4712    PidFile => $pid_file,
4713    ScoreboardFile => $scoreboard_file,
4714    SystemLog => $log_file,
4715
4716    IfModules => {
4717      'mod_delay.c' => {
4718        DelayEngine => 'off',
4719      },
4720
4721      'mod_sql.c' => {
4722        SQLAuthTypes => 'sha1',
4723        SQLBackend => 'sqlite3',
4724        SQLConnectInfo => $db_file,
4725        SQLLogFile => $log_file,
4726        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
4727        SQLMinID => '100',
4728      },
4729
4730      'mod_sql_passwd.c' => {
4731        SQLPasswordEngine => 'on',
4732        SQLPasswordEncoding => 'hex',
4733        SQLPasswordUserSalt => 'sql:/get-user-salt Prepend',
4734        SQLPasswordOptions => 'HashEncodePassword',
4735      },
4736    },
4737  };
4738
4739  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4740
4741  # Open pipes, for use between the parent and child processes.  Specifically,
4742  # the child will indicate when it's done with its test by writing a message
4743  # to the parent.
4744  my ($rfh, $wfh);
4745  unless (pipe($rfh, $wfh)) {
4746    die("Can't open pipe: $!");
4747  }
4748
4749  my $ex;
4750
4751  # Fork child
4752  $self->handle_sigchld();
4753  defined(my $pid = fork()) or die("Can't fork: $!");
4754  if ($pid) {
4755    eval {
4756      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4757      $client->login($user, $passwd);
4758
4759      my $resp_msgs = $client->response_msgs();
4760      my $nmsgs = scalar(@$resp_msgs);
4761
4762      my $expected;
4763
4764      $expected = 1;
4765      $self->assert($expected == $nmsgs,
4766        test_msg("Expected $expected, got $nmsgs"));
4767
4768      $expected = "User proftpd logged in";
4769      $self->assert($expected eq $resp_msgs->[0],
4770        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4771
4772    };
4773
4774    if ($@) {
4775      $ex = $@;
4776    }
4777
4778    $wfh->print("done\n");
4779    $wfh->flush();
4780
4781  } else {
4782    eval { server_wait($config_file, $rfh) };
4783    if ($@) {
4784      warn($@);
4785      exit 1;
4786    }
4787
4788    exit 0;
4789  }
4790
4791  # Stop server
4792  server_stop($pid_file);
4793
4794  $self->assert_child_ok($pid);
4795
4796  if ($ex) {
4797    test_append_logfile($log_file, $ex);
4798    unlink($log_file);
4799
4800    die($ex);
4801  }
4802
4803  unlink($log_file);
4804}
4805
4806sub sql_passwd_pbkdf2_sha1_base64 {
4807  my $self = shift;
4808  my $tmpdir = $self->{tmpdir};
4809
4810  my $config_file = "$tmpdir/sqlpasswd.conf";
4811  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
4812  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
4813
4814  my $log_file = test_get_logfile();
4815
4816  my $user = 'proftpd';
4817  my $group = 'ftpd';
4818
4819  # RFC 6070: PKCS#5 PBKDF2 Test Vectors
4820  #
4821  # Input:
4822  #   P = "password" (8 octets)
4823  #   S = "salt" (4 octets)
4824  #   c = 4096
4825  #   dkLen = 20
4826  #
4827  # Output:
4828  #   DK = 4b 00 79 01 b7 65 48 9a
4829  #        be ad 49 d9 26 f7 21 d0
4830  #        65 a4 29 c1             (20 octets)
4831  #
4832  # Base64:
4833  #   DK = SwB5AbdlSJq+rUnZJvch0GWkKcE=
4834  #
4835  my $passwd = "SwB5AbdlSJq+rUnZJvch0GWkKcE=";
4836
4837  my $home_dir = File::Spec->rel2abs($tmpdir);
4838  my $uid = 500;
4839  my $gid = 500;
4840
4841  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
4842
4843  # Build up sqlite3 command to create users, groups tables and populate them
4844  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
4845
4846  if (open(my $fh, "> $db_script")) {
4847    print $fh <<EOS;
4848CREATE TABLE users (
4849  userid TEXT,
4850  passwd TEXT,
4851  uid INTEGER,
4852  gid INTEGER,
4853  homedir TEXT,
4854  shell TEXT
4855);
4856INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
4857
4858CREATE TABLE groups (
4859  groupname TEXT,
4860  gid INTEGER,
4861  members TEXT
4862);
4863INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
4864EOS
4865
4866    unless (close($fh)) {
4867      die("Can't write $db_script: $!");
4868    }
4869
4870  } else {
4871    die("Can't open $db_script: $!");
4872  }
4873
4874  my $cmd = "sqlite3 $db_file < $db_script";
4875
4876  if ($ENV{TEST_VERBOSE}) {
4877    print STDERR "Executing sqlite3: $cmd\n";
4878  }
4879
4880  my @output = `$cmd`;
4881  if (scalar(@output) &&
4882      $ENV{TEST_VERBOSE}) {
4883    print STDERR "Output: ", join('', @output), "\n";
4884  }
4885
4886  my $salt = 'salt';
4887
4888  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
4889  if (open(my $fh, "> $salt_file")) {
4890    binmode($fh);
4891    print $fh $salt;
4892
4893    unless (close($fh)) {
4894      die("Can't write $salt_file: $!");
4895    }
4896
4897  } else {
4898    die("Can't open $salt_file: $!");
4899  }
4900
4901  my $config = {
4902    PidFile => $pid_file,
4903    ScoreboardFile => $scoreboard_file,
4904    SystemLog => $log_file,
4905
4906    IfModules => {
4907      'mod_delay.c' => {
4908        DelayEngine => 'off',
4909      },
4910
4911      'mod_sql.c' => {
4912        SQLAuthTypes => 'pbkdf2',
4913        SQLBackend => 'sqlite3',
4914        SQLConnectInfo => $db_file,
4915        SQLLogFile => $log_file,
4916      },
4917
4918      'mod_sql_passwd.c' => {
4919        SQLPasswordEngine => 'on',
4920        SQLPasswordEncoding => 'base64',
4921        SQLPasswordPBKDF2 => 'sha1 4096 20',
4922        SQLPasswordSaltFile => $salt_file,
4923      },
4924    },
4925  };
4926
4927  my ($port, $config_user, $config_group) = config_write($config_file, $config);
4928
4929  # Open pipes, for use between the parent and child processes.  Specifically,
4930  # the child will indicate when it's done with its test by writing a message
4931  # to the parent.
4932  my ($rfh, $wfh);
4933  unless (pipe($rfh, $wfh)) {
4934    die("Can't open pipe: $!");
4935  }
4936
4937  my $ex;
4938
4939  # Fork child
4940  $self->handle_sigchld();
4941  defined(my $pid = fork()) or die("Can't fork: $!");
4942  if ($pid) {
4943    eval {
4944      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
4945      $client->login($user, "password");
4946
4947      my $resp_msgs = $client->response_msgs();
4948      my $nmsgs = scalar(@$resp_msgs);
4949
4950      my $expected;
4951
4952      $expected = 1;
4953      $self->assert($expected == $nmsgs,
4954        test_msg("Expected $expected, got $nmsgs"));
4955
4956      $expected = "User proftpd logged in";
4957      $self->assert($expected eq $resp_msgs->[0],
4958        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
4959
4960    };
4961
4962    if ($@) {
4963      $ex = $@;
4964    }
4965
4966    $wfh->print("done\n");
4967    $wfh->flush();
4968
4969  } else {
4970    eval { server_wait($config_file, $rfh) };
4971    if ($@) {
4972      warn($@);
4973      exit 1;
4974    }
4975
4976    exit 0;
4977  }
4978
4979  # Stop server
4980  server_stop($pid_file);
4981
4982  $self->assert_child_ok($pid);
4983
4984  if ($ex) {
4985    test_append_logfile($log_file, $ex);
4986    unlink($log_file);
4987
4988    die($ex);
4989  }
4990
4991  unlink($log_file);
4992}
4993
4994sub sql_passwd_pbkdf2_sha1_hex_lc {
4995  my $self = shift;
4996  my $tmpdir = $self->{tmpdir};
4997
4998  my $config_file = "$tmpdir/sqlpasswd.conf";
4999  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5000  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5001
5002  my $log_file = test_get_logfile();
5003
5004  my $user = 'proftpd';
5005  my $group = 'ftpd';
5006
5007  # RFC 6070: PKCS#5 PBKDF2 Test Vectors
5008  #
5009  # Input:
5010  #   P = "password" (8 octets)
5011  #   S = "salt" (4 octets)
5012  #   c = 4096
5013  #   dkLen = 20
5014  #
5015  # Output:
5016  #   DK = 4b 00 79 01 b7 65 48 9a
5017  #        be ad 49 d9 26 f7 21 d0
5018  #        65 a4 29 c1             (20 octets)
5019  my $passwd = "4b007901b765489abead49d926f721d065a429c1";
5020
5021  my $home_dir = File::Spec->rel2abs($tmpdir);
5022  my $uid = 500;
5023  my $gid = 500;
5024
5025  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5026
5027  # Build up sqlite3 command to create users, groups tables and populate them
5028  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5029
5030  if (open(my $fh, "> $db_script")) {
5031    print $fh <<EOS;
5032CREATE TABLE users (
5033  userid TEXT,
5034  passwd TEXT,
5035  uid INTEGER,
5036  gid INTEGER,
5037  homedir TEXT,
5038  shell TEXT
5039);
5040INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5041
5042CREATE TABLE groups (
5043  groupname TEXT,
5044  gid INTEGER,
5045  members TEXT
5046);
5047INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
5048EOS
5049
5050    unless (close($fh)) {
5051      die("Can't write $db_script: $!");
5052    }
5053
5054  } else {
5055    die("Can't open $db_script: $!");
5056  }
5057
5058  my $cmd = "sqlite3 $db_file < $db_script";
5059
5060  if ($ENV{TEST_VERBOSE}) {
5061    print STDERR "Executing sqlite3: $cmd\n";
5062  }
5063
5064  my @output = `$cmd`;
5065  if (scalar(@output) &&
5066      $ENV{TEST_VERBOSE}) {
5067    print STDERR "Output: ", join('', @output), "\n";
5068  }
5069
5070  my $salt = 'salt';
5071
5072  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
5073  if (open(my $fh, "> $salt_file")) {
5074    binmode($fh);
5075    print $fh $salt;
5076
5077    unless (close($fh)) {
5078      die("Can't write $salt_file: $!");
5079    }
5080
5081  } else {
5082    die("Can't open $salt_file: $!");
5083  }
5084
5085  my $config = {
5086    PidFile => $pid_file,
5087    ScoreboardFile => $scoreboard_file,
5088    SystemLog => $log_file,
5089
5090    IfModules => {
5091      'mod_delay.c' => {
5092        DelayEngine => 'off',
5093      },
5094
5095      'mod_sql.c' => {
5096        SQLAuthTypes => 'pbkdf2',
5097        SQLBackend => 'sqlite3',
5098        SQLConnectInfo => $db_file,
5099        SQLLogFile => $log_file,
5100      },
5101
5102      'mod_sql_passwd.c' => {
5103        SQLPasswordEngine => 'on',
5104        SQLPasswordEncoding => 'hex',
5105        SQLPasswordPBKDF2 => 'sha1 4096 20',
5106        SQLPasswordSaltFile => $salt_file,
5107      },
5108    },
5109  };
5110
5111  my ($port, $config_user, $config_group) = config_write($config_file, $config);
5112
5113  # Open pipes, for use between the parent and child processes.  Specifically,
5114  # the child will indicate when it's done with its test by writing a message
5115  # to the parent.
5116  my ($rfh, $wfh);
5117  unless (pipe($rfh, $wfh)) {
5118    die("Can't open pipe: $!");
5119  }
5120
5121  my $ex;
5122
5123  # Fork child
5124  $self->handle_sigchld();
5125  defined(my $pid = fork()) or die("Can't fork: $!");
5126  if ($pid) {
5127    eval {
5128      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
5129      $client->login($user, "password");
5130
5131      my $resp_msgs = $client->response_msgs();
5132      my $nmsgs = scalar(@$resp_msgs);
5133
5134      my $expected;
5135
5136      $expected = 1;
5137      $self->assert($expected == $nmsgs,
5138        test_msg("Expected $expected, got $nmsgs"));
5139
5140      $expected = "User proftpd logged in";
5141      $self->assert($expected eq $resp_msgs->[0],
5142        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
5143
5144    };
5145
5146    if ($@) {
5147      $ex = $@;
5148    }
5149
5150    $wfh->print("done\n");
5151    $wfh->flush();
5152
5153  } else {
5154    eval { server_wait($config_file, $rfh) };
5155    if ($@) {
5156      warn($@);
5157      exit 1;
5158    }
5159
5160    exit 0;
5161  }
5162
5163  # Stop server
5164  server_stop($pid_file);
5165
5166  $self->assert_child_ok($pid);
5167
5168  if ($ex) {
5169    test_append_logfile($log_file, $ex);
5170    unlink($log_file);
5171
5172    die($ex);
5173  }
5174
5175  unlink($log_file);
5176}
5177
5178sub sql_passwd_pbkdf2_sha1_hex_uc {
5179  my $self = shift;
5180  my $tmpdir = $self->{tmpdir};
5181
5182  my $config_file = "$tmpdir/sqlpasswd.conf";
5183  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5184  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5185
5186  my $log_file = test_get_logfile();
5187
5188  my $user = 'proftpd';
5189  my $group = 'ftpd';
5190
5191  # RFC 6070: PKCS#5 PBKDF2 Test Vectors
5192  #
5193  # Input:
5194  #   P = "password" (8 octets)
5195  #   S = "salt" (4 octets)
5196  #   c = 4096
5197  #   dkLen = 20
5198  #
5199  # Output:
5200  #   DK = 4b 00 79 01 b7 65 48 9a
5201  #        be ad 49 d9 26 f7 21 d0
5202  #        65 a4 29 c1             (20 octets)
5203  my $passwd = "4B007901B765489ABEAD49D926F721D065A429C1";
5204
5205  my $home_dir = File::Spec->rel2abs($tmpdir);
5206  my $uid = 500;
5207  my $gid = 500;
5208
5209  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5210
5211  # Build up sqlite3 command to create users, groups tables and populate them
5212  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5213
5214  if (open(my $fh, "> $db_script")) {
5215    print $fh <<EOS;
5216CREATE TABLE users (
5217  userid TEXT,
5218  passwd TEXT,
5219  uid INTEGER,
5220  gid INTEGER,
5221  homedir TEXT,
5222  shell TEXT
5223);
5224INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5225
5226CREATE TABLE groups (
5227  groupname TEXT,
5228  gid INTEGER,
5229  members TEXT
5230);
5231INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
5232EOS
5233
5234    unless (close($fh)) {
5235      die("Can't write $db_script: $!");
5236    }
5237
5238  } else {
5239    die("Can't open $db_script: $!");
5240  }
5241
5242  my $cmd = "sqlite3 $db_file < $db_script";
5243
5244  if ($ENV{TEST_VERBOSE}) {
5245    print STDERR "Executing sqlite3: $cmd\n";
5246  }
5247
5248  my @output = `$cmd`;
5249  if (scalar(@output) &&
5250      $ENV{TEST_VERBOSE}) {
5251    print STDERR "Output: ", join('', @output), "\n";
5252  }
5253
5254  my $salt = 'salt';
5255
5256  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
5257  if (open(my $fh, "> $salt_file")) {
5258    binmode($fh);
5259    print $fh $salt;
5260
5261    unless (close($fh)) {
5262      die("Can't write $salt_file: $!");
5263    }
5264
5265  } else {
5266    die("Can't open $salt_file: $!");
5267  }
5268
5269  my $config = {
5270    PidFile => $pid_file,
5271    ScoreboardFile => $scoreboard_file,
5272    SystemLog => $log_file,
5273
5274    IfModules => {
5275      'mod_delay.c' => {
5276        DelayEngine => 'off',
5277      },
5278
5279      'mod_sql.c' => {
5280        SQLAuthTypes => 'pbkdf2',
5281        SQLBackend => 'sqlite3',
5282        SQLConnectInfo => $db_file,
5283        SQLLogFile => $log_file,
5284      },
5285
5286      'mod_sql_passwd.c' => {
5287        SQLPasswordEngine => 'on',
5288        SQLPasswordEncoding => 'HEX',
5289        SQLPasswordPBKDF2 => 'sha1 4096 20',
5290        SQLPasswordSaltFile => $salt_file,
5291      },
5292    },
5293  };
5294
5295  my ($port, $config_user, $config_group) = config_write($config_file, $config);
5296
5297  # Open pipes, for use between the parent and child processes.  Specifically,
5298  # the child will indicate when it's done with its test by writing a message
5299  # to the parent.
5300  my ($rfh, $wfh);
5301  unless (pipe($rfh, $wfh)) {
5302    die("Can't open pipe: $!");
5303  }
5304
5305  my $ex;
5306
5307  # Fork child
5308  $self->handle_sigchld();
5309  defined(my $pid = fork()) or die("Can't fork: $!");
5310  if ($pid) {
5311    eval {
5312      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
5313      $client->login($user, "password");
5314
5315      my $resp_msgs = $client->response_msgs();
5316      my $nmsgs = scalar(@$resp_msgs);
5317
5318      my $expected;
5319
5320      $expected = 1;
5321      $self->assert($expected == $nmsgs,
5322        test_msg("Expected $expected, got $nmsgs"));
5323
5324      $expected = "User proftpd logged in";
5325      $self->assert($expected eq $resp_msgs->[0],
5326        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
5327
5328    };
5329
5330    if ($@) {
5331      $ex = $@;
5332    }
5333
5334    $wfh->print("done\n");
5335    $wfh->flush();
5336
5337  } else {
5338    eval { server_wait($config_file, $rfh) };
5339    if ($@) {
5340      warn($@);
5341      exit 1;
5342    }
5343
5344    exit 0;
5345  }
5346
5347  # Stop server
5348  server_stop($pid_file);
5349
5350  $self->assert_child_ok($pid);
5351
5352  if ($ex) {
5353    test_append_logfile($log_file, $ex);
5354    unlink($log_file);
5355
5356    die($ex);
5357  }
5358
5359  unlink($log_file);
5360}
5361
5362sub sql_passwd_pbkdf2_sha512_hex_lc {
5363  my $self = shift;
5364  my $tmpdir = $self->{tmpdir};
5365
5366  my $config_file = "$tmpdir/sqlpasswd.conf";
5367  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5368  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5369
5370  my $log_file = test_get_logfile();
5371
5372  my $user = 'proftpd';
5373  my $group = 'ftpd';
5374
5375  # See:
5376  #  http://stackoverflow.com/questions/15593184/pbkdf2-hmac-sha-512-test-vectors
5377  my $passwd = '867f70cf1ade02cff3752599a3a53dc4af34c7a669815ae5d513554e1c8cf252c02d470a285a0501bad999bfe943c08f050235d7d68b1da55e63f73b60a57fce';
5378
5379  my $home_dir = File::Spec->rel2abs($tmpdir);
5380  my $uid = 500;
5381  my $gid = 500;
5382
5383  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5384
5385  # Build up sqlite3 command to create users, groups tables and populate them
5386  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5387
5388  if (open(my $fh, "> $db_script")) {
5389    print $fh <<EOS;
5390CREATE TABLE users (
5391  userid TEXT,
5392  passwd TEXT,
5393  uid INTEGER,
5394  gid INTEGER,
5395  homedir TEXT,
5396  shell TEXT
5397);
5398INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5399
5400CREATE TABLE groups (
5401  groupname TEXT,
5402  gid INTEGER,
5403  members TEXT
5404);
5405INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
5406EOS
5407
5408    unless (close($fh)) {
5409      die("Can't write $db_script: $!");
5410    }
5411
5412  } else {
5413    die("Can't open $db_script: $!");
5414  }
5415
5416  my $cmd = "sqlite3 $db_file < $db_script";
5417
5418  if ($ENV{TEST_VERBOSE}) {
5419    print STDERR "Executing sqlite3: $cmd\n";
5420  }
5421
5422  my @output = `$cmd`;
5423  if (scalar(@output) &&
5424      $ENV{TEST_VERBOSE}) {
5425    print STDERR "Output: ", join('', @output), "\n";
5426  }
5427
5428  my $salt = 'salt';
5429
5430  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
5431  if (open(my $fh, "> $salt_file")) {
5432    binmode($fh);
5433    print $fh $salt;
5434
5435    unless (close($fh)) {
5436      die("Can't write $salt_file: $!");
5437    }
5438
5439  } else {
5440    die("Can't open $salt_file: $!");
5441  }
5442
5443  my $config = {
5444    PidFile => $pid_file,
5445    ScoreboardFile => $scoreboard_file,
5446    SystemLog => $log_file,
5447
5448    IfModules => {
5449      'mod_delay.c' => {
5450        DelayEngine => 'off',
5451      },
5452
5453      'mod_sql.c' => {
5454        SQLAuthTypes => 'pbkdf2',
5455        SQLBackend => 'sqlite3',
5456        SQLConnectInfo => $db_file,
5457        SQLLogFile => $log_file,
5458      },
5459
5460      'mod_sql_passwd.c' => {
5461        SQLPasswordEngine => 'on',
5462        SQLPasswordEncoding => 'hex',
5463        SQLPasswordPBKDF2 => 'sha512 1 64',
5464        SQLPasswordSaltFile => $salt_file,
5465      },
5466    },
5467  };
5468
5469  my ($port, $config_user, $config_group) = config_write($config_file, $config);
5470
5471  # Open pipes, for use between the parent and child processes.  Specifically,
5472  # the child will indicate when it's done with its test by writing a message
5473  # to the parent.
5474  my ($rfh, $wfh);
5475  unless (pipe($rfh, $wfh)) {
5476    die("Can't open pipe: $!");
5477  }
5478
5479  my $ex;
5480
5481  # Fork child
5482  $self->handle_sigchld();
5483  defined(my $pid = fork()) or die("Can't fork: $!");
5484  if ($pid) {
5485    eval {
5486      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
5487      $client->login($user, "password");
5488
5489      my $resp_msgs = $client->response_msgs();
5490      my $nmsgs = scalar(@$resp_msgs);
5491
5492      my $expected;
5493
5494      $expected = 1;
5495      $self->assert($expected == $nmsgs,
5496        test_msg("Expected $expected, got $nmsgs"));
5497
5498      $expected = "User proftpd logged in";
5499      $self->assert($expected eq $resp_msgs->[0],
5500        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
5501
5502    };
5503
5504    if ($@) {
5505      $ex = $@;
5506    }
5507
5508    $wfh->print("done\n");
5509    $wfh->flush();
5510
5511  } else {
5512    eval { server_wait($config_file, $rfh) };
5513    if ($@) {
5514      warn($@);
5515      exit 1;
5516    }
5517
5518    exit 0;
5519  }
5520
5521  # Stop server
5522  server_stop($pid_file);
5523
5524  $self->assert_child_ok($pid);
5525
5526  if ($ex) {
5527    test_append_logfile($log_file, $ex);
5528    unlink($log_file);
5529
5530    die($ex);
5531  }
5532
5533  unlink($log_file);
5534}
5535
5536sub sql_passwd_pbkdf2_per_user_bug4052 {
5537  my $self = shift;
5538  my $tmpdir = $self->{tmpdir};
5539
5540  my $config_file = "$tmpdir/sqlpasswd.conf";
5541  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5542  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5543
5544  my $log_file = test_get_logfile();
5545
5546  my $user = 'proftpd';
5547  my $group = 'ftpd';
5548
5549  # RFC 6070: PKCS#5 PBKDF2 Test Vectors
5550  #
5551  # Input:
5552  #   P = "password" (8 octets)
5553  #   S = "salt" (4 octets)
5554  #   c = 4096
5555  #   dkLen = 20
5556  #
5557  # Output:
5558  #   DK = 4b 00 79 01 b7 65 48 9a
5559  #        be ad 49 d9 26 f7 21 d0
5560  #        65 a4 29 c1             (20 octets)
5561  #
5562  # Base64:
5563  #   DK = SwB5AbdlSJq+rUnZJvch0GWkKcE=
5564  #
5565  my $passwd = "SwB5AbdlSJq+rUnZJvch0GWkKcE=";
5566
5567  my $home_dir = File::Spec->rel2abs($tmpdir);
5568  my $uid = 500;
5569  my $gid = 500;
5570
5571  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5572
5573  # Build up sqlite3 command to create users, groups tables and populate them
5574  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5575
5576  if (open(my $fh, "> $db_script")) {
5577    print $fh <<EOS;
5578CREATE TABLE users (
5579  userid TEXT,
5580  passwd TEXT,
5581  uid INTEGER,
5582  gid INTEGER,
5583  homedir TEXT,
5584  shell TEXT
5585);
5586INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5587
5588CREATE TABLE groups (
5589  groupname TEXT,
5590  gid INTEGER,
5591  members TEXT
5592);
5593INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
5594
5595CREATE TABLE user_pbkdf2 (
5596  userid TEXT,
5597  algo TEXT,
5598  rounds INTEGER,
5599  len INTEGER
5600);
5601INSERT INTO user_pbkdf2 (userid, algo, rounds, len) VALUES ('$user', 'sha1', 4096, 20);
5602EOS
5603
5604    unless (close($fh)) {
5605      die("Can't write $db_script: $!");
5606    }
5607
5608  } else {
5609    die("Can't open $db_script: $!");
5610  }
5611
5612  my $cmd = "sqlite3 $db_file < $db_script";
5613
5614  if ($ENV{TEST_VERBOSE}) {
5615    print STDERR "Executing sqlite3: $cmd\n";
5616  }
5617
5618  my @output = `$cmd`;
5619  if (scalar(@output) &&
5620      $ENV{TEST_VERBOSE}) {
5621    print STDERR "Output: ", join('', @output), "\n";
5622  }
5623
5624  my $salt = 'salt';
5625
5626  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
5627  if (open(my $fh, "> $salt_file")) {
5628    binmode($fh);
5629    print $fh $salt;
5630
5631    unless (close($fh)) {
5632      die("Can't write $salt_file: $!");
5633    }
5634
5635  } else {
5636    die("Can't open $salt_file: $!");
5637  }
5638
5639  my $config = {
5640    PidFile => $pid_file,
5641    ScoreboardFile => $scoreboard_file,
5642    SystemLog => $log_file,
5643
5644    IfModules => {
5645      'mod_delay.c' => {
5646        DelayEngine => 'off',
5647      },
5648
5649      'mod_sql.c' => {
5650        SQLAuthTypes => 'pbkdf2',
5651        SQLBackend => 'sqlite3',
5652        SQLConnectInfo => $db_file,
5653        SQLLogFile => $log_file,
5654        SQLNamedQuery => 'get-user-pbkdf2 SELECT "algo, rounds, len FROM user_pbkdf2 WHERE userid = \'%{0}\'"',
5655      },
5656
5657      'mod_sql_passwd.c' => {
5658        SQLPasswordEngine => 'on',
5659        SQLPasswordEncoding => 'base64',
5660        SQLPasswordPBKDF2 => 'sql:/get-user-pbkdf2',
5661        SQLPasswordSaltFile => $salt_file,
5662      },
5663    },
5664  };
5665
5666  my ($port, $config_user, $config_group) = config_write($config_file, $config);
5667
5668  # Open pipes, for use between the parent and child processes.  Specifically,
5669  # the child will indicate when it's done with its test by writing a message
5670  # to the parent.
5671  my ($rfh, $wfh);
5672  unless (pipe($rfh, $wfh)) {
5673    die("Can't open pipe: $!");
5674  }
5675
5676  my $ex;
5677
5678  # Fork child
5679  $self->handle_sigchld();
5680  defined(my $pid = fork()) or die("Can't fork: $!");
5681  if ($pid) {
5682    eval {
5683      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
5684      $client->login($user, "password");
5685
5686      my $resp_msgs = $client->response_msgs();
5687      my $nmsgs = scalar(@$resp_msgs);
5688
5689      my $expected;
5690
5691      $expected = 1;
5692      $self->assert($expected == $nmsgs,
5693        test_msg("Expected $expected, got $nmsgs"));
5694
5695      $expected = "User proftpd logged in";
5696      $self->assert($expected eq $resp_msgs->[0],
5697        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
5698    };
5699
5700    if ($@) {
5701      $ex = $@;
5702    }
5703
5704    $wfh->print("done\n");
5705    $wfh->flush();
5706
5707  } else {
5708    eval { server_wait($config_file, $rfh) };
5709    if ($@) {
5710      warn($@);
5711      exit 1;
5712    }
5713
5714    exit 0;
5715  }
5716
5717  # Stop server
5718  server_stop($pid_file);
5719
5720  $self->assert_child_ok($pid);
5721
5722  if ($ex) {
5723    test_append_logfile($log_file, $ex);
5724    unlink($log_file);
5725
5726    die($ex);
5727  }
5728
5729  unlink($log_file);
5730}
5731
5732sub sql_passwd_salt_file_with_user_salt {
5733  my $self = shift;
5734  my $tmpdir = $self->{tmpdir};
5735
5736  my $config_file = "$tmpdir/sqlpasswd.conf";
5737  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5738  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5739
5740  my $log_file = test_get_logfile();
5741
5742  my $global_salt = '8Hkqr7bnPaZ52j81VvuoWdOEuq6EeXwpiIw5Q679xzvEqwe128';
5743  my $user_salt = 'MyS00p3r$3kr3t$@lt';
5744
5745  my $user = 'proftpd';
5746  my $group = 'ftpd';
5747
5748  # I used:
5749  #
5750  #  Digest::SHA1::sha1_hex($user_salt . (lc("password")) . $global_salt);
5751  #
5752  # to generate this password.
5753  my $passwd = 'b939d5c8e2857d9e8d27b87939a27fb986cd41ef';
5754
5755  my $home_dir = File::Spec->rel2abs($tmpdir);
5756  my $uid = 500;
5757  my $gid = 500;
5758
5759  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5760
5761  # Build up sqlite3 command to create users, groups tables and populate them
5762  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5763
5764  if (open(my $fh, "> $db_script")) {
5765    print $fh <<EOS;
5766CREATE TABLE users (
5767  userid TEXT,
5768  passwd TEXT,
5769  uid INTEGER,
5770  gid INTEGER,
5771  homedir TEXT,
5772  shell TEXT
5773);
5774INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5775
5776CREATE TABLE groups (
5777  groupname TEXT,
5778  gid INTEGER,
5779  members TEXT
5780);
5781INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
5782
5783CREATE TABLE user_salts (
5784  userid TEXT,
5785  salt TEXT
5786);
5787INSERT INTO user_salts (userid, salt) VALUES ('$user', '$user_salt');
5788EOS
5789
5790    unless (close($fh)) {
5791      die("Can't write $db_script: $!");
5792    }
5793
5794  } else {
5795    die("Can't open $db_script: $!");
5796  }
5797
5798  my $cmd = "sqlite3 $db_file < $db_script";
5799
5800  if ($ENV{TEST_VERBOSE}) {
5801    print STDERR "Executing sqlite3: $cmd\n";
5802  }
5803
5804  my @output = `$cmd`;
5805  if (scalar(@output) &&
5806      $ENV{TEST_VERBOSE}) {
5807    print STDERR "Output: ", join('', @output), "\n";
5808  }
5809
5810  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
5811  if (open(my $fh, "> $salt_file")) {
5812    binmode($fh);
5813    print $fh "$global_salt\n";
5814
5815    unless (close($fh)) {
5816      die("Can't write $salt_file: $!");
5817    }
5818
5819  } else {
5820    die("Can't open $salt_file: $!");
5821  }
5822
5823  my $config = {
5824    PidFile => $pid_file,
5825    ScoreboardFile => $scoreboard_file,
5826    SystemLog => $log_file,
5827    TraceLog => $log_file,
5828    Trace => 'sql.passwd:20',
5829
5830    IfModules => {
5831      'mod_delay.c' => {
5832        DelayEngine => 'off',
5833      },
5834
5835      'mod_sql.c' => {
5836        SQLAuthTypes => 'sha1',
5837        SQLBackend => 'sqlite3',
5838        SQLConnectInfo => $db_file,
5839        SQLLogFile => $log_file,
5840        SQLMinID => '100',
5841        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
5842      },
5843
5844      'mod_sql_passwd.c' => {
5845        SQLPasswordEngine => 'on',
5846        SQLPasswordEncoding => 'hex',
5847        SQLPasswordSaltFile => "$salt_file Append",
5848        SQLPasswordUserSalt => "sql:/get-user-salt Prepend",
5849      },
5850    },
5851  };
5852
5853  my ($port, $config_user, $config_group) = config_write($config_file, $config);
5854
5855  # Open pipes, for use between the parent and child processes.  Specifically,
5856  # the child will indicate when it's done with its test by writing a message
5857  # to the parent.
5858  my ($rfh, $wfh);
5859  unless (pipe($rfh, $wfh)) {
5860    die("Can't open pipe: $!");
5861  }
5862
5863  my $ex;
5864
5865  # Fork child
5866  $self->handle_sigchld();
5867  defined(my $pid = fork()) or die("Can't fork: $!");
5868  if ($pid) {
5869    eval {
5870      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
5871      $client->login($user, "password");
5872
5873      my $resp_msgs = $client->response_msgs();
5874      my $nmsgs = scalar(@$resp_msgs);
5875
5876      my $expected;
5877
5878      $expected = 1;
5879      $self->assert($expected == $nmsgs,
5880        test_msg("Expected $expected, got $nmsgs"));
5881
5882      $expected = "User proftpd logged in";
5883      $self->assert($expected eq $resp_msgs->[0],
5884        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
5885
5886    };
5887
5888    if ($@) {
5889      $ex = $@;
5890    }
5891
5892    $wfh->print("done\n");
5893    $wfh->flush();
5894
5895  } else {
5896    eval { server_wait($config_file, $rfh) };
5897    if ($@) {
5898      warn($@);
5899      exit 1;
5900    }
5901
5902    exit 0;
5903  }
5904
5905  # Stop server
5906  server_stop($pid_file);
5907
5908  $self->assert_child_ok($pid);
5909
5910  if ($ex) {
5911    test_append_logfile($log_file, $ex);
5912    unlink($log_file);
5913
5914    die($ex);
5915  }
5916
5917  unlink($log_file);
5918}
5919
5920sub sql_passwd_user_salt_sql_base64_bug4138 {
5921  my $self = shift;
5922  my $tmpdir = $self->{tmpdir};
5923
5924  my $config_file = "$tmpdir/sqlpasswd.conf";
5925  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
5926  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
5927
5928  my $log_file = test_get_logfile();
5929
5930  my $user = 'proftpd';
5931
5932  # This base64-encoded salt data has embedded NULs
5933  my $salt = 'p8CCsyWyyDX/infYBQBolKobDsQlWca9LLgteqD+rSo=';
5934
5935  # I used:
5936  #
5937  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
5938  #
5939  # to generate this password.
5940  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
5941
5942  my $home_dir = File::Spec->rel2abs($tmpdir);
5943  my $uid = 500;
5944  my $gid = 500;
5945
5946  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
5947
5948  # Build up sqlite3 command to create users, groups tables and populate them
5949  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
5950
5951  if (open(my $fh, "> $db_script")) {
5952    print $fh <<EOS;
5953CREATE TABLE users (
5954  userid TEXT,
5955  passwd TEXT,
5956  uid INTEGER,
5957  gid INTEGER,
5958  homedir TEXT,
5959  shell TEXT
5960);
5961INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
5962
5963CREATE TABLE groups (
5964  groupname TEXT,
5965  gid INTEGER,
5966  members TEXT
5967);
5968INSERT INTO groups (groupname, gid, members) VALUES ('ftpd', $gid, '$user');
5969
5970CREATE TABLE user_salts (
5971  userid TEXT,
5972  salt TEXT
5973);
5974INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
5975EOS
5976
5977    unless (close($fh)) {
5978      die("Can't write $db_script: $!");
5979    }
5980
5981  } else {
5982    die("Can't open $db_script: $!");
5983  }
5984
5985  my $cmd = "sqlite3 $db_file < $db_script";
5986
5987  if ($ENV{TEST_VERBOSE}) {
5988    print STDERR "Executing sqlite3: $cmd\n";
5989  }
5990
5991  my @output = `$cmd`;
5992  if (scalar(@output) &&
5993      $ENV{TEST_VERBOSE}) {
5994    print STDERR "Output: ", join('', @output), "\n";
5995  }
5996
5997  my $config = {
5998    PidFile => $pid_file,
5999    ScoreboardFile => $scoreboard_file,
6000    SystemLog => $log_file,
6001
6002    IfModules => {
6003      'mod_delay.c' => {
6004        DelayEngine => 'off',
6005      },
6006
6007      'mod_sql.c' => {
6008        SQLAuthTypes => 'sha1',
6009        SQLBackend => 'sqlite3',
6010        SQLConnectInfo => $db_file,
6011        SQLLogFile => $log_file,
6012        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
6013      },
6014
6015      'mod_sql_passwd.c' => {
6016        SQLPasswordEngine => 'on',
6017        SQLPasswordEncoding => 'hex',
6018        SQLPasswordSaltEncoding => 'base64',
6019        SQLPasswordUserSalt => 'sql:/get-user-salt',
6020      },
6021    },
6022  };
6023
6024  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6025
6026  # Open pipes, for use between the parent and child processes.  Specifically,
6027  # the child will indicate when it's done with its test by writing a message
6028  # to the parent.
6029  my ($rfh, $wfh);
6030  unless (pipe($rfh, $wfh)) {
6031    die("Can't open pipe: $!");
6032  }
6033
6034  my $ex;
6035
6036  # Fork child
6037  $self->handle_sigchld();
6038  defined(my $pid = fork()) or die("Can't fork: $!");
6039  if ($pid) {
6040    eval {
6041      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6042      $client->login($user, "password");
6043
6044      my $resp_msgs = $client->response_msgs();
6045      my $nmsgs = scalar(@$resp_msgs);
6046
6047      my $expected;
6048
6049      $expected = 1;
6050      $self->assert($expected == $nmsgs,
6051        test_msg("Expected $expected, got $nmsgs"));
6052
6053      $expected = "User proftpd logged in";
6054      $self->assert($expected eq $resp_msgs->[0],
6055        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6056
6057    };
6058
6059    if ($@) {
6060      $ex = $@;
6061    }
6062
6063    $wfh->print("done\n");
6064    $wfh->flush();
6065
6066  } else {
6067    eval { server_wait($config_file, $rfh) };
6068    if ($@) {
6069      warn($@);
6070      exit 1;
6071    }
6072
6073    exit 0;
6074  }
6075
6076  # Stop server
6077  server_stop($pid_file);
6078
6079  $self->assert_child_ok($pid);
6080
6081  if ($ex) {
6082    test_append_logfile($log_file, $ex);
6083    unlink($log_file);
6084
6085    die($ex);
6086  }
6087
6088  unlink($log_file);
6089}
6090
6091sub sql_passwd_user_salt_sql_hex_lc_bug4138 {
6092  my $self = shift;
6093  my $tmpdir = $self->{tmpdir};
6094
6095  my $config_file = "$tmpdir/sqlpasswd.conf";
6096  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6097  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6098
6099  my $log_file = test_get_logfile();
6100
6101  my $user = 'proftpd';
6102
6103  # This hex-encoded salt data has embedded NULs
6104  my $salt = 'a7c082b325b2c835ff8a77d805006894aa1b0ec42559c6bd2cb82d7aa0fead2a';
6105
6106  # I used:
6107  #
6108  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
6109  #
6110  # to generate this password.
6111  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
6112
6113  my $home_dir = File::Spec->rel2abs($tmpdir);
6114  my $uid = 500;
6115  my $gid = 500;
6116
6117  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6118
6119  # Build up sqlite3 command to create users, groups tables and populate them
6120  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6121
6122  if (open(my $fh, "> $db_script")) {
6123    print $fh <<EOS;
6124CREATE TABLE users (
6125  userid TEXT,
6126  passwd TEXT,
6127  uid INTEGER,
6128  gid INTEGER,
6129  homedir TEXT,
6130  shell TEXT
6131);
6132INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
6133
6134CREATE TABLE groups (
6135  groupname TEXT,
6136  gid INTEGER,
6137  members TEXT
6138);
6139INSERT INTO groups (groupname, gid, members) VALUES ('ftpd', $gid, '$user');
6140
6141CREATE TABLE user_salts (
6142  userid TEXT,
6143  salt TEXT
6144);
6145INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
6146EOS
6147
6148    unless (close($fh)) {
6149      die("Can't write $db_script: $!");
6150    }
6151
6152  } else {
6153    die("Can't open $db_script: $!");
6154  }
6155
6156  my $cmd = "sqlite3 $db_file < $db_script";
6157
6158  if ($ENV{TEST_VERBOSE}) {
6159    print STDERR "Executing sqlite3: $cmd\n";
6160  }
6161
6162  my @output = `$cmd`;
6163  if (scalar(@output) &&
6164      $ENV{TEST_VERBOSE}) {
6165    print STDERR "Output: ", join('', @output), "\n";
6166  }
6167
6168  my $config = {
6169    PidFile => $pid_file,
6170    ScoreboardFile => $scoreboard_file,
6171    SystemLog => $log_file,
6172
6173    IfModules => {
6174      'mod_delay.c' => {
6175        DelayEngine => 'off',
6176      },
6177
6178      'mod_sql.c' => {
6179        SQLAuthTypes => 'sha1',
6180        SQLBackend => 'sqlite3',
6181        SQLConnectInfo => $db_file,
6182        SQLLogFile => $log_file,
6183        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
6184      },
6185
6186      'mod_sql_passwd.c' => {
6187        SQLPasswordEngine => 'on',
6188        SQLPasswordEncoding => 'hex',
6189        SQLPasswordSaltEncoding => 'hex',
6190        SQLPasswordUserSalt => 'sql:/get-user-salt',
6191      },
6192    },
6193  };
6194
6195  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6196
6197  # Open pipes, for use between the parent and child processes.  Specifically,
6198  # the child will indicate when it's done with its test by writing a message
6199  # to the parent.
6200  my ($rfh, $wfh);
6201  unless (pipe($rfh, $wfh)) {
6202    die("Can't open pipe: $!");
6203  }
6204
6205  my $ex;
6206
6207  # Fork child
6208  $self->handle_sigchld();
6209  defined(my $pid = fork()) or die("Can't fork: $!");
6210  if ($pid) {
6211    eval {
6212      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6213      $client->login($user, "password");
6214
6215      my $resp_msgs = $client->response_msgs();
6216      my $nmsgs = scalar(@$resp_msgs);
6217
6218      my $expected;
6219
6220      $expected = 1;
6221      $self->assert($expected == $nmsgs,
6222        test_msg("Expected $expected, got $nmsgs"));
6223
6224      $expected = "User proftpd logged in";
6225      $self->assert($expected eq $resp_msgs->[0],
6226        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6227
6228    };
6229
6230    if ($@) {
6231      $ex = $@;
6232    }
6233
6234    $wfh->print("done\n");
6235    $wfh->flush();
6236
6237  } else {
6238    eval { server_wait($config_file, $rfh) };
6239    if ($@) {
6240      warn($@);
6241      exit 1;
6242    }
6243
6244    exit 0;
6245  }
6246
6247  # Stop server
6248  server_stop($pid_file);
6249
6250  $self->assert_child_ok($pid);
6251
6252  if ($ex) {
6253    test_append_logfile($log_file, $ex);
6254    unlink($log_file);
6255
6256    die($ex);
6257  }
6258
6259  unlink($log_file);
6260}
6261
6262sub sql_passwd_user_salt_sql_hex_uc_bug4138 {
6263  my $self = shift;
6264  my $tmpdir = $self->{tmpdir};
6265
6266  my $config_file = "$tmpdir/sqlpasswd.conf";
6267  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6268  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6269
6270  my $log_file = test_get_logfile();
6271
6272  my $user = 'proftpd';
6273
6274  # This hex-encoded salt data has embedded NULs
6275  my $salt = 'A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A';
6276
6277  # I used:
6278  #
6279  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
6280  #
6281  # to generate this password.
6282  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
6283
6284  my $home_dir = File::Spec->rel2abs($tmpdir);
6285  my $uid = 500;
6286  my $gid = 500;
6287
6288  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6289
6290  # Build up sqlite3 command to create users, groups tables and populate them
6291  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6292
6293  if (open(my $fh, "> $db_script")) {
6294    print $fh <<EOS;
6295CREATE TABLE users (
6296  userid TEXT,
6297  passwd TEXT,
6298  uid INTEGER,
6299  gid INTEGER,
6300  homedir TEXT,
6301  shell TEXT
6302);
6303INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
6304
6305CREATE TABLE groups (
6306  groupname TEXT,
6307  gid INTEGER,
6308  members TEXT
6309);
6310INSERT INTO groups (groupname, gid, members) VALUES ('ftpd', $gid, '$user');
6311
6312CREATE TABLE user_salts (
6313  userid TEXT,
6314  salt TEXT
6315);
6316INSERT INTO user_salts (userid, salt) VALUES ('$user', '$salt');
6317EOS
6318
6319    unless (close($fh)) {
6320      die("Can't write $db_script: $!");
6321    }
6322
6323  } else {
6324    die("Can't open $db_script: $!");
6325  }
6326
6327  my $cmd = "sqlite3 $db_file < $db_script";
6328
6329  if ($ENV{TEST_VERBOSE}) {
6330    print STDERR "Executing sqlite3: $cmd\n";
6331  }
6332
6333  my @output = `$cmd`;
6334  if (scalar(@output) &&
6335      $ENV{TEST_VERBOSE}) {
6336    print STDERR "Output: ", join('', @output), "\n";
6337  }
6338
6339  my $config = {
6340    PidFile => $pid_file,
6341    ScoreboardFile => $scoreboard_file,
6342    SystemLog => $log_file,
6343
6344    IfModules => {
6345      'mod_delay.c' => {
6346        DelayEngine => 'off',
6347      },
6348
6349      'mod_sql.c' => {
6350        SQLAuthTypes => 'sha1',
6351        SQLBackend => 'sqlite3',
6352        SQLConnectInfo => $db_file,
6353        SQLLogFile => $log_file,
6354        SQLNamedQuery => 'get-user-salt SELECT "salt FROM user_salts WHERE userid = \'%{0}\'"',
6355      },
6356
6357      'mod_sql_passwd.c' => {
6358        SQLPasswordEngine => 'on',
6359        SQLPasswordEncoding => 'hex',
6360        SQLPasswordSaltEncoding => 'HEX',
6361        SQLPasswordUserSalt => 'sql:/get-user-salt',
6362      },
6363    },
6364  };
6365
6366  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6367
6368  # Open pipes, for use between the parent and child processes.  Specifically,
6369  # the child will indicate when it's done with its test by writing a message
6370  # to the parent.
6371  my ($rfh, $wfh);
6372  unless (pipe($rfh, $wfh)) {
6373    die("Can't open pipe: $!");
6374  }
6375
6376  my $ex;
6377
6378  # Fork child
6379  $self->handle_sigchld();
6380  defined(my $pid = fork()) or die("Can't fork: $!");
6381  if ($pid) {
6382    eval {
6383      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6384      $client->login($user, "password");
6385
6386      my $resp_msgs = $client->response_msgs();
6387      my $nmsgs = scalar(@$resp_msgs);
6388
6389      my $expected;
6390
6391      $expected = 1;
6392      $self->assert($expected == $nmsgs,
6393        test_msg("Expected $expected, got $nmsgs"));
6394
6395      $expected = "User proftpd logged in";
6396      $self->assert($expected eq $resp_msgs->[0],
6397        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6398
6399    };
6400
6401    if ($@) {
6402      $ex = $@;
6403    }
6404
6405    $wfh->print("done\n");
6406    $wfh->flush();
6407
6408  } else {
6409    eval { server_wait($config_file, $rfh) };
6410    if ($@) {
6411      warn($@);
6412      exit 1;
6413    }
6414
6415    exit 0;
6416  }
6417
6418  # Stop server
6419  server_stop($pid_file);
6420
6421  $self->assert_child_ok($pid);
6422
6423  if ($ex) {
6424    test_append_logfile($log_file, $ex);
6425    unlink($log_file);
6426
6427    die($ex);
6428  }
6429
6430  unlink($log_file);
6431}
6432
6433sub sql_passwd_salt_file_base64_bug4138 {
6434  my $self = shift;
6435  my $tmpdir = $self->{tmpdir};
6436
6437  my $config_file = "$tmpdir/sqlpasswd.conf";
6438  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6439  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6440
6441  my $log_file = test_get_logfile();
6442
6443  # This base64-encoded salt data has embedded NULs
6444  my $salt = 'p8CCsyWyyDX/infYBQBolKobDsQlWca9LLgteqD+rSo=';
6445
6446  my $user = 'proftpd';
6447  my $group = 'ftpd';
6448
6449  # I used:
6450  #
6451  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
6452  #
6453  # to generate this password.
6454  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
6455
6456  my $home_dir = File::Spec->rel2abs($tmpdir);
6457  my $uid = 500;
6458  my $gid = 500;
6459
6460  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6461
6462  # Build up sqlite3 command to create users, groups tables and populate them
6463  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6464
6465  if (open(my $fh, "> $db_script")) {
6466    print $fh <<EOS;
6467CREATE TABLE users (
6468  userid TEXT,
6469  passwd TEXT,
6470  uid INTEGER,
6471  gid INTEGER,
6472  homedir TEXT,
6473  shell TEXT
6474);
6475INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
6476
6477CREATE TABLE groups (
6478  groupname TEXT,
6479  gid INTEGER,
6480  members TEXT
6481);
6482INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
6483EOS
6484
6485    unless (close($fh)) {
6486      die("Can't write $db_script: $!");
6487    }
6488
6489  } else {
6490    die("Can't open $db_script: $!");
6491  }
6492
6493  my $cmd = "sqlite3 $db_file < $db_script";
6494
6495  if ($ENV{TEST_VERBOSE}) {
6496    print STDERR "Executing sqlite3: $cmd\n";
6497  }
6498
6499  my @output = `$cmd`;
6500  if (scalar(@output) &&
6501      $ENV{TEST_VERBOSE}) {
6502    print STDERR "Output: ", join('', @output), "\n";
6503  }
6504
6505  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
6506  if (open(my $fh, "> $salt_file")) {
6507    binmode($fh);
6508    print $fh $salt;
6509
6510    unless (close($fh)) {
6511      die("Can't write $salt_file: $!");
6512    }
6513
6514  } else {
6515    die("Can't open $salt_file: $!");
6516  }
6517
6518  my $config = {
6519    PidFile => $pid_file,
6520    ScoreboardFile => $scoreboard_file,
6521    SystemLog => $log_file,
6522
6523    IfModules => {
6524      'mod_delay.c' => {
6525        DelayEngine => 'off',
6526      },
6527
6528      'mod_sql.c' => {
6529        SQLAuthTypes => 'sha1',
6530        SQLBackend => 'sqlite3',
6531        SQLConnectInfo => $db_file,
6532        SQLLogFile => $log_file,
6533      },
6534
6535      'mod_sql_passwd.c' => {
6536        SQLPasswordEngine => 'on',
6537        SQLPasswordEncoding => 'hex',
6538        SQLPasswordSaltEncoding => 'base64',
6539        SQLPasswordSaltFile => $salt_file,
6540      },
6541    },
6542  };
6543
6544  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6545
6546  # Open pipes, for use between the parent and child processes.  Specifically,
6547  # the child will indicate when it's done with its test by writing a message
6548  # to the parent.
6549  my ($rfh, $wfh);
6550  unless (pipe($rfh, $wfh)) {
6551    die("Can't open pipe: $!");
6552  }
6553
6554  my $ex;
6555
6556  # Fork child
6557  $self->handle_sigchld();
6558  defined(my $pid = fork()) or die("Can't fork: $!");
6559  if ($pid) {
6560    eval {
6561      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6562      $client->login($user, "password");
6563
6564      my $resp_msgs = $client->response_msgs();
6565      my $nmsgs = scalar(@$resp_msgs);
6566
6567      my $expected;
6568
6569      $expected = 1;
6570      $self->assert($expected == $nmsgs,
6571        test_msg("Expected $expected, got $nmsgs"));
6572
6573      $expected = "User proftpd logged in";
6574      $self->assert($expected eq $resp_msgs->[0],
6575        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6576
6577    };
6578
6579    if ($@) {
6580      $ex = $@;
6581    }
6582
6583    $wfh->print("done\n");
6584    $wfh->flush();
6585
6586  } else {
6587    eval { server_wait($config_file, $rfh) };
6588    if ($@) {
6589      warn($@);
6590      exit 1;
6591    }
6592
6593    exit 0;
6594  }
6595
6596  # Stop server
6597  server_stop($pid_file);
6598
6599  $self->assert_child_ok($pid);
6600
6601  if ($ex) {
6602    test_append_logfile($log_file, $ex);
6603    unlink($log_file);
6604
6605    die($ex);
6606  }
6607
6608  unlink($log_file);
6609}
6610
6611sub sql_passwd_salt_file_hex_lc_bug4138 {
6612  my $self = shift;
6613  my $tmpdir = $self->{tmpdir};
6614
6615  my $config_file = "$tmpdir/sqlpasswd.conf";
6616  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6617  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6618
6619  my $log_file = test_get_logfile();
6620
6621  # This hex-encoded salt data has embedded NULs
6622  my $salt = 'a7c082b325b2c835ff8a77d805006894aa1b0ec42559c6bd2cb82d7aa0fead2a';
6623
6624  my $user = 'proftpd';
6625  my $group = 'ftpd';
6626
6627  # I used:
6628  #
6629  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
6630  #
6631  # to generate this password.
6632  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
6633
6634  my $home_dir = File::Spec->rel2abs($tmpdir);
6635  my $uid = 500;
6636  my $gid = 500;
6637
6638  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6639
6640  # Build up sqlite3 command to create users, groups tables and populate them
6641  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6642
6643  if (open(my $fh, "> $db_script")) {
6644    print $fh <<EOS;
6645CREATE TABLE users (
6646  userid TEXT,
6647  passwd TEXT,
6648  uid INTEGER,
6649  gid INTEGER,
6650  homedir TEXT,
6651  shell TEXT
6652);
6653INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
6654
6655CREATE TABLE groups (
6656  groupname TEXT,
6657  gid INTEGER,
6658  members TEXT
6659);
6660INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
6661EOS
6662
6663    unless (close($fh)) {
6664      die("Can't write $db_script: $!");
6665    }
6666
6667  } else {
6668    die("Can't open $db_script: $!");
6669  }
6670
6671  my $cmd = "sqlite3 $db_file < $db_script";
6672
6673  if ($ENV{TEST_VERBOSE}) {
6674    print STDERR "Executing sqlite3: $cmd\n";
6675  }
6676
6677  my @output = `$cmd`;
6678  if (scalar(@output) &&
6679      $ENV{TEST_VERBOSE}) {
6680    print STDERR "Output: ", join('', @output), "\n";
6681  }
6682
6683  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
6684  if (open(my $fh, "> $salt_file")) {
6685    binmode($fh);
6686    print $fh $salt;
6687
6688    unless (close($fh)) {
6689      die("Can't write $salt_file: $!");
6690    }
6691
6692  } else {
6693    die("Can't open $salt_file: $!");
6694  }
6695
6696  my $config = {
6697    PidFile => $pid_file,
6698    ScoreboardFile => $scoreboard_file,
6699    SystemLog => $log_file,
6700
6701    IfModules => {
6702      'mod_delay.c' => {
6703        DelayEngine => 'off',
6704      },
6705
6706      'mod_sql.c' => {
6707        SQLAuthTypes => 'sha1',
6708        SQLBackend => 'sqlite3',
6709        SQLConnectInfo => $db_file,
6710        SQLLogFile => $log_file,
6711      },
6712
6713      'mod_sql_passwd.c' => {
6714        SQLPasswordEngine => 'on',
6715        SQLPasswordEncoding => 'hex',
6716        SQLPasswordSaltEncoding => 'hex',
6717        SQLPasswordSaltFile => $salt_file,
6718      },
6719    },
6720  };
6721
6722  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6723
6724  # Open pipes, for use between the parent and child processes.  Specifically,
6725  # the child will indicate when it's done with its test by writing a message
6726  # to the parent.
6727  my ($rfh, $wfh);
6728  unless (pipe($rfh, $wfh)) {
6729    die("Can't open pipe: $!");
6730  }
6731
6732  my $ex;
6733
6734  # Fork child
6735  $self->handle_sigchld();
6736  defined(my $pid = fork()) or die("Can't fork: $!");
6737  if ($pid) {
6738    eval {
6739      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6740      $client->login($user, "password");
6741
6742      my $resp_msgs = $client->response_msgs();
6743      my $nmsgs = scalar(@$resp_msgs);
6744
6745      my $expected;
6746
6747      $expected = 1;
6748      $self->assert($expected == $nmsgs,
6749        test_msg("Expected $expected, got $nmsgs"));
6750
6751      $expected = "User proftpd logged in";
6752      $self->assert($expected eq $resp_msgs->[0],
6753        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6754
6755    };
6756
6757    if ($@) {
6758      $ex = $@;
6759    }
6760
6761    $wfh->print("done\n");
6762    $wfh->flush();
6763
6764  } else {
6765    eval { server_wait($config_file, $rfh) };
6766    if ($@) {
6767      warn($@);
6768      exit 1;
6769    }
6770
6771    exit 0;
6772  }
6773
6774  # Stop server
6775  server_stop($pid_file);
6776
6777  $self->assert_child_ok($pid);
6778
6779  if ($ex) {
6780    test_append_logfile($log_file, $ex);
6781    unlink($log_file);
6782
6783    die($ex);
6784  }
6785
6786  unlink($log_file);
6787}
6788
6789sub sql_passwd_salt_file_hex_uc_bug4138 {
6790  my $self = shift;
6791  my $tmpdir = $self->{tmpdir};
6792
6793  my $config_file = "$tmpdir/sqlpasswd.conf";
6794  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6795  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6796
6797  my $log_file = test_get_logfile();
6798
6799  # This hex-encoded salt data has embedded NULs
6800  my $salt = 'A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A';
6801
6802  my $user = 'proftpd';
6803  my $group = 'ftpd';
6804
6805  # I used:
6806  #
6807  #  Digest::SHA1::sha1_hex((lc("password")) . $decoded_salt);
6808  #
6809  # to generate this password.
6810  my $passwd = '39ff37588e1a56b243b00cb6479a716ef50fc980';
6811
6812  my $home_dir = File::Spec->rel2abs($tmpdir);
6813  my $uid = 500;
6814  my $gid = 500;
6815
6816  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6817
6818  # Build up sqlite3 command to create users, groups tables and populate them
6819  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6820
6821  if (open(my $fh, "> $db_script")) {
6822    print $fh <<EOS;
6823CREATE TABLE users (
6824  userid TEXT,
6825  passwd TEXT,
6826  uid INTEGER,
6827  gid INTEGER,
6828  homedir TEXT,
6829  shell TEXT
6830);
6831INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
6832
6833CREATE TABLE groups (
6834  groupname TEXT,
6835  gid INTEGER,
6836  members TEXT
6837);
6838INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
6839EOS
6840
6841    unless (close($fh)) {
6842      die("Can't write $db_script: $!");
6843    }
6844
6845  } else {
6846    die("Can't open $db_script: $!");
6847  }
6848
6849  my $cmd = "sqlite3 $db_file < $db_script";
6850
6851  if ($ENV{TEST_VERBOSE}) {
6852    print STDERR "Executing sqlite3: $cmd\n";
6853  }
6854
6855  my @output = `$cmd`;
6856  if (scalar(@output) &&
6857      $ENV{TEST_VERBOSE}) {
6858    print STDERR "Output: ", join('', @output), "\n";
6859  }
6860
6861  my $salt_file = File::Spec->rel2abs("$home_dir/sqlpasswd.salt");
6862  if (open(my $fh, "> $salt_file")) {
6863    binmode($fh);
6864    print $fh $salt;
6865
6866    unless (close($fh)) {
6867      die("Can't write $salt_file: $!");
6868    }
6869
6870  } else {
6871    die("Can't open $salt_file: $!");
6872  }
6873
6874  my $config = {
6875    PidFile => $pid_file,
6876    ScoreboardFile => $scoreboard_file,
6877    SystemLog => $log_file,
6878
6879    IfModules => {
6880      'mod_delay.c' => {
6881        DelayEngine => 'off',
6882      },
6883
6884      'mod_sql.c' => {
6885        SQLAuthTypes => 'sha1',
6886        SQLBackend => 'sqlite3',
6887        SQLConnectInfo => $db_file,
6888        SQLLogFile => $log_file,
6889      },
6890
6891      'mod_sql_passwd.c' => {
6892        SQLPasswordEngine => 'on',
6893        SQLPasswordEncoding => 'hex',
6894        SQLPasswordSaltEncoding => 'HEX',
6895        SQLPasswordSaltFile => $salt_file,
6896      },
6897    },
6898  };
6899
6900  my ($port, $config_user, $config_group) = config_write($config_file, $config);
6901
6902  # Open pipes, for use between the parent and child processes.  Specifically,
6903  # the child will indicate when it's done with its test by writing a message
6904  # to the parent.
6905  my ($rfh, $wfh);
6906  unless (pipe($rfh, $wfh)) {
6907    die("Can't open pipe: $!");
6908  }
6909
6910  my $ex;
6911
6912  # Fork child
6913  $self->handle_sigchld();
6914  defined(my $pid = fork()) or die("Can't fork: $!");
6915  if ($pid) {
6916    eval {
6917      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
6918      $client->login($user, "password");
6919
6920      my $resp_msgs = $client->response_msgs();
6921      my $nmsgs = scalar(@$resp_msgs);
6922
6923      my $expected;
6924
6925      $expected = 1;
6926      $self->assert($expected == $nmsgs,
6927        test_msg("Expected $expected, got $nmsgs"));
6928
6929      $expected = "User proftpd logged in";
6930      $self->assert($expected eq $resp_msgs->[0],
6931        test_msg("Expected '$expected', got '$resp_msgs->[0]'"));
6932
6933    };
6934
6935    if ($@) {
6936      $ex = $@;
6937    }
6938
6939    $wfh->print("done\n");
6940    $wfh->flush();
6941
6942  } else {
6943    eval { server_wait($config_file, $rfh) };
6944    if ($@) {
6945      warn($@);
6946      exit 1;
6947    }
6948
6949    exit 0;
6950  }
6951
6952  # Stop server
6953  server_stop($pid_file);
6954
6955  $self->assert_child_ok($pid);
6956
6957  if ($ex) {
6958    test_append_logfile($log_file, $ex);
6959    unlink($log_file);
6960
6961    die($ex);
6962  }
6963
6964  unlink($log_file);
6965}
6966
6967sub sql_passwd_scrypt_base64_interactive_cost {
6968  my $self = shift;
6969  my $tmpdir = $self->{tmpdir};
6970
6971  my $config_file = "$tmpdir/sqlpasswd.conf";
6972  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
6973  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
6974
6975  my $log_file = test_get_logfile();
6976
6977  my $user = 'proftpd';
6978  my $group = 'ftpd';
6979
6980  # I had to look at the mod_sql_passwd-generated logs to get this password;
6981  # this means it's a bit incestuous and thus suspect.
6982  my $passwd = 'ZI+02v+UMvtIvFBALSHyBiNu9At1h+HgN6xzI7nhAtM=';
6983
6984  my $home_dir = File::Spec->rel2abs($tmpdir);
6985  my $uid = 500;
6986  my $gid = 500;
6987
6988  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
6989
6990  # Build up sqlite3 command to create users, groups tables and populate them
6991  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
6992
6993  if (open(my $fh, "> $db_script")) {
6994    print $fh <<EOS;
6995CREATE TABLE users (
6996  userid TEXT,
6997  passwd TEXT,
6998  uid INTEGER,
6999  gid INTEGER,
7000  homedir TEXT,
7001  shell TEXT
7002);
7003INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7004
7005CREATE TABLE groups (
7006  groupname TEXT,
7007  gid INTEGER,
7008  members TEXT
7009);
7010INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7011EOS
7012
7013    unless (close($fh)) {
7014      die("Can't write $db_script: $!");
7015    }
7016
7017  } else {
7018    die("Can't open $db_script: $!");
7019  }
7020
7021  my $cmd = "sqlite3 $db_file < $db_script";
7022
7023  if ($ENV{TEST_VERBOSE}) {
7024    print STDERR "Executing sqlite3: $cmd\n";
7025  }
7026
7027  my @output = `$cmd`;
7028  if (scalar(@output) &&
7029      $ENV{TEST_VERBOSE}) {
7030    print STDERR "Output: ", join('', @output), "\n";
7031  }
7032
7033  # This hex-encoded salt data has embedded NULs
7034  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7035  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7036  if (open(my $fh, "> $salt_file")) {
7037    binmode($fh);
7038    print $fh $salt;
7039
7040    unless (close($fh)) {
7041      die("Can't write $salt_file: $!");
7042    }
7043
7044  } else {
7045    die("Can't open $salt_file: $!");
7046  }
7047
7048  my $config = {
7049    PidFile => $pid_file,
7050    ScoreboardFile => $scoreboard_file,
7051    SystemLog => $log_file,
7052    TraceLog => $log_file,
7053    Trace => 'sql.passwd:20',
7054
7055    IfModules => {
7056      'mod_delay.c' => {
7057        DelayEngine => 'off',
7058      },
7059
7060      'mod_sql.c' => {
7061        SQLAuthTypes => 'scrypt',
7062        SQLBackend => 'sqlite3',
7063        SQLConnectInfo => $db_file,
7064        SQLLogFile => $log_file,
7065      },
7066
7067      'mod_sql_passwd.c' => {
7068        SQLPasswordEngine => 'on',
7069        SQLPasswordEncoding => 'base64',
7070        SQLPasswordSaltEncoding => 'hex',
7071        SQLPasswordSaltFile => $salt_file,
7072        SQLPasswordCost => 'Interactive',
7073      },
7074    },
7075  };
7076
7077  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7078
7079  # Open pipes, for use between the parent and child processes.  Specifically,
7080  # the child will indicate when it's done with its test by writing a message
7081  # to the parent.
7082  my ($rfh, $wfh);
7083  unless (pipe($rfh, $wfh)) {
7084    die("Can't open pipe: $!");
7085  }
7086
7087  my $ex;
7088
7089  # Fork child
7090  $self->handle_sigchld();
7091  defined(my $pid = fork()) or die("Can't fork: $!");
7092  if ($pid) {
7093    eval {
7094      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7095      $client->login($user, "test");
7096
7097      my $resp_msgs = $client->response_msgs();
7098      my $nmsgs = scalar(@$resp_msgs);
7099
7100      my $expected;
7101
7102      $expected = 1;
7103      $self->assert($expected == $nmsgs,
7104        test_msg("Expected response message count $expected, got $nmsgs"));
7105
7106      $expected = "User $user logged in";
7107      $self->assert($expected eq $resp_msgs->[0],
7108        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7109    };
7110
7111    if ($@) {
7112      $ex = $@;
7113    }
7114
7115    $wfh->print("done\n");
7116    $wfh->flush();
7117
7118  } else {
7119    eval { server_wait($config_file, $rfh) };
7120    if ($@) {
7121      warn($@);
7122      exit 1;
7123    }
7124
7125    exit 0;
7126  }
7127
7128  # Stop server
7129  server_stop($pid_file);
7130
7131  $self->assert_child_ok($pid);
7132
7133  if ($ex) {
7134    test_append_logfile($log_file, $ex);
7135    unlink($log_file);
7136
7137    die($ex);
7138  }
7139
7140  unlink($log_file);
7141}
7142
7143sub sql_passwd_scrypt_hex_lc_interactive_cost {
7144  my $self = shift;
7145  my $tmpdir = $self->{tmpdir};
7146
7147  my $config_file = "$tmpdir/sqlpasswd.conf";
7148  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
7149  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
7150
7151  my $log_file = test_get_logfile();
7152
7153  my $user = 'proftpd';
7154  my $group = 'ftpd';
7155
7156  # I had to look at the mod_sql_passwd-generated logs to get this password;
7157  # this means it's a bit incestuous and thus suspect.
7158  my $passwd = '648fb4daff9432fb48bc50402d21f206236ef40b7587e1e037ac7323b9e102d3';
7159
7160  my $home_dir = File::Spec->rel2abs($tmpdir);
7161  my $uid = 500;
7162  my $gid = 500;
7163
7164  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
7165
7166  # Build up sqlite3 command to create users, groups tables and populate them
7167  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
7168
7169  if (open(my $fh, "> $db_script")) {
7170    print $fh <<EOS;
7171CREATE TABLE users (
7172  userid TEXT,
7173  passwd TEXT,
7174  uid INTEGER,
7175  gid INTEGER,
7176  homedir TEXT,
7177  shell TEXT
7178);
7179INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7180
7181CREATE TABLE groups (
7182  groupname TEXT,
7183  gid INTEGER,
7184  members TEXT
7185);
7186INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7187EOS
7188
7189    unless (close($fh)) {
7190      die("Can't write $db_script: $!");
7191    }
7192
7193  } else {
7194    die("Can't open $db_script: $!");
7195  }
7196
7197  my $cmd = "sqlite3 $db_file < $db_script";
7198
7199  if ($ENV{TEST_VERBOSE}) {
7200    print STDERR "Executing sqlite3: $cmd\n";
7201  }
7202
7203  my @output = `$cmd`;
7204  if (scalar(@output) &&
7205      $ENV{TEST_VERBOSE}) {
7206    print STDERR "Output: ", join('', @output), "\n";
7207  }
7208
7209  # This hex-encoded salt data has embedded NULs
7210  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7211  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7212  if (open(my $fh, "> $salt_file")) {
7213    binmode($fh);
7214    print $fh $salt;
7215
7216    unless (close($fh)) {
7217      die("Can't write $salt_file: $!");
7218    }
7219
7220  } else {
7221    die("Can't open $salt_file: $!");
7222  }
7223
7224  my $config = {
7225    PidFile => $pid_file,
7226    ScoreboardFile => $scoreboard_file,
7227    SystemLog => $log_file,
7228    TraceLog => $log_file,
7229    Trace => 'sql.passwd:20',
7230
7231    IfModules => {
7232      'mod_delay.c' => {
7233        DelayEngine => 'off',
7234      },
7235
7236      'mod_sql.c' => {
7237        SQLAuthTypes => 'scrypt',
7238        SQLBackend => 'sqlite3',
7239        SQLConnectInfo => $db_file,
7240        SQLLogFile => $log_file,
7241      },
7242
7243      'mod_sql_passwd.c' => {
7244        SQLPasswordEngine => 'on',
7245        SQLPasswordEncoding => 'hex',
7246        SQLPasswordSaltEncoding => 'hex',
7247        SQLPasswordSaltFile => $salt_file,
7248        SQLPasswordCost => 'Interactive',
7249      },
7250    },
7251  };
7252
7253  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7254
7255  # Open pipes, for use between the parent and child processes.  Specifically,
7256  # the child will indicate when it's done with its test by writing a message
7257  # to the parent.
7258  my ($rfh, $wfh);
7259  unless (pipe($rfh, $wfh)) {
7260    die("Can't open pipe: $!");
7261  }
7262
7263  my $ex;
7264
7265  # Fork child
7266  $self->handle_sigchld();
7267  defined(my $pid = fork()) or die("Can't fork: $!");
7268  if ($pid) {
7269    eval {
7270      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7271      $client->login($user, "test");
7272
7273      my $resp_msgs = $client->response_msgs();
7274      my $nmsgs = scalar(@$resp_msgs);
7275
7276      my $expected;
7277
7278      $expected = 1;
7279      $self->assert($expected == $nmsgs,
7280        test_msg("Expected response message count $expected, got $nmsgs"));
7281
7282      $expected = "User $user logged in";
7283      $self->assert($expected eq $resp_msgs->[0],
7284        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7285    };
7286
7287    if ($@) {
7288      $ex = $@;
7289    }
7290
7291    $wfh->print("done\n");
7292    $wfh->flush();
7293
7294  } else {
7295    eval { server_wait($config_file, $rfh) };
7296    if ($@) {
7297      warn($@);
7298      exit 1;
7299    }
7300
7301    exit 0;
7302  }
7303
7304  # Stop server
7305  server_stop($pid_file);
7306
7307  $self->assert_child_ok($pid);
7308
7309  if ($ex) {
7310    test_append_logfile($log_file, $ex);
7311    unlink($log_file);
7312
7313    die($ex);
7314  }
7315
7316  unlink($log_file);
7317}
7318
7319sub sql_passwd_scrypt_hex_uc_interactive_cost {
7320  my $self = shift;
7321  my $tmpdir = $self->{tmpdir};
7322
7323  my $config_file = "$tmpdir/sqlpasswd.conf";
7324  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
7325  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
7326
7327  my $log_file = test_get_logfile();
7328
7329  my $user = 'proftpd';
7330  my $group = 'ftpd';
7331
7332  # I had to look at the mod_sql_passwd-generated logs to get this password;
7333  # this means it's a bit incestuous and thus suspect.
7334  my $passwd = uc('648fb4daff9432fb48bc50402d21f206236ef40b7587e1e037ac7323b9e102d3');
7335
7336  my $home_dir = File::Spec->rel2abs($tmpdir);
7337  my $uid = 500;
7338  my $gid = 500;
7339
7340  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
7341
7342  # Build up sqlite3 command to create users, groups tables and populate them
7343  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
7344
7345  if (open(my $fh, "> $db_script")) {
7346    print $fh <<EOS;
7347CREATE TABLE users (
7348  userid TEXT,
7349  passwd TEXT,
7350  uid INTEGER,
7351  gid INTEGER,
7352  homedir TEXT,
7353  shell TEXT
7354);
7355INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7356
7357CREATE TABLE groups (
7358  groupname TEXT,
7359  gid INTEGER,
7360  members TEXT
7361);
7362INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7363EOS
7364
7365    unless (close($fh)) {
7366      die("Can't write $db_script: $!");
7367    }
7368
7369  } else {
7370    die("Can't open $db_script: $!");
7371  }
7372
7373  my $cmd = "sqlite3 $db_file < $db_script";
7374
7375  if ($ENV{TEST_VERBOSE}) {
7376    print STDERR "Executing sqlite3: $cmd\n";
7377  }
7378
7379  my @output = `$cmd`;
7380  if (scalar(@output) &&
7381      $ENV{TEST_VERBOSE}) {
7382    print STDERR "Output: ", join('', @output), "\n";
7383  }
7384
7385  # This hex-encoded salt data has embedded NULs
7386  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7387  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7388  if (open(my $fh, "> $salt_file")) {
7389    binmode($fh);
7390    print $fh $salt;
7391
7392    unless (close($fh)) {
7393      die("Can't write $salt_file: $!");
7394    }
7395
7396  } else {
7397    die("Can't open $salt_file: $!");
7398  }
7399
7400  my $config = {
7401    PidFile => $pid_file,
7402    ScoreboardFile => $scoreboard_file,
7403    SystemLog => $log_file,
7404    TraceLog => $log_file,
7405    Trace => 'sql.passwd:20',
7406
7407    IfModules => {
7408      'mod_delay.c' => {
7409        DelayEngine => 'off',
7410      },
7411
7412      'mod_sql.c' => {
7413        SQLAuthTypes => 'scrypt',
7414        SQLBackend => 'sqlite3',
7415        SQLConnectInfo => $db_file,
7416        SQLLogFile => $log_file,
7417      },
7418
7419      'mod_sql_passwd.c' => {
7420        SQLPasswordEngine => 'on',
7421        SQLPasswordEncoding => 'HEX',
7422        SQLPasswordSaltEncoding => 'hex',
7423        SQLPasswordSaltFile => $salt_file,
7424        SQLPasswordCost => 'Interactive',
7425      },
7426    },
7427  };
7428
7429  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7430
7431  # Open pipes, for use between the parent and child processes.  Specifically,
7432  # the child will indicate when it's done with its test by writing a message
7433  # to the parent.
7434  my ($rfh, $wfh);
7435  unless (pipe($rfh, $wfh)) {
7436    die("Can't open pipe: $!");
7437  }
7438
7439  my $ex;
7440
7441  # Fork child
7442  $self->handle_sigchld();
7443  defined(my $pid = fork()) or die("Can't fork: $!");
7444  if ($pid) {
7445    eval {
7446      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7447      $client->login($user, "test");
7448
7449      my $resp_msgs = $client->response_msgs();
7450      my $nmsgs = scalar(@$resp_msgs);
7451
7452      my $expected;
7453
7454      $expected = 1;
7455      $self->assert($expected == $nmsgs,
7456        test_msg("Expected response message count $expected, got $nmsgs"));
7457
7458      $expected = "User $user logged in";
7459      $self->assert($expected eq $resp_msgs->[0],
7460        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7461    };
7462
7463    if ($@) {
7464      $ex = $@;
7465    }
7466
7467    $wfh->print("done\n");
7468    $wfh->flush();
7469
7470  } else {
7471    eval { server_wait($config_file, $rfh) };
7472    if ($@) {
7473      warn($@);
7474      exit 1;
7475    }
7476
7477    exit 0;
7478  }
7479
7480  # Stop server
7481  server_stop($pid_file);
7482
7483  $self->assert_child_ok($pid);
7484
7485  if ($ex) {
7486    test_append_logfile($log_file, $ex);
7487    unlink($log_file);
7488
7489    die($ex);
7490  }
7491
7492  unlink($log_file);
7493}
7494
7495sub sql_passwd_scrypt_base64_sensitive_cost {
7496  my $self = shift;
7497  my $tmpdir = $self->{tmpdir};
7498
7499  my $config_file = "$tmpdir/sqlpasswd.conf";
7500  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
7501  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
7502
7503  my $log_file = test_get_logfile();
7504
7505  my $user = 'proftpd';
7506  my $group = 'ftpd';
7507
7508  # I had to look at the mod_sql_passwd-generated logs to get this password;
7509  # this means it's a bit incestuous and thus suspect.
7510  my $passwd = 'AxoE22pqKy/Vxa30TMz3D71E0LTQ45abmtVlXOyP2hA=';
7511
7512  my $home_dir = File::Spec->rel2abs($tmpdir);
7513  my $uid = 500;
7514  my $gid = 500;
7515
7516  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
7517
7518  # Build up sqlite3 command to create users, groups tables and populate them
7519  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
7520
7521  if (open(my $fh, "> $db_script")) {
7522    print $fh <<EOS;
7523CREATE TABLE users (
7524  userid TEXT,
7525  passwd TEXT,
7526  uid INTEGER,
7527  gid INTEGER,
7528  homedir TEXT,
7529  shell TEXT
7530);
7531INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7532
7533CREATE TABLE groups (
7534  groupname TEXT,
7535  gid INTEGER,
7536  members TEXT
7537);
7538INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7539EOS
7540
7541    unless (close($fh)) {
7542      die("Can't write $db_script: $!");
7543    }
7544
7545  } else {
7546    die("Can't open $db_script: $!");
7547  }
7548
7549  my $cmd = "sqlite3 $db_file < $db_script";
7550
7551  if ($ENV{TEST_VERBOSE}) {
7552    print STDERR "Executing sqlite3: $cmd\n";
7553  }
7554
7555  my @output = `$cmd`;
7556  if (scalar(@output) &&
7557      $ENV{TEST_VERBOSE}) {
7558    print STDERR "Output: ", join('', @output), "\n";
7559  }
7560
7561  # This hex-encoded salt data has embedded NULs
7562  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7563  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7564  if (open(my $fh, "> $salt_file")) {
7565    binmode($fh);
7566    print $fh $salt;
7567
7568    unless (close($fh)) {
7569      die("Can't write $salt_file: $!");
7570    }
7571
7572  } else {
7573    die("Can't open $salt_file: $!");
7574  }
7575
7576  my $config = {
7577    PidFile => $pid_file,
7578    ScoreboardFile => $scoreboard_file,
7579    SystemLog => $log_file,
7580    TraceLog => $log_file,
7581    Trace => 'sql.passwd:20',
7582
7583    IfModules => {
7584      'mod_delay.c' => {
7585        DelayEngine => 'off',
7586      },
7587
7588      'mod_sql.c' => {
7589        SQLAuthTypes => 'scrypt',
7590        SQLBackend => 'sqlite3',
7591        SQLConnectInfo => $db_file,
7592        SQLLogFile => $log_file,
7593      },
7594
7595      'mod_sql_passwd.c' => {
7596        SQLPasswordEngine => 'on',
7597        SQLPasswordEncoding => 'base64',
7598        SQLPasswordSaltEncoding => 'hex',
7599        SQLPasswordSaltFile => $salt_file,
7600        SQLPasswordCost => 'Sensitive',
7601      },
7602    },
7603  };
7604
7605  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7606
7607  # Open pipes, for use between the parent and child processes.  Specifically,
7608  # the child will indicate when it's done with its test by writing a message
7609  # to the parent.
7610  my ($rfh, $wfh);
7611  unless (pipe($rfh, $wfh)) {
7612    die("Can't open pipe: $!");
7613  }
7614
7615  my $ex;
7616
7617  # Fork child
7618  $self->handle_sigchld();
7619  defined(my $pid = fork()) or die("Can't fork: $!");
7620  if ($pid) {
7621    eval {
7622      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7623      $client->login($user, "test");
7624
7625      my $resp_msgs = $client->response_msgs();
7626      my $nmsgs = scalar(@$resp_msgs);
7627
7628      my $expected;
7629
7630      $expected = 1;
7631      $self->assert($expected == $nmsgs,
7632        test_msg("Expected response message count $expected, got $nmsgs"));
7633
7634      $expected = "User $user logged in";
7635      $self->assert($expected eq $resp_msgs->[0],
7636        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7637    };
7638
7639    if ($@) {
7640      $ex = $@;
7641    }
7642
7643    $wfh->print("done\n");
7644    $wfh->flush();
7645
7646  } else {
7647    eval { server_wait($config_file, $rfh) };
7648    if ($@) {
7649      warn($@);
7650      exit 1;
7651    }
7652
7653    exit 0;
7654  }
7655
7656  # Stop server
7657  server_stop($pid_file);
7658
7659  $self->assert_child_ok($pid);
7660
7661  if ($ex) {
7662    test_append_logfile($log_file, $ex);
7663    unlink($log_file);
7664
7665    die($ex);
7666  }
7667
7668  unlink($log_file);
7669}
7670
7671sub sql_passwd_scrypt_hex_lc_sensitive_cost {
7672  my $self = shift;
7673  my $tmpdir = $self->{tmpdir};
7674
7675  my $config_file = "$tmpdir/sqlpasswd.conf";
7676  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
7677  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
7678
7679  my $log_file = test_get_logfile();
7680
7681  my $user = 'proftpd';
7682  my $group = 'ftpd';
7683
7684  # I had to look at the mod_sql_passwd-generated logs to get this password;
7685  # this means it's a bit incestuous and thus suspect.
7686  my $passwd = '031a04db6a6a2b2fd5c5adf44cccf70fbd44d0b4d0e3969b9ad5655cec8fda10';
7687
7688  my $home_dir = File::Spec->rel2abs($tmpdir);
7689  my $uid = 500;
7690  my $gid = 500;
7691
7692  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
7693
7694  # Build up sqlite3 command to create users, groups tables and populate them
7695  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
7696
7697  if (open(my $fh, "> $db_script")) {
7698    print $fh <<EOS;
7699CREATE TABLE users (
7700  userid TEXT,
7701  passwd TEXT,
7702  uid INTEGER,
7703  gid INTEGER,
7704  homedir TEXT,
7705  shell TEXT
7706);
7707INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7708
7709CREATE TABLE groups (
7710  groupname TEXT,
7711  gid INTEGER,
7712  members TEXT
7713);
7714INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7715EOS
7716
7717    unless (close($fh)) {
7718      die("Can't write $db_script: $!");
7719    }
7720
7721  } else {
7722    die("Can't open $db_script: $!");
7723  }
7724
7725  my $cmd = "sqlite3 $db_file < $db_script";
7726
7727  if ($ENV{TEST_VERBOSE}) {
7728    print STDERR "Executing sqlite3: $cmd\n";
7729  }
7730
7731  my @output = `$cmd`;
7732  if (scalar(@output) &&
7733      $ENV{TEST_VERBOSE}) {
7734    print STDERR "Output: ", join('', @output), "\n";
7735  }
7736
7737  # This hex-encoded salt data has embedded NULs
7738  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7739  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7740  if (open(my $fh, "> $salt_file")) {
7741    binmode($fh);
7742    print $fh $salt;
7743
7744    unless (close($fh)) {
7745      die("Can't write $salt_file: $!");
7746    }
7747
7748  } else {
7749    die("Can't open $salt_file: $!");
7750  }
7751
7752  my $config = {
7753    PidFile => $pid_file,
7754    ScoreboardFile => $scoreboard_file,
7755    SystemLog => $log_file,
7756    TraceLog => $log_file,
7757    Trace => 'sql.passwd:20',
7758
7759    IfModules => {
7760      'mod_delay.c' => {
7761        DelayEngine => 'off',
7762      },
7763
7764      'mod_sql.c' => {
7765        SQLAuthTypes => 'scrypt',
7766        SQLBackend => 'sqlite3',
7767        SQLConnectInfo => $db_file,
7768        SQLLogFile => $log_file,
7769      },
7770
7771      'mod_sql_passwd.c' => {
7772        SQLPasswordEngine => 'on',
7773        SQLPasswordEncoding => 'hex',
7774        SQLPasswordSaltEncoding => 'hex',
7775        SQLPasswordSaltFile => $salt_file,
7776        SQLPasswordCost => 'Sensitive',
7777      },
7778    },
7779  };
7780
7781  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7782
7783  # Open pipes, for use between the parent and child processes.  Specifically,
7784  # the child will indicate when it's done with its test by writing a message
7785  # to the parent.
7786  my ($rfh, $wfh);
7787  unless (pipe($rfh, $wfh)) {
7788    die("Can't open pipe: $!");
7789  }
7790
7791  my $ex;
7792
7793  # Fork child
7794  $self->handle_sigchld();
7795  defined(my $pid = fork()) or die("Can't fork: $!");
7796  if ($pid) {
7797    eval {
7798      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7799      $client->login($user, "test");
7800
7801      my $resp_msgs = $client->response_msgs();
7802      my $nmsgs = scalar(@$resp_msgs);
7803
7804      my $expected;
7805
7806      $expected = 1;
7807      $self->assert($expected == $nmsgs,
7808        test_msg("Expected response message count $expected, got $nmsgs"));
7809
7810      $expected = "User $user logged in";
7811      $self->assert($expected eq $resp_msgs->[0],
7812        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7813    };
7814
7815    if ($@) {
7816      $ex = $@;
7817    }
7818
7819    $wfh->print("done\n");
7820    $wfh->flush();
7821
7822  } else {
7823    eval { server_wait($config_file, $rfh) };
7824    if ($@) {
7825      warn($@);
7826      exit 1;
7827    }
7828
7829    exit 0;
7830  }
7831
7832  # Stop server
7833  server_stop($pid_file);
7834
7835  $self->assert_child_ok($pid);
7836
7837  if ($ex) {
7838    test_append_logfile($log_file, $ex);
7839    unlink($log_file);
7840
7841    die($ex);
7842  }
7843
7844  unlink($log_file);
7845}
7846
7847sub sql_passwd_scrypt_hex_uc_sensitive_cost {
7848  my $self = shift;
7849  my $tmpdir = $self->{tmpdir};
7850
7851  my $config_file = "$tmpdir/sqlpasswd.conf";
7852  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
7853  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
7854
7855  my $log_file = test_get_logfile();
7856
7857  my $user = 'proftpd';
7858  my $group = 'ftpd';
7859
7860  # I had to look at the mod_sql_passwd-generated logs to get this password;
7861  # this means it's a bit incestuous and thus suspect.
7862  my $passwd = uc('031a04db6a6a2b2fd5c5adf44cccf70fbd44d0b4d0e3969b9ad5655cec8fda10');
7863
7864  my $home_dir = File::Spec->rel2abs($tmpdir);
7865  my $uid = 500;
7866  my $gid = 500;
7867
7868  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
7869
7870  # Build up sqlite3 command to create users, groups tables and populate them
7871  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
7872
7873  if (open(my $fh, "> $db_script")) {
7874    print $fh <<EOS;
7875CREATE TABLE users (
7876  userid TEXT,
7877  passwd TEXT,
7878  uid INTEGER,
7879  gid INTEGER,
7880  homedir TEXT,
7881  shell TEXT
7882);
7883INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
7884
7885CREATE TABLE groups (
7886  groupname TEXT,
7887  gid INTEGER,
7888  members TEXT
7889);
7890INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
7891EOS
7892
7893    unless (close($fh)) {
7894      die("Can't write $db_script: $!");
7895    }
7896
7897  } else {
7898    die("Can't open $db_script: $!");
7899  }
7900
7901  my $cmd = "sqlite3 $db_file < $db_script";
7902
7903  if ($ENV{TEST_VERBOSE}) {
7904    print STDERR "Executing sqlite3: $cmd\n";
7905  }
7906
7907  my @output = `$cmd`;
7908  if (scalar(@output) &&
7909      $ENV{TEST_VERBOSE}) {
7910    print STDERR "Output: ", join('', @output), "\n";
7911  }
7912
7913  # This hex-encoded salt data has embedded NULs
7914  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
7915  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
7916  if (open(my $fh, "> $salt_file")) {
7917    binmode($fh);
7918    print $fh $salt;
7919
7920    unless (close($fh)) {
7921      die("Can't write $salt_file: $!");
7922    }
7923
7924  } else {
7925    die("Can't open $salt_file: $!");
7926  }
7927
7928  my $config = {
7929    PidFile => $pid_file,
7930    ScoreboardFile => $scoreboard_file,
7931    SystemLog => $log_file,
7932    TraceLog => $log_file,
7933    Trace => 'sql.passwd:20',
7934
7935    IfModules => {
7936      'mod_delay.c' => {
7937        DelayEngine => 'off',
7938      },
7939
7940      'mod_sql.c' => {
7941        SQLAuthTypes => 'scrypt',
7942        SQLBackend => 'sqlite3',
7943        SQLConnectInfo => $db_file,
7944        SQLLogFile => $log_file,
7945      },
7946
7947      'mod_sql_passwd.c' => {
7948        SQLPasswordEngine => 'on',
7949        SQLPasswordEncoding => 'HEX',
7950        SQLPasswordSaltEncoding => 'hex',
7951        SQLPasswordSaltFile => $salt_file,
7952        SQLPasswordCost => 'Sensitive',
7953      },
7954    },
7955  };
7956
7957  my ($port, $config_user, $config_group) = config_write($config_file, $config);
7958
7959  # Open pipes, for use between the parent and child processes.  Specifically,
7960  # the child will indicate when it's done with its test by writing a message
7961  # to the parent.
7962  my ($rfh, $wfh);
7963  unless (pipe($rfh, $wfh)) {
7964    die("Can't open pipe: $!");
7965  }
7966
7967  my $ex;
7968
7969  # Fork child
7970  $self->handle_sigchld();
7971  defined(my $pid = fork()) or die("Can't fork: $!");
7972  if ($pid) {
7973    eval {
7974      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
7975      $client->login($user, "test");
7976
7977      my $resp_msgs = $client->response_msgs();
7978      my $nmsgs = scalar(@$resp_msgs);
7979
7980      my $expected;
7981
7982      $expected = 1;
7983      $self->assert($expected == $nmsgs,
7984        test_msg("Expected response message count $expected, got $nmsgs"));
7985
7986      $expected = "User $user logged in";
7987      $self->assert($expected eq $resp_msgs->[0],
7988        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
7989    };
7990
7991    if ($@) {
7992      $ex = $@;
7993    }
7994
7995    $wfh->print("done\n");
7996    $wfh->flush();
7997
7998  } else {
7999    eval { server_wait($config_file, $rfh) };
8000    if ($@) {
8001      warn($@);
8002      exit 1;
8003    }
8004
8005    exit 0;
8006  }
8007
8008  # Stop server
8009  server_stop($pid_file);
8010
8011  $self->assert_child_ok($pid);
8012
8013  if ($ex) {
8014    test_append_logfile($log_file, $ex);
8015    unlink($log_file);
8016
8017    die($ex);
8018  }
8019
8020  unlink($log_file);
8021}
8022
8023sub sql_passwd_scrypt_hex_uc_sensitive_cost_len_64 {
8024  my $self = shift;
8025  my $tmpdir = $self->{tmpdir};
8026
8027  my $config_file = "$tmpdir/sqlpasswd.conf";
8028  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8029  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8030
8031  my $log_file = test_get_logfile();
8032
8033  my $user = 'proftpd';
8034  my $group = 'ftpd';
8035
8036  my $scrypt_hash_len = 64;
8037
8038  # I had to look at the mod_sql_passwd-generated logs to get this password;
8039  # this means it's a bit incestuous and thus suspect.
8040  my $passwd = uc('031a04db6a6a2b2fd5c5adf44cccf70fbd44d0b4d0e3969b9ad5655cec8fda10ae28c6b433df6575876c10212a0b9c26cddef82294a223fb72d6a2fc4aa173e0');
8041
8042  my $home_dir = File::Spec->rel2abs($tmpdir);
8043  my $uid = 500;
8044  my $gid = 500;
8045
8046  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8047
8048  # Build up sqlite3 command to create users, groups tables and populate them
8049  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8050
8051  if (open(my $fh, "> $db_script")) {
8052    print $fh <<EOS;
8053CREATE TABLE users (
8054  userid TEXT,
8055  passwd TEXT,
8056  uid INTEGER,
8057  gid INTEGER,
8058  homedir TEXT,
8059  shell TEXT
8060);
8061INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8062
8063CREATE TABLE groups (
8064  groupname TEXT,
8065  gid INTEGER,
8066  members TEXT
8067);
8068INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8069EOS
8070
8071    unless (close($fh)) {
8072      die("Can't write $db_script: $!");
8073    }
8074
8075  } else {
8076    die("Can't open $db_script: $!");
8077  }
8078
8079  my $cmd = "sqlite3 $db_file < $db_script";
8080
8081  if ($ENV{TEST_VERBOSE}) {
8082    print STDERR "Executing sqlite3: $cmd\n";
8083  }
8084
8085  my @output = `$cmd`;
8086  if (scalar(@output) &&
8087      $ENV{TEST_VERBOSE}) {
8088    print STDERR "Output: ", join('', @output), "\n";
8089  }
8090
8091  # This hex-encoded salt data has embedded NULs
8092  my $salt = lc('A7C082B325B2C835FF8A77D805006894AA1B0EC42559C6BD2CB82D7AA0FEAD2A');
8093  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8094  if (open(my $fh, "> $salt_file")) {
8095    binmode($fh);
8096    print $fh $salt;
8097
8098    unless (close($fh)) {
8099      die("Can't write $salt_file: $!");
8100    }
8101
8102  } else {
8103    die("Can't open $salt_file: $!");
8104  }
8105
8106  my $config = {
8107    PidFile => $pid_file,
8108    ScoreboardFile => $scoreboard_file,
8109    SystemLog => $log_file,
8110    TraceLog => $log_file,
8111    Trace => 'sql.passwd:20',
8112
8113    IfModules => {
8114      'mod_delay.c' => {
8115        DelayEngine => 'off',
8116      },
8117
8118      'mod_sql.c' => {
8119        SQLAuthTypes => 'scrypt',
8120        SQLBackend => 'sqlite3',
8121        SQLConnectInfo => $db_file,
8122        SQLLogFile => $log_file,
8123      },
8124
8125      'mod_sql_passwd.c' => {
8126        SQLPasswordEngine => 'on',
8127        SQLPasswordEncoding => 'HEX',
8128        SQLPasswordSaltEncoding => 'hex',
8129        SQLPasswordSaltFile => $salt_file,
8130        SQLPasswordCost => 'Sensitive',
8131        SQLPasswordScrypt => $scrypt_hash_len,
8132      },
8133    },
8134  };
8135
8136  my ($port, $config_user, $config_group) = config_write($config_file, $config);
8137
8138  # Open pipes, for use between the parent and child processes.  Specifically,
8139  # the child will indicate when it's done with its test by writing a message
8140  # to the parent.
8141  my ($rfh, $wfh);
8142  unless (pipe($rfh, $wfh)) {
8143    die("Can't open pipe: $!");
8144  }
8145
8146  my $ex;
8147
8148  # Fork child
8149  $self->handle_sigchld();
8150  defined(my $pid = fork()) or die("Can't fork: $!");
8151  if ($pid) {
8152    eval {
8153      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
8154      $client->login($user, "test");
8155
8156      my $resp_msgs = $client->response_msgs();
8157      my $nmsgs = scalar(@$resp_msgs);
8158
8159      my $expected;
8160
8161      $expected = 1;
8162      $self->assert($expected == $nmsgs,
8163        test_msg("Expected response message count $expected, got $nmsgs"));
8164
8165      $expected = "User $user logged in";
8166      $self->assert($expected eq $resp_msgs->[0],
8167        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
8168    };
8169
8170    if ($@) {
8171      $ex = $@;
8172    }
8173
8174    $wfh->print("done\n");
8175    $wfh->flush();
8176
8177  } else {
8178    eval { server_wait($config_file, $rfh) };
8179    if ($@) {
8180      warn($@);
8181      exit 1;
8182    }
8183
8184    exit 0;
8185  }
8186
8187  # Stop server
8188  server_stop($pid_file);
8189
8190  $self->assert_child_ok($pid);
8191
8192  if ($ex) {
8193    test_append_logfile($log_file, $ex);
8194    unlink($log_file);
8195
8196    die($ex);
8197  }
8198
8199  unlink($log_file);
8200}
8201
8202sub sql_passwd_argon2_base64_interactive_cost {
8203  my $self = shift;
8204  my $tmpdir = $self->{tmpdir};
8205
8206  my $config_file = "$tmpdir/sqlpasswd.conf";
8207  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8208  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8209
8210  my $log_file = test_get_logfile();
8211
8212  my $user = 'proftpd';
8213  my $group = 'ftpd';
8214
8215  # I had to look at the mod_sql_passwd-generated logs to get this password;
8216  # this means it's a bit incestuous and thus suspect.
8217  my $passwd = 'CMEEQfxzMwu2y7YuG3R1r5n/qC0g24DXPRLb3ZpI6ZU=';
8218
8219  my $home_dir = File::Spec->rel2abs($tmpdir);
8220  my $uid = 500;
8221  my $gid = 500;
8222
8223  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8224
8225  # Build up sqlite3 command to create users, groups tables and populate them
8226  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8227
8228  if (open(my $fh, "> $db_script")) {
8229    print $fh <<EOS;
8230CREATE TABLE users (
8231  userid TEXT,
8232  passwd TEXT,
8233  uid INTEGER,
8234  gid INTEGER,
8235  homedir TEXT,
8236  shell TEXT
8237);
8238INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8239
8240CREATE TABLE groups (
8241  groupname TEXT,
8242  gid INTEGER,
8243  members TEXT
8244);
8245INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8246EOS
8247
8248    unless (close($fh)) {
8249      die("Can't write $db_script: $!");
8250    }
8251
8252  } else {
8253    die("Can't open $db_script: $!");
8254  }
8255
8256  my $cmd = "sqlite3 $db_file < $db_script";
8257
8258  if ($ENV{TEST_VERBOSE}) {
8259    print STDERR "Executing sqlite3: $cmd\n";
8260  }
8261
8262  my @output = `$cmd`;
8263  if (scalar(@output) &&
8264      $ENV{TEST_VERBOSE}) {
8265    print STDERR "Output: ", join('', @output), "\n";
8266  }
8267
8268  # This hex-encoded salt data has embedded NULs
8269  my $salt = 'A7C082B325B2C835FF8A77D805006894';
8270  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8271  if (open(my $fh, "> $salt_file")) {
8272    binmode($fh);
8273    print $fh $salt;
8274
8275    unless (close($fh)) {
8276      die("Can't write $salt_file: $!");
8277    }
8278
8279  } else {
8280    die("Can't open $salt_file: $!");
8281  }
8282
8283  my $config = {
8284    PidFile => $pid_file,
8285    ScoreboardFile => $scoreboard_file,
8286    SystemLog => $log_file,
8287    TraceLog => $log_file,
8288    Trace => 'sql.passwd:20',
8289
8290    IfModules => {
8291      'mod_delay.c' => {
8292        DelayEngine => 'off',
8293      },
8294
8295      'mod_sql.c' => {
8296        SQLAuthTypes => 'argon2',
8297        SQLBackend => 'sqlite3',
8298        SQLConnectInfo => $db_file,
8299        SQLLogFile => $log_file,
8300      },
8301
8302      'mod_sql_passwd.c' => {
8303        SQLPasswordEngine => 'on',
8304        SQLPasswordEncoding => 'base64',
8305        SQLPasswordSaltEncoding => 'HEX',
8306        SQLPasswordSaltFile => $salt_file,
8307        SQLPasswordCost => 'Interactive',
8308      },
8309    },
8310  };
8311
8312  my ($port, $config_user, $config_group) = config_write($config_file, $config);
8313
8314  # Open pipes, for use between the parent and child processes.  Specifically,
8315  # the child will indicate when it's done with its test by writing a message
8316  # to the parent.
8317  my ($rfh, $wfh);
8318  unless (pipe($rfh, $wfh)) {
8319    die("Can't open pipe: $!");
8320  }
8321
8322  my $ex;
8323
8324  # Fork child
8325  $self->handle_sigchld();
8326  defined(my $pid = fork()) or die("Can't fork: $!");
8327  if ($pid) {
8328    eval {
8329      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
8330      $client->login($user, "test");
8331
8332      my $resp_msgs = $client->response_msgs();
8333      my $nmsgs = scalar(@$resp_msgs);
8334
8335      my $expected;
8336
8337      $expected = 1;
8338      $self->assert($expected == $nmsgs,
8339        test_msg("Expected response message count $expected, got $nmsgs"));
8340
8341      $expected = "User $user logged in";
8342      $self->assert($expected eq $resp_msgs->[0],
8343        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
8344    };
8345
8346    if ($@) {
8347      $ex = $@;
8348    }
8349
8350    $wfh->print("done\n");
8351    $wfh->flush();
8352
8353  } else {
8354    eval { server_wait($config_file, $rfh) };
8355    if ($@) {
8356      warn($@);
8357      exit 1;
8358    }
8359
8360    exit 0;
8361  }
8362
8363  # Stop server
8364  server_stop($pid_file);
8365
8366  $self->assert_child_ok($pid);
8367
8368  if ($ex) {
8369    test_append_logfile($log_file, $ex);
8370    unlink($log_file);
8371
8372    die($ex);
8373  }
8374
8375  unlink($log_file);
8376}
8377
8378sub sql_passwd_argon2_hex_lc_interactive_cost {
8379  my $self = shift;
8380  my $tmpdir = $self->{tmpdir};
8381
8382  my $config_file = "$tmpdir/sqlpasswd.conf";
8383  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8384  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8385
8386  my $log_file = test_get_logfile();
8387
8388  my $user = 'proftpd';
8389  my $group = 'ftpd';
8390
8391  # I had to look at the mod_sql_passwd-generated logs to get this password;
8392  # this means it's a bit incestuous and thus suspect.
8393  my $passwd = '08c10441fc73330bb6cbb62e1b7475af99ffa82d20db80d73d12dbdd9a48e995';
8394
8395  my $home_dir = File::Spec->rel2abs($tmpdir);
8396  my $uid = 500;
8397  my $gid = 500;
8398
8399  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8400
8401  # Build up sqlite3 command to create users, groups tables and populate them
8402  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8403
8404  if (open(my $fh, "> $db_script")) {
8405    print $fh <<EOS;
8406CREATE TABLE users (
8407  userid TEXT,
8408  passwd TEXT,
8409  uid INTEGER,
8410  gid INTEGER,
8411  homedir TEXT,
8412  shell TEXT
8413);
8414INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8415
8416CREATE TABLE groups (
8417  groupname TEXT,
8418  gid INTEGER,
8419  members TEXT
8420);
8421INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8422EOS
8423
8424    unless (close($fh)) {
8425      die("Can't write $db_script: $!");
8426    }
8427
8428  } else {
8429    die("Can't open $db_script: $!");
8430  }
8431
8432  my $cmd = "sqlite3 $db_file < $db_script";
8433
8434  if ($ENV{TEST_VERBOSE}) {
8435    print STDERR "Executing sqlite3: $cmd\n";
8436  }
8437
8438  my @output = `$cmd`;
8439  if (scalar(@output) &&
8440      $ENV{TEST_VERBOSE}) {
8441    print STDERR "Output: ", join('', @output), "\n";
8442  }
8443
8444  # This hex-encoded salt data has embedded NULs
8445  my $salt = lc('A7C082B325B2C835FF8A77D805006894');
8446  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8447  if (open(my $fh, "> $salt_file")) {
8448    binmode($fh);
8449    print $fh $salt;
8450
8451    unless (close($fh)) {
8452      die("Can't write $salt_file: $!");
8453    }
8454
8455  } else {
8456    die("Can't open $salt_file: $!");
8457  }
8458
8459  my $config = {
8460    PidFile => $pid_file,
8461    ScoreboardFile => $scoreboard_file,
8462    SystemLog => $log_file,
8463    TraceLog => $log_file,
8464    Trace => 'sql.passwd:20',
8465
8466    IfModules => {
8467      'mod_delay.c' => {
8468        DelayEngine => 'off',
8469      },
8470
8471      'mod_sql.c' => {
8472        SQLAuthTypes => 'argon2',
8473        SQLBackend => 'sqlite3',
8474        SQLConnectInfo => $db_file,
8475        SQLLogFile => $log_file,
8476      },
8477
8478      'mod_sql_passwd.c' => {
8479        SQLPasswordEngine => 'on',
8480        SQLPasswordEncoding => 'hex',
8481        SQLPasswordSaltEncoding => 'hex',
8482        SQLPasswordSaltFile => $salt_file,
8483        SQLPasswordCost => 'Interactive',
8484      },
8485    },
8486  };
8487
8488  my ($port, $config_user, $config_group) = config_write($config_file, $config);
8489
8490  # Open pipes, for use between the parent and child processes.  Specifically,
8491  # the child will indicate when it's done with its test by writing a message
8492  # to the parent.
8493  my ($rfh, $wfh);
8494  unless (pipe($rfh, $wfh)) {
8495    die("Can't open pipe: $!");
8496  }
8497
8498  my $ex;
8499
8500  # Fork child
8501  $self->handle_sigchld();
8502  defined(my $pid = fork()) or die("Can't fork: $!");
8503  if ($pid) {
8504    eval {
8505      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
8506      $client->login($user, "test");
8507
8508      my $resp_msgs = $client->response_msgs();
8509      my $nmsgs = scalar(@$resp_msgs);
8510
8511      my $expected;
8512
8513      $expected = 1;
8514      $self->assert($expected == $nmsgs,
8515        test_msg("Expected response message count $expected, got $nmsgs"));
8516
8517      $expected = "User $user logged in";
8518      $self->assert($expected eq $resp_msgs->[0],
8519        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
8520    };
8521
8522    if ($@) {
8523      $ex = $@;
8524    }
8525
8526    $wfh->print("done\n");
8527    $wfh->flush();
8528
8529  } else {
8530    eval { server_wait($config_file, $rfh) };
8531    if ($@) {
8532      warn($@);
8533      exit 1;
8534    }
8535
8536    exit 0;
8537  }
8538
8539  # Stop server
8540  server_stop($pid_file);
8541
8542  $self->assert_child_ok($pid);
8543
8544  if ($ex) {
8545    test_append_logfile($log_file, $ex);
8546    unlink($log_file);
8547
8548    die($ex);
8549  }
8550
8551  unlink($log_file);
8552}
8553
8554sub sql_passwd_argon2_hex_uc_interactive_cost {
8555  my $self = shift;
8556  my $tmpdir = $self->{tmpdir};
8557
8558  my $config_file = "$tmpdir/sqlpasswd.conf";
8559  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8560  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8561
8562  my $log_file = test_get_logfile();
8563
8564  my $user = 'proftpd';
8565  my $group = 'ftpd';
8566
8567  # I had to look at the mod_sql_passwd-generated logs to get this password;
8568  # this means it's a bit incestuous and thus suspect.
8569  my $passwd = uc('08c10441fc73330bb6cbb62e1b7475af99ffa82d20db80d73d12dbdd9a48e995');
8570
8571  my $home_dir = File::Spec->rel2abs($tmpdir);
8572  my $uid = 500;
8573  my $gid = 500;
8574
8575  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8576
8577  # Build up sqlite3 command to create users, groups tables and populate them
8578  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8579
8580  if (open(my $fh, "> $db_script")) {
8581    print $fh <<EOS;
8582CREATE TABLE users (
8583  userid TEXT,
8584  passwd TEXT,
8585  uid INTEGER,
8586  gid INTEGER,
8587  homedir TEXT,
8588  shell TEXT
8589);
8590INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8591
8592CREATE TABLE groups (
8593  groupname TEXT,
8594  gid INTEGER,
8595  members TEXT
8596);
8597INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8598EOS
8599
8600    unless (close($fh)) {
8601      die("Can't write $db_script: $!");
8602    }
8603
8604  } else {
8605    die("Can't open $db_script: $!");
8606  }
8607
8608  my $cmd = "sqlite3 $db_file < $db_script";
8609
8610  if ($ENV{TEST_VERBOSE}) {
8611    print STDERR "Executing sqlite3: $cmd\n";
8612  }
8613
8614  my @output = `$cmd`;
8615  if (scalar(@output) &&
8616      $ENV{TEST_VERBOSE}) {
8617    print STDERR "Output: ", join('', @output), "\n";
8618  }
8619
8620  # This hex-encoded salt data has embedded NULs
8621  my $salt = lc('A7C082B325B2C835FF8A77D805006894');
8622  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8623  if (open(my $fh, "> $salt_file")) {
8624    binmode($fh);
8625    print $fh $salt;
8626
8627    unless (close($fh)) {
8628      die("Can't write $salt_file: $!");
8629    }
8630
8631  } else {
8632    die("Can't open $salt_file: $!");
8633  }
8634
8635  my $config = {
8636    PidFile => $pid_file,
8637    ScoreboardFile => $scoreboard_file,
8638    SystemLog => $log_file,
8639    TraceLog => $log_file,
8640    Trace => 'sql.passwd:20',
8641
8642    IfModules => {
8643      'mod_delay.c' => {
8644        DelayEngine => 'off',
8645      },
8646
8647      'mod_sql.c' => {
8648        SQLAuthTypes => 'argon2',
8649        SQLBackend => 'sqlite3',
8650        SQLConnectInfo => $db_file,
8651        SQLLogFile => $log_file,
8652      },
8653
8654      'mod_sql_passwd.c' => {
8655        SQLPasswordEngine => 'on',
8656        SQLPasswordEncoding => 'HEX',
8657        SQLPasswordSaltEncoding => 'hex',
8658        SQLPasswordSaltFile => $salt_file,
8659        SQLPasswordCost => 'Interactive',
8660      },
8661    },
8662  };
8663
8664  my ($port, $config_user, $config_group) = config_write($config_file, $config);
8665
8666  # Open pipes, for use between the parent and child processes.  Specifically,
8667  # the child will indicate when it's done with its test by writing a message
8668  # to the parent.
8669  my ($rfh, $wfh);
8670  unless (pipe($rfh, $wfh)) {
8671    die("Can't open pipe: $!");
8672  }
8673
8674  my $ex;
8675
8676  # Fork child
8677  $self->handle_sigchld();
8678  defined(my $pid = fork()) or die("Can't fork: $!");
8679  if ($pid) {
8680    eval {
8681      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
8682      $client->login($user, "test");
8683
8684      my $resp_msgs = $client->response_msgs();
8685      my $nmsgs = scalar(@$resp_msgs);
8686
8687      my $expected;
8688
8689      $expected = 1;
8690      $self->assert($expected == $nmsgs,
8691        test_msg("Expected response message count $expected, got $nmsgs"));
8692
8693      $expected = "User $user logged in";
8694      $self->assert($expected eq $resp_msgs->[0],
8695        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
8696    };
8697
8698    if ($@) {
8699      $ex = $@;
8700    }
8701
8702    $wfh->print("done\n");
8703    $wfh->flush();
8704
8705  } else {
8706    eval { server_wait($config_file, $rfh) };
8707    if ($@) {
8708      warn($@);
8709      exit 1;
8710    }
8711
8712    exit 0;
8713  }
8714
8715  # Stop server
8716  server_stop($pid_file);
8717
8718  $self->assert_child_ok($pid);
8719
8720  if ($ex) {
8721    test_append_logfile($log_file, $ex);
8722    unlink($log_file);
8723
8724    die($ex);
8725  }
8726
8727  unlink($log_file);
8728}
8729
8730sub sql_passwd_argon2_base64_sensitive_cost {
8731  my $self = shift;
8732  my $tmpdir = $self->{tmpdir};
8733
8734  my $config_file = "$tmpdir/sqlpasswd.conf";
8735  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8736  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8737
8738  my $log_file = test_get_logfile();
8739
8740  my $user = 'proftpd';
8741  my $group = 'ftpd';
8742
8743  # I had to look at the mod_sql_passwd-generated logs to get this password;
8744  # this means it's a bit incestuous and thus suspect.
8745  my $passwd = '6dZPscXkBbvrcU7FcLTuhKMqpjLlHItzRkLuic95h0k=';
8746
8747  my $home_dir = File::Spec->rel2abs($tmpdir);
8748  my $uid = 500;
8749  my $gid = 500;
8750
8751  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8752
8753  # Build up sqlite3 command to create users, groups tables and populate them
8754  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8755
8756  if (open(my $fh, "> $db_script")) {
8757    print $fh <<EOS;
8758CREATE TABLE users (
8759  userid TEXT,
8760  passwd TEXT,
8761  uid INTEGER,
8762  gid INTEGER,
8763  homedir TEXT,
8764  shell TEXT
8765);
8766INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8767
8768CREATE TABLE groups (
8769  groupname TEXT,
8770  gid INTEGER,
8771  members TEXT
8772);
8773INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8774EOS
8775
8776    unless (close($fh)) {
8777      die("Can't write $db_script: $!");
8778    }
8779
8780  } else {
8781    die("Can't open $db_script: $!");
8782  }
8783
8784  my $cmd = "sqlite3 $db_file < $db_script";
8785
8786  if ($ENV{TEST_VERBOSE}) {
8787    print STDERR "Executing sqlite3: $cmd\n";
8788  }
8789
8790  my @output = `$cmd`;
8791  if (scalar(@output) &&
8792      $ENV{TEST_VERBOSE}) {
8793    print STDERR "Output: ", join('', @output), "\n";
8794  }
8795
8796  # This hex-encoded salt data has embedded NULs
8797  my $salt = 'A7C082B325B2C835FF8A77D805006894';
8798  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8799  if (open(my $fh, "> $salt_file")) {
8800    binmode($fh);
8801    print $fh $salt;
8802
8803    unless (close($fh)) {
8804      die("Can't write $salt_file: $!");
8805    }
8806
8807  } else {
8808    die("Can't open $salt_file: $!");
8809  }
8810
8811  my $config = {
8812    PidFile => $pid_file,
8813    ScoreboardFile => $scoreboard_file,
8814    SystemLog => $log_file,
8815    TraceLog => $log_file,
8816    Trace => 'sql.passwd:20',
8817
8818    IfModules => {
8819      'mod_delay.c' => {
8820        DelayEngine => 'off',
8821      },
8822
8823      'mod_sql.c' => {
8824        SQLAuthTypes => 'argon2',
8825        SQLBackend => 'sqlite3',
8826        SQLConnectInfo => $db_file,
8827        SQLLogFile => $log_file,
8828      },
8829
8830      'mod_sql_passwd.c' => {
8831        SQLPasswordEngine => 'on',
8832        SQLPasswordEncoding => 'base64',
8833        SQLPasswordSaltEncoding => 'HEX',
8834        SQLPasswordSaltFile => $salt_file,
8835        SQLPasswordCost => 'Sensitive',
8836      },
8837    },
8838  };
8839
8840  my ($port, $config_user, $config_group) = config_write($config_file, $config);
8841
8842  # Open pipes, for use between the parent and child processes.  Specifically,
8843  # the child will indicate when it's done with its test by writing a message
8844  # to the parent.
8845  my ($rfh, $wfh);
8846  unless (pipe($rfh, $wfh)) {
8847    die("Can't open pipe: $!");
8848  }
8849
8850  my $ex;
8851
8852  # Fork child
8853  $self->handle_sigchld();
8854  defined(my $pid = fork()) or die("Can't fork: $!");
8855  if ($pid) {
8856    eval {
8857      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
8858      $client->login($user, "test");
8859
8860      my $resp_msgs = $client->response_msgs();
8861      my $nmsgs = scalar(@$resp_msgs);
8862
8863      my $expected;
8864
8865      $expected = 1;
8866      $self->assert($expected == $nmsgs,
8867        test_msg("Expected response message count $expected, got $nmsgs"));
8868
8869      $expected = "User $user logged in";
8870      $self->assert($expected eq $resp_msgs->[0],
8871        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
8872    };
8873
8874    if ($@) {
8875      $ex = $@;
8876    }
8877
8878    $wfh->print("done\n");
8879    $wfh->flush();
8880
8881  } else {
8882    eval { server_wait($config_file, $rfh) };
8883    if ($@) {
8884      warn($@);
8885      exit 1;
8886    }
8887
8888    exit 0;
8889  }
8890
8891  # Stop server
8892  server_stop($pid_file);
8893
8894  $self->assert_child_ok($pid);
8895
8896  if ($ex) {
8897    test_append_logfile($log_file, $ex);
8898    unlink($log_file);
8899
8900    die($ex);
8901  }
8902
8903  unlink($log_file);
8904}
8905
8906sub sql_passwd_argon2_hex_lc_sensitive_cost {
8907  my $self = shift;
8908  my $tmpdir = $self->{tmpdir};
8909
8910  my $config_file = "$tmpdir/sqlpasswd.conf";
8911  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
8912  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
8913
8914  my $log_file = test_get_logfile();
8915
8916  my $user = 'proftpd';
8917  my $group = 'ftpd';
8918
8919  # I had to look at the mod_sql_passwd-generated logs to get this password;
8920  # this means it's a bit incestuous and thus suspect.
8921  my $passwd = 'e9d64fb1c5e405bbeb714ec570b4ee84a32aa632e51c8b734642ee89cf798749';
8922
8923  my $home_dir = File::Spec->rel2abs($tmpdir);
8924  my $uid = 500;
8925  my $gid = 500;
8926
8927  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
8928
8929  # Build up sqlite3 command to create users, groups tables and populate them
8930  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
8931
8932  if (open(my $fh, "> $db_script")) {
8933    print $fh <<EOS;
8934CREATE TABLE users (
8935  userid TEXT,
8936  passwd TEXT,
8937  uid INTEGER,
8938  gid INTEGER,
8939  homedir TEXT,
8940  shell TEXT
8941);
8942INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
8943
8944CREATE TABLE groups (
8945  groupname TEXT,
8946  gid INTEGER,
8947  members TEXT
8948);
8949INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
8950EOS
8951
8952    unless (close($fh)) {
8953      die("Can't write $db_script: $!");
8954    }
8955
8956  } else {
8957    die("Can't open $db_script: $!");
8958  }
8959
8960  my $cmd = "sqlite3 $db_file < $db_script";
8961
8962  if ($ENV{TEST_VERBOSE}) {
8963    print STDERR "Executing sqlite3: $cmd\n";
8964  }
8965
8966  my @output = `$cmd`;
8967  if (scalar(@output) &&
8968      $ENV{TEST_VERBOSE}) {
8969    print STDERR "Output: ", join('', @output), "\n";
8970  }
8971
8972  # This hex-encoded salt data has embedded NULs
8973  my $salt = lc('A7C082B325B2C835FF8A77D805006894');
8974  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
8975  if (open(my $fh, "> $salt_file")) {
8976    binmode($fh);
8977    print $fh $salt;
8978
8979    unless (close($fh)) {
8980      die("Can't write $salt_file: $!");
8981    }
8982
8983  } else {
8984    die("Can't open $salt_file: $!");
8985  }
8986
8987  my $config = {
8988    PidFile => $pid_file,
8989    ScoreboardFile => $scoreboard_file,
8990    SystemLog => $log_file,
8991    TraceLog => $log_file,
8992    Trace => 'sql.passwd:20',
8993
8994    IfModules => {
8995      'mod_delay.c' => {
8996        DelayEngine => 'off',
8997      },
8998
8999      'mod_sql.c' => {
9000        SQLAuthTypes => 'argon2',
9001        SQLBackend => 'sqlite3',
9002        SQLConnectInfo => $db_file,
9003        SQLLogFile => $log_file,
9004      },
9005
9006      'mod_sql_passwd.c' => {
9007        SQLPasswordEngine => 'on',
9008        SQLPasswordEncoding => 'hex',
9009        SQLPasswordSaltEncoding => 'hex',
9010        SQLPasswordSaltFile => $salt_file,
9011        SQLPasswordCost => 'Sensitive',
9012      },
9013    },
9014  };
9015
9016  my ($port, $config_user, $config_group) = config_write($config_file, $config);
9017
9018  # Open pipes, for use between the parent and child processes.  Specifically,
9019  # the child will indicate when it's done with its test by writing a message
9020  # to the parent.
9021  my ($rfh, $wfh);
9022  unless (pipe($rfh, $wfh)) {
9023    die("Can't open pipe: $!");
9024  }
9025
9026  my $ex;
9027
9028  # Fork child
9029  $self->handle_sigchld();
9030  defined(my $pid = fork()) or die("Can't fork: $!");
9031  if ($pid) {
9032    eval {
9033      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
9034      $client->login($user, "test");
9035
9036      my $resp_msgs = $client->response_msgs();
9037      my $nmsgs = scalar(@$resp_msgs);
9038
9039      my $expected;
9040
9041      $expected = 1;
9042      $self->assert($expected == $nmsgs,
9043        test_msg("Expected response message count $expected, got $nmsgs"));
9044
9045      $expected = "User $user logged in";
9046      $self->assert($expected eq $resp_msgs->[0],
9047        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
9048    };
9049
9050    if ($@) {
9051      $ex = $@;
9052    }
9053
9054    $wfh->print("done\n");
9055    $wfh->flush();
9056
9057  } else {
9058    eval { server_wait($config_file, $rfh) };
9059    if ($@) {
9060      warn($@);
9061      exit 1;
9062    }
9063
9064    exit 0;
9065  }
9066
9067  # Stop server
9068  server_stop($pid_file);
9069
9070  $self->assert_child_ok($pid);
9071
9072  if ($ex) {
9073    test_append_logfile($log_file, $ex);
9074    unlink($log_file);
9075
9076    die($ex);
9077  }
9078
9079  unlink($log_file);
9080}
9081
9082sub sql_passwd_argon2_hex_uc_sensitive_cost {
9083  my $self = shift;
9084  my $tmpdir = $self->{tmpdir};
9085
9086  my $config_file = "$tmpdir/sqlpasswd.conf";
9087  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
9088  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
9089
9090  my $log_file = test_get_logfile();
9091
9092  my $user = 'proftpd';
9093  my $group = 'ftpd';
9094
9095  # I had to look at the mod_sql_passwd-generated logs to get this password;
9096  # this means it's a bit incestuous and thus suspect.
9097  my $passwd = uc('e9d64fb1c5e405bbeb714ec570b4ee84a32aa632e51c8b734642ee89cf798749');
9098
9099  my $home_dir = File::Spec->rel2abs($tmpdir);
9100  my $uid = 500;
9101  my $gid = 500;
9102
9103  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
9104
9105  # Build up sqlite3 command to create users, groups tables and populate them
9106  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
9107
9108  if (open(my $fh, "> $db_script")) {
9109    print $fh <<EOS;
9110CREATE TABLE users (
9111  userid TEXT,
9112  passwd TEXT,
9113  uid INTEGER,
9114  gid INTEGER,
9115  homedir TEXT,
9116  shell TEXT
9117);
9118INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
9119
9120CREATE TABLE groups (
9121  groupname TEXT,
9122  gid INTEGER,
9123  members TEXT
9124);
9125INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
9126EOS
9127
9128    unless (close($fh)) {
9129      die("Can't write $db_script: $!");
9130    }
9131
9132  } else {
9133    die("Can't open $db_script: $!");
9134  }
9135
9136  my $cmd = "sqlite3 $db_file < $db_script";
9137
9138  if ($ENV{TEST_VERBOSE}) {
9139    print STDERR "Executing sqlite3: $cmd\n";
9140  }
9141
9142  my @output = `$cmd`;
9143  if (scalar(@output) &&
9144      $ENV{TEST_VERBOSE}) {
9145    print STDERR "Output: ", join('', @output), "\n";
9146  }
9147
9148  # This hex-encoded salt data has embedded NULs
9149  my $salt = 'A7C082B325B2C835FF8A77D805006894';
9150  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
9151  if (open(my $fh, "> $salt_file")) {
9152    binmode($fh);
9153    print $fh $salt;
9154
9155    unless (close($fh)) {
9156      die("Can't write $salt_file: $!");
9157    }
9158
9159  } else {
9160    die("Can't open $salt_file: $!");
9161  }
9162
9163  my $config = {
9164    PidFile => $pid_file,
9165    ScoreboardFile => $scoreboard_file,
9166    SystemLog => $log_file,
9167    TraceLog => $log_file,
9168    Trace => 'sql.passwd:20',
9169
9170    IfModules => {
9171      'mod_delay.c' => {
9172        DelayEngine => 'off',
9173      },
9174
9175      'mod_sql.c' => {
9176        SQLAuthTypes => 'argon2',
9177        SQLBackend => 'sqlite3',
9178        SQLConnectInfo => $db_file,
9179        SQLLogFile => $log_file,
9180      },
9181
9182      'mod_sql_passwd.c' => {
9183        SQLPasswordEngine => 'on',
9184        SQLPasswordEncoding => 'HEX',
9185        SQLPasswordSaltEncoding => 'HEX',
9186        SQLPasswordSaltFile => $salt_file,
9187        SQLPasswordCost => 'Sensitive',
9188      },
9189    },
9190  };
9191
9192  my ($port, $config_user, $config_group) = config_write($config_file, $config);
9193
9194  # Open pipes, for use between the parent and child processes.  Specifically,
9195  # the child will indicate when it's done with its test by writing a message
9196  # to the parent.
9197  my ($rfh, $wfh);
9198  unless (pipe($rfh, $wfh)) {
9199    die("Can't open pipe: $!");
9200  }
9201
9202  my $ex;
9203
9204  # Fork child
9205  $self->handle_sigchld();
9206  defined(my $pid = fork()) or die("Can't fork: $!");
9207  if ($pid) {
9208    eval {
9209      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
9210      $client->login($user, "test");
9211
9212      my $resp_msgs = $client->response_msgs();
9213      my $nmsgs = scalar(@$resp_msgs);
9214
9215      my $expected;
9216
9217      $expected = 1;
9218      $self->assert($expected == $nmsgs,
9219        test_msg("Expected response message count $expected, got $nmsgs"));
9220
9221      $expected = "User $user logged in";
9222      $self->assert($expected eq $resp_msgs->[0],
9223        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
9224    };
9225
9226    if ($@) {
9227      $ex = $@;
9228    }
9229
9230    $wfh->print("done\n");
9231    $wfh->flush();
9232
9233  } else {
9234    eval { server_wait($config_file, $rfh) };
9235    if ($@) {
9236      warn($@);
9237      exit 1;
9238    }
9239
9240    exit 0;
9241  }
9242
9243  # Stop server
9244  server_stop($pid_file);
9245
9246  $self->assert_child_ok($pid);
9247
9248  if ($ex) {
9249    test_append_logfile($log_file, $ex);
9250    unlink($log_file);
9251
9252    die($ex);
9253  }
9254
9255  unlink($log_file);
9256}
9257
9258sub sql_passwd_argon2_hex_uc_sensitive_cost_len_64 {
9259  my $self = shift;
9260  my $tmpdir = $self->{tmpdir};
9261
9262  my $config_file = "$tmpdir/sqlpasswd.conf";
9263  my $pid_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.pid");
9264  my $scoreboard_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.scoreboard");
9265
9266  my $log_file = test_get_logfile();
9267
9268  my $user = 'proftpd';
9269  my $group = 'ftpd';
9270
9271  my $argon2_hash_len = 64;
9272
9273  # I had to look at the mod_sql_passwd-generated logs to get this password;
9274  # this means it's a bit incestuous and thus suspect.
9275  my $passwd = uc('c7d9ca94cc8631322c30b8c39d8b9ba3350873a81acea38771e5ede737625a99bc904b33c8fd53cb314147cba409fe21fccf9f6bbb04f775f01076d2188a7dae');
9276
9277  my $home_dir = File::Spec->rel2abs($tmpdir);
9278  my $uid = 500;
9279  my $gid = 500;
9280
9281  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
9282
9283  # Build up sqlite3 command to create users, groups tables and populate them
9284  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
9285
9286  if (open(my $fh, "> $db_script")) {
9287    print $fh <<EOS;
9288CREATE TABLE users (
9289  userid TEXT,
9290  passwd TEXT,
9291  uid INTEGER,
9292  gid INTEGER,
9293  homedir TEXT,
9294  shell TEXT
9295);
9296INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$user', '$passwd', $uid, $gid, '$home_dir', '/bin/bash');
9297
9298CREATE TABLE groups (
9299  groupname TEXT,
9300  gid INTEGER,
9301  members TEXT
9302);
9303INSERT INTO groups (groupname, gid, members) VALUES ('$group', $gid, '$user');
9304EOS
9305
9306    unless (close($fh)) {
9307      die("Can't write $db_script: $!");
9308    }
9309
9310  } else {
9311    die("Can't open $db_script: $!");
9312  }
9313
9314  my $cmd = "sqlite3 $db_file < $db_script";
9315
9316  if ($ENV{TEST_VERBOSE}) {
9317    print STDERR "Executing sqlite3: $cmd\n";
9318  }
9319
9320  my @output = `$cmd`;
9321  if (scalar(@output) &&
9322      $ENV{TEST_VERBOSE}) {
9323    print STDERR "Output: ", join('', @output), "\n";
9324  }
9325
9326  # This hex-encoded salt data has embedded NULs
9327  my $salt = 'A7C082B325B2C835FF8A77D805006894';
9328  my $salt_file = File::Spec->rel2abs("$tmpdir/sqlpasswd.salt");
9329  if (open(my $fh, "> $salt_file")) {
9330    binmode($fh);
9331    print $fh $salt;
9332
9333    unless (close($fh)) {
9334      die("Can't write $salt_file: $!");
9335    }
9336
9337  } else {
9338    die("Can't open $salt_file: $!");
9339  }
9340
9341  my $config = {
9342    PidFile => $pid_file,
9343    ScoreboardFile => $scoreboard_file,
9344    SystemLog => $log_file,
9345    TraceLog => $log_file,
9346    Trace => 'sql.passwd:20',
9347
9348    IfModules => {
9349      'mod_delay.c' => {
9350        DelayEngine => 'off',
9351      },
9352
9353      'mod_sql.c' => {
9354        SQLAuthTypes => 'argon2',
9355        SQLBackend => 'sqlite3',
9356        SQLConnectInfo => $db_file,
9357        SQLLogFile => $log_file,
9358      },
9359
9360      'mod_sql_passwd.c' => {
9361        SQLPasswordEngine => 'on',
9362        SQLPasswordEncoding => 'HEX',
9363        SQLPasswordSaltEncoding => 'HEX',
9364        SQLPasswordSaltFile => $salt_file,
9365        SQLPasswordCost => 'Sensitive',
9366        SQLPasswordArgon2 => $argon2_hash_len,
9367      },
9368    },
9369  };
9370
9371  my ($port, $config_user, $config_group) = config_write($config_file, $config);
9372
9373  # Open pipes, for use between the parent and child processes.  Specifically,
9374  # the child will indicate when it's done with its test by writing a message
9375  # to the parent.
9376  my ($rfh, $wfh);
9377  unless (pipe($rfh, $wfh)) {
9378    die("Can't open pipe: $!");
9379  }
9380
9381  my $ex;
9382
9383  # Fork child
9384  $self->handle_sigchld();
9385  defined(my $pid = fork()) or die("Can't fork: $!");
9386  if ($pid) {
9387    eval {
9388      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
9389      $client->login($user, "test");
9390
9391      my $resp_msgs = $client->response_msgs();
9392      my $nmsgs = scalar(@$resp_msgs);
9393
9394      my $expected;
9395
9396      $expected = 1;
9397      $self->assert($expected == $nmsgs,
9398        test_msg("Expected response message count $expected, got $nmsgs"));
9399
9400      $expected = "User $user logged in";
9401      $self->assert($expected eq $resp_msgs->[0],
9402        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
9403    };
9404
9405    if ($@) {
9406      $ex = $@;
9407    }
9408
9409    $wfh->print("done\n");
9410    $wfh->flush();
9411
9412  } else {
9413    eval { server_wait($config_file, $rfh) };
9414    if ($@) {
9415      warn($@);
9416      exit 1;
9417    }
9418
9419    exit 0;
9420  }
9421
9422  # Stop server
9423  server_stop($pid_file);
9424
9425  $self->assert_child_ok($pid);
9426
9427  if ($ex) {
9428    test_append_logfile($log_file, $ex);
9429    unlink($log_file);
9430
9431    die($ex);
9432  }
9433
9434  unlink($log_file);
9435}
9436
9437sub sql_passwd_bcrypt {
9438  my $self = shift;
9439  my $tmpdir = $self->{tmpdir};
9440
9441  # I used the bcrypt Python module to generate this password:
9442  #
9443  # $ cat passwd.py
9444  # import bcrypt
9445  # password = b"password"
9446  # hashed = bcrypt.hashpw(password, bcrypt.gensalt())
9447  # print("hash for password '" + str(password) + ": " + str(hashed))
9448  my $passwd = '$2b$12$IoFxXvbRQUKssPqFacJFFuZl1KXl5ULppqf0aLFjwCFnLRh3NbYSG';
9449
9450  my $setup = test_setup($tmpdir, 'sql_passwd', undef, $passwd);
9451
9452  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
9453
9454  # Build up sqlite3 command to create users, groups tables and populate them
9455  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
9456
9457  if (open(my $fh, "> $db_script")) {
9458    print $fh <<EOS;
9459CREATE TABLE users (
9460  userid TEXT,
9461  passwd TEXT,
9462  uid INTEGER,
9463  gid INTEGER,
9464  homedir TEXT,
9465  shell TEXT
9466);
9467INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$setup->{user}', '$passwd', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
9468
9469CREATE TABLE groups (
9470  groupname TEXT,
9471  gid INTEGER,
9472  members TEXT
9473);
9474INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
9475EOS
9476
9477    unless (close($fh)) {
9478      die("Can't write $db_script: $!");
9479    }
9480
9481  } else {
9482    die("Can't open $db_script: $!");
9483  }
9484
9485  my $cmd = "sqlite3 $db_file < $db_script";
9486
9487  if ($ENV{TEST_VERBOSE}) {
9488    print STDERR "Executing sqlite3: $cmd\n";
9489  }
9490
9491  my @output = `$cmd`;
9492  if (scalar(@output) &&
9493      $ENV{TEST_VERBOSE}) {
9494    print STDERR "Output: ", join('', @output), "\n";
9495  }
9496
9497  my $config = {
9498    PidFile => $setup->{pid_file},
9499    ScoreboardFile => $setup->{scoreboard_file},
9500    SystemLog => $setup->{log_file},
9501
9502    IfModules => {
9503      'mod_delay.c' => {
9504        DelayEngine => 'off',
9505      },
9506
9507      'mod_sql.c' => {
9508        SQLAuthTypes => 'bcrypt',
9509        SQLBackend => 'sqlite3',
9510        SQLConnectInfo => $db_file,
9511        SQLLogFile => $setup->{log_file},
9512      },
9513
9514      'mod_sql_passwd.c' => {
9515        SQLPasswordEngine => 'on',
9516        SQLPasswordEncoding => 'base64',
9517      },
9518    },
9519  };
9520
9521  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
9522    $config);
9523
9524  # Open pipes, for use between the parent and child processes.  Specifically,
9525  # the child will indicate when it's done with its test by writing a message
9526  # to the parent.
9527  my ($rfh, $wfh);
9528  unless (pipe($rfh, $wfh)) {
9529    die("Can't open pipe: $!");
9530  }
9531
9532  my $ex;
9533
9534  # Fork child
9535  $self->handle_sigchld();
9536  defined(my $pid = fork()) or die("Can't fork: $!");
9537  if ($pid) {
9538    eval {
9539      sleep(1);
9540      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
9541      $client->login($setup->{user}, "password");
9542
9543      my $resp_msgs = $client->response_msgs();
9544      my $nmsgs = scalar(@$resp_msgs);
9545
9546      my $expected = 1;
9547      $self->assert($expected == $nmsgs,
9548        test_msg("Expected $expected, got $nmsgs"));
9549
9550      $expected = "User $setup->{user} logged in";
9551      $self->assert($expected eq $resp_msgs->[0],
9552        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
9553    };
9554    if ($@) {
9555      $ex = $@;
9556    }
9557
9558    $wfh->print("done\n");
9559    $wfh->flush();
9560
9561  } else {
9562    eval { server_wait($setup->{config_file}, $rfh) };
9563    if ($@) {
9564      warn($@);
9565      exit 1;
9566    }
9567
9568    exit 0;
9569  }
9570
9571  # Stop server
9572  server_stop($setup->{pid_file});
9573  $self->assert_child_ok($pid);
9574
9575  test_cleanup($setup->{log_file}, $ex);
9576}
9577
9578sub sql_passwd_bcrypt_php_variant {
9579  my $self = shift;
9580  my $tmpdir = $self->{tmpdir};
9581
9582  my $passwd = '$2y$14$r5BQoR3rAFQGnOEh73QrOOIsQJPjIYVF76IitsqqA7ix51SE/llCG';
9583
9584  my $setup = test_setup($tmpdir, 'sql_passwd', undef, $passwd);
9585
9586  my $db_file = File::Spec->rel2abs("$tmpdir/proftpd.db");
9587
9588  # Build up sqlite3 command to create users, groups tables and populate them
9589  my $db_script = File::Spec->rel2abs("$tmpdir/proftpd.sql");
9590
9591  if (open(my $fh, "> $db_script")) {
9592    print $fh <<EOS;
9593CREATE TABLE users (
9594  userid TEXT,
9595  passwd TEXT,
9596  uid INTEGER,
9597  gid INTEGER,
9598  homedir TEXT,
9599  shell TEXT
9600);
9601INSERT INTO users (userid, passwd, uid, gid, homedir, shell) VALUES ('$setup->{user}', '$passwd', $setup->{uid}, $setup->{gid}, '$setup->{home_dir}', '/bin/bash');
9602
9603CREATE TABLE groups (
9604  groupname TEXT,
9605  gid INTEGER,
9606  members TEXT
9607);
9608INSERT INTO groups (groupname, gid, members) VALUES ('$setup->{group}', $setup->{gid}, '$setup->{user}');
9609EOS
9610
9611    unless (close($fh)) {
9612      die("Can't write $db_script: $!");
9613    }
9614
9615  } else {
9616    die("Can't open $db_script: $!");
9617  }
9618
9619  my $cmd = "sqlite3 $db_file < $db_script";
9620
9621  if ($ENV{TEST_VERBOSE}) {
9622    print STDERR "Executing sqlite3: $cmd\n";
9623  }
9624
9625  my @output = `$cmd`;
9626  if (scalar(@output) &&
9627      $ENV{TEST_VERBOSE}) {
9628    print STDERR "Output: ", join('', @output), "\n";
9629  }
9630
9631  my $config = {
9632    PidFile => $setup->{pid_file},
9633    ScoreboardFile => $setup->{scoreboard_file},
9634    SystemLog => $setup->{log_file},
9635
9636    IfModules => {
9637      'mod_delay.c' => {
9638        DelayEngine => 'off',
9639      },
9640
9641      'mod_sql.c' => {
9642        SQLAuthTypes => 'bcrypt',
9643        SQLBackend => 'sqlite3',
9644        SQLConnectInfo => $db_file,
9645        SQLLogFile => $setup->{log_file},
9646      },
9647
9648      'mod_sql_passwd.c' => {
9649        SQLPasswordEngine => 'on',
9650        SQLPasswordEncoding => 'base64',
9651      },
9652    },
9653  };
9654
9655  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
9656    $config);
9657
9658  # Open pipes, for use between the parent and child processes.  Specifically,
9659  # the child will indicate when it's done with its test by writing a message
9660  # to the parent.
9661  my ($rfh, $wfh);
9662  unless (pipe($rfh, $wfh)) {
9663    die("Can't open pipe: $!");
9664  }
9665
9666  my $ex;
9667
9668  # Fork child
9669  $self->handle_sigchld();
9670  defined(my $pid = fork()) or die("Can't fork: $!");
9671  if ($pid) {
9672    eval {
9673      sleep(1);
9674      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1);
9675      $client->login($setup->{user}, "password");
9676
9677      my $resp_msgs = $client->response_msgs();
9678      my $nmsgs = scalar(@$resp_msgs);
9679
9680      my $expected = 1;
9681      $self->assert($expected == $nmsgs,
9682        test_msg("Expected $expected, got $nmsgs"));
9683
9684      $expected = "User $setup->{user} logged in";
9685      $self->assert($expected eq $resp_msgs->[0],
9686        test_msg("Expected response message '$expected', got '$resp_msgs->[0]'"));
9687    };
9688    if ($@) {
9689      $ex = $@;
9690    }
9691
9692    $wfh->print("done\n");
9693    $wfh->flush();
9694
9695  } else {
9696    eval { server_wait($setup->{config_file}, $rfh) };
9697    if ($@) {
9698      warn($@);
9699      exit 1;
9700    }
9701
9702    exit 0;
9703  }
9704
9705  # Stop server
9706  server_stop($setup->{pid_file});
9707  $self->assert_child_ok($pid);
9708
9709  test_cleanup($setup->{log_file}, $ex);
9710}
9711
97121;
9713