diff --git a/core/admin/mailu/internal/nginx.py b/core/admin/mailu/internal/nginx.py index 9271df8e..54eb3eb6 100644 --- a/core/admin/mailu/internal/nginx.py +++ b/core/admin/mailu/internal/nginx.py @@ -1,4 +1,4 @@ -from mailu import models +from mailu import models, utils from flask import current_app as app import re @@ -32,8 +32,8 @@ def check_credentials(user, password, ip, protocol=None, auth_port=None): return False is_ok = False # webmails - if len(password) == 64 and auth_port in ['10143', '10025']: - if user.verify_temp_token(password): + if auth_port in ['10143', '10025'] and password.startswith('token-'): + if utils.verify_temp_token(user.get_id(), password): is_ok = True # All tokens are 32 characters hex lowercase if not is_ok and len(password) == 32: diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py index 344be78b..270b5cdf 100644 --- a/core/admin/mailu/internal/views/auth.py +++ b/core/admin/mailu/internal/views/auth.py @@ -68,8 +68,9 @@ def user_authentication(): if (not flask_login.current_user.is_anonymous and flask_login.current_user.enabled): response = flask.Response() - response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, flask_login.current_user.get_id(), "") - response.headers["X-User-Token"] = models.User.get_temp_token(flask_login.current_user.get_id()) + email = flask_login.current_user.get_id() + response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, email, "") + response.headers["X-User-Token"] = utils.gen_temp_token(email, flask.session) return response return flask.abort(403) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index aedef62a..aea81fb7 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -16,7 +16,6 @@ import passlib.hash import passlib.registry import time import os -import hmac import smtplib import idna import dns.resolver @@ -645,15 +644,6 @@ in clear-text regardless of the presence of the cache. user = cls.query.get(email) return user if (user and user.enabled and user.check_password(password)) else None - @classmethod - def get_temp_token(cls, email): - user = cls.query.get(email) - return hmac.new(app.temp_token_key, bytearray("{}|{}".format(time.strftime('%Y%m%d'), email), 'utf-8'), 'sha256').hexdigest() if (user and user.enabled) else None - - def verify_temp_token(self, token): - return hmac.compare_digest(self.get_temp_token(self.email), token) - - class Alias(Base, Email): """ An alias is an email address that redirects to some destination. diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index e12def11..44a4ec7f 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -121,7 +121,6 @@ proxy = PrefixMiddleware() # Data migrate migrate = flask_migrate.Migrate() - # session store (inspired by https://github.com/mbr/flask-kvsession) class RedisStore: """ Stores session data in a redis db. """ @@ -232,7 +231,8 @@ class MailuSession(CallbackDict, SessionMixin): def destroy(self): """ destroy session for security reasons. """ - + if 'webmail_token' in self: + self.app.session_store.delete(self['webmail_token']) self.delete() self._uid = None @@ -273,6 +273,11 @@ class MailuSession(CallbackDict, SessionMixin): if self._sid is None: self._sid = self.app.session_config.gen_sid() set_cookie = True + if 'webmail_token' in self: + app.session_store.put(self['webmail_token'], + self.sid, + int(app.config['PERMANENT_SESSION_LIFETIME']), + ) # get new session key key = self.sid @@ -477,3 +482,24 @@ class MailuSessionExtension: cleaned = Value('i', False) session = MailuSessionExtension() + +# this is used by the webmail to authenticate IMAP/SMTP +def verify_temp_token(email, token): + try: + if token.startswith('token-'): + sessid = app.session_store.get(token) + if sessid: + session = MailuSession(sessid, app) + if session.get('_user_id', '') == email: + return True + except: + pass + +def gen_temp_token(email, session): + token = session.get('webmail_token', 'token-'+secrets.token_urlsafe()) + session['webmail_token'] = token + app.session_store.put(token, + session.sid, + int(app.config['PERMANENT_SESSION_LIFETIME']), + ) + return token