Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon. Entire thread

forced indentation of smtp

Name: Anonymous 2007-10-25 10:06

Hey guys, I am the OP from http://dis.4chan.org/read/prog/1193225538/. It's quite shit, but here is what I ended up writing:

# vsss
# (very simple smtp server)
# revision 0.1

import socket, threading, urllib, os, random, datetime

HOSTNAME = ''
VALID_RECIPIENTS = [
    ('dis.4chan.org', ['prog', 'VIP']),
    ('example.com',   ['lol'])
    ]
MAIL_PATH = 'Z:\MAIL'
DEBUG = False

########################################

CRLF = "\r\n"

def gethostname():

    try:
        # this returns ip address of the requestor
        # i do it this way to help NAT'd servers find their external address
        ipuo = urllib.urlopen('http://zxcsh.com/ip/')
        ip = ipuo.read()
    except:
        try:
            # if that doesn't work, just get hostname in usual way
            hn = socket.gethostname()
        except:
            # and if that fails too, just make one up
            hn = 'vsss'
    else:
        try:
            # hostname using reverse dns
            hn = socket.gethostbyaddr(ip)[0]
        except:
            # or just the ip address if lookup fails
            hn = '[' + ip + ']'
    return hn


class SMTPServer(threading.Thread):

    def __init__(self, sock, addr, port):

        threading.Thread.__init__(self)
        self.sock = sock
        self.addr = addr
        self.port = port
        self.buffer = ''
        self.running = True
       
        self.remotehost = None
        self.returnpath = None
        self.recipients = []

        self.debug('new connection')

    def debug(self, message):
        if DEBUG:
            print self.addr+':'+str(self.port)+' '+message

    def run(self):

        # send greeting
        self.sendline(220, HOSTNAME+' ESMTP')

        # loop until quit
        while self.running:
            line = self.getline()
            if line == None:
                break;
            command = line.partition(' ')
            try:
                getattr(self, 'cmd_'+command[0].lower())(command[2])
            except AttributeError:
                self.resp_badcmd()

        # this checks if it was a clean exit or not
        if not self.running:
            self.sendline(221, '2.0.0 '+HOSTNAME+' Service closing transmission channel')

        self.sock.close()
        self.debug('connection closed')

    def getline(self):
        # keep filling buffer until it contains a crlf
        while CRLF not in self.buffer:
            recvbuf = self.sock.recv(4096)
            if recvbuf == '':
                return None
            self.buffer += recvbuf
        # then return everything up to the crlf, and keep the rest in the buffer
        crlfpart = self.buffer.partition(CRLF)
        self.buffer = crlfpart[2]
        self.debug('<<< '+crlfpart[0])
        return crlfpart[0]

    def sendline(self, status, message):
        if type(message) == str:
            message = [message]
        for line in message[:-1]:
            self.sendline2(str(status)+'-'+line)
        self.sendline2(str(status)+' '+message[-1])

    def sendline2(self, output):
        self.sock.send(output+CRLF)
        self.debug('>>> '+output)

    def resp_badcmd(self):
        self.sendline(502, '5.5.1 Syntax error, command unrecognized')

    def resp_ok(self):
        self.sendline(250, '2.0.0 OK')

    def resp_noremotehost(self):
        self.sendline(503, '5.5.1 Commands out of sequence, require HELO or EHLO')

    def resp_noreturnpath(self):
        self.sendline(503, '5.5.1 Commands out of sequence, require MAIL FROM')

    def resp_norecipients(self):
        self.sendline(503, '5.5.1 Commands out of sequence, require RCPT TO')

    def resp_badreturnpath(self):
        self.sendline(501, '5.5.4 Return path is invalid')

    def resp_badaddress(self):
        self.sendline(501, '5.5.4 Recipient address is invalid')

    def resp_baddomain(self):
        self.sendline(550, '5.7.1 Unable to relay')

    def resp_badrecipient(self):
        self.sendline(550, '5.7.1 Recipient address does not exist')

    def resp_dataerror(self):
        self.sendline(451, '4.3.0 Unknown error while accepting mail, try later')
       
    def cmd_helo(self, args):
        self.sendline(250, HOSTNAME+' says hello')
        self.remotehost = args.strip()

    def cmd_ehlo(self, args):
        self.sendline(250, [HOSTNAME+' says hello','8BITMIME','ENHANCEDSTATUSCODES'])
        self.remotehost = args.strip()

    def check_returnpath(self, returnpath):
        # (TODO: read the relevant RFCs and do this properly)
        if returnpath == '':
            return True
        atsplit = returnpath.split('@')
        if len(atsplit) == 2 and len(atsplit[0]) > 0 and len(atsplit[1]) > 0:
            return True
        return False

    def cmd_mail(self, args):
        if args[:5].lower() != 'from:':
            self.resp_badcmd()
        else:
            if self.remotehost == None:
                self.resp_noremotehost()
            else:
                args = args[5:].strip()
                if args == '':
                    self.resp_badreturnpath()
                    return
                # ignore any other args
                rp = args.partition(' ')[0]
                if rp[0] == '<':
                    if rp[-1] == '>':
                        rp = rp[1:-1]
                    else:
                        self.resp_badreturnpath()
                        return
                if self.check_returnpath(rp):
                    self.returnpath = rp
                    self.resp_ok()
                else:
                    self.resp_badreturnpath()

    def cmd_rcpt(self, args):
        if args[:3].lower() != 'to:':
            self.resp_badcmd()
        else:
            if self.remotehost == None:
                self.resp_noremotehost()
            elif self.returnpath == None:
                self.resp_noreturnpath()
            else:
                args = args[3:].strip()
                if args == '':
                    self.resp_badaddress()
                    return
                ra = args.partition(' ')[0]
                if ra[0] == '<':
                    if ra[-1] == '>':
                        ra = ra[1:-1]
                    else:
                        self.resp_badaddress()
                        return
                atsplit = ra.split('@')
                if not (len(atsplit) == 2 and len(atsplit[0])>0 and len(atsplit[1])>0):
                    self.resp_badaddress()
                    return
                matchvr = None
                for vr in VALID_RECIPIENTS:
                    if atsplit[1].lower() == vr[0].lower():
                        matchvr = vr
                if matchvr == None:
                    self.resp_baddomain()
                    return
                for recp in matchvr[1]:
                    if atsplit[0].lower() == recp:
                        self.recipients.append((matchvr[0],recp))
                        self.resp_ok()
                        return
                self.resp_badrecipient()

    def chdir_mkdir(self, dir):
        try:
            os.chdir(dir)
        except:
            os.mkdir(dir)
            os.chdir(dir)

    def random_string(self):
        s = ''
        for i in range(1,16):
            s += chr(random.randint(96,122))
        return s
   
    def cmd_data(self, args):
        if self.remotehost == None:
            self.resp_noremotehost()
        if self.returnpath == None:
            self.resp_noreturnpath()
        elif self.recipients == []:
            self.resp_norecipients()
        else:
            self.sendline(354, 'Start mail input; end with <CRLF>.<CRLF>')
            message = ''
            line = self.getline()
            while line != '.':
                if line[:1] == '.':
                    line = line[1:]
                message += line + CRLF
                line = self.getline()
            now = datetime.datetime.now()
            message = 'Received: from '+self.remotehost+' (['+self.addr+']) by '+HOSTNAME+'; '+now.strftime('%a, %d %b %Y %H:%M:%S +0000') + CRLF + message
            message = 'Return-path: <'+self.returnpath+'>' + CRLF + message
            filename = now.strftime('%Y%m%d%H%M%S-')+self.random_string()
            try:
                for recp in self.recipients:
                    os.chdir(MAIL_PATH)
                    self.chdir_mkdir(recp[0])
                    self.chdir_mkdir(recp[1])
                    mailfile = open(filename+'.eml', 'wb')
                    mailfile.write(message)
                    mailfile.close()
                    print "wrote mail "+filename+" for "+str(recp)
            except:
                                print "ERROR!"
                self.resp_dataerror()
            else:
                self.resp_ok()
            os.chdir(MAIL_PATH)
           
    def cmd_rset(self, args):
        self.returnpath = None
        self.recipients = []
        self.resp_ok()

    def cmd_noop(self, args):
        self.resp_ok()

    def cmd_quit(self, args):
        self.running = False


