Package routerdefense :: Package reports :: Module html_reporting
[hide private]
[frames] | no frames]

Source Code for Module routerdefense.reports.html_reporting

  1  # -*- coding: iso-8859-1 -*- 
  2   
  3  __docformat__ = 'restructuredtext' 
  4  __version__ = '$Id$' 
  5   
  6  from routerdefense.common import * 
  7  from routerdefense.metrics import * 
  8   
9 -def htmlReport(outputFile,genericCfg, ManagementPlaneMetrics, ControlPlaneMetrics, DataPlaneMetrics):
10 import time 11 import datetime 12 import inspect 13 import __builtin__ 14 date = time.strftime('%D') 15 try: 16 report = open(outputFile, 'w') 17 except IOError: 18 print "Error while accessing the Output file. Perhaps you should check the file permissions ;)" 19 exit(1) 20 htmlHeader = [] 21 htmlFooter = [] 22 htmlSummary = [] 23 htmlTOC = [] 24 htmlMPlane = [] 25 htmlCPlane = [] 26 htmlDPlane = [] 27 28 MPlaneCounter = 0 29 CPlaneCounter = 0 30 DPlaneCounter = 0 31 32 htmlTOC.append('<div id="toc">') 33 htmlTOC.append('\n') 34 htmlTOC.append('<ol>') 35 htmlTOC.append('\n') 36 37 htmlSummary.append('<div id="summary">') 38 htmlSummary.append('\n') 39 htmlSummary.append('<table') 40 htmlSummary.append('\n') 41 htmlSummary.append('<thead><tr><th>Management Plane</th><th>Control Plane</th><th>Data Plane</th></tr></thead>') 42 htmlSummary.append('\n') 43 htmlSummary.append('<tbody>') 44 htmlSummary.append('\n') 45 46 htmlHeader.append('<!DOCTYPE html>') 47 htmlHeader.append('\n') 48 htmlHeader.append('<head>') 49 htmlHeader.append('\n') 50 htmlHeader.append('<title>'+ 'RouterDefense ' + date + '</title>') 51 htmlHeader.append('\n') 52 htmlHeader.append('<link rel="stylesheet" media="all" href="style.css" />') 53 htmlHeader.append('\n') 54 htmlHeader.append('<script src="http://code.jquery.com/jquery-latest.min.js"></script>') 55 htmlHeader.append('\n') 56 htmlHeader.append('</head>') 57 htmlHeader.append('\n') 58 htmlHeader.append('<body>') 59 htmlHeader.append('\n') 60 htmlHeader.append('<div id="content">') 61 htmlHeader.append('\n') 62 htmlHeader.append('<header role="banner">') 63 htmlHeader.append('\n') 64 65 htmlHeader.append('<h1><a href="http://code.google.com/p/routerdefense">Router <span>Defense</span></a></h1>') 66 htmlHeader.append('\n') 67 htmlHeader.append('<nav role="navigation"><a href=#MPlane>Management Plane</a> <a href=#CPlane>Control Plane</a> <a href=#DPlane>Data Plane</a></nav>') 68 htmlHeader.append('\n') 69 htmlHeader.append('</header>') 70 htmlHeader.append('\n') 71 htmlHeader.append('<p id="presentation">') 72 htmlHeader.append('\n') 73 htmlHeader.append('Documentation and new release can be found at: <a href="http://code.google.com/p/routerdefense">http://code.google.com/p/routerdefense</a>') 74 htmlHeader.append('\n') 75 htmlHeader.append('<br />') 76 htmlHeader.append('Document reference: ' + time.strftime("%d%b%Y-%H%M%S", time.gmtime()) ) 77 htmlHeader.append('</p>') 78 htmlHeader.append('\n') 79 htmlHeader.append('<section class="content">') 80 htmlHeader.append('\n') 81 82 htmlHeader.append('<table>') 83 htmlHeader.append('\n') 84 htmlHeader.append('<thead>') 85 htmlHeader.append('\n') 86 htmlHeader.append('<tr><th>Hostname</th><th>IOS</th><th>Switching</th><th>Multicast</th><th>QoS</th><th>IPv6</th><th>IPSEC VPN</th></tr>') 87 htmlHeader.append('\n') 88 htmlHeader.append('</thead>') 89 htmlHeader.append('\n') 90 htmlHeader.append('<tbody>') 91 htmlHeader.append('\n') 92 htmlHeader.append('<tr>') 93 htmlHeader.append('\n') 94 htmlHeader.append('<td>' + genericCfg.hostName + '</td>') 95 htmlHeader.append('\n') 96 htmlHeader.append('<td>' + genericCfg.iosVersion + '</td>') 97 htmlHeader.append('\n') 98 htmlHeader.append('<td>' + genericCfg.switchingMethod + '</td>') 99 htmlHeader.append('\n') 100 htmlHeader.append('<td>' + genericCfg.multicast + '</td>') 101 htmlHeader.append('\n') 102 htmlHeader.append('<td>' + genericCfg.qos + '</td>') 103 htmlHeader.append('\n') 104 htmlHeader.append('<td>' + genericCfg.ipv6 + '</td>') 105 htmlHeader.append('\n') 106 htmlHeader.append('<td>' + genericCfg.ipsec + '</td>') 107 htmlHeader.append('\n') 108 htmlHeader.append('</tr>') 109 htmlHeader.append('\n') 110 htmlHeader.append('</tbody>') 111 htmlHeader.append('\n') 112 htmlHeader.append('</table>') 113 htmlHeader.append('\n') 114 htmlHeader.append('<br />') 115 htmlHeader.append('\n') 116 117 htmlMPlane.append('<a id="planeTitle" name="MPlane">Management Plane</a><br /><br />') 118 vulnIndex = 0 119 for name in ManagementPlaneMetrics: 120 if name != 'interface': 121 for k,v in inspect.getmembers(name): 122 if isinstance(v, dict): 123 if v['must_report'] == True: 124 vulnIndex = vulnIndex + 1 125 definition = v['definition'].strip() 126 threatInfo = v['threatInfo'].strip() 127 howtofix = v['howtofix'].split('\n') 128 fixImpact = v['fixImpact'].strip() 129 cvss = v['cvss'].strip() 130 htmlMPlane.append('\n') 131 htmlMPlane.append('<table id="namedef">') 132 htmlMPlane.append('\n') 133 htmlMPlane.append('<tbody>') 134 htmlMPlane.append('\n') 135 htmlMPlane.append('<tr>') 136 htmlMPlane.append('\n') 137 htmlMPlane.append('<a id="planeContent" name="MP'+ str(MPlaneCounter) +'">' + '<td id="name">' + name.long_name + '</td>' + '</a>') 138 htmlMPlane.append('\n') 139 htmlMPlane.append('<td id="definition">' + definition + '</td>') 140 htmlMPlane.append('\n') 141 if float(cvss) <= 4: 142 htmlMPlane.append('<td id="cvssGreen">' + cvss + '/10' + '</td>') 143 htmlMPlane.append('\n') 144 elif float(cvss) <= 7.9 and cvss >=4.1: 145 htmlMPlane.append('<td id="cvssOrange">' + cvss + '/10' + '</td>') 146 htmlMPlane.append('\n') 147 elif float(cvss) >=8: 148 htmlMPlane.append('<td id="cvssRed">' + cvss + '/10' + '</td>') 149 htmlMPlane.append('\n') 150 htmlMPlane.append('<td id="ToggleButton"><button id='+ str(vulnIndex) +'>Hide/Show</button></td>') 151 htmlMPlane.append('</tr>') 152 htmlMPlane.append('\n') 153 htmlMPlane.append('</tbody>') 154 htmlMPlane.append('\n') 155 htmlMPlane.append('</table>') 156 htmlMPlane.append('\n') 157 htmlMPlane.append('<article id="'+ str(vulnIndex) +'"><p>') 158 htmlMPlane.append('\n') 159 htmlMPlane.append('<p id="threatInfo">Threat information: </p>' + threatInfo + '<br />') 160 htmlMPlane.append('\n') 161 htmlMPlane.append('<p id="threatInfo">Fix impact: </p>' + fixImpact + '<br />') 162 htmlMPlane.append('\n') 163 164 htmlMPlane.append('<table id ="fix">') 165 htmlMPlane.append('\n') 166 htmlMPlane.append('<thead>') 167 htmlMPlane.append('\n') 168 htmlMPlane.append('<tr><th id="fix">How to fix</th></tr>') 169 htmlMPlane.append('\n') 170 htmlMPlane.append('</thead>') 171 htmlMPlane.append('\n') 172 htmlMPlane.append('<tbody>') 173 htmlMPlane.append('\n') 174 for index in howtofix: 175 htmlMPlane.append('<tr>') 176 htmlMPlane.append('\n') 177 htmlMPlane.append('<td id="fix">' + index + '</td>') 178 htmlMPlane.append('\n') 179 htmlMPlane.append('</tr>') 180 htmlMPlane.append('\n') 181 htmlMPlane.append('</tbody>') 182 htmlMPlane.append('\n') 183 htmlMPlane.append('</table>') 184 htmlMPlane.append('\n') 185 htmlMPlane.append('</p></article><br />') 186 htmlMPlane.append('\n') 187 htmlMPlane.append(' \ 188 <script> \ 189 $("button[id=' + str(vulnIndex) + ']").click(function () { \ 190 $("article[id=' + str(vulnIndex) + ']").toggle("slow"); \ 191 }); \ 192 </script> \ 193 ') 194 195 htmlTOC.append('<li><a href=#MP'+ str(MPlaneCounter) + '>' + name.long_name + ': ' + definition + '</a></li>') 196 htmlTOC.append('\n') 197 MPlaneCounter = MPlaneCounter + 1 198 199 htmlCPlane.append('<a id="planeTitle" name="CPlane">Control Plane</a><br /><br />') 200 htmlCPlane.append('\n') 201 for name in ControlPlaneMetrics: 202 if name != 'interface': 203 for k,v in inspect.getmembers(name): 204 if isinstance(v, dict): 205 if v['must_report'] == True: 206 vulnIndex = vulnIndex + 1 207 definition = v['definition'].strip() 208 threatInfo = v['threatInfo'].strip() 209 fixImpact = v['fixImpact'].strip() 210 cvss = v['cvss'].strip() 211 if definition == 'OSPF route filtering in': 212 v['howtofix'] = v['howtofix'].strip().replace('[%ospfPID]', ", ".join(name.rfilter_in['pid']), 1) 213 v['howtofix'] = v['howtofix'].strip().replace('[%ospfArea]', ", ".join(name.rfilter_in['area']), 1) 214 elif definition == 'OSPF MD5 authentication': 215 v['howtofix'] = v['howtofix'].strip().replace('[%ospfinterface]', ", ".join(name.auth_md5['interfaces']), 1) 216 v['howtofix'] = v['howtofix'].strip().replace('[%ospfArea]', ", ".join(name.auth_md5['area']), 1) 217 v['howtofix'] = v['howtofix'].strip().replace('[%ospfPID]', ", ".join(name.auth_md5['pid']), 1) 218 elif definition == 'OSPF route filtering out': 219 v['howtofix'] = v['howtofix'].strip().replace('[%ospfPID]', ", ".join(name.rfilter_out['pid']), 1) 220 v['howtofix'] = v['howtofix'].strip().replace('[%ospfArea]', ", ".join(name.rfilter_out['area']), 1) 221 elif definition == 'OSPF passive interface default': 222 v['howtofix'] = v['howtofix'].strip().replace('[%ospfInstance]', ", ".join(name.passive['pid']), 1) 223 elif definition == 'OSPF maximum LSA': 224 v['howtofix'] = v['howtofix'].strip().replace('[%ospfInstance]', ", ".join(name.maxLSA['pid']), 1) 225 elif definition == 'EIGRP MD5 authentication': 226 v['howtofix'] = v['howtofix'].strip().replace('[%eigrpinterface]', ", ".join(name.auth_md5['interfaces']), 1) 227 v['howtofix'] = v['howtofix'].strip().replace('[%eigrpAs]', ", ".join(name.auth_md5['asn']), 1) 228 elif definition == 'EIGRP passive interface default': 229 v['howtofix'] = v['howtofix'].strip().replace('[%eigrpAs]', ", ".join(name.passive['asn']), 1) 230 elif definition == 'EIGRP route filtering inbound': 231 v['howtofix'] = v['howtofix'].strip().replace('[%eigrpAs]', ", ".join(name.rfilter_in['asn']), 1) 232 elif definition == 'EIGRP route filtering outbound': 233 v['howtofix'] = v['howtofix'].strip().replace('[%eigrpAs]', ", ".join(name.rfilter_out['asn']), 1) 234 elif definition == 'RIP MD5 authentication': 235 v['howtofix'] = v['howtofix'].strip().replace('[%ripinterface]', ", ".join(name.auth_md5['interfaces']), 1) 236 237 howtofix = v['howtofix'].split('\n') 238 htmlCPlane.append(' \ 239 <script> \ 240 $("button").click(function () { \ 241 $("article[id=' + str(vulnIndex) + ']").toggle("slow"); \ 242 }); \ 243 </script> \ 244 ') 245 htmlCPlane.append('\n') 246 htmlCPlane.append('<article id="'+ str(vulnIndex) +'"><p>') 247 htmlCPlane.append('\n') 248 htmlCPlane.append('<table id="namedef">') 249 htmlCPlane.append('\n') 250 htmlCPlane.append('<tbody>') 251 htmlCPlane.append('\n') 252 htmlCPlane.append('<tr>') 253 htmlCPlane.append('\n') 254 htmlCPlane.append('<a id="planeContent" name="CP'+ str(CPlaneCounter) +'">' + '<td id="name">' + name.long_name + '</td>' + '</a>') 255 htmlCPlane.append('\n') 256 htmlCPlane.append('<td id="definition">' + definition + '</td>') 257 htmlCPlane.append('\n') 258 if float(cvss) <= 4: 259 htmlCPlane.append('<td id="cvssGreen">' + cvss + '/10' + '</td>') 260 htmlCPlane.append('\n') 261 elif float(cvss) <= 7.9 and cvss >=4.1: 262 htmlCPlane.append('<td id="cvssOrange">' + cvss + '/10' + '</td>') 263 htmlCPlane.append('\n') 264 elif float(cvss) >=8: 265 htmlCPlane.append('<td id="cvssRed">' + cvss + '/10' + '</td>') 266 htmlCPlane.append('\n') 267 htmlCPlane.append('<td id="ToggleButton"><button id='+ str(vulnIndex) +'>Hide/Show</button></td>') 268 htmlCPlane.append('</tr>') 269 htmlCPlane.append('\n') 270 htmlCPlane.append('</tbody>') 271 htmlCPlane.append('\n') 272 htmlCPlane.append('</table>') 273 htmlCPlane.append('\n') 274 275 htmlCPlane.append('<p id="threatInfo">Threat information: </p>' + threatInfo + '<br />') 276 htmlCPlane.append('\n') 277 htmlCPlane.append('<p id="threatInfo">Fix impact: </p>' + fixImpact + '<br />') 278 htmlCPlane.append('\n') 279 280 htmlCPlane.append('<table id ="fix">') 281 htmlCPlane.append('\n') 282 htmlCPlane.append('<thead>') 283 htmlCPlane.append('\n') 284 htmlCPlane.append('<tr><th id="fix">How to fix</th></tr>') 285 htmlCPlane.append('\n') 286 htmlCPlane.append('</thead>') 287 htmlCPlane.append('\n') 288 htmlCPlane.append('<tbody>') 289 htmlCPlane.append('\n') 290 for index in howtofix: 291 htmlCPlane.append('<tr>') 292 htmlCPlane.append('\n') 293 htmlCPlane.append('<td id="fix">' + index + '</td>') 294 htmlCPlane.append('\n') 295 htmlCPlane.append('</tr>') 296 htmlCPlane.append('\n') 297 htmlCPlane.append('</tbody>') 298 htmlCPlane.append('\n') 299 htmlCPlane.append('</table>') 300 htmlCPlane.append('\n') 301 htmlCPlane.append('</p></article><br />') 302 htmlCPlane.append('\n') 303 htmlCPlane.append(' \ 304 <script> \ 305 $("button[id=' + str(vulnIndex) + ']").click(function () { \ 306 $("article[id=' + str(vulnIndex) + ']").toggle("slow"); \ 307 }); \ 308 </script> \ 309 ') 310 311 htmlTOC.append('<li><a href=#CP'+ str(CPlaneCounter) + '>' + name.long_name + ': ' + definition + '</a></li>') 312 htmlTOC.append('\n') 313 CPlaneCounter = CPlaneCounter + 1 314 315 htmlDPlane.append('<a id="planeTitle" name="DPlane">Data Plane</a><br /><br />') 316 htmlDPlane.append('\n') 317 for name in DataPlaneMetrics: 318 if name != 'interface': 319 for k,v in inspect.getmembers(name): 320 if isinstance(v, dict): 321 if v['must_report'] == True: 322 vulnIndex = vulnIndex + 1 323 definition = v['definition'].strip() 324 threatInfo = v['threatInfo'].strip() 325 fixImpact = v['fixImpact'].strip() 326 cvss = v['cvss'].strip() 327 if definition == 'Port security violation': 328 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.violation['candidates']), 1) 329 if definition == 'Port security MAC address sticky': 330 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.sticky['candidates']), 1) 331 if definition == 'Port security total maximum MAC addresses': 332 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.maximum_total['candidates']), 1) 333 if definition == 'Port security access vlan maximum MAC addresses': 334 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.maximum_access['candidates']), 1) 335 if definition == 'Port security voice vlan maximum MAC addresses': 336 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.maximum_voice['candidates']), 1) 337 if definition == 'DTP negotiation': 338 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.nonegotiate['candidates']), 1) 339 if definition == 'Flow Control 802.3x': 340 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.flowcontrol['candidates']), 1) 341 if definition == 'VLAN 1': 342 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.vlan_1['candidates']), 1) 343 if definition == 'Unused ports': 344 v['howtofix'] = v['howtofix'].strip().replace('[%interface]', ", ".join(name.unused_ports['candidates']), 1) 345 346 howtofix = v['howtofix'].split('\n') 347 htmlDPlane.append(' \ 348 <script> \ 349 $("button").click(function () { \ 350 $("article[id=' + str(vulnIndex) + ']").toggle("slow"); \ 351 }); \ 352 </script> \ 353 ') 354 htmlDPlane.append('\n') 355 htmlDPlane.append('<article id="'+ str(vulnIndex) +'"><p>') 356 htmlDPlane.append('\n') 357 htmlDPlane.append('<table id="namedef">') 358 htmlDPlane.append('\n') 359 htmlDPlane.append('<tbody>') 360 htmlDPlane.append('\n') 361 htmlDPlane.append('<tr>') 362 htmlDPlane.append('\n') 363 htmlDPlane.append('<a id="planeContent" name="DP'+ str(DPlaneCounter) +'">' + '<td id="name">' + name.long_name + '</td>' + '</a>') 364 htmlDPlane.append('\n') 365 htmlDPlane.append('<td id="definition">' + definition + '</td>') 366 htmlDPlane.append('\n') 367 if float(cvss) <= 4: 368 htmlDPlane.append('<td id="cvssGreen">' + cvss + '/10' + '</td>') 369 htmlDPlane.append('\n') 370 elif float(cvss) <= 7.9 and cvss >=4.1: 371 htmlDPlane.append('<td id="cvssOrange">' + cvss + '/10' + '</td>') 372 htmlDPlane.append('\n') 373 elif float(cvss) >=8: 374 htmlDPlane.append('<td id="cvssRed">' + cvss + '/10' + '</td>') 375 htmlDPlane.append('\n') 376 htmlDPlane.append('<td id="ToggleButton"><button id='+ str(vulnIndex) +'>Hide/Show</button></td>') 377 htmlDPlane.append('</tr>') 378 htmlDPlane.append('\n') 379 htmlDPlane.append('</tbody>') 380 htmlDPlane.append('\n') 381 htmlDPlane.append('</table>') 382 htmlDPlane.append('\n') 383 384 htmlDPlane.append('<p id="threatInfo">Threat information: </p>' + threatInfo + '<br />') 385 htmlDPlane.append('\n') 386 htmlDPlane.append('<p id="threatInfo">Fix impact: </p>' + fixImpact + '<br />') 387 htmlDPlane.append('\n') 388 389 htmlDPlane.append('<table id ="fix">') 390 htmlDPlane.append('\n') 391 htmlDPlane.append('<thead>') 392 htmlDPlane.append('\n') 393 htmlDPlane.append('<tr><th id="fix">How to fix</th></tr>') 394 htmlDPlane.append('\n') 395 htmlDPlane.append('</thead>') 396 htmlDPlane.append('\n') 397 htmlDPlane.append('<tbody>') 398 htmlDPlane.append('\n') 399 for index in howtofix: 400 htmlDPlane.append('<tr>') 401 htmlDPlane.append('\n') 402 htmlDPlane.append('<td id="fix">' + index + '</td>') 403 htmlDPlane.append('\n') 404 htmlDPlane.append('</tr>') 405 htmlDPlane.append('\n') 406 htmlDPlane.append('</tbody>') 407 htmlDPlane.append('\n') 408 htmlDPlane.append('</table>') 409 htmlDPlane.append('\n') 410 htmlDPlane.append('</p></article><br />') 411 htmlDPlane.append('\n') 412 htmlDPlane.append(' \ 413 <script> \ 414 $("button[id=' + str(vulnIndex) + ']").click(function () { \ 415 $("article[id=' + str(vulnIndex) + ']").toggle("slow"); \ 416 }); \ 417 </script> \ 418 ') 419 420 htmlTOC.append('<li><a href=#DP'+ str(DPlaneCounter) + '>' + name.long_name + ': ' + definition + '</a></li>') 421 htmlTOC.append('\n') 422 DPlaneCounter = DPlaneCounter + 1 423 424 425 htmlSummary.append('<tr>') 426 htmlSummary.append('\n') 427 htmlSummary.append('<td>' + str(MPlaneCounter) + '</td>') 428 htmlSummary.append('\n') 429 htmlSummary.append('<td>' + str(CPlaneCounter) + '</td>') 430 htmlSummary.append('\n') 431 htmlSummary.append('<td>' + str(DPlaneCounter) + '</td>') 432 htmlSummary.append('\n') 433 htmlSummary.append('</tr>') 434 htmlSummary.append('\n') 435 436 htmlSummary.append('</tbody>') 437 htmlSummary.append('\n') 438 htmlSummary.append('</table>') 439 htmlSummary.append('\n') 440 htmlSummary.append('</div>') 441 htmlSummary.append('\n') 442 443 htmlSummary.append('<u>Management plane:</u> impact on management.<br /><u>Control plane:</u> impact on infrastructure protected by the device.<br /><u>Data plane:</u> impact on traffic flowing through the device.') 444 htmlSummary.append('\n') 445 446 htmlTOC.append('</ol>') 447 htmlTOC.append('\n') 448 htmlTOC.append('</div>') 449 htmlTOC.append('\n') 450 451 htmlFooter.append('</section>') 452 htmlFooter.append('\n') 453 htmlFooter.append('</div>') 454 htmlFooter.append('\n') 455 htmlFooter.append('<footer>') 456 htmlFooter.append('\n') 457 htmlFooter.append('<p id="presentation">') 458 htmlFooter.append('\n') 459 htmlFooter.append('RouterDefense is created and maintained by Francois Ropert.') 460 htmlFooter.append('\n') 461 htmlFooter.append('</p>') 462 htmlFooter.append('\n') 463 htmlFooter.append('</footer>') 464 htmlFooter.append('\n') 465 htmlFooter.append('</body>') 466 htmlFooter.append('\n') 467 468 for line in htmlHeader: 469 report.write(line) 470 report.write('<font size="+1"><b><u>Recommandations found</u></b></font>') 471 report.write('<br /><br />') 472 for line in htmlSummary: 473 report.write(line) 474 report.write('<br /><br />') 475 report.write('<font size="+1"><b><u>Table of contents</u></b></font>') 476 report.write('<br /><br />') 477 for line in htmlTOC: 478 report.write(line) 479 report.write('<br />') 480 for line in htmlMPlane: 481 report.write(line) 482 report.write('<br />') 483 for line in htmlCPlane: 484 report.write(line) 485 report.write('<br />') 486 for line in htmlDPlane: 487 report.write(line) 488 for line in htmlFooter: 489 report.write(line) 490 491 report.close() 492 print "Audit has been saved under the filename: %s " % outputFile 493 return "HTML"
494