Rewrite anything in Postfix using Python

Posted on Tue 27 October 2020 in python

There is always a well working, prepared, documented and good way to solve a problem. The problem with that is that in the most cases we end up with a rat tail of components, we cannot install, maintain or want.

The real solution can be found here:

https://dokuwiki.nausch.org/doku.php/centos:mail_c7:spam_11 https://github.com/roehling/postsrsd/

It sounds very simple to implement working srs with Postfix. There is a tool set to perform this task. My only problem was that it was over engeneered to my understanding of the problem I found and liked to solve.

Make things easier for me and not for others, was my goal. So simple Python hack was my choice.

Postfix has a simple communication protocol to have TCP backed maps in place.

# Postfix asks for it
< get localpart@domain.com
# You reply with the new address
> 200 new-localpart@domain.com

There might also be the case, that you cannot lookup or have an error.

# Postfix asks for it
< get localpart@domain.com
# You reply with an error code and the original address
> 400 localpart@domain.com

This is simple to implement and with utilizing some other modules like the hashlib to generate a salted hash it's robust.

"""Server to provide address encoding for simplified SRS"""
import SocketServer
import hashlib, base64

MY_DOMAIN='example.com'
SECRET='Who does know me?'

class MyTCPHandler(SocketServer.BaseRequestHandler):
  def handle(self):
    self.data = self.request.recv(1024).strip()
    self.data=self.data.replace('get ','')
    if self.data.endswith(MY_DOMAIN):
        return self.request.send('200 {0}\n'.format(self.data))
    if self.data.strip() == '""':
        return self.request.send('200 {0}\n'.format(self.data))
    if '@' not in self.data:
        return self.request.send('400 {0}\n'.format(self.data))
    d=hashlib.md5(b"{0}{1}".format(SECRET, self.data)).digest(); d=base64.b64encode(d); 
    return self.request.send('200 {0}#{1}@{2}\n'.format(self.data.replace('@','-at-'), d, MY_DOMAIN))

if __name__ == "__main__":
  HOST, PORT = "localhost", 9999
  SocketServer.TCPServer.allow_reuse_address = True
  server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
  server.serve_forever()

And to convert everything back the following was the way to go.

"""Server to provide address decoding for simplified SRS"""
import SocketServer
import hashlib, base64

MY_DOMAIN='example.com'
SECRET='Who does know me?'

class MyTCPHandler(SocketServer.BaseRequestHandler):
  def handle(self):
    self.data = self.request.recv(1024).strip()
    self.data=self.data.replace('get ','')
    if self.data.endswith('@{0}'.format(MY_DOMAIN)):
      if '#' in self.data and '-at-' in self.data:
        orig=self.data.split('#',1)[0].replace('-at-','@')
        orighash=self.data.split('#',1)[1].split('@',1)[0]
        d=hashlib.md5(b"{0}{1}".format(SECRET, orig)).digest(); d=base64.b64encode(d);
        if d == orighash:
          return self.request.send('200 {0}\n'.format(orig))
        return self.request.send('200 {0}\n'.format(self.data))
    return self.request.send('200 {0}\n'.format(self.data))

if __name__ == "__main__":
  HOST, PORT = "localhost", 9998
  SocketServer.TCPServer.allow_reuse_address = True
  server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
  server.serve_forever()

In order to make it play nice with Postfix you need to enable the lookup via TCP socket there.

# main.cf
recipient_canonical_maps = tcp:127.0.0.1:9998
recipient_canonical_classes = envelope_recipient
sender_canonical_maps = tcp:127.0.0.1:9999
sender_canonical_classes = envelope_sender

# master.cf
smtp      inet  n       -       -       -       -       smtpd
  -o sender_canonical_maps=tcp:127.0.0.1:9999
  -o sender_canonical_classes=envelope_sender

With that the emails are encoded and decoded in and out.


Please be aware that the information you put into this form will be stored with this site. If you want to get rid of your post, please contact me. I'll delete it, if you can proove that you are the author.

Please also be not shocked that you get a cookie after writing a comment. This will be last 30 minutes and enables you to delete your comment on your own. You can decide to not accept it.

Please additionally do not try to dry your pet in a microwave oven. This may kill your pet and harm the microwave device. Both outcomes of that story should be avoided.