-
Notifications
You must be signed in to change notification settings - Fork 2
/
pidgin-fb-login-2fa.py
executable file
·228 lines (177 loc) · 6.49 KB
/
pidgin-fb-login-2fa.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/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.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
from pathlib import Path
from optparse import OptionParser
FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'
DEBUG = False
# 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: %s", 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
home = str(Path.home())
ACCOUNTS = home + "/.purple/accounts.xml"
debug("accounts.xml is: %s" % ACCOUNTS)
with open(ACCOUNTS, 'r') as myfile:
obj = xmltodict.parse(myfile.read())
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 ", 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 ", 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
Make sure Pidgin is *not running* before proceeding. Pidgin modifies
the accounts.xml on exit as well as while running, so it is important
to exit pidgin before starting so our changes are not lost.
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:
<token type='string'>THE_CODE_HERE</token>
''')
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:
debug("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("<setting name='token' type='string'>%s</setting>" % response['access_token'])
# Pidgin initializes UID to 0...
if UID == 0:
print("<setting name='uid' type='string'>%s</setting>" % 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("<setting name='did' type='string'>%s</setting>" % response['device_id'])
# Machine ID seems to change every time, but we're not supposed to update it.
# If we do, pidgin will want to re-sync the history and FB will say it's too
# much data.
if ( MID != response['machine_id'] ):
debug("<setting name='mid' type='string'>%s</setting>" % response['machine_id'])