From c99e2c9f2bbb12140f636f15ca696d60438da6ff Mon Sep 17 00:00:00 2001 From: Josh d'Entremont Date: Tue, 26 May 2015 18:37:25 -0300 Subject: [PATCH] Added login script --- README.md | 21 +++- fallout.py | 6 ++ fallout_login.py | 261 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+), 2 deletions(-) create mode 100755 fallout.py create mode 100644 fallout_login.py diff --git a/README.md b/README.md index 48183a3..0caeec0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,21 @@ -fallout-terminal +Fallout Terminal ================ -simulates a computer from fallout +Simulates a computer terminal from fallout. + +Currently only implements the login portion. + +Usage +================ + +``` +python fallout.py +``` + +To use the login in another program: + +``` +import fallout_login + +fallout_login.beginLogin() +``` diff --git a/fallout.py b/fallout.py new file mode 100755 index 0000000..aa4d596 --- /dev/null +++ b/fallout.py @@ -0,0 +1,6 @@ +#! /usr/bin/env python +import fallout_login as login + +login.beginLogin() + + diff --git a/fallout_login.py b/fallout_login.py new file mode 100644 index 0000000..23c7b8e --- /dev/null +++ b/fallout_login.py @@ -0,0 +1,261 @@ +#! /usr/bin/env python + +import curses +import random +import os + +################## global "constants" ################ + +# number of characters for hex digits and spaces +CONST_WIDTH = 16 + +# position of the attempt squares +SQUARE_X = 19 +SQUARE_Y = 3 + +LOGIN_ATTEMPTS = 4 + +# amount of time to pause after correct password input +LOGIN_PAUSE = 3000 + +# amount of time to pause after lockout +LOCKED_OUT_TIME = 5000 + +# starting number for hex generation +START_HEX = 0xf650 + +# list of possible symbols for password hiding +SYMBOLS = '!@#$%^*()_-+={}[]|\\:;\'",<>./?' + +################## functions ######################### + +def generateHex(n): + """ + generates n numbers starting at START_HEX and increasing by 12 each time + """ + num = START_HEX + list = [] + for i in xrange(n): + list.append(num) + num += 12 + return list + + +def getSymbols(n): + """ + return n random symbols + """ + count = len(SYMBOLS) + result = "" + for i in xrange(n): + result += SYMBOLS[random.randint(0, count - 1)] + return result + + +def getPasswords(): + """ + Returns an array of strings to be used as the password and the decoys + """ + # temporarily hard coded for testing + #TODO - move the passwords out of this file and add more arrays + passwords = [ + 'FEVER', + 'SEVER', + 'SEWER', + 'SEVEN', + 'ROVER', + 'ERROR', + 'HELLO', + 'BOXER' + ] + + random.shuffle(passwords) + return passwords + + +def getFiller(length, passwords): + """ + Return a string of symbols with potential passwords mixed in + + length - the length of the string to create + passwords - an array of passwords to hide in the symbols + """ + filler = getSymbols(length) + + # add the passwords to the symbols + pwdLen = len(passwords[0]) + pwdCount = len(passwords) + i = 0 + for pwd in passwords: + # skip a distance based on total size to cover then place a password + maxSkip = length / pwdCount - pwdLen + i += random.randint(maxSkip - 2, maxSkip) + filler = filler[:i] + pwd + filler[i + pwdLen:] + i += pwdLen + return filler + + +def initScreen(scr): + """ + Fill the screen to prepare for password entry + + scr - curses window returned from curses.initscr() + """ + size = scr.getmaxyx() + width = size[1] + height = size[0] - 5 # - 5 for header lines + + hexes = generateHex(height * 2) + + hexCol1 = hexes[:height] + hexCol2 = hexes[height:] + + # generate the symbols and passwords + fillerLength = width / 2 * height + passwords = getPasswords() + filler = getFiller(fillerLength, passwords) + fillerCol1 = filler[:len(filler) / 2] + fillerCol2 = filler[len(filler) / 2:] + + # each column of symbols and passwords should be 1/4 of the screen + fillerWidth = width / 4 + + # print the header stuff + scr.addstr('ROBCO INDUSTRIES (TM) TERMLINK PROTOCOL\n') + scr.addstr('ENTER PASSWORD NOW\n\n') + scr.addstr(str(LOGIN_ATTEMPTS) + ' ATTEMPT(S) LEFT: ') + for i in xrange(LOGIN_ATTEMPTS): + scr.addch(curses.ACS_BLOCK) + scr.addstr(' ') + scr.addstr('\n\n') + + # print the hex and filler + for i in xrange(height): + scr.addstr("0x%X %s 0x%X %s" % (hexCol1[i], fillerCol1[i * fillerWidth: (i + 1) * fillerWidth], hexCol2[i], fillerCol2[i * fillerWidth: (i + 1) * fillerWidth])) + if i < height - 1: + scr.addstr('\n') + + scr.refresh() + + return passwords + + +def moveInput(scr, inputPad): + """ + moves the input pad to display all text then a blank line then the cursor + """ + size = scr.getmaxyx() + height = size[0] + width = size[1] + + inputPad.addstr('\n>') + + # cursor position relative to inputPad + cursorPos = inputPad.getyx() + + inputPad.refresh(0, 0, + height - cursorPos[0] - 1, + width / 2 + CONST_WIDTH, + height - 1, + width - 1) + + +def userInput(scr, passwords): + """ + let the user attempt to crack the password + + scr - curses window returned from curses.initscr() + passwords - array of passwords hidden in the symbols + """ + size = scr.getmaxyx() + height = size[0] + width = size[1] + + # set up a pad for user input + inputPad = curses.newpad(height, width / 2 + CONST_WIDTH) + + attempts = LOGIN_ATTEMPTS + + # randomly pick a password from the list + pwd = passwords[random.randint(0, len(passwords) - 1)] + curses.echo() + + while attempts > 0: + # move the curser to the correct spot for typing + scr.move(height - 1, width / 2 + CONST_WIDTH + 1) + + # scroll user input up as the user tries passwords + moveInput(scr, inputPad) + + guess = scr.getstr() + cursorPos = inputPad.getyx() + + # write under the last line of text + inputPad.move(cursorPos[0] - 1, cursorPos[1] - 1) + inputPad.addstr('>' + guess.upper() + '\n') + + # user got password right + if guess.upper() == pwd.upper(): + inputPad.addstr('>Exact match!\n') + inputPad.addstr('>Please wait\n') + inputPad.addstr('>while system\n') + inputPad.addstr('>is accessed.\n') + + moveInput(scr, inputPad) + + curses.napms(LOGIN_PAUSE) + return + + # wrong password + else: + pwdLen = len(pwd) + matched = 0 + try: + for i in xrange(pwdLen): + if pwd[i].upper() == guess[i].upper(): + matched += 1 + except IndexError: + pass # user did not enter enough letters + + inputPad.addstr('>Entry denied\n') + inputPad.addstr('>' + str(matched) + '/' + str(pwdLen) + + ' correct.\n') + + attempts -= 1 + # show remaining attempts + scr.move(SQUARE_Y, 0) + scr.addstr(str(attempts)) + scr.move(SQUARE_Y, SQUARE_X) + for i in xrange(LOGIN_ATTEMPTS): + if i < attempts: + scr.addch(curses.ACS_BLOCK) + else: + scr.addstr(' ') + scr.addstr(' ') + + # Out of attempts + scr.erase() + scr.move(height / 2 - 1, width / 2 - 7) + scr.addstr('TERMINAL LOCKED') + scr.move(height / 2 + 1, width / 2 - 16) + scr.addstr('PLEASE CONTACT AN ADMINISTRATOR') + curses.curs_set(0) + scr.refresh() + + curses.napms(LOCKED_OUT_TIME) + +def runLogin(scr): + """ + Start the login portion of the terminal + """ + random.seed() + passwords = initScreen(scr) + userInput(scr, passwords) + + +def beginLogin(): + """ + Initialize curses and start the login process + """ + scr = curses.initscr() + curses.wrapper(runLogin)