mirror of
https://github.com/Kozea/Radicale.git
synced 2025-10-15 21:51:57 +00:00
auth: dovecot: pass remote IP (rip=) to auth server
If known, let the auth server know where the client came from, using REMOTE_ADDR or, optionally/configurably, the X-Remote-Addr header value (which is needed when running behind a trusted proxy.) Addresses #1859.
This commit is contained in:
parent
1bac038f5a
commit
b5a1ea911d
8 changed files with 136 additions and 15 deletions
|
@ -91,6 +91,15 @@ def load(configuration: "config.Configuration") -> "BaseAuth":
|
|||
configuration)
|
||||
|
||||
|
||||
class AuthContext:
|
||||
remote_addr: str
|
||||
x_remote_addr: str
|
||||
|
||||
def __init__(self):
|
||||
self.remote_addr = None
|
||||
self.x_remote_addr = None
|
||||
|
||||
|
||||
class BaseAuth:
|
||||
|
||||
_ldap_groups: Set[str] = set([])
|
||||
|
@ -187,6 +196,21 @@ class BaseAuth:
|
|||
|
||||
raise NotImplementedError
|
||||
|
||||
def _login_ext(self, login: str, password: str, context: AuthContext) -> str:
|
||||
"""Check credentials and map login to internal user
|
||||
|
||||
``login`` the login name
|
||||
|
||||
``password`` the password
|
||||
|
||||
``context`` additional data for the login, e.g. IP address used
|
||||
|
||||
Returns the username or ``""`` for invalid credentials.
|
||||
"""
|
||||
|
||||
# override this method instead of _login() if you want the context
|
||||
return self._login(login, password)
|
||||
|
||||
def _sleep_for_constant_exec_time(self, time_ns_begin: int):
|
||||
"""Sleep some time to reach a constant execution time for failed logins
|
||||
|
||||
|
@ -216,7 +240,7 @@ class BaseAuth:
|
|||
time.sleep(sleep)
|
||||
|
||||
@final
|
||||
def login(self, login: str, password: str) -> Tuple[str, str]:
|
||||
def login(self, login: str, password: str, context: AuthContext) -> Tuple[str, str]:
|
||||
time_ns_begin = time.time_ns()
|
||||
result_from_cache = False
|
||||
if self._lc_username:
|
||||
|
@ -284,7 +308,7 @@ class BaseAuth:
|
|||
if result == "":
|
||||
# verify login+password via configured backend
|
||||
logger.debug("Login verification for user+password via backend: '%s'", login)
|
||||
result = self._login(login, password)
|
||||
result = self._login_ext(login, password, context)
|
||||
if result != "":
|
||||
logger.debug("Login successful for user+password via backend: '%s'", login)
|
||||
if digest == "":
|
||||
|
@ -314,7 +338,7 @@ class BaseAuth:
|
|||
return (result, self._type)
|
||||
else:
|
||||
# self._cache_logins is False
|
||||
result = self._login(login, password)
|
||||
result = self._login_ext(login, password, context)
|
||||
if result == "":
|
||||
self._sleep_for_constant_exec_time(time_ns_begin)
|
||||
return (result, self._type)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import base64
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from contextlib import closing
|
||||
|
||||
|
@ -32,6 +33,8 @@ class Auth(auth.BaseAuth):
|
|||
self.timeout = 5
|
||||
self.request_id_gen = itertools.count(1)
|
||||
|
||||
self.use_x_remote_addr = configuration.get("auth", "dovecot_rip_x_remote_addr")
|
||||
|
||||
config_family = configuration.get("auth", "dovecot_connection_type")
|
||||
if config_family == "AF_UNIX":
|
||||
self.family = socket.AF_UNIX
|
||||
|
@ -46,7 +49,7 @@ class Auth(auth.BaseAuth):
|
|||
else:
|
||||
self.family = socket.AF_INET6
|
||||
|
||||
def _login(self, login, password):
|
||||
def _login_ext(self, login, password, context):
|
||||
"""Validate credentials.
|
||||
|
||||
Check if the ``login``/``password`` pair is valid according to Dovecot.
|
||||
|
@ -148,10 +151,19 @@ class Auth(auth.BaseAuth):
|
|||
"Authenticating with request id: '{}'"
|
||||
.format(request_id)
|
||||
)
|
||||
rip = b''
|
||||
if self.use_x_remote_addr and context.x_remote_addr:
|
||||
rip = context.x_remote_addr.encode('ascii')
|
||||
elif context.remote_addr:
|
||||
rip = context.remote_addr.encode('ascii')
|
||||
# squash all whitespace - shouldn't be there and auth protocol
|
||||
# is sensitive to whitespace (in particular \t and \n)
|
||||
if rip:
|
||||
rip = b'\trip=' + re.sub(br'\s', b'', rip)
|
||||
sock.send(
|
||||
b'AUTH\t%u\tPLAIN\tservice=radicale\tresp=%b\n' %
|
||||
b'AUTH\t%u\tPLAIN\tservice=radicale%s\tresp=%b\n' %
|
||||
(
|
||||
request_id, base64.b64encode(
|
||||
request_id, rip, base64.b64encode(
|
||||
b'\0%b\0%b' %
|
||||
(login.encode(), password.encode())
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue