From f2a2241b6ba0c192c12b077fbde2f860f5a432f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Wed, 1 Nov 2017 19:20:12 +0200 Subject: [PATCH] Email listener: "./manage.py smtpd" --- hc/api/management/commands/smtpd.py | 50 +++++++++++++++++++++++++++++ hc/api/models.py | 20 ++++++++++++ hc/api/views.py | 28 +++++----------- 3 files changed, 78 insertions(+), 20 deletions(-) create mode 100644 hc/api/management/commands/smtpd.py diff --git a/hc/api/management/commands/smtpd.py b/hc/api/management/commands/smtpd.py new file mode 100644 index 00000000..35d10f8c --- /dev/null +++ b/hc/api/management/commands/smtpd.py @@ -0,0 +1,50 @@ +import asyncore +import re +from smtpd import SMTPServer + +from django.core.management.base import BaseCommand +from hc.api.models import Check + +RE_UUID = re.compile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") + + +class Listener(SMTPServer): + def __init__(self, localaddr, stdout): + self.stdout = stdout + super(Listener, self).__init__(localaddr, None) + + def process_message(self, peer, mailfrom, rcpttos, data): + to_parts = rcpttos[0].split("@") + code = to_parts[0] + + if not RE_UUID.match(code): + self.stdout.write("Not an UUID: %s" % code) + return + + try: + check = Check.objects.get(code=code) + except Check.DoesNotExist: + self.stdout.write("Check not found: %s" % code) + return + + ua = "Email from %s" % mailfrom + check.ping(peer[0], "email", "", ua, data) + self.stdout.write("Processed ping for %s" % code) + + +class Command(BaseCommand): + help = "Listen for ping emails" + + def add_arguments(self, parser): + parser.add_argument("--host", + help="ip address to listen on, default 0.0.0.0", + default="0.0.0.0") + parser.add_argument('--port', + help="port to listen on, default 25", + type=int, + default=25) + + def handle(self, host, port, *args, **options): + listener = Listener((host, port), self.stdout) + print("Starting SMTP listener on %s:%d ..." % (host, port)) + asyncore.loop() diff --git a/hc/api/models.py b/hc/api/models.py index 4439abd5..3fa9d2ca 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -188,6 +188,26 @@ class Check(models.Model): def has_confirmation_link(self): return "confirm" in self.last_ping_body.lower() + def ping(self, remote_addr, scheme, method, ua, body): + self.n_pings = models.F("n_pings") + 1 + self.last_ping = timezone.now() + self.last_ping_body = body[:10000] + self.alert_after = self.get_alert_after() + if self.status in ("new", "paused"): + self.status = "up" + + self.save() + self.refresh_from_db() + + ping = Ping(owner=self) + ping.n = self.n_pings + ping.remote_addr = remote_addr + ping.scheme = scheme + ping.method = method + # If User-Agent is longer than 200 characters, truncate it: + ping.ua = ua[:200] + ping.save() + class Ping(models.Model): n = models.IntegerField(null=True) diff --git a/hc/api/views.py b/hc/api/views.py index 04844c0b..6f18e853 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -1,7 +1,6 @@ from datetime import timedelta as td from django.db import connection -from django.db.models import F from django.http import (HttpResponse, HttpResponseForbidden, HttpResponseNotFound, JsonResponse) from django.shortcuts import get_object_or_404 @@ -12,7 +11,7 @@ from django.views.decorators.http import require_POST from hc.api import schemas from hc.api.decorators import check_api_key, uuid_or_400, validate_json -from hc.api.models import Check, Notification, Ping +from hc.api.models import Check, Notification from hc.lib.badges import check_signature, get_badge_svg @@ -22,26 +21,15 @@ from hc.lib.badges import check_signature, get_badge_svg def ping(request, code): check = get_object_or_404(Check, code=code) - check.n_pings = F("n_pings") + 1 - check.last_ping = timezone.now() - check.last_ping_body = request.body[:10000] - check.alert_after = check.get_alert_after() - if check.status in ("new", "paused"): - check.status = "up" - - check.save() - check.refresh_from_db() - - ping = Ping(owner=check) headers = request.META - ping.n = check.n_pings remote_addr = headers.get("HTTP_X_FORWARDED_FOR", headers["REMOTE_ADDR"]) - ping.remote_addr = remote_addr.split(",")[0] - ping.scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") - ping.method = headers["REQUEST_METHOD"] - # If User-Agent is longer than 200 characters, truncate it: - ping.ua = headers.get("HTTP_USER_AGENT", "")[:200] - ping.save() + remote_addr = remote_addr.split(",")[0] + scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http") + method = headers["REQUEST_METHOD"] + ua = headers.get("HTTP_USER_AGENT", "") + body = request.body[:10000] + + check.ping(remote_addr, scheme, method, ua, body) response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*"