"SfR Fresh" - the SfR Freeware/Shareware Archive

Member "wormscan-1.6.1-src/net/websoup/wormscan/Program.java" of archive wormscan-1.6.1-src.tar.gz:


As a special service "SfR Fresh" has tried to format the requested source page into HTML format using (guessed) Java source code syntax highlighting with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. That can be also achieved for any archive member file by clicking within an archive contents listing on the first character of the file(path) respectively on the according byte size field.
    1 package net.websoup.wormscan;
    2 
    3 /*
    4 *    This file is part of WormScan.
    5 *
    6 *    WormScan is free software; you can redistribute it and/or
    7 *    modify it under the terms of the GNU General Public License
    8 *    as published by the Free Software Foundation; either version 2
    9 *    of the License, or (at your option) any later version.
   10 *
   11 *    This program is distributed in the hope that it will be useful,
   12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
   13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   14 *    GNU General Public License for more details.
   15 *
   16 *    You should have received a copy of the GNU General Public License
   17 *    along with this program; if not, write to the Free Software
   18 *    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   19 */
   20 
   21 import java.io.*;
   22 import java.net.InetAddress;
   23 import java.text.SimpleDateFormat;
   24 import java.util.Locale;
   25 import java.util.Date;
   26 import java.util.Vector;
   27 import java.util.Map;
   28 import java.util.TreeMap;
   29 import java.util.Enumeration;
   30 import java.util.Collection;
   31 import java.util.Iterator;
   32 import java.util.Properties;
   33 import java.util.Stack;
   34 
   35 import org.apache.oro.text.perl.Perl5Util;
   36 import org.apache.oro.text.regex.Perl5Compiler;
   37 import org.apache.oro.text.regex.Perl5Matcher;
   38 import org.apache.oro.text.regex.Pattern;
   39 import org.apache.oro.text.regex.MatchResult;
   40 import org.apache.tools.bzip2.CBZip2InputStream;
   41 import org.apache.velocity.VelocityContext;
   42 import org.apache.velocity.Template;
   43 import org.apache.velocity.app.Velocity;
   44 
   45 import java.util.zip.GZIPInputStream;
   46 import java.util.zip.GZIPOutputStream;
   47 
   48 import net.websoup.wormscan.*;
   49 import net.websoup.utility.FilenameResolver;
   50 
   51 import org.jfree.chart.*;
   52 import org.jfree.chart.plot.*;
   53 import org.jfree.chart.ui.*;
   54 import org.jfree.data.*;
   55 import org.jfree.data.time.*;
   56 import org.jfree.ui.*;
   57 
   58 /**
   59  * Title:        WormScan
   60  * Description:  Extendable system to parse web server log files and output reports on attack attempts by various known internet worms.
   61  * Copyright:    Copyright (c) 2001-2004 Andriy Rozeluk <arozeluk@websoup.net>
   62  * @author Andriy Rozeluk
   63  * @version 1.6.1
   64  */
   65 
   66 public class Program extends Thread {
   67   private static int bufferSize = 32768;
   68 
   69   private static final int GZIP_BUFFER = 8192;
   70 
   71   /* how many parser threads to run */
   72   private static int NUM_PARSE_THREADS = 1;
   73 
   74   /* current line being processed by this thread */
   75   private String currentLine;
   76 
   77   /* identify this thread by a unique number */
   78   private int threadID;
   79 
   80   /* our logfile reader */
   81   private BufferedReader reader;
   82 
   83   /* the imported worms */
   84   private static Worm[] worms;
   85 
   86   /* store our list of attacks here */
   87   private static Vector attackStorage;
   88 
   89   /* what size to initialize our attackStorage to */
   90   private static int INITIAL_ATTACK_STORAGE_SIZE = 10000;
   91 
   92   /* store our list of hosts here */
   93   private static Map sourceStorage;
   94 
   95   /* what size to initialize our sourceStorage to */
   96   private static int INITIAL_SOURCE_STORAGE_SIZE = 4000;
   97 
   98   /* save DNS information in file cache [Y/N]*/
   99   private static boolean cacheDNS = true;
  100 
  101   /* all filenames of reports will start with this */
  102   private static String BASE_FILENAME = "wormreport";
  103 
  104   /* all filenames of reports will end with this */
  105   private static String BASE_EXTENSION = ".html";
  106 
  107   /* template to load for detailed report generation */
  108   private static String REPORT_TEMPLATE_NAME = "wormreport.vm";
  109 
  110   /* template to load for summary report generation */
  111   private static String SUMMARY_TEMPLATE_NAME = "wormsummary.vm";
  112 
  113   /* filename to store number of attacks in */
  114   private static String NUM_ATTACKS_FILE = null;
  115 
  116   /* filename to store DNS cache in */
  117   private static String DNS_CACHE_FILENAME = "dnscache";
  118 
  119   /* show only latest attack by host in generated reports */
  120   private static boolean SHOW_LATEST_ATTACK_ONLY = true;
  121 
  122   /* output GZIP-compressed version of reports while we're at it */
  123   private static boolean WRITE_GZIP = true;
  124 
  125   /* go ahead and check to see if log files are GZIP-compressed before parsing */
  126   private static boolean CHECK_GZIP = true;
  127 
  128   /* go ahead and check to see if log files are BZIP2-compressed before parsing */
  129   private static boolean CHECK_BZIP2 = true;
  130 
  131   /* go ahead and resolve hostnames */
  132   static boolean RESOLVE_DNS = true;
  133 
  134   /* how much information should we give the console? */
  135   static int OUTPUT_LEVEL = 3;
  136 
  137   /* how many sort threads to run simultaneously */
  138   private static int NUM_RUNNING_SORT_THREADS = 1;
  139 
  140   private static String CHART_BASE_FILENAME = "chart";
  141   private static int CHART_WIDTH = 640;
  142   private static int CHART_HEIGHT = 480;
  143   private static boolean CHARTS_PRODUCE = false;
  144 
  145   private Perl5Matcher matcher;
  146 
  147   /* how many resolver threads to run */
  148   private static int NUM_RESOLVER_THREADS = 1;
  149 
  150   private static Resolver[] resolverThreads;
  151 
  152   private static Perl5Compiler p5 = new Perl5Compiler();
  153   private static Pattern pattern;
  154   private static int patternHostIndex = 1;
  155   private static int patternDateIndex = 3;
  156   private static Locale LOG_LOCALE = Locale.US;
  157   private static String defaultDatePattern = "d/MMM/yyy:H:mm:ss";
  158   private static SimpleDateFormat patternDateFormat = new SimpleDateFormat(defaultDatePattern, LOG_LOCALE);
  159   private static int patternUriIndex = 6;
  160   private static boolean patternPrependFilenameToDate = false;
  161 
  162   /* get a bunch of threads ready to resolve hostnames. They'll wait */
  163   private static boolean resolveStarted = false;
  164 
  165   private static boolean enableReportByWorm = true;
  166   private static boolean enableReportByNumattacks = true;
  167   private static boolean enableReportByDate = true;
  168   private static boolean enableReportByIP = true;
  169   private static boolean enableReportByHostname = true;
  170 
  171   private String filename;
  172 
  173   static {
  174     //try it just in case JRE 1.4 is installed for charts
  175     System.setProperty("java.awt.headless", "true");
  176 
  177     try {
  178       pattern = p5.compile( "^(.*?)\\s+(.*?)\\[(.*?)\\](.*?)(GET|POST|HEAD|SEARCH)\\s+(.*?)\\s+(.*?)$" );
  179     }
  180     catch ( Exception e){
  181       System.err.println( "Exception thrown on init: " + e.getMessage() );
  182     }
  183   }
  184 
  185   /**
  186    * Compress a file with GZIP
  187    */
  188   private static void writeGZIP( String inputFilename, String outputFilename ) throws Exception {
  189     BufferedInputStream fis = new BufferedInputStream( new FileInputStream( inputFilename ) );
  190     BufferedOutputStream gos = new BufferedOutputStream( new GZIPOutputStream( new FileOutputStream( outputFilename ) ) );
  191     byte[] buffer = new byte[GZIP_BUFFER];
  192     for(;;){
  193       int numBytes = fis.read( buffer, 0, GZIP_BUFFER );
  194       if ( numBytes < 0 ) break;
  195       gos.write( buffer, 0, numBytes );
  196     }
  197     fis.close();
  198     gos.close();
  199   }
  200 
  201   /**
  202    * Some static initialization stuff. Read in our DNS cache and create some of our storage Vectors
  203    */
  204   private static void initialize(){
  205     attackStorage = new Vector( INITIAL_ATTACK_STORAGE_SIZE );
  206 
  207     if ( cacheDNS ){
  208       try {
  209         ObjectInputStream ois = new ObjectInputStream( new FileInputStream(DNS_CACHE_FILENAME) );
  210         sourceStorage = (TreeMap)(ois.readObject());
  211         ois.close();
  212         if ( OUTPUT_LEVEL > 2 ) System.out.println( "DNS Cache loaded" );
  213       }
  214       catch( Exception e ){
  215         sourceStorage = new TreeMap();
  216         if ( OUTPUT_LEVEL > 1 ) System.err.println("DNS Cache couldn't be loaded: " + e.getMessage());
  217       }
  218 
  219       try {
  220         //do some fixup processing of previously-stored objects
  221         Iterator i = sourceStorage.values().iterator();
  222         while( i.hasNext() ){
  223           AttackSource source = (AttackSource)(i.next());
  224           source.convertIP();
  225         }
  226       }
  227       catch( Exception e ){
  228         if ( OUTPUT_LEVEL > 1 ) System.err.println("Error performing fixup: " + e.getMessage());
  229         e.printStackTrace();
  230       }
  231     }
  232     else {
  233       sourceStorage = new TreeMap();
  234     }
  235   }
  236 
  237   /**
  238    * IP/Hostname should only be stored once. This method controls that.
  239    */
  240   private static AttackSource getAttackSource( String ip ){
  241     AttackSource as;
  242     //changes 3.26.2002 to not synchronize if only one thread
  243     //synchronization too expensive if not needed
  244     if ( NUM_PARSE_THREADS > 1 ){
  245       synchronized( sourceStorage ){
  246         as = (AttackSource)(sourceStorage.get(ip));
  247       }
  248 
  249       if ( as == null ){
  250         as = new AttackSource( ip );
  251         synchronized( sourceStorage ){
  252           sourceStorage.put( ip, as );
  253 
  254           Resolver.addSource( as );
  255           if ( !resolveStarted ){
  256             for ( int i = 0; i < NUM_RESOLVER_THREADS; i++ ) {
  257               resolverThreads[i] = new Resolver();
  258               resolverThreads[i].setPriority(Thread.MIN_PRIORITY);
  259               resolverThreads[i].start();
  260             }
  261             resolveStarted = true;
  262           }
  263         }
  264       }
  265     }
  266     else {
  267       as = (AttackSource)(sourceStorage.get(ip));
  268       if ( as == null ){
  269         as = new AttackSource( ip );
  270         sourceStorage.put( ip, as );
  271         Resolver.addSource( as );
  272         if ( !resolveStarted ){
  273           for ( int i = 0; i < NUM_RESOLVER_THREADS; i++ ) {
  274             resolverThreads[i] = new Resolver();
  275             resolverThreads[i].setPriority(Thread.MIN_PRIORITY);
  276             resolverThreads[i].start();
  277           }
  278           resolveStarted = true;
  279         }
  280       }
  281     }
  282 
  283     return as;
  284   }
  285 
  286   /**
  287    * Constructs a parse thread.
  288    */
  289   public Program( int id, BufferedReader bf, String filename ) {
  290     threadID = id;
  291     reader = bf;
  292     this.filename = filename;
  293     matcher = new Perl5Matcher();
  294   }
  295 
  296   /**
  297    * Begins a parse thread. This will loop until no more lines can be read from file
  298    */
  299   public void run(){
  300     /* read lines until there's nothing left to read */
  301     for(;;){
  302       try {
  303         currentLine = reader.readLine();
  304       }
  305       catch (Exception e){
  306         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: couldn't read a line: " + e.getMessage() );
  307         return;
  308       }
  309       if ( currentLine == null ) return;
  310 
  311         /* check each line against each worm. Do stuff if attack found. */
  312         if ( matcher.matches( currentLine, pattern ) ) {
  313           MatchResult lineMatch = matcher.getMatch();
  314           String uri = lineMatch.group( patternUriIndex );
  315           for( int i = 0; i < worms.length; i++ ){
  316             if ( matcher.contains( uri, worms[i].getPattern() ) ){
  317               String host = lineMatch.group( patternHostIndex );
  318               String tempdate = lineMatch.group( patternDateIndex );
  319 
  320               /* try to parse the date */
  321               Date date = null;
  322               try {
  323                 if ( patternPrependFilenameToDate ){
  324                   tempdate = filename + tempdate;
  325                 }
  326                 synchronized( patternDateFormat ){
  327                   date = patternDateFormat.parse( tempdate );
  328                 }
  329               }
  330               catch ( Exception e ){
  331                 if ( OUTPUT_LEVEL > 1 ) System.err.println("cannot interpret date in log file:" + e.getMessage());
  332               }
  333 
  334               /* increment attack counter for worm */
  335               worms[i].addAttack();
  336               AttackSource as = getAttackSource( host );
  337 
  338               /* increment attack counter for host */
  339               as.addAttack();
  340 
  341               Attack attack = new Attack( date, worms[i], as );
  342               if ( NUM_PARSE_THREADS > 1 ){
  343                 synchronized( attackStorage ){
  344                   attackStorage.add( attack );
  345                   /* check to see if this is the first or last attack by this host.
  346                    Update if necessary */
  347                   as.checkAttackDates( attack );
  348                 }
  349               }
  350               else {
  351                 attackStorage.add( attack );
  352                 as.checkAttackDates( attack );
  353               }
  354 
  355               if ( OUTPUT_LEVEL > 3 )
  356                 System.out.println( "Thread " + threadID + " found " + attack.getAttackingWorm().getShortName() + " attack" );
  357 
  358               /* no need to continue on this line - we've marked it as an attack already */
  359               break;
  360             }
  361           }
  362 
  363         currentLine = null;
  364       }
  365 /* screw the yield - it's taking up too much of our time to be polite
  366       try {
  367         this.yield();
  368       }
  369       catch ( Exception e ){
  370       }
  371 */
  372     }
  373   }
  374 
  375   /**
  376    * Return a BufferedReader which can read in the log file. Can also detect and handle GZIP/BZIP2 compression
  377    */
  378   private static BufferedReader getLogfileReader( String filename ) throws Exception{
  379     if ( CHECK_GZIP ){
  380       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Detecting GZIP... in " + filename );
  381       byte[] test = new byte[2];
  382       FileInputStream fis = new FileInputStream( filename );
  383       int read = fis.read( test, 0, 2 );
  384       if ( read == 2 ){
  385         if ( test[0] == new Integer( 31 ).byteValue() && test[1] == new Integer( 139 ).byteValue() ){
  386           fis.close();
  387           fis = new FileInputStream( filename );
  388           GZIPInputStream gis = new GZIPInputStream( fis );
  389           InputStreamReader isr = new InputStreamReader( gis );
  390           if ( OUTPUT_LEVEL > 2 ) System.out.println( "Log file " + filename + " was compressed with GZIP. Decompression will take place automatically." );
  391           return new BufferedReader( isr, bufferSize );
  392         }
  393       }
  394       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Log file was NOT compressed with GZIP" );
  395     }
  396     if ( CHECK_BZIP2 ){
  397       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Detecting BZIP2... in " + filename );
  398       byte[] test = new byte[4];
  399       FileInputStream fis = new FileInputStream( filename );
  400       int read = fis.read( test, 0, 2 );
  401       if ( read == 2 ){
  402         if ( test[0] == 'B' && test[1] == 'Z' ){
  403           CBZip2InputStream bis = new CBZip2InputStream( fis );
  404           InputStreamReader isr = new InputStreamReader( bis );
  405           if ( OUTPUT_LEVEL > 2 ) System.out.println( "Log file " + filename + " was compressed with BZIP2. Decompression will take place automatically." );
  406           return new BufferedReader( isr, bufferSize );
  407         }
  408       }
  409       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Log file was NOT compressed with BZIP2" );
  410     }
  411     FileReader fileInput = new FileReader( filename );
  412     return new BufferedReader( fileInput, bufferSize );
  413   }
  414 
  415   public static void main(String[] args) {
  416     /* the user entered this as our list of logfile(s) to parse */
  417     String LOGFILE = null;
  418 
  419     /* this mess reads in the properties file and sets values accordingly */
  420     Properties p = new Properties();
  421     try {
  422       p.load( new FileInputStream("wormscan.properties") );
  423       String temp = p.getProperty( "outputLevel" );
  424       if ( temp != null ){
  425         try {
  426           OUTPUT_LEVEL = Integer.parseInt( temp );
  427         }
  428         catch ( Exception e){
  429           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: outputLevel property was not an integer. Using default" );
  430         }
  431       }
  432       else {
  433         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: outputLevel property not specified. Using default" );
  434       }
  435       temp = p.getProperty("reportFilename");
  436       if ( temp != null ) BASE_FILENAME = temp;
  437       else {
  438         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: reportFilename property not specified. Using default" );
  439       }
  440       temp = p.getProperty("reportExtension");
  441       if ( temp != null ) BASE_EXTENSION = temp;
  442       else {
  443         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: reportExtension property not specified. Using default" );
  444       }
  445       temp = p.getProperty( "logFile" );
  446       if ( temp != null ) LOGFILE = temp;
  447       else {
  448         if ( OUTPUT_LEVEL > 0 ) System.err.println( "logFile property not specified in wormscan.properties - unable to proceed" );
  449         System.exit(1);
  450       }
  451       temp = p.getProperty( "numAttacksFile" );
  452       if ( temp != null ) NUM_ATTACKS_FILE = temp;
  453       else {
  454         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numAttacksFile property not specified. File won't be written" );
  455       }
  456       temp = p.getProperty( "initialAttackStorageSize" );
  457       if ( temp != null ){
  458         try {
  459           INITIAL_ATTACK_STORAGE_SIZE = Integer.parseInt( temp );
  460         }
  461         catch ( Exception e){
  462           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: initialAttackStorageSize property was not an integer. Using default" );
  463         }
  464       }
  465       else {
  466         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: initialAttackStorageSize property not specified. Using default" );
  467       }
  468       temp = p.getProperty( "initialSourceStorageSize" );
  469       if ( temp != null ){
  470         try {
  471           INITIAL_SOURCE_STORAGE_SIZE = Integer.parseInt( temp );
  472         }
  473         catch ( Exception e){
  474           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: initialSourceStorageSize property was not an integer. Using default" );
  475         }
  476       }
  477       else {
  478         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: initialSourceStorageSize property not specified. Using default" );
  479       }
  480       temp = p.getProperty( "numParseThreads" );
  481       if ( temp != null ){
  482         try {
  483           NUM_PARSE_THREADS = Integer.parseInt( temp );
  484         }
  485         catch ( Exception e){
  486           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numParseThreads property was not an integer. Using default" );
  487         }
  488       }
  489       else {
  490         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numParseThreads property not specified. Using default" );
  491       }
  492       temp = p.getProperty( "numResolverThreads" );
  493       if ( temp != null ){
  494         try {
  495           NUM_RESOLVER_THREADS = Integer.parseInt( temp );
  496         }
  497         catch ( Exception e){
  498           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numResolverThreads property was not an integer. Using default" );
  499         }
  500       }
  501       else {
  502         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numResolverThreads property not specified. Using default" );
  503       }
  504       temp = p.getProperty( "maxResolverQueue" );
  505       if ( temp != null ){
  506         try {
  507           Resolver.MAX_WAITING = Integer.parseInt( temp );
  508         }
  509         catch ( Exception e){
  510           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: maxResolverQueue property was not an integer. Using default" );
  511         }
  512       }
  513       else {
  514         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numResolverThreads property not specified. Using default" );
  515       }
  516       temp = p.getProperty( "numSortThreads" );
  517       if ( temp != null ){
  518         try {
  519           NUM_RUNNING_SORT_THREADS = Integer.parseInt( temp );
  520         }
  521         catch ( Exception e){
  522           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numSortThreads property was not an integer. Using default" );
  523         }
  524       }
  525       else {
  526         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: numSortThreads property not specified. Using default" );
  527       }
  528       temp = p.getProperty( "cacheDNS" );
  529       if ( temp != null ){
  530         if ( temp.equals("1") ){
  531           cacheDNS = true;
  532           temp = p.getProperty( "cacheFilename" );
  533           if ( temp != null ){
  534            DNS_CACHE_FILENAME = temp;
  535           }
  536           else if ( OUTPUT_LEVEL > 1 ){
  537            System.err.println( "Warning: cacheFilename property not specified. Using default" );
  538           }
  539         }
  540         else if ( temp.equals("0") ){
  541          cacheDNS = false;
  542         }
  543         else if ( OUTPUT_LEVEL > 1 ){
  544          System.err.println( "Warning: cacheDNS property is not 1 or 0. Using default" );
  545         }
  546       }
  547       else {
  548         if ( OUTPUT_LEVEL > 1 ){
  549          System.err.println( "Warning: numResolverThreads property not specified. Using default" );
  550         }
  551       }
  552       temp = p.getProperty( "showLatestAttackOnly" );
  553       if ( temp != null ){
  554         if ( temp.equals("1") ){
  555          SHOW_LATEST_ATTACK_ONLY = true;
  556         }
  557         else if ( temp.equals("0") ){
  558          SHOW_LATEST_ATTACK_ONLY = false;
  559         }
  560         else if ( OUTPUT_LEVEL > 1 ){
  561          System.err.println( "Warning: showLatestAttackOnly property is not 1 or 0. Using default" );
  562         }
  563       }
  564       else {
  565         if ( OUTPUT_LEVEL > 1 ){
  566          System.err.println( "Warning: showLatestAttackOnly property not specified. Using default" );
  567         }
  568       }
  569       temp = p.getProperty( "resolveDNS" );
  570       if ( temp != null ){
  571         if ( temp.equals("1") ){
  572          RESOLVE_DNS = true;
  573         }
  574         else if ( temp.equals("0") ){
  575          RESOLVE_DNS = false;
  576         }
  577         else if ( OUTPUT_LEVEL > 1 ){
  578          System.err.println( "Warning: resolveDNS property is not 1 or 0. Using default" );
  579         }
  580       }
  581       else {
  582         if ( OUTPUT_LEVEL > 1 ){
  583          System.err.println( "Warning: resolveDNS property not specified. Using default" );
  584         }
  585       }
  586       temp = p.getProperty( "logCheckGZIP" );
  587       if ( temp != null ){
  588         if ( temp.equals("1") ){
  589          CHECK_GZIP = true;
  590         }
  591         else if ( temp.equals("0") ){
  592          CHECK_GZIP = false;
  593         }
  594         else if ( OUTPUT_LEVEL > 1 ){
  595          System.err.println( "Warning: logCheckGZIP property is not 1 or 0. Using default" );
  596         }
  597       }
  598       else {
  599         if ( OUTPUT_LEVEL > 1 ){
  600          System.err.println( "Warning: logCheckGZIP property not specified. Using default" );
  601         }
  602       }
  603       temp = p.getProperty( "logCheckBZIP2" );
  604       if ( temp != null ){
  605         if ( temp.equals("1") ){
  606          CHECK_BZIP2 = true;
  607         }
  608         else if ( temp.equals("0") ){
  609          CHECK_BZIP2 = false;
  610         }
  611         else if ( OUTPUT_LEVEL > 1 ){
  612          System.err.println( "Warning: logCheckBZIP2 property is not 1 or 0. Using default" );
  613         }
  614       }
  615       else {
  616         if ( OUTPUT_LEVEL > 1 ){
  617          System.err.println( "Warning: logCheckBZIP2 property not specified. Using default" );
  618         }
  619       }
  620       temp = p.getProperty( "writeGZIP" );
  621       if ( temp != null ){
  622         if ( temp.equals("1") ){
  623          WRITE_GZIP = true;
  624         }
  625         else if ( temp.equals("0") ){
  626          WRITE_GZIP = false;
  627         }
  628         else if ( OUTPUT_LEVEL > 1 ){
  629          System.err.println( "Warning: writeGZIP property is not 1 or 0. Using default" );
  630         }
  631       }
  632       else {
  633         if ( OUTPUT_LEVEL > 1 ){
  634          System.err.println( "Warning: writeGZIP property not specified. Using default" );
  635         }
  636       }
  637       temp = p.getProperty( "disableReportByWorm" );
  638       if ( temp != null && temp.equals("1") ){
  639         enableReportByWorm = false;
  640       }
  641       temp = p.getProperty( "disableReportByNumattacks" );
  642       if ( temp != null && temp.equals("1") ){
  643         enableReportByNumattacks = false;
  644       }
  645       temp = p.getProperty( "disableReportByDate" );
  646       if ( temp != null && temp.equals("1") ){
  647         enableReportByDate = false;
  648       }
  649       temp = p.getProperty( "disableReportByIP" );
  650       if ( temp != null && temp.equals("1") ){
  651         enableReportByIP = false;
  652       }
  653       temp = p.getProperty( "disableReportByHostname" );
  654       if ( temp != null && temp.equals("1") ){
  655         enableReportByHostname = false;
  656       }
  657       temp = p.getProperty( "produceCharts" );
  658       if ( temp != null ){
  659         if ( temp.equals("1") ) {
  660           CHARTS_PRODUCE = true;
  661           temp = p.getProperty( "chartFilename" );
  662           if ( temp != null && !temp.equals("") ){
  663             CHART_BASE_FILENAME = temp;
  664           }
  665           else if ( OUTPUT_LEVEL > 1 ){
  666             System.err.println( "Warning: chartFilename property not specified. Using default" );
  667           }
  668           temp = p.getProperty( "chartWidth" );
  669           if ( temp != null && !temp.equals("") ){
  670             try {
  671               int tempInt = Integer.parseInt( temp );
  672               if ( tempInt >= 50 ){
  673                 CHART_WIDTH = tempInt;
  674               }
  675               else if ( OUTPUT_LEVEL > 1 ){
  676                 System.err.println( "Warning: chartWidth property is too small. Using default" );
  677               }
  678             }
  679             catch ( Exception ex ){
  680               if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: chartWidth property is not an integer. Using default" );
  681             }
  682           }
  683           else if ( OUTPUT_LEVEL > 1 ){
  684             System.err.println( "Warning: chartWidth property not specified. Using default" );
  685           }
  686           temp = p.getProperty( "chartHeight" );
  687           if ( temp != null && !temp.equals("") ){
  688             try {
  689               int tempInt = Integer.parseInt( temp );
  690               if ( tempInt >= 50 ){
  691                 CHART_HEIGHT = tempInt;
  692               }
  693               else if ( OUTPUT_LEVEL > 1 ){
  694                 System.err.println( "Warning: chartHeight property is too small. Using default" );
  695               }
  696             }
  697             catch ( Exception ex ){
  698               if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: chartHeight property is not an integer. Using default" );
  699             }
  700           }
  701           else if ( OUTPUT_LEVEL > 1 ){
  702             System.err.println( "Warning: chartHeight property not specified. Using default" );
  703           }
  704         }
  705         else if ( temp.equals("0") ){
  706           CHARTS_PRODUCE = false;
  707         }
  708         else if ( OUTPUT_LEVEL > 1 ) {
  709           System.err.println( "Warning: produceCharts property is not 1 or 0. Using default" );
  710         }
  711       }
  712       else {
  713         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: produceCharts property not specified. Charts will not be created" );
  714       }
  715 
  716       String locLanguage = "en";
  717       String locCountry = "US";
  718       temp = p.getProperty( "serverLocaleLanguage" );
  719       if ( temp != null && !temp.equals("")){
  720         locLanguage = temp;
  721       }
  722       else {
  723         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: serverLocaleLanguage property not specified. Using default" );
  724       }
  725       temp = p.getProperty( "serverLocaleCountry" );
  726       if ( temp != null && !temp.equals("")){
  727         locCountry = temp;
  728       }
  729       else {
  730         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: serverLocaleCountry property not specified. Using default" );
  731       }
  732       if ( !locLanguage.equals("en") || !locCountry.equals("US") ){
  733         try {
  734           Locale tempLocale = new Locale( locLanguage, locCountry );
  735           LOG_LOCALE = tempLocale;
  736           patternDateFormat = new SimpleDateFormat(defaultDatePattern, LOG_LOCALE);
  737         }
  738         catch( Exception e ){
  739           System.err.println( "Warning: unable to create Locale for " + locLanguage + ", " + locCountry + ": " + e.getMessage() );
  740         }
  741       }
  742       temp = p.getProperty( "serverType" );
  743       if ( temp == null ){
  744         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: serverType property not specified. Using default" );
  745         temp = "Apache";
  746       }
  747       String serverType = temp;
  748       boolean caseSens = true;
  749       temp = p.getProperty( serverType + "_caseSensitive" );
  750       if ( temp != null && temp.trim().equals("0") ){
  751         caseSens = false;
  752       }
  753       temp = p.getProperty( serverType + "_pattern" );
  754       if ( temp != null ){
  755         try{
  756           if ( caseSens ){
  757             pattern = p5.compile( temp );
  758           }
  759           else {
  760             pattern = p5.compile( temp, p5.CASE_INSENSITIVE_MASK );
  761           }
  762         }
  763         catch ( Exception e ){
  764           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Error: could not process pattern: " + e.getMessage() );
  765         }
  766       }
  767       else{
  768         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_pattern property not specified. Using default" );
  769       }
  770       temp = p.getProperty( serverType + "_hostIndex" );
  771       if ( temp != null ){
  772         try {
  773           patternHostIndex = Integer.parseInt(temp);
  774         }
  775         catch( Exception e ){
  776           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_hostIndex property is not an integer. Using default" );
  777         }
  778       }
  779       else {
  780         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_hostIndex property not specified. Using default" );
  781       }
  782       temp = p.getProperty( serverType + "_dateIndex" );
  783       if ( temp != null ){
  784         try {
  785           patternDateIndex = Integer.parseInt(temp);
  786         }
  787         catch( Exception e ){
  788           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_dateIndex property is not an integer. Using default" );
  789         }
  790       }
  791       else {
  792         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_dateIndex property not specified. Using default" );
  793       }
  794       temp = p.getProperty( serverType + "_dateFormat" );
  795       if ( temp != null ) patternDateFormat = new SimpleDateFormat( temp, LOG_LOCALE );
  796       else {
  797         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: dateFormat property not specified. Using default" );
  798       }
  799       temp = p.getProperty( serverType + "_uriIndex" );
  800       if ( temp != null ){
  801         try {
  802           patternUriIndex = Integer.parseInt(temp);
  803         }
  804         catch( Exception e ){
  805           if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_uriIndex property is not an integer. Using default" );
  806         }
  807       }
  808       else {
  809         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_uriIndex property not specified. Using default" );
  810       }
  811       temp = p.getProperty( serverType + "_prependFilenameToDate" );
  812       if ( temp != null ){
  813         if ( temp.equals("1") ){
  814          patternPrependFilenameToDate = true;
  815         }
  816         else if ( temp.equals("0") ){
  817          patternPrependFilenameToDate = false;
  818         }
  819         else if ( OUTPUT_LEVEL > 1 ){
  820          System.err.println( "Warning: " + serverType + "_prependFilenameToDate property is not 1 or 0. Using default" );
  821         }
  822       }
  823       else {
  824         if ( OUTPUT_LEVEL > 1 ) System.err.println( "Warning: " + serverType + "_prependFilenameToDate property not specified. Using default" );
  825       }
  826       temp = p.getProperty( "numWorms" );
  827       if ( temp != null ){
  828         int numWorms = 0;
  829         try {
  830           numWorms = Integer.parseInt( temp );
  831         }
  832         catch ( Exception e){
  833           if ( OUTPUT_LEVEL > 0 ) System.err.println( "numWorms property is not an integer. Unable to continue" );
  834           System.exit(1);
  835         }
  836         if ( numWorms > 0 ){
  837           Vector wormStorage = new Vector( numWorms );
  838           for ( int i = 0; i < numWorms; i++ ){
  839             Worm worm = new Worm();
  840             worm.setName( p.getProperty("worm" + i + "_name") );
  841             worm.setShortName( p.getProperty("worm" + i + "_shortName") );
  842             worm.setColour( p.getProperty("worm" + i + "_colour") );
  843             temp = p.getProperty( "worm" + i + "_caseSensitive" );
  844             if ( temp != null && temp.trim().equals("0") ){
  845               caseSens = false;
  846             }
  847             else{
  848               caseSens = true;
  849             }
  850 
  851             try {
  852               worm.setPatternString( p.getProperty("worm" + i + "_pattern"), caseSens );
  853               wormStorage.add( worm );
  854             }
  855             catch ( Exception e ){
  856               if ( OUTPUT_LEVEL > 1 ) System.err.println( "Worm " + i + ": " + worm.getName() + " has an invalid pattern (unable to scan for this worm): " + e.getMessage() );
  857             }
  858           }
  859           worms = (Worm[])( wormStorage.toArray( new Worm[ wormStorage.size() ] ));
  860           if ( OUTPUT_LEVEL > 2 ) System.out.println( "" + worms.length + " worms loaded for scanning" );
  861         }
  862         if ( worms == null || worms.length <= 0 ){
  863           if ( OUTPUT_LEVEL > 0 ) System.err.println( "Unable to continue. No worms to scan for" );
  864           System.exit(1);
  865         }
  866       }
  867       else {
  868         if ( OUTPUT_LEVEL > 0 ) System.err.println( "numWorms property not specified. Unable to continue" );
  869         System.exit(1);
  870       }
  871     }
  872     catch( Exception e ){
  873       if ( OUTPUT_LEVEL > 0 ) System.err.println( "Unable to load properties file: wormscan.properties - " + e.getMessage() );
  874       e.printStackTrace();
  875       System.exit(1);
  876     }
  877 
  878     if ( OUTPUT_LEVEL > 2 ) System.out.println( "Started: " + new Date() );
  879     initialize();
  880 
  881     Program[] threads = new Program[ NUM_PARSE_THREADS ];
  882 
  883     if ( !RESOLVE_DNS ){
  884       NUM_RESOLVER_THREADS = 0;
  885       cacheDNS = false;
  886     }
  887     resolverThreads = new Resolver[ NUM_RESOLVER_THREADS ];
  888 
  889     try {
  890       /* locate all log files matching to specified properties */
  891       String[] logFilenames = new FilenameResolver().resolve( LOGFILE );
  892 
  893       if ( logFilenames.length <= 0 ) {
  894         if ( OUTPUT_LEVEL > 0 ) System.err.println( "No log files were found with pattern: " + LOGFILE );
  895         System.exit(1);
  896       }
  897 
  898       /* loop through, read each log file one by one until complete */
  899       for ( int logIndex = 0; logIndex < logFilenames.length; logIndex++ ){
  900         if ( OUTPUT_LEVEL > 2 ) System.out.println( "Parsing log file " + logFilenames[ logIndex ] );
  901         BufferedReader input = null;
  902         try {
  903           input = getLogfileReader( logFilenames[ logIndex ] );
  904         }
  905         catch ( Exception e ){
  906           if ( OUTPUT_LEVEL > 0 ) System.err.println( "Warning - error reading file " + logFilenames[ logIndex ] + ": " + e.getMessage() );
  907           continue;
  908         }
  909 
  910         for( int i = 0; i < NUM_PARSE_THREADS; i++ ){
  911           String fileComponent;
  912           fileComponent = logFilenames[ logIndex ].substring( logFilenames[ logIndex ].lastIndexOf( File.separator ) + 1 );
  913           threads[i] = new Program( i, input, fileComponent );
  914           threads[i].start();
  915         }
  916 
  917         Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
  918 
  919         for( int i = 0; i < NUM_PARSE_THREADS; i++ ){
  920           threads[i].join();
  921           threads[i] = null;
  922         }
  923         input.close();
  924 
  925         if ( OUTPUT_LEVEL > 2 ) System.out.println( "Completed parsing log file " + logFilenames[ logIndex ] );
  926         System.gc();
  927       }
  928 
  929       /* signal to Resolver that no more hostnames will be added to Stack */
  930       Resolver.areDone();
  931 
  932       if ( resolveStarted ){
  933         for ( int i = 0; i < NUM_RESOLVER_THREADS; i++ ) {
  934           if ( resolverThreads[i].isAlive() ){
  935             try {
  936               resolverThreads[i].setPriority(Thread.NORM_PRIORITY);
  937             }
  938             catch( Exception e ){
  939             }
  940           }
  941         }
  942       }
  943 
  944       /* if hosts are cached, we can't rely on the number of unique hosts
  945          to tell us how many unique hosts attacked us in this run.
  946          Instead, count how many hosts attacked us this time around. */
  947       int numhosts = 0;
  948       Collection numhosts_c = sourceStorage.values();
  949       Iterator numhosts_i = numhosts_c.iterator();
  950       while ( numhosts_i.hasNext() ) {
  951         if (((AttackSource)(numhosts_i.next())).getAttackCount() > 0 ){
  952           numhosts ++;
  953         }
  954       }
  955 
  956       int totalAttacks = attackStorage.size();
  957       Vector fullAttackStorage = attackStorage;
  958       Sorter fullAttackDateSorter = null;
  959       if ( SHOW_LATEST_ATTACK_ONLY || !enableReportByDate ){
  960         attackStorage = new Vector( numhosts );
  961         Iterator i = fullAttackStorage.iterator();
  962         while ( i.hasNext() ){
  963           try {
  964             Attack a = (Attack)(i.next());
  965             if ( a.getSourceOfAttack().isLastAttack( a ) ){
  966               attackStorage.add( a );
  967             }
  968           }
  969           catch( Exception ex ){
  970             if ( OUTPUT_LEVEL > 2 ) System.out.println( "Error removing attack: " + ex.getMessage() );
  971           }
  972         }
  973         fullAttackDateSorter = new Sorter( fullAttackStorage, Sorter.SORT_DATE, "date of ALL attacks" );
  974       }
  975 
  976       /* begin sorting of data - we can sort everything but hostname so far */
  977       Stack sorterThreadStack = new Stack();
  978       Sorter[] sorterThreads = new Sorter[ 5 ];
  979 
  980       Sorter[] runningSorterThreads = new Sorter[NUM_RUNNING_SORT_THREADS];
  981       for( int i = 0; i < NUM_RUNNING_SORT_THREADS; i++ ){
  982         if ( !sorterThreadStack.empty() ){
  983           runningSorterThreads[i] = (Sorter)(sorterThreadStack.pop());
  984           runningSorterThreads[i].start();
  985         }
  986         else {
  987           break;
  988         }
  989       }
  990 
  991       /* loop until finished resolving hostnames */
  992       for(;;){
  993         boolean allDone = true;
  994 
  995         /* scan through the number of sorters waiting to be run in the queue */
  996         for ( int j = 0; j < NUM_RUNNING_SORT_THREADS; j++ ){
  997           /* one of sorter threads is done? start another one if needed */
  998           if ( runningSorterThreads[j] != null && runningSorterThreads[j].isFinished() ){
  999             runningSorterThreads[j].join();
 1000             if ( OUTPUT_LEVEL > 2 ) System.err.println( "Sorting by " + runningSorterThreads[j].getSortMethodName() + " complete" );
 1001 
 1002             if ( !sorterThreadStack.empty() ){
 1003               runningSorterThreads[j] = (Sorter)(sorterThreadStack.pop());
 1004               runningSorterThreads[j].start();
 1005             }
 1006             else {
 1007               runningSorterThreads[j] = null;
 1008             }
 1009           }
 1010         }
 1011 
 1012         /* check to see if we're done resolving */
 1013         if ( resolveStarted ){
 1014           for ( int i = 0; i < NUM_RESOLVER_THREADS; i++ ){
 1015             /* if we're done resolving then join up */
 1016             if ( resolverThreads[i] != null && !resolverThreads[i].isAlive() ){
 1017               resolverThreads[i].join();
 1018               resolverThreads[i] = null;
 1019             }
 1020             else if ( resolverThreads[i] != null ){
 1021               allDone = false;
 1022             }
 1023           }
 1024         }
 1025 
 1026         if ( allDone ) break;
 1027 
 1028         Thread.sleep( 5000 );
 1029       }
 1030 
 1031       resolverThreads = null;
 1032       System.gc();
 1033 
 1034       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Completed resolving hostnames" );
 1035 
 1036       /* write out DNS cache using Serialization mechanism */
 1037       if ( cacheDNS ){
 1038         try {
 1039           ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( DNS_CACHE_FILENAME ) );
 1040           oos.writeObject( sourceStorage );
 1041           oos.close();
 1042           if ( OUTPUT_LEVEL > 2 ) System.out.println( "DNS Cache written to disk" );
 1043         }
 1044         catch ( Exception e ){
 1045           if ( OUTPUT_LEVEL > 1 ) System.err.println("DNS Cache couldn't be saved: " + e.getMessage());
 1046         }
 1047       }
 1048 
 1049       /* write out file containing number of attacks */
 1050       if ( NUM_ATTACKS_FILE != null ){
 1051         try {
 1052           FileOutputStream fos = new FileOutputStream( NUM_ATTACKS_FILE );
 1053           fos.write( Integer.toString( totalAttacks ).getBytes() );
 1054           fos.close();
 1055         }
 1056         catch ( Exception e ){
 1057           if ( OUTPUT_LEVEL > 1 ) System.err.println("Couldn't write out number of attacks to file: " + e.getMessage());
 1058         }
 1059       }
 1060 
 1061       if (enableReportByWorm){
 1062         sorterThreads[0] = new Sorter( attackStorage, Sorter.SORT_WORM, "_worm" );
 1063         sorterThreads[0].setPriority(Thread.MIN_PRIORITY);
 1064         sorterThreadStack.push( sorterThreads[0] );
 1065       }
 1066       if (enableReportByNumattacks){
 1067         sorterThreads[1] = new Sorter( attackStorage, Sorter.SORT_NUMATTACKS, "_numattacks" );
 1068         sorterThreads[1].setPriority(Thread.MIN_PRIORITY);
 1069         sorterThreadStack.push( sorterThreads[1] );
 1070       }
 1071       if (enableReportByDate){
 1072         sorterThreads[2] = new Sorter( attackStorage, Sorter.SORT_DATE, "_date" );
 1073         sorterThreads[2].setPriority(Thread.MIN_PRIORITY);
 1074         sorterThreadStack.push( sorterThreads[2] );
 1075       }
 1076 
 1077       if ( fullAttackDateSorter == null ){
 1078         fullAttackDateSorter = sorterThreads[2];
 1079       }
 1080       else {
 1081         sorterThreadStack.push( fullAttackDateSorter );
 1082       }
 1083 
 1084       /* now is a safe time to sort by hostname and IP */
 1085       if ( enableReportByIP ){
 1086         sorterThreads[3] = new Sorter( attackStorage, Sorter.SORT_IP, "_ip" );
 1087         sorterThreadStack.push( sorterThreads[3] );
 1088       }
 1089       if ( enableReportByHostname ){
 1090         sorterThreads[4] = new Sorter( attackStorage, Sorter.SORT_HOSTNAME, "_hostname" );
 1091         sorterThreadStack.push( sorterThreads[4] );
 1092       }
 1093 
 1094       for ( int i = 0; i < sorterThreads.length; i++ ){
 1095         if ( sorterThreads[i] != null ){
 1096           sorterThreads[i].setPriority(Thread.NORM_PRIORITY);
 1097         }
 1098       }
 1099 
 1100       /* initialize Velocity */
 1101       Velocity.init();
 1102 
 1103       /* begin output */
 1104       VelocityContext context = new VelocityContext();
 1105 
 1106       /* find the base filename to be used for relative paths */
 1107       String fileComponent;
 1108       fileComponent = BASE_FILENAME.substring( BASE_FILENAME.lastIndexOf( File.separator ) + 1 );
 1109 
 1110       /* prepare the info which will be visible from within the templates */
 1111       if ( CHARTS_PRODUCE ){
 1112         context.put( "chartFilename", CHART_BASE_FILENAME.substring( CHART_BASE_FILENAME.lastIndexOf( File.separator ) + 1) );
 1113       }
 1114 
 1115       boolean detailedEnabled = false;
 1116       if ( enableReportByWorm ){
 1117         context.put( "enableReportByWorm", "yes" );
 1118         detailedEnabled = true;
 1119       }
 1120       if ( enableReportByNumattacks ){
 1121         context.put( "enableReportByNumattacks", "yes" );
 1122         detailedEnabled = true;
 1123       }
 1124       if ( enableReportByDate ){
 1125         context.put( "enableReportByDate", "yes" );
 1126         detailedEnabled = true;
 1127       }
 1128       if ( enableReportByIP ){
 1129         context.put( "enableReportByIP", "yes" );
 1130         detailedEnabled = true;
 1131       }
 1132       if ( enableReportByHostname ){
 1133         context.put( "enableReportByHostname", "yes" );
 1134         detailedEnabled = true;
 1135       }
 1136       if ( detailedEnabled ){
 1137         context.put( "enableDetailedReports", "yes" );
 1138       }
 1139 
 1140       context.put( "worms", worms );
 1141       context.put( "numattacks", new Integer( totalAttacks ) );
 1142       context.put( "hosts", sourceStorage );
 1143       context.put( "numhosts", new Integer( numhosts ));
 1144       context.put( "timestamp", new Date() );
 1145       context.put( "basename", fileComponent );
 1146       context.put( "baseext", BASE_EXTENSION);
 1147       context.put( "showLatestAttackOnly", new Boolean( SHOW_LATEST_ATTACK_ONLY ) );
 1148 
 1149       Template template = null;
 1150 
 1151       /* write out the summary report */
 1152       try{
 1153         template = Velocity.getTemplate( SUMMARY_TEMPLATE_NAME );
 1154         FileWriter fw = new FileWriter( BASE_FILENAME + BASE_EXTENSION );
 1155         template.merge( context, fw );
 1156         fw.close();
 1157         if ( WRITE_GZIP ){
 1158           try {
 1159             writeGZIP( BASE_FILENAME + BASE_EXTENSION, BASE_FILENAME + BASE_EXTENSION + ".gz" );
 1160           }
 1161           catch ( Exception e ){
 1162             if ( OUTPUT_LEVEL > 0 ) System.out.println( "Unable to write GZIP report: " + e.getMessage() );
 1163           }
 1164         }
 1165         if ( OUTPUT_LEVEL > 2 ) System.out.println( "Report summary written to disk" );
 1166       }
 1167       catch( Exception e ){
 1168         if ( OUTPUT_LEVEL > 0 ) System.err.println( "Unable to produce report summary: " + e.getMessage() );
 1169       }
 1170 
 1171       /* run until all sorts are either done or running (not waiting) */
 1172       while( !sorterThreadStack.empty() ){
 1173         /* scan through the number of sorters waiting to be run in the queue */
 1174         for ( int j = 0; j < NUM_RUNNING_SORT_THREADS; j++ ){
 1175           /* if empty slot, fill it */
 1176           if ( runningSorterThreads[j] == null ){
 1177             if ( !sorterThreadStack.empty() ){
 1178               runningSorterThreads[j] = (Sorter)(sorterThreadStack.pop());
 1179               runningSorterThreads[j].start();
 1180             }
 1181           }
 1182           /* if one is done, join up with it and fill slot if possible */
 1183           else if ( runningSorterThreads[j].isFinished() ){
 1184             runningSorterThreads[j].join();
 1185             if ( OUTPUT_LEVEL > 2 ) System.err.println( "Sorting by " + runningSorterThreads[j].getSortMethodName() + " complete" );
 1186 
 1187             if ( !sorterThreadStack.empty() ){
 1188               runningSorterThreads[j] = (Sorter)(sorterThreadStack.pop());
 1189               runningSorterThreads[j].start();
 1190             }
 1191             else {
 1192               runningSorterThreads[j] = null;
 1193             }
 1194           }
 1195         }
 1196 
 1197         if ( sorterThreadStack.empty() ) break;
 1198 
 1199         Thread.sleep( 5000 );
 1200       }
 1201 
 1202       System.gc();
 1203 
 1204       /* wait until all sorts done */
 1205       for( int i = 0; i < runningSorterThreads.length; i++ ){
 1206         if( runningSorterThreads[i] != null ) {
 1207           runningSorterThreads[i].join();
 1208           if ( OUTPUT_LEVEL > 2 ) System.err.println( "Sorting by " + runningSorterThreads[i].getSortMethodName() + " complete" );
 1209           runningSorterThreads[i] = null;
 1210         }
 1211       }
 1212 
 1213       runningSorterThreads = null;
 1214 
 1215       Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
 1216 
 1217       /* write out all of the reports */
 1218       try {
 1219         template = Velocity.getTemplate( REPORT_TEMPLATE_NAME );
 1220 
 1221         writeChart( CHART_BASE_FILENAME, fullAttackDateSorter.getResults() );
 1222         for ( int i = 0; i < sorterThreads.length; i++ ){
 1223           System.gc();
 1224           if ( sorterThreads[i] != null ){
 1225             writeReport( template, context, sorterThreads[i].getSortMethodName(), sorterThreads[i].getResults(), true );
 1226             if ( OUTPUT_LEVEL > 2 ) System.out.println( "Detailed reports sorted by " + sorterThreads[i].getSortMethodName() + " written to disk" );
 1227           }
 1228         }
 1229 
 1230       }
 1231       catch( Exception e ){
 1232         if ( OUTPUT_LEVEL > 0 ) System.err.println( "Unable to produce detailed reports: " + e.getMessage() );
 1233         e.printStackTrace();
 1234       }
 1235 
 1236 
 1237       if ( OUTPUT_LEVEL > 2 ) System.out.println( "Total attacks: " + totalAttacks );
 1238       for ( int i = 0; i < worms.length; i++ ){
 1239         if ( OUTPUT_LEVEL > 2 ) System.out.println( "Total " + worms[i].getName() + " attacks: " + worms[i].getNumAttacks() );
 1240       }
 1241     }
 1242     catch ( Exception e ){
 1243       if ( OUTPUT_LEVEL > 1 ) System.err.println("Exception: " +  e.getMessage());
 1244       if ( OUTPUT_LEVEL > 3 ) e.printStackTrace();
 1245     }
 1246     if ( OUTPUT_LEVEL > 2 ) System.out.println( "Finished: " + new Date() );
 1247   }
 1248 
 1249   private static void writeChart( String filename, Object[] results ){
 1250     int cd = -1;
 1251     int cm = -1;
 1252     int cy = -1;
 1253 
 1254     Worm[] wormsToday = new Worm[ worms.length ];
 1255     Worm[] wormsTotal = new Worm[ worms.length ];
 1256     Worm[] attacksLastDay = new Worm[ worms.length ];
 1257     java.util.Calendar yesterday = java.util.Calendar.getInstance();
 1258     yesterday.add( java.util.Calendar.DATE, -1 );
 1259 
 1260     TimeSeries[] wormGraph = new TimeSeries[ worms.length ];
 1261     TimeSeries[] wormGraphTotal = new TimeSeries[ worms.length ];
 1262 
 1263     //create new worm counters and graphs for each worm
 1264     for ( int i = 0; i < worms.length; i++ ){
 1265       wormsToday[i] = new Worm();
 1266       wormsToday[i].setName( worms[i].getName() );
 1267       wormsToday[i].setShortName( worms[i].getShortName() );
 1268 
 1269       wormsTotal[i] = new Worm();
 1270       wormsTotal[i].setName( worms[i].getName() );
 1271       wormsTotal[i].setShortName( worms[i].getShortName() );
 1272 
 1273       attacksLastDay[i] = new Worm();
 1274       attacksLastDay[i].setName( worms[i].getName() );
 1275       attacksLastDay[i].setShortName( worms[i].getShortName() );
 1276 
 1277       wormGraph[i] = new TimeSeries( worms[i].getName() );
 1278       wormGraphTotal[i] = new TimeSeries( worms[i].getName() );
 1279     }
 1280 
 1281     //loop through attacks and count number of attacks by worm per day
 1282     boolean onLastDay = false;
 1283     java.util.Calendar workingCalendar = java.util.Calendar.getInstance();
 1284     for( int i = 0; i < results.length; i++ ){
 1285       if ( results[i] instanceof Attack ){
 1286         Attack attack = (Attack)(results[i]);
 1287         Date doa = attack.getDateOfAttack();
 1288         workingCalendar.setTime( doa );
 1289         int d = workingCalendar.get( java.util.Calendar.DAY_OF_MONTH );
 1290         int m = workingCalendar.get( java.util.Calendar.MONTH ) + 1;
 1291         int y = workingCalendar.get( java.util.Calendar.YEAR );
 1292 
 1293         //last day means last 24 hours, NOT last calendar date
 1294         if ( !onLastDay && workingCalendar.after( yesterday ) ){
 1295           onLastDay = true;
 1296         }
 1297 
 1298         //this only happens the first time through
 1299         if ( cd == -1 && cm == -1 && cy == -1 ){
 1300           cd = d;
 1301           cm = m;
 1302           cy = y;
 1303         }
 1304         //the date changed. deal with it.
 1305         else if ( cd != d || cm != m || cy != y ){
 1306           for ( int j = 0; j < wormsToday.length; j++ ){
 1307             try {
 1308               wormGraph[j].add(new Day(cd,cm,cy), new Integer( wormsToday[j].getNumAttacks() ));
 1309               wormGraphTotal[j].add(new Day(cd,cm,cy), new Integer( wormsTotal[j].getNumAttacks() ));
 1310             }
 1311             catch( SeriesException sex ){
 1312               System.err.println( sex.getMessage() );
 1313             }
 1314             wormsToday[j].resetNumAttacks();
 1315           }
 1316           cd = d;
 1317           cm = m;
 1318           cy = y;
 1319         }
 1320 
 1321         //so who attacked us just now?
 1322         for( int j = 0; j < wormsToday.length; j++ ){
 1323           if ( wormsToday[j].getName().equals( attack.getAttackingWorm().getName() ) ){
 1324             wormsToday[j].addAttack();
 1325             wormsTotal[j].addAttack();
 1326             if ( onLastDay ){
 1327               attacksLastDay[j].addAttack();
 1328             }
 1329             break;
 1330           }
 1331         }
 1332 
 1333       }//end if
 1334     }//end for
 1335 
 1336     //finish up the last of it and add to TimeSeriesCollection
 1337     TimeSeriesCollection allData = new TimeSeriesCollection();
 1338     TimeSeriesCollection allDataTotal = new TimeSeriesCollection();
 1339     for ( int j = 0; j < wormGraph.length; j++ ){
 1340       if ( workingCalendar != null ){
 1341         allData.addSeries( wormGraph[j] );
 1342         allDataTotal.addSeries( wormGraphTotal[j] );
 1343       }
 1344     }
 1345 
 1346     try {
 1347       JFreeChart dailyChart = ChartFactory.createTimeSeriesChart("Daily Attacks by Worm", "Date", "Number of Attacks Daily", allData, true, false, false);
 1348       JFreeChart totalChart = ChartFactory.createTimeSeriesChart("Total Attacks by Worm", "Date", "Total Number of Attacks", allDataTotal, true, false, false);
 1349 
 1350       BufferedOutputStream fos = new BufferedOutputStream( new FileOutputStream(filename + "_daily.jpg") );
 1351       ChartUtilities.writeChartAsJPEG(fos, dailyChart, CHART_WIDTH, CHART_HEIGHT);
 1352       fos.close();
 1353 
 1354       BufferedOutputStream fos2 = new BufferedOutputStream( new FileOutputStream(filename + "_total.jpg") );
 1355       ChartUtilities.writeChartAsJPEG(fos2, totalChart, CHART_WIDTH, CHART_HEIGHT);
 1356       fos2.close();
 1357 
 1358       //since chart doesn't handle 0-values well, we need to count non-zero categories
 1359       int numActiveWormsToday = 0;
 1360       for ( int i = 0; i < attacksLastDay.length; i++ ){
 1361         if ( attacksLastDay[i].getNumAttacks() > 0 )
 1362           numActiveWormsToday++;
 1363       }
 1364 
 1365       //now set up the raw data for the pie chart for last 24 hours
 1366       Number[][] pieRawData = new Integer[1][ numActiveWormsToday ];
 1367       String[] series = new String[1];
 1368       series[0] = "Worms";
 1369       String[] pieWormNames = new String[ numActiveWormsToday ];
 1370       int activeWormIndex = 0;
 1371       for ( int i = 0; i < attacksLastDay.length; i++ ){
 1372         if ( attacksLastDay[i].getNumAttacks() > 0 ){
 1373           pieRawData[0][activeWormIndex] = new Integer(attacksLastDay[i].getNumAttacks());
 1374           pieWormNames[activeWormIndex] = attacksLastDay[i].getName() + " " + attacksLastDay[i].getNumAttacks();
 1375           activeWormIndex++;
 1376         }
 1377       }
 1378       //create the datasets
 1379       DefaultPieDataset pieExtracted = new DefaultPieDataset();
 1380       for( int i = 0; i < pieRawData[0].length; i++ ){
 1381         pieExtracted.setValue( pieWormNames[i], pieRawData[0][i] );
 1382       }
 1383       JFreeChart pieChart = ChartFactory.createPie3DChart("Attacks in Last 24 Hours", pieExtracted, true, false, false);
 1384 
 1385       PiePlot pie = (PiePlot)pieChart.getPlot();
 1386 
 1387       pie.setSectionLabelType(PiePlot.PERCENT_LABELS);
 1388 
 1389       //output the pie chart for last 24 hours
 1390       OutputStream fos3 = new BufferedOutputStream( new FileOutputStream( filename + "_today.jpg" ) );
 1391       ChartUtilities.writeChartAsJPEG(fos3, pieChart, CHART_WIDTH, CHART_HEIGHT);
 1392       fos3.close();
 1393 
 1394       //count number of active worms in all attacks
 1395       numActiveWormsToday = 0;
 1396       for ( int i = 0; i < wormsTotal.length; i++ ){
 1397         if ( wormsTotal[i].getNumAttacks() > 0 )
 1398           numActiveWormsToday++;
 1399       }
 1400 
 1401       //now set up the raw data for the pie chart
 1402       pieRawData = new Integer[1][ numActiveWormsToday ];
 1403       pieWormNames = new String[ numActiveWormsToday ];
 1404       activeWormIndex = 0;
 1405       for ( int i = 0; i < wormsTotal.length; i++ ){
 1406         if ( wormsTotal[i].getNumAttacks() > 0 ){
 1407           pieRawData[0][activeWormIndex] = new Integer(wormsTotal[i].getNumAttacks());
 1408           pieWormNames[activeWormIndex] = wormsTotal[i].getName() + " " + wormsTotal[i].getNumAttacks();
 1409           activeWormIndex++;
 1410         }
 1411       }
 1412 
 1413       //create the datasets
 1414       pieExtracted = new DefaultPieDataset();
 1415       for( int i = 0; i < pieRawData[0].length; i++ ){
 1416         pieExtracted.setValue( pieWormNames[i], pieRawData[0][i] );
 1417       }
 1418       pieChart = ChartFactory.createPie3DChart("Total Attacks by Worm", pieExtracted, true, false, false);
 1419 
 1420       pie = (PiePlot)pieChart.getPlot();
 1421       pie.setSectionLabelType(PiePlot.PERCENT_LABELS);
 1422 
 1423       //output the all attacks pie chart
 1424       OutputStream fos4 = new BufferedOutputStream( new FileOutputStream( filename + "_allattacks.jpg" ) );
 1425       ChartUtilities.writeChartAsJPEG(fos4, pieChart, CHART_WIDTH, CHART_HEIGHT);
 1426       fos4.close();
 1427     }
 1428     catch( RuntimeException ex ){
 1429       System.err.println( "Runtime ex making chart: " + ex.getMessage() );
 1430       ex.printStackTrace();
 1431     }
 1432     catch( Exception ex ){
 1433       System.err.println( "Normal ex making chart: " + ex.getMessage() );
 1434     }
 1435     catch ( java.lang.Error error ){
 1436       System.err.println( "Unable to produce charts (no AWT available): " + error.getMessage() );
 1437     }
 1438   }
 1439 
 1440   private static void writeReport( Template template, VelocityContext context, String fileExt, Object[] results, boolean reverseAlso ) throws Exception {
 1441     template = Velocity.getTemplate( REPORT_TEMPLATE_NAME );
 1442     context.put( "attacks", results );
 1443 
 1444     Writer fw = new BufferedWriter( new FileWriter( BASE_FILENAME + fileExt + BASE_EXTENSION ) );
 1445     template.merge( context, fw );
 1446     fw.close();
 1447 
 1448     if ( WRITE_GZIP ){
 1449       try {
 1450         writeGZIP( BASE_FILENAME + fileExt + BASE_EXTENSION, BASE_FILENAME + fileExt + BASE_EXTENSION + ".gz" );
 1451       }
 1452       catch ( Exception e ){
 1453         if ( OUTPUT_LEVEL > 0 ) System.out.println( "Unable to write GZIP report: " + e.getMessage() );
 1454       }
 1455     }
 1456 
 1457     if ( reverseAlso ){
 1458       writeReport ( template, context, fileExt + "_desc", Sorter.reverseArray( results ), false );
 1459     }
 1460   }
 1461 }