-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Example: authrelay (shows off AUTH system) (#249)
* Add simple example for Authentication using DB * Add documentation * Bump version + NEWS * Add new example requirements for pytype stage * Add examples' deps to tox.ini * Version Bump to 1.4.0a2 * Optimize release.py * Enable multiple afterbar's for housekeep.py
- Loading branch information
Showing
12 changed files
with
164 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
# Copyright 2014-2021 The aiosmtpd Developers | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
__version__ = "1.4.0a1" | ||
__version__ = "1.4.0a2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mail.db |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Copyright 2014-2021 The aiosmtpd Developers | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import sqlite3 | ||
from argon2 import PasswordHasher | ||
from pathlib import Path | ||
from typing import Dict | ||
|
||
|
||
DB_FILE = "mail.db~" | ||
USER_AND_PASSWORD: Dict[str, str] = { | ||
"user1": "not@password", | ||
"user2": "correctbatteryhorsestaple", | ||
"user3": "1d0ntkn0w", | ||
"user4": "password", | ||
"user5": "password123", | ||
"user6": "a quick brown fox jumps over a lazy dog" | ||
} | ||
|
||
|
||
if __name__ == '__main__': | ||
dbfp = Path(DB_FILE).absolute() | ||
if dbfp.exists(): | ||
dbfp.unlink() | ||
conn = sqlite3.connect(DB_FILE) | ||
curs = conn.cursor() | ||
curs.execute("CREATE TABLE userauth (username text, hashpass text)") | ||
ph = PasswordHasher() | ||
insert_up = "INSERT INTO userauth VALUES (?, ?)" | ||
for u, p in USER_AND_PASSWORD.items(): | ||
h = ph.hash(p) | ||
curs.execute(insert_up, (u, h)) | ||
conn.commit() | ||
conn.close() | ||
assert dbfp.exists() | ||
print(f"database created at {dbfp}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
argon2-cffi | ||
dnspython |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Copyright 2014-2021 The aiosmtpd Developers | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import asyncio | ||
import dns.resolver | ||
import logging | ||
import sqlite3 | ||
import sys | ||
|
||
from aiosmtpd.controller import Controller | ||
from aiosmtpd.smtp import AuthResult, LoginPassword | ||
from argon2 import PasswordHasher | ||
from functools import lru_cache | ||
from pathlib import Path | ||
from smtplib import SMTP as SMTPCLient | ||
|
||
|
||
DEST_PORT = 25 | ||
DB_AUTH = Path("mail.db~") | ||
|
||
|
||
class Authenticator: | ||
def __init__(self, auth_database): | ||
self.auth_db = Path(auth_database) | ||
self.ph = PasswordHasher() | ||
|
||
def __call__(self, server, session, envelope, mechanism, auth_data): | ||
fail_nothandled = AuthResult(success=False, handled=False) | ||
if mechanism not in ("LOGIN", "PLAIN"): | ||
return fail_nothandled | ||
if not isinstance(auth_data, LoginPassword): | ||
return fail_nothandled | ||
username = auth_data.login | ||
password = auth_data.password | ||
hashpass = self.ph.hash(password) | ||
conn = sqlite3.connect(self.auth_db) | ||
curs = conn.execute( | ||
"SELECT hashpass FROM userauth WHERE username=?", (username,) | ||
) | ||
hash_db = curs.fetchone() | ||
conn.close() | ||
if not hash_db: | ||
return fail_nothandled | ||
if hashpass != hash_db[0]: | ||
return fail_nothandled | ||
return AuthResult(success=True) | ||
|
||
|
||
@lru_cache(maxsize=256) | ||
def get_mx(domain): | ||
records = dns.resolver.resolve(domain, "MX") | ||
if not records: | ||
return None | ||
records = sorted(records, key=lambda r: r.preference) | ||
return str(records[0].exchange) | ||
|
||
|
||
class RelayHandler: | ||
def handle_data(self, server, session, envelope, data): | ||
mx_rcpt = {} | ||
for rcpt in envelope.rcpt_tos: | ||
_, _, domain = rcpt.partition("@") | ||
mx = get_mx(domain) | ||
if mx is None: | ||
continue | ||
mx_rcpt.setdefault(mx, []).append(rcpt) | ||
|
||
for mx, rcpts in mx_rcpt.items(): | ||
with SMTPCLient(mx, 25) as client: | ||
client.sendmail( | ||
from_addr=envelope.mail_from, | ||
to_addrs=rcpts, | ||
msg=envelope.original_content | ||
) | ||
|
||
|
||
# noinspection PyShadowingNames | ||
async def amain(): | ||
handler = RelayHandler() | ||
cont = Controller( | ||
handler, | ||
hostname='', | ||
port=8025, | ||
authenticator=Authenticator(DB_AUTH) | ||
) | ||
try: | ||
cont.start() | ||
finally: | ||
cont.stop() | ||
|
||
|
||
if __name__ == '__main__': | ||
if not DB_AUTH.exists(): | ||
print(f"Please create {DB_AUTH} first using make_user_db.py") | ||
sys.exit(1) | ||
logging.basicConfig(level=logging.DEBUG) | ||
loop = asyncio.get_event_loop() | ||
loop.create_task(amain()) | ||
try: | ||
loop.run_forever() | ||
except KeyboardInterrupt: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters