bbarchivist.scripts.droidlookup   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 158
dl 0
loc 276
rs 9.52
c 0
b 0
f 0
wmc 36

11 Functions

Rating   Name   Duplication   Size   Complexity  
B grab_args() 0 62 2
A questionnaire() 0 14 2
A questionnaire_initial() 0 15 5
A droidlookup_main() 0 31 5
A parse_ceiling() 0 11 1
A parse_floor() 0 8 1
A questionnaire_branch() 0 12 4
A questionnaire_single() 0 17 3
A questionnaire_final() 0 18 5
A parse_extreme() 0 27 3
A execute_args() 0 19 5
1
#!/usr/bin/env python3
2
"""Check Android autoloader files."""
3
4
import sys  # load arguments
5
6
import requests  # session
7
from bbarchivist import argutils  # arguments
8
from bbarchivist import decorators  # Ctrl+C wrapping
9
from bbarchivist import jsonutils  # json
10
from bbarchivist import networkutils  # lookup
11
from bbarchivist import utilities  # argument filters
12
13
__author__ = "Thurask"
14
__license__ = "WTFPL v2"
15
__copyright__ = "2015-2019 Thurask"
16
17
18
def grab_args():
19
    """
20
    Parse arguments from argparse/questionnaire.
21
22
    Invoke :func:`droidlookup.droidlookup_main` with those arguments.
23
    """
24
    if len(sys.argv) > 1:
25
        parser = argutils.default_parser("bb-droidlookup", "Get Android autoloaders")
26
        parser.add_argument(
27
            "branch",
28
            help="OS branch, 3 letters")
29
        parser.add_argument(
30
            "floor",
31
            help="Start of search range",
32
            default=0,
33
            nargs="?",
34
            type=int,
35
            choices=range(0, 999),
36
            metavar="floor")
37
        parser.add_argument(
38
            "-d",
39
            "--device",
40
            dest="device",
41
            help="Device to check",
42
            nargs="?",
43
            type=argutils.droidlookup_devicetype,
44
            default=None)
45
        parser.add_argument(
46
            "-c",
47
            "--ceiling",
48
            dest="ceil",
49
            help="End of search range",
50
            default=999,
51
            nargs="?",
52
            type=int,
53
            choices=range(1, 1000),
54
            metavar="ceil")
55
        parser.add_argument(
56
            "-t",
57
            "--type",
58
            help="Check SHA256/512 hashes instead",
59
            default=None,
60
            type=argutils.droidlookup_hashtype)
61
        parser.add_argument(
62
            "-s",
63
            "--single",
64
            dest="single",
65
            help="Only scan one OS build",
66
            action="store_true",
67
            default=False)
68
        parser.add_argument(
69
            "-a",
70
            "--all-devices",
71
            dest="alldevices",
72
            help="Scan all devices, not just known ones",
73
            action="store_true",
74
            default=False)
75
        args = parser.parse_args(sys.argv[1:])
76
        parser.set_defaults()
77
        execute_args(args)
78
    else:
79
        questionnaire()
80
81
82
def execute_args(args):
83
    """
84
    Get args and decide what to do with them.
85
86
    :param args: Arguments.
87
    :type args: argparse.Namespace
88
    """
89
    if args.single:
90
        args.ceil = args.floor  # range(x, x+1) == x
91
    famlist = jsonutils.load_json("droidfamilies")
92
    cleanlist = famlist[:4]  # Priv/DTEK50/DTEK60/KEYone
93
    if args.device is None:
94
        if not args.alldevices:
95
            famlist = cleanlist
96
        droidlookup_main(famlist, args.branch, args.floor, args.ceil, args.type)
97
    elif args.device not in cleanlist:
98
        print("Selected device {0} has unknown autoloader scheme!".format(args.device))
99
    else:
100
        droidlookup_main(args.device, args.branch, args.floor, args.ceil, args.type)
101
102
103
def questionnaire_single():
104
    """
105
    What to ask if only one lookup is needed.
106
    """
107
    while True:
108
        scanos = input("OS (ex. AAD250): ")
109
        branch = scanos[:3]
110
        floor = scanos[3:6]
111
        quants = [len(scanos) == 6, branch.isalpha(), floor.isdigit()]
112
        if not all(quants):
113
            print("OS MUST BE 3 LETTERS AND 3 NUMBERS, TRY AGAIN")
114
            continue
115
        else:
116
            floor = int(floor)
117
            ceil = floor
118
            break
119
    return branch, floor, ceil
120
121
122
def questionnaire_branch():
123
    """
124
    Ask about lookup branch.
125
    """
