adding AUTH support to pop3 and smtp. identities require logging in now
This commit is contained in:
parent
7694a54a15
commit
f4676e2685
|
@ -33,10 +33,15 @@ class bitmessagePOP3Connection(asyncore.dispatcher):
|
||||||
self.messages = None
|
self.messages = None
|
||||||
self.storage_size = 0
|
self.storage_size = 0
|
||||||
self.address = None
|
self.address = None
|
||||||
|
self.pw = None
|
||||||
|
self.loggedin = False
|
||||||
|
|
||||||
self.sendline("+OK Bitmessage POP3 server ready")
|
self.sendline("+OK Bitmessage POP3 server ready")
|
||||||
|
|
||||||
def populateMessageIndex(self):
|
def populateMessageIndex(self):
|
||||||
|
if not self.loggedin:
|
||||||
|
raise Exception("Cannot be called when not logged in.")
|
||||||
|
|
||||||
if self.address is None:
|
if self.address is None:
|
||||||
raise Exception("Invalid address: {}".format(self.address))
|
raise Exception("Invalid address: {}".format(self.address))
|
||||||
|
|
||||||
|
@ -153,6 +158,9 @@ class bitmessagePOP3Connection(asyncore.dispatcher):
|
||||||
break
|
break
|
||||||
|
|
||||||
def handleUser(self, data):
|
def handleUser(self, data):
|
||||||
|
if self.loggedin:
|
||||||
|
raise Exception("Cannot login twice")
|
||||||
|
|
||||||
self.address = data
|
self.address = data
|
||||||
|
|
||||||
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address)
|
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address)
|
||||||
|
@ -168,11 +176,25 @@ class bitmessagePOP3Connection(asyncore.dispatcher):
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
raise Exception("Invalid Bitmessage address: {}".format(self.address))
|
raise Exception("Invalid Bitmessage address: {}".format(self.address))
|
||||||
|
|
||||||
return ["+OK user accepted"]
|
# Each identity must be enabled independly by setting the smtppop3password for the identity
|
||||||
|
# If no password is set, then the identity is not available for SMTP/POP3 access.
|
||||||
|
try:
|
||||||
|
self.pw = shared.config.get(addBMIfNotPresent(self.address), "smtppop3password")
|
||||||
|
yield "+OK user accepted"
|
||||||
|
except:
|
||||||
|
yield "-ERR account not available"
|
||||||
|
self.close()
|
||||||
|
|
||||||
def handlePass(self, data):
|
def handlePass(self, data):
|
||||||
# TODO
|
if self.pw is None:
|
||||||
return ["+OK pass accepted"]
|
yield "-ERR must specify USER"
|
||||||
|
else:
|
||||||
|
pw = data.decode('ascii')
|
||||||
|
if pw == self.pw: # TODO - hashed passwords?
|
||||||
|
yield "+OK pass accepted"
|
||||||
|
self.loggedin = True
|
||||||
|
else:
|
||||||
|
yield "-ERR invalid password"
|
||||||
|
|
||||||
def handleStat(self, data):
|
def handleStat(self, data):
|
||||||
self.populateMessageIndex()
|
self.populateMessageIndex()
|
||||||
|
|
|
@ -1,12 +1,258 @@
|
||||||
from pyelliptic.openssl import OpenSSL
|
from pyelliptic.openssl import OpenSSL
|
||||||
|
import asynchat
|
||||||
|
import base64
|
||||||
|
import errno
|
||||||
import shared
|
import shared
|
||||||
import smtpd
|
import smtpd
|
||||||
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from addresses import *
|
from addresses import *
|
||||||
import helper_sent
|
import helper_sent
|
||||||
|
|
||||||
|
# This is copied from Python's smtpd module and modified to support basic SMTP AUTH.
|
||||||
|
class bitmessageSMTPChannel(asynchat.async_chat):
|
||||||
|
COMMAND = 0
|
||||||
|
DATA = 1
|
||||||
|
|
||||||
|
def __init__(self, server, conn, addr):
|
||||||
|
asynchat.async_chat.__init__(self, conn)
|
||||||
|
self.__server = server
|
||||||
|
self.__conn = conn
|
||||||
|
self.__addr = addr
|
||||||
|
self.__line = []
|
||||||
|
self.__state = self.COMMAND
|
||||||
|
self.__greeting = 0
|
||||||
|
self.__mailfrom = None
|
||||||
|
self.__rcpttos = []
|
||||||
|
self.__data = ''
|
||||||
|
self.__fqdn = socket.getfqdn()
|
||||||
|
self.__version = 'Python SMTP proxy version 0.2a'
|
||||||
|
self.logged_in = False
|
||||||
|
try:
|
||||||
|
self.__peer = conn.getpeername()
|
||||||
|
except socket.error, err:
|
||||||
|
# a race condition may occur if the other end is closing
|
||||||
|
# before we can get the peername
|
||||||
|
self.close()
|
||||||
|
if err[0] != errno.ENOTCONN:
|
||||||
|
raise
|
||||||
|
return
|
||||||
|
print >> smtpd.DEBUGSTREAM, 'Peer:', repr(self.__peer)
|
||||||
|
self.push('220 %s %s' % (self.__fqdn, self.__version))
|
||||||
|
self.set_terminator('\r\n')
|
||||||
|
|
||||||
|
# Overrides base class for convenience
|
||||||
|
def push(self, msg):
|
||||||
|
asynchat.async_chat.push(self, msg + '\r\n')
|
||||||
|
|
||||||
|
# Implementation of base class abstract method
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.__line.append(data)
|
||||||
|
|
||||||
|
# Implementation of base class abstract method
|
||||||
|
def found_terminator(self):
|
||||||
|
line = ''.join(self.__line)
|
||||||
|
print >> smtpd.DEBUGSTREAM, 'Data:', repr(line)
|
||||||
|
self.__line = []
|
||||||
|
if self.__state == self.COMMAND:
|
||||||
|
if not line:
|
||||||
|
self.push('500 Error: bad syntax')
|
||||||
|
return
|
||||||
|
method = None
|
||||||
|
i = line.find(' ')
|
||||||
|
if i < 0:
|
||||||
|
command = line.upper()
|
||||||
|
arg = None
|
||||||
|
else:
|
||||||
|
command = line[:i].upper()
|
||||||
|
arg = line[i+1:].strip()
|
||||||
|
method = getattr(self, 'smtp_' + command, None)
|
||||||
|
if not method:
|
||||||
|
self.push('502 Error: command "%s" not implemented' % command)
|
||||||
|
return
|
||||||
|
method(arg)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if self.__state != self.DATA:
|
||||||
|
self.push('451 Internal confusion')
|
||||||
|
return
|
||||||
|
# Remove extraneous carriage returns and de-transparency according
|
||||||
|
# to RFC 821, Section 4.5.2.
|
||||||
|
data = []
|
||||||
|
for text in line.split('\r\n'):
|
||||||
|
if text and text[0] == '.':
|
||||||
|
data.append(text[1:])
|
||||||
|
else:
|
||||||
|
data.append(text)
|
||||||
|
self.__data = '\n'.join(data)
|
||||||
|
status = self.__server.process_message(self.__peer,
|
||||||
|
self.__mailfrom,
|
||||||
|
self.__rcpttos,
|
||||||
|
self.__data)
|
||||||
|
self.__rcpttos = []
|
||||||
|
self.__mailfrom = None
|
||||||
|
self.__state = self.COMMAND
|
||||||
|
self.set_terminator('\r\n')
|
||||||
|
if not status:
|
||||||
|
self.push('250 Ok')
|
||||||
|
else:
|
||||||
|
self.push(status)
|
||||||
|
|
||||||
|
# SMTP and ESMTP commands
|
||||||
|
def smtp_EHLO(self, arg):
|
||||||
|
if not arg:
|
||||||
|
self.push('501 Syntax: EHLO hostname')
|
||||||
|
return
|
||||||
|
if self.__greeting:
|
||||||
|
self.push('503 Duplicate HELO/EHLO')
|
||||||
|
else:
|
||||||
|
self.__greeting = arg
|
||||||
|
self.push('250-%s offers:' % self.__fqdn)
|
||||||
|
self.push('250 AUTH PLAIN')
|
||||||
|
|
||||||
|
def smtp_HELO(self, arg):
|
||||||
|
if not arg:
|
||||||
|
self.push('501 Syntax: HELO hostname')
|
||||||
|
return
|
||||||
|
if self.__greeting:
|
||||||
|
self.push('503 Duplicate HELO/EHLO')
|
||||||
|
else:
|
||||||
|
self.__greeting = arg
|
||||||
|
self.push('250 %s' % self.__fqdn)
|
||||||
|
|
||||||
|
def smtp_AUTH(self, arg):
|
||||||
|
encoding, pw = arg.split(' ')
|
||||||
|
if encoding != 'PLAIN':
|
||||||
|
self.push('501 encoding not understood')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
z, self.address, pw = base64.b64decode(pw).split('\x00')
|
||||||
|
if z != '':
|
||||||
|
self.push('501 encoding not understood')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.address)
|
||||||
|
if status != 'success':
|
||||||
|
shared.printLock.acquire()
|
||||||
|
print 'Error: Could not decode address: ' + self.address + ' : ' + status
|
||||||
|
if status == 'checksumfailed':
|
||||||
|
print 'Error: Checksum failed for address: ' + self.address
|
||||||
|
if status == 'invalidcharacters':
|
||||||
|
print 'Error: Invalid characters in address: ' + self.address
|
||||||
|
if status == 'versiontoohigh':
|
||||||
|
print 'Error: Address version number too high (or zero) in address: ' + self.address
|
||||||
|
shared.printLock.release()
|
||||||
|
raise Exception("Invalid Bitmessage address: {}".format(self.address))
|
||||||
|
|
||||||
|
# Each identity must be enabled independly by setting the smtppop3password for the identity
|
||||||
|
# If no password is set, then the identity is not available for SMTP/POP3 access.
|
||||||
|
try:
|
||||||
|
self.pw = shared.config.get(addBMIfNotPresent(self.address), "smtppop3password")
|
||||||
|
if pw == self.pw:
|
||||||
|
self.push('235 Authentication successful. Proceed.')
|
||||||
|
self.logged_in = True
|
||||||
|
return
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.push('530 Access denied.')
|
||||||
|
self.close_when_done()
|
||||||
|
|
||||||
|
def smtp_NOOP(self, arg):
|
||||||
|
if arg:
|
||||||
|
self.push('501 Syntax: NOOP')
|
||||||
|
else:
|
||||||
|
self.push('250 Ok')
|
||||||
|
|
||||||
|
def smtp_QUIT(self, arg):
|
||||||
|
# args is ignored
|
||||||
|
self.push('221 Bye')
|
||||||
|
self.close_when_done()
|
||||||
|
|
||||||
|
# factored
|
||||||
|
def __getaddr(self, keyword, arg):
|
||||||
|
address = None
|
||||||
|
keylen = len(keyword)
|
||||||
|
if arg[:keylen].upper() == keyword:
|
||||||
|
address = arg[keylen:].strip()
|
||||||
|
if not address:
|
||||||
|
pass
|
||||||
|
elif address[0] == '<' and address[-1] == '>' and address != '<>':
|
||||||
|
# Addresses can be in the form <person@dom.com> but watch out
|
||||||
|
# for null address, e.g. <>
|
||||||
|
address = address[1:-1]
|
||||||
|
return address
|
||||||
|
|
||||||
|
def smtp_MAIL(self, arg):
|
||||||
|
if not self.logged_in:
|
||||||
|
self.push('503 Not authenticated.')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
print >> smtpd.DEBUGSTREAM, '===> MAIL', arg
|
||||||
|
address = self.__getaddr('FROM:', arg) if arg else None
|
||||||
|
if not address:
|
||||||
|
self.push('501 Syntax: MAIL FROM:<address>')
|
||||||
|
return
|
||||||
|
if self.__mailfrom:
|
||||||
|
self.push('503 Error: nested MAIL command')
|
||||||
|
return
|
||||||
|
self.__mailfrom = address
|
||||||
|
print >> smtpd.DEBUGSTREAM, 'sender:', self.__mailfrom
|
||||||
|
self.push('250 Ok')
|
||||||
|
|
||||||
|
def smtp_RCPT(self, arg):
|
||||||
|
if not self.logged_in:
|
||||||
|
self.push('503 Not authenticated.')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
print >> smtpd.DEBUGSTREAM, '===> RCPT', arg
|
||||||
|
if not self.__mailfrom:
|
||||||
|
self.push('503 Error: need MAIL command')
|
||||||
|
return
|
||||||
|
address = self.__getaddr('TO:', arg) if arg else None
|
||||||
|
if not address:
|
||||||
|
self.push('501 Syntax: RCPT TO: <address>')
|
||||||
|
return
|
||||||
|
self.__rcpttos.append(address)
|
||||||
|
print >> smtpd.DEBUGSTREAM, 'recips:', self.__rcpttos
|
||||||
|
self.push('250 Ok')
|
||||||
|
|
||||||
|
def smtp_RSET(self, arg):
|
||||||
|
if not self.logged_in:
|
||||||
|
self.push('503 Not authenticated.')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
if arg:
|
||||||
|
self.push('501 Syntax: RSET')
|
||||||
|
return
|
||||||
|
# Resets the sender, recipients, and data, but not the greeting
|
||||||
|
self.__mailfrom = None
|
||||||
|
self.__rcpttos = []
|
||||||
|
self.__data = ''
|
||||||
|
self.__state = self.COMMAND
|
||||||
|
self.push('250 Ok')
|
||||||
|
|
||||||
|
def smtp_DATA(self, arg):
|
||||||
|
if not self.logged_in:
|
||||||
|
self.push('503 Not authenticated.')
|
||||||
|
self.close_when_done()
|
||||||
|
return
|
||||||
|
if not self.__rcpttos:
|
||||||
|
self.push('503 Error: need RCPT command')
|
||||||
|
return
|
||||||
|
if arg:
|
||||||
|
self.push('501 Syntax: DATA')
|
||||||
|
return
|
||||||
|
self.__state = self.DATA
|
||||||
|
self.set_terminator('\r\n.\r\n')
|
||||||
|
self.push('354 End data with <CR><LF>.<CR><LF>')
|
||||||
|
|
||||||
|
|
||||||
class bitmessageSMTPServer(smtpd.SMTPServer):
|
class bitmessageSMTPServer(smtpd.SMTPServer):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# TODO - move to separate file/class
|
# TODO - move to separate file/class
|
||||||
|
@ -24,12 +270,10 @@ class bitmessageSMTPServer(smtpd.SMTPServer):
|
||||||
|
|
||||||
def handle_accept(self):
|
def handle_accept(self):
|
||||||
# Override SMTPServer's handle_accept so that we can start an SSL connection.
|
# Override SMTPServer's handle_accept so that we can start an SSL connection.
|
||||||
if not self.ssl:
|
|
||||||
return smtpd.SMTPServer.handle_accept(self)
|
|
||||||
|
|
||||||
sock, peer_address = self.accept()
|
sock, peer_address = self.accept()
|
||||||
sock = ssl.wrap_socket(sock, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23)
|
if self.ssl:
|
||||||
channel = smtpd.SMTPChannel(self, sock, peer_address)
|
sock = ssl.wrap_socket(sock, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23)
|
||||||
|
bitmessageSMTPChannel(self, sock, peer_address)
|
||||||
|
|
||||||
def process_message(self, peer, mailfrom, rcpttos, data):
|
def process_message(self, peer, mailfrom, rcpttos, data):
|
||||||
#print("Peer", peer)
|
#print("Peer", peer)
|
||||||
|
@ -138,3 +382,5 @@ class bitmessageSMTPServer(smtpd.SMTPServer):
|
||||||
# TODO - what should we do with ackdata.encode('hex') ?
|
# TODO - what should we do with ackdata.encode('hex') ?
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
smtpd.DEBUGSTREAM = sys.stdout
|
||||||
|
|
Reference in New Issue
Block a user