diff -urN smeserver-manager-0.1.4.old/createlinks smeserver-manager-0.1.4/createlinks --- smeserver-manager-0.1.4.old/createlinks 2021-06-21 13:25:10.000000000 +0400 +++ smeserver-manager-0.1.4/createlinks 2022-07-18 14:14:26.458000000 +0400 @@ -47,3 +47,8 @@ event_link('systemd-default', "smeserver-manager-update", '88'); event_link('systemd-reload', "smeserver-manager-update", '89'); + +use esmith::Build::Backup qw(:all); +backup_includes("smeserver-manager", qw( +/usr/share/smanager/data +)); diff -urN smeserver-manager-0.1.4.old/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/27SManagerProxyPass smeserver-manager-0.1.4/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/27SManagerProxyPass --- smeserver-manager-0.1.4.old/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/27SManagerProxyPass 2022-07-17 20:31:12.000000000 +0400 +++ smeserver-manager-0.1.4/root/etc/e-smith/templates/etc/httpd/conf/httpd.conf/VirtualHosts/27SManagerProxyPass 2022-07-17 21:08:53.332000000 +0400 @@ -27,19 +27,17 @@ $OUT .= " RequestHeader set X-Forwarded-Proto 'http'\n"; $OUT .= " \n"; - $OUT .= " order deny,allow\n"; - $OUT .= " deny from all\n"; if ($port eq $plainPort) { - $OUT .= ' allow from 127.0.0.1' . "\n"; + $OUT .= ' Require ip 127.0.0.1' . "\n"; } elsif (($haveSSL eq 'yes') && ($port eq $sslPort) && ($adminAccess eq 'public')) { $OUT .= "# public access requested in conf db\n"; - $OUT .= " allow from all\n"; + $OUT .= " Require all granted\n"; } else { $OUT .= "# private access by default\n"; - $OUT .= " allow from $localAccess $externalSSLAccess\n"; + $OUT .= " Require ip $localAccess $externalSSLAccess\n"; } $OUT .= " \n"; } diff -urN smeserver-manager-0.1.4.old/root/etc/e-smith/templates/usr/share/smanager/conf/srvmngr.conf/25Pwdrst smeserver-manager-0.1.4/root/etc/e-smith/templates/usr/share/smanager/conf/srvmngr.conf/25Pwdrst --- smeserver-manager-0.1.4.old/root/etc/e-smith/templates/usr/share/smanager/conf/srvmngr.conf/25Pwdrst 1970-01-01 04:00:00.000000000 +0400 +++ smeserver-manager-0.1.4/root/etc/e-smith/templates/usr/share/smanager/conf/srvmngr.conf/25Pwdrst 2022-01-24 20:32:49.549000000 +0400 @@ -0,0 +1,4 @@ + # password reset disabled by default + pwdreset => { ($smanager{'PwdReset'} eq 'enabled' ? '1' : '0') || '0' }, + # reset delay in hours + pwdreset_delay => 2, diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/conf/admin_muttrc smeserver-manager-0.1.4/root/usr/share/smanager/conf/admin_muttrc --- smeserver-manager-0.1.4.old/root/usr/share/smanager/conf/admin_muttrc 1970-01-01 04:00:00.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/conf/admin_muttrc 2022-01-24 20:32:49.549000000 +0400 @@ -0,0 +1,5 @@ +set from = "admin" +set realname = "Administrator" +set record = "/usr/share/smanager/log/mail_sent" +##set content_type = "text/html" + diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/Controller/Login.pm smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/Controller/Login.pm --- smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/Controller/Login.pm 2021-06-21 13:25:10.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/Controller/Login.pm 2022-01-24 20:32:49.550000000 +0400 @@ -7,6 +7,8 @@ # for information # $r->get('/login')->to('login#main')->name('login'); # $r->post('/login')->to('login#login')->name('signin'); +# $r->get('/login2')->to('login#pwdrescue')->name('pwdresc'); +# $r->get('/loginc')->to('login#confpwd')->name('resetpwdconf'); # for information use strict; @@ -47,6 +49,18 @@ my $trt = $c->param('Trt'); + # password reset request + if ( $trt eq 'RESET' ) { + my $res = $c->mail_rescue(); + if ( $res ne 'OK' ) { + $c->stash( error => $res, trt => $trt ); + return $c->render('login'); + } + $c->flash( success => $c->l('use_RESET_REGISTERED') ); + record_login_attempt($c, 'RESET'); + return $c->redirect_to( $c->home_page ); + } + # normal loggin my $name = $c->param('Username'); my $pass = $c->param('Password'); @@ -64,7 +78,6 @@ return $c->render('login'); } - my $alias = SrvMngr::Model::Main->check_adminalias( $c ); if ( $alias ) { if ( $name eq $alias ) { @@ -79,13 +92,14 @@ if (SrvMngr::Model::Main->check_credentials($name, $pass)) { $c->session(logged_in => 1); # set the logged_in flag $c->session(username => $name); # keep a copy of the username -# if ( $name eq 'admin' || $adb->is_user_in_group($name, 'AdmiN') ) { # for futur use +# if ( $name eq 'admin' || $adb->is_user_in_group($name, 'AdmiN') ) # for futur use if ( $name eq 'admin' ) { $c->session(is_admin => 1); } else { $c->session(is_admin => 0); } $c->session(expiration => 600); # expire this session in 10 minutes + $c->flash( success => $c->l('use_WELCOME') ); record_login_attempt($c, 'SUCCESS'); } else { @@ -102,6 +116,68 @@ } +sub pwdrescue { + + my $c = shift; + + $c->stash( trt => 'RESET' ); + + $c->render('login'); + +} + + +sub mail_rescue { + + my $c = shift; + my $name = $c->param('Username'); + my $from = $c->param('From'); + + my $res; + + $res .= $c->l('use_TOO_MANY_LOGIN') if ( is_denied($c) ); + +# untaint + if ( ! $res && $name !~ /^([a-z][\-\_\.a-z0-9]*)$/ ) { + record_login_attempt($c, 'FAILED'); + $res .= $c->l('use_ERR_NAME'); + } + + if ( ! $res && $name eq 'admin' ) { + $res .= $c->l('use_NOT_THAT_OPER'); + } + +# user exists ? + if ( ! $res ) { + my $acct = $adb->get($name); + if ( ! $acct || $acct->prop('type') ne "user" || $acct->prop('PasswordSet') ne 'yes' ) { + $res .= $c->l('use_NOT_THAT_OPER'); + } + } + + return $res if $res; + +# send email + my $email = $name .'@'. $c->session->{DomainName}; + my $until = time() + $RESET_DURATION; + + $c->pwdrst->{$name} = { + email => $email, + date => $until, + confirmed => 0, + }; + my $jwt = $c->jwt->claims({username => $name})->encode; + my $url = $c->url_for('loginc')->to_abs->query(jwt => $jwt); + +# $c->email( $email, $c->l('use_CONFIRM_RESET'), $c->render_to_string(inline => $c->l('use_GO_TO_URL', $url) ) ); +# directly (without minion) + $c->send_email( $email, $c->l('use_CONFIRM_RESET'), $c->render_to_string(inline => $c->l('use_GO_TO_URL', $url) ) ); + + return 'OK'; + +} + + sub logout { my $c = shift; @@ -109,19 +185,52 @@ $c->session( expires => 1 ); $c->flash( success => $c->l('use_BYE') ); + $c->flash( error => 'Byegood' ); $c->redirect_to( $c->home_page ); } +sub confpwd { + + my $c = shift; + + my $jwt = $c->param('jwt'); + my $name = $c->jwt->decode($jwt)->{username}; + + # request already treated or outdated + if ( $c->pwdrst->{$name}{confirmed} != 0 or $c->pwdrst->{$name}{date} < time() ) { + $c->flash( error => $c->l('use_INVALID_REQUEST')); + return $c->redirect_to( $c->home_page ); + } + + # reset password for this account + $c->pwdrst->{$name}{confirmed} = 1; + + $c->flash( success => $c->l('use_OK_FOR_RESET') ); + + # call userpassword with encoded name + my $url = $c->url_for('userpasswordr')->to_abs->query(jwt => $jwt); + # warn "confpwd: " . $url . "\n"; + + return $c->redirect_to( $url ); + +} + + sub record_login_attempt { + my ($c, $result) = @_; my $user = $c->param('Username'); my $ip_address = $c->tx->remote_address; - if ($result eq 'SUCCESS') { + if ($result eq 'RESET') { + + $c->app->log->info(join "\t", "Password reset requested for : $user at ", $ip_address); + + } elsif ($result eq 'SUCCESS') { $c->app->log->info(join "\t", "Login succeeded: $user", $ip_address); $Login_Attempts{$ip_address}->{tries} = 0; # reset the number of login attempts diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/Controller/Userpassword.pm smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/Controller/Userpassword.pm --- smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/Controller/Userpassword.pm 2020-11-19 11:53:26.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/Controller/Userpassword.pm 2022-07-11 23:14:53.574000000 +0400 @@ -33,8 +33,29 @@ $pwd_datas{Account} = $c->session->{username}; $pwd_datas{trt} = 'NORM'; } else { - $c->stash( error => 'Invalid state' ); - return $c->redirect_to ( $c->home_page ); + my $rt = $c->current_route; + my $mess = ''; + my $jwt = $c->param('jwt') || ''; + my $name = $c->jwt->decode($jwt)->{username} || ''; + + $mess = 'Invalid state' unless ($jwt and $name and $rt eq 'upwdreset'); + + # request already treated or outdated + if ( $c->pwdrst->{$name}{confirmed} != 1 or $c->pwdrst->{$name}{date} < time() ) { + $mess = $c->l('use_INVALID_REQUEST').' -step 1-'; + } + + if ( $mess ) { + $c->stash( error => $mess ); + return $c->redirect_to ( $c->home_page ); + } + + # ok for reset password for this account - step 2 + $c->pwdrst->{$name}{confirmed} = 2; + $pwd_datas{Account} = $name; + $pwd_datas{trt} = 'RESET'; + $pwd_datas{jwt} = $jwt; + $c->flash( success => $c->l('use_OK_FOR_RESET') ); } $c->stash( pwd_datas => \%pwd_datas ); @@ -55,6 +76,31 @@ my $pass = $c->param('Pass'); my $passVerify = $c->param('Passverify'); + my $jwt = $c->param('jwt') || ''; + my $rt = $c->current_route; + my $mess = ''; my $name = ''; + $name = $c->jwt->decode($jwt)->{username} if $jwt; + + if ( $trt eq 'RESET' ) { + $mess = 'Invalid state' unless ($jwt and $name and ($rt eq 'upwdreset2')); + # request already treated or outdated + if ( $c->pwdrst->{$name}{confirmed} != 2 or $c->pwdrst->{$name}{date} < time() ) { + $mess = $c->l('use_INVALID_REQUEST').' -step 2-'; + } + if ( ! $name or $c->is_logged_in or $name ne $acctName ) { + $mess = 'Invalid reset state'; + } + } else { + if ( $name or $jwt or ! $c->is_logged_in ) { + $mess = 'Invalid update state'; + } + } + + if ( $mess ) { + $c->stash( error => $mess ); + return $c->redirect_to ( $c->home_page ); + } + $pwd_datas{Account} = $acctName; $pwd_datas{trt} = $trt; @@ -79,8 +125,10 @@ $res = $c->check_password( $pass ); $result .= $res . "
" unless ( $res eq 'OK' ); + # controls old password + if ( $trt ne 'RESET' ) { unless ( $oldPass ) { - $result .= $c->l('pwd_FIELDS_REQUIRED') . "
"; + $result .= $c->l('pwd_FIELDS_REQUIRED') . "
" unless $trt eq 'RESET'; } else { $result .= $c->l('pwd_PASSWORD_OLD_INVALID_CHARS') . "
" unless (($oldPass) = ($oldPass =~ /^(\S+)$/ )); } @@ -91,8 +139,11 @@ } # verify old password - $result .= $c->l('pwd_ERROR_PASSWORD_CHANGE') . "
" - unless (SrvMngr::Model::Main->check_credentials($acctName, $oldPass)); + if ( $trt ne 'RESET') { + $result .= $c->l('pwd_ERROR_PASSWORD_CHANGE') . "
" + unless (SrvMngr::Model::Main->check_credentials($acctName, $oldPass)); + } + } # $result .= 'Blocked for test (prevents updates)
'; @@ -107,6 +158,7 @@ return $c->render( 'userpassword' ); } + $c->pwdrst->{$name}{confirmed} = 9 if $trt eq 'RESET'; record_password_change_attempt($c, 'SUCCESS'); $result .= $c->l('pwd_PASSWORD_CHANGE_SUCCESS'); $c->flash( success => $result ); @@ -127,7 +179,8 @@ my $acct = $adb->get($user); return $c->l('NO_SUCH_USER', $user) unless ( $acct->prop('type') eq 'user' ); - $ret = esmith::util::setUserPasswordRequirePrevious( $user, $oldpassword, $password ); + $ret = esmith::util::setUserPasswordRequirePrevious( $user, $oldpassword, $password ) if $trt ne 'RESET'; + $ret = esmith::util::setUserPassword( $user, $password ) if $trt eq 'RESET'; return $c->l('pwd_ERROR_PASSWORD_CHANGE') .' '. $trt unless $ret; diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Login/login_en.lex smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Login/login_en.lex --- smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Login/login_en.lex 2020-11-19 11:53:26.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr/I18N/Modules/Login/login_en.lex 2022-01-24 20:32:49.551000000 +0400 @@ -20,3 +20,4 @@ use_DESC_RESET => 'Please enter an account name for a password reset !', use_RESET => 'Reset Password', use_OK_FOR_RESET => 'You are about to reset your user account password', +use_INVALID_REQUEST => 'Error: your request is invalid or outdated', diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr.pm smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr.pm --- smeserver-manager-0.1.4.old/root/usr/share/smanager/lib/SrvMngr.pm 2022-07-17 20:31:12.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/lib/SrvMngr.pm 2022-07-18 13:53:55.920000000 +0400 @@ -15,6 +15,9 @@ use Mojo::File qw( path ); use Mojo::Home; +use DBM::Deep; +use Mojo::JWT; + use Mojolicious::Plugin::Config; #use Mojolicious::Plugin::I18N; @@ -23,7 +26,7 @@ use SrvMngr::Model::Main; -our $VERSION = '1.411'; +our $VERSION = '1.417'; $VERSION = eval $VERSION; use Exporter 'import'; @@ -168,6 +171,30 @@ $self->plugin( Config => { file => $self->config_file()} ); + $self->helper( send_email => sub { + my ($c, $address, $subject, $body) = @_; + + if (not defined $body) { + warn "send_email: Need 3 parameters (Address, Subject, Body)\n"; + return; + } + + my $rcfile = $c->app->conf_dir().'/admin_muttrc'; + + #warn "send_email: $rcfile * $address\n"; #$rcfile $subject $address\n"; + system( "/bin/echo \"$body\" | /usr/bin/mutt -F $rcfile -s \"$subject\" \"$address\"" ) == 0 + or warn "error sendmail: $address \n"; # $subject"; + }); + + $self->helper( pwdrst => sub { + my $c = shift; + my $file = $c->app->data_dir().'/pwdrst.db'; + state $db = DBM::Deep->new($file); + }); + + $self->helper( jwt => sub { + Mojo::JWT->new(secret => shift->app->secrets->[0] || die) + }); } @@ -222,6 +249,14 @@ $r->get('/manual')->to('manual#main')->name('manual'); $r->get('/support')->to('support#main')->name('support'); + # Password reset allowed for this server + if ( ( $self->config->{pwdreset} || '0') == 1 ) { + $r->get('/login2')->to('login#pwdrescue')->name('pwdresc'); + $r->get('/loginc')->to('login#confpwd')->name('resetpwdconf'); + $r->get('/userpasswordr')->to('userpassword#main')->name('upwdreset'); + $r->post('/userpasswordr')->to('userpassword#change_password')->name('upwdreset2'); + } + my $if_logged_in = $r->under( sub { my $c =shift; return $c->is_logged_in || $c->auth_fail($c->l("acs_LOGIN")); diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/t/001_load.t smeserver-manager-0.1.4/root/usr/share/smanager/t/001_load.t --- smeserver-manager-0.1.4.old/root/usr/share/smanager/t/001_load.t 2021-06-21 13:25:11.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/t/001_load.t 2022-01-24 20:32:49.551000000 +0400 @@ -2,7 +2,7 @@ plan skip_all => 'unset QUICK_TEST to enable this test' if $ENV{QUICK_TEST}; -plan tests => 6; +plan tests => 8; use FindBin; use lib "$FindBin::Bin/../lib"; @@ -15,3 +15,5 @@ use_ok('Mojolicious::Plugin::RenderFile'); use_ok('Mojolicious::Plugin::CSRFDefender'); use_ok('Net::Netmask'); +use_ok('DBM::Deep'); +use_ok('Mojo::JWT'); diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/login.html.ep smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/login.html.ep --- smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/login.html.ep 2022-07-17 20:31:12.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/login.html.ep 2022-01-24 20:32:49.552000000 +0400 @@ -4,7 +4,7 @@
- %if ($config->{debug} == 1) { + %if ( config 'debug' ) {

%= dumper $c->current_route %if ( stash 'trt' ) { @@ -19,7 +19,13 @@

%} - % my $btn = l('use_SIGNIN'); +% my $btn = l('use_SIGNIN'); +% if ( $trt eq 'RESET' ) { +

+ %= $c->render_to_string(inline => l 'use_DESC_RESET') +

+ % $btn = l('use_RESET'); +%}

%=l 'use_TITLE' @@ -33,6 +39,7 @@ %= text_field 'Username'

+% if ( $trt ne 'RESET' ) {

%=l 'PASSWORD' @@ -41,17 +48,22 @@ Visible % }

+%} %= hidden_field 'From' => $c->tx->req->url - %= hidden_field 'Trt' => stash 'trt' + %= hidden_field 'Trt' => $trt
%= submit_button "$btn", class => 'action'
+ %if ( config 'pwdreset' ) { +
+ %=l 'use_FORGOT' +
+ %} % end %end - diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/partials/_header.html.ep smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/partials/_header.html.ep --- smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/partials/_header.html.ep 2022-07-17 20:31:12.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/partials/_header.html.ep 2022-07-18 18:16:57.639000000 +0400 @@ -3,8 +3,8 @@
SME Server
-
Server Manager II -     (Previous)
+
diff -urN smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/userpassword.html.ep smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/userpassword.html.ep --- smeserver-manager-0.1.4.old/root/usr/share/smanager/themes/default/templates/userpassword.html.ep 2022-07-17 20:31:12.000000000 +0400 +++ smeserver-manager-0.1.4/root/usr/share/smanager/themes/default/templates/userpassword.html.ep 2022-01-24 20:32:49.552000000 +0400 @@ -21,52 +21,50 @@ %} - % my $btn = l('pwd_PASSWORD_CHANGE'); -

