Witness: enum witness_notifyResponse_type
[wireshark-wip.git] / tools / msnchat
blobeb8ff565ce71d69f91ac9b17ffeddf207ebf53d2
1 #!/usr/bin/env python
2 """
3 Process packet capture files and produce a nice HTML
4 report of MSN Chat sessions.
6 Copyright (c) 2003 by Gilbert Ramirez <gram@alumni.rice.edu>
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 """
23 import os
24 import re
25 import sys
26 import array
27 import string
28 import WiresharkXML
29 import getopt
31 # By default we output the HTML to stdout
32 out_fh = sys.stdout
34 class MSNMessage:
35 pass
37 class MSN_MSG(MSNMessage):
38 def __init__(self, timestamp, user, message):
39 self.timestamp = timestamp
40 self.user = user
41 self.message = message
44 class Conversation:
45 """Keeps track of a single MSN chat session"""
47 re_MSG_out = re.compile("MSG (?P<TrID>\d+) (?P<ACKTYPE>[UNA]) (?P<len>\d+)")
48 re_MSG_in = re.compile("MSG (?P<user>\S+)@(?P<domain>\S+) (?P<alias>\S+) (?P<len>\d+)")
50 USER_NOT_FOUND = -1
51 DEFAULT_USER = None
54 DEFAULT_USER_COLOR = "#0000ff"
55 USER_COLORS = [ "#ff0000", "#00ff00",
56 "#800000", "#008000", "#000080" ]
58 DEFAULT_USER_TEXT_COLOR = "#000000"
59 USER_TEXT_COLOR = "#000080"
61 def __init__(self):
62 self.packets = []
63 self.messages = []
65 def AddPacket(self, packet):
66 self.packets.append(packet)
68 def Summarize(self):
69 for packet in self.packets:
70 msg = self.CreateMSNMessage(packet)
71 if msg:
72 self.messages.append(msg)
73 else:
74 #XXX
75 pass
78 def CreateMSNMessage(self, packet):
79 msnms = packet.get_items("msnms")[0]
81 # Check the first line in the msnms transmission for the user
82 child = msnms.children[0]
83 user = self.USER_NOT_FOUND
85 m = self.re_MSG_out.search(child.show)
86 if m:
87 user = self.DEFAULT_USER
89 else:
90 m = self.re_MSG_in.search(child.show)
91 if m:
92 user = m.group("alias")
94 if user == self.USER_NOT_FOUND:
95 print >> sys.stderr, "No match for", child.show
96 sys.exit(1)
97 return None
99 msg = ""
101 i = 5
102 check_trailing = 0
103 if len(msnms.children) > 5:
104 check_trailing = 1
106 while i < len(msnms.children):
107 msg += msnms.children[i].show
108 if check_trailing:
109 j = msg.find("MSG ")
110 if j >= 0:
111 msg = msg[:j]
112 i += 5
113 else:
114 i += 6
115 else:
116 i += 6
118 timestamp = packet.get_items("frame.time")[0].get_show()
119 i = timestamp.rfind(".")
120 timestamp = timestamp[:i]
122 return MSN_MSG(timestamp, user, msg)
124 def MsgToHTML(self, text):
125 bytes = array.array("B")
127 new_string = text
128 i = new_string.find("\\")
130 while i > -1:
131 # At the end?
132 if i == len(new_string) - 1:
133 # Just let the default action
134 # copy everything to 'bytes'
135 break
137 if new_string[i+1] in string.digits:
138 left = new_string[:i]
139 bytes.fromstring(left)
141 right = new_string[i+4:]
143 oct_string = new_string[i+1:i+4]
144 char = int(oct_string, 8)
145 bytes.append(char)
147 new_string = right
149 # ignore \r and \n
150 elif new_string[i+1] in "rn":
151 copy_these = new_string[:i]
152 bytes.fromstring(copy_these)
153 new_string = new_string[i+2:]
155 else:
156 copy_these = new_string[:i+2]
157 bytes.fromstring(copy_these)
158 new_string = new_string[i+2:]
160 i = new_string.find("\\")
163 bytes.fromstring(new_string)
165 return bytes
167 def CreateHTML(self, default_user):
168 if not self.messages:
169 return
171 print >> out_fh, """
172 <HR><BR><H3 Align=Center> ---- New Conversation @ %s ----</H3><BR>""" \
173 % (self.messages[0].timestamp)
175 user_color_assignments = {}
177 for msg in self.messages:
178 # Calculate 'user' and 'user_color' and 'user_text_color'
179 if msg.user == self.DEFAULT_USER:
180 user = default_user
181 user_color = self.DEFAULT_USER_COLOR
182 user_text_color = self.DEFAULT_USER_TEXT_COLOR
183 else:
184 user = msg.user
185 user_text_color = self.USER_TEXT_COLOR
186 if user_color_assignments.has_key(user):
187 user_color = user_color_assignments[user]
188 else:
189 num_assigned = len(user_color_assignments.keys())
190 user_color = self.USER_COLORS[num_assigned]
191 user_color_assignments[user] = user_color
193 # "Oct 6, 2003 21:45:25" --> "21:45:25"
194 timestamp = msg.timestamp.split()[-1]
196 htmlmsg = self.MsgToHTML(msg.message)
198 print >> out_fh, """
199 <FONT COLOR="%s"><FONT SIZE="2">(%s) </FONT><B>%s:</B></FONT> <FONT COLOR="%s">""" \
200 % (user_color, timestamp, user, user_text_color)
202 htmlmsg.tofile(out_fh)
204 print >> out_fh, "</FONT><BR>"
207 class CaptureFile:
208 """Parses a single a capture file and keeps track of
209 all chat sessions in the file."""
211 def __init__(self, capture_filename, tshark):
212 """Run tshark on the capture file and parse
213 the data."""
214 self.conversations = []
215 self.conversations_map = {}
217 pipe = os.popen(tshark + " -Tpdml -n -R "
218 "'msnms contains \"X-MMS-IM-Format\"' "
219 "-r " + capture_filename, "r")
221 WiresharkXML.parse_fh(pipe, self.collect_packets)
223 for conv in self.conversations:
224 conv.Summarize()
226 def collect_packets(self, packet):
227 """Collect the packets passed back from WiresharkXML.
228 Sort them by TCP/IP conversation, as there could be multiple
229 clients per machine."""
230 # Just in case we're looking at tunnelling protocols where
231 # more than one IP or TCP header exists, look at the last one,
232 # which would be the one inside the tunnel.
233 src_ip = packet.get_items("ip.src")[-1].get_show()
234 dst_ip = packet.get_items("ip.dst")[-1].get_show()
235 src_tcp = packet.get_items("tcp.srcport")[-1].get_show()
236 dst_tcp = packet.get_items("tcp.dstport")[-1].get_show()
238 key_params = [src_ip, dst_ip, src_tcp, dst_tcp]
239 key_params.sort()
240 key = '|'.join(key_params)
242 if not self.conversations_map.has_key(key):
243 conv = self.conversations_map[key] = Conversation()
244 self.conversations.append(conv)
245 else:
246 conv = self.conversations_map[key]
248 conv.AddPacket(packet)
251 def CreateHTML(self, default_user):
252 if not self.conversations:
253 return
255 for conv in self.conversations:
256 conv.CreateHTML(default_user)
259 def run_filename(filename, default_user, tshark):
260 """Process one capture file."""
262 capture = CaptureFile(filename, tshark)
263 capture.CreateHTML(default_user)
266 def run(filenames, default_user, tshark):
267 # HTML Header
268 print >> out_fh, """
269 <HTML><TITLE>MSN Conversation</TITLE>
270 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
271 <BODY>
273 for filename in filenames:
274 run_filename(filename, default_user, tshark)
276 # HTML Footer
277 print >> out_fh, """
278 <HR>
279 </BODY>
280 </HTML>
284 def usage():
285 print >> sys.stderr, "msnchat [OPTIONS] CAPTURE_FILE [...]"
286 print >> sys.stderr, " -o FILE name of output file"
287 print >> sys.stderr, " -t TSHARK location of tshark binary"
288 print >> sys.stderr, " -u USER name for unknown user"
289 sys.exit(1)
291 def main():
292 default_user = "Unknown"
293 tshark = "tshark"
295 optstring = "ho:t:u:"
296 longopts = ["help"]
298 try:
299 opts, args = getopt.getopt(sys.argv[1:], optstring, longopts)
300 except getopt.GetoptError:
301 usage()
303 for opt, arg in opts:
304 if opt == "-h" or opt == "--help":
305 usage()
307 elif opt == "-o":
308 filename = arg
309 global out_fh
310 try:
311 out_fh = open(filename, "w")
312 except IOError:
313 sys.exit("Could not open %s for writing." % (filename,))
315 elif opt == "-u":
316 default_user = arg
318 elif opt == "-t":
319 tshark = arg
321 else:
322 sys.exit("Unhandled command-line option: " + opt)
324 run(args, default_user, tshark)
326 if __name__ == '__main__':
327 main()