# Functions for configuring the fail2ban log analyser BEGIN { push(@INC, ".."); }; use strict; use warnings; no warnings 'redefine'; no warnings 'uninitialized'; use WebminCore; &init_config(); our ($module_root_directory, %text, %config, %gconfig, $base_remote_user); our %access = &get_module_acl(); our @all_files_for_lock; # check_fail2ban() # Returns undef if installed, or an appropriate error message if missing sub check_fail2ban { -d $config{'config_dir'} || return &text('check_edir', "<tt>$config{'config_dir'}</tt>"); -r "$config{'config_dir'}/fail2ban.conf" || return &text('check_econf', "<tt>$config{'config_dir'}</tt>", "<tt>fail2ban.conf</tt>"); &has_command($config{'client_cmd'}) || return &text('check_eclient', "<tt>$config{'client_cmd'}</tt>"); &has_command($config{'server_cmd'}) || return &text('check_eserver', "<tt>$config{'server_cmd'}</tt>"); return undef; } sub is_fail2ban_running { my ($pid) = &find_byname($config{'server_cmd'}); if (!$pid) { ($pid) = &find_byname("fail2ban-server"); } return $pid; } # list_filters() # Returns a list of all defined filter files, each of which contains multiple # sections like [Definition] sub list_filters { my $dir = "$config{'config_dir'}/filter.d"; my @rv; foreach my $f (glob("$dir/*.conf")) { my @conf = &parse_config_file($f); my @lconf = &parse_config_file(&make_local_file($f)); &merge_local_files(\@conf, \@lconf); if (@conf) { push(@rv, \@conf); } } return @rv; } # list_actions() # Returns a list of all defined action files, each of which contains multiple # sections like [Definition] and [Init] sub list_actions { my $dir = "$config{'config_dir'}/action.d"; my @rv; foreach my $f (glob("$dir/*.conf")) { my @conf = &parse_config_file($f); my @lconf = &parse_config_file(&make_local_file($f)); &merge_local_files(\@conf, \@lconf); if (@conf) { push(@rv, \@conf); } } return @rv; } # list_jails() # Returns a list of all sections from the jails file sub list_jails { # Read the main config file my @rv; my $jfile = "$config{'config_dir'}/jail.conf"; if (-r $jfile) { push(@rv, &parse_config_file($jfile)); } # Read separate config files under jail.d my $jdir = "$config{'config_dir'}/jail.d"; if (-d $jdir) { foreach my $f (glob("$jdir/*.conf")) { push(@rv, &parse_config_file($f)); } } # Read the main local file, and separate files under jail.d my @lrv; my $jlfile = &make_local_file($jfile); if (-r $jlfile) { push(@lrv, &parse_config_file($jlfile)); } if (-d $jdir) { foreach my $f (glob("$jdir/*.local")) { push(@lrv, &parse_config_file($f)); } } # Use local file entries to override the global config &merge_local_files(\@rv, \@lrv); return @rv; } # merge_local_files(&rv, &locals) # Merges .local file entries in with .conf files sub merge_local_files { my ($rv, $lrv) = @_; foreach my $l (@$lrv) { my ($r) = grep { $_->{'name'} eq $l->{'name'} } @$rv; if ($r) { # Section exists in the global config, so put the local # directives first my $m = { %$l }; $m->{'local'} = 1; $m->{'origfile'} = $r->{'file'}; push(@{$m->{'members'}}, @{$r->{'members'}}); $rv->[&indexof($r, @$rv)] = $m; } else { # Section does not exist, so just add it push(@$rv, $l); } } } # make_local_file(path) sub make_local_file { my ($f) = @_; $f =~ s/\.conf$/\.local/g; return $f; } # get_config() # Returns the global config as an array ref of directives sub get_config { my $file = "$config{'config_dir'}/fail2ban.conf"; my @conf = &parse_config_file($file); my @lconf = &parse_config_file(&make_local_file($file)); &merge_local_files(\@conf, \@lconf); return \@conf; } # parse_config_file(file) # Parses one file into a list of [] sections, each with multiple directives sub parse_config_file { my ($file) = @_; my $lref = &read_file_lines($file, 1); my $lnum = 0; my $fh = "CONF"; my $sect; my @rv; &open_readfile($fh, $file) || return ( ); while(<$fh>) { s/\r|\n//g; s/^\s*#.*$//; s/^\s;.*$//; if (/^\[([^\]]+)\]/) { # Start of a section $sect = { 'name' => $1, 'line' => $lnum, 'eline' => $lnum, 'file' => $file, 'members' => [] }; push(@rv, $sect); } elsif (/^(\S+)\s*=\s*(.*)/ && $sect) { # A directive in a section my $dir = { 'name' => $1, 'value' => $2, 'line' => $lnum, 'eline' => $lnum, 'file' => $file, }; push(@{$sect->{'members'}}, $dir); $sect->{'eline'} = $lnum; &split_directive_values($dir); } elsif (/^\s+(\S.*)/ && $sect && @{$sect->{'members'}}) { # Continuation of a directive my $dir = $sect->{'members'}->[@{$sect->{'members'}} - 1]; $dir->{'value'} .= "\n".$1; $dir->{'eline'} = $lnum; $sect->{'eline'} = $lnum; &split_directive_values($dir); } $lnum++; } close($fh); return @rv; } # split_directive_values(&dir) # Populate the 'values' field by splitting up the 'value' field sub split_directive_values { my ($dir) = @_; my @w; my $v = $dir->{'value'}; $v =~ s/\n/ /g; while($v =~ /\S/) { if ($v =~ /^([^\[]+\[[^\]]+\])\s*(.*)/) { push(@w, $1); $v = $2; } elsif ($v =~ /^\s*(\S+)\s*(.*)/) { push(@w, $1); $v = $2; } } $dir->{'words'} = \@w; } # create_section(file, §ion) # Add a new section to a file sub create_section { my ($file, $sect) = @_; my $lref = &read_file_lines($file); $sect->{'file'} = $file; $sect->{'line'} = scalar(@$lref); push(@$lref, §ion_lines($sect)); $sect->{'eline'} = scalar(@$lref); &flush_file_lines($file); } # modify_section(file, §ion) # Update the first line (only) for some section sub modify_section { my ($file, $sect) = @_; my $lref = &read_file_lines($file); my @lines = §ion_lines($sect); $lref->[$sect->{'line'}] = $lines[0]; &flush_file_lines($file); } # delete_section(file, §ion, [keep-file]) # Remove a section and all directives from a file sub delete_section { my ($file, $sect, $keepfile) = @_; my $lref = &read_file_lines($file); splice(@$lref, $sect->{'line'}, $sect->{'eline'} - $sect->{'line'} + 1); my $empty = 1; foreach my $l (@$lref) { my $ll = $l; $ll =~ s/^\s*#.*//; $empty = 0 if ($ll =~ /\S/); } if ($empty && !$keepfile) { # File is now empty, so delete it &unflush_file_lines($file); &unlink_file($file); } else { # Save the file &flush_file_lines($file); } } # section_lines(§ion) # Returns all the lines of text for some section plus directives sub section_lines { my ($sect) = @_; my @rv; push(@rv, "[".$sect->{'name'}."]"); foreach my $m (@{$sect->{'members'}}) { push(@rv, &directive_lines($m)); } return @rv; } # directive_lines(&directive) # Returns all lines of text for some directive sub directive_lines { my ($dir) = @_; my @rv; my @v = ref($dir->{'value'}) eq 'ARRAY' ? @{$dir->{'value'}} : split(/\n/, $dir->{'value'}); push(@rv, $dir->{'name'}." = ".shift(@v)); push(@rv, map { " ".$_ } @v); # Continuation return @rv; } # save_directive(name, value|&values|&directive, §ion) # Updates one directive in a section sub save_directive { my ($name, $v, $sect) = @_; my $dir; if (ref($v) eq 'HASH') { $dir = $v; } elsif (ref($v) eq 'ARRAY') { $dir = { 'name' => $name, 'value' => $v }; } elsif (defined($v)) { $dir = { 'name' => $name, 'value' => $v }; } else { $dir = undef; } my $old = &find($name, $sect); my $oldlen = $old ? $old->{'eline'} - $old->{'line'} + 1 : undef; my $oldidx = $old ? &indexof($old, @{$sect->{'members'}}) : -1; my $file = $old ? $old->{'file'} : $sect->{'file'}; my $lref = &read_file_lines($file); my @dirlines = defined($dir) ? &directive_lines($dir) : (); if ($old && defined($dir) && $old->{'value'} ne $dir->{'value'}) { # Update existing if ($sect->{'local'} && $old->{'file'} ne $sect->{'file'}) { # Section is in a local file, so to override we need to # add a new line in the local file &unflush_file_lines($file); $file = $sect->{'file'}; $lref = &read_file_lines($file); splice(@$lref, $sect->{'eline'}+1, 0, @dirlines); $dir->{'line'} = $sect->{'eline'}+1; $dir->{'file'} = $sect->{'file'}; $sect->{'eline'} += scalar(@dirlines); $dir->{'eline'} = $sect->{'eline'}; } else { # Just update the existing line splice(@$lref, $old->{'line'}, $oldlen, @dirlines); $dir->{'line'} = $old->{'line'}; $dir->{'eline'} = $dir->{'line'} + scalar(@dirlines) - 1; $dir->{'file'} = $sect->{'file'}; if ($oldidx >= 0) { $sect->{'members'}->[$oldidx] = $dir; } my $offset = scalar(@dirlines) - $oldlen; foreach my $m (@{$sect->{'members'}}) { next if ($m eq $dir || $m eq $old); if ($m->{'line'} > $old->{'line'}) { $m->{'line'} += $offset; $m->{'eline'} += $offset; } } } } elsif (!$old && defined($dir)) { # Add new if (!$sect->{'local'} && $file =~ /^(.*)\.conf$/) { # New directives should go in a .local file. We can assume at # this point that it doesn't exist yet, or that there is no # section in it. So convert this section object to local. my $lfile = $1.".local"; &unflush_file_lines($file); $file = $lfile; $lref = &read_file_lines($file); $sect->{'line'} = $sect->{'eline'} = scalar(@$lref); $sect->{'file'} = $file; splice(@$lref, $sect->{'eline'}, 0, "[$sect->{'name'}]"); splice(@$lref, $sect->{'eline'}+1, 0, @dirlines); $dir->{'line'} = $sect->{'eline'}+1; $dir->{'file'} = $sect->{'file'}; $sect->{'eline'} += scalar(@dirlines); $dir->{'eline'} = $sect->{'eline'}; } else { # Just add to the file the section is in (which will be local) splice(@$lref, $sect->{'eline'}+1, 0, @dirlines); $dir->{'line'} = $sect->{'eline'}+1; $dir->{'file'} = $sect->{'file'}; $sect->{'eline'} += scalar(@dirlines); $dir->{'eline'} = $sect->{'eline'}; } } elsif ($old && !defined($dir)) { # Remove existing splice(@$lref, $old->{'line'}, $oldlen); $sect->{'eline'} -= $oldlen; if ($oldidx >= 0) { splice(@{$sect->{'members'}}, $oldidx, 1); } foreach my $m (@{$sect->{'members'}}) { next if ($m eq $old); if ($m->{'line'} > $old->{'line'}) { $m->{'eline'} -= $oldlen; $m->{'line'} -= $oldlen; } } } &flush_file_lines($file); } sub find_value { my ($name, $object) = @_; my @rv = map { $_->{'value'} } &find($name, $object); return wantarray ? @rv : $rv[0]; } sub find { my ($name, $object) = @_; my $members = ref($object) eq 'HASH' ? $object->{'members'} : $object; my @rv = grep { lc($_->{'name'}) eq $name } @$members; return wantarray ? @rv : $rv[0]; } # filename_to_name(file) # Given a filename like /etc/fail2ban/foo.d/bar.conf , return bar sub filename_to_name { my ($file) = @_; $file =~ s/^.*\///; $file =~ s/\.[^\.]+$//; return $file; } # find_jail_by_filter(&filter) # returns the jail objects using a filter sub find_jail_by_filter { my ($filter) = @_; my $fname = &filename_to_name($filter->[0]->{'file'}); my @rv; foreach my $jail (&list_jails()) { my $jfilter = &find_value("filter", $jail); if ($jfilter eq $fname) { push(@rv, $jail); } } return @rv; } # find_jail_by_action(&action) # returns the jail objects using an action sub find_jail_by_action { my ($action) = @_; my $aname = &filename_to_name($action->[0]->{'file'}); my @rv; foreach my $jail (&list_jails()) { my $jaction = &find("action", $jail); next if (!$jaction); my @jactions = map { /^([^\[]+)/; $1 } @{$jaction->{'words'}}; if (&indexof($aname, @jactions) >= 0) { push(@rv, $jail); } } return @rv; } # start_fail2ban_server() # Attempts to start the server process, returning undef on success or an error message # on failure. sub start_fail2ban_server { if ($config{'init_script'}) { &foreign_require("init"); foreach my $init (split(/\s+/, $config{'init_script'})) { my ($ok, $out) = &init::start_action($init); return $out if (!$ok); } return undef; } else { my $out = &backquote_logged("$config{'client_cmd'} -x start 2>&1 </dev/null"); return $? ? $out : undef; } } # stop_fail2ban_server() # Attempts to stop the server process, returning undef on success or an error message # on failure. sub stop_fail2ban_server { if ($config{'init_script'}) { &foreign_require("init"); foreach my $init (split(/\s+/, $config{'init_script'})) { my ($ok, $out) = &init::stop_action($init); return $out if (!$ok); } return undef; } else { my $out = &backquote_logged("$config{'client_cmd'} stop 2>&1 </dev/null"); return $? ? $out : undef; } } # restart_fail2ban_server() # Force the fail2ban server to re-read its config sub restart_fail2ban_server { my ($force_restart) = @_; my $out; $out = &backquote_logged("$config{'client_cmd'} reload 2>&1 </dev/null") if (!$force_restart); if ($? || $force_restart) { &stop_fail2ban_server(); $out = &start_fail2ban_server(); } return $? ? $out : undef; } # list_all_config_files() # Returns a list of all Fail2Ban config files sub list_all_config_files { my @rv; push(@rv, "$config{'config_dir'}/fail2ban.local"); push(@rv, "$config{'config_dir'}/fail2ban.conf"); push(@rv, glob("$config{'config_dir'}/filter.d/*.conf")); push(@rv, glob("$config{'config_dir'}/filter.d/*.local")); push(@rv, glob("$config{'config_dir'}/action.d/*.conf")); push(@rv, glob("$config{'config_dir'}/action.d/*.local")); push(@rv, "$config{'config_dir'}/jail.conf"); push(@rv, "$config{'config_dir'}/jail.local"); push(@rv, glob("$config{'config_dir'}/jail.d/*.conf")); push(@rv, glob("$config{'config_dir'}/jail.d/*.local")); return grep { -r $_ || $_ =~ /fail2ban\.local$/ } @rv; } sub lock_all_config_files { @all_files_for_lock = &list_all_config_files(); foreach my $f (@all_files_for_lock) { &lock_file($f); } } sub unlock_all_config_files { foreach my $f (reverse(@all_files_for_lock)) { &unlock_file($f); } @all_files_for_lock = (); } # get_fail2ban_version() # Returns the version number, or undef if it cannot be found sub get_fail2ban_version { my $out = &backquote_command("$config{'client_cmd'} -V 2>/dev/null </dev/null"); return !$? && $out =~ /v([0-9\.]+)/ ? $1 : undef; } # Unblock given IP in given jail sub unblock_jailed_ip { my ($jail, $ip) = @_; my $cmd = "$config{'client_cmd'} set ".quotemeta($jail)." unbanip ".quotemeta($ip)." 2>&1 </dev/null"; my $out = &backquote_logged($cmd); if ($?) { &error(&text('status_err_unban', &html_escape($ip)) . " : $out"); } } 1;
Name | Type | Size | Permission | Actions |
---|---|---|---|---|
help | Folder | 0755 |
|
|
images | Folder | 0755 |
|
|
lang | Folder | 0755 |
|
|
CHANGELOG | File | 110 B | 0644 |
|
atboot.cgi | File | 919 B | 0755 |
|
backup_config.pl | File | 771 B | 0755 |
|
config | File | 100 B | 0644 |
|
config-debian-linux | File | 118 B | 0644 |
|
config-redhat-linux | File | 118 B | 0644 |
|
config-syno-linux | File | 100 B | 0644 |
|
config.info | File | 196 B | 0644 |
|
config.info.ca | File | 249 B | 0644 |
|
config.info.de | File | 202 B | 0644 |
|
config.info.fr | File | 231 B | 0644 |
|
config.info.ms | File | 201 B | 0644 |
|
config.info.no | File | 203 B | 0644 |
|
config.info.pl | File | 232 B | 0644 |
|
delete_actions.cgi | File | 877 B | 0755 |
|
delete_filters.cgi | File | 877 B | 0755 |
|
delete_jails.cgi | File | 624 B | 0755 |
|
edit_action.cgi | File | 2.12 KB | 0755 |
|
edit_config.cgi | File | 1.67 KB | 0755 |
|
edit_filter.cgi | File | 1.67 KB | 0755 |
|
edit_jail.cgi | File | 3.91 KB | 0755 |
|
edit_jaildef.cgi | File | 2.57 KB | 0755 |
|
edit_manual.cgi | File | 1.05 KB | 0755 |
|
fail2ban-lib.pl | File | 13.86 KB | 0644 |
|
index.cgi | File | 2.15 KB | 0755 |
|
install_check.pl | File | 465 B | 0755 |
|
list_actions.cgi | File | 1.15 KB | 0755 |
|
list_filters.cgi | File | 1.2 KB | 0755 |
|
list_jails.cgi | File | 1.55 KB | 0755 |
|
list_status.cgi | File | 3.04 KB | 0755 |
|
log_parser.pl | File | 804 B | 0755 |
|
module.info | File | 201 B | 0644 |
|
module.info.af | File | 0 B | 0644 |
|
module.info.af.auto | File | 150 B | 0644 |
|
module.info.ar | File | 0 B | 0644 |
|
module.info.ar.auto | File | 233 B | 0644 |
|
module.info.be | File | 0 B | 0644 |
|
module.info.be.auto | File | 225 B | 0644 |
|
module.info.bg | File | 0 B | 0644 |
|
module.info.bg.auto | File | 239 B | 0644 |
|
module.info.ca | File | 149 B | 0644 |
|
module.info.cs | File | 0 B | 0644 |
|
module.info.cs.auto | File | 161 B | 0644 |
|
module.info.da | File | 0 B | 0644 |
|
module.info.da.auto | File | 157 B | 0644 |
|
module.info.de | File | 138 B | 0644 |
|
module.info.el | File | 0 B | 0644 |
|
module.info.el.auto | File | 291 B | 0644 |
|
module.info.es | File | 0 B | 0644 |
|
module.info.es.auto | File | 152 B | 0644 |
|
module.info.eu | File | 0 B | 0644 |
|
module.info.eu.auto | File | 157 B | 0644 |
|
module.info.fa | File | 0 B | 0644 |
|
module.info.fa.auto | File | 220 B | 0644 |
|
module.info.fi | File | 0 B | 0644 |
|
module.info.fi.auto | File | 175 B | 0644 |
|
module.info.fr | File | 0 B | 0644 |
|
module.info.fr.auto | File | 180 B | 0644 |
|
module.info.he | File | 0 B | 0644 |
|
module.info.he.auto | File | 201 B | 0644 |
|
module.info.hr | File | 0 B | 0644 |
|
module.info.hr.auto | File | 141 B | 0644 |
|
module.info.hu | File | 0 B | 0644 |
|
module.info.hu.auto | File | 159 B | 0644 |
|
module.info.it | File | 0 B | 0644 |
|
module.info.it.auto | File | 163 B | 0644 |
|
module.info.ja | File | 0 B | 0644 |
|
module.info.ja.auto | File | 187 B | 0644 |
|
module.info.ko | File | 0 B | 0644 |
|
module.info.ko.auto | File | 167 B | 0644 |
|
module.info.lt | File | 0 B | 0644 |
|
module.info.lt.auto | File | 171 B | 0644 |
|
module.info.lv | File | 0 B | 0644 |
|
module.info.lv.auto | File | 162 B | 0644 |
|
module.info.ms | File | 161 B | 0644 |
|
module.info.mt | File | 0 B | 0644 |
|
module.info.mt.auto | File | 176 B | 0644 |
|
module.info.nl | File | 0 B | 0644 |
|
module.info.nl.auto | File | 156 B | 0644 |
|
module.info.no | File | 36 B | 0644 |
|
module.info.no.auto | File | 114 B | 0644 |
|
module.info.pl | File | 158 B | 0644 |
|
module.info.pt | File | 0 B | 0644 |
|
module.info.pt.auto | File | 161 B | 0644 |
|
module.info.pt_BR | File | 0 B | 0644 |
|
module.info.pt_BR.auto | File | 167 B | 0644 |
|
module.info.ro | File | 0 B | 0644 |
|
module.info.ro.auto | File | 186 B | 0644 |
|
module.info.ru | File | 0 B | 0644 |
|
module.info.ru.auto | File | 249 B | 0644 |
|
module.info.sk | File | 0 B | 0644 |
|
module.info.sk.auto | File | 174 B | 0644 |
|
module.info.sl | File | 0 B | 0644 |
|
module.info.sl.auto | File | 147 B | 0644 |
|
module.info.sv | File | 0 B | 0644 |
|
module.info.sv.auto | File | 164 B | 0644 |
|
module.info.th | File | 0 B | 0644 |
|
module.info.th.auto | File | 326 B | 0644 |
|
module.info.tr | File | 0 B | 0644 |
|
module.info.tr.auto | File | 173 B | 0644 |
|
module.info.uk | File | 0 B | 0644 |
|
module.info.uk.auto | File | 236 B | 0644 |
|
module.info.ur | File | 0 B | 0644 |
|
module.info.ur.auto | File | 272 B | 0644 |
|
module.info.vi | File | 0 B | 0644 |
|
module.info.vi.auto | File | 203 B | 0644 |
|
module.info.zh | File | 0 B | 0644 |
|
module.info.zh.auto | File | 118 B | 0644 |
|
module.info.zh_TW | File | 0 B | 0644 |
|
module.info.zh_TW.auto | File | 124 B | 0644 |
|
restart.cgi | File | 334 B | 0755 |
|
save_action.cgi | File | 1.86 KB | 0755 |
|
save_config.cgi | File | 975 B | 0755 |
|
save_filter.cgi | File | 1.69 KB | 0755 |
|
save_jail.cgi | File | 3.71 KB | 0755 |
|
save_jaildef.cgi | File | 1.49 KB | 0755 |
|
save_manual.cgi | File | 620 B | 0755 |
|
start.cgi | File | 326 B | 0755 |
|
stop.cgi | File | 322 B | 0755 |
|
syslog_logs.pl | File | 599 B | 0755 |
|
unblock_jail.cgi | File | 708 B | 0755 |
|