-% my $url = '/userpassword'; + % my $btn = l('pwd_PASSWORD_CHANGE'); + % my $url = '/userpassword'; %= $c->render_to_string( inline => l('pwd_DESCRIPTION')); + % if ( $pwd_datas->{trt} eq 'RESET' ) { + % $btn = l('pwd_PASSWORD_RESET'); + % $url = '/userpasswordr'; + %= $c->render_to_string( inline => l('pwd_DESCRIPTION_RESET')); + % }

%= form_for $url => (method => 'POST') => begin - -

- +

%= l 'pwd_YOUR_ACCOUNT' %= $pwd_datas->{Account} %= hidden_field 'User' => $pwd_datas->{Account} %= hidden_field 'Trt' => $pwd_datas->{trt} -

-

+ %= hidden_field 'jwt' => $pwd_datas->{jwt} +

-

- + % if ( $pwd_datas->{trt} ne 'RESET' ) { +

%= l 'pwd_PASSWORD_OLD' %= password_field 'Oldpass', class => 'input' -

-

- -

- +

+ % } + +

%=l 'pwd_PASSWORD_NEW' %= password_field 'Pass', class => 'input' -

-

- -

- +

+ +

%=l 'pwd_PASSWORD_VERIFY_NEW' %= password_field 'Passverify', class => 'input' -

-

+

%= submit_button "$btn", class => 'action'