#!/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(("", 10025), ("", 10026)) def die(signum, frame): global srv srv.die() signal.signal(signal.SIGINT, die) signal.signal(signal.SIGTERM, die) srv.start()