-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmtpproxy.py
More file actions
173 lines (138 loc) · 6.42 KB
/
smtpproxy.py
File metadata and controls
173 lines (138 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#|##############################################################################
#|Copyright (c) 2012, The Green-Span Project. All rights reserved. This code is
#|Open Source Free Software - redistribution and use in source and binary forms,
#|with or without modification, are permitted under the Two Clause BSD License.
#|##############################################################################
#|File Created: 2012-08-15
#|Author(s): Sean Hastings,
#|##############################################################################
VERBOSE = True
DEBUG = True
from twisted.internet import ssl, reactor
from twisted.internet.protocol import ClientFactory, Factory, Protocol
from twisted.python import log
import string, re
HOST_NAME = 'smtp.googlemail.com' #Assumed hostname (later versions will discover from login or have on file)
#Client Commands that the proxy needs to know about
CLIENT_COMMANDS = ['APPEND','CAPABILITY','CHECK','COPY','CREATE','DELETE','EXAMINE','FETCH','GETQUOTAROOT','ID','IDLE','LIST','LOGIN','LSUB','NAMESPACE','NOOP','RENAME','SELECT','STATUS','SUBSCRIBE','UID','UNSUBSCRIBE']
#Commands that need their parameters changed in passing through the proxy
CHANGE_FIRST_PARAM = ['APPEND','CREATE','DELETE','EXAMINE','GETQUOTAROOT','RENAME','SELECT','STATUS','SUBSCRIBE','UNSUBSCRIBE']
CHANGE_SECOND_PARAM = ['COPY','RENAME','LIST','LSUB']
#LIST and LSUB also have a first "context" parameter that maybe should be greenwashed
#but it is often blank and seems to have different meanings based on second parameter
#this is something to look at again later...
class SmtpProxy(Protocol):
noisy = True
peer = None
def setPeer(self, peer):
self.peer = peer
def connectionLost(self, reason):
if self.peer is not None:
self.peer.transport.loseConnection()
self.peer = None
elif self.noisy:
log.msg("Unable to connect to peer: %s" % (reason,))
def dataReceived(self, data):
self.peer.transport.write(data)
def handleData(self, data):
"""Builds data lines for parsing"""
altered_data = ""
#process raw data into lines for processing
lines = data.split('\n')
#add any previous line fragment to begining of first line
lines[0] = self.fragment + lines[0]
#remove and store any final fragement for use in later complete line
self.fragment = string.join(lines[-1:])
lines = lines[:-1]
#parse/alter each full line
for line in lines:
altered_line = self.parseDataLine(line)
if altered_line:
altered_data += altered_line + "\n"
#send altered data on to Server
return altered_data
def parseDataLine(self, line):
"""Override to Parse and sometimes alter each line of data"""
altered_line = line
return altered_line
class SmtpProxyClient(SmtpProxy):
"""This half of the proxy recieves data from an IMAP server and sometimes alters it
before passing it along to the real connected client."""
def __init__(self):
self.fragment = ""
def connectionMade(self):
"""Establishes internal proxy connection and start data flow once remote server connects"""
self.peer.setPeer(self)
self.peer.transport.resumeProducing()
def dataReceived(self, data):
if VERBOSE: print "C P < S: %s" % data
altered_data = self.handleData(data)
if altered_data:
if VERBOSE: print "C < P S: %s" % altered_data
SmtpProxy.dataReceived(self, altered_data)
def parseDataLine(self, line):
"""Parse and sometimes alter each line of data"""
altered_line = line
#Return altered
return altered_line
class SmtpProxyClientFactory(ClientFactory):
protocol = SmtpProxyClient
def setServer(self, server):
self.server = server
def buildProtocol(self, *args, **kw):
new_protocol = ClientFactory.buildProtocol(self, *args, **kw)
new_protocol.setPeer(self.server)
return new_protocol
def clientConnectionFailed(self, connector, reason):
self.server.transport.loseConnection()
class SmtpProxyServer(SmtpProxy):
"""This half of the proxy recieves data from a client and sometimes alters it
before passing it along to the real connected server."""
clientFactory = SmtpProxyClientFactory
def __init__(self):
self.fragment = ""
self.sending = False
self.recipients = []
def connectionMade(self):
"""Pauses data flow until remote server connects, sets up for future data flow"""
self.transport.pauseProducing()
client = self.clientFactory()
client.setServer(self)
reactor.connectSSL(self.factory.host, self.factory.port, client, ssl.ClientContextFactory())
def dataReceived(self, data):
"""Overide for handling data sent from client"""
if VERBOSE: print "C > P S: %s" % data
altered_data = self.handleData(data)
if altered_data:
if VERBOSE: print "C P > S: %s" % altered_data
SmtpProxy.dataReceived(self, altered_data)
def parseDataLine(self, line):
"""Parse and sometimes alter each line of data"""
altered_line = line
if len(line) >= 4 and line[:4].upper() == "DATA":
self.sending = True
if DEBUG: print "PROXY INTERNAL: <RECIPIENTS=%s>"%self.recipients
elif self.stripReturn(line) == ".":
self.sending = False
if not self.sending:
if len(line) >= 8 and line[:8].upper() == "RCPT TO:":
self.recipients.append(self.stripReturn(line[8:]))
return altered_line
def stripReturn(self,line):
"""Strip \r character if it exists at end of line"""
if line[-1:] == "\r":
return line[:-1]
return line
class SmtpProxyServerFactory(Factory):
protocol = SmtpProxyServer
def __init__(self, host, port):
self.host = host
self.port = port
#Execute from shell
if __name__ == "__main__":
factory = SmtpProxyServerFactory(HOST_NAME,465)
reactor.listenSSL(465, factory, ssl.DefaultOpenSSLContextFactory('ssl/server.key', 'ssl/server.crt'))
#SSL key and self signed cert generated following instructions at https://help.ubuntu.com/10.04/serverguide/certificates-and-security.html
print "Proxy Started"
reactor.run()
print "Proxy Stopped"