Changeset - d969304ee9cb
[Not reviewed]
0 2 0
x - 11 months ago 2024-05-09 18:38:30
xbr@c3l.lu
style: remove empty line spaces
2 files changed with 3 insertions and 3 deletions:
0 comments (0 inline, 0 general)
backend/mail.py
Show inline comments
 
#!/usr/bin/env python3
 
import ssl
 
import smtplib
 
import imaplib
 
from rich.console import Console
 
from cryptography import x509
 
import tls_utils
 
from tls_utils import TLSDetails
 
from abc import ABC, abstractmethod
 

	
 
class MailHandler(ABC):
 
    def __init__(self, host: str, port: int, context: ssl.SSLContext):
 
        self.host = host
 
        self.port = port
 
        self.context = context
 
    
 

	
 
    def connect(self, verification: bool) -> int:
 
        connection = self.protocol_init(self.host, self.port)
 
        if verification:
 
            connection.starttls(**self.protocol_starttls_args())
 
        else:
 
            connection.starttls()
 
        cert = connection.sock.getpeercert()
 
        self.protocol_close(connection)
 
        return tls_utils.get_validity_days(cert)[1]
 

	
 
    @abstractmethod
 
    def protocol_init(self, host, port):
 
        raise NotImplementedError()
 
    @abstractmethod
 
    def protocol_close(self, connection):
 
        raise NotImplementedError()
 
    @abstractmethod
 
    def protocol_starttls_args(self):
 
        raise NotImplementedError()
 
    
 

	
 
    @staticmethod
 
    def create_handler(protocol: str):
 
        if protocol == "smtp":
 
            return SMTPHandler
 
        elif protocol == "imap":
 
            return IMAPHandler
 
        else:
 
            raise ValueError("Invalid protocol")
 

	
 
class IMAPHandler(MailHandler):
 
    def protocol_init(self, host, port):
 
        return imaplib.IMAP4(host, port)
 
    def protocol_close(self, connection):
 
        connection.logout()
 
    def protocol_starttls_args(self):
 
        return {"ssl_context": self.context}
 

	
 
class SMTPHandler(MailHandler):
 
    def protocol_init(self, host, port):
 
        return smtplib.SMTP(host, port)
 
    def protocol_close(self, connection):
 
        connection.quit()
 
    def protocol_starttls_args(self):
 
        return {"context": self.context}
 

	
 
class MailVerificator:
 
    def __init__(self, context: ssl.SSLContext):
 
        self.context = context
 

	
 
    def connect(self, domain: str, port: int, protocol: str) -> TLSDetails:
 
        mail = MailHandler.create_handler(protocol)(domain, port, self.context)
 
        try:
 
            expiry = mail.connect(True)
 
            return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
        except ssl.SSLCertVerificationError as e:
 
            if (e.verify_code == tls_utils.EXPIRED_VERIFY_CODE):
 
                expiry = mail.connect(False)
 
                return TLSDetails(domain_name=domain, expires_in_days=expiry)
 
            else:
 
                error = "failed verification:", e.verify_message + "."
 
                return TLSDetails(domain_name=domain, error_message=error)
 
\ No newline at end of file
backend/tls_utils.py
Show inline comments
 
#!/usr/bin/env python3
 
from rich.console import Console
 
import datetime
 
import math
 

	
 
EXPIRED = 10
 
REVOKED = 23
 
SELF_SIGNED = 18
 
ROOT_NOT_TRUSTED = 19
 

	
 
class TLSDetails:
 
    domain_name = None
 
    expires_in_days = None
 
    error_message = None
 
    connection_error = False
 

	
 
    def __init__(self, domain_name : str = None, expires_in_days : str = None, error_message : str = None, connection_error : bool = False):
 
        self.domain_name = domain_name
 
        self.expires_in_days = expires_in_days
 
        self.error_message = error_message
 
        self.connection_error = connection_error
 
    
 

	
 
    def print(self, console: Console):
 
        if self.connection_error:
 
            console.log("[orange bold underline]" + self.domain_name, self.error_message, style="orange")
 
        elif self.error_message != None:
 
            console.log("[red bold underline]" + self.domain_name, self.error_message, style="red")
 
        elif self.expires_in_days < 0:
 
            console.log("[red bold underline]" + self.domain_name, "expired", abs(self.expires_in_days), "days ago.", style="red")
 
        else:
 
            console.log("[green bold underline]" + self.domain_name, "expires in", self.expires_in_days, "days", style="green")
 

	
 
def get_expiry_timestamps(expiry_timestamp: int, now_timestamp: int = datetime.datetime.now(datetime.UTC).timestamp()) -> tuple[bool, int]:
 
    seconds_left = expiry_timestamp - now_timestamp
 
    days_left = math.floor(seconds_left / 86400)
 
    return (seconds_left >= 0, days_left)
 

	
 
def get_validity_days(cert) -> tuple[bool, int]:
 
    # Get expiry date
 
    notAfter = cert['notAfter']
 
    notAfter_date = datetime.datetime.strptime(notAfter, '%b %d %H:%M:%S %Y %Z')
 

	
 
    # datetime to UNIX time
 
    notAfter_timestamp = notAfter_date.timestamp()
 
    expiry = get_expiry_timestamps(notAfter_timestamp)
 
    return (expiry[0], abs(expiry[1]))
 
\ No newline at end of file
0 comments (0 inline, 0 general)