########################################

print 'vsss - very simple smtp server'
print

# generate hostname, if required
if HOSTNAME == '':
    HOSTNAME = gethostname()
print 'hostname is '+HOSTNAME

# listen on port 25
srvsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srvsock.bind(('', 25))
srvsock.listen(5)

# create thread for each connection
while True:
    (clisock, addrport) = srvsock.accept()
    srvthrd = SMTPServer(clisock, addrport[0], addrport[1])
    srvthrd.start()        

# this is not reached (yet)
srvsock.close()

Name: Anonymous 2007-10-30 16:19

How the fuck does this happen?

os.mkdir(dir) succeeds but the os.chdir(dir) immediately following it fails, even though the directory must exist as mkdir just completed without raising an exception!

ERROR while writing 20071030201458-dumzkcpmaxdchdt for ('yahoo.com.tw', 'yeh2200')
Exception in thread Thread-627:
Traceback (most recent call last):
  File "C:\Program Files\Python\lib\threading.py", line 460, in __bootstrap
    self.run()
  File "N:\vsss.py", line 77, in run
    getattr(self, 'cmd_'+command[0].lower())(command[2])
  File "N:\vsss.py", line 277, in cmd_data
    self.chdir_mkdir(self.escape_filename(recp[1]))
  File "N:\vsss.py", line 236, in chdir_mkdir
    os.chdir(dir)
