Skip to content

Commit

Permalink
Refactoring project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
mikechabot committed Aug 8, 2018
1 parent fa6f782 commit b0f72c3
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 266 deletions.
1 change: 0 additions & 1 deletion args/__init__.py

This file was deleted.

2 changes: 0 additions & 2 deletions commands/__init__.py

This file was deleted.

31 changes: 0 additions & 31 deletions commands/wizard.py

This file was deleted.

Empty file added example.html
Empty file.
1 change: 0 additions & 1 deletion models/__init__.py

This file was deleted.

9 changes: 0 additions & 9 deletions setup.py

This file was deleted.

26 changes: 5 additions & 21 deletions spoof.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import sys
from setup import init
from spoofer import conf

init() # Add packages to PYTHONPATH
def main():
args = conf.parser.parse_args()
args.func(args)

from args import config
from commands import cli, wizard

if __name__ == '__main__':
arg_length = len(sys.argv)
if arg_length == 1:
config.parser.print_help() # Print help
exit(1)
elif arg_length == 2:
if 'wizard' in sys.argv:
wizard.run() # Run wizard
elif 'cli' in sys.argv:
config.cli.print_help() # Print CLI help
exit(1)
else:
config.parser.parse_args() # Generate parser warning messages
else:
args = config.parser.parse_args()
cli.run(args) # Run CLI
sys.exit(main())
20 changes: 12 additions & 8 deletions commands/cli.py → spoofer/commands/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import logger
from appheader import print_header
from smtpconnection import SMTPConnection
from ..utils import logger, appdescription
from ..models.smtpconnection import SMTPConnection
from ..utils.userinput import get_yes_no


def run(args):
print_header()
appdescription.print_description()

# Connect to SMTP over TLS
connection = SMTPConnection(args.host, str(args.port))
Expand All @@ -17,9 +17,12 @@ def run(args):
else:
exit(1)

message_body = None
with open(args.filename) as f:
message_body = f.read()
try:
with open(args.filename) as f:
message_body = f.read()
except FileNotFoundError:
logger.error("No such file: " + args.filename)
exit(1)

# Compose MIME message
message = connection.compose_message(
Expand All @@ -30,5 +33,6 @@ def run(args):
message_body
)

connection.send_mail(message)
if get_yes_no('Send message (Y/N)?: ', None):
connection.send_mail(message)

75 changes: 75 additions & 0 deletions spoofer/commands/wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from colorama import Fore
from getpass import getpass
from ..utils import logger, appdescription
from ..utils.userinput import prompt, get_required, get_optional, get_yes_no
from ..models.smtpconnection import SMTPConnection


def run(args):
appdescription.print_description()

host = get_required('SMTP host: ')
port = None;

while not port:
try:
port = int(get_required('SMTP port: '))
if port < 0 or port > 65535:
logger.error('SMTP port is out-of-range (0-65535)')
port = None
except ValueError:
logger.error('SMTP port must be a number')
port = None

# Connect to SMTP over TLS
connection = SMTPConnection(host, str(port))

# Attempt login
if not get_yes_no("Disable authentication (Y/N)?: ", 'n'):
success = False
while not success:
success = connection.login(
get_required('Username: '),
getpass()
)
logger.success('Authentication successful')

sender = get_required('Sender address: ')
sender_name = get_required('Sender name: ');

recipients = [get_required('Recipient address: ')]
if get_yes_no('Enter additional recipients (Y/N)?: ', 'n'):
while recipient:
recipient = get_optional('Recipient address: ', None)
if recipient:
recipients.append(recipient)

subject = get_required('Subject line: ')

html = ''
if get_yes_no('Load message body from file (Y/N)?: ', 'n'):
filename = get_required('Filename: ')
with open(filename) as f:
html = f.read()
else:
logger.info('Enter HTML line by line')
logger.info('To finish, press CTRL+D (*nix) or CTRL-Z (win) on an *empty* line')
while True:
try:
line = prompt('>| ', Fore.LIGHTBLACK_EX)
html += line + '\n'
except EOFError:
logger.success('Captured HTML body')
break

# Compose MIME message
message = connection.compose_message(
sender,
sender_name,
recipients,
subject,
html
)

if get_yes_no('Send message (Y/N)?: ', None):
connection.send_mail(message)
15 changes: 9 additions & 6 deletions args/config.py → spoofer/conf.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import sys
import argparse
from spoofer.commands import cli, wizard

parser = argparse.ArgumentParser(description='Python 3.x based email spoofer', allow_abbrev=False)

# Allowed commands: "wizard" or "cli"
subparsers = parser.add_subparsers(title='commands', dest='command', help='Allowed commands')
subparsers.add_parser('wizard', help='Use the step-by-step wizard')
subparsers = parser.add_subparsers(title='commands', dest='command', help='Allowed commands', required=True)
wizard_subparser = subparsers.add_parser('wizard', help='Use the step-by-step wizard')
wizard_subparser.set_defaults(func=wizard.run)

cli = subparsers.add_parser('cli', help='Pass arguments directly')
cli_subparser = subparsers.add_parser('cli', help='Pass arguments directly')
cli_subparser.set_defaults(func=cli.run)
# cli.add_argument('--host', dest='host', required=True, type=str, help='SMTP hostname')
# cli.add_argument('--port', dest='port', required=True, type=int, help='SMTP port number')

