diff options
| -rw-r--r-- | Makefile | 35 | ||||
| -rw-r--r-- | emailcanary/__init__.py | 0 | ||||
| -rw-r--r-- | emailcanary/canary.py | 33 | ||||
| -rw-r--r-- | emailcanary/canarydb.py | 41 | ||||
| -rw-r--r-- | emailcanary/email-digest-sender.py | 38 | ||||
| -rw-r--r-- | emailcanary/emailutils.py | 19 | ||||
| -rw-r--r-- | requirements.txt | 3 | ||||
| -rw-r--r-- | setup.py | 22 | ||||
| -rw-r--r-- | tests/__init__.py | 0 | ||||
| -rw-r--r-- | tests/test_canarydb.py | 66 | 
10 files changed, 257 insertions, 0 deletions
| diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e365f62 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +PYTHON=`which python` +NAME=`python setup.py --name` + +all: check test source deb + +init: +	pip install -r requirements.txt --use-mirrors + +dist: source deb + +source: +	$(PYTHON) setup.py sdist + +deb: +	$(PYTHON) setup.py --command-packages=stdeb.command bdist_deb + +rpm: +	$(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall + +test: +	unit2 discover -s tests -t . +	python -mpytest weasyprint + +check: +	find . -name \*.py | grep -v "^test_" | xargs pylint --errors-only --reports=n +	# pep8 +	# pyntch +	# pyflakes +	# pychecker +	# pymetrics + +clean: +	$(PYTHON) setup.py clean +	rm -rf build/ MANIFEST dist build my_program.egg-info deb_dist +	find . -name '*.pyc' -delete
\ No newline at end of file diff --git a/emailcanary/__init__.py b/emailcanary/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/emailcanary/__init__.py diff --git a/emailcanary/canary.py b/emailcanary/canary.py new file mode 100644 index 0000000..d07e2e9 --- /dev/null +++ b/emailcanary/canary.py @@ -0,0 +1,33 @@ +import uuid, datetime, time +import email +import re + +class Canary: +	def __init__(self, db, smtp, fromaddress): +		self.db = db +		self.smtp = smtp +		self.fromaddress = fromaddress + +	def chirp(self, list, expectedreceipients): +		uuid = uuid.uuid4() +		now = datetime.datetime.now() + +		self.send(list, now, uuid) +		for dest in expectedreceipients: +			self.db.ping(dest, now, uuid) + +	def echo(self, receipient, msg): +		uuid = re.match('Canary Email (.+)', msg['Subject']).group(1) +		now = datetime.datetime.now() + +		self.db.pong(receipient, now, uuid) + + +	def send(self, dest, date, uuid): +		msg = email.message.Message() +		msg['From'] = self.fromaddress +		msg['To'] = dest +		msg['Subject'] = "Canary Email " + str(uuid) +		msg['Date'] = email.utils.formatdate(time.mktime(date.timetuple())) + +		self.smtp.sendmail(self.fromaddress, dest, msg.as_string())
\ No newline at end of file diff --git a/emailcanary/canarydb.py b/emailcanary/canarydb.py new file mode 100644 index 0000000..112b8f5 --- /dev/null +++ b/emailcanary/canarydb.py @@ -0,0 +1,41 @@ +import sqlite3 +from datetime import datetime + +class CanaryDB: +	def __init__(self, filename): +		self.conn = sqlite3.connect(filename, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) + +		# Create Tables if necessary +		cursor = self.conn.cursor() +		cursor.execute("CREATE TABLE IF NOT EXISTS pings (address, uuid, timesent timestamp, timereceived timestamp)") + +	def close(self): +		self.conn.close() + +	def ping(self, address, time, uuid): +		cursor = self.conn.cursor() +		cursor.execute("INSERT INTO pings (address, timesent, uuid) VALUES (?, ?, ?)", \ +			(address, time, uuid)) +		self.conn.commit() + +	def pong(self, address, time, uuid): +		cursor = self.conn.cursor() +		cursor.execute("UPDATE pings SET timereceived=? WHERE address=? AND uuid=?", \ +			(time, address, uuid)) +		self.conn.commit() + +	def get_missing_pongs(self): +		'''Return a list of tupls of missing pongs. +		   Each tupl contains (uuid, address, time since send)''' +		cursor = self.conn.cursor() +		cursor.execute("SELECT uuid, address, timesent FROM pings WHERE timereceived IS NULL"); + +		now = datetime.now() +		results = list() +		for row in cursor: +			uuid = row[0] +			address = row[1] +			delta = now - row[2] +			results.append((uuid, address, delta)) + +		return results diff --git a/emailcanary/email-digest-sender.py b/emailcanary/email-digest-sender.py new file mode 100644 index 0000000..f1583c0 --- /dev/null +++ b/emailcanary/email-digest-sender.py @@ -0,0 +1,38 @@ +import emailutils +import smtplib +from email import message + +PASSWORD="secret" +ACCOUNTS = [('mail.example.com', 'email@example.com', PASSWORD)] +DESTINATION="other@example.com" + +youve_got_mail = False +all_subjects = {} + +for account in ACCOUNTS: +	mail = emailutils.get_imap(account) +	these_subjects = [] +	for uid in emailutils.get_mail_uids(mail): +		message = emailutils.get_message(mail, uid) +		these_subjects.append(message['subject']) +		youve_got_mail = True +	all_subjects[account[1]] = these_subjects + + +if youve_got_mail: +	msg = "" +	for account in all_subjects: +		msg = msg + "# Messages for %s\n" % account +		for subject in all_subjects[account]: +			msg = msg + " * %s\n" % subject +		msg = msg + "\n" + +	digest_message = message.Message() +	digest_message.set_payload(msg) +	digest_message['From'] = DESTINATION +	digest_message['To'] = DESTINATION +	digest_message['Subject'] = "Email Digest" + +	s = smtplib.SMTP_SSL('localhost', 2465) +	s.sendmail(DESTINATION, DESTINATION, digest_message.as_string()) +	s.quit()
\ No newline at end of file diff --git a/emailcanary/emailutils.py b/emailcanary/emailutils.py new file mode 100644 index 0000000..6646c1e --- /dev/null +++ b/emailcanary/emailutils.py @@ -0,0 +1,19 @@ +import imaplib, email + +def get_imap(account): +	'''Connect and login via IMAP''' +	mail = imaplib.IMAP4_SSL(account[0]) +	mail.login(account[1],  account[2]) +	return mail + +def get_mail_uids(mail): +	'''Return a list of message UIDs in the inbox''' +	mail.select("inbox") # connect to inbox. +	result, data = mail.uid('search', None, "ALL") # search and return uids instead +	return data[0].split() + +def get_message(mail, uid): +	'''Get a single email message object by UID''' +	result, data = mail.uid('fetch', uid, '(RFC822)') +	raw_email = data[0][1] +	return email.message_from_string(raw_email)
\ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e51ea6d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +setuptools +pylint +unittest2
\ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bc53c06 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +setup(name='emailcanary', +      version='0.1', +      author='Jesse Morgan', +      author_email='jesse@jesterpm.net', +      url='https://jesterpm.net', +      download_url='http://www.my_program.org/files/', +      description='Email Canary sends emails to a distribution list and checks for proper distribution.', + +      packages = find_packages(), +      include_package_data = True, +      exclude_package_data = { '': ['README.txt'] }, +       +      scripts = ['bin/emailcanary'], +       +      license='MIT', +                   +      #setup_requires = ['python-stdeb', 'fakeroot', 'python-all'], +      install_requires = ['setuptools'], +     )
\ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/__init__.py diff --git a/tests/test_canarydb.py b/tests/test_canarydb.py new file mode 100644 index 0000000..5a0fc23 --- /dev/null +++ b/tests/test_canarydb.py @@ -0,0 +1,66 @@ +import unittest +import tempfile, shutil +import datetime + +from emailcanary import canarydb + +class TestCanaryDB(unittest.TestCase): +	def setUp(self): +		self.tempdir = tempfile.mkdtemp() +		self.db = canarydb.CanaryDB(self.tempdir + "canary.db") + +	def tearDown(self): +		self.db.close() +		shutil.rmtree(self.tempdir) + +	def testPingCheckPong(self): +		address = "test@example.com" +		time = datetime.datetime(2015, 10, 24, 9, 00) +		uuid = "1234" +		expectedDelta = datetime.datetime.now() - time + +		# Record a Ping +		self.db.ping(address, time, uuid) + +		# Check for missing pongs +		missing = self.db.get_missing_pongs() + +		self.assertEqual(1, len(missing)) +		firstMissing = missing[0] +		self.assertEqual(3, len(firstMissing)) +		self.assertEqual(uuid, firstMissing[0]) +		self.assertEqual(address, firstMissing[1]) +		delta = firstMissing[2].total_seconds() - expectedDelta.total_seconds() +		self.assertTrue(delta <= 10) + +		# Record a pong +		pongtime = datetime.datetime(2015, 10, 24, 9, 05) +		self.db.pong(address, pongtime, uuid) + +		# Check for missing pongs +		missing = self.db.get_missing_pongs() +		self.assertEqual(0, len(missing)) + +	def testCloseReopen(self): +		address = "test@example.com" +		time = datetime.datetime(2015, 10, 24, 9, 00) +		uuid = "1234" +		expectedDelta = datetime.datetime.now() - time + +		# Record a Ping +		self.db.ping(address, time, uuid) + +		# Close, Reopen +		self.db.close() +		self.db = canarydb.CanaryDB(self.tempdir + "canary.db") + +		# Check for missing pongs +		missing = self.db.get_missing_pongs() + +		self.assertEqual(1, len(missing)) +		firstMissing = missing[0] +		self.assertEqual(3, len(firstMissing)) +		self.assertEqual(uuid, firstMissing[0]) +		self.assertEqual(address, firstMissing[1]) +		delta = firstMissing[2].total_seconds() - expectedDelta.total_seconds() +		self.assertTrue(delta <= 10) | 
