Added login script
This commit is contained in:
parent
33c38f0cb5
commit
c99e2c9f2b
3 changed files with 286 additions and 2 deletions
21
README.md
21
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()
|
||||||
|
```
|
||||||
|
|
6
fallout.py
Executable file
6
fallout.py
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
import fallout_login as login
|
||||||
|
|
||||||
|
login.beginLogin()
|
||||||
|
|
||||||
|
|
261
fallout_login.py
Normal file
261
fallout_login.py
Normal file
|
@ -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)
|
Loading…
Reference in a new issue