From 8ee2d7149baa58ea225cb40e0f95030ee21f1081 Mon Sep 17 00:00:00 2001 From: "Jannis M. Hoffmann" Date: Mon, 13 Mar 2023 21:34:03 +0100 Subject: Split up Helper plugin and added Views instead --- lib/JWebmail/Controller/Webmail.pm | 166 ++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 19 deletions(-) (limited to 'lib/JWebmail/Controller/Webmail.pm') diff --git a/lib/JWebmail/Controller/Webmail.pm b/lib/JWebmail/Controller/Webmail.pm index 8325050..acf7557 100644 --- a/lib/JWebmail/Controller/Webmail.pm +++ b/lib/JWebmail/Controller/Webmail.pm @@ -2,13 +2,20 @@ package JWebmail::Controller::Webmail; use Mojo::Base Mojolicious::Controller; +use Carp 'carp'; use List::Util 'first'; +use Mojo::Util qw(encode decode b64_encode b64_decode); use Mojolicious::Types; +use JWebmail::View::Webmail; +use JWebmail::View::RenderMail; + +use constant TRUE_RANDOM => eval { require Crypt::URandom; Crypt::URandom->import('urandom'); 1 }; + use constant { - S_USER => 'user', # Key for user name in active session - ST_AUTH => 'auth', + SES_USER => 'user', # Key for user name in active session + STS_AUTH => 'auth', }; @@ -16,7 +23,7 @@ use constant { sub noaction { my $self = shift; - my $user = $self->session(S_USER); + my $user = $self->session(SES_USER); if ($user) { $self->res->code(307); $self->redirect_to('home'); @@ -30,8 +37,8 @@ sub noaction { sub auth { my $self = shift; - my $user = $self->session(S_USER); - my ($pw, $ch) = $self->session_passwd(); + my $user = $self->session(SES_USER); + my ($pw, $ch) = $self->_session_passwd(); unless ($user && $pw) { $self->flash(message => $self->l('No active session.')); @@ -40,7 +47,7 @@ sub auth { return 0; } - $self->stash(ST_AUTH() => $self->users->Auth(user => $user, password => $pw, challenge => $ch)); + $self->stash(STS_AUTH() => $self->users->Auth(user => $user, password => $pw, challenge => $ch)); return 1; } @@ -86,8 +93,8 @@ sub login { my $valid = _time { $self->users->verify_user($auth) } $self, 'verify user'; if ($valid) { - $self->session(S_USER() => $user); - $self->session_passwd($passwd, $challenge); + $self->session(SES_USER() => $user); + $self->_session_passwd($passwd, $challenge); $self->res->code(303); $self->redirect_to('displayheaders'); @@ -104,8 +111,8 @@ sub login { sub logout { my $self = shift; - delete $self->session->{S_USER()}; - $self->session_passwd(''); + delete $self->session->{SES_USER()}; + $self->_session_passwd(''); # $self->session(expires => 1); @@ -130,7 +137,7 @@ sub displayheaders { no warnings 'experimental::smartmatch'; my $self = shift; - my $auth = $self->stash(ST_AUTH); + my $auth = $self->stash(STS_AUTH); my $folders = _time { $self->users->folders($auth) } $self, 'user folders'; @@ -178,6 +185,7 @@ sub displayheaders { $self->app->log->debug(sprintf("Reading user headers took %fs", $elapsed)); $self->stash( + v => JWebmail::View::Webmail->new, msgs => $headers, mail_folders => $folders, total_size => $count->{byte_size}, @@ -190,7 +198,7 @@ sub readmail { my $self = shift; my $mid = $self->stash('id'); - my $auth = $self->stash(ST_AUTH); + my $auth = $self->stash(STS_AUTH); my $mail; my $ok = eval { $mail = $self->users->show($auth, '', $mid); 1 }; @@ -203,7 +211,10 @@ sub readmail { die; } - $self->stash(msg => $mail); + $self->stash( + v => JWebmail::View::RenderMail->new(c => $self), + msg => $mail, + ); } @@ -211,7 +222,7 @@ sub raw { my $self = shift; my $mid = $self->stash('id'); - my $auth = $self->stash(ST_AUTH); + my $auth = $self->stash(STS_AUTH); # select a single body element my $v = $self->validation; @@ -221,9 +232,9 @@ sub raw { my $content = $self->users->raw($auth, '', $mid, $path); $self->res->headers->content_disposition(qq[attachment; filename="$content->{head}{filename}"]) - if lc $content->{head}{content_disposition} eq 'attachment'; - my $ct = $self->to_mime_type($content->{head}); - if ($ct eq 'text/plain') { $ct .= '; charset=UTF-8' } + if lc ($content->{head}{content_disposition}//'') eq 'attachment'; + my $ct = JWebmail::View::RenderMail::to_mime_type($content->{head}); + if ($ct =~ m'^text/') { $ct .= '; charset=UTF-8' } $self->res->headers->content_type($ct); $self->render(data => $content->{body}); } @@ -246,7 +257,7 @@ sub sendmail { bcc => scalar $v->optional('bcc', 'not_empty')->check('mail_line')->every_param, reply => scalar $v->optional('back_to', 'not_empty')->check('mail_line')->param, attach => scalar $v->optional('attach', 'non_empty_ul')->upload->param, - from => scalar $self->stash(ST_AUTH)->{user}, + from => scalar $self->stash(STS_AUTH)->{user}, ); $mail{attach_type} = Mojolicious::Types->new()->file_type($mail{attach}->filename) if $mail{attach}; @@ -287,7 +298,7 @@ sub move { return; } - my $auth = $self->stash(ST_AUTH); + my $auth = $self->stash(STS_AUTH); my $folders = $self->users->folders($auth); my $mm = $self->every_param('mail'); @@ -304,6 +315,85 @@ sub move { } +### session password handling + +use constant { S_PASSWD => 'pw', S_OTP_S3D_PW => 'otp_s3d_pw' }; + +sub _rand_data { + my $len = shift; + + if (TRUE_RANDOM) { + #return makerandom_octet(Length => $len, Strength => 0); # was used for Crypt::Random + return urandom($len); + } + else { + my $res = ''; + for (0..$len-1) { + vec($res, $_, 8) = int rand 256; + } + + return $res; + } +} + +sub _session_passwd { + my ($self, $passwd, $challenge) = @_; + my $secAlg = $self->config->{session}{secure}; + + $self->_warn_crypt; + + if (defined $passwd) { # set + if ($secAlg eq 'cram') { + $self->session(S_PASSWD() => $passwd, challenge => $challenge); + } + elsif ($secAlg eq 's3d') { + unless ($passwd) { + $self->s3d(S_PASSWD, ''); + delete $self->session->{S_OTP_S3D_PW()}; + return; + } + die "'$passwd' contains invalid character \\n" if $passwd =~ /\n/; + if (length $passwd < 20) { + $passwd .= "\n" . ' ' x (20 - length($passwd) - 1); + } + my $passwd_utf8 = encode('UTF-8', $passwd); + my $rand_bytes = _rand_data(length $passwd_utf8); + $self->s3d(S_PASSWD, b64_encode($passwd_utf8 ^ $rand_bytes, '')); + $self->session(S_OTP_S3D_PW, b64_encode($rand_bytes, '')); + } + else { + $self->session(S_PASSWD() => $passwd); + } + } + else { # get + if ($secAlg eq 'cram') { + wantarray or carp "you forgot the challenge"; + return ($self->session(S_PASSWD), $self->session('challenge')); + } + elsif ($secAlg eq 's3d') { + my $pw = b64_decode($self->s3d(S_PASSWD) || ''); + my $otp = b64_decode($self->session(S_OTP_S3D_PW) || ''); + my ($res) = split "\n", decode('UTF-8', $pw ^ $otp), 2; + return $res; + } + else { + return $self->session(S_PASSWD); + } + } +} + +sub _warn_crypt { + my $self = shift; + + state $once = 0; + + if ( !TRUE_RANDOM && !$once && lc $self->config->{session}{secure} eq 's3d' ) { + $self->log->warn("Falling back to pseudo random generation. Please install Crypt::URandom"); + $once = 1; + } +} + + 1 __END__ @@ -368,3 +458,41 @@ Sends a mail written in writemail. =head2 move Moves mails between mail forlders. + +=head1 METHODS + +=head2 _session_passwd + +A helper used to set and get the session password. The behavior can be altered by +setting the config variable C<< session => {secure => 's3d'} >>. + + $c->_session_passwd('s3cret'); + +Currently the following modes are supported: + +=over 6 + +=item none + +The password is plainly stored in session cookie. +The cookie is stored on the client side and send with every request. + +=item cram + +A nonce is send to the client and the cram_md5 is generated there via js +and crypto-js. +This is vulnurable to replay attacks as the nonce is not invalidated ever. + +=item s3d + +The password is stored on the server. Additionally the password is encrypted +by an one-time-pad that is stored in the users cookie. +This is vulnurable to replay attacks during an active session. +On log-in it is transfered plainly. + +=back + +=head1 DEPENDENCIES + +Crypt::URandom is recommended + -- cgit v1.2.3