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.
31 # By default we output the HTML to stdout
37 class MSN_MSG(MSNMessage
):
38 def __init__(self
, timestamp
, user
, message
):
39 self
.timestamp
= timestamp
41 self
.message
= message
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+)")
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"
65 def AddPacket(self
, packet
):
66 self
.packets
.append(packet
)
69 for packet
in self
.packets
:
70 msg
= self
.CreateMSNMessage(packet
)
72 self
.messages
.append(msg
)
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
)
87 user
= self
.DEFAULT_USER
90 m
= self
.re_MSG_in
.search(child
.show
)
92 user
= m
.group("alias")
94 if user
== self
.USER_NOT_FOUND
:
95 print >> sys
.stderr
, "No match for", child
.show
103 if len(msnms
.children
) > 5:
106 while i
< len(msnms
.children
):
107 msg
+= msnms
.children
[i
].show
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")
128 i
= new_string
.find("\\")
132 if i
== len(new_string
) - 1:
133 # Just let the default action
134 # copy everything to 'bytes'
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)
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:]
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
)
167 def CreateHTML(self
, default_user
):
168 if not self
.messages
:
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
:
181 user_color
= self
.DEFAULT_USER_COLOR
182 user_text_color
= self
.DEFAULT_USER_TEXT_COLOR
185 user_text_color
= self
.USER_TEXT_COLOR
186 if user_color_assignments
.has_key(user
):
187 user_color
= user_color_assignments
[user
]
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
)
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>"
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
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
:
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
]
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
)
246 conv
= self
.conversations_map
[key
]
248 conv
.AddPacket(packet
)
251 def CreateHTML(self
, default_user
):
252 if not self
.conversations
:
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
):
269 <HTML><TITLE>MSN Conversation</TITLE>
270 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
273 for filename
in filenames
:
274 run_filename(filename
, default_user
, tshark
)
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"
292 default_user
= "Unknown"
295 optstring
= "ho:t:u:"
299 opts
, args
= getopt
.getopt(sys
.argv
[1:], optstring
, longopts
)
300 except getopt
.GetoptError
:
303 for opt
, arg
in opts
:
304 if opt
== "-h" or opt
== "--help":
311 out_fh
= open(filename
, "w")
313 sys
.exit("Could not open %s for writing." % (filename
,))
322 sys
.exit("Unhandled command-line option: " + opt
)
324 run(args
, default_user
, tshark
)
326 if __name__
== '__main__':