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 SPDX-License-Identifier: GPL-2.0-or-later
19 # By default we output the HTML to stdout
25 class MSN_MSG(MSNMessage
):
26 def __init__(self
, timestamp
, user
, message
):
27 self
.timestamp
= timestamp
29 self
.message
= message
33 """Keeps track of a single MSN chat session"""
35 re_MSG_out
= re
.compile("MSG (?P<TrID>\d+) (?P<ACKTYPE>[UNA]) (?P<len>\d+)")
36 re_MSG_in
= re
.compile("MSG (?P<user>\S+)@(?P<domain>\S+) (?P<alias>\S+) (?P<len>\d+)")
42 DEFAULT_USER_COLOR
= "#0000ff"
43 USER_COLORS
= [ "#ff0000", "#00ff00",
44 "#800000", "#008000", "#000080" ]
46 DEFAULT_USER_TEXT_COLOR
= "#000000"
47 USER_TEXT_COLOR
= "#000080"
53 def AddPacket(self
, packet
):
54 self
.packets
.append(packet
)
57 for packet
in self
.packets
:
58 msg
= self
.CreateMSNMessage(packet
)
60 self
.messages
.append(msg
)
66 def CreateMSNMessage(self
, packet
):
67 msnms
= packet
.get_items("msnms")[0]
69 # Check the first line in the msnms transmission for the user
70 child
= msnms
.children
[0]
71 user
= self
.USER_NOT_FOUND
73 m
= self
.re_MSG_out
.search(child
.show
)
75 user
= self
.DEFAULT_USER
78 m
= self
.re_MSG_in
.search(child
.show
)
80 user
= m
.group("alias")
82 if user
== self
.USER_NOT_FOUND
:
83 print >> sys
.stderr
, "No match for", child
.show
91 if len(msnms
.children
) > 5:
94 while i
< len(msnms
.children
):
95 msg
+= msnms
.children
[i
].show
106 timestamp
= packet
.get_items("frame.time")[0].get_show()
107 i
= timestamp
.rfind(".")
108 timestamp
= timestamp
[:i
]
110 return MSN_MSG(timestamp
, user
, msg
)
112 def MsgToHTML(self
, text
):
113 bytes
= array
.array("B")
116 i
= new_string
.find("\\")
120 if i
== len(new_string
) - 1:
121 # Just let the default action
122 # copy everything to 'bytes'
125 if new_string
[i
+1] in string
.digits
:
126 left
= new_string
[:i
]
127 bytes
.fromstring(left
)
129 right
= new_string
[i
+4:]
131 oct_string
= new_string
[i
+1:i
+4]
132 char
= int(oct_string
, 8)
138 elif new_string
[i
+1] in "rn":
139 copy_these
= new_string
[:i
]
140 bytes
.fromstring(copy_these
)
141 new_string
= new_string
[i
+2:]
144 copy_these
= new_string
[:i
+2]
145 bytes
.fromstring(copy_these
)
146 new_string
= new_string
[i
+2:]
148 i
= new_string
.find("\\")
151 bytes
.fromstring(new_string
)
155 def CreateHTML(self
, default_user
):
156 if not self
.messages
:
160 <HR><BR><H3 Align=Center> ---- New Conversation @ %s ----</H3><BR>""" \
161 % (self
.messages
[0].timestamp
)
163 user_color_assignments
= {}
165 for msg
in self
.messages
:
166 # Calculate 'user' and 'user_color' and 'user_text_color'
167 if msg
.user
== self
.DEFAULT_USER
:
169 user_color
= self
.DEFAULT_USER_COLOR
170 user_text_color
= self
.DEFAULT_USER_TEXT_COLOR
173 user_text_color
= self
.USER_TEXT_COLOR
174 if user_color_assignments
.has_key(user
):
175 user_color
= user_color_assignments
[user
]
177 num_assigned
= len(user_color_assignments
.keys())
178 user_color
= self
.USER_COLORS
[num_assigned
]
179 user_color_assignments
[user
] = user_color
181 # "Oct 6, 2003 21:45:25" --> "21:45:25"
182 timestamp
= msg
.timestamp
.split()[-1]
184 htmlmsg
= self
.MsgToHTML(msg
.message
)
187 <FONT COLOR="%s"><FONT SIZE="2">(%s) </FONT><B>%s:</B></FONT> <FONT COLOR="%s">""" \
188 % (user_color
, timestamp
, user
, user_text_color
)
190 htmlmsg
.tofile(out_fh
)
192 print >> out_fh
, "</FONT><BR>"
196 """Parses a single a capture file and keeps track of
197 all chat sessions in the file."""
199 def __init__(self
, capture_filename
, tshark
):
200 """Run tshark on the capture file and parse
202 self
.conversations
= []
203 self
.conversations_map
= {}
205 pipe
= os
.popen(tshark
+ " -Tpdml -n -R "
206 "'msnms contains \"X-MMS-IM-Format\"' "
207 "-r " + capture_filename
, "r")
209 WiresharkXML
.parse_fh(pipe
, self
.collect_packets
)
211 for conv
in self
.conversations
:
214 def collect_packets(self
, packet
):
215 """Collect the packets passed back from WiresharkXML.
216 Sort them by TCP/IP conversation, as there could be multiple
217 clients per machine."""
218 # Just in case we're looking at tunnelling protocols where
219 # more than one IP or TCP header exists, look at the last one,
220 # which would be the one inside the tunnel.
221 src_ip
= packet
.get_items("ip.src")[-1].get_show()
222 dst_ip
= packet
.get_items("ip.dst")[-1].get_show()
223 src_tcp
= packet
.get_items("tcp.srcport")[-1].get_show()
224 dst_tcp
= packet
.get_items("tcp.dstport")[-1].get_show()
226 key_params
= [src_ip
, dst_ip
, src_tcp
, dst_tcp
]
228 key
= '|'.join(key_params
)
230 if not self
.conversations_map
.has_key(key
):
231 conv
= self
.conversations_map
[key
] = Conversation()
232 self
.conversations
.append(conv
)
234 conv
= self
.conversations_map
[key
]
236 conv
.AddPacket(packet
)
239 def CreateHTML(self
, default_user
):
240 if not self
.conversations
:
243 for conv
in self
.conversations
:
244 conv
.CreateHTML(default_user
)
247 def run_filename(filename
, default_user
, tshark
):
248 """Process one capture file."""
250 capture
= CaptureFile(filename
, tshark
)
251 capture
.CreateHTML(default_user
)
254 def run(filenames
, default_user
, tshark
):
257 <HTML><TITLE>MSN Conversation</TITLE>
258 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
261 for filename
in filenames
:
262 run_filename(filename
, default_user
, tshark
)
273 print >> sys
.stderr
, "msnchat [OPTIONS] CAPTURE_FILE [...]"
274 print >> sys
.stderr
, " -o FILE name of output file"
275 print >> sys
.stderr
, " -t TSHARK location of tshark binary"
276 print >> sys
.stderr
, " -u USER name for unknown user"
280 default_user
= "Unknown"
283 optstring
= "ho:t:u:"
287 opts
, args
= getopt
.getopt(sys
.argv
[1:], optstring
, longopts
)
288 except getopt
.GetoptError
:
291 for opt
, arg
in opts
:
292 if opt
== "-h" or opt
== "--help":
299 out_fh
= open(filename
, "w")
301 sys
.exit("Could not open %s for writing." % (filename
,))
310 sys
.exit("Unhandled command-line option: " + opt
)
312 run(args
, default_user
, tshark
)
314 if __name__
== '__main__':