Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

Commit

Permalink
fixed tests, style cleanup, and a .travis.yml file
Browse files Browse the repository at this point in the history
  • Loading branch information
Roguelazer committed Apr 20, 2015
1 parent c6733cb commit a4f53fb
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 111 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -1,2 +1,5 @@
*.pyc
build/
dist/
sdist/
env/
21 changes: 21 additions & 0 deletions .travis.yml
@@ -0,0 +1,21 @@
sudo: false
language: python
python:
- "2.6"
- "2.7"
env:
- TORNADO_VERSION=1.2.1
- TORNADO_VERSION=2.4.1
- TORNADO_VERSION=3.2.2
- TORNADO_VERSION=4.0.2
- TORNADO_VERSION=4.1.0
install:
- "pip install -r requirements.txt -r requirements-tests.txt --use-mirrors"
- "pip install tornado==$TORNADO_VERSION"
# only build master and PRs, not every branch
branches:
only:
- master
script: "testify -v tests"
matrix:
fast_finish: true
2 changes: 1 addition & 1 deletion bin/fakemtpd
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/env python

import fakemtpd
import fakemtpd.server
Expand Down
56 changes: 28 additions & 28 deletions fakemtpd/config.py
@@ -1,12 +1,13 @@
from __future__ import with_statement

import copy
import new
import itertools
import os.path
import socket
import ssl
import yaml


def _param_getter_factory(parameter):
def f(self):
return self._config[parameter]
Expand All @@ -28,6 +29,7 @@ def __new__(clsarg, name, bases, d):
setattr(cls, parameter, property(f))
return cls


