summaryrefslogtreecommitdiff
path: root/lib/JWebmail/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'lib/JWebmail/Controller')
-rw-r--r--lib/JWebmail/Controller/Webmail.pm166
1 files changed, 147 insertions, 19 deletions
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
+