Welcome, guest | Sign In | My Account | Store | Cart
#!/usr/bin/python2.6
# -*-coding:UTF-8 -*

#import smtpd, smtplib, asyncore
from __future__ import print_function
import re, sys, os, socket, threading, signal
from select import select
import pdb

CRLF
="\r\n"

class Server:
   
def __init__(self, listen_addr, remote_addr):
       
self.local_addr = listen_addr
       
self.remote_addr = remote_addr
       
self.srv_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       
self.srv_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
       
self.srv_socket.bind(listen_addr)
       
self.srv_socket.setblocking(0)

       
self.please_die = False

       
self.accepted = {}

   
def start(self):
       
self.srv_socket.listen(5)
       
while not self.please_die:
           
try:
                ready_to_read
, ready_to_write, in_error = select([self.srv_socket], [], [], 0.1)
           
except Exception as err:
               
pass
           
if len(ready_to_read) > 0:
               
try:
                    client_socket
, client_addr = self.srv_socket.accept()
               
except Exception as err:
                   
print("Problem:", err)
               
else:
                   
print("Connection from {0}:{1}".format(client_addr[0], client_addr[1]))
                    tclient
= ThreadClient(self, client_socket, self.remote_addr)
                    tclient
.start()
                   
self.accepted[tclient.getName()] = tclient

   
def die(self):
       
"""Used to kill the server (joining threads, etc...)
        """

       
print("Killing all client threads...")
       
self.please_die = True
       
for tc in self.accepted.values():
            tc
.die()
            tc
.join()

class ThreadClient(threading.Thread):
   
"""This class is used to manage a 'client to final SMTP server' connection.
    It is the 'Proxy' part of the program
    """

   
(MAIL_DIALOG, MSG_HEADER, MSG_BODY) = range(3)
   
def __init__(self, serv, conn, remote_addr):
        threading
.Thread.__init__(self)
       
self.server = serv
       
self.local = conn
       
self.remote_addr = remote_addr
       
self.remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       
self.please_die = False
       
self.mbuffer = []
       
self.msg_state = ThreadClient.MAIL_DIALOG

   
def recv_body_line(self, line):
       
"""Each line of the body is received here and can be processed, one by one.
        A typical behaviour should be to send it immediatly... or keep all the
        body until it reaches the end of it, and then process it and finally,
        send it.

        Body example:
            Hello foo !
            blabla
        """

        mline
= "{0}{1}".format(line, CRLF)
       
print("B>", line)
       
self.mbuffer.append(line)

   
def flush_body(self):
       
"""This method is called when the end of body (matched with a single
        dot (.) on en empty line is encountered. This method is useful if you
        want to process the whole body.
        """

       
for line in self.mbuffer:
            mline
= "{0}{1}".format(line, CRLF)
           
print("~B>", mline)
           
self.remote.send(mline.encode())

       
# Append example:
       
#toto = "---{0}{0}Un peu de pub{0}".format(CRLF)
       
#self.remote.send(toto.encode())

   
def recv_header_line(self, line):
       
"""All header lines (subject, date, mailer, ...) are processed here.
        """

        mline
= "{0}{1}".format(line, CRLF)
       
print("H>", line)
       
self.remote.send(mline.encode())

   
def recv_server_dialog_line(self, line):
       
"""All 'dialog' lines (which are mail commands send by the mail client
        to the MTA) are processed here.

        Dialog example:
            MAIL FROM: foo@bar.tld
        """

        mline
= "{0}{1}".format(line, CRLF)
       
print(">>", line)
       
self.remote.send(mline.encode())

   
def run(self):
       
"""Here is the core of the proxy side of this script:
        For each line sent by the Mail client to the MTA, split it on the CRLF
        character, and then:
            If it is a DOT on an empty line, call the 'flush_body()' method
            else, if it matches 'DATA' begin to process the body of the message,
            else:
                if we're processing the header, give each line to the
                   'recv_header_line()' method,
                else if we're processing the 'MAIL DIALOG' give the line to the
                     'recv_server_dialog_line()' method.
                else, consider that we're processing the body and give each line
                      to the 'recv_body_line()' method,
        """

       
self.remote.connect(self.remote_addr)
       
self.remote.setblocking(0)
       
while not self.please_die:
           
# Check if the client side has something to say:
            ready_to_read
, ready_to_write, in_error = select([self.local], [], [], 0.1)
           
if len(ready_to_read) > 0:
               
try:
                    msg
= self.local.recv(1024)
               
except Exception as err:
                   
print(str(self.getName()) + " > " + str(err))
                   
break
               
else:
                    dmsg
= msg.decode()
                   
if dmsg != "":
                        dmsg
= dmsg.strip(CRLF)
                       
for line in dmsg.split(CRLF):
                            mline
= "{0}{1}".format(line,CRLF)
                           
if line != "":
                               
if line == "DATA":
                                   
# the 'DATA' string means: 'BEGINNING of the # MESSAGE { HEADER + BODY }
                                   
self.msg_state = ThreadClient.MSG_HEADER
                                   
self.remote.send(mline.encode())
                               
elif line == ".":
                                   
# a signle dot means 'END OF MESSAGE { HEADER+BODY }'
                                   
self.msg_state = ThreadClient.MAIL_DIALOG
                                   
self.flush_body()
                                   
self.remote.send(mline.encode())
                               
else:
                                   
# else, the line can be anything and its
                                   
# signification depend on the part of the
                                   
# whole dialog we're processing.
                                   
if self.msg_state == ThreadClient.MSG_HEADER:
                                       
self.recv_header_line(line)
                                   
elif self.msg_state == ThreadClient.MAIL_DIALOG:
                                       
self.recv_server_dialog_line(line)
                                   
else:
                                       
self.recv_body_line(line)
                           
else:
                               
# Probably the most important: An empty line
                               
# inside the { HEADER + BODY } part of the
                               
# message means we're done with the 'HEADER'
                               
# part and we're beginning the BODY part.
                               
self.msg_state = ThreadClient.MSG_BODY
                   
else:
                       
break

           
# Check if the server side has something to say:
            ready_to_read
, ready_to_write, in_error = select([self.remote], [], [], 0.1)
           
if len(ready_to_read) > 0:
               
try:
                    msg
= self.remote.recv(1024)
               
except Exception as err:
                   
print(str(self.getName()) + " > " + str(err))
                   
break
               
else:
                    dmsg
= msg.decode()
                   
if dmsg != "":
                       
print("<< {0}".format(repr(msg.decode())))
                       
self.local.send(dmsg.encode())
                   
else:
                       
break

       
self.remote.close()
       
self.local.close()
       
self.server.accepted.pop(self.getName())

   
def die(self):
       
self.please_die = True


srv
= Server(("127.0.0.1", 10025), ("127.0.0.1", 10026))
def die(signum, frame):
   
global srv
    srv
.die()

signal
.signal(signal.SIGINT, die)
signal
.signal(signal.SIGTERM, die)

srv
.start()

History

  • revision 2 (13 years ago)
  • previous revisions are not available