Add web config http server
This commit is contained in:
parent
8fe42e9163
commit
6d0040415b
1
Pipfile
1
Pipfile
@ -8,6 +8,7 @@ name = "pypi"
|
|||||||
[packages]
|
[packages]
|
||||||
aiosmtpd = "*"
|
aiosmtpd = "*"
|
||||||
python-jata = "*"
|
python-jata = "*"
|
||||||
|
asyncbasehttp = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3"
|
python_version = "3"
|
||||||
|
10
Pipfile.lock
generated
10
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "5c16e20a67c73101d465516d657e21c6c1d3f853ae16dcfe782b7f9e8ba139e5"
|
"sha256": "6b224d3d72187a39e4fba357ffb4b162dc28994bd869c7960fcfd5acbb56c959"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -24,6 +24,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.4.4.post2"
|
"version": "==1.4.4.post2"
|
||||||
},
|
},
|
||||||
|
"asyncbasehttp": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2cea7b61113bb1e69d1c90f32919f004e7f56a7e1e2955e8a6286ce40f9da188",
|
||||||
|
"sha256:70fb983c4df630a9b63081cb8ef6d2e06beb51c89f42a88da6c9a9c7ebf9fb89"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.0"
|
||||||
|
},
|
||||||
"atpublic": {
|
"atpublic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0f40433219e124edf115c6c363808ca6f0e1cfa7d160d86b2fb94793086d1294",
|
"sha256:0f40433219e124edf115c6c363808ca6f0e1cfa7d160d86b2fb94793086d1294",
|
||||||
|
@ -16,6 +16,8 @@ VERSION = b"\x01"
|
|||||||
SALT_LEN = 30
|
SALT_LEN = 30
|
||||||
KEY_LEN = 64 # This is python default
|
KEY_LEN = 64 # This is python default
|
||||||
|
|
||||||
|
# len(VERSION) + SALT_LEN + KEY_LEN should be multiple of 5 to avoid base32 padding
|
||||||
|
|
||||||
|
|
||||||
def gen_pwhash(password: str) -> str:
|
def gen_pwhash(password: str) -> str:
|
||||||
salt = os.urandom(SALT_LEN)
|
salt = os.urandom(SALT_LEN)
|
||||||
|
@ -10,6 +10,7 @@ from getpass import getpass
|
|||||||
from .smtp import create_smtp_server_starttls, create_smtp_server
|
from .smtp import create_smtp_server_starttls, create_smtp_server
|
||||||
from .pop3 import create_pop_server
|
from .pop3 import create_pop_server
|
||||||
from .version import VERSION
|
from .version import VERSION
|
||||||
|
from .web_config import create_web_config_server
|
||||||
|
|
||||||
from . import config
|
from . import config
|
||||||
from . import pwhash
|
from . import pwhash
|
||||||
@ -98,6 +99,14 @@ async def a_main(cfg: config.Config) -> None:
|
|||||||
ssl_context=get_tls_context(smtp.tls),
|
ssl_context=get_tls_context(smtp.tls),
|
||||||
)
|
)
|
||||||
servers.append(smtp_server)
|
servers.append(smtp_server)
|
||||||
|
elif scfg.server_type == "web_config":
|
||||||
|
web = config.ServerCfg(scfg)
|
||||||
|
web_server = await create_web_config_server(
|
||||||
|
host=get_host(web.host),
|
||||||
|
port=web.port,
|
||||||
|
ssl_context=get_tls_context(web.tls),
|
||||||
|
)
|
||||||
|
servers.append(web_server)
|
||||||
else:
|
else:
|
||||||
logging.error(f"Unknown server {scfg.server_type=}")
|
logging.error(f"Unknown server {scfg.server_type=}")
|
||||||
|
|
||||||
|
5
mail4one/template_web_config.html
Normal file
5
mail4one/template_web_config.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head><title>Mail4one Web config</title></head>
|
||||||
|
<body><h1>Hello World</h1></body>
|
||||||
|
</html>
|
85
mail4one/web_config.py
Normal file
85
mail4one/web_config.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import asyncio
|
||||||
|
from asyncbasehttp import request_handler, Request, Response, RequestHandler
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
import ssl
|
||||||
|
import logging
|
||||||
|
from pprint import pprint
|
||||||
|
import http
|
||||||
|
from base64 import b64decode
|
||||||
|
from .pwhash import gen_pwhash, parse_hash, PWInfo, check_pass
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
def get_template() -> bytes:
|
||||||
|
if data:= pkgutil.get_data('mail4one', 'template_web_config.html'):
|
||||||
|
return data
|
||||||
|
raise Exception("Failed to get template data from 'template_web_config.html'")
|
||||||
|
|
||||||
|
|
||||||
|
def get_dummy_pwinfo() -> PWInfo:
|
||||||
|
pwhash = gen_pwhash("world")
|
||||||
|
return parse_hash(pwhash)
|
||||||
|
|
||||||
|
|
||||||
|
class WebonfigHandler(RequestHandler):
|
||||||
|
def __init__(self, username: str, pwinfo: PWInfo):
|
||||||
|
self.username = username.encode()
|
||||||
|
self.pwinfo = pwinfo
|
||||||
|
self.auth_required = True
|
||||||
|
|
||||||
|
def do_auth(self, req: Request) -> Tuple[bool, Optional[Response]]:
|
||||||
|
|
||||||
|
def resp_unauthorized():
|
||||||
|
resp = Response.no_body_response(http.HTTPStatus.UNAUTHORIZED)
|
||||||
|
resp.add_header("WWW-Authenticate", 'Basic realm="Mail4one"')
|
||||||
|
return resp
|
||||||
|
|
||||||
|
auth_header = req.headers["Authorization"]
|
||||||
|
|
||||||
|
if not auth_header:
|
||||||
|
return False, resp_unauthorized()
|
||||||
|
|
||||||
|
if not auth_header.startswith("Basic "):
|
||||||
|
logging.error("Authorization header malformed")
|
||||||
|
return False, Response.no_body_response(http.HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
userpassb64 = auth_header[len("Basic ") :]
|
||||||
|
try:
|
||||||
|
userpass = b64decode(userpassb64)
|
||||||
|
except:
|
||||||
|
logging.exception("bad request")
|
||||||
|
return False, Response.no_body_response(http.HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user, passwd = userpass.split(b":")
|
||||||
|
except:
|
||||||
|
logging.exception("bad request")
|
||||||
|
return False, Response.no_body_response(http.HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
if user == self.username and check_pass(passwd.decode(), self.pwinfo):
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
return False, resp_unauthorized()
|
||||||
|
|
||||||
|
|
||||||
|
async def process_request(self, req: Request) -> Response:
|
||||||
|
if self.auth_required:
|
||||||
|
ok, resp = self.do_auth(req)
|
||||||
|
if not ok:
|
||||||
|
if resp:
|
||||||
|
return resp
|
||||||
|
else:
|
||||||
|
raise Exception("Something went wrong!")
|
||||||
|
return Response.create_ok_response(get_template())
|
||||||
|
|
||||||
|
|
||||||
|
async def create_web_config_server(
|
||||||
|
host: str, port: int, ssl_context: Optional[ssl.SSLContext]
|
||||||
|
) -> asyncio.Server:
|
||||||
|
logging.info(f"template: {get_template().decode()}")
|
||||||
|
logging.info(f"Starting Webconfig server {host=}, {port=}, {ssl_context != None=}")
|
||||||
|
return await asyncio.start_server(
|
||||||
|
WebonfigHandler("hello", get_dummy_pwinfo()),
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
ssl=ssl_context,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user