1#!/usr/bin/env python 2""" 3Process packet capture files and produce a nice HTML 4report of MSN Chat sessions. 5 6Copyright (c) 2003 by Gilbert Ramirez <gram@alumni.rice.edu> 7 8SPDX-License-Identifier: GPL-2.0-or-later 9""" 10 11import os 12import re 13import sys 14import array 15import string 16import WiresharkXML 17import getopt 18 19# By default we output the HTML to stdout 20out_fh = sys.stdout 21 22class MSNMessage: 23 pass 24 25class MSN_MSG(MSNMessage): 26 def __init__(self, timestamp, user, message): 27 self.timestamp = timestamp 28 self.user = user 29 self.message = message 30 31 32class Conversation: 33 """Keeps track of a single MSN chat session""" 34 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+)") 37 38 USER_NOT_FOUND = -1 39 DEFAULT_USER = None 40 41 42 DEFAULT_USER_COLOR = "#0000ff" 43 USER_COLORS = [ "#ff0000", "#00ff00", 44 "#800000", "#008000", "#000080" ] 45 46 DEFAULT_USER_TEXT_COLOR = "#000000" 47 USER_TEXT_COLOR = "#000080" 48 49 def __init__(self): 50 self.packets = [] 51 self.messages = [] 52 53 def AddPacket(self, packet): 54 self.packets.append(packet) 55 56 def Summarize(self): 57 for packet in self.packets: 58 msg = self.CreateMSNMessage(packet) 59 if msg: 60 self.messages.append(msg) 61 else: 62 #XXX 63 pass 64 65 66 def CreateMSNMessage(self, packet): 67 msnms = packet.get_items("msnms")[0] 68 69 # Check the first line in the msnms transmission for the user 70 child = msnms.children[0] 71 user = self.USER_NOT_FOUND 72 73 m = self.re_MSG_out.search(child.show) 74 if m: 75 user = self.DEFAULT_USER 76 77 else: 78 m = self.re_MSG_in.search(child.show) 79 if m: 80 user = m.group("alias") 81 82 if user == self.USER_NOT_FOUND: 83 print >> sys.stderr, "No match for", child.show 84 sys.exit(1) 85 return None 86 87 msg = "" 88 89 i = 5 90 check_trailing = 0 91 if len(msnms.children) > 5: 92 check_trailing = 1 93 94 while i < len(msnms.children): 95 msg += msnms.children[i].show 96 if check_trailing: 97 j = msg.find("MSG ") 98 if j >= 0: 99 msg = msg[:j] 100 i += 5 101 else: 102 i += 6 103 else: 104 i += 6 105 106 timestamp = packet.get_items("frame.time")[0].get_show() 107 i = timestamp.rfind(".") 108 timestamp = timestamp[:i] 109 110 return MSN_MSG(timestamp, user, msg) 111 112 def MsgToHTML(self, text): 113 bytes = array.array("B") 114 115 new_string = text 116 i = new_string.find("\\") 117 118 while i > -1: 119 # At the end? 120 if i == len(new_string) - 1: 121 # Just let the default action 122 # copy everything to 'bytes' 123 break 124 125 if new_string[i+1] in string.digits: 126 left = new_string[:i] 127 bytes.fromstring(left) 128 129 right = new_string[i+4:] 130 131 oct_string = new_string[i+1:i+4] 132 char = int(oct_string, 8) 133 bytes.append(char) 134 135 new_string = right 136 137 # ignore \r and \n 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:] 142 143 else: 144 copy_these = new_string[:i+2] 145 bytes.fromstring(copy_these) 146 new_string = new_string[i+2:] 147 148 i = new_string.find("\\") 149 150 151 bytes.fromstring(new_string) 152 153 return bytes 154 155 def CreateHTML(self, default_user): 156 if not self.messages: 157 return 158 159 print >> out_fh, """ 160<HR><BR><H3 Align=Center> ---- New Conversation @ %s ----</H3><BR>""" \ 161 % (self.messages[0].timestamp) 162 163 user_color_assignments = {} 164 165 for msg in self.messages: 166 # Calculate 'user' and 'user_color' and 'user_text_color' 167 if msg.user == self.DEFAULT_USER: 168 user = default_user 169 user_color = self.DEFAULT_USER_COLOR 170 user_text_color = self.DEFAULT_USER_TEXT_COLOR 171 else: 172 user = msg.user 173 user_text_color = self.USER_TEXT_COLOR 174 if user_color_assignments.has_key(user): 175 user_color = user_color_assignments[user] 176 else: 177 num_assigned = len(user_color_assignments.keys()) 178 user_color = self.USER_COLORS[num_assigned] 179 user_color_assignments[user] = user_color 180 181 # "Oct 6, 2003 21:45:25" --> "21:45:25" 182 timestamp = msg.timestamp.split()[-1] 183 184 htmlmsg = self.MsgToHTML(msg.message) 185 186 print >> out_fh, """ 187<FONT COLOR="%s"><FONT SIZE="2">(%s) </FONT><B>%s:</B></FONT> <FONT COLOR="%s">""" \ 188 % (user_color, timestamp, user, user_text_color) 189 190 htmlmsg.tofile(out_fh) 191 192 print >> out_fh, "</FONT><BR>" 193 194 195class CaptureFile: 196 """Parses a single a capture file and keeps track of 197 all chat sessions in the file.""" 198 199 def __init__(self, capture_filename, tshark): 200 """Run tshark on the capture file and parse 201 the data.""" 202 self.conversations = [] 203 self.conversations_map = {} 204 205 pipe = os.popen(tshark + " -Tpdml -n -R " 206 "'msnms contains \"X-MMS-IM-Format\"' " 207 "-r " + capture_filename, "r") 208 209 WiresharkXML.parse_fh(pipe, self.collect_packets) 210 211 for conv in self.conversations: 212 conv.Summarize() 213 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() 225 226 key_params = [src_ip, dst_ip, src_tcp, dst_tcp] 227 key_params.sort() 228 key = '|'.join(key_params) 229 230 if not self.conversations_map.has_key(key): 231 conv = self.conversations_map[key] = Conversation() 232 self.conversations.append(conv) 233 else: 234 conv = self.conversations_map[key] 235 236 conv.AddPacket(packet) 237 238 239 def CreateHTML(self, default_user): 240 if not self.conversations: 241 return 242 243 for conv in self.conversations: 244 conv.CreateHTML(default_user) 245 246 247def run_filename(filename, default_user, tshark): 248 """Process one capture file.""" 249 250 capture = CaptureFile(filename, tshark) 251 capture.CreateHTML(default_user) 252 253 254def run(filenames, default_user, tshark): 255 # HTML Header 256 print >> out_fh, """ 257<HTML><TITLE>MSN Conversation</TITLE> 258<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 259<BODY> 260""" 261 for filename in filenames: 262 run_filename(filename, default_user, tshark) 263 264 # HTML Footer 265 print >> out_fh, """ 266<HR> 267</BODY> 268</HTML> 269""" 270 271 272def usage(): 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" 277 sys.exit(1) 278 279def main(): 280 default_user = "Unknown" 281 tshark = "tshark" 282 283 optstring = "ho:t:u:" 284 longopts = ["help"] 285 286 try: 287 opts, args = getopt.getopt(sys.argv[1:], optstring, longopts) 288 except getopt.GetoptError: 289 usage() 290 291 for opt, arg in opts: 292 if opt == "-h" or opt == "--help": 293 usage() 294 295 elif opt == "-o": 296 filename = arg 297 global out_fh 298 try: 299 out_fh = open(filename, "w") 300 except IOError: 301 sys.exit("Could not open %s for writing." % (filename,)) 302 303 elif opt == "-u": 304 default_user = arg 305 306 elif opt == "-t": 307 tshark = arg 308 309 else: 310 sys.exit("Unhandled command-line option: " + opt) 311 312 run(args, default_user, tshark) 313 314if __name__ == '__main__': 315 main() 316