235 lines
6.5 KiB
Python
Executable File
235 lines
6.5 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import time
|
|
import asyncore
|
|
import select
|
|
import smtpd
|
|
import smtplib
|
|
import imaplib
|
|
import email
|
|
from email.mime.text import MIMEText
|
|
import threading
|
|
import sys
|
|
sys.path.append("../../../")
|
|
|
|
from wtbase import SpamGenerator, DecodingException
|
|
from ether2any import Ether2Any
|
|
from ether2any.helper import getDstMacFromPkt, isBroadcast, binToHexStr
|
|
from conf import Conf, ENCRYPTION_NONE, ENCRYPTION_STARTTLS, ENCRYPTION_SSL
|
|
|
|
# Todo
|
|
# Error checking at some places
|
|
# Check for closed imap/smtp connections
|
|
|
|
class NetMailHandler():
|
|
devWriteMutex = threading.Lock()
|
|
""" Parse, decode and write incoming mails to output. """
|
|
def __init__(self, dev, allowFrom, allowTo):
|
|
self.dev = dev
|
|
self.allowFrom = allowFrom
|
|
self.allowTo = allowTo
|
|
self.generator = SpamGenerator()
|
|
|
|
def receiveMail(self, mailfrom, mailto, data):
|
|
# FIXME: mailfrom can be None
|
|
mail = email.email.message_from_string(data)
|
|
|
|
# try to harvest text/plain part of mail
|
|
if mail.get_content_type() == "text/plain":
|
|
self._handleText(mail.get_payload())
|
|
elif mail.get_content_type() == "multipart/alternative":
|
|
for msg in mail.get_payload():
|
|
if msg.get_content_type() == "text/plain":
|
|
self.handleText(msg.get_payload())
|
|
elif self.allowFrom == None and self.allowTo == None:
|
|
self._handleText(data)
|
|
else:
|
|
pass
|
|
|
|
def _handleText(self, text):
|
|
data = None
|
|
# FIXME: Where do these "\n " or 0a 20 come from?
|
|
# Seem to occure only when smtplib sends (long) mails to smtpd
|
|
text = text.replace("\n ", "")
|
|
try:
|
|
data = self.generator.decode(text)
|
|
except DecodingException, e:
|
|
print "Error: Could not decode text! See error below"
|
|
print " < ----------- 8< ----------- > "
|
|
print e
|
|
print " < ----------- 8< ----------- > "
|
|
if data:
|
|
self.devWriteMutex.acquire()
|
|
self.dev.write(data)
|
|
self.devWriteMutex.release()
|
|
|
|
class SimpleSMTPServer(smtpd.SMTPServer):
|
|
""" Simple small SMTP Server, gives mails to a handler. """
|
|
def __init__(self, handler, *args, **kwargs):
|
|
smtpd.SMTPServer.__init__(self, *args, **kwargs)
|
|
self._handler = handler
|
|
def process_message(self, peer, mailfrom, mailto, data):
|
|
# give mail to handler
|
|
self._handler.receiveMail(mailfrom, mailto, data)
|
|
|
|
class SMTPServerThread(threading.Thread):
|
|
def __init__(self, listen, handler):
|
|
threading.Thread.__init__(self)
|
|
self.server = SimpleSMTPServer(handler, listen, None)
|
|
|
|
def run(self):
|
|
asyncore.loop()
|
|
|
|
class SimpleIMAPClient(threading.Thread):
|
|
def __init__(self, imapConf, mailTo, handler):
|
|
threading.Thread.__init__(self)
|
|
self.imapConf = imapConf
|
|
self.imap = None
|
|
self.quit = False
|
|
self.mailTo = mailTo
|
|
self.handler = handler
|
|
|
|
self.idleTagNum = 0
|
|
|
|
def connect(self):
|
|
if self.imapConf['crypto'] == ENCRYPTION_SSL:
|
|
self.imap = imaplib.IMAP4_SSL(self.imapConf['server'], 993)
|
|
else:
|
|
self.smtp = imaplib.IMAP4(self.imapConf['server'])
|
|
|
|
if self.imapConf['crypto'] == ENCRYPTION_STARTTLS:
|
|
self.imap.starttls()
|
|
|
|
if self.imapConf['authentication']:
|
|
self.imap.login(self.imapConf['user'], self.imapConf['password'])
|
|
|
|
ret = self.imap.select(self.imapConf['folder'])
|
|
if ret[0] != 'OK':
|
|
print "Error!"
|
|
|
|
def fetchNewMailToDev(self):
|
|
l = self.imap.search(None, 'UNSEEN')
|
|
newMsgIds = l[1][0].replace(" ", ",")
|
|
if newMsgIds == '':
|
|
return False
|
|
msgs = self.imap.fetch(newMsgIds, '(RFC822)')
|
|
for msg in msgs[1]:
|
|
if msg == ")":
|
|
# where does this come from...?
|
|
continue
|
|
if len(msg) != 2:
|
|
print "Warning: Message broken, %d values in list, text '%s'" % (len(msg), msg)
|
|
continue
|
|
(flags, data) = msg
|
|
self.handler.receiveMail(None, self.mailTo, data)
|
|
return (len(msgs) > 0)
|
|
|
|
def run(self):
|
|
self.connect()
|
|
|
|
while not self.quit:
|
|
print "New IMAP loop"
|
|
tries = 0
|
|
while tries < len(self.imapConf['mailWait']):
|
|
# get new mail
|
|
if self.fetchNewMailToDev():
|
|
tries = 0
|
|
else:
|
|
time.sleep(self.imapConf['mailWait'][tries])
|
|
tries += 1
|
|
# go into idle mode
|
|
# NOT IMPLEMENTED
|
|
if self.imapConf['useIDLE']:
|
|
print "Going into IDLE mode..."
|
|
self.idleTagNum += 1
|
|
idleTag = "a%04d" % self.idleTagNum
|
|
self.imap.send("%s IDLE\r\n" % (idleTag,))
|
|
quitLoop = False
|
|
while not quitLoop:
|
|
(r, w, e) = select.select([self.imap.socket()], [], [])
|
|
msg = self.imap.readline()
|
|
# TODO: Check if this filters out all idle "no new message" status msgs
|
|
if not msg.startswith("+") and not msg.startswith("* OK"):
|
|
#if msg.find("RECENT") >= 0
|
|
quitLoop = True
|
|
self.imap.send("DONE\r\n")
|
|
# clear away ack msg
|
|
msg = self.imap.readline()
|
|
while msg.find(idleTag) < 0:
|
|
msg = self.imap.readline()
|
|
|
|
class MailTunnel(Ether2Any):
|
|
def __init__(self):
|
|
Ether2Any.__init__(self, tap=Conf.get('tunnelEthernet', True))
|
|
|
|
handlerConf = Conf.get('handler', {'allowFrom': None, 'allowTo': None})
|
|
self.mailHandler = NetMailHandler(self.dev, **handlerConf)
|
|
self.mailTo = Conf.get('mailTo', None)
|
|
self.mailFrom = Conf.get('mailFrom', None)
|
|
|
|
self.smtpConf = Conf.get('smtp')
|
|
smtpd = Conf.get("smtpd", {'enabled': False})
|
|
if smtpd['enabled']:
|
|
self.smtpd = SMTPServerThread(smtpd['listen'], self.mailHandler)
|
|
else:
|
|
self.smtpd = None
|
|
|
|
imapConf = Conf.get("imap", {'enabled': False})
|
|
if imapConf['enabled']:
|
|
self.imap = SimpleIMAPClient(imapConf, self.mailTo, self.mailHandler)
|
|
else:
|
|
self.imap = None
|
|
|
|
self.generator = SpamGenerator()
|
|
|
|
network = Conf.get('network', {'mtu': 1400})
|
|
self.dev.ifconfig(**network)
|
|
|
|
def connectSMTP(self):
|
|
if self.smtpConf['crypto'] == ENCRYPTION_SSL:
|
|
self.smtp = smtplib.SMTP_SSL(self.smtpConf['server'], 465)
|
|
else:
|
|
self.smtp = smtplib.SMTP(self.smtpConf['server'])
|
|
|
|
if self.smtpConf['crypto'] == ENCRYPTION_STARTTLS:
|
|
self.smtp.starttls()
|
|
|
|
if self.smtpConf['authentication']:
|
|
self.smtp.login(self.smtpConf['user'], self.smtpConf['password'])
|
|
|
|
def sendMail(self, fromAddr, toAddrs, subject, msg):
|
|
e = MIMEText(msg)
|
|
e['Subject'] = subject
|
|
e['From'] = fromAddr
|
|
e['To'] = ",\n".join(toAddrs)
|
|
try:
|
|
self.smtp.sendmail(fromAddr, toAddrs, e.as_string())
|
|
except smtplib.SMTPServerDisconnected:
|
|
self.connectSMTP()
|
|
self.smtp.sendmail(fromAddr, toAddrs, e.as_string())
|
|
|
|
def sendToNet(self, packet):
|
|
data = self.generator.encode(packet)
|
|
self.sendMail(self.mailFrom, [self.mailTo], "Ohai!", data)
|
|
|
|
def sendToDev(self, socket):
|
|
pass
|
|
|
|
def run(self):
|
|
# start threads / connections
|
|
self.connectSMTP()
|
|
|
|
if self.imap:
|
|
self.imap.start()
|
|
|
|
if self.smtpd:
|
|
self.smtpd.start()
|
|
|
|
# call super method
|
|
Ether2Any.run(self)
|
|
|
|
if __name__ == '__main__':
|
|
mailtun = MailTunnel()
|
|
mailtun.run()
|
|
|