forked from kz26/mailproxy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
179 lines (147 loc) · 5.84 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import configparser
import sys
from pathlib import Path
from time import sleep
from typing import Dict, List
from user_handlers import SmtpHandler, ImapHandler, MailUser
from aiosmtpd.controller import Controller
from aiosmtpd.smtp import SMTP as SMTPServer
from aiosmtpd.smtp import Envelope
import logging
import logging.config
logging.config.fileConfig('logging.conf', disable_existing_loggers=True)
logger = logging.getLogger('main')
logger.info("Starting program...")
class LocalSmtpHandler:
"""Class for handling SMTP requests from local server"""
def __init__(self):
self.mail_users: Dict[
str, MailUser
] = {} # dict of users with username and handler for each user.
def load_users(self, config: configparser.ConfigParser) -> None:
"""Loads users from config file
Args:
config (configparser.ConfigParser): Config file
"""
try:
list_emails = config.get("local", "email_list").replace(" ",
"").split(
",")
logger.info("Loaded users from config file")
logger.debug(f"Loaded users from config file {list_emails}")
except (configparser.NoOptionError, configparser.NoSectionError) as e:
raise ValueError(f"Error reading config file: {e}") from e
loaded_users: Dict[str, MailUser] = {}
for email in list_emails:
# TODO replace try by if and logging
try:
smtp_handler = SmtpHandler.load_smtp(config, email)
imap_handler = ImapHandler.load_imap(config, email)
if smtp_handler is None and imap_handler is None:
raise ValueError
elif smtp_handler is None:
logger.warning(
f"SMTP handler missing for {email}. Program will continue."
)
elif imap_handler is None:
logger.warning(
f"IMAP handler missing for {email}. Program will continue"
)
temp_mail_user = MailUser(email, smtp_handler, imap_handler)
loaded_users[email] = temp_mail_user
except ValueError:
logger.error(
f"No SMTP and IMAP handlers found for email {email}. Exiting program."
)
sys.exit(1)
self.mail_users = loaded_users
async def handle_DATA(
self, server: SMTPServer, session: object, envelope: Envelope
) -> str:
"""Handle DATA command from SMTP server.
#TODO check this tech aiosmtpd.handlers.Proxy
Args:
server (object): SMTP server object
session (object): SMTP session object
envelope (object): SMTP envelope object
Returns:
str: Response code
"""
email_from: str = envelope.mail_from
emails_to: List[str] = envelope.rcpt_tos
content: bytes = envelope.original_content
refused_recipients: Dict[str, str] = {}
try:
mail_user = self.mail_users[email_from]
except KeyError:
logger.error(f"Error: From {email_from} not in config file")
return "450 User not found"
logger.debug(mail_user.smtp_handler)
logger.debug(mail_user.imap_handler)
if mail_user.imap_handler:
mail_user.imap_handler.store_email(content)
if mail_user.smtp_handler:
smtp_reply = mail_user.smtp_handler.send_email(emails_to, content)
else:
smtp_reply = f"451 Message not sent because of server error"
logger.info(
f"Success send mail form {email_from} to {emails_to}."
f" content size: {len(content)}")
return smtp_reply
# if refused_recipients:
# return f"553 Recipients refused: {refused_recipients}"
# else:
# return "250 Message accepted for delivery"
# TODO: Test it and make it work with real server and config file.
class UTF8Controller(Controller):
"""Allow UTF8 in SMTP server"""
def factory(self):
# TODO remoteaddr not filled!
return SMTPServer(self.handler, decode_data=True, enable_SMTPUTF8=True)
"""Config Init"""
if len(sys.argv) == 2:
config_path = sys.argv[1]
else:
config_path = Path(sys.path[0]) / "config.ini"
if not Path(config_path).exists():
raise OSError(f"Config file not found: {config_path}")
with open(
config_path, "r"
) as f: # Use context manager to automatically close the file after reading
config = configparser.ConfigParser()
config.read_file(f)
logger.info("Config Loaded")
if __name__ == "__main__":
local_handler = LocalSmtpHandler()
local_handler.load_users(config)
if config.getboolean("local", "use_utf8"):
controller = UTF8Controller(
local_handler,
hostname=config.get("local", "host"),
port=config.getint("local", "port"),
)
else:
controller = Controller(
local_handler,
hostname=config.get("local", "host"),
port=config.getint("local", "port"),
)
logger.debug(type(controller))
try:
controller.start()
logger.info("Server started.")
while controller.loop.is_running():
sleep(0.2)
# TODO fix to asynctio
# go main thread to sleep...
# maybe need fix?
except KeyboardInterrupt:
controller.stop()
logger.info("Exiting")
except OSError as e:
if e.winerror == 10049 and controller.hostname == "0.0.0.0":
host_in_config = config.get("local", "host")
logger.error(
f"Invalid host in config! Host in config: {host_in_config} Closing program..."
)
sys.exit(1)