#!/usr/bin/python3 # Portions of this script directly from https://gist.github.com/jaymzh/3ed8817cf8c20222ca09ce33a544b695 # Permanent home: https://github.com/akhepcat/Miscellaneous/ # Direct download: https://raw.githubusercontent.com/akhepcat/Miscellaneous/master/pidgin-fb-login-2fa-v2.py import sys import cgi from urllib.parse import urlencode, quote_plus import hashlib import getpass import http.client import urllib import json import xmltodict import dbus from pathlib import Path from optparse import OptionParser FB_API_KEY = '256002347743983' FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895' DEBUG = False ppath = None prpl = None # Somehelper methods def fb_sig(data): newdata = data.copy() params = ''.join(['%s=%s' % x for x in sorted(data.items())]) newdata['sig'] = hashlib.md5((params + FB_API_SECRET).encode('utf-8')).hexdigest() return newdata def debug(msg): global DEBUG if DEBUG: print("DEBUG: ", msg) parser = OptionParser() parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False) (options, args) = parser.parse_args() if options.debug: DEBUG = True try: bus = dbus.SessionBus() obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") prpl = purple.PurpleAccountsFindConnected("", "prpl-facebook") ppath = purple.PurpleUserDir() except: debug("pidgin not running: falling back to system path") ppath = str(Path.home()) + "/.purple" ACCOUNTS = ppath + "/accounts.xml" debug("accounts.xml is: %s" % ACCOUNTS) try: with open(ACCOUNTS, 'r') as myfile: obj = xmltodict.parse(myfile.read()) except: print("Can't read accounts from %s" % ACCOUNTS) sys.exit(1) # From here, we should have everything we need to validate... acts = obj["account"]["account"] found=0 idx=0 while found == 0: if acts[idx]["protocol"] == "prpl-facebook": found=1 else: idx = idx + 1 if found == 0: print("No prpl-facebook account found in %s" % ACCOUNTS) print("exiting") exit(1) EMAIL = acts[idx]["name"] passwd = acts[idx]["password"] settings = acts[idx]["settings"] ### Get did, uid, mid found=0 ox=0 while 1: ix=0 while 1: debug("Checking ox %s ix %s, it is %s" % (ox, ix, settings[ox]["setting"][ix]["@name"])) try: if settings[ox]["setting"][ix]["@name"] == "did": DID = settings[ox]["setting"][ix]["#text"] found = found +1 if settings[ox]["setting"][ix]["@name"] == "uid": UID = settings[ox]["setting"][ix]["#text"] found = found +1 if settings[ox]["setting"][ix]["@name"] == "mid": MID = settings[ox]["setting"][ix]["#text"] found = found +1 if found > 2: break except: break ix = ix + 1 if ix > 10: break if found < 2: ox = ox + 1 else: break if ox > 20: break if found < 3: print("Warning: Either no machine (mid), device (did) or user account (uid) found in prpl-facebook section of %s" % ACCOUNTS) print("This may cause unexpected errors in the script") # exit(1) debug("Account UID: %s" % UID) debug("Account DID: %s" % DID) debug("Account MID: %s" % MID) if EMAIL == '': print("ERROR: set an email address, please") sys.exit() if DID == '': print("ERROR: set a device id (to any UUID), please") sys.exit() data = { "fb_api_req_friendly_name": "authenticate", "locale": "en", "format": "json", "api_key": FB_API_KEY, "method": "auth.login", "generate_session_cookies": "1", "generate_machine_id": "1", "email": EMAIL, "uid": UID, "device_id": DID, } print('Access Token generator for Facebook 2factor login\n') if (prpl is not None): print('''Making sure Pidgin is *not running* before proceeding. Pidgin modifies the accounts.xml on exit as well as while running, so we will tell pidgin to exit in 5 seconds before continuing so our changes are not lost. \n''') sleep(5) purple.PurpleCoreQuit() sleep(1) print('''This tool will perform 2-factor login to FB and then print out an access token needed for the FB plugin for bitlbee and pidgin. Do *NOT* select "yes this was me" if you get a security pop-up from your Facebook app. Instead, enter the code in here. Take the resulting code and put it in the "token" tag in accounts.xml like so: THE_CODE_HERE ''') data['password'] = passwd headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"} conn = http.client.HTTPSConnection('b-api.facebook.com:443') params = urlencode(fb_sig(data), quote_via=quote_plus) conn.request('POST', '/method/auth.login', params, headers) response = conn.getresponse() debug("status, reason: %s, %s" % (response.status, response.reason)) response_data = response.read() debug("undecoded response: %s" % response_data) response = json.loads(response_data.decode('utf-8')) debug(response) # check to make sure that worked... if response['error_code'] != 406: print( "ERROR: Incorrect password, 2-fac is not enabled, or some other issue." " Dumping results:\n\n\t", end='' ) print(response) sys.exit(1) code = input('Code: ') error_data = json.loads(response['error_data']) first_fac = error_data['login_first_factor'] data['credentials_type'] = 'two_factor' data['error_detail_type'] = 'button_with_disabled' data['first_factor'] = first_fac data['twofactor_code'] = code data['password'] = data['twofactor_code'] data['userid'] = error_data['uid'] data['machine_id'] = error_data['machine_id'] debug("FB Account UID: %s" % error_data['uid'] ) try: data['device_id'] = error_data['device_id'] debug("FB Account DID: %s" % error_data['device_id']) except: print("FB Account DID not present in error_data: equal to accounts.xml did") debug("FB Account MID: %s" % error_data['machine_id']) params = urlencode(fb_sig(data)) conn.request('POST', '/method/auth.login', params, headers) response = conn.getresponse() debug("status, reason: %s, %s" % (response.status, response.reason)) response_data = response.read() debug("undecoded response: %s" % response_data) response = json.loads(response_data.decode('utf-8')) print("Update or add the following settings in %s under the Facebook account:" % ACCOUNTS) print("%s" % response['access_token']) # Pidgin initializes UID to 0... if UID == 0: print("%s" % response['uid']) # We don't always get back a "device_id", but if we do, make sure it's correct remote_did = response.get('device_id') if ( remote_did is not None and DID != response.get('device_id') ): print("%s" % response['device_id']) if ( MID != response['machine_id'] ): print("%s" % response['machine_id']) print("After updating, pidgin should be ready to run")