class Config(object):
"""Singleton class for implementing configuration. Use the instance
method to get handles to it. Supports loading options from both objects
Expand All @@ -42,32 +44,31 @@ class Config(object):
smtp_versions = ('SMTP', 'ESMTP')

"""Known SSL versions"""
ssl_versions = ('ssl2', 'ssl3', 'tls1')

ssl_versions = ('ssl2', 'ssl3', 'ssl23', 'tls1')

# Parameters and their defaults. All of these can be overridden
# via the YAML configuration. Some can also be overridden via
# command-line options
_parameters = {
'port': 25,
'address': '0.0.0.0',
'user': None,
'group': None,
'hostname': socket.gethostname(),
'verbose': 0,
'mtd': 'FakeMTPD',
'smtp_ver': 'SMTP',
'tls_cert': None,
'tls_key': None,
'timeout': 30,
'daemonize': False,
'pid_file': None,
'log_file': None,
'logging_method': 'stderr',
'ssl_version': 'ssl3',
'syslog_host': 'localhost',
'syslog_port': 514,
'syslog_domain_socket': None,
'port': 25,
'address': '0.0.0.0',
'user': None,
'group': None,
'hostname': socket.gethostname(),
'verbose': 0,
'mtd': 'FakeMTPD',
'smtp_ver': 'SMTP',
'tls_cert': None,
'tls_key': None,
'timeout': 30,
'daemonize': False,
'pid_file': None,
'log_file': None,
'logging_method': 'stderr',
'ssl_version': 'ssl23',
'syslog_host': 'localhost',
'syslog_port': 514,
'syslog_domain_socket': None,
}

def __init__(self):
Expand Down Expand Up @@ -140,12 +141,11 @@ def port(self):

@property
def ssl_version(self):
if self._config['ssl_version'] == 'ssl3':
return ssl.PROTOCOL_SSLv3
elif self._config['ssl_version'] == 'tls1':
return ssl.PROTOCOL_TLSv1
else:
return ssl.PROTOCOL_SSLv23
parts = ["".join(b) for _, b in itertools.groupby(
self._config['ssl_version'], str.isdigit
)]
attr = 'PROTOCOL_%sV%s' % (parts[0], parts[1])
return getattr(ssl, attr)

@property
def syslog_connection(self):
Expand Down
85 changes: 51 additions & 34 deletions fakemtpd/server.py
Expand Up @@ -19,6 +19,7 @@
from fakemtpd.smtpsession import SMTPSession
from fakemtpd.signals import Signalable


class SMTPD(Signalable):
_signals = ('stop', 'hup', 'stop_user')

Expand All @@ -30,39 +31,55 @@ def __init__(self):

def handle_opts(self):
parser = optparse.OptionParser()
parser.add_option('-c', '--config-path', action='store', default=None,
help='Path to a YAML configuration file (overridden by any conflicting args)')
parser.add_option('-p', '--port', dest='port', action='store', type=int, default=self.config.port,
help='Port to listen on (default %default)')
parser.add_option('-H', '--hostname', dest='hostname', action='store', default=self.config.hostname,
help='Hostname to report as (default %default)')
parser.add_option('-B', '--bind', dest='address', action='store', default=self.config.address,
help='Address to bind to (default "%default")')
parser.add_option('-v', '--verbose', action='count', default=self.config.verbose,
help='Be more verbose')
parser.add_option('--tls-cert', action='store', default=self.config.tls_cert,
help='Certificate to use for TLS')
parser.add_option('--tls-key', action='store', default=self.config.tls_key,
help='Key to use for TLS')
parser.add_option('--gen-config', action='store_true', default=False,
help='Print out a config file with all parameters')
parser.add_option('--smtp-ver', action='store', type='choice', choices=self.config.smtp_versions, default=self.config.smtp_ver,
help='SMTP version (one of (%s)), default %s' % (','.join(self.config.smtp_versions), self.config.smtp_ver))
parser.add_option('-d', '--daemonize', action='store_true', default=self.config.daemonize,
help='Damonize (must also specify a pid_file)')
parser.add_option('--pid-file', action='store', default=self.config.pid_file,
help='PID File')
parser.add_option('--logging-method', type='choice', action='store', default=self.config.logging_method,
choices=self.config.logging_methods,
help="Logging method, must be one of (%s), default %s" % (','.join(self.config.logging_methods), self.config.logging_method))
parser.add_option('--log-file', action='store', default=self.config.log_file,
help="File to write logs to (only valid if logging method is 'file')")
parser.add_option('--syslog-domain-socket', action='store', default=self.config.syslog_domain_socket,
help="Syslog domain socket to write to (default %default, overrides syslog host if provided)")
parser.add_option('--syslog-host', action='store', default=self.config.syslog_host,
help="Syslog host to write to (default %default, only valid if logging method is 'syslog')")
parser.add_option('--syslog-port', type=int, action='store', default=self.config.syslog_port,
help="Syslog port to write to (default %default, only valid of logging method is 'syslog')")
parser.add_option(
'-c', '--config-path', action='store', default=None,
help='Path to a YAML configuration file (overridden by any conflicting args)')
parser.add_option(
'-p', '--port', dest='port', action='store', type=int, default=self.config.port,
help='Port to listen on (default %default)')
parser.add_option(
'-H', '--hostname', dest='hostname', action='store', default=self.config.hostname,
help='Hostname to report as (default %default)')
parser.add_option(
'-B', '--bind', dest='address', action='store', default=self.config.address,
help='Address to bind to (default "%default")')
parser.add_option(
'-v', '--verbose', action='count', default=self.config.verbose,
help='Be more verbose')
parser.add_option(
'--tls-cert', action='store', default=self.config.tls_cert,
help='Certificate to use for TLS')
parser.add_option(
'--tls-key', action='store', default=self.config.tls_key,
help='Key to use for TLS')
parser.add_option(
'--gen-config', action='store_true', default=False,
help='Print out a config file with all parameters')
parser.add_option(
'--smtp-ver', action='store', type='choice', choices=self.config.smtp_versions, default=self.config.smtp_ver,
help='SMTP version (one of (%s)), default %s' % (','.join(self.config.smtp_versions), self.config.smtp_ver))
parser.add_option(
'-d', '--daemonize', action='store_true', default=self.config.daemonize,
help='Damonize (must also specify a pid_file)')
parser.add_option(
'--pid-file', action='store', default=self.config.pid_file,
help='PID File')
parser.add_option(
'--logging-method', type='choice', action='store', default=self.config.logging_method,
choices=self.config.logging_methods,
help="Logging method, must be one of (%s), default %s" % (','.join(self.config.logging_methods), self.config.logging_method))
parser.add_option(
'--log-file', action='store', default=self.config.log_file,
help="File to write logs to (only valid if logging method is 'file')")
parser.add_option(
'--syslog-domain-socket', action='store', default=self.config.syslog_domain_socket,
help="Syslog domain socket to write to (default %default, overrides syslog host if provided)")
parser.add_option(
'--syslog-host', action='store', default=self.config.syslog_host,
help="Syslog host to write to (default %default, only valid if logging method is 'syslog')")
parser.add_option(
'--syslog-port', type=int, action='store', default=self.config.syslog_port,
help="Syslog port to write to (default %default, only valid of logging method is 'syslog')")
(opts, _) = parser.parse_args()
return opts

Expand Down Expand Up @@ -160,7 +177,7 @@ def run(self, handle_opts=True):
self.on_stop(lambda: self.log_file.flush())
self.on_stop(lambda: self.log_file.close())
self.on_hup(lambda: self._reopen_log_files())
except IOError, e:
except IOError:
self.die("Could not access log file %s" % self.config.log_file)
self._log_fmt = '\t'.join((
'fakemtpd',
Expand Down
49 changes: 25 additions & 24 deletions fakemtpd/smtpsession.py
Expand Up @@ -10,21 +10,22 @@
SMTP_MAIL_FROM = 3

# Command REs
MAIL_FROM_COMMAND=re.compile(r'MAIL\s+FROM:\s*<([^>]*)>', re.I)
HELO_COMMAND=re.compile(r'^HELO\s+(.*)', re.I)
EHLO_COMMAND=re.compile(r'^EHLO\s+(.*)', re.I)
RCPT_TO_COMMAND=re.compile(r'^RCPT\s+TO:\s*<([^>]+)>', re.I)
VRFY_COMMAND=re.compile(r'^VRFY (<?.+>?)', re.I)
QUIT_COMMAND=re.compile(r'^QUIT', re.I)
NOOP_COMMAND=re.compile(r'^NOOP', re.I)
RSET_COMMAND=re.compile(r'^RSET', re.I)
DATA_COMMAND=re.compile(r'^DATA', re.I)
HELP_COMMAND=re.compile(r'^HELP', re.I)
EXPN_COMMAND=re.compile(r'^EXPN', re.I)
STARTTLS_COMMAND=re.compile(r'^STARTTLS', re.I)
MAIL_FROM_COMMAND = re.compile(r'MAIL\s+FROM:\s*<([^>]*)>', re.I)
HELO_COMMAND = re.compile(r'^HELO\s+(.*)', re.I)
EHLO_COMMAND = re.compile(r'^EHLO\s+(.*)', re.I)
RCPT_TO_COMMAND = re.compile(r'^RCPT\s+TO:\s*<([^>]+)>', re.I)
VRFY_COMMAND = re.compile(r'^VRFY (<?.+>?)', re.I)
QUIT_COMMAND = re.compile(r'^QUIT', re.I)
NOOP_COMMAND = re.compile(r'^NOOP', re.I)
RSET_COMMAND = re.compile(r'^RSET', re.I)
DATA_COMMAND = re.compile(r'^DATA', re.I)
HELP_COMMAND = re.compile(r'^HELP', re.I)
EXPN_COMMAND = re.compile(r'^EXPN', re.I)
STARTTLS_COMMAND = re.compile(r'^STARTTLS', re.I)

log = logging.getLogger("smtpsession")


class SMTPSession(object):
"""Implement the SMTP protocol on top of a Connection"""

Expand Down Expand Up @@ -75,7 +76,7 @@ def _handle_data(self, data):
rv = self._state_helo(data) or rv
elif self._state == SMTP_MAIL_FROM:
rv = self._state_mail_from(data)
if rv == False:
if rv is False:
self._write("503 Commands out of sync or unrecognized")
log.warn("Bad command '%s' from %s" % (data, self.conn.address))
self._state = SMTP_HELO if self._state >= SMTP_HELO else SMTP_CONNECTED
Expand Down Expand Up @@ -183,17 +184,17 @@ def _print_timeout(self):
def write_help(self):
self._write("250 Ok")
message = [
"HELO",
"EHLO",
"HELP",
"NOOP",
"QUIT",
"MAIL FROM:<address>",
"RCPT TO:<address>",
"DATA",
"VRFY",
"EXPN",
"RSET",
"HELO",
"EHLO",
"HELP",
"NOOP",
"QUIT",
"MAIL FROM:<address>",
"RCPT TO:<address>",
"DATA",
"VRFY",
"EXPN",
"RSET",
]
if self.config.tls_cert:
message.append("STARTTLS")
Expand Down
1 change: 1 addition & 0 deletions requirements-tests.txt
@@ -0,0 +1 @@
testify==0.6.0
4 changes: 4 additions & 0 deletions requirements.txt
@@ -0,0 +1,4 @@
PyYAML==3.10
lockfile>=0.7
tornado>=1.0,<4.2
daemon==1.1
2 changes: 2 additions & 0 deletions setup.cfg
@@ -0,0 +1,2 @@
[flake8]
max-line-length=160
36 changes: 18 additions & 18 deletions setup.py
@@ -1,22 +1,22 @@
from distutils.core import setup

setup(
name="fakemtpd",
version="0.2.3",
provides="fakemtpd",
author="James Brown",
author_email="jbrown@yelp.com",
url="http://github.com/Roguelazer/fakemtpd",
description="Fake SMTP Daemon",
classifiers = [
"Programming Language :: Python",
"Operating System :: POSIX",
"License :: OSI Approved :: ISC License (ISCL)",
"Intended Audience :: System Administrators",
"Topic :: Communications :: Email",
"Development Status :: 2 - Pre-Alpha"
],
requires = [ "tornado (>=1.0)", "lockfile (>=0.7)", "yaml" ],
packages = [ "fakemtpd" ],
scripts = [ "bin/fakemtpd" ],
name="fakemtpd",
version="0.2.3",
provides="fakemtpd",
author="James Brown",
author_email="jbrown@yelp.com",
url="http://github.com/Roguelazer/fakemtpd",
description="Fake SMTP Daemon",
classifiers=[
"Programming Language :: Python",
"Operating System :: POSIX",
"License :: OSI Approved :: ISC License (ISCL)",
"Intended Audience :: System Administrators",
"Topic :: Communications :: Email",
"Development Status :: 2 - Pre-Alpha"
],
requires=["tornado (>=1.0)", "lockfile (>=0.7)", "yaml", "daemon"],
packages=["fakemtpd"],
scripts=["bin/fakemtpd"],
)

0 comments on commit a4f53fb

Please sign in to comment.