diff --git a/miniserv.pl b/miniserv.pl index 2b71b5c75..a5b3d9348 100755 --- a/miniserv.pl +++ b/miniserv.pl @@ -283,6 +283,9 @@ if ($config{'debuglog'}) { print DEBUG "miniserv.pl starting ..\n"; } +# Write out (empty) blocked hosts file +&write_blocked_file(); + # Re-direct STDERR to a log file if ($config{'errorlog'} ne '-') { open(STDERR, ">>$config{'errorlog'}") || die "failed to open $config{'errorlog'} : $!"; @@ -547,19 +550,43 @@ while(1) { # run the unblocking procedure to check if enough time has passed to # unblock hosts that heve been blocked because of password failures + $unblocked = 0; if ($config{'blockhost_failures'}) { $i = 0; while ($i <= $#deny) { - if ($blockhosttime{$deny[$i]} && $config{'blockhost_time'} != 0 && - ($time_now - $blockhosttime{$deny[$i]}) >= $config{'blockhost_time'}) { + if ($blockhosttime{$deny[$i]} && + $config{'blockhost_time'} != 0 && + ($time_now - $blockhosttime{$deny[$i]}) >= + $config{'blockhost_time'}) { # the host can be unblocked now $hostfail{$deny[$i]} = 0; splice(@deny, $i, 1); + $unblocked = 1; } $i++; } } + # Do the same for blocked users + if ($config{'blockuser_failures'}) { + $i = 0; + while ($i <= $#deny) { + if ($blockusertime{$deny[$i]} && + $config{'blockuser_time'} != 0 && + ($time_now - $blockusertime{$deny[$i]}) >= + $config{'blockuser_time'}) { + # the user can be unblocked now + $userfail{$deny[$i]} = 0; + splice(@denyusers, $i, 1); + $unblocked = 1; + } + $i++; + } + } + if ($unblocked) { + &write_blocked_file(); + } + if ($config{'session'} && (++$remove_session_count%50) == 0) { # Remove sessions with more than 7 days of inactivity, local $s; @@ -742,32 +769,61 @@ while(1) { if ($3) { # login OK.. no delay print $outfd "0 0\n"; + $wasblocked = $hostfail{$2} || + $userfail{$1}; $hostfail{$2} = 0; + $userfail{$1} = 0; + if ($wasblocked) { + &write_blocked_file(); + } } else { # login failed.. $hostfail{$2}++; - # add the host to the block list if necessary + $userfail{$1}++; + $blocked = 0; + + # add the host to the block list, + # if configured if ($config{'blockhost_failures'} && - $hostfail{$2} >= $config{'blockhost_failures'}) { - push(@deny, $2); + $hostfail{$2} >= + $config{'blockhost_failures'}) { + push(@deny, $2); $blockhosttime{$2} = $time_now; $blocked = 1; if ($use_syslog) { - local $logtext = "Security alert: Host $2 ". - "blocked after $config{'blockhost_failures'} ". - "failed logins for user $1"; - syslog("crit", "%s", $logtext); + local $logtext = "Security alert: Host $2 blocked after $config{'blockhost_failures'} failed logins for user $1"; + syslog("crit", "%s", + $logtext); } } - else { - $blocked = 0; + + # add the user to the user block list, + # if configured + if ($config{'blockuser_failures'} && + $userfail{$1} >= + $config{'blockuser_failures'}) { + push(@denyusers, $1); + $blockusertime{$1} = $time_now; + $blocked = 2; + if ($use_syslog) { + local $logtext = "Security alert: User $1 blocked after $config{'blockuser_failures'} failed logins"; + syslog("crit", "%s", + $logtext); + } } + + # Send back a delay $dl = $userdlay{$1} - - int(($time_now - $userlast{$1})/50); + int(($time_now - $userlast{$1})/50); $dl = $dl < 0 ? 0 : $dl+1; print $outfd "$dl $blocked\n"; $userdlay{$1} = $dl; + + # Write out blocked status file + if ($blocked) { + &write_blocked_file(); + } } $userlast{$1} = $time_now; } @@ -1599,12 +1655,18 @@ if (%users) { return 0; } } - else { + elsif ($blocked == 1) { # when the host has been blocked, give it an error &http_error(403, "Access denied for $acptip. The host ". "has been blocked because of too ". "many authentication failures."); } + elsif ($blocked == 2) { + # when the user has been blocked, give it an error + &http_error(403, "Access denied. The user ". + "has been blocked because of too ". + "many authentication failures."); + } } else { # Get the real Webmin username @@ -3759,6 +3821,10 @@ if (!$config{'tempbase'}) { $config{'pidfile'} =~ /^(.*)\/[^\/]+$/; $config{'tempbase'} = "$1/cgitemp"; } +if (!$config{'blockedfile'}) { + $config{'pidfile'} =~ /^(.*)\/[^\/]+$/; + $config{'blockedfile'} = "$1/blocked"; + } } # read_users_file() @@ -4353,3 +4419,18 @@ foreach my $s (@substrings, @mobile_agents) { return 0; } +# write_blocked_file() +# Writes out a text file of blocked hosts and users +sub write_blocked_file +{ +open(BLOCKED, ">$config{'blockedfile'}"); +foreach my $d (grep { $hostfail{$_} } @deny) { + print BLOCKED "host $d $hostfail{$d} $blockhosttime{$d}\n"; + } +foreach my $d (grep { $userfail{$_} } @denyusers) { + print BLOCKED "user $d $userfail{$d} $blockusertime{$d}\n"; + } +close(BLOCKED); +chmod(0700, $config{'blockedfile'}); +} + diff --git a/webmin/CHANGELOG b/webmin/CHANGELOG index 49e82bd05..0814315d6 100644 --- a/webmin/CHANGELOG +++ b/webmin/CHANGELOG @@ -60,3 +60,6 @@ When Webmin's detected OS is automatically updated, Usermin's will be too (if it ---- Changes since 1.340 ---- Added an option to the Proxy Servers form to fallback to a direct connection if the proxy is down. Added a tab showing details of the current cert, with a link to download in PEM or PKCS12 format. +---- Changes since 1.350 ---- +Added an option to the Authentication page to block users with too many failed logins, as well as hosts. +Created the new Blocked Hosts and Users page to show blocks currently in force, and allow them to be cleared. diff --git a/webmin/change_session.cgi b/webmin/change_session.cgi index 4847c4f66..a07b1f980 100755 --- a/webmin/change_session.cgi +++ b/webmin/change_session.cgi @@ -9,6 +9,8 @@ require './webmin-lib.pl'; &lock_file($ENV{'MINISERV_CONFIG'}); &get_miniserv_config(\%miniserv); $miniserv{'passdelay'} = $in{'passdelay'}; + +# Save blocked hosts if ($in{'blockhost_on'}) { $in{'blockhost_time'} =~ /^\d+$/ && $in{'blockhost_time'} > 0 || &error($text{'session_eblockhost_time'}); @@ -20,6 +22,20 @@ if ($in{'blockhost_on'}) { else { $miniserv{'blockhost_time'} = $miniserv{'blockhost_failures'} = undef; } + +# Save blocked users +if ($in{'blockuser_on'}) { + $in{'blockuser_time'} =~ /^\d+$/ && $in{'blockuser_time'} > 0 || + &error($text{'session_eblockuser_time'}); + $in{'blockuser_failures'} =~ /^\d+$/ && $in{'blockuser_failures'} > 0 || + &error($text{'session_eblockuser_failures'}); + $miniserv{'blockuser_time'} = $in{'blockuser_time'}; + $miniserv{'blockuser_failures'} = $in{'blockuser_failures'}; + } +else { + $miniserv{'blockuser_time'} = $miniserv{'blockuser_failures'} = undef; + } + $miniserv{'syslog'} = $in{'syslog'}; if ($in{'session'} && $ENV{'HTTP_COOKIE'} !~ /sessiontest=1/i) { &error($text{'session_ecookie'}); diff --git a/webmin/clear_blocked.cgi b/webmin/clear_blocked.cgi new file mode 100755 index 000000000..081b77689 --- /dev/null +++ b/webmin/clear_blocked.cgi @@ -0,0 +1,8 @@ +#!/usr/local/bin/perl +# Re-start Webmin to clear blocks + +require './webmin-lib.pl'; + +&show_restart_page($text{'blocked_title'}, $text{'blocked_restarting'}); + + diff --git a/webmin/edit_session.cgi b/webmin/edit_session.cgi index 017c9826d..5e9cbed19 100755 --- a/webmin/edit_session.cgi +++ b/webmin/edit_session.cgi @@ -15,15 +15,27 @@ print "
| $text{'session_header'} |
| \n";
+# Bad password delay
printf " %s \n", $miniserv{'passdelay'} ? '' : 'checked', $text{'session_pdisable'}; printf " %s \n", $miniserv{'passdelay'} ? 'checked' : '', $text{'session_penable'}; + +# Block bad hosts printf " \n", $miniserv{'blockhost_failures'} ? "checked" : ""; print &text('session_blockhost', - "", - "")," \n"; + &ui_textbox("blockhost_failures", $miniserv{'blockhost_failures'}, 4), + &ui_textbox("blockhost_time", $miniserv{'blockhost_time'}, 4))," \n"; + +# Block bad users +printf " \n", + $miniserv{'blockuser_failures'} ? "checked" : ""; +print &text('session_blockuser', + &ui_textbox("blockuser_failures", $miniserv{'blockuser_failures'}, 4), + &ui_textbox("blockuser_time", $miniserv{'blockuser_time'}, 4))," \n"; + +# Log to syslog eval "use Sys::Syslog qw(:DEFAULT setlogsock)"; if (!$@) { printf " %s\n", diff --git a/webmin/images/blocked.gif b/webmin/images/blocked.gif new file mode 100644 index 000000000..0cbd21e63 Binary files /dev/null and b/webmin/images/blocked.gif differ diff --git a/webmin/index.cgi b/webmin/index.cgi index 1ab002bb5..e39535e64 100755 --- a/webmin/index.cgi +++ b/webmin/index.cgi @@ -14,7 +14,7 @@ $ver = &get_webmin_version(); "edit_upgrade.cgi", "edit_session.cgi", "edit_assignment.cgi", "edit_categories.cgi", "edit_descs.cgi", "edit_themes.cgi", "edit_referers.cgi", "edit_anon.cgi", "edit_lock.cgi", - "edit_mobile.cgi", "edit_advanced.cgi" ); + "edit_mobile.cgi", "edit_blocked.cgi", "edit_advanced.cgi" ); @wtitles = ( $text{'access_title'}, $text{'bind_title'}, $text{'log_title'}, $text{'proxy_title'}, $text{'ui_title'}, $text{'mods_title'}, @@ -24,7 +24,8 @@ $ver = &get_webmin_version(); $text{'categories_title'}, $text{'descs_title'}, $text{'themes_title'}, $text{'referers_title'}, $text{'anon_title'}, $text{'lock_title'}, - $text{'mobile_title'}, $text{'advanced_title'} ); + $text{'mobile_title'}, $text{'blocked_title'}, + $text{'advanced_title'} ); @wicons = ( "images/access.gif", "images/bind.gif", "images/log.gif", "images/proxy.gif", "images/ui.gif", "images/mods.gif", "images/os.gif", "images/lang.gif", "images/startpage.gif", @@ -32,7 +33,7 @@ $ver = &get_webmin_version(); "images/assignment.gif", "images/categories.gif", "images/descs.gif", "images/themes.gif", "images/referers.gif", "images/anon.gif", "images/lock.gif", "images/mobile.gif", - "images/advanced.gif" ); + "images/blocked.gif", "images/advanced.gif" ); if ($gconfig{'eazel'}) { push(@wlinks, "edit_syslet.cgi"); push(@wtitles, $text{'syslet_title'}); diff --git a/webmin/lang/de b/webmin/lang/de index 26fc65af7..0ceae9b31 100644 --- a/webmin/lang/de +++ b/webmin/lang/de @@ -19,9 +19,12 @@ advanced_etemp=Fehlendes oder nicht-existentes Verzeichnis für temporä advanced_header=Erweiterte und experimentelle Optionen advanced_pass=Mache Passwort anderen Webmin-Programmen verfügbar? (Funktioniert nicht, wenn Session-Authentication eingeschaltet ist) advanced_preload=Webmin-Funktionsbibliotheken vorladen? +advanced_showstderr=Perl-Fehler im Browser anzeigen? +advanced_tdir=Verzeichnis advanced_temp=Verzeichnis für temporäre Dateien advanced_tempdef=Standard (/tmp/.webmin) advanced_title=Erweiterte Optionen +advanced_tmod=Module anon_desc=Diese Seite erlaubt Ihnen, Zugriff auf ausgwählte Webmin-Module und Pfade zu gewähren, ohne dass sich die Benutzer anmelden müssen. Für jeden Modul-Pfad, den Sie unten eingeben (so wie /custom oder /passwd) müssen Sie auch den Namen des Webminbenutzers eingeben, dessen Rechte für den Zugriff auf die Module benutzt werden soll. anon_desc2=Sie sollten SEHR vorsichtig sein, wenn Sie anonymen Zugriff gewähren, da ungenügende IP-Zugriffskontrolle oder Zugriff auf die falschen Module Angreifern die Übernahme des Systems ermöglichen kann. anon_err=Konnte Einstellungen für Anonymen Zugriff nicht speichern @@ -79,6 +82,8 @@ ca_stop=Zertifikatsautorität herunterfahren ca_stopmsg=Klicken Sie auf diesen Schaltfläche, damit Webmin keine existierenden Zertifikate mehr akzeptiert oder neue ausstellt. Dies wird Benutzer zwingen, sich stattdessen mit Benutzernamen und Kennwort anzumelden. ca_stopok=Ihre Zertifikatsautorität wurde erfolgreich heruntergefahren. ca_title=Zertifikatsautorität +cache_ok=Suchen +cache_size=Größe categories_code=ID categories_desc=In diesem Dialog können Sie vorhandene Webmin-Kategorien bearbeiten und neue hinzufügen, denen Sie dann einzelne Module zuweisen können. Im oberen Teil der Maske sehen Sie die vorhandenen Kategorien, deren Beschreibungen Sie hier ändern können. In der untersten Zeile können Sie eine neue Kategorie erzeugen. categories_ecat=Kategorie-ID $1 ist bereits verwendet. @@ -307,6 +312,10 @@ os_update=Aktualisiere Webmin, so daß es das erkannte Betriebssystem benutz os_value=Wert os_webmin=Betriebssystem, wie Webmin es sieht. proxy_bind=Quell-IP-Adresse für HTTP-Verbindungen +proxy_cache1=Nein +proxy_clear=Cache löschen +proxy_days=Tage +proxy_daysdef=Für immer proxy_desc=Wenn der Host, auf dem Webmin läuft, sich hinter einer Firewall befindet, kann es sein, dass Sie einen Proxyserver zum Zugriff auf Web- und FTP-Seiten verwenden müssen. Einige Module, wie z. B. das Modul Software-Pakete, werden diese Proxies benutzen, wenn Sie Dateien oder Programme herunterladen. proxy_desc2=Wenn Webmin eine Datei von einer SourceForge-URL herunterlädt, dann wird wenn möglich automatisch der Mirror genutzt, den Sie hier angeben. proxy_ebind=Fehlende oder ungültige Quell-IP-Adresse @@ -320,6 +329,7 @@ proxy_header2=Download Sites proxy_http=HTTP-Proxy proxy_mirrordef=<Standard (UNC)> proxy_mirrordef2=Standard (UNC) +proxy_mods0=Alle Module proxy_nofor=Kein Proxy für proxy_none=Keiner proxy_osdn=Standard OSDN-Spiegel für Downloads @@ -407,9 +417,13 @@ ssl_key=Private Schlüssel-Datei ssl_newfile=Schlüssel in Datei sichern ssl_newkey=Dieses Formular kann dazu benutzt werden, um einen neuen SSL-Schlüssel für Ihren Webmin-Server zu erzeugen. ssl_on=SSL aktivieren, wenn verfügbar +ssl_pem=PEM-Format +ssl_pkcs12=PKCS12-Format ssl_redirect=Nicht-SSL-Anfragen auf SSL umleiten? ssl_return=SSL-Schlüssel ssl_size=RSA-Schlüsselgrösse +ssl_tabcurrent=Aktuelles Zertifikat +ssl_tabssl=SSL Einstellungen ssl_title=SSL-Verschlüsselung ssl_usenew=Neuen Schlüssel sofort benutzen? ssl_version=SSL-Protokollversion @@ -439,6 +453,7 @@ syslet_desc=Diese Seite ist zur Konfiguration des automatischen Herunterladens u syslet_ebase=Ungültige Basis-URL syslet_err=Syslet-Optionen speichern gescheitert syslet_title=Automatischer Syslet-Download +syslog_errorlog=Webmin Fehler-Log themes_change=Ändern themes_default=Standard Webmin-Theme themes_delete=Dieses Formular kann benutzt werden, um ein derzeit nicht benutztes Theme, welches auf Ihrem System installiert ist, zu löschen. diff --git a/webmin/lang/en b/webmin/lang/en index 770681395..9aa62f351 100644 --- a/webmin/lang/en +++ b/webmin/lang/en @@ -497,6 +497,7 @@ session_header=Authentication and session options session_pdisable=Disable password timeouts session_penable=Enable password timeouts session_blockhost=Block hosts with more than $1 failed logins for $2 seconds. +session_blockuser=Block users with more than $1 failed logins for $2 seconds. session_syslog2=Log blocked hosts, logins and authentication failures to syslog session_disable=Disable session authentication session_enable=Enable session authentication @@ -508,6 +509,8 @@ session_err=Failed to save authentication session_elogouttime=Missing or invalid logout time session_eblockhost_time=Missing or invalid blocking time session_eblockhost_failures=Missing or invalid blocking logins +session_eblockuser_time=Missing or invalid user blocking time +session_eblockuser_failures=Missing or invalid user blocking logins session_ecookie=Your browser does not support cookies, which are required for session authentication session_elsof=Local authentication requires the lsof program session_remember=Offer to remember login permanently? @@ -768,3 +771,14 @@ mobile_agents=Additional user agents for mobile browsers mobile_err=Failed to save mobile device options mobile_prefixes=URL hostname prefixes for mobile browsers +blocked_title=Blocked Hosts and Users +blocked_type=Type +blocked_who=Hostname or username +blocked_fails=Login failures +blocked_when=Blocked at +blocked_none=No hosts or users are currently blocked by Webmin. +blocked_user=Webmin user +blocked_host=Client host +blocked_clear=Clear All Blocks +blocked_cleardesc=Click this button to clear all current host and user blocks, by restarting the Webmin server process. +blocked_restarting=The Webmin server process is now restarting to clear blocked hosts and users - please wait for a few seconds before continuing. diff --git a/webmin/webmin-lib.pl b/webmin/webmin-lib.pl index 2ff62b218..0c1ad36f2 100644 --- a/webmin/webmin-lib.pl +++ b/webmin/webmin-lib.pl @@ -1128,11 +1128,15 @@ closedir(DIR); return @rv; } +# show_restart_page([title, msg]) sub show_restart_page { -&ui_print_header(undef, $text{'restart_title'}, ""); +local ($title, $msg) = @_; +$title ||= $text{'restart_title'}; +$msg ||= $text{'restart_done'}; +&ui_print_header(undef, $title, ""); -print " $text{'restart_done'} \n"; +print " $msg \n";
&ui_print_footer("", $text{'index_return'});
&restart_miniserv(1);
@@ -1208,6 +1212,28 @@ close(OUT);
return $data;
}
-
+# get_blocked_users_hosts(&miniserv)
+# Returns a list of blocked users and hosts from the file written by Webmin
+sub get_blocked_users_hosts
+{
+local ($miniserv) = @_;
+local $bf = $miniserv->{'blockedfile'};
+if (!$bf) {
+ $miniserv->{'pidfile'} =~ /^(.*)\/[^\/]+$/;
+ $bf = "$1/blocked";
+ }
+local @rv;
+&open_readfile(BLOCKED, $bf);
+while( |