126
    while True:
127
        branch = input("BRANCH (ex. AAD): ")
128
        if len(branch) != 3 or not branch.isalpha():
129
            print("BRANCH MUST BE 3 LETTERS, TRY AGAIN")
130
            continue
131
        else:
132
            break
133
    return branch
134
135
136
def parse_floor(floor):
137
    """
138
    Check if floor value is OK.
139
140
    :param floor: Starting OS version.
141
    :type floor: int
142
    """
143
    return parse_extreme(floor, 0, 998, "INITIAL < 0, TRY AGAIN", "INITIAL > 998, TRY AGAIN")
144
145
146
def parse_ceiling(ceil, floor):
147
    """
148
    Check if ceiling value is OK.
149
150
    :param ceil: Ending OS version.
151
    :type ceil: int
152
153
    :param floor: Starting OS version.
154
    :type floor: int
155
    """
156
    return parse_extreme(ceil, floor, 999, "FINAL < INITIAL, TRY AGAIN", "FINAL > 999, TRY AGAIN")
157
158
159
def questionnaire_initial():
160
    """
161
    Ask about lookup start.
162
    """
163
    while True:
164
        try:
165
            floor = int(input("INITIAL OS (0-998): "))
166
        except ValueError:
167
            continue
168
        else:
169
            if parse_floor(floor):
170
                break
171
            else:
172
                continue
173
    return floor
174
175
176
def parse_extreme(starter, minim, maxim, mintext, maxtext):
177
    """
178
    Check if floor/ceiling value is OK.
179
180
    :param starter: Minimum/maximum OS version.
181
    :type starter: int
182
183
    :param minim: Minimum value for starter.
184
    :type minim: int
185
186
    :param maxim: Maximum value for starter.
187
    :type maxim: int
188
189
    :param mintext: What to print if starter < minim.
190
    :type mintext: str
191
192
    :param maxtext: What to print if starter > maxim.
193
    :type maxtext: str
194
    """
195
    okay = False
196
    if starter < minim:
197
        print(mintext)
198
    elif starter > maxim:
199
        print(maxtext)
200
    else:
201
        okay = True
202
    return okay
203
204
205
def questionnaire_final(floor):
206
    """
207
    Ask about lookup end.
208
209
    :param floor: Starting OS version.
210
    :type floor: int
211
    """
212
    while True:
213
        try:
214
            ceil = int(input("FINAL OS (1-999): "))
215
        except ValueError:
216
            ceil = 999
217
        else:
218
            if parse_ceiling(ceil, floor):
219
                break
220
            else:
221
                continue
222
    return ceil
223
224
225
def questionnaire():
226
    """
227
    Questions to ask if no arguments given.
228
    """
229
    single = utilities.i2b("SINGLE OS (Y/N)?: ")
230
    if single:
231
        branch, floor, ceil = questionnaire_single()
232
    else:
233
        branch = questionnaire_branch()
234
        floor = questionnaire_initial()
235
        ceil = questionnaire_final(floor)
236
    famlist = jsonutils.load_json("droidfamilies")  # same here
237
    droidlookup_main(famlist[:2], branch, floor, ceil)
238
    decorators.enter_to_exit(True)
239
240
241
@decorators.wrap_keyboard_except
242
def droidlookup_main(device, branch, floor=0, ceil=999, method=None):
243
    """
244
    Check the existence of Android factory images, in a range.
245
246
    :param device: Device to check.
247
    :type device: str
248
249
    :param branch: OS version, 3 letters.
250
    :type branch: str
251
252
    :param floor: Starting OS version, padded to 3 numbers. Default is 0.
253
    :type floor: int
254
255
    :param ceil: Ending OS version, padded to 3 numbers. Default is 999.
256
    :type ceil: int
257
258
    :param method: None for regular OS links, "hash256/512" for SHA256 or 512 hash.
259
    :type method: str
260
    """
261
    argutils.slim_preamble("DROIDLOOKUP")
262
    text = "DEVICE: ALL" if isinstance(device, list) else "DEVICE: {0}".format(device.upper())
263
    print(text)
264
    sess = requests.Session()
265
    for ver in range(floor, ceil + 1):
266
        build = "{0}{1}".format(branch.upper(), str(ver).zfill(3))
267
        print("NOW SCANNING: {0}".format(build), end="\r")
268
        results = networkutils.droid_scanner(build, device, method, sess)
269
        if results is not None:
270
            for result in results:
271
                print("{0} AVAILABLE! {1}\n".format(build, result), end="\r")
272
273
274
if __name__ == "__main__":
275
    grab_args()
276