SMTP Server Entwurf

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
monocult
User
Beiträge: 37
Registriert: Donnerstag 31. März 2005, 09:55
Wohnort: hennef
Kontaktdaten:

Montag 1. Oktober 2007, 16:44

Hallo zusammen,

ich versuch mich gerade in die Abläufe von SMTP einzuarbeiten und dachte womit geht das besser als selber einen kleinen Server zu schreiben.

ich hab heute mal ein bisschen zeit gehabt wobei unten stehender Code herausgekommen ist.

Momentan nimmt es nur Mails entgegen und leg diese in die Mail-Que. zur zeit noch ohne jegliche Überprüfung. (super open relay wenn er den versenden würde). Als Authtype ist CRAM-MD5 implementiert. Wie gesagt nimmt das Script momentan allerdings noch alle Mails an.

bei mir mit Evolution geht es Outlook ua. sollten aufgrund der etwas anderen Syntax noch Probleme machen.

das zweite Script ist zur Generierung der Passwörter.

Vielleicht habt Ihr Anregungen Das Konstrukt zu verbessern oder einfach nur ein paar allgemeine Hinweise.

Bin über jeden Kommentar Dankbar :)

PS: Das ist wirklich ein Entwurf und als SMTP Server so mehr als unbrauchbar

Code: Alles auswählen

#! /usr/bin/env python
# -*- encoding: UTF-8 -*-

# YPS SMTP mit Gimmick

from socket import *
import string,time, os, random, string
from stat import *
import md5, base64
import threading
import array

# Base Config
SMTP_PORT = 25
BUFSIZE = 1024
HOSTNAME = gethostname()
HOSTIP = gethostbyname(HOSTNAME)
GREATING = 'Welcome to alphalog Mail-Service'
DATA = ''
MAILQDIR = './mailq/'
LOGFILE = 'mail.log'

# Local Mail Users (ipad, opad in base64)
# daniel = testpw1
# sandra = testpw2
LOCALUSERS = { 
    'daniel' : ['QlNFQkZBBzY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Ng==','KDkvKCwrbVxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA=='], 
    'sandra' : ['QlNFQkZBBDY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Ng==','KDkvKCwrblxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXA==']
    }


