Linux mail.vriendennsm.nl 6.1.0-51-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.177-1 (2026-07-16) x86_64
Apache/2.4.68 (Debian)
Server IP : 217.154.75.17 & Your IP : 216.73.216.195
Domains :
Cant Read [ /etc/named.conf ]
User : www-data
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
usr /
share /
webmin /
webmin /
Delete
Unzip
Name
Size
Permission
Date
Action
help
[ DIR ]
drwxr-xr-x
2026-07-25 20:55
images
[ DIR ]
drwxr-xr-x
2026-07-25 20:55
lang
[ DIR ]
drwxr-xr-x
2026-07-25 20:55
subdir
[ DIR ]
drwxr-xr-x
2026-07-25 20:55
CHANGELOG
12.02
KB
-rw-r--r--
2026-07-17 21:17
acme_tiny.py
11.53
KB
-rwxr-xr-x
2026-07-17 21:17
adminupgrade
299
B
-rw-r--r--
2026-07-17 21:17
backup_config.pl
1.97
KB
-rwxr-xr-x
2026-07-17 21:17
bootup.cgi
1.04
KB
-rwxr-xr-x
2026-07-17 21:22
cache.cgi
1.47
KB
-rwxr-xr-x
2026-07-17 21:22
cgi_args.pl
159
B
-rwxr-xr-x
2026-07-17 21:17
change_access.cgi
1.93
KB
-rwxr-xr-x
2026-07-17 21:22
change_advanced.cgi
7.02
KB
-rwxr-xr-x
2026-07-17 21:22
change_anon.cgi
712
B
-rwxr-xr-x
2026-07-17 21:22
change_bind.cgi
5.08
KB
-rwxr-xr-x
2026-07-17 21:22
change_ca.cgi
674
B
-rwxr-xr-x
2026-07-17 21:22
change_debug.cgi
1.38
KB
-rwxr-xr-x
2026-07-17 21:22
change_lang.cgi
558
B
-rwxr-xr-x
2026-07-17 21:22
change_lock.cgi
554
B
-rwxr-xr-x
2026-07-17 21:22
change_log.cgi
2.42
KB
-rwxr-xr-x
2026-07-17 21:22
change_mobile.cgi
940
B
-rwxr-xr-x
2026-07-17 21:22
change_os.cgi
2.21
KB
-rwxr-xr-x
2026-07-17 21:22
change_osdn.cgi
1.54
KB
-rwxr-xr-x
2026-07-17 21:22
change_overlay.cgi
1.17
KB
-rwxr-xr-x
2026-07-17 21:22
change_proxy.cgi
1.09
KB
-rwxr-xr-x
2026-07-17 21:22
change_referers.cgi
633
B
-rwxr-xr-x
2026-07-17 21:22
change_session.cgi
5.88
KB
-rwxr-xr-x
2026-07-17 21:22
change_ssl.cgi
3.08
KB
-rwxr-xr-x
2026-07-17 21:22
change_startpage.cgi
687
B
-rwxr-xr-x
2026-07-17 21:22
change_status.cgi
1.08
KB
-rwxr-xr-x
2026-07-17 21:22
change_theme.cgi
1.17
KB
-rwxr-xr-x
2026-07-17 21:22
change_twofactor.cgi
1.48
KB
-rwxr-xr-x
2026-07-17 21:22
change_ui.cgi
1.61
KB
-rwxr-xr-x
2026-07-17 21:22
change_web.cgi
2.47
KB
-rwxr-xr-x
2026-07-17 21:22
clear_blocked.cgi
230
B
-rwxr-xr-x
2026-07-17 21:22
clear_cache.cgi
205
B
-rwxr-xr-x
2026-07-17 21:22
clone_mod.cgi
2.15
KB
-rwxr-xr-x
2026-07-17 21:22
config
155
B
-rw-r--r--
2026-07-17 21:17
config.info
940
B
-rw-r--r--
2026-07-17 21:17
config.info.ar
406
B
-rw-r--r--
2026-07-17 21:17
config.info.ca
388
B
-rw-r--r--
2026-07-17 21:17
config.info.cs
225
B
-rw-r--r--
2026-07-17 21:17
config.info.de
351
B
-rw-r--r--
2026-07-17 21:17
config.info.es
221
B
-rw-r--r--
2026-07-17 21:17
config.info.fa
293
B
-rw-r--r--
2026-07-17 21:17
config.info.fr
554
B
-rw-r--r--
2026-07-17 21:17
config.info.hr
0
B
-rw-r--r--
2026-07-17 21:17
config.info.hu
0
B
-rw-r--r--
2026-07-17 21:17
config.info.it
237
B
-rw-r--r--
2026-07-17 21:17
config.info.ja
514
B
-rw-r--r--
2026-07-17 21:17
config.info.ko
198
B
-rw-r--r--
2026-07-17 21:17
config.info.ms
278
B
-rw-r--r--
2026-07-17 21:17
config.info.nl
291
B
-rw-r--r--
2026-07-17 21:17
config.info.no
275
B
-rw-r--r--
2026-07-17 21:17
config.info.pl
276
B
-rw-r--r--
2026-07-17 21:17
config.info.pt_BR
291
B
-rw-r--r--
2026-07-17 21:17
config.info.ru
483
B
-rw-r--r--
2026-07-17 21:17
config.info.sk
124
B
-rw-r--r--
2026-07-17 21:17
config.info.sv
194
B
-rw-r--r--
2026-07-17 21:17
config.info.tr
147
B
-rw-r--r--
2026-07-17 21:17
cpan_modules.pl
229
B
-rwxr-xr-x
2026-07-17 21:17
defaultacl
17
B
-rw-r--r--
2026-07-17 21:17
delete_cache.cgi
471
B
-rwxr-xr-x
2026-07-17 21:22
delete_mod.cgi
2.24
KB
-rwxr-xr-x
2026-07-17 21:22
delete_webmincron.cgi
1.51
KB
-rwxr-xr-x
2026-07-17 21:22
developers-key.asc
3.07
KB
-rw-r--r--
2026-07-17 21:17
download_cert.cgi
532
B
-rwxr-xr-x
2026-07-17 21:22
edit_access.cgi
1.88
KB
-rwxr-xr-x
2026-07-17 21:22
edit_advanced.cgi
4.24
KB
-rwxr-xr-x
2026-07-17 21:22
edit_anon.cgi
812
B
-rwxr-xr-x
2026-07-17 21:22
edit_assignment.cgi
1.12
KB
-rwxr-xr-x
2026-07-17 21:22
edit_bind.cgi
3.54
KB
-rwxr-xr-x
2026-07-17 21:22
edit_blocked.cgi
944
B
-rwxr-xr-x
2026-07-17 21:22
edit_ca.cgi
2.82
KB
-rwxr-xr-x
2026-07-17 21:22
edit_categories.cgi
1.69
KB
-rwxr-xr-x
2026-07-17 21:22
edit_debug.cgi
2.05
KB
-rwxr-xr-x
2026-07-17 21:22
edit_descs.cgi
1.49
KB
-rwxr-xr-x
2026-07-17 21:22
edit_ipkey.cgi
1.7
KB
-rwxr-xr-x
2026-07-17 21:22
edit_lang.cgi
1.83
KB
-rwxr-xr-x
2026-07-17 21:22
edit_lock.cgi
2.13
KB
-rwxr-xr-x
2026-07-17 21:22
edit_log.cgi
3.16
KB
-rwxr-xr-x
2026-07-17 21:22
edit_mobile.cgi
1.26
KB
-rwxr-xr-x
2026-07-17 21:22
edit_mods.cgi
4.45
KB
-rwxr-xr-x
2026-07-17 21:22
edit_os.cgi
3.17
KB
-rwxr-xr-x
2026-07-17 21:22
edit_proxy.cgi
3.7
KB
-rwxr-xr-x
2026-07-17 21:22
edit_referers.cgi
899
B
-rwxr-xr-x
2026-07-17 21:22
edit_sendmail.cgi
3.68
KB
-rwxr-xr-x
2026-07-17 21:22
edit_session.cgi
6.23
KB
-rwxr-xr-x
2026-07-17 21:22
edit_ssl.cgi
13.69
KB
-rwxr-xr-x
2026-07-17 21:22
edit_startpage.cgi
1.48
KB
-rwxr-xr-x
2026-07-17 21:22
edit_status.cgi
1.26
KB
-rwxr-xr-x
2026-07-17 21:22
edit_themes.cgi
4.15
KB
-rwxr-xr-x
2026-07-17 21:22
edit_twofactor.cgi
1.5
KB
-rwxr-xr-x
2026-07-17 21:22
edit_ui.cgi
2.51
KB
-rwxr-xr-x
2026-07-17 21:22
edit_upgrade.cgi
4.39
KB
-rwxr-xr-x
2026-07-17 21:22
edit_web.cgi
2.92
KB
-rwxr-xr-x
2026-07-17 21:22
edit_webmincron.cgi
1.35
KB
-rwxr-xr-x
2026-07-17 21:22
export_mod.cgi
1.23
KB
-rwxr-xr-x
2026-07-17 21:22
feedback_files.pl
126
B
-rwxr-xr-x
2026-07-17 21:17
fix_os.cgi
228
B
-rwxr-xr-x
2026-07-17 21:22
fix_repo.cgi
1.87
KB
-rwxr-xr-x
2026-07-17 21:22
gnupg-lib.pl
14.82
KB
-rwxr-xr-x
2026-07-17 21:17
hide.cgi
326
B
-rwxr-xr-x
2026-07-17 21:22
index.cgi
4.41
KB
-rwxr-xr-x
2026-07-17 21:22
install_mod.cgi
3.11
KB
-rwxr-xr-x
2026-07-17 21:22
install_theme.cgi
2.29
KB
-rwxr-xr-x
2026-07-17 21:22
jcameron-key.asc
1.29
KB
-rw-r--r--
2026-07-17 21:17
kill_lock.cgi
1.57
KB
-rwxr-xr-x
2026-07-17 21:22
letsencrypt-cleanup.pl
1.94
KB
-rwxr-xr-x
2026-07-17 21:22
letsencrypt-dns.pl
2.49
KB
-rwxr-xr-x
2026-07-17 21:22
letsencrypt-lib.pl
19.62
KB
-rwxr-xr-x
2026-07-17 21:17
letsencrypt.cgi
8.25
KB
-rwxr-xr-x
2026-07-17 21:22
log_parser.pl
1.23
KB
-rwxr-xr-x
2026-07-17 21:17
module.info
195
B
-rw-r--r--
2026-07-17 21:17
module.info.ar
185
B
-rw-r--r--
2026-07-17 21:17
module.info.ar.auto
22
B
-rw-r--r--
2026-07-17 21:17
module.info.bg
0
B
-rw-r--r--
2026-07-17 21:17
module.info.bg.auto
218
B
-rw-r--r--
2026-07-17 21:17
module.info.ca
134
B
-rw-r--r--
2026-07-17 21:17
module.info.ca.auto
15
B
-rw-r--r--
2026-07-17 21:17
module.info.cs
28
B
-rw-r--r--
2026-07-17 21:17
module.info.cs.auto
128
B
-rw-r--r--
2026-07-17 21:17
module.info.da
0
B
-rw-r--r--
2026-07-17 21:17
module.info.da.auto
142
B
-rw-r--r--
2026-07-17 21:17
module.info.de
126
B
-rw-r--r--
2026-07-17 21:17
module.info.de.auto
15
B
-rw-r--r--
2026-07-17 21:17
module.info.el
0
B
-rw-r--r--
2026-07-17 21:17
module.info.el.auto
262
B
-rw-r--r--
2026-07-17 21:17
module.info.es
33
B
-rw-r--r--
2026-07-17 21:17
module.info.es.auto
109
B
-rw-r--r--
2026-07-17 21:17
module.info.eu
0
B
-rw-r--r--
2026-07-17 21:17
module.info.eu.auto
158
B
-rw-r--r--
2026-07-17 21:17
module.info.fa
0
B
-rw-r--r--
2026-07-17 21:17
module.info.fa.auto
202
B
-rw-r--r--
2026-07-17 21:17
module.info.fi
0
B
-rw-r--r--
2026-07-17 21:17
module.info.fi.auto
141
B
-rw-r--r--
2026-07-17 21:17
module.info.fr
32
B
-rw-r--r--
2026-07-17 21:17
module.info.fr.auto
129
B
-rw-r--r--
2026-07-17 21:17
module.info.hr
0
B
-rw-r--r--
2026-07-17 21:17
module.info.hr.auto
149
B
-rw-r--r--
2026-07-17 21:17
module.info.hu
30
B
-rw-r--r--
2026-07-17 21:17
module.info.hu.auto
148
B
-rw-r--r--
2026-07-17 21:17
module.info.it
33
B
-rw-r--r--
2026-07-17 21:17
module.info.it.auto
107
B
-rw-r--r--
2026-07-17 21:17
module.info.ja
180
B
-rw-r--r--
2026-07-17 21:17
module.info.ko
22
B
-rw-r--r--
2026-07-17 21:17
module.info.ko.auto
129
B
-rw-r--r--
2026-07-17 21:17
module.info.ms
119
B
-rw-r--r--
2026-07-17 21:17
module.info.ms.auto
15
B
-rw-r--r--
2026-07-17 21:17
module.info.nl
28
B
-rw-r--r--
2026-07-17 21:17
module.info.nl.auto
117
B
-rw-r--r--
2026-07-17 21:17
module.info.no
29
B
-rw-r--r--
2026-07-17 21:17
module.info.no.auto
117
B
-rw-r--r--
2026-07-17 21:17
module.info.pl
155
B
-rw-r--r--
2026-07-17 21:17
module.info.pl.auto
15
B
-rw-r--r--
2026-07-17 21:17
module.info.pt
33
B
-rw-r--r--
2026-07-17 21:17
module.info.pt.auto
113
B
-rw-r--r--
2026-07-17 21:17
module.info.pt_BR
36
B
-rw-r--r--
2026-07-17 21:17
module.info.pt_BR.auto
119
B
-rw-r--r--
2026-07-17 21:17
module.info.ru
34
B
-rw-r--r--
2026-07-17 21:17
module.info.ru.auto
172
B
-rw-r--r--
2026-07-17 21:17
module.info.sk
30
B
-rw-r--r--
2026-07-17 21:17
module.info.sk.auto
132
B
-rw-r--r--
2026-07-17 21:17
module.info.sv
30
B
-rw-r--r--
2026-07-17 21:17
module.info.sv.auto
114
B
-rw-r--r--
2026-07-17 21:17
module.info.tr
33
B
-rw-r--r--
2026-07-17 21:17
module.info.tr.auto
128
B
-rw-r--r--
2026-07-17 21:17
module.info.uk
0
B
-rw-r--r--
2026-07-17 21:17
module.info.uk.auto
215
B
-rw-r--r--
2026-07-17 21:17
module.info.zh
22
B
-rw-r--r--
2026-07-17 21:17
module.info.zh_TW
25
B
-rw-r--r--
2026-07-17 21:17
module.info.zh_TW.auto
115
B
-rw-r--r--
2026-07-17 21:17
newcsr.cgi
800
B
-rwxr-xr-x
2026-07-17 21:22
newkey.cgi
879
B
-rwxr-xr-x
2026-07-17 21:22
os-eol-lib.pl
9.55
KB
-rwxr-xr-x
2026-07-17 21:17
postinstall.pl
2.59
KB
-rwxr-xr-x
2026-07-17 21:17
refresh_modules.cgi
661
B
-rwxr-xr-x
2026-07-17 21:22
restart.cgi
87
B
-rwxr-xr-x
2026-07-17 21:22
save_assignment.cgi
485
B
-rwxr-xr-x
2026-07-17 21:22
save_categories.cgi
986
B
-rwxr-xr-x
2026-07-17 21:22
save_descs.cgi
1006
B
-rwxr-xr-x
2026-07-17 21:22
save_ipkey.cgi
1.31
KB
-rwxr-xr-x
2026-07-17 21:22
save_newmod.cgi
278
B
-rwxr-xr-x
2026-07-17 21:22
save_sendmail.cgi
2.32
KB
-rwxr-xr-x
2026-07-17 21:22
save_webmincron.cgi
1016
B
-rwxr-xr-x
2026-07-17 21:22
savekey.cgi
2.8
KB
-rwxr-xr-x
2026-07-17 21:22
setup_ca.cgi
1.52
KB
-rwxr-xr-x
2026-07-17 21:22
standard_chooser.cgi
1.68
KB
-rwxr-xr-x
2026-07-17 21:22
stop_ca.cgi
1.03
KB
-rwxr-xr-x
2026-07-17 21:22
syslog_logs.pl
633
B
-rwxr-xr-x
2026-07-17 21:17
system_info.pl
5.08
KB
-rw-r--r--
2026-07-17 21:17
test_sendmail.cgi
784
B
-rwxr-xr-x
2026-07-17 21:22
third_chooser.cgi
1.55
KB
-rwxr-xr-x
2026-07-17 21:22
twofactor-funcs-lib.pl
11.24
KB
-rw-r--r--
2026-07-17 21:17
uninstall.pl
236
B
-rwxr-xr-x
2026-07-17 21:17
update.cgi
2.89
KB
-rwxr-xr-x
2026-07-17 21:22
upgrade.cgi
16.95
KB
-rwxr-xr-x
2026-07-17 21:22
view_webmincron.cgi
1.66
KB
-rwxr-xr-x
2026-07-17 21:22
webmin-lib.pl
75.99
KB
-rwxr-xr-x
2026-07-17 21:17
Save
Rename
# Functions for two-factor enrollment and verification # 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 ( [ 'totp', $text{'twofactor_totp'}, 'https://en.wikipedia.org/wiki/Time-based_one-time_password' ], [ 'authy', 'Authy', 'http://www.authy.com/' ] ); } # show_twofactor_apikey_authy(&miniserv) # Returns HTML for the form for authy-specific provider inputs sub show_twofactor_apikey_authy { my ($miniserv) = @_; my $rv; $rv .= ui_table_row($text{'twofactor_apikey'}, ui_textbox("authy_apikey", $miniserv->{'twofactor_apikey'}, 40)); return $rv; } # validate_twofactor_apikey_authy(&in, &miniserv) # Validates inputs from show_twofactor_apikey_authy, and stores them. Returns # undef if OK, or an error message on failure sub validate_twofactor_apikey_authy { my ($in, $miniserv) = @_; my $key = $in->{'authy_apikey'}; my $test = $miniserv->{'twofactor_test'}; $key =~ /^\S+$/ || return $text{'twofactor_eapikey'}; my $host = $test ? "sandbox-api.authy.com" : "api.authy.com"; my $port = $test ? 80 : 443; my $page = "/protected/xml/app/details?api_key=".&urlize($key); my $ssl = $test ? 0 : 1; my ($out, $err); &http_download($host, $port, $page, \$out, \$err, undef, $ssl, undef, undef, 60, 0, 1); if ($err =~ /401/) { return $text{'twofactor_eauthykey'}; } elsif ($err) { return &text('twofactor_eauthy', $err); } $miniserv->{'twofactor_apikey'} = $key; return undef; } # 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) = @_; my %miniserv; &get_miniserv_config(\%miniserv); my $host = $miniserv{'twofactor_test'} ? "sandbox-api.authy.com" : "api.authy.com"; my $port = $miniserv{'twofactor_test'} ? 80 : 443; my $page = "/protected/xml/users/new?api_key=". &urlize($miniserv{'twofactor_apikey'}); my $ssl = $miniserv{'twofactor_test'} ? 0 : 1; my $content = "user[email]=".&urlize($details->{'email'})."&". "user[country_code]=".&urlize($details->{'country'})."&". "user[cellphone]=".&urlize($details->{'phone'}); my ($out, $err); &http_post($host, $port, $page, $content, \$out, \$err, undef, $ssl, undef, undef, 60, 0, 1); return $err if ($err); if ($out =~ /<id[^>]*>([^<]+)<\/id>/i) { $user->{'twofactor_id'} = $1; $user->{'twofactor_apikey'} = $miniserv{'twofactor_apikey'}; return undef; } else { return &text('twofactor_eauthyenroll', "<pre>".&html_escape($out)."</pre>"); } } # validate_twofactor_authy(id, token, apikey) # Checks the validity of some token for a user ID sub validate_twofactor_authy { my ($id, $token, $apikey) = @_; $id =~ /^\d+$/ || return $text{'twofactor_eauthyid'}; $token =~ /^\d+$/ || return $text{'twofactor_eauthytoken'}; my %miniserv; &get_miniserv_config(\%miniserv); my $host = $miniserv{'twofactor_test'} ? "sandbox-api.authy.com" : "api.authy.com"; my $port = $miniserv{'twofactor_test'} ? 80 : 443; my $page = "/protected/xml/verify/$token/$id?api_key=".&urlize($apikey). "&force=true"; my $ssl = $miniserv{'twofactor_test'} ? 0 : 1; my ($out, $err); &http_download($host, $port, $page, \$out, \$err, undef, $ssl, undef, undef, 60, 0, 1); if ($err && $err =~ /401/) { # Token rejected return $text{'twofactor_eauthyotp'}; } elsif ($err) { # Some other error return $err; } elsif ($out && $out =~ /<success[^>]*>([^<]+)<\/success>/i) { if (lc($1) eq "true") { # Worked! return undef; } elsif ($out =~ /<message[^>]*>([^<]+)<\/message>/i) { # Failed, but with a message return $1; } else { # Failed, not sure why return $out; } } else { # Unknown output return $out; } } # validate_twofactor_apikey_totp() # Checks that the needed Perl module for TOTP is installed. sub validate_twofactor_apikey_totp { return undef; } # show_twofactor_form_totp(&user) # Show form allowing the user to choose a twofactor secret sub show_twofactor_form_totp { my ($user) = @_; my $secret = $user->{'twofactor_id'}; $secret = undef if ($secret !~ /^[A-Z0-9=]+$/i || (length($secret) != 16 && length($secret) != 26 && length($secret) != 32)); my $rv; $rv .= &ui_table_row($text{'twofactor_secret'}, &ui_opt_textbox("totp_secret", $secret, 20, $text{'twofactor_secret1'}, $text{'twofactor_secret0'})); return $rv; } # parse_twofactor_form_totp(&in, &user) # Generate or use a secret key for this user sub parse_twofactor_form_totp { my ($in, $user) = @_; if ($in->{'totp_secret_def'}) { $user->{'twofactor_id'} = &encode_base32(&generate_base32_secret()); } else { $in{'totp_secret'} =~ /^[A-Z0-9=]{16}$/i || return $text{'twofactor_esecret'}; $user->{'twofactor_id'} = $in{'totp_secret'}; } return { }; } # generate_base32_secret([length]) # Returns a base-32 encoded secret of by default 10 bytes sub generate_base32_secret { my ($length) = @_; $length ||= 10; &seed_random(); my $secret = ""; while(length($secret) < $length) { $secret .= chr(rand()*256); } return $secret; } # enroll_twofactor_totp(&in, &user) # Generate a secret for this user, based-32 encoded sub enroll_twofactor_totp { my ($in, $user) = @_; $user->{'twofactor_id'} ||= &encode_base32(&generate_base32_secret()); return undef; } # message_twofactor_totp(&user) # Returns HTML to display after a user enrolls sub message_twofactor_totp { my ($user) = @_; my $name = &get_display_hostname()." (".$user->{'name'}.")"; my $str = "otpauth://totp/".$name."?secret=".$user->{'twofactor_id'}; my ($qrimg, $mime) = &generate_qr_code($str, 6); if ($qrimg) { my $qrcode = &ui_tag('p', &text('twofactor_qrcode', "<tt>$user->{'twofactor_id'}</tt>")); my $src = "data:$mime;base64,".&encode_base64($qrimg, 'noeol'); my $img = &ui_tag('img', undef, { 'src' => $src, 'border' => 0, 'style' => 'width:180px; height:180px; '. 'border:1px solid #444;', 'alt' => 'QR code' }); return <<EOF; $qrcode$img<p> EOF } else { return <<EOF; <p>@{[ &text('twofactor_qrcode_manual', "<tt>$user->{'twofactor_id'}</tt>") ]}</p> <p> EOF } } # validate_twofactor_totp(id, token) # Checks the validity of some token with TOPT sub validate_twofactor_totp { my ($id, $token) = @_; $id =~ /^[A-Z0-9=]+$/i || return $text{'twofactor_etotpid'}; $id = &decode_base32($id); $token =~ /^\d+$/ || return $text{'twofactor_etotptoken'}; eval "use lib (\"$root_directory/vendor_perl\")"; eval "use Digest::HMAC_SHA1 qw/ hmac_sha1 /;"; my $now = time(); my $totp = sub { my ($secret, $time) = @_; # Compute HMAC-SHA1 my $data = pack('H*', sprintf("%016x", int($time / 30))); my $packed_key = pack('H*', unpack("H*", $secret)); my $hmac = hmac_sha1($data, $packed_key); # Convert HMAC to hexadecimal my $hmac_hex = unpack("H*", $hmac); # Generate the TOTP my $offset = hex(substr($hmac_hex, -1)); my $part1 = hex(substr($hmac_hex, $offset * 2, 8)); my $part2 = hex("7fffffff"); return substr(($part1 & $part2), -6); }; foreach my $t ($now - 30, $now, $now + 30) { my $expected = $totp->($id, $t); return undef if ($expected eq $token); } return $text{'twofactor_etotpmatch'}; } # get_user_twofactor(username, &miniserv) # Returns the twofactor provider, ID and API key for a user sub get_user_twofactor { my ($user, $miniserv) = @_; return () if (!$miniserv->{'twofactorfile'}); my $lref = &read_file_lines($miniserv->{'twofactorfile'}, 1); foreach my $l (@$lref) { my @two = split(/:/, $l, -1); if ($two[0] eq $user) { return ($two[1], $two[2], $two[3]); } } return (); } # save_user_twofactor(username, &miniserv, [provider, id, api-key]) # Updates or removes the twofactor provider for a user sub save_user_twofactor { my ($user, $miniserv, $prov, $id, $key) = @_; return 0 if (!$miniserv->{'twofactorfile'}); &lock_file($miniserv->{'twofactorfile'}); my $lref = &read_file_lines($miniserv->{'twofactorfile'}); my $found = 0; my $i = 0; foreach my $l (@$lref) { my @two = split(/:/, $l, -1); if ($two[0] eq $user) { # Found the line to update or remove if ($prov) { $lref->[$i] = join(":", $user, $prov, $id, $key); } else { splice(@$lref, $i, 1); } $found++; last; } $i++; } if (!$found && $prov) { # Need to add the user push(@$lref, join(":", $user, $prov, $id, $key)); } &flush_file_lines($miniserv->{'twofactorfile'}); &unlock_file($miniserv->{'twofactorfile'}); } # can_generate_qr_encoder() # Returns 1 if the local QRCode::Encoder fallback can be used sub can_generate_qr_encoder { eval "use lib (\"$root_directory/vendor_perl\")"; eval "use QRCode::Encoder qw(qr_encode)"; return $@ ? 0 : 1; } # generate_qr_code(string, [block-size]) # Turn a string into a QR code image, and returns the data and MIME type sub generate_qr_code { my ($str, $size) = @_; if (&can_generate_qr_encoder()) { my @rv = eval { &generate_qr_code_encoder($str, $size) }; return @rv if (!$@ && defined($rv[0])); } if (&has_command("qrencode")) { # Use the qrencode shell command my $cmd = "qrencode -o - -t PNG ".quotemeta($str); $cmd .= " -s ".quotemeta($size) if ($size); my ($out, $err); my $ex = &execute_command($cmd, undef, \$out, \$err); if ($ex) { return (undef, $err); } return ($out, "image/png"); } return (undef, "QR code generation requires either the qrencode command or ". "QRCode::Encoder Perl module"); } # generate_qr_code_encoder(string, [block-size]) # Turn a string into a QR code SVG using the local QRCode::Encoder fallback sub generate_qr_code_encoder { my ($str, $size) = @_; eval "use lib (\"$root_directory/vendor_perl\")"; eval "use QRCode::Encoder qw(qr_encode)"; if ($@) { return (undef, "QR code generation requires the ". "QRCode::Encoder Perl module"); } my $encoded = qr_encode($str, level => 'M'); my $matrix = $encoded->{'matrix'}; my $mod_size = $size || 6; my $count = scalar(@$matrix); my $dim = $count * $mod_size; my $svg = qq{<svg xmlns="http://www.w3.org/2000/svg" }. qq{viewBox="0 0 $dim $dim" }. qq{width="$dim" height="$dim">}; $svg .= qq{<rect width="$dim" height="$dim" fill="white"/>}; for my $y (0 .. $#$matrix) { for my $x (0 .. $#{$matrix->[$y]}) { if ($matrix->[$y][$x] & 1) { my $px = $x * $mod_size; my $py = $y * $mod_size; $svg .= qq{<rect x="$px" y="$py" }. qq{width="$mod_size" }. qq{height="$mod_size"/>}; } } } $svg .= "</svg>"; return ($svg, "image/svg+xml"); } 1;