-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3e0bc6d
commit 9bcf712
Showing
7 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,5 @@ venv.bak/ | |
|
||
# mypy | ||
.mypy_cache/ | ||
.idea | ||
/*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
This is an e-mail message to be sent in HTML format | ||
<b>This is HTML message.</b> | ||
<h1>This is headline.</h1> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import util | ||
|
||
PORT_OUT_OF_RANGE = 'SMTP port is out-of-range (0-65535)' | ||
PORT_NAN = 'SMTP port must be a number' | ||
|
||
|
||
def get_port(): | ||
while True: | ||
try: | ||
port = int(util.get_required_prompt('SMTP port: ')) | ||
if port < 0: | ||
print(PORT_OUT_OF_RANGE) | ||
elif port > 65535: | ||
print(PORT_OUT_OF_RANGE) | ||
else: | ||
return str(port) | ||
except ValueError: | ||
print(PORT_NAN) | ||
|
||
|
||
def get_debug_level(): | ||
is_debug = util.get_optional_prompt('Enable debug (y/n)?: ', 'n') | ||
return util.convert_answer_to_int(is_debug) | ||
|
||
|
||
def get_from_address(): | ||
return util.get_required_prompt('\nEnter FROM address: ') | ||
|
||
|
||
def get_from_name(): | ||
return util.get_required_prompt('Enter FROM name (e.g. John Smith): ') | ||
|
||
|
||
def get_subject(): | ||
return util.get_required_prompt('Enter SUBJECT line: ') | ||
|
||
|
||
def get_to_addresses(): | ||
to_address = util.get_required_prompt('Enter TO address: ') | ||
to_addresses = [to_address] | ||
if is_multi_address(): | ||
while to_address: | ||
to_address = util.get_optional_prompt('Enter TO address (blank to continue): ', None) | ||
if to_address: | ||
to_addresses.append(to_address) | ||
return to_addresses | ||
|
||
|
||
def is_multi_address(): | ||
is_multi = util.get_optional_prompt('Enter more TO addresses (y/n)?: ', 'n') | ||
return util.convert_answer_to_int(is_multi) | ||
|
||
|
||
def load_body_from_file(): | ||
load_from_file = util.get_optional_prompt('Load message body from file (y/n)?: ', 'n') | ||
return util.convert_answer_to_int(load_from_file) | ||
|
||
|
||
def get_body_filename(): | ||
return util.get_required_prompt('Filename: ') | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
colorama==0.3.9 | ||
freeze==1.0.10 | ||
six==1.11.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import smtplib | ||
import util as u | ||
from socket import gaierror | ||
|
||
AUTH = 'auth' | ||
STARTTLS = 'STARTTLS' | ||
SUPPORTED_AUTH_TYPES = {'PLAIN', 'LOGIN'} | ||
|
||
# Error strings | ||
TLS_NOT_SUPPORTED = u.generate_fatal('SMTP server does not support TLS.') | ||
NO_SMTP_FEATURES = u.generate_fatal('No SMTP features detected.') | ||
NO_AUTH_FEATURES = u.generate_fatal('No AUTH types detected.') | ||
NO_PLAIN_OR_LOGIN_FEATURE = u.generate_fatal('SMTP server does not support AUTH PLAIN or AUTH LOGIN.') | ||
UNABLE_TO_CONNECT = u.generate_fatal('Unable to connect to SMTP server. Check hostname and port.') | ||
|
||
INVALID_CREDENTIALS = 'Error: The server did not accept the username/password combination' | ||
AUTH_NOT_SUPPORTED = 'Error: The AUTH command is not supported by the server' | ||
GENERIC_AUTHENTICATION_EXCEPTION = 'Error: Encountered an error during authentication' | ||
|
||
|
||
def connect(host, port, debug): | ||
socket = host + ':' + port | ||
print('\nAttempting connection to socket (' + socket + ')...') | ||
try: | ||
server = smtplib.SMTP(host, port) | ||
server.set_debuglevel(debug) | ||
print('--> Successfully connected to SMTP server!\n') | ||
return server | ||
except (gaierror, OSError): | ||
print(UNABLE_TO_CONNECT) | ||
exit(1) | ||
|
||
|
||
def start_tls(server): | ||
server.ehlo() | ||
if not server.has_extn(STARTTLS): | ||
print(TLS_NOT_SUPPORTED) | ||
exit(1) | ||
else: | ||
server.starttls() | ||
|
||
|
||
def verify_auth_feature(features): | ||
if not features: | ||
print(NO_SMTP_FEATURES) | ||
exit(1) | ||
elif not features[AUTH]: | ||
print(NO_AUTH_FEATURES) | ||
exit(1) | ||
|
||
|
||
def get_supported_server_auth_types(features): | ||
server_auth_types = features[AUTH].strip().split() | ||
|
||
auth_types = [] | ||
for auth_type in SUPPORTED_AUTH_TYPES: | ||
if auth_type in server_auth_types: | ||
auth_types.append(auth_type) | ||
|
||
if not auth_types: | ||
print(NO_PLAIN_OR_LOGIN_FEATURE) | ||
exit(1) | ||
else: | ||
return auth_types | ||
|
||
|
||
def evaluate_server(server): | ||
server.ehlo() | ||
verify_auth_feature(server.esmtp_features) | ||
|
||
print('Listing supported AUTH types...') | ||
for auth_type in get_supported_server_auth_types(server.esmtp_features): | ||
print("--> " + auth_type) | ||
|
||
|
||
def login(server): | ||
login_attempt = None | ||
while not login_attempt: | ||
try: | ||
username = u.get_required_prompt('\nEnter username: ') | ||
password = u.get_required_prompt('Enter password: ') | ||
login_attempt = server.login(username, password) | ||
except smtplib.SMTPAuthenticationError: | ||
print(INVALID_CREDENTIALS) | ||
except smtplib.SMTPNotSupportedError: | ||
print(AUTH_NOT_SUPPORTED) | ||
except smtplib.SMTPException: | ||
print(GENERIC_AUTHENTICATION_EXCEPTION) | ||
|
||
print('--> ' + login_attempt[1].decode()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import smtplib | ||
import util as u | ||
import raw as r | ||
import smtp as s | ||
|
||
from email.mime.multipart import MIMEMultipart | ||
from email.mime.text import MIMEText | ||
|
||
smtp_host = u.get_required_prompt('SMTP server: ') | ||
smtp_port = r.get_port() | ||
debug_level = r.get_debug_level() | ||
|
||
server = s.connect(smtp_host, smtp_port, debug_level) | ||
|
||
s.start_tls(server) | ||
s.evaluate_server(server) | ||
s.login(server) | ||
|
||
from_address = r.get_from_address() | ||
from_name = r.get_from_name() | ||
to_addresses = r.get_to_addresses() | ||
subject = r.get_subject() | ||
|
||
msg = MIMEMultipart('alternative') | ||
msg.set_charset("utf-8") | ||
|
||
msg["From"] = from_name + "<" + from_address + ">" | ||
msg['Subject'] = subject | ||
msg["To"] = u.COMMASPACE.join(to_addresses) | ||
|
||
load_body_from_file = r.load_body_from_file() | ||
|
||
if load_body_from_file: | ||
filename = r.get_body_filename() | ||
with open(filename) as file: | ||
body = MIMEText(file.read(), 'html') | ||
msg.attach(body) | ||
else: | ||
print('--> Enter HTML line by line.') | ||
print('--> Press CTRL+D on an empty line to finish.)') | ||
html = u.EMPTY_STRING | ||
while True: | ||
try: | ||
line = input("> ") | ||
html += line + "\n" | ||
except EOFError: | ||
print('HTML captured.') | ||
break | ||
body = MIMEText(html, 'html') | ||
msg.attach(body) | ||
|
||
print('\n--> Spoofing from ' + from_address + ' (' + from_name + ')') | ||
print('--> Sending to ' + u.COMMASPACE.join(to_addresses)) | ||
|
||
try: | ||
server.sendmail(from_address, to_addresses, msg.as_string()) | ||
print('\nSuccessfully sent email') | ||
except smtplib.SMTPException: | ||
print('\nError sending email. Check FROM/TO and MESSAGE body.') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from colorama import init | ||
init() | ||
|
||
AFFIRMATIVE_RESPONSES = {'y', 'ye', 'yes'} | ||
ERROR = 'Error: ' | ||
EXITING = ' Exiting...' | ||
EMPTY_STRING = '' | ||
COMMASPACE = ', ' | ||
|
||
|
||
def prompt(text): | ||
return input(text).strip() | ||
|
||
|
||
def get_required_prompt(text): | ||
var = None | ||
while not var: | ||
var = prompt(text) | ||
return var | ||
|
||
|
||
def get_optional_prompt(text, default_value): | ||
var = prompt(text) | ||
if var: | ||
return var | ||
else: | ||
return default_value | ||
|
||
|
||
def convert_answer_to_int(answer): | ||
if answer in AFFIRMATIVE_RESPONSES: | ||
return 1 | ||
return 0 | ||
|
||
|
||
def generate_fatal(message): | ||
return ERROR + message + EXITING |