1package ProFTPD::Tests::Config::MaxLoginAttempts;
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  maxloginattempts_one => {
19    order => ++$order,
20    test_class => [qw(forking)],
21  },
22
23  maxloginattempts_absent => {
24    order => ++$order,
25    test_class => [qw(forking)],
26  },
27
28};
29
30sub new {
31  return shift()->SUPER::new(@_);
32}
33
34sub list_tests {
35  return testsuite_get_runnable_tests($TESTS);
36}
37
38sub maxloginattempts_one {
39  my $self = shift;
40  my $tmpdir = $self->{tmpdir};
41  my $setup = test_setup($tmpdir, 'config');
42
43  my $max_logins = 1;
44
45  my $config = {
46    PidFile => $setup->{pid_file},
47    ScoreboardFile => $setup->{scoreboard_file},
48    SystemLog => $setup->{log_file},
49
50    AuthUserFile => $setup->{auth_user_file},
51    AuthGroupFile => $setup->{auth_group_file},
52
53    MaxLoginAttempts => $max_logins,
54
55    IfModules => {
56      'mod_delay.c' => {
57        DelayEngine => 'off',
58      },
59    },
60  };
61
62  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
63    $config);
64
65  # Open pipes, for use between the parent and child processes.  Specifically,
66  # the child will indicate when it's done with its test by writing a message
67  # to the parent.
68  my ($rfh, $wfh);
69  unless (pipe($rfh, $wfh)) {
70    die("Can't open pipe: $!");
71  }
72
73  my $ex;
74
75  # Fork child
76  $self->handle_sigchld();
77  defined(my $pid = fork()) or die("Can't fork: $!");
78  if ($pid) {
79    eval {
80      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
81
82      eval { $client->login($setup->{user}, 'foo') };
83      unless ($@) {
84        die("Logged in unexpectedly");
85      }
86
87      my $resp_code = $client->response_code();
88      my $resp_msg = $client->response_msg(0);
89
90      my $expected = 530;
91      $self->assert($expected == $resp_code,
92        test_msg("Expected response code $expected, got $resp_code"));
93
94      $expected = "Login incorrect.";
95      $self->assert($expected eq $resp_msg,
96        test_msg("Expected response message '$expected', got '$resp_msg'"));
97
98      # A MaxLoginAttempts of one should have caused our connection to be
99      # closed above.
100
101      eval { ($resp_code, $resp_msg) = $client->login($setup->{user}, 'foo') };
102      unless ($@) {
103        die("Logged in unexpectedly ($resp_code $resp_msg)");
104      }
105
106      $resp_code = $client->response_code();
107      $resp_msg = $client->response_msg(0);
108
109      $expected = 599;
110      $self->assert($expected == $resp_code,
111        test_msg("Expected response code $expected, got $resp_code"));
112    };
113    if ($@) {
114      $ex = $@;
115    }
116
117    $wfh->print("done\n");
118    $wfh->flush();
119
120  } else {
121    eval { server_wait($setup->{config_file}, $rfh) };
122    if ($@) {
123      warn($@);
124      exit 1;
125    }
126
127    exit 0;
128  }
129
130  # Stop server
131  server_stop($setup->{pid_file});
132  $self->assert_child_ok($pid);
133
134  # We can peruse the generated debug log messages for what we want, but
135  # only if the TEST_VERBOSE environment variable is true.
136  unless ($ENV{TEST_VERBOSE}) {
137    test_cleanup($setup->{log_file}, $ex);
138    return;
139  }
140
141  eval {
142    if (open(my $fh, "< $setup->{log_file}")) {
143      my $expected_post_cmd_err = 0;
144      my $expected_log_cmd_err = 0;
145
146      while (my $line = <$fh>) {
147        chomp($line);
148
149        if ($ENV{TEST_VERBOSE}) {
150          print STDERR "# $line\n";
151        }
152
153        if ($line =~ /POST_CMD_ERR command 'PASS/) {
154          $expected_post_cmd_err = 1;
155          next;
156        }
157
158        if ($line =~ /LOG_CMD_ERR command 'PASS/) {
159          $expected_log_cmd_err = 1;
160          next;
161        }
162      }
163
164      close($fh);
165
166      $self->assert($expected_post_cmd_err && $expected_log_cmd_err,
167        test_msg("Did not see expected PASS POST_CMD_ERR and LOG_CMD_ERR log messages"));
168
169    } else {
170      die("Can't read $setup->{log_file}: $!");
171    }
172  };
173  if ($@) {
174    $ex = $@;
175  }
176
177  test_cleanup($setup->{log_file}, $ex);
178}
179
180sub maxloginattempts_absent {
181  my $self = shift;
182  my $tmpdir = $self->{tmpdir};
183  my $setup = test_setup($tmpdir, 'config');
184
185  my $config = {
186    PidFile => $setup->{pid_file},
187    ScoreboardFile => $setup->{scoreboard_file},
188    SystemLog => $setup->{log_file},
189
190    AuthUserFile => $setup->{auth_user_file},
191    AuthGroupFile => $setup->{auth_group_file},
192
193    IfModules => {
194      'mod_delay.c' => {
195        DelayEngine => 'off',
196      },
197    },
198  };
199
200  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
201    $config);
202
203  # Open pipes, for use between the parent and child processes.  Specifically,
204  # the child will indicate when it's done with its test by writing a message
205  # to the parent.
206  my ($rfh, $wfh);
207  unless (pipe($rfh, $wfh)) {
208    die("Can't open pipe: $!");
209  }
210
211  my $ex;
212
213  # Fork child
214  $self->handle_sigchld();
215  defined(my $pid = fork()) or die("Can't fork: $!");
216  if ($pid) {
217    eval {
218      my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port);
219
220      for (my $i = 0; $i < 3; $i++) {
221        eval { $client->login($setup->{user}, 'foo') };
222        unless ($@) {
223          die("Logged in unexpectedly");
224        }
225
226        my $resp_code = $client->response_code();
227        my $resp_msg = $client->response_msg(0);
228
229        my $expected = 530;
230        $self->assert($expected == $resp_code,
231          test_msg("Expected response code $expected, got $resp_code"));
232
233        $expected = "Login incorrect.";
234        $self->assert($expected eq $resp_msg,
235          test_msg("Expected response message '$expected', got '$resp_msg'"));
236      }
237
238      eval { $client->login($setup->{user}, 'foo') };
239      unless ($@) {
240        die("Logged in unexpectedly");
241      }
242
243      my $resp_code = $client->response_code();
244      my $resp_msg = $client->response_msg(0);
245
246      my $expected = 599;
247      $self->assert($expected == $resp_code,
248        test_msg("Expected response code $expected, got $resp_code"));
249    };
250    if ($@) {
251      $ex = $@;
252    }
253
254    $wfh->print("done\n");
255    $wfh->flush();
256
257  } else {
258    eval { server_wait($setup->{config_file}, $rfh) };
259    if ($@) {
260      warn($@);
261      exit 1;
262    }
263
264    exit 0;
265  }
266
267  # Stop server
268  server_stop($setup->{pid_file});
269  $self->assert_child_ok($pid);
270
271  test_cleanup($setup->{log_file}, $ex);
272}
273
2741;
275