]>
Commit | Line | Data |
---|---|---|
aac0f711 CS |
1 | #!/usr/bin/perl |
2 | # | |
3 | # IMSpector real-time log viewer | |
4 | # (c) SmoothWall Ltd 2008 | |
5 | # | |
6 | # Released under the GPL v2. | |
7 | ||
8 | use POSIX qw(strftime); | |
9 | ||
10 | # Common configuration parameters. | |
11 | ||
12 | my $logbase = "/var/log/imspector/"; | |
13 | my $oururl = '/cgi-bin/imspector.cgi'; | |
14 | ||
15 | # Colours | |
16 | ||
17 | my $protocol_colour = '#06264d'; | |
18 | my $local_colour = '#1d398b'; | |
19 | my $remote_colour = '#2149c1'; | |
20 | my $conversation_colour = '#335ebe'; | |
21 | ||
22 | my $local_user_colour = 'blue'; | |
23 | my $remote_user_colour = 'green'; | |
24 | ||
25 | # No need to change anything from this point | |
26 | ||
27 | # Page declaration, The following code should parse the CGI headers, and render the page | |
28 | # accordingly... How you do this depends what environment you're in. | |
29 | ||
30 | my %cgiparams; | |
31 | ||
32 | print "Content-type: text/html\n"; | |
33 | print "\n"; | |
34 | ||
35 | if ($ENV{'QUERY_STRING'}) | |
36 | { | |
37 | my @vars = split('\&', $ENV{'QUERY_STRING'}); | |
38 | foreach $_ (@vars) | |
39 | { | |
40 | my ($var, $val) = split(/\=/); | |
41 | $cgiparams{$var} = $val; | |
42 | } | |
43 | } | |
44 | ||
45 | # Act in Tail mode (as in just generate the raw logs and pass back to the other CGI | |
46 | ||
47 | if ( defined $cgiparams{'mode'} and $cgiparams{'mode'} eq "render" ){ | |
48 | &parser( $cgiparams{'section'}, $cgiparams{'offset'}, $cgiparams{'conversation'}, $cgiparams{'skimhtml'} ); | |
49 | exit; | |
50 | } | |
51 | ||
52 | # Start rendering the Page using Express' rendering functions | |
53 | ||
54 | my $script = &scriptheader(); | |
55 | ||
56 | # Print Some header information | |
57 | ||
58 | print qq| | |
59 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> | |
60 | <html> | |
61 | <head> | |
62 | <title>IMSpector real-time log viewer</title> | |
63 | $script | |
64 | </head> | |
65 | <body> | |
66 | |; | |
67 | ||
68 | print &pagebody(); | |
69 | ||
70 | # and now finish off the HTML page. | |
71 | ||
72 | print qq| | |
73 | </body> | |
74 | </html> | |
75 | |; | |
76 | ||
77 | exit; | |
78 | ||
79 | # ----------------------------------------------------------------------------- | |
80 | # ---------------------- IMSPector Log Viewer Code ---------------------------- | |
81 | # ----------------------------------------------------------------------------- | |
82 | # ^"^ ^"^ | |
83 | ||
84 | # Scriptheader | |
85 | # ------------ | |
86 | # Return the bulk of the page, which should reside in the pages <head> field | |
87 | ||
88 | sub scriptheader | |
89 | { | |
90 | my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime( time() ); | |
91 | $year += 1900; $mon++; | |
92 | my $conversation = sprintf( "%.4d-%.2d-%.2d", $year, $mon, $mday ); | |
93 | ||
94 | my $script = qq { | |
95 | <script language="Javascript"> | |
96 | var section ='none'; | |
97 | var moveit = 1; | |
98 | var skimhtml = 1; | |
99 | var the_timeout; | |
100 | var offset = 0; | |
101 | var fragment = ""; | |
102 | var conversationdate = "$conversation"; | |
103 | ||
104 | function xmlhttpPost() | |
105 | { | |
106 | var self = this; | |
107 | ||
108 | if (window.XMLHttpRequest) { | |
109 | // Mozilla/Safari | |
110 | self.xmlHttpReq = new XMLHttpRequest(); | |
111 | } else if (window.ActiveXObject) { | |
112 | // IE | |
113 | self.xmlHttpReq = new ActiveXObject("Microsoft.XMLHTTP"); | |
114 | } | |
115 | ||
116 | var url = "$url" + "?mode=render§ion=" + section + "&skimhtml=" + skimhtml + "&offset=" + offset + "&conversation=" + conversationdate; | |
117 | self.xmlHttpReq.open('POST', url, true); | |
118 | self.xmlHttpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); | |
119 | ||
120 | self.xmlHttpReq.onreadystatechange = function() { | |
121 | if ( self.xmlHttpReq && self.xmlHttpReq.readyState == 4) { | |
122 | updatepage(self.xmlHttpReq.responseText); | |
123 | } | |
124 | } | |
125 | ||
126 | document.getElementById('status').style.display = "inline"; | |
127 | ||
128 | self.xmlHttpReq.send( url ); | |
129 | delete self; | |
130 | } | |
131 | ||
132 | function updatepage(str){ | |
133 | /* update the list of conversations ( if we need to ) */ | |
134 | ||
135 | var parts = str.split( "--END--\\n" ); | |
136 | ||
137 | var lines = parts[0].split( "\\n" ); | |
138 | ||
139 | for ( var line = 0 ; line < lines.length ; line ++ ){ | |
140 | var a = lines[line].split("|"); | |
141 | ||
142 | if ( !a[1] || !a[2] || !a[3] ){ | |
143 | continue; | |
144 | } | |
145 | ||
146 | /* convert the modification stamp into something sensible */ | |
147 | a[5] = parseInt( a[5] * 24 * 60 * 60 ); | |
148 | ||
149 | /* create titling information if needed */ | |
150 | if ( !document.getElementById( a[1] ) ){ | |
151 | document.getElementById('conversations').innerHTML += "<div id='" + a[1] + "_t' style='width: 100%; background-color: #d9d9f3; color: $protocol_colour;'>" + a[1] + "</div><div id='" + a[1] + "' style='width: 100%; background-color: #e5e5f3;'></div>"; | |
152 | } | |
153 | ||
154 | if ( !document.getElementById( a[1] + "_" + a[2] ) ){ | |
155 | document.getElementById( a[1] ).innerHTML += "<div id='" + a[1] + "_" + a[2] + "_t' style='width: 100%; color: $local_colour; padding-left: 5px;'>" + a[2] + "</div><div id='" + a[1] + "_" + a[2] + "' style='width: 100%; background-color: #efeffa; border-bottom: solid 1px #d9d9f3;'></div>"; | |
156 | } | |
157 | ||
158 | if ( !document.getElementById( a[1] + "_" + a[2] + "_" + a[3] ) ){ | |
159 | document.getElementById( a[1] + "_" + a[2] ).innerHTML += "<div id='" + a[1] + "_" + a[2] + "_" + a[3] + "_t' style='width: 100%; color: $remote_colour; padding-left: 10px; cursor: pointer;' onClick=" + '"' + "setsection('" + a[1] + "|" + a[2] + "|" + a[3] + "|" + a[4] + "');" + '"' + "' + >» " + a[3] + "</div><div id='" + a[1] + "_" + a[2] + "_" + a[3] + "' style='width: 1%; display: none;'></div>"; | |
160 | } | |
161 | ||
162 | if ( document.getElementById( a[1] + "_" + a[2] + "_" + a[3] ) && a[5] <= 60 ){ | |
163 | /* modified within the last minute! */ | |
164 | document.getElementById( a[1] + "_" + a[2] + "_" + a[3] + "_t" ).style.fontWeight = "bold"; | |
165 | } else { | |
166 | document.getElementById( a[1] + "_" + a[2] + "_" + a[3] + "_t" ).style.fontWeight = "normal"; | |
167 | } | |
168 | delete a; | |
169 | } | |
170 | ||
171 | delete lines; | |
172 | ||
173 | /* rework the list of active conversation dates ... */ | |
174 | ||
175 | var lines = parts[1].split( "\\n" ); | |
176 | ||
177 | var the_select = document.getElementById('conversationdates'); | |
178 | the_select.options.length = 0; | |
179 | ||
180 | for ( var line = 0 ; line < lines.length ; line ++ ){ | |
181 | if ( lines[ line ] != "" ){ | |
182 | the_select.options.length ++; | |
183 | the_select.options[ line ].text = lines[line]; | |
184 | the_select.options[ line ].value = lines[line]; | |
185 | if ( lines[line] == conversationdate ){ | |
186 | the_select.selectedIndex = line; | |
187 | } | |
188 | } | |
189 | } | |
190 | ||
191 | delete the_select; | |
192 | delete lines; | |
193 | ||
194 | /* determine the title of this conversation */ | |
195 | if ( parts[2] ){ | |
196 | var details = parts[2].split(","); | |
197 | var title = details[0] + " conversation between <span style='color: $local_user_colour;'>" + details[ 1 ] + "</span> and <span style='color: $remote_user_colour;'>" + details[2] + "</span>"; | |
198 | if ( !details[1] ){ | |
199 | title = " "; | |
200 | } | |
201 | ||
202 | document.getElementById('status').style.display = "none"; | |
203 | ||
204 | var bottom = parseInt( document.getElementById('content').scrollTop ); | |
205 | var bottom2 = parseInt( document.getElementById('content').style.height ); | |
206 | var absheight = parseInt( bottom + bottom2 ); | |
207 | ||
208 | if ( absheight == document.getElementById('content').scrollHeight ){ | |
209 | moveit = 1; | |
210 | } | |
211 | ||
212 | fragment += parts[4]; | |
213 | document.getElementById('content').innerHTML = "<table style='width: 100%'>" + fragment + "</table>"; | |
214 | if (moveit == 1 ){ | |
215 | document.getElementById('content').scrollTop = 0; | |
216 | document.getElementById('content').scrollTop = document.getElementById('content').scrollHeight; | |
217 | } | |
218 | ||
219 | document.getElementById('content_title').innerHTML = title; | |
220 | delete details; | |
221 | delete title; | |
222 | delete bottom; | |
223 | delete bottom2; | |
224 | delete absheight; | |
225 | } | |
226 | ||
227 | /* set the file offset */ | |
228 | offset = parts[3]; | |
229 | ||
230 | if ( moveit == 1 ){ | |
231 | document.getElementById('scrlck').style.color = 'green'; | |
232 | } else { | |
233 | document.getElementById('scrlck').style.color = '#202020'; | |
234 | } | |
235 | ||
236 | if ( skimhtml == 1 ){ | |
237 | document.getElementById('skimhtml').style.color = 'green'; | |
238 | } else { | |
239 | document.getElementById('skimhtml').style.color = '#202020'; | |
240 | } | |
241 | ||
242 | delete parts; | |
243 | ||
244 | the_timeout = setTimeout( "xmlhttpPost();", 5000 ); | |
245 | } | |
246 | ||
247 | function setsection( value ) | |
248 | { | |
249 | section = value; | |
250 | offset = 0; | |
251 | fragment = ""; | |
252 | moveit = 1; | |
253 | clearTimeout(the_timeout); | |
254 | xmlhttpPost(); | |
255 | document.getElementById('content').scrollTop = 0; | |
256 | document.getElementById('content').scrollTop = document.getElementById('content').scrollHeight; | |
257 | } | |
258 | ||
259 | function togglescrlck() | |
260 | { | |
261 | if ( moveit == 1 ){ | |
262 | moveit = 0; | |
263 | document.getElementById('scrlck').style.color = '#202020'; | |
264 | } else { | |
265 | moveit = 1; | |
266 | document.getElementById('scrlck').style.color = 'green'; | |
267 | } | |
268 | } | |
269 | ||
270 | function toggleskimhtml() | |
271 | { | |
272 | if ( skimhtml == 1 ){ | |
273 | skimhtml = 0; | |
274 | document.getElementById('skimhtml').style.color = '#202020'; | |
275 | } else { | |
276 | skimhtml = 1; | |
277 | document.getElementById('skimhtml').style.color = 'green'; | |
278 | } | |
279 | clearTimeout(the_timeout); | |
280 | xmlhttpPost(); | |
281 | } | |
282 | ||
283 | function setDate() | |
284 | { | |
285 | var the_select = document.getElementById('conversationdates'); | |
286 | conversationdate = the_select.options[ the_select.selectedIndex ].value; | |
287 | document.getElementById('conversations').innerHTML = ""; | |
288 | fragment = ""; | |
289 | offset = 0; | |
290 | section = ""; | |
291 | clearTimeout(the_timeout); | |
292 | xmlhttpPost(); | |
293 | } | |
294 | ||
295 | </script> | |
296 | }; | |
297 | ||
298 | return $script; | |
299 | } | |
300 | ||
301 | # pagebody function | |
302 | # ----------------- | |
303 | # Return the HTML fragment which includes the page body. | |
304 | ||
305 | sub pagebody | |
306 | { | |
307 | my $body = qq { | |
308 | <div style='width: 100%; text-align: right;'><span id='status' style='background-color: #fef1b5; display: none;'>Updating</span> </div> | |
309 | <style> | |
310 | ||
311 | .powerbutton { | |
312 | color: #202020; | |
313 | font-size: 9pt; | |
314 | cursor: pointer; | |
315 | } | |
316 | ||
317 | .remoteuser { | |
318 | color: $remote_user_colour; | |
319 | font-size: 9pt; | |
320 | } | |
321 | ||
322 | .localuser { | |
323 | color: $local_user_colour; | |
324 | font-size: 9pt; | |
325 | } | |
326 | ||
327 | </style> | |
328 | <table style='width: 100%;'> | |
329 | <tr> | |
330 | <td style='width: 170px; text-align: left; vertical-align: top; overflow: auto; font-size: 8pt; border: solid 1px #c0c0c0;'><div id='conversations' style='height: 400px; overflow: auto; font-size: 10px; overflow-x: hidden;'></div></td> | |
331 | <td style='border: solid 1px #c0c0c0;'> | |
332 | <div id='content_title' style='height: 20px; overflow: auto; vertical-align: top; background-color: #E6E8FA; border-bottom: solid 1px #c0c0c0;'></div> | |
333 | <div id='content' style='height: 376px; overflow: auto; vertical-align: bottom; border-bottom: solid 1px #c0c0c0; overflow-x: hidden;'></div> | |
334 | <div id='content_subtitle' style='height: 24px; overflow: auto; vertical-align: top; background-color: #E6E8FA; width: 100%; padding: 2px;'> | |
335 | <div style='width: 60%; float: left;' id='statuswindow'> | |
336 | For conversations on: | |
337 | <select id='conversationdates' onChange='setDate()';> | |
338 | </select> | |
339 | </div> | |
340 | <div style='width: 40%; text-align: right; float: right;'> | |
341 | <span class='powerbutton' id='skimhtml' onClick='toggleskimhtml();'>[HTML]</span> | |
342 | <span class='powerbutton' id='scrlck' onClick='togglescrlck();'>[SCROLL LOCK]</span> | |
343 | </div> | |
344 | </div> | |
345 | </td> | |
346 | </tr> | |
347 | </table> | |
348 | <script>xmlhttpPost();</script> | |
349 | }; | |
350 | return $body; | |
351 | } | |
352 | ||
353 | # Parser function ... | |
354 | # --------------- | |
355 | # Retrieves the IMspector logs from their nestling place and displays them accordingly. | |
356 | ||
357 | sub parser | |
358 | { | |
359 | my ( $section, $offset, $conversationdate, $skimhtml ) = @_; | |
360 | # render the user list ... | |
361 | ||
362 | chomp $offset; | |
363 | ||
364 | unless ( $offset =~ /^([\d]*)$/ ){ | |
365 | print STDERR "Illegal offset ($offset $1) resetting...\n"; | |
366 | $offset = 0; | |
367 | } | |
368 | ||
369 | # browse for the available protocols | |
370 | unless ( opendir DIR, $logbase ){ | |
371 | exit; | |
372 | } | |
373 | ||
374 | my %conversationaldates; | |
375 | my @protocols = grep {!/^\./} readdir(DIR); | |
376 | ||
377 | foreach my $protocol ( @protocols ){ | |
378 | unless ( opendir LUSER, "$logbase$protocol" ){ | |
379 | next; | |
380 | } | |
381 | ||
382 | my @localusers = grep {!/^\./} readdir(LUSER); | |
383 | foreach my $localuser ( @localusers ){ | |
384 | unless ( opendir RUSER, "$logbase$protocol/$localuser/" ){ | |
385 | next; | |
386 | } | |
387 | my @remoteusers = grep {!/^\./} readdir( RUSER ); | |
388 | foreach my $remoteuser ( @remoteusers ){ | |
389 | unless ( opendir CONVERSATIONS, "$logbase$protocol/$localuser/$remoteuser/" ){ | |
390 | next; | |
391 | } | |
392 | my @conversations = grep {!/^\./} readdir( CONVERSATIONS ); | |
393 | foreach my $conversation ( @conversations ){ | |
394 | $conversationaldates{ $conversation } = $localuser; | |
395 | } | |
396 | ||
397 | closedir CONVERSATIONS; | |
398 | ||
399 | my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime( time() ); | |
400 | $year += 1900; $mon++; | |
401 | my $conversation = sprintf( "%.4d-%.2d-%.2d", $year, $mon, $mday ); | |
402 | ||
403 | $conversation = $conversationdate if ( defined $conversationdate and $conversationdate ne "" ); | |
404 | ||
405 | if ( -e "$logbase$protocol/$localuser/$remoteuser/$conversation" ){ | |
406 | my $modi = -M "$logbase$protocol/$localuser/$remoteuser/$conversation"; | |
407 | print "|$protocol|$localuser|$remoteuser|$conversation|$modi\n"; | |
408 | } | |
409 | } | |
410 | closedir RUSER; | |
411 | } | |
412 | closedir LUSER; | |
413 | } | |
414 | closedir DIR; | |
415 | ||
416 | print "--END--\n"; | |
417 | ||
418 | # display a list of conversational dates .. i.e. the dates which we have conversations on. | |
419 | foreach my $key ( sort keys %conversationaldates ){ | |
420 | print "$key\n"; | |
421 | } | |
422 | ||
423 | print "--END--\n"; | |
424 | ||
425 | ||
426 | # now check the log file ... | |
427 | ||
428 | if ( $section ne "none" ){ | |
429 | my ( $protocol, $localuser, $remoteuser, $conversation ) = split /\|/, $section; | |
430 | ||
431 | print "$protocol, $localuser, $remoteuser, $conversation\n"; | |
432 | print "--END--\n"; | |
433 | ||
434 | my $filename = "$logbase$protocol/$localuser/$remoteuser/$conversation"; | |
435 | ||
436 | unless ( open(FD, "$filename" ) ){ | |
437 | exit; | |
438 | }; | |
439 | ||
440 | # perform some *reasonably* complicated file hopping and stuff of that ilk. | |
441 | # it's not beyond reason that logs *could* be extremely large, so what we | |
442 | # should do to speed up their processing is to jump to the end of the file, | |
443 | # then backtrack a little (say a meg, which is a reasonably amount of logs) | |
444 | # and parse from that point onwards. This, *post* filtering might of course | |
445 | # not leave us with the desired resolution for the tail. If this is the case, | |
446 | # we keep that array and jump back another meg and have another go, concatinating | |
447 | # the logs as we go.... <wheh> | |
448 | ||
449 | my $jumpback = 100000; # not quite a meg, but hey ho | |
450 | my $goneback = 0; | |
451 | my $gonebacklimit = 1000000000; # don't go back more than 100MB | |
452 | ||
453 | # firstly jump to the end of the file. | |
454 | seek( FD, 0, 2 ); | |
455 | ||
456 | my $log_position = tell( FD ); | |
457 | my $end = $log_position; | |
458 | my $end_position = $log_position; | |
459 | ||
460 | my $lines; | |
461 | my @content; | |
462 | ||
463 | my $TAILSIZE = 100; | |
464 | ||
465 | do { | |
466 | $end_position = $log_position; | |
467 | ||
468 | if ( $offset != 0 ){ | |
469 | # we were given a hint as to where we should have been anyhow .. | |
470 | # so we might as well use that to go back to. | |
471 | $log_position = $offset; | |
472 | $goneback = $end_position - $log_position; | |
473 | } else { | |
474 | $log_position -= $jumpback; | |
475 | $goneback += $jumpback; | |
476 | } | |
477 | ||
478 | last if ( $goneback > $gonebacklimit ); | |
479 | ||
480 | if ( $log_position > 0 ){ | |
481 | seek( FD, $log_position, 0 ); | |
482 | } else { | |
483 | seek( FD, 0, 0 ); | |
484 | } | |
485 | ||
486 | my @newcontent; | |
487 | ||
488 | while ( my $line = <FD> and ( tell( FD ) <= $end_position ) ){ | |
489 | chomp $line; | |
490 | push @content, $line; | |
491 | } | |
492 | shift @content if $#content >= $TAILSIZE; | |
493 | } while ( $#content < $TAILSIZE and $log_position > 0 and $offset == 0 ); | |
494 | ||
495 | # trim the content down as we may have more entries than we should. | |
496 | ||
497 | while ( $#content > $TAILSIZE ){ shift @content; }; | |
498 | close FD; | |
499 | ||
500 | print "$end_position\n--END--\n"; | |
501 | ||
502 | foreach my $line ( @content ){ | |
503 | my ( $address, $timestamp, $direction, $type, $filtered, $cat, $data ); | |
504 | ||
505 | ( $address, $timestamp, $direction, $type, $filtered, $cat, $data ) = ( $line =~ /([^,]*),(\d+),(\d+),(\d+),(\d+),([^,]*),(.*)/ ); | |
506 | ||
507 | # are we using the oldstyle or new style logs ? | |
508 | if ( not defined $address and not defined $timestamp ){ | |
509 | ( $address, $timestamp, $type, $data ) = ( $line =~ /([^,]*),([^,]*),([^,]*),(.*)/ ); | |
510 | if ( $type eq "1" ){ | |
511 | $direction = 0; | |
512 | $type = 1; | |
513 | } elsif ( $type eq "2" ){ | |
514 | $direction = 1; | |
515 | $type = 1; | |
516 | } elsif ( $type eq "3" ){ | |
517 | $direction = 0; | |
518 | $type = 2; | |
519 | } elsif ( $type eq "4" ){ | |
520 | $direction = 1; | |
521 | $type = 4; | |
522 | } | |
523 | } | |
524 | ||
525 | my ( $severity, $classification ) = '0', 'None'; | |
526 | if ($cat) { | |
527 | ( $severity, $classification) = split(/ /, $cat, 2); } | |
528 | else { | |
529 | $cat = 'N/A'; } | |
530 | ||
531 | my $red = 255; | |
532 | my $green = 255; | |
533 | my $blue = 255; | |
534 | ||
535 | if ($severity < 0 && $severity >= -5) { | |
536 | $red = 0; $green = abs($severity) * (255 / 5); $blue = 0; } | |
537 | elsif ($severity > 0 && $severity <= 5) { | |
538 | $red = $severity * (255 / 5); $green = 0; $blue = 0; } | |
539 | else { | |
540 | $red = 0; $green = 0; $blue = 0; } | |
541 | ||
542 | my $severitycolour = ''; | |
543 | if ($cat ne 'N/A') { | |
544 | $severitycolour = sprintf("background-color: #%02x%02x%02x;", $red, $green, $blue); } | |
545 | ||
546 | # some protocols (ICQ, I'm looking in your direction) have a habit of starting | |
547 | # and ending each sentence with HTML (evil program) | |
548 | ||
549 | if ( defined $skimhtml and $skimhtml eq "1" ){ | |
550 | $data =~ s/^<HTML><BODY[^>]*><FONT[^>]*>//ig; | |
551 | $data =~ s/<\/FONT><\/BODY><\/HTML>//ig; | |
552 | } | |
553 | ||
554 | $data = &htmlescape($data); | |
555 | $data =~ s/\r\\n/<br>\n/g; | |
556 | my $user = ""; | |
557 | ||
558 | my $bstyle = ""; | |
559 | $bstyle = "style='background-color: #FFE4E1;'" if ( $filtered eq "1" ); | |
560 | ||
561 | if ( $type eq "1" ){ | |
562 | # a message message (from remote user) | |
563 | if ( $direction eq "0" ){ | |
564 | # incoming | |
565 | my $u = $remoteuser; | |
566 | $u =~ s/\@.*//g; | |
567 | $user = "<<span class='remoteuser'>$u</span>>"; | |
568 | } else { | |
569 | # outgoing message | |
570 | my $u = $localuser; | |
571 | $u =~ s/\@.*//g; | |
572 | $user = "<<span class='localuser'>$u</span>>"; | |
573 | } | |
574 | } elsif ($type eq "2") { | |
575 | if ( $direction eq "0" ){ | |
576 | # incoming file | |
577 | my $u = $remoteuser; | |
578 | $u =~ s/\@.*//g; | |
579 | $user = "<<span class='remoteuser'><b><i>$u</i></b></span>>"; | |
580 | } else { | |
581 | # outgoing file | |
582 | my $u = $localuser; | |
583 | $u =~ s/\@.*//g; | |
584 | $user = "<<span class='localuser'><b><i>$u</i></b></span>>"; | |
585 | } | |
586 | } | |
587 | ||
588 | my $t = strftime "%H:%M:%S", localtime($timestamp); | |
589 | if ($type eq "3" or $type eq "4") { | |
590 | $data = "<b><i>$data</i></b>"; | |
591 | } | |
592 | print "<tr $bstyle><td style='width: 30px; vertical-align: top;'>[$t]</td><td style='width: 10px; $severitycolour' title='$cat'><td style=' width: 60px; vertical-align: top;'>$user</td><td style='vertical-align: top;'>$data</td></tr>"; | |
593 | } | |
594 | } | |
595 | return; | |
596 | } | |
597 | ||
598 | sub htmlescape | |
599 | { | |
600 | my ($value) = @_; | |
601 | $value =~ s/&/\&/g; | |
602 | $value =~ s/</\</g; | |
603 | $value =~ s/>/\>/g; | |
604 | $value =~ s/"/\"/g; | |
605 | $value =~ s/'/\'/g; | |
606 | return $value; | |
607 | } |