class SmtpSession(threading.Thread):

    def __init__ ( self, channel, remotehost ):
        self.channel = channel
        self.remotehost = remotehost
        threading.Thread.__init__ ( self )
    
    def log( self, logline ):
        log=open(LOGFILE,'a')
        log.write(logline)
        log.close()


    def cdigest(self, mailuser, data):
                   
        ipad = md5.md5(base64.b64decode(LOCALUSERS[mailuser][0]))
        opad = md5.md5(base64.b64decode(LOCALUSERS[mailuser][1]))

        ipad.update(data)
        opad.update(ipad.digest())
        return opad.hexdigest()
        
    def run( self ):
   
        STATUS = 0
        self.log('connect from %s\n' %(self.remotehost))
        self.channel.send('220 %s\n' %(GREATING))

        sessionid = str(int(time.time())) + '.'
        for i in range(14):
            sessionid = sessionid + random.choice(string.letters + string.digits)        
        
        while 1:
            # Empfange daten vom Client
            DATA = self.channel.recv(BUFSIZE).strip()
            print "Echo:" + DATA    # Debug
            
            # HELO Befehl
            if 'HELO' in DATA:
                try:
                    self.channel.send('250 Hello %s, nice to meet you\n' %(DATA.split()[1]))
                except:   
                    self.channel.send('501 Syntax: HELO hostname\n')
                    
            # EHLO Befehl
            elif 'EHLO' in DATA:
                self.channel.send('250 AUTH CRAM-MD5\n')
            
            # AUTH CRAM-MD5 Befehl
            elif 'AUTH CRAM-MD5' in DATA:
                try:
                    
                    #callenge erzeugen random.time@hostname und senden 
                    
                    rand = ''
                    for i in range(5):
                        rand = rand + random.choice(string.digits)

                    challenge = rand + '.' + str(int(time.time())) + '@' + HOSTNAME
                    
                    challengeb64 = base64.b64encode(challenge)
                                        
                    self.channel.send('334 ' + challengeb64 + '\n')
                    
                    # Authstring vom Client lessen und Pruefen 
                    
                    authstring = self.channel.recv(BUFSIZE).strip()
                    
                    authstring = base64.b64decode(authstring).split()
                    mailuser = authstring[0]
                    mailpwdigest = authstring[1]
                   
                    signature = self.cdigest(mailuser,challenge)
                    
                    print 'signature: ' + signature
                    if mailpwdigest == signature:
                        self.channel.send('235 ok\n')
                        self.log('Login from %s User: %s\n' %(self.remotehost, mailuser))
                    else:
                        self.channel.send('535 Incorrect username or password\n')
                        self.log('FAILD Login from %s User: %s\n' %(self.remotehost, mailuser))
                        
                    

                except:   
                    #Wenn nicht implementiertes Syntax bzw. ander Fehler
                    self.channel.send('535 Incorrect username or password\n')  
                    
            # MAIL Befehl                    
            elif 'MAIL' in DATA:
                if 'MAIL FROM:' in DATA:
                    try:
                        MAILFROM = DATA.split(':')[1].strip()
                        self.channel.send('250 %s Sender OK \n' %(MAILFROM))
                        STATUS = 1
                    except:   
                        self.channel.send('501 Syntax: MAIL FROM: <address>\n')
                else:
                    self.channel.send('501 Syntax: MAIL FROM: <address>\n')
                    
            # RCPT Befehl                   
            elif 'RCPT' in DATA:
                if STATUS != 1:
                    self.channel.send('503 Error: need MAIL command\n')                    
                elif 'RCPT TO:' in DATA:
                    try:
                        RCPTTO = DATA.split(':')[1].strip()
                        self.channel.send('250 %s RE OK \n' %(RCPTTO))
                        STATUS = 2
                        print 'ok RCPTTO'
                    except:   
                        self.channel.send('501 Syntax: RCPT TO: <address>\n')
                else:
                    self.channel.send('501 Syntax: RCPT TO: <address>\n')
            
            # DATA Befehl
            elif 'DATA' in DATA:
            
                if STATUS != 2:
                    self.channel.send('503 Error: need RCPT command\n')
                 
                else:    
                    self.channel.send('354 End DATA with <CR><LF>.<CR><LF>\n')
                    DATA = ''
                
                    #MAIL lessen bis <CR><LF>.<CR><LF>
                    while not '\n.\n' in DATA:
                        DATA = DATA + conn.recv(BUFSIZE).strip() + '\n'
                          
                    # Speichern                
                    fd=open(MAILQDIR + sessionid,'w')
                    fd.write(DATA)
                    fd.close()
                    
                    self.log('%s: Mail from %s to %s stored in mailq\n' %(sessionid, MAILFROM,RCPTTO))

                    self.channel.send('250 Message accepted for delivery\n')

                    
            # RSET Befehl   
            elif 'RSET' in DATA:
                self.channel.send('250 Ok \n')
            # NOOP Befehl 
            elif 'NOOP' in DATA:
                self.channel.send('250 Ok \n')
            # QUIT Befehl                
            elif 'QUIT' in DATA:
                self.channel.send('221 See you later \n')
                break
                
            # ka
            else:
                self.channel.send('502 Error: command not implemented\n')

        # Session Beenden
        self.channel.close()
        self.log('disconnect from %s\n' %(self.remotehost))
        print('Verbindung beendet')




if __name__ == '__main__':

    # TCP-Socket erstellen und an Port binden
    s = socket(AF_INET, SOCK_STREAM)
    print "Socket initiert"
    s.bind(('', SMTP_PORT))
    s.listen(500)
    print "Server gestartet"

    # Warte auf eine Verbindung zum Server
    while True:
        print 'Warte auf eine Verbindung zum Server'
        conn, (remotehost, remoteport) = s.accept()
        print 'Verbunden mit %s:%s' % (remotehost, remoteport)
        
        SmtpSession(conn,remotehost).start()
        
    s.close()

Code: Alles auswählen

import array, string, md5, base64
key = 'testpw2'


ipada = array.array("B", [0x36] * 64)
opada = array.array("B", [0x5C] * 64)
for i in range(len(key)):
    ipada[i] = ipada[i] ^ ord(key[i])
    opada[i] = opada[i] ^ ord(key[i])
    
ipada = ipada.tostring()
opada = opada.tostring()

print base64.b64encode(ipada)
print base64.b64encode(opada)
Antworten