WindowsError: [Error 2] The system cannot find the file specified: 'yeh2200'


This spam-hammer is bringing out loads of other bugs too, but this is the most bizarre. It happens every other minute or so.

Name: Anonymous 2007-10-30 16:25

>>41
One word.

Name: Anonymous 2007-10-30 16:39

>>41
Race condition. Your filesystem hasn't bothered making the directory by the time your program comes to change to it.

Name: Anonymous 2007-10-30 23:01

>>43
you're doin it wrong.
the real reason is the FORCED INDENTATION OF CODE

Name: Anonymous 2007-10-31 4:36

>>44
What does Python have to do with anything? You do realize in the end it's using system calls to create the directory on the file system, right? >>43 has the right idea. A crude way to fulfill >>41's intention is to spin loop after creating the directory, breaking once you can confirm it does exist.

Name: Anonymous 2007-10-31 6:23

Obviously Python has failed to provide the abstract API it aims for. If I have to micromanage OS-level filesystem problems like this, I might as well write in C.

Name: Anonymous 2007-10-31 6:33

>>46
[ ] File a bug report
[ ] GTFO

The Choice Is Yours

Name: Anonymous 2007-10-31 6:40

>>47
So it's a bug now?

Name: Anonymous 2007-10-31 6:44

>>48
Looks like a bug to me

Name: TuneAFish 2007-10-31 6:44

>>47
Asynchronous file creation is not a bug; you just suck. Spin-loop after the syscall

Name: Anonymous 2007-10-31 6:56

>>50
the program: please create me a directory with the name "faggots".
the system: ok, that's all done for you.
the program: thanks. now change to the directory named "faggots".
the system: sorry, i can't. it doesn't exist!
the program: but you just told me you made it.
the system: did i? i can't remember.
the program: yes, you did.
the system: no, i can't possibly have.
the program: yes you fucking well did. i'm filing a bug report!!
the system: oh, wait. here it is now. i just realised, i forgot to do it earlier. LOL!

Name: TuneAFish 2007-10-31 6:58

>>51
If it was a bug, don't you think it would have been fixed by now? I refuse to believe 4chan are able to do anything significant in the programming community. The behaviour you experience with that particular function is well-known and expected, not a bug.

This thread has ended peacefully

.

Name: Anonymous 2007-10-31 7:02

The behaviour you experience with that particular function is well-known and expected, not a bug.

