CoolFace
Apppublic

beppause/smtp-ethical-hack

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
email_sender.py185 linesDownload Raw Back to src
1"""
2EmailSender class for handling SMTP connections and email sending with detailed logging.
3"""
4import smtplib
5import ssl
6from email.mime.multipart import MIMEMultipart
7from email.mime.text import MIMEText
8from email.mime.base import MIMEBase
9from email import encoders
10import os
11from typing import List, Optional, Callable
12from dataclasses import dataclass
13
14
15@dataclass
16class EmailConfig:
17    """Configuration for email sending."""
18    smtp_server: str
19    smtp_port: int
20    username: str
21    password: str
22    use_tls: bool = True
23    ssl_verify: bool = True
24
25
26class EmailSender:
27    """Handles SMTP connections and email sending with detailed logging."""
28    
29    def __init__(self, log_callback: Optional[Callable[[str], None]] = None):
30        """
31        Initialize EmailSender with optional logging callback.
32        
33        Args:
34            log_callback: Function to call with log messages for real-time updates
35        """
36        self.smtp_connection = None
37        self.log_callback = log_callback
38        self.log_buffer = []
39    
40    def _log(self, message: str):
41        """Log a message and send to callback if provided."""
42        self.log_buffer.append(message)
43        if self.log_callback:
44            self.log_callback(message)
45    
46    def connect(self, config: EmailConfig) -> bool:
47        """
48        Connect to SMTP server with the given configuration.
49        
50        Args:
51            config: EmailConfig object with connection details
52            
53        Returns:
54            bool: True if connection successful, False otherwise
55        """
56        try:
57            self._log(f"CONNECTING TO {config.smtp_server}:{config.smtp_port}")
58            
59            # Create SMTP connection
60            self.smtp_connection = smtplib.SMTP(config.smtp_server, config.smtp_port)
61            self._log(f"CLIENT: Connected to {config.smtp_server}:{config.smtp_port}")
62            
63            # Enable debug to capture SMTP dialogue
64            self.smtp_connection.set_debuglevel(1)
65            
66            # Send EHLO and handle response
67            ehlo_response = self.smtp_connection.ehlo()
68            self._log(f"SERVER: {ehlo_response}")
69            
70            if config.use_tls:
71                self._log("CLIENT: Starting TLS encryption")
72                # Create SSL context with optional verification
73                ssl_context = ssl.create_default_context()
74                if not config.ssl_verify:
75                    ssl_context.check_hostname = False
76                    ssl_context.verify_mode = ssl.CERT_NONE
77                    self._log("WARNING: SSL certificate verification disabled")
78                
79                tls_response = self.smtp_connection.starttls(context=ssl_context)
80                self._log(f"SERVER: {tls_response}")
81                
82                # Send EHLO again after TLS
83                ehlo_response = self.smtp_connection.ehlo()
84                self._log(f"SERVER: {ehlo_response}")
85            
86            # Authenticate if credentials provided
87            if config.username and config.password:
88                self._log("CLIENT: Attempting authentication")
89                auth_response = self.smtp_connection.login(config.username, config.password)
90                self._log(f"SERVER: Authentication successful")
91            
92            self._log("CONNECTION ESTABLISHED SUCCESSFULLY")
93            return True
94            
95        except Exception as e:
96            self._log(f"ERROR: Connection failed - {str(e)}")
97            return False
98    
99    def send_email(
100        self,
101        from_addr: str,
102        to_addrs: List[str],
103        subject: str,
104        body: str,
105        attachments: Optional[List[str]] = None
106    ) -> bool:
107        """
108        Send an email with optional attachments.
109        
110        Args:
111            from_addr: Sender email address
112            to_addrs: List of recipient email addresses
113            subject: Email subject
114            body: Email body text
115            attachments: List of file paths to attach
116            
117        Returns:
118            bool: True if email sent successfully, False otherwise
119        """
120        if not self.smtp_connection:
121            self._log("ERROR: Not connected to SMTP server")
122            return False
123        
124        try:
125            # Create message
126            msg = MIMEMultipart()
127            msg['From'] = from_addr
128            msg['To'] = ', '.join(to_addrs)
129            msg['Subject'] = subject
130            
131            # Add body
132            msg.attach(MIMEText(body, 'plain'))
133            
134            # Add attachments
135            if attachments:
136                for attachment_path in attachments:
137                    if os.path.exists(attachment_path):
138                        self._log(f"ATTACHING: {os.path.basename(attachment_path)}")
139                        self._add_attachment(msg, attachment_path)
140                    else:
141                        self._log(f"WARNING: Attachment not found - {attachment_path}")
142            
143            # Send email
144            self._log(f"CLIENT: Sending email from {from_addr} to {to_addrs}")
145            self.smtp_connection.sendmail(from_addr, to_addrs, msg.as_string())
146            self._log("EMAIL SENT SUCCESSFULLY")
147            return True
148            
149        except Exception as e:
150            self._log(f"ERROR: Failed to send email - {str(e)}")
151            return False
152    
153    def _add_attachment(self, msg: MIMEMultipart, file_path: str):
154        """Add a file attachment to the email message."""
155        with open(file_path, "rb") as attachment:
156            part = MIMEBase('application', 'octet-stream')
157            part.set_payload(attachment.read())
158        
159        encoders.encode_base64(part)
160        part.add_header(
161            'Content-Disposition',
162            f'attachment; filename={os.path.basename(file_path)}',
163        )
164        msg.attach(part)
165    
166    def disconnect(self):
167        """Disconnect from SMTP server."""
168        if self.smtp_connection:
169            try:
170                self._log("CLIENT: Quitting SMTP connection")
171                self.smtp_connection.quit()
172                self._log("DISCONNECTED SUCCESSFULLY")
173            except Exception as e:
174                self._log(f"ERROR during disconnect: {str(e)}")
175            finally:
176                self.smtp_connection = None
177    
178    def get_log(self) -> str:
179        """Get the complete log as a string."""
180        return "\n".join(self.log_buffer)
181    
182    def clear_log(self):
183        """Clear the log buffer."""
184        self.log_buffer.clear()
185