'''
halberd.py

Copyright 2006 Andres Riancho

This file is part of w3af, w3af.sourceforge.net .

w3af is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation version 2 of the License.

w3af is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with w3af; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

'''

import core.controllers.outputManager as om
from core.controllers.basePlugin.baseDiscoveryPlugin import baseDiscoveryPlugin
import core.data.kb.knowledgeBase as kb
import core.data.kb.info as infokb
import core.data.parsers.urlParser as urlParser
from core.controllers.w3afException import w3afRunOnce

# halberd imports!
# done this way so the user can run this plugin without having to install halberd !
# Also, the halberd version i'm using, has some minimal changes.
import sys, os.path
halberdDir = 'plugins' + os.path.sep + 'discovery' + os.path.sep + 'oHalberd'

# This insert in the first position of the path is to "step over" an installation of halberd.
sys.path.insert( 0, halberdDir )

import Halberd.shell as HalberdShell
import Halberd.logger as HalberdLogger
import Halberd.ScanTask as HalberdScanTask
import Halberd.version as HalberdshellVersion
import Halberd.clues.analysis as HalberdAnalysis

class halberd(baseDiscoveryPlugin):
	'''
	This plugin tries to find HTTP load balancers.
	@author: Andres Riancho ( andres.riancho@gmail.com )
	'''
	
	'''
	This plugin is a wrapper for  Juan M. Bello Rivas <jmbr |at| superadditive.com> halberd.
	'''

	def __init__(self):
		baseDiscoveryPlugin.__init__(self)
		
		self._exec = True

	def discover(self, fuzzableRequest ):
		'''
		It calls the "main" from halberd and writes the results to the kb.
		
		@parameter fuzzableRequest: A fuzzableRequest instance that contains (among other things) the URL to test.
		'''
		if not self._exec:
			# This will remove the plugin from the discovery plugins to be runned.
			raise w3afRunOnce()
		else:
			self._exec = False
			om.out.information('halberd plugin is starting. Original halberd author: Juan M. Bello Rivas ; http://halberd.superadditive.com/')
			
			self._main( urlParser.baseUrl( fuzzableRequest.getURL() ) )
			
		return []
		
	def getOptionsXML(self):
		'''
		This method returns a XML containing the Options that the plugin has.
		Using this XML the framework will build a window, a menu, or some other input method to retrieve
		the info from the user. The XML has to validate against the xml schema file located at :
		w3af/core/ui/userInterface.dtd
		
		@return: XML with the plugin options.
		'''	
		return	'<?xml version="1.0" encoding="ISO-8859-1"?>\
		<OptionList>\
		</OptionList>\
		'

	def setOptions( self, OptionList ):
		'''
		This method sets all the options that are configured using the user interface 
		generated by the framework using the result of getOptionsXML().
		
		@parameter OptionList: A dictionary with the options for the plugin.
		@return: No value is returned.
		'''	
		pass
		

	def getPluginDeps( self ):
		'''
		@return: A list with the names of the plugins that should be runned before the
		current one.
		'''
		return []
		
	def _main( self, url ):
		'''
		This was taken from the original halberd, 'script/halberd' .
		'''
		scantask = HalberdScanTask.ScanTask()

		scantask.scantime = HalberdScanTask.default_scantime
		scantask.parallelism = HalberdScanTask.default_parallelism
		scantask.verbose = False
		scantask.debug = False
		scantask.conf_file = HalberdScanTask.default_conf_file
		scantask.cluefile = ''
		scantask.save = ''
		scantask.output = ''
		
		HalberdLogger.setError()
		scantask.readConf()
		
		# UniScan
		scantask.url = url
		scantask.addr = ''
		scanner = HalberdShell.UniScanStrategy
		
		s = scanner(scantask)
		
		try:
			result = s.execute()
		except HalberdShell.ScanError, msg:
			om.out.debug('*** %s ***' % msg )
		except KeyboardInterrupt:
			raise
		else:
			self._report( result )

	def _report( self, scantask):
		"""
		Displays detailed report information to the user and save the data to the kb.
		"""
		if len(scantask.analyzed) == 1:
			om.out.information('The site: ' + scantask.url + " doesn't seem to have a HTTP load balancer configuration.")
		else:
			clues = scantask.analyzed
			hits = HalberdAnalysis.hits(clues)
		
			# xxx This could be passed by the caller in order to avoid recomputation in
			# case the clues needed a re-analysis.
			diff_fields = HalberdAnalysis.diff_fields(clues)
		
			om.out.information('=' * 70 )
			om.out.information('%s' % scantask.url, newLine=False)
			if scantask.addr:
				om.out.information(' (%s)' % scantask.addr, newLine=False)
			om.out.information(': %d real server(s)'  % len(clues) )
			om.out.information('=' * 70 )
		
			for num, clue in enumerate(clues):
				assert hits > 0
				info = clue.info
		
				om.out.information('')
				om.out.information('server %d: %s' % (num + 1, info['server'].lstrip()))
				om.out.information('-' * 70 + '\n')
				
				# This is added so other w3af plugins can read the halberd results.
				# If needed by other plugins, I could fill up the info object with more
				# data about the different headers, time, etc...
				i = infokb.info()
				i['server'] = info['server'].lstrip()
				i['serverNumber'] = num +1
				kb.kb.append( self, 'halberd', i )
				
				om.out.information('difference: %d seconds' % clue.diff)
		
				om.out.information('successful requests: %d hits (%.2f%%)' \
						  % (clue.getCount(), clue.getCount() * 100 / float(hits)))
		
				if info['contloc']:
					om.out.information('content-location: %s' % info['contloc'].lstrip())
		
				if len(info['cookies']) > 0:
					om.out.information('cookie(s):')
				for cookie in info['cookies']:
					om.out.information('  %s' % cookie.lstrip())
		
				om.out.information('header fingerprint: %s' % info['digest'])
		
				different = [(field, value) for field, value in clue.headers \
											if field in diff_fields]
				if different:
					om.out.information('different headers:')
					idx = 1
					for field, value in different:
						om.out.information('  %d. %s:%s' % (idx, field, value))
						idx += 1
		
				if scantask.debug:
					import pprint
					import StringIO
					tmp = StringIO.StringIO()
					om.out.information('headers:')
					pprint.pprint(clue.headers, stream=tmp, indent=2)
					om.out.information( tmp )
					
			om.out.information('\n')
	
	def getLongDesc( self ):
		'''
		@return: A DETAILED description of the plugin functions and features.
		'''
		return '''
		This plugin tries to find if an HTTP Load balancer is present.
		'''