Citation?

Name: TuneAFish 2007-10-31 7:05

>>53
Why certainly.
http://www.google.com
The fact that it exists, after all these years, is proof enough. Do you mind if I proceed with saging this thread?

Name: Anonymous 2007-10-31 7:18

>>54
No, it is not proof. I have already searched to see if this is a common behaviour and came up with nothing. Therefore, I can only conclude that this is a bug unless shown otherwise.

Name: TuneAFish 2007-10-31 7:24

In this case, I, TuneAFish, will submit my findings and see what they respond with. I'll be sure to keep you posted, Anon.

Name: Anonymous 2007-10-31 8:16

I think it's a threading bug. Using the following program with a parameter of 1, it works fine. But anything greater than that and the file not found error occurs in os.chdir until only one thread is left to run completely.

# mkdirtest.py
#
# usage: mkdirtest <number of threads>

import os, threading, sys

x = int(sys.argv[1])

class MkdirTest(threading.Thread):

    def __init__(self, t):
        threading.Thread.__init__(self)
        self.t = t
        print "new thread "+str(t)

    def run(self):
        for i in range(0,50):
            s = str(self.t)+"_"+str(i)
            print "mkdir "+s
            os.mkdir(s)
            os.chdir(s)
            os.chdir("..")
        print "end thread "+str(t)

for t in range(0,x):
    print t
    a = MkdirTest(t)
    a.start()

Name: Anonymous 2007-10-31 11:15

This isn't a bug in Python. Working directory, which os.chdir modifies, is process-global. One of your threads makes a directory, then gets suspended while another one makes a different directory and changes into it, then the first tries to change into its directory and fails.

Name: Anonymous 2007-10-31 11:44

>>58 wins the thread
>>1-57 FAIL

Name: Anonymous 2007-10-31 13:29

Indeed. Always use absolute pa

Name: Anonymous 2007-10-31 20:41

pa-pa pa-pa pa-pa pa-pa pa-pa-pa, pa-pa pa-pa pa-pa-pa-paaaah pa!

Name: Anonymous 2007-10-31 21:00

absolute pa = absolute python absolute

Name: Anonymous 2007-10-31 22:15

|pa|

Name: Anonymous 2007-11-01 11:50

Shithausen

Name: Anonymous 2007-11-01 14:21

Now I'm getting errors like this all the time:

new connection from 202.75.56.212:58287
Exception in thread Thread-43:
Traceback (most recent call last):
  File "C:\Program Files\Python\lib\threading.py", line 460, in __bootstrap
    self.run()
  File "N:\vsss.py", line 73, in run
    line = self.getline()
  File "N:\vsss.py", line 93, in getline
    recvbuf = self.sock.recv(4096)
error: (10055, 'No buffer space available')


Odd, very odd. I have no idea why this is happening. It is only on the recv, not the send.

Any ideas?

Name: Anonymous 2007-11-01 15:24

>>65
File "C:\Program Files\Python\lib\threading.py", line 460, in __bootstrap
There's your problem.

Name: Anonymous 2007-11-01 17:02

>>66
What's wrong with threads?

Name: Computer Scientist 2007-11-01 17:18

>>67
HERE'S WHAT'S WRONG WITH THREADS!! *shoves a cudder down your throat*

Name: Anonymous 2007-11-01 19:33

>>68
GTFO. Now.

Name: Anonymous 2007-11-01 20:04

>>69
NO U

Name: Anonymous 2008-09-18 8:20

しゃぶるマイおしっこ

Name: Anonymous 2008-09-18 8:26

>>71
FOR FUCK'S SAKE, STOP TALKING IN JAPANESE FAG AND SUCK MY DICK

Name: Anonymous 2008-09-18 16:25

>>72
That's funny, because he said "Suck my wee-wee" in Japanese.

Name: Anonymous 2008-09-18 17:41

>>71
Horrible GRAMMAR FAILURE

Name: Anonymous 2010-11-14 21:04

Newer Posts
Don't change these.
Name: Email:
Entire Thread Thread List