# Mutually exclude "--noauth" and "--username"
noauth_or_username = cli.add_mutually_exclusive_group(required=True)
noauth_or_username = cli_subparser.add_mutually_exclusive_group(required=True)
noauth_or_username.add_argument('--noauth', dest='noauth', action='store_true', help='Disable authentication check')
noauth_or_username.add_argument('--username', dest='username', type=str, help='SMTP username')

# Make password required if "--username" is present
cli.add_argument('--password', dest='password', required='--username' in sys.argv, type=str, help='SMTP password (required with --username)')
cli_subparser.add_argument('--password', dest='password', required='--username' in sys.argv, type=str, help='SMTP password (required with --username)')

required = cli.add_argument_group('required arguments')
required = cli_subparser.add_argument_group('required arguments')
required.add_argument('--host', dest='host', required=True, type=str, help='SMTP hostname')
required.add_argument('--port', dest='port', type=int, required=True, help='SMTP port number')

Expand Down
50 changes: 12 additions & 38 deletions models/smtpconnection.py → spoofer/models/smtpconnection.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,22 @@
import smtplib
import logger
from socket import gaierror
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

COMMASPACE = ', '
from ..utils import logger

class SMTPConnection:
def __init__(self, host, port):
self._host = host
self._port = port
self._socket = host + ':' + port
self._server = None
self._sender = None
self._recipients = None
self.host = host
self.port = port
self.socket = host + ':' + port
self.server = None
self.sender = None
self.recipients = None

self.__connect()
self.__start_tls()
self.__eval_server_features()

@property
def host(self):
return self._host

@property
def port(self):
return self._port

@property
def server(self):
return self._server

@property
def socket(self):
return self._socket

@property
def sender(self):
return self._sender

@property
def recipients(self):
return self._recipients

def __ehlo(self):
try:
self.server.ehlo()
Expand All @@ -56,7 +30,7 @@ def __ehlo(self):
def __connect(self):
try:
logger.info('Connecting to SMTP socket (' + self.socket + ')...')
self._server = smtplib.SMTP(self.host, self.port)
self.server = smtplib.SMTP(self.host, self.port)
except (gaierror, OSError):
logger.error('Unable to establish connection to SMTP socket.')
exit(1)
Expand Down Expand Up @@ -102,15 +76,15 @@ def login(self, username, password):
exit(1)

def compose_message(self, sender, name, recipients, subject, html):
self._sender = sender
self._recipients = recipients
self.sender = sender
self.recipients = recipients

message = MIMEMultipart('alternative')
message.set_charset("utf-8")

message["From"] = name + "<" + sender + ">"
message["From"] = name + "<" + self.sender + ">"
message['Subject'] = subject
message["To"] = COMMASPACE.join(recipients)
message["To"] = ', '.join(self.recipients)

body = MIMEText(html, 'html')
message.attach(body)
Expand Down
12 changes: 12 additions & 0 deletions spoofer/utils/appdescription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from . import logger

description = """ email-spoofer-py v0.0.3 (CLI wizard)
Python 3.x based email spoofer
https://github.com/mikechabot/email-spoofer-py"""

def print_description():
logger.bright('\n{0}'.format('='*50))
logger.header(description)
logger.bright('{0}\n'.format('='*50))


File renamed without changes.
45 changes: 45 additions & 0 deletions spoofer/utils/userinput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from colorama import Fore, Style
from . import logger


def prompt(text, color):
try:
print(color, end='')
return input(text).strip()
except KeyboardInterrupt:
logger.error('\nInterrupt received. Exiting...')
exit(1)
finally:
print(Style.RESET_ALL, end='')


def get_required(text):
var = None
while not var:
var = prompt(text, Fore.WHITE)
return var


def get_optional(text, default_value):
var = prompt(text, Fore.WHITE)
if var:
return var
else:
return default_value


def get_yes_no(text, default_value):
if not default_value:
val = get_required(text)
else:
val = get_optional(text, default_value)
return _convert_answer_to_int(val)


def _convert_answer_to_int(answer):
if answer.lower() in {'y', 'ye', 'yes'}:
return 1
return 0



3 changes: 0 additions & 3 deletions utils/__init__.py

This file was deleted.

12 changes: 0 additions & 12 deletions utils/appheader.py

This file was deleted.

Loading

2 comments on commit b0f72c3

@rome138
Copy link

@rome138 rome138 commented on b0f72c3 Feb 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not working, tried so manything but always get these traceback errors

Traceback (most recent call last):
File ".\spoof.py", line 2, in
from spoofer import conf

subparsers = parser.add_subparsers(title='commands', dest='command', help='Allowed commands', required=True)

...

in add_subparsers
action = parsers_class(option_strings=[], **kwargs)
TypeError: init() got an unexpected keyword argument 'required'

@mikechabot
Copy link
Owner Author

@mikechabot mikechabot commented on b0f72c3 Feb 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rome138 What's the command you're running? Are you able to reproduce using the wizard?

Please sign in to comment.