(Sorry my english, as is not my natural language)
The case goes back to January, when I needed to configure Outlook 2010 to work with the company's server that is based in a different country. This is a little important detail, as the foreign IT department wouldn't be any helpfull. The server was an Exchange 2000 but all the clients are working via SMTP and POP.
If you need to connect O2010 to Exchange 2000 using Exchange's protocol, please stop reading right here.
So, I knew that e-mails could be received, but by all the means, they just won't go out.
I grabbed a copy of Wireshark and started to analyse what's wrong in here.
Long story short:
...the server tells the client what authentication protocols it supports, however Outlook 2010 wants to use DIGEST-MD5.
This is the main difference between Outlook Express (which works great) and Outlook 2010:
Outlook Express:
HELO machine
AUTH LOGIN
username base64 encoded
password base64 encoded
mails go through.
Outlook 2010:
HELO machine
AUTH DIGEST-MD5
response from server
Outlook sends just a *
AUTH LOGIN
password base64 encodedAnd it stops right here, no auth, no e-mails to the outside world :)
So, if this is just a little glitch in the matrix, we'll fix it with a python script.
This script, is just a bad re-writen code of a transparent proxy, we're you can mess around with the messages sent between server<->client.
My biggest thanks go to:
Dirk Holtwick (INI reader)
Lobsang (Python Proxy) @ ActiveState
They sure saved me a lot of work by not re-inventing the wheel myself.
So this is the script:
# -*- coding: utf-8 -*-
#############################################################################################################
### Filipe Polido - Trigenius 2011 - Thanks to: "Dirk Holtwick (INI reader); Lobsang (SMTP Proxy); ActiveState ###
#############################################################################################################
from __future__ import print_function
import re, sys, os, socket, threading, signal
from select import select
import pdb
import ConfigParser
import string
_configuracao = {
"config.endlocal": "127.0.0.1",
"config.prtlocal": 25,
"config.endremoto": "123.123.123.123",
"config.prtremoto": 25,
"srv2clt.msgoriginal": "AUTH PLAIN LOGIN",
"srv2clt.msgalterada": "AUTH PLAIN LOGIN",
"clt2srv.msgoriginal": "MAIL FROM",
"clt2srv.msgalterada": "EMAIL FROM"
}
CRLF="\r\n"
############################################################################################################
########################## NAO MEXER A PARTIR DAQUI.. #######################################################
############################################################################################################
def LoadConfig(file, config={}):
config = config.copy()
cp = ConfigParser.ConfigParser()
cp.read(file)
for sec in cp.sections():
name = string.lower(sec)
for opt in cp.options(sec):
config[name + "." + string.lower(opt)] = string.strip(cp.get(sec, opt))
return config
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(1)
self.please_die = False
self.accepted = {}
def start(self):
print("#########################################################################")
print(" Trigenius - Transparent Proxy - Filipe Polido 2011")
print("#########################################################################")
print("NAO PODE PARAR ESTE PROCESSO! CASO CONTRARIO O OUTLOOK 2010 NAO FUNCIONA!")
print("#########################################################################")
print("mycfg actual:")
print("Endereço local.....: "+mycfg['config.endlocal']+":"+str(mycfg['config.prtlocal']))
print("Endereço remoto....: "+mycfg['config.endremoto']+":"+str(mycfg['config.prtremoto']))
print("#### Servidor -> Cliente ####")
print("String original....: "+mycfg['srv2clt.msgoriginal'])
print("String alterada....: "+mycfg['srv2clt.msgalterada'])
print("#### Cliente -> Servidor ####")
print("String original....: "+mycfg['clt2srv.msgoriginal'])
print("String alterada....: "+mycfg['clt2srv.msgalterada'])
print("#########################################################################")
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("ERRO:", err)
else:
#print("Ligado {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):
print("AVISO: Terminou este processo. O envio de mails no Outlook 2010 deixa de funcionar!!")
self.please_die = True
for tc in self.accepted.values():
tc.die()
tc.join()
class ThreadClient(threading.Thread):
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 = []
def run(self):
self.remote.connect(self.remote_addr)
self.remote.setblocking(1)
while not self.please_die:
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("ERRO: " + str(self.getName()) + " > " + str(err))
break
else:
magiaremote = msg.replace(mycfg['clt2srv.msgoriginal'], mycfg['clt2srv.msgalterada'], 1)
self.remote.send(magiaremote)
# ver se o servidor tem algo a dizer
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("ERRO: " + str(self.getName()) + " > " + str(err))
break
else:
magia = msg.replace(mycfg['srv2clt.msgoriginal'],mycfg['srv2clt.msgalterada'], 1)
if magia != "":
#print("<< {0}".format(repr(msg)))
self.local.send(magia)
else:
break
self.remote.close()
self.local.close()
self.server.accepted.pop(self.getName())
def die(self):
self.please_die = True
####### INICIO
if not os.path.exists('config.ini'):
print("ERRO: Falta o ficheiro CONFIG.INI")
sys.exit()
mycfg = LoadConfig("config.ini", _configuracao)
srv = Server((mycfg['config.endlocal'], int(mycfg['config.prtlocal'])), (mycfg['config.endremoto'], int(mycfg['config.prtremoto'])))
def die(signum, frame):
global srv
srv.die()
signal.signal(signal.SIGINT, die)
signal.signal(signal.SIGTERM, die)
srv.start()
And this is the config file:
[config]
endlocal=127.0.0.1
prtlocal=25
endremoto=mail.mydomain.pt
prtremoto=25
[srv2clt]
msgoriginal=AUTH PLAIN CRAM-MD5 LOGIN DIGEST-MD5
msgalterada=AUTH PLAIN LOGIN
[clt2srv]
msgoriginal=FOOBAR_SMTP
msgalterada=FOOBAR_SMTP
Sorry, some pieces are in Portuguese, but the code is self-explainatory.
In the config file, you have 2 sections, on srv2clt it processes all the messages from server to client and changes what is need to make Exchange 2000 work with Outlook 2010 (Auth strings)
The clt2srv isn't really needed, but, while I was at it, I thought this could come in handy sometime.
So... now, you have to run the script at localhost or any other server, and tell Outlook to use THAT server as the SMTP server, also, change the config.ini to meet your real SMTP server.
I've also added the port option, just in case.
My costumer uses only windows machines *sigh*, so I've converted python script to a Windows executable and used a tutorial (easy to find online) to make it work like a windows service.
If you have any questions or it doesn't work out for you, just send a e-mail or comment, I'll try to help as soon as possible.
Hi,
ReplyDeleteThanks for sharing. I try compile your script on http://codepad.org but its says many errors, how can I get compiled file, I'm not programmer. Thanks.
Hi.
DeleteI will add an download link to the original script, the spaces/tabs in this post are wrong. Sorry.
Thanks for answer, I'm still waiting :)
Delete