diff --git a/acl/acl-lib.pl b/acl/acl-lib.pl index fd7cfde92..006c83a8e 100755 --- a/acl/acl-lib.pl +++ b/acl/acl-lib.pl @@ -73,6 +73,8 @@ while() { $user{'minsize'} = $user[8]; $user{'nochange'} = int($user[9]); $user{'temppass'} = int($user[10]); + $user{'twofactor_provider'} = $user[11]; + $user{'twofactor_id'} = $user[12]; $user{'modules'} = $acl{$user[0]}; $user{'lang'} = $gconfig{"lang_$user[0]"}; $user{'notabs'} = $gconfig{"notabs_$user[0]"}; @@ -460,7 +462,9 @@ else { join(" ", @{$user{'olds'}}),":", $user{'minsize'},":", $user{'nochange'},":", - $user{'temppass'}, + $user{'temppass'},":", + $user{'twofactor_provider'},":", + $user{'twofactor_id'}, "\n"); &close_tempfile(PWFILE); &unlock_file($miniserv{'userfile'}); @@ -646,7 +650,9 @@ else { join(" ", @{$user{'olds'}}),":", $user{'minsize'},":", $user{'nochange'},":", - $user{'temppass'}, + $user{'temppass'},":", + $user{'twofactor_provider'},":", + $user{'twofactor_id'}, "\n"); } else { diff --git a/acl/edit_user.cgi b/acl/edit_user.cgi index 7bac2c5ea..7ee2f0639 100755 --- a/acl/edit_user.cgi +++ b/acl/edit_user.cgi @@ -165,6 +165,15 @@ if ($access{'chcert'}) { &ui_opt_textbox("cert", $user{'cert'}, 50, $text{'edit_none'})); } +# Two-factor details +if ($user{'twofactor_provider'}) { + ($prov) = grep { $_->[0] eq $user{'twofactor_provider'} } + &webmin::list_twofactor_providers(); + print &ui_table_row($text{'edit_twofactor'}, + &text('edit_twofactorprov', "$prov->[1]", + "$user{'twofactor_id'}")); + } + if ($access{'lang'}) { # Current language print &ui_table_row($text{'edit_lang'}, diff --git a/acl/images/twofactor.gif b/acl/images/twofactor.gif new file mode 100755 index 000000000..fdfcd7a3b Binary files /dev/null and b/acl/images/twofactor.gif differ diff --git a/acl/index.cgi b/acl/index.cgi index 9fe39a27d..0eaf847fe 100755 --- a/acl/index.cgi +++ b/acl/index.cgi @@ -188,6 +188,11 @@ if (uc($ENV{'HTTPS'}) eq "ON" && $miniserv{'ca'}) { push(@links, "cert_form.cgi"); push(@titles, $text{'index_cert'}); } +if ($miniserv{'twofactor_provider'}) { + push(@icons, "images/twofactor.gif"); + push(@links, "twofactor_form.cgi"); + push(@titles, $text{'index_twofactor'}); + } if ($access{'rbacenable'}) { push(@icons, "images/rbac.gif"); push(@links, "edit_rbac.cgi"); diff --git a/acl/lang/en b/acl/lang/en index 6d786a68f..a0194c54a 100644 --- a/acl/lang/en +++ b/acl/lang/en @@ -4,7 +4,8 @@ index_modules=Modules index_create=Create a new Webmin user. index_rcreate=Create a new risk-level user. index_convert=Convert Unix To Webmin Users -index_cert=Request an SSL certificate +index_cert=Request an SSL Sertificate +index_twofactor=Two-Factor Authentication index_certmsg=Click this button to request an SSL certificate that will allow you to securely login to Webmin without having to enter a username and password. index_return=user list index_none=None @@ -48,6 +49,8 @@ edit_passold=Password was last changed $1 days ago edit_passtoday=Password was changed less than a day ago edit_modules=Modules edit_clone=Clone +edit_twofactor=Two-factor authentication type +edit_twofactorprov=Using provider $1 with ID $2 edit_lang=Language edit_notabs=Categorise modules? edit_logout=Inactivity logout time @@ -449,4 +452,14 @@ makedn_still=Some problems were found even after DN creation : $1 schema_title=Download LDAP Schema schema_desc=Before Webmin can use an LDAP server to store users and groups it must be configured to use the schema below. This can typically be done by saving the schema definition in /etc/ldap/schema or /etc/openldap/schema as webmin.schema, then configuring the server to load that schema file. schema_download=Download schema file : $1 + +twofactor_err=Failed to setup two-factor authentication +twofactor_euser=Your Webmin user was not found! +twofactor_title=Two-Factor Authentication +twofactor_disable=Disable Two-Factor Authentication +twofactor_already=Your Webmin login already has two-factor authentication enabled with provider $1 and account ID $2. +twofactor_desc=This page allows you to enable two-factor authentication for your Webmin login using $1. Once active, an additional authentication token will be required when logging into Webmin. +twofactor_enable=Enroll For Two-Factor Authentication +twofactor_header=Two-factor authentication enrollment details + __norefs=1 diff --git a/acl/twofactor_form.cgi b/acl/twofactor_form.cgi new file mode 100644 index 000000000..05c009083 --- /dev/null +++ b/acl/twofactor_form.cgi @@ -0,0 +1,35 @@ +#!/usr/local/bin/perl +# Show a form for enabling two-factor authentication + +require './acl-lib.pl'; +&foreign_require("webmin"); +&error_setup($text{'twofactor_err'}); +&get_miniserv_config(\%miniserv); + +# Get the user +($user) = grep { $_->{'name'} eq $base_remote_user } &list_users(); +$user || &error($twxt{'twofactor_euser'}); + +&ui_print_header(undef, $text{'twofactor_title'}, ""); + +print &ui_form_start("save_twofactor.cgi", "post"); +if ($user->{'twofactor_provider'}) { + @buts = ( [ "disable", $text{'twofactor_disable'} ] ); + ($prov) = grep { $_->[0] eq $user->{'twofactor_provider'} } + &webmin::list_twofactor_providers(); + print &text('twofactor_already', "$prov->[1]", + "$user->{'twofactor_id'}"),"

\n"; + } +else { + ($prov) = grep { $_->[0] eq $miniserv{'twofactor_provider'} } + &webmin::list_twofactor_providers(); + print &text('twofactor_desc', $prov->[1], $prov->[2]),"

\n"; + print &ui_table_start($text{'twofactor_header'}, undef, 2); + $ffunc = "webmin::show_twofactor_form_".$miniserv{'twofactor_provider'}; + print &$ffunc($user); + print &ui_table_end(); + @buts = ( [ "enable", $text{'twofactor_enable'} ] ); + } +print &ui_form_end(\@buts); + +&ui_print_footer("", $text{'index_return'}); diff --git a/webmin/edit_twofactor.cgi b/webmin/edit_twofactor.cgi index 80de2e4e1..27f605bd5 100644 --- a/webmin/edit_twofactor.cgi +++ b/webmin/edit_twofactor.cgi @@ -12,14 +12,14 @@ print ui_table_start($text{'twofactor_header'}, undef, 2); # Two-factor provider print ui_table_row($text{'twofactor_provider'}, - ui_select("provider", $miniserv{'twofactor_provider'}, + ui_select("twofactor_provider", $miniserv{'twofactor_provider'}, [ [ "", "<".$text{'twofactor_none'}.">" ], map { [ $_->[0], $_->[1]." - ".$_->[2] ] } &list_twofactor_providers() ])); # API key print ui_table_row($text{'twofactor_apikey'}, - ui_textbox("apikey", $miniserv{'twofactor_apikey'}, 40)); + ui_textbox("twofactor_apikey", $miniserv{'twofactor_apikey'}, 40)); print ui_table_end(); print ui_form_end([ [ "save", $text{'save'} ] ]); diff --git a/webmin/lang/en b/webmin/lang/en index 16b9e263f..9544cb2b9 100644 --- a/webmin/lang/en +++ b/webmin/lang/en @@ -630,6 +630,7 @@ log_lang=Changed global language log_startpage=Changed index page options log_upgrade=Upgraded Webmin to version $1 log_session=Changed authentication options +log_twofactor=Changed two-factor authentication options log_ssl=Changed SSL encryption mode log_newkey=Created new SSL key log_newcsr=Created new SSL CSR @@ -1030,5 +1031,11 @@ twofactor_err=Failed to save two-factor authentication twofactor_eprovider=Invalid provider! twofactor_eapikey=Missing or invalid-looking API key twofactor_authy=Authy +twofactor_email=Your email address +twofactor_country=Cellphone country code +twofactor_phone=Cellphone phone number +twofactor_eemail=Missing or invalid email address - must be formatted like user@domain.com +twofactor_ecountry=Missing or invalid country code - must be a number, like 65 +twofactor_ephone=Missing or invalid phone number - only digits, dashes and spaces are allowed __norefs=1 diff --git a/webmin/webmin-lib.pl b/webmin/webmin-lib.pl index a5785a155..d15cccba7 100755 --- a/webmin/webmin-lib.pl +++ b/webmin/webmin-lib.pl @@ -2163,10 +2163,51 @@ if ($tryerror) { } } +# list_twofactor_providers() +# Returns a list of all supported providers, each of which is an array ref +# containing an ID, name and URL for more info sub list_twofactor_providers { return ( [ 'authy', $text{'twofactor_authy'}, 'http://www.authy.com/' ] ); } +# show_twofactor_form_authy(&webmin-user) +# Returns HTML for a form for enrolling for Authy two-factor +sub show_twofactor_form_authy +{ +my ($user) = @_; +my $rv; +$rv .= &ui_table_row($text{'twofactor_email'}, + &ui_textbox("email", undef, 40)); +$rv .= &ui_table_row($text{'twofactor_country'}, + &ui_textbox("country", undef, 3)); +$rv .= &ui_table_row($text{'twofactor_phone'}, + &ui_textbox("phone", undef, 20)); +return $rv; +} + +# parse_twofactor_form_authy(&in, &user) +# Parses inputs from show_twofactor_form_authy, and returns a hash ref with enrollment +# details on success, or an error message on failure. +sub parse_twofactor_form_authy +{ +my ($in, $user) = @_; +$in->{'email'} =~ /^\S+\@\S+$/ || return $text{'twofactor_eemail'}; +$in->{'country'} =~ s/^\+//; +$in->{'country'} =~ /^\d{1,3}$/ || return $text{'twofactor_ecountry'}; +$in->{'phone'} =~ /^[0-9\- ]+$/ || return $text{'twofactor_ephone'}; +return { 'email' => $in->{'email'}, + 'country' => $in->{'country'}, + 'phone' => $in->{'phone'} }; +} + +# enroll_twofactor_authy(&details, &user) +# Attempts to enroll a user for Authy two-factor. Returns undef on success and sets +# twofactor_id in &user, or an error message on failure. +sub enroll_twofactor_authy +{ +my ($details, $user) = @_; +} + 1;