semio has asked for the wisdom of the Perl Monks concerning the following question:

fellow monks,

In my current position I'm required to write network reports. To make this process less tedious, I've put together several scripts that will build certain components of a standard report for me. One such script will generate a table that maps services and available service banners to each available IP. I was able to do this by taking the style sections from a previous report and incorporating these into my script...which is as follows.

#!perlenv -w #--------------------------------------------------------- # nessus-stable - nessus service table # David J Kyger - April 27, 2003 # Used to create a table that provides service and banner # information for each IP in a NessusWX export file. #--------------------------------------------------------- use strict; my $style = "<style> p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:\"\"\; margin-top:0in\; margin-right:32.0pt\; margin-bottom:12.0pt\; margin-left:32.0pt\; mso-pagination:widow-orphan\; mso-hyphenate:none\; font-size:12.0pt\; mso-bidi-font-size:10.0pt\; font-family:\"Times New Roman\"\; mso-fareast-font-family:\"Times New Roman\"\;} p.tabletxt0, li.tabletxt0, div.tabletxt0 {mso-style-name:tabletxt\; mso-style-parent:\"\"\; margin:0in\; margin-bottom:.0001pt\; mso-pagination:widow-orphan\; font-size:10.0pt\; font-family:\"Times New Roman\"\; mso-fareast-font-family:\"Times New Roman\"\;} \@page Section1 {size:8.5in 11.0in\; margin:1.0in 1.0in 1.0in 1.0in\; mso-header-margin:.5in\; mso-footer-margin:.5in\; mso-page-numbers:1\; mso-paper-source:0\;} </style>"; my $header = "<table border=1 cellspacing=0 cellpadding=0 width=720 st +yle='width:6.0in\; margin-left:37.45pt\;border-collapse:collapse\;border:none\;mso-borde +r-alt:solid windowtext .5pt\; mso-padding-alt:0in 1.45pt 0in 1.45pt'> <thead> <tr style='height:.1in'> <td width=225 style='width:135.0pt\;border:solid windowtext .5pt\;b +ackground: \#CCCCCC\;padding:0in 1.45pt 0in 1.45pt\;height:.1in'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>IP Addr +ess/</span></p> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>Hostnam +e</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span> <td width=210 style='width:1.75in\;border:solid windowtext .5pt\;bo +rder-left: none\;mso-border-left-alt:solid windowtext .5pt\;background:\#CCCCC +C\; padding:0in 1.45pt 0in 1.45pt\;height:.1in'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>Ports I +dentified</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span> <td width=285 style='width:171.0pt\;border:solid windowtext .5pt\;b +order-left: none\;mso-border-left-alt:solid windowtext .5pt\;background:\#CCCCC +C\; padding:0in 1.45pt 0in 1.45pt\;height:.1in'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>System/ +Services Identification</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span> </tr> </thead>"; my $tablecolumn1astart = "<tr style='height:12.35pt'> <td width=225 rowspan="; my $tablecolumn1bstart = " valign=top style='width:135.0pt\;border:sol +id windowtext .5pt\; border-top:none\;mso-border-top-alt:solid windowtext .5pt\;padding:0 +in 1.45pt 0in 1.45pt\; height:12.35pt'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>"; my $tablecolumn1end = "</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span>"; my $tablecolumn2start = "<td width=210 valign=top style='width:1.75in\ +;border-top:none\;border-left: none\;border-bottom:solid windowtext .5pt\;border-right:solid window +text .5pt\; mso-border-top-alt:solid windowtext .5pt\;mso-border-left-alt:solid +windowtext .5pt\; padding:0in 1.45pt 0in 1.45pt\;height:8.5pt'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>"; my $tablecolumn2end = "</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span>"; my $tablecolumn3start = " <td width=285 valign=top style='width:171.0p +t\;border-top:none\;border-left: none\;border-bottom:solid windowtext .5pt\;border-right:solid window +text .5pt\; mso-border-top-alt:solid windowtext .5pt\;mso-border-left-alt:solid +windowtext .5pt\; padding:0in 1.45pt 0in 1.45pt\;height:8.5pt'> <p class=tabletxt0><span style='mso-bookmark:_Toc523573038'>"; my $tablecolumn3end = "</span></p> </td> <span style='mso-bookmark:_Toc523573038'></span> </tr>"; my $servicestart = "<tr style='height:12.35pt'> <td width=210 valign=top style='width:1.75in\;border-top:none\;borde +r-left: none\;border-bottom:solid windowtext .5pt\;border-right:solid window +text .5pt\; mso-border-top-alt:solid windowtext .5pt\;mso-border-left-alt:solid +windowtext .5pt\; padding:0in 1.45pt 0in 1.45pt\;height:12.35pt'> <p class=tabletxt0>"; my $serviceend = "</p> </td> "; my $bannerstart = "<td width=285 valign=top style='width:171.0pt\;bord +er-top:none\;border-left: none\;border-bottom:solid windowtext .5pt\;border-right:solid window +text .5pt\; mso-border-top-alt:solid windowtext .5pt\;mso-border-left-alt:solid +windowtext .5pt\; padding:0in 1.45pt 0in 1.45pt\;height:12.35pt'> <p class=tabletxt0>"; my $bannerend = "</p> </td> </tr>"; my ( $port, $uniqip, $longip ); my ( @nessusdata, @ports, @ips, @uniqip, @splitip, @rearranged ); my %longips; my $nessusdata = $ARGV[0]; my $num = 256; my $count = 0; my $bannerplace = 0; my $allbanners; my @allbanners; my @osreport; my $os; if ( $#ARGV < 1 ) { print "usage: stable <NessusWX Export File> <output file name>"; exit; } my $htmlreport = $ARGV[1] . "\.html"; open( HTML, "> $htmlreport" ) || die "Could not open file: $! \n"; print HTML $style; print HTML $header; open( NESSUSDATA, "< $nessusdata" ) || die "Could not open file: $! \n +"; @nessusdata = <NESSUSDATA>; close(NESSUSDATA); if ( $nessusdata[0] !~ m/\[NessusWX Export File\]/ ) { print "File $ARGV[0] does not appear to be a valid NessusWX Export + File\n"; exit 0; } foreach $nessusdata (@nessusdata) { my @result = split ( /\|/, $nessusdata ); if ( $result[0] =~ /(NessusWX|DATA)/ ) { next; } elsif ( !$result[2] ) { chomp $nessusdata; push @ports, $nessusdata; push @ips, $result[0]; } elsif ($nessusdata =~ m/10336\|INFO/) { $result[4] =~ s/Nmap(.*)running //; $result[4] =~ s/\;//; push(@osreport, $result[0] . "|" . $result[4]); } elsif ($nessusdata =~ m/10337\|INFO/) { $result[4] =~ s/QueSO(.*)host OS is//; $result[4] =~ s/(\;\;\;(.*)$| \;\* )//g; push(@osreport, $result[0] . "|" . $result[4]); } elsif ($nessusdata =~ m/10107(.*)web server/) { $result[4] =~ s/The remote(.*):\;\;//; $result[4] =~ s/\;\;Solution(.*)$//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10281(.*)telnet banner/) { $result[4] =~ s/Remote telnet banner ://; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10267(.*)SSH version/) { $result[4] =~ s/Remote SSH version : //; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10185(.*)POP server/) { push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10092(.*)FTP server banner/) { $result[4] =~ s/Remote FTP server banner :\;//; $result[4] =~ s/\;//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10330(.*)SMTP server/) { $result[4] =~ s/An SMTP server(.*)banner : \;//; $result[4] =~ s/ready at(.*)$//; $result[4] =~ s/\;(.*)$//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10159(.*)NNTP server version/) { $result[4] =~ s/Remote(.*)version ://; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10622(.*)PPTP server/) { $result[4] =~ s/A PPTP(.*)port\;//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10658(.*)Oracle tnslsnr/) { $result[4] =~ s/This host is running(.*)tnslsnr: //; $result[4] =~ s/- Production(.*)$//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } elsif ($nessusdata =~ m/10785(.*)lan manager/) { $result[4] =~ s/The remote(.*)manager is ://; $result[4] =~ s/\;The remote Operating (.*)$//; push(@allbanners,$result[0] . "|" . $result[1] . "|" . $result[4]) +; } } @uniqip = keys %{ { @ips, reverse @ips } }; foreach $uniqip (@uniqip) { @splitip = split ( /\./, $uniqip ); $longip = ( $splitip[0] * ( $num * $num * $num ) ) + ( $splitip[1] * ( $num * $num ) ) + ( $splitip[2] * $num ) + ( $splitip[3] ); $longips{$longip} = $uniqip; } @rearranged = sort { $a <=> $b } keys %longips; foreach (@rearranged) { my $osmarker = 0; my $rowspan = 1; foreach $port (@ports) { my @result = split ( /\|/, $port ); if ( $result[1] =~ m/general/ ) { next; } elsif ( $longips{$_} eq $result[0] ) { $rowspan = $rowspan + 1; } } print HTML $tablecolumn1astart . $rowspan . $tablecolumn1bstart . +$longips{$_} . $tablecolumn1end; foreach $os (@osreport) { my @result = split ( /\|/, $os ); if ( $longips{$_} eq $result[0] ) { print HTML $tablecolumn2start . "OS" . $tablecolumn2end; print HTML $tablecolumn3start . $result[1] . $tablecolumn3end; $osmarker = 1; } } if ($osmarker != 1) { print HTML $tablecolumn2start . "OS" . $tablecolumn2end; print HTML $tablecolumn3start . "n/a" . $tablecolumn3end; $osmarker = 0; } foreach $port (@ports) { my $bannermarker = 0; my @result = split ( /\|/, $port ); if ( $result[1] =~ m/general/ ) { next; } elsif ( $longips{$_} eq $result[0] ) { print HTML $servicestart . $result[1] . $serviceend; foreach $allbanners (@allbanners) { my @resultban = split ( /\|/, $allbanners ); if ( $longips{$_} eq $resultban[0] && $resultban[1] eq $resul +t[1] ) { chomp $resultban[2]; print HTML $bannerstart . $resultban[2] . $bannerend; $bannermarker = 1; } } if ($bannermarker != 1) { print HTML $bannerstart . "n/a" . $bannerend; } } } } print HTML "</table>";
If interested, you can see sample output from this script here.
I'm interested in hearing opinions on this approach as well as other ways to solve this problem, module recommendations, etc. Of course, code critiques are very welcome.

cheers, -semio

Edit by tye, add READMORE tags

Replies are listed 'Best First'.
Re: creating report tables
by Abstraction (Friar) on May 06, 2003 at 05:03 UTC
    First, I would recommend using some sort of templating system, such as HTML::Template. HTML mixed in with your code is generally frowned upon. Once you've removed the HTML from your code you'll find that it's much easier to read and make improvements to.

    In the area of code improvement, I would recommend seperating your code into functions or even to go as far as to split it up into modules if you feel that it's warranted. How far you want to go is up to you, everyone has there own opinion and style.

    Lastly, I would recommed that you read the Program Repair Shop and Red Flags series of articles by Mark-Jason Dominus. They'll give great ideas as to how you can improve your code and what to look for that can use improvement.



Re: creating report tables
by CountZero (Bishop) on May 06, 2003 at 05:52 UTC

    At first glance I don't see a problem with the Perl-code, but I think by using CSS (style sheets) consistently in your HTML, the whole lot would become much less cluttered and easier to maintain.

    Another solution would be to have your Perl-script output XML, which you then transform through XSLT into HTML (or whatever the final form needs to be). By doing so, your script is rid of all formatting code.

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law