Compare commits
3 Commits
f3e80c43ae
...
v1.1
Author | SHA1 | Date | |
---|---|---|---|
fbb2cf38ae | |||
4df53ce247 | |||
eb5c71968a |
14
Pipfile.lock
generated
14
Pipfile.lock
generated
@ -18,20 +18,20 @@
|
|||||||
"default": {
|
"default": {
|
||||||
"aiosmtpd": {
|
"aiosmtpd": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:f821fe424b703b2ea391dc2df11d89d2afd728af27393e13cf1a3530f19fdc5e",
|
"sha256:78d7b14f859ad0e6de252b47f9cf1ca6f1c82a8b0f10a9e39bec7e915a6aa5fe",
|
||||||
"sha256:f9243b7dfe00aaf567da8728d891752426b51392174a34d2cf5c18053b63dcbc"
|
"sha256:a196922f1903e54c4d37c53415b7613056d39e2b1e8249f324b9ee7a439be0f1"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version ~= '3.7'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.4.4.post2"
|
"version": "==1.4.5"
|
||||||
},
|
},
|
||||||
"atpublic": {
|
"atpublic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0f40433219e124edf115c6c363808ca6f0e1cfa7d160d86b2fb94793086d1294",
|
"sha256:d1c8cd931af7461f6d18bc6063383e8654d9e9ef19d58ee6dc01e8515bbf55df",
|
||||||
"sha256:80057c55641253b86dcb68b524f82328172371b6547d4c7462a9127fbfbbabfc"
|
"sha256:df90de1162b1a941ee486f484691dc7c33123ee638ea5d6ca604061306e0fdde"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.0"
|
"version": "==4.1.0"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -81,15 +81,16 @@ systemctl status mail4one
|
|||||||
Above command should fail as the TLS certificates don't exist yet.
|
Above command should fail as the TLS certificates don't exist yet.
|
||||||
|
|
||||||
## Setup TLS certificates
|
## Setup TLS certificates
|
||||||
Install [certbot](https://certbot.eff.org/) and run below command. Follow instructions to create TLS certificates. Usually you want certificate for domain name like `mail.example.com`
|
Install [certbot](https://certbot.eff.org/) and run below command. Follow instructions to create TLS certificates. Usually you want certificate for domain name like `mail.mydomain.com`
|
||||||
```sh
|
```sh
|
||||||
sudo certbot certonly
|
sudo certbot certonly
|
||||||
sudo cp /etc/letsencrypt/live/mail.example.com/{fullchain,privkey}.pem /var/lib/mail4one/certs/
|
|
||||||
sudo chown mail4one:mail4one /var/lib/mail4one/certs/{fullchain,privkey}.pem
|
|
||||||
|
|
||||||
# Edit mail4one_cert_copy.sh to update your domain name
|
# **Edit** mail4one_cert_copy.sh to update your domain name
|
||||||
sudo cp mail4one_cert_copy.sh /etc/letsencrypt/renewal-hooks/deploy/
|
sudo cp mail4one_cert_copy.sh /etc/letsencrypt/renewal-hooks/deploy/
|
||||||
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail4one_cert_copy.sh
|
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/mail4one_cert_copy.sh
|
||||||
|
|
||||||
|
# This will create and copy the certificates to the right path with correct permissions and ownership
|
||||||
|
sudo certbot certonly -d mail.mydomain.com --run-deploy-hooks --dry-run
|
||||||
```
|
```
|
||||||
## Restart service and check logs
|
## Restart service and check logs
|
||||||
```sh
|
```sh
|
||||||
@ -109,6 +110,6 @@ python3 -m http.server 25
|
|||||||
In local machine or a browser
|
In local machine or a browser
|
||||||
You should see file listing a, b, c. Repeat for port 465, 995 to make sure firewall rules and dns is working
|
You should see file listing a, b, c. Repeat for port 465, 995 to make sure firewall rules and dns is working
|
||||||
```sh
|
```sh
|
||||||
curl http://mail.example.com:25
|
curl http://mail.mydomain.com:25
|
||||||
```
|
```
|
||||||
If not working, refer to VPS settings and OS firewall settings.
|
If not working, refer to VPS settings and OS firewall settings.
|
||||||
|
@ -7,13 +7,21 @@
|
|||||||
# This file is supposed to be copied to /etc/letsencrypt/renewal-hooks/deploy/
|
# This file is supposed to be copied to /etc/letsencrypt/renewal-hooks/deploy/
|
||||||
# Change the mail domain to the one on MX record
|
# Change the mail domain to the one on MX record
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
if [ "$RENEWED_DOMAINS" = "mail.mydomain.com" ]
|
if [ "$RENEWED_DOMAINS" = "mail.mydomain.com" ]
|
||||||
then
|
then
|
||||||
mkdir -p /var/lib/mail4one/certs
|
app=mail4one
|
||||||
chmod 750 /var/lib/mail4one/certs
|
appuser=$app
|
||||||
chown mail4one:mail4one /var/lib/mail4one/certs
|
certpath="/var/lib/$app/certs"
|
||||||
cp "$RENEWED_LINEAGE/fullchain.pem" /var/lib/mail4one/certs/
|
|
||||||
cp "$RENEWED_LINEAGE/privkey.pem" /var/lib/mail4one/certs/
|
mkdir -p "$certpath"
|
||||||
systemctl restart mail4one.service
|
chmod 750 "$certpath"
|
||||||
echo "$(date) Renewed and deployed certificates for mail4one" >> /var/log/mail4one-cert-renew.log
|
|
||||||
|
chown $appuser:$appuser "$certpath"
|
||||||
|
install -o "$appuser" -g "$appuser" -m 444 "$RENEWED_LINEAGE/fullchain.pem" -t "$certpath"
|
||||||
|
install -o "$appuser" -g "$appuser" -m 400 "$RENEWED_LINEAGE/privkey.pem" -t "$certpath"
|
||||||
|
|
||||||
|
systemctl restart $app.service
|
||||||
|
echo "$(date) Renewed and deployed certificates for $app" >> /var/log/cert-renew.log
|
||||||
fi
|
fi
|
||||||
|
@ -29,7 +29,7 @@ from .poputils import (
|
|||||||
end,
|
end,
|
||||||
Request,
|
Request,
|
||||||
MailEntry,
|
MailEntry,
|
||||||
get_mail,
|
get_mail_fp,
|
||||||
get_mails_list,
|
get_mails_list,
|
||||||
MailList,
|
MailList,
|
||||||
)
|
)
|
||||||
@ -217,7 +217,12 @@ def trans_command_retr(mails: MailList, req: Request) -> None:
|
|||||||
entry = mails.get(req.arg1)
|
entry = mails.get(req.arg1)
|
||||||
if entry:
|
if entry:
|
||||||
write(ok("Contents follow"))
|
write(ok("Contents follow"))
|
||||||
write(get_mail(entry))
|
with get_mail_fp(entry) as fp:
|
||||||
|
for line in fp:
|
||||||
|
if line.startswith(b"."):
|
||||||
|
write(b".") # prepend dot
|
||||||
|
write(line)
|
||||||
|
# write(get_mail(entry)) # no prepend dot
|
||||||
write(end())
|
write(end())
|
||||||
mails.delete(req.arg1)
|
mails.delete(req.arg1)
|
||||||
else:
|
else:
|
||||||
|
@ -2,6 +2,7 @@ import os
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
class ClientError(Exception):
|
class ClientError(Exception):
|
||||||
@ -124,6 +125,12 @@ def set_nid(entries: list[MailEntry]):
|
|||||||
entry.nid = i
|
entry.nid = i
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_mail_fp(entry: MailEntry):
|
||||||
|
with open(entry.path, mode="rb") as fp:
|
||||||
|
yield fp
|
||||||
|
|
||||||
|
|
||||||
def get_mail(entry: MailEntry) -> bytes:
|
def get_mail(entry: MailEntry) -> bytes:
|
||||||
with open(entry.path, mode="rb") as fp:
|
with open(entry.path, mode="rb") as fp:
|
||||||
return fp.read()
|
return fp.read()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
-i https://pypi.org/simple
|
-i https://pypi.org/simple
|
||||||
aiosmtpd==1.4.4.post2; python_version ~= '3.7'
|
aiosmtpd==1.4.5; python_version >= '3.8'
|
||||||
atpublic==4.0; python_version >= '3.8'
|
atpublic==4.1.0; python_version >= '3.8'
|
||||||
attrs==23.2.0; python_version >= '3.7'
|
attrs==23.2.0; python_version >= '3.7'
|
||||||
python-jata==1.2; python_version >= '3.8'
|
python-jata==1.2; python_version >= '3.8'
|
||||||
|
@ -4,6 +4,7 @@ import logging
|
|||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
import poplib
|
||||||
from mail4one.pop3 import create_pop_server
|
from mail4one.pop3 import create_pop_server
|
||||||
from mail4one.config import User
|
from mail4one.config import User
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -19,7 +20,13 @@ AQXWXSWNGAEPGIMG2F3QDKBXL3MRHY6K2BPID64ZR6LABLPVSF
|
|||||||
TEST_USER = "foobar"
|
TEST_USER = "foobar"
|
||||||
TEST_MBOX = "foobar_mails"
|
TEST_MBOX = "foobar_mails"
|
||||||
|
|
||||||
USERS = [User(username=TEST_USER, password_hash=TEST_HASH, mbox=TEST_MBOX)]
|
TEST_USER2 = "foo2"
|
||||||
|
TEST_MBOX2 = "foo2mails"
|
||||||
|
|
||||||
|
USERS = [
|
||||||
|
User(username=TEST_USER, password_hash=TEST_HASH, mbox=TEST_MBOX),
|
||||||
|
User(username=TEST_USER2, password_hash=TEST_HASH, mbox=TEST_MBOX2),
|
||||||
|
]
|
||||||
|
|
||||||
MAILS_PATH: Path
|
MAILS_PATH: Path
|
||||||
|
|
||||||
@ -50,13 +57,21 @@ def setUpModule() -> None:
|
|||||||
td = tempfile.TemporaryDirectory(prefix="m41.pop.")
|
td = tempfile.TemporaryDirectory(prefix="m41.pop.")
|
||||||
unittest.addModuleCleanup(td.cleanup)
|
unittest.addModuleCleanup(td.cleanup)
|
||||||
MAILS_PATH = Path(td.name)
|
MAILS_PATH = Path(td.name)
|
||||||
os.mkdir(MAILS_PATH / TEST_MBOX)
|
for mbox in (TEST_MBOX, TEST_MBOX2):
|
||||||
for md in ("new", "cur", "tmp"):
|
os.mkdir(MAILS_PATH / mbox)
|
||||||
os.mkdir(MAILS_PATH / TEST_MBOX / md)
|
for md in ("new", "cur", "tmp"):
|
||||||
|
os.mkdir(MAILS_PATH / mbox / md)
|
||||||
with open(MAILS_PATH / TEST_MBOX / "new/msg1.eml", "wb") as f:
|
with open(MAILS_PATH / TEST_MBOX / "new/msg1.eml", "wb") as f:
|
||||||
f.write(TESTMAIL)
|
f.write(TESTMAIL)
|
||||||
with open(MAILS_PATH / TEST_MBOX / "new/msg2.eml", "wb") as f:
|
with open(MAILS_PATH / TEST_MBOX / "new/msg2.eml", "wb") as f:
|
||||||
f.write(TESTMAIL)
|
f.write(TESTMAIL)
|
||||||
|
with open(MAILS_PATH / TEST_MBOX2 / "new/msg1.eml", "wb") as f:
|
||||||
|
f.write(TESTMAIL)
|
||||||
|
f.write(b"More lines to follow\r\n")
|
||||||
|
f.write(b".Line starts with a dot\r\n")
|
||||||
|
f.write(b"some more lines\r\n")
|
||||||
|
f.write(b".\r\n")
|
||||||
|
f.write(b"Previous line just has a dot\r\n")
|
||||||
logging.debug(MAILS_PATH)
|
logging.debug(MAILS_PATH)
|
||||||
|
|
||||||
|
|
||||||
@ -198,6 +213,22 @@ class TestPop3(unittest.IsolatedAsyncioTestCase):
|
|||||||
"""
|
"""
|
||||||
await self.dialog_checker(dialog)
|
await self.dialog_checker(dialog)
|
||||||
|
|
||||||
|
async def test_poplib(self) -> None:
|
||||||
|
def run_poplib():
|
||||||
|
pc = poplib.POP3("127.0.0.1", 7995)
|
||||||
|
try:
|
||||||
|
self.assertEqual(b"+OK Server Ready", pc.getwelcome())
|
||||||
|
self.assertEqual(b"+OK Welcome", pc.user("foo2"))
|
||||||
|
self.assertEqual(b"+OK Login successful", pc.pass_("helloworld"))
|
||||||
|
_, eml, oc = pc.retr(1)
|
||||||
|
self.assertIn(b"Previous line just has a dot", eml)
|
||||||
|
self.assertIn(b".Line starts with a dot", eml)
|
||||||
|
self.assertIn(b".", eml)
|
||||||
|
finally:
|
||||||
|
pc.quit()
|
||||||
|
|
||||||
|
await asyncio.to_thread(run_poplib)
|
||||||
|
|
||||||
async def asyncTearDown(self) -> None:
|
async def asyncTearDown(self) -> None:
|
||||||
logging.debug("at teardown")
|
logging.debug("at teardown")
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
|
Reference in New Issue
Block a user