899 lines
34 KiB
Python
Executable File
899 lines
34 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||
# https://github.com/reid-k/gridfire
|
||
|
||
from __future__ import unicode_literals, print_function
|
||
|
||
__program__ = "Gridfire"
|
||
__version__ = "v0.5+testforge"
|
||
__doc__ = """
|
||
Gridfire is inspired by gpggrid, a security tool included in
|
||
Tinfoil Hat Linux, intended to resist shoulder-surfing and keylogging.
|
||
For more information on the project, see the README file distributed with Gridfire.
|
||
|
||
Gridfire is named after a fictional superweapon in Iain M Banks' Culture novels.
|
||
See http://everything2.com/title/Gridfire for more.
|
||
RIP Iain M Banks (1954-02-16 to 2013-06-09)
|
||
|
||
usage: gridfire.py [-h] [-a TTYALERT] [-c] [-g] [-u] [-l] [-b] [-o] [-d] [-f]
|
||
[-p] [-w] [-m METHOD] [-n] [-v VERBOSITY]
|
||
[-S SINGLE] [-D DOUBLE] [-R REPEAT ]
|
||
[ -P POS] [-H HEADER] [-A ANSWER] [-O fd]
|
||
[STDIN COMMAND [STDIN ARGS ...]]
|
||
|
||
positional arguments:
|
||
STDIN COMMAND Arguments to run a subprocess wih the password in stdin.
|
||
|
||
optional arguments:
|
||
-h, --help show this help message and exit
|
||
-a TTYALERT, --ttyalert TTYALERT
|
||
Set the alert mode (none, beep or flash)
|
||
-c Force concealment of entered text.
|
||
-g, --grid Ignore other options and behave like gpggrid.
|
||
-u Allow uppercase letters [A-Z].
|
||
-l Allow lowercase letters [a-z].
|
||
-b Allow binary numbers [0-1].
|
||
-o Allow octal numbers [0-7].
|
||
-d Allow decimal numbers [0-9].
|
||
-f Allow hexadecimal numbers [0-9][a-f][A-F].
|
||
-p Allow punctuation.
|
||
-w Allow whitespace.
|
||
-m METHOD, --method METHOD
|
||
Specify indexing method (0-3, higher is more secure,
|
||
default=3)
|
||
-n Append N newlines on output.
|
||
-v VERBOSITY, --verbosity VERBOSITY
|
||
Specify verbosity (0-5, higher is verbose, default=3)
|
||
-S SINGLE, --single SINGLE
|
||
Arg to pass to a subprocess with -SINGLE=password.
|
||
-D DOUBLE, --double DOUBLE
|
||
Arg to pass to a subprocess with --DOUBLE=password.
|
||
-R, --repeat
|
||
Repeat the password with a newline to a subprocess.
|
||
-P POS, --pos POS Position to place the Arg to a subprocess with
|
||
--DOUBLE/SINGLE=password.
|
||
-H HEADER, --header HEADER
|
||
Header line as a prompt (default "").
|
||
-A ANSWER, --answer ANSWER
|
||
Skip the grid and give the answer (for testing only).
|
||
|
||
"""
|
||
|
||
#Learn more about Tinfoil Hat Linux:
|
||
#Homepage: http://tinfoilhat.shmoo.com/
|
||
#Wikipedia: https://en.wikipedia.org/wiki/Tinfoil_Hat_Linux
|
||
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU Affero General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU Affero General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU Affero General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
#TODO: Decide whether or not implement method 4
|
||
#TODO: Fix row index for non-default charsets
|
||
|
||
import os
|
||
import sys
|
||
import signal
|
||
import curses
|
||
import locale
|
||
import string
|
||
import random
|
||
import argparse
|
||
import _curses
|
||
from io import StringIO
|
||
import subprocess
|
||
from time import sleep
|
||
|
||
PY3 = sys.version_info[0] == 3
|
||
VERBOSE = 3
|
||
#
|
||
# 0: stdin
|
||
# 1: stdout
|
||
# 2: stderr
|
||
iOUT_FD = sys.stdout
|
||
|
||
try:
|
||
from pinentry import PinEntry
|
||
except ImportError:
|
||
PinEntry = None
|
||
oPinEntry = None
|
||
|
||
try:
|
||
from dialog import Dialog
|
||
except ImportError:
|
||
Dialog = None
|
||
oDIALOG = None
|
||
oLAST_DIR = '/tmp/'
|
||
|
||
# Allow stderr for the caller to use
|
||
def debug(message):
|
||
if VERBOSE <= 4: return
|
||
sys.stdout.write('DEBUG: ' + repr(message) +'\n')
|
||
pass
|
||
def info(message):
|
||
if VERBOSE <= 3: return
|
||
sys.stdout.write('INFO: ' + repr(message) +'\n')
|
||
pass
|
||
def warn(message):
|
||
if VERBOSE <= 2: return
|
||
sys.stdout.write('WARN: ' + repr(message) +'\n')
|
||
pass
|
||
def error(message):
|
||
if VERBOSE <= 1: return
|
||
sys.stdout.write('ERROR: ' + message +'\n')
|
||
pass
|
||
|
||
class DummyContextManager:
|
||
def __enter__(self):
|
||
return self
|
||
|
||
def __exit__(self, *exc):
|
||
return False
|
||
|
||
#Helps curses play nice
|
||
locale.setlocale(locale.LC_ALL, '')
|
||
code = locale.getpreferredencoding()
|
||
|
||
##### Defaults & Initial variables #####
|
||
charsets = ''
|
||
min_x, min_y = 55, 17
|
||
upperchar = ''
|
||
lowerchar = ''
|
||
out = ''
|
||
hide_out = 0
|
||
newline = 1
|
||
|
||
def spaceout(s):
|
||
i=len(s)-1 #number of spaces to be inserted
|
||
while i >= 1:
|
||
s = s[:-i]+" "+s[-i:]
|
||
i -= 1
|
||
return s
|
||
|
||
def make_parser():
|
||
"""Argument parsing"""
|
||
global VERBOSE
|
||
|
||
parser = argparse.ArgumentParser(add_help=True)
|
||
parser.add_argument('-a', '--ttyalert', type=str, help='Set the alert mode (none, beep or flash)')
|
||
parser.add_argument('-c', help="Force concealment of entered text.", action="store_true")
|
||
parser.add_argument('-g', '--grid', help="Ignore other options and behave like gpggrid.", action="store_true")
|
||
#~ parser.add_argument('-u', dest='char_u', action='store_true')
|
||
parser.add_argument('-u', help="Allow uppercase letters [A-Z].", action="store_true")
|
||
parser.add_argument('-l', help="Allow lowercase letters [a-z].", action="store_true")
|
||
parser.add_argument('-b', help="Allow binary numbers [0-1].", action="store_true")
|
||
parser.add_argument('-o', help="Allow octal numbers [0-7].", action="store_true")
|
||
parser.add_argument('-d', help="Allow decimal numbers [0-9].", action="store_true")
|
||
parser.add_argument('-f', help="Allow hexadecimal numbers [0-9][a-f][A-F].", action="store_true")
|
||
parser.add_argument('-p', help="Allow punctuation.", action="store_true")
|
||
parser.add_argument('-w', help="Allow whitespace.", action="store_true")
|
||
parser.add_argument('-m', '--method', help='Specify indexing method (0-3, higher is more secure, default=3)', dest='method', type=int, default=3)
|
||
parser.add_argument('-n', help="Append newlines on output.", dest='newline', type=int, default=1)
|
||
parser.add_argument('-v', '--verbosity', help='Specify verbosity (0-5, higher is verbose, default=3)', type=int, default=3)
|
||
parser.add_argument('-S', '--single', help="Arg to pass to a subprocess with -SINGLE=password.", dest="single", default='')
|
||
parser.add_argument('-D', '--double', help="Arg to pass to a subprocess with --DOUBLE=password.", dest="double", default='')
|
||
parser.add_argument('-R', '--repeat', help="Repeat the password with a newline to a subprocess", action="store_true")
|
||
parser.add_argument('-P', '--pos', help="Position to place the Arg to a subprocess with --DOUBLE/SINGLE=password.", dest="pos", type=int, default=1)
|
||
parser.add_argument('-H', '--header', help="Header line as a prompt (default \"\").", dest="header", default='')
|
||
parser.add_argument('-A', '--answer', help="Skip the grid and give the answer (for testing only).", dest="answer", default='')
|
||
parser.add_argument('-O', '--output_fd', help="Output file descriptor to print the answer (default 1).", dest="output_fd", type=int, default=1)
|
||
parser.add_argument('sargs', help="Arguments to run a subprocess wih the password in stdin.", type=str, default='', nargs='*') # metavar='STDIN COMMAND',
|
||
return parser
|
||
|
||
def make_grid(args):
|
||
"""Define character sets"""
|
||
punct = spaceout(string.punctuation)
|
||
punct2 = punct[52:]
|
||
punct = punct[:52]
|
||
whitespace = spaceout(string.whitespace)
|
||
|
||
#Select character sets
|
||
#TODO: Can this be simplified? args['u'+'l'] or whatnot?
|
||
#use_uppercases = (-1 != charsets.find("u"))
|
||
use_uppercases = args['u']
|
||
use_lowercases = args['l']
|
||
use_bindigits = args['b']
|
||
use_octdigits = args['o']
|
||
use_decdigits = args['d']
|
||
use_hexdigits = args['f']
|
||
use_punct = args['p']
|
||
use_whitespace = args['w']
|
||
_setcount = args['u']+args['l']+args['b']+args['o']+args['d']+args['f']+args['p']+args['w']
|
||
|
||
#if use_uppercases+use_lowercases+use_bindigits+use_octdigits+use_decdigits+use_hexdigits+use_punct+use_whitespace == 0:
|
||
if _setcount == 0:
|
||
use_uppercases = 1
|
||
use_lowercases = 1
|
||
use_decdigits = 1
|
||
use_punct = 1
|
||
use_whitespace = 1
|
||
|
||
use_numbers = 0
|
||
numbers = ''
|
||
if use_bindigits+use_octdigits+use_decdigits+use_hexdigits: use_numbers = 1
|
||
if use_bindigits: numbers = '0 1'
|
||
if use_octdigits: numbers = spaceout(string.octdigits)
|
||
if use_decdigits: numbers = spaceout(string.digits)
|
||
if use_hexdigits: numbers = spaceout(string.hexdigits)
|
||
|
||
##### Build static grid #####
|
||
grid = []
|
||
if use_uppercases:
|
||
if PY3:
|
||
grid.append(spaceout(string.ascii_uppercase))
|
||
else:
|
||
grid.append(spaceout(string.uppercase))
|
||
if use_lowercases:
|
||
if PY3:
|
||
grid.append(spaceout(string.ascii_lowercase))
|
||
else:
|
||
grid.append(spaceout(string.lowercase))
|
||
if use_numbers: grid.append(numbers)
|
||
if use_punct:
|
||
grid.append(punct)
|
||
grid.append(punct2)
|
||
return (use_uppercases,
|
||
use_lowercases,
|
||
use_decdigits,
|
||
use_punct,
|
||
use_whitespace,
|
||
punct,
|
||
punct2,
|
||
whitespace,
|
||
numbers,
|
||
grid, )
|
||
|
||
##### Build user interface #####
|
||
usagebanner = "Choose from grid by typing letter pairs like eF or Dg"
|
||
|
||
#? not hasattr(sys, 'frozen') and
|
||
if Dialog is not None and os.path.isfile('/usr/bin/dialog'):
|
||
shortcuts = "4:Shell 5:FName 6:FContents 7:Delete 8:Show/Hide 9:Quit 0:Done"
|
||
else:
|
||
shortcuts = "7:Delete 8:Show/Hide 9:Quit 0:Done"
|
||
|
||
def total_rows(use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct):
|
||
return use_lowercases+use_uppercases+use_decdigits+use_whitespace+use_punct+use_punct+1
|
||
|
||
def iMain(lSysArgv):
|
||
global password, exit_code, VERBOSE, iOUT_FD, oDIALOG, oLAST_DIR
|
||
global min_x, min_y, upperchar, lowerchar, out, hide_out
|
||
global oLAST_DIR
|
||
|
||
padpos = 0
|
||
|
||
stdscr = None
|
||
password = ''
|
||
exit_code = -1
|
||
out = ''
|
||
upperchar = ''
|
||
lowerchar = ''
|
||
|
||
if PY3:
|
||
uppercase = string.ascii_uppercase
|
||
lowercase = string.ascii_lowercase
|
||
else:
|
||
uppercase = string.uppercase
|
||
lowercase = string.lowercase
|
||
|
||
# Check for required size before continuing
|
||
def check_size(stdscr):
|
||
(size_y,size_x) = stdscr.getmaxyx()
|
||
if ((size_x < min_x)|(size_y < min_y)):
|
||
error( __program__+" needs a "+str(min_x)+"x"+str(min_y)+" character area to function.\n")
|
||
error( "please adjust your terminal size and restart "+__program__+".")
|
||
sys.exit(1)
|
||
|
||
def pad_draw(stdscr, pad, ly, lx, hy, hx, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, title):
|
||
rv = __program__ +" "+ __version__
|
||
pad.addstr(0,0," "*(hx+1),curses.A_REVERSE)
|
||
pad.addstr(0,2,rv,curses.A_REVERSE)
|
||
pad.addstr(1,0," "*min_x)
|
||
#~ pad.addstr(0,40," ")
|
||
pad.addstr(0,40,lowerchar,curses.A_REVERSE)
|
||
pad.addstr(0,41,upperchar,curses.A_REVERSE)
|
||
pad.addstr(0,73,"SHOW")
|
||
if hide_out:
|
||
pad.addstr(0,73,"HIDE")
|
||
pad.addstr(1,0,header)
|
||
pad.addstr(2,0,">>")
|
||
pad.addstr(2,4," "*(len(out)+1))
|
||
disp_out = out
|
||
if hide_out:
|
||
disp_out = len(out)*"*"
|
||
pad.addstr(2,4,disp_out)
|
||
|
||
##Draw the grid
|
||
global gridrow
|
||
gridrow = 6
|
||
gridoffset = 4
|
||
|
||
#Grid Indexes
|
||
pad.addstr(gridrow-2,gridoffset,spaceout(col_index))
|
||
for t in range(0, total_rows(use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct)):
|
||
pad.addstr(gridrow+t,1,row_index[t])
|
||
t += 1
|
||
|
||
#Index selection highlighting
|
||
if len(upperchar):
|
||
stdscr.addstr(gridrow-2,4+spaceout(col_index).find(upperchar),upperchar,curses.A_REVERSE)
|
||
if len(lowerchar):
|
||
stdscr.addstr(gridrow+row_index.find(lowerchar),1,lowerchar,curses.A_REVERSE)
|
||
|
||
#Static grid elements
|
||
### New grid draw method ###
|
||
r = 0
|
||
for s in grid:
|
||
pad.addstr(gridrow,gridoffset,grid[r])
|
||
r += 1
|
||
gridrow += 1
|
||
|
||
if use_whitespace:
|
||
#Draw the whitespace row
|
||
pad.addstr(gridrow,gridoffset," :Space :Tab :Enter")
|
||
pad.addstr(gridrow,gridoffset,col_index[0])
|
||
pad.addstr(gridrow,9+gridoffset,col_index[1])
|
||
pad.addstr(gridrow,18+gridoffset,col_index[2])
|
||
#If the corresponding columns are selected, highlight them
|
||
if len(upperchar)&(upperchar==col_index[0]):
|
||
stdscr.addstr(gridrow,4+col_index.find(upperchar),upperchar,curses.A_REVERSE)
|
||
if len(upperchar)&(upperchar==col_index[1]):
|
||
stdscr.addstr(gridrow,11+col_index.find(upperchar),upperchar,curses.A_REVERSE)
|
||
if len(upperchar)&(upperchar==col_index[2]):
|
||
stdscr.addstr(gridrow,19+col_index.find(upperchar),upperchar,curses.A_REVERSE)
|
||
gridrow += 1
|
||
|
||
#Draw the 'done' line, and highlight column if selected
|
||
pad.addstr(gridrow,gridoffset," :Ignore")
|
||
pad.addstr(gridrow,gridoffset,col_index[0])
|
||
# pad.addstr(gridrow,9+gridoffset,col_index[1])
|
||
# pad.addstr(gridrow,18+gridoffset,col_index[2])
|
||
if len(upperchar)&(upperchar==col_index[0]):
|
||
stdscr.addstr(gridrow,4+spaceout(col_index).find(upperchar),upperchar,curses.A_REVERSE)
|
||
if len(upperchar)&(upperchar==col_index[1]):
|
||
stdscr.addstr(gridrow,10+spaceout(col_index).find(upperchar),upperchar,curses.A_REVERSE)
|
||
if len(upperchar)&(upperchar==col_index[2]):
|
||
stdscr.addstr(gridrow,17+spaceout(col_index).find(upperchar),upperchar,curses.A_REVERSE)
|
||
|
||
#Help banners
|
||
pad.addstr(14,0,title)
|
||
pad.addstr(15,0,usagebanner)
|
||
# (1+hx-len(shortcuts))*' '+
|
||
pad.addstr(16,0,shortcuts,curses.A_REVERSE)
|
||
|
||
pad.refresh(padpos, 0, ly, lx, hy, hx)
|
||
|
||
def main_draw(stdscr, pad, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, title):
|
||
(max_y, max_x,) = stdscr.getmaxyx()
|
||
pad_draw(stdscr, pad, 0, 0, (max_y-2), (max_x-1), use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, title)
|
||
stdscr.move(max_y-1,0)
|
||
try:
|
||
curses.curs_set(0)
|
||
except:
|
||
pass
|
||
stdscr.refresh()
|
||
|
||
def cycle_index(cyclemethod=3): #Argument specifies method, default is 3
|
||
global col_index, row_index
|
||
if PY3:
|
||
uppercase = string.ascii_uppercase
|
||
lowercase = string.ascii_lowercase
|
||
else:
|
||
uppercase = string.uppercase
|
||
lowercase = string.lowercase
|
||
#Method 0: Ordered letters and numbers, no randomization
|
||
#This method has 1 possible state and provides no security.
|
||
#Included to allow gridfire's use to limit possible symbols entered.
|
||
#For example, hex digits only when entering a WEP key.
|
||
if cyclemethod==0:
|
||
col_index = uppercase
|
||
row_index = lowercase
|
||
|
||
#Method 1: Ordered letters, random offset (as with gpggrid)
|
||
#The math might differ from gpggrid
|
||
#This method has 676 or 26^2 possible states
|
||
if cyclemethod==1:
|
||
offset = random.randint(0,25)
|
||
col_index = uppercase[offset:]+uppercase[:offset]
|
||
offset = random.randint(0,25)
|
||
row_index = lowercase[offset:]+lowercase[:offset]
|
||
|
||
#Method 2: use random.shuffle() to shuffle one index in place
|
||
#This might not work very well, see module documentation on
|
||
#issues with small len(x) exceeding period of RNGs
|
||
# http://docs.python.org/2/library/random.html
|
||
##This method has:
|
||
##403,291,461,126,605,635,584,000,000
|
||
##or 26! possible states (403.3 septillion)
|
||
if cyclemethod==2:
|
||
col_index = uppercase
|
||
colcycle = list(uppercase)
|
||
random.shuffle(colcycle)
|
||
col_index = ''.join(colcycle)
|
||
row_index = col_index.lower()
|
||
|
||
#Method 3: use random.shuffle() to shuffle indices in place
|
||
#This might not work very well, see module documentation on
|
||
#issues with small len(x) exceeding period of RNGs
|
||
# http://docs.python.org/2/library/random.html
|
||
#This method has:
|
||
#162,644,002,617,632,464,507,038,883,409,628,607,021,056,000,000,000,000
|
||
##or 26!^2 possible states (162.6 sexdecillion).
|
||
if cyclemethod==3:
|
||
col_index = uppercase
|
||
colcycle = list(uppercase)
|
||
random.shuffle(colcycle)
|
||
col_index = ''.join(colcycle)
|
||
|
||
row_index = lowercase
|
||
rowcycle = list(lowercase)
|
||
random.shuffle(rowcycle)
|
||
row_index = ''.join(rowcycle)
|
||
|
||
#TODO: Implement method 4 (mixed cases in row/column indexes)
|
||
# (This would require redoing input handling to recognize same-case pairs)
|
||
#Method 4 would have:
|
||
#80,658,175,170,943,878,571,660,636,856,403,766,975,289,505,440,883,277,824,000,000,000,000
|
||
#or 52! possible states (80.6 Unvigintillion).
|
||
|
||
def quit_scr(leave_message = "", exit_status= 0):
|
||
global password, exit_code
|
||
stdscr.keypad(0)
|
||
curses.echo()
|
||
curses.nocbreak()
|
||
curses.endwin()
|
||
# print leave_message
|
||
password = leave_message
|
||
exit_code = exit_status
|
||
|
||
parser = make_parser()
|
||
args = vars(parser.parse_args(lSysArgv))
|
||
|
||
debug('args.keys() ' +repr(args.keys()))
|
||
VERBOSE = args['verbosity']
|
||
cyclemethod = args['method'] or 3
|
||
header = args['header'] or ''
|
||
answer = args['answer'] or ''
|
||
output_fd = args['output_fd'] or 1
|
||
sargs = args['sargs']
|
||
|
||
if output_fd != 1:
|
||
iOUT_FD = os.fdopen(int(output_fd), 'w')
|
||
|
||
info('DEBUG: sargs=' + repr(sargs))
|
||
|
||
(use_uppercases,
|
||
use_lowercases,
|
||
use_decdigits,
|
||
use_punct,
|
||
use_whitespace,
|
||
punct,
|
||
punct2,
|
||
whitespace,
|
||
numbers,
|
||
grid ) = make_grid(args)
|
||
|
||
if answer:
|
||
# for testing - bypass the grid and accept the answer
|
||
password = answer
|
||
exit_code = 0
|
||
else:
|
||
if not stdscr:
|
||
stdscr = curses.initscr()
|
||
curses.noecho()
|
||
curses.start_color()
|
||
try:
|
||
curses.use_default_colors()
|
||
except:
|
||
pass
|
||
stdscr.keypad(1)
|
||
|
||
for key, value in _curses.__dict__.items():
|
||
if key[0:4] == 'ACS_' or key in ('LINES', 'COLS'):
|
||
if key == 'LINES':
|
||
value = value - 3
|
||
setattr(curses, key, value)
|
||
|
||
curses.flushinp ()
|
||
check_size(stdscr)
|
||
pad = curses.newpad(100,100)
|
||
if args['ttyalert'] == 'beep':
|
||
curses.beep()
|
||
elif args['ttyalert'] == 'flash':
|
||
curses.flash()
|
||
|
||
##### MAIN LOOP: draw the interface and handle user input #####
|
||
cycle_index(cyclemethod) #Don't start the program with unrandomized indexes
|
||
# If this isn't done, the program initially shows nothing
|
||
main_draw(stdscr, pad, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, '')
|
||
|
||
_previous_sigcont_handler = None
|
||
while exit_code == -1:
|
||
if hasattr(sys.stderr, 'getvalue'):
|
||
title = sys.stderr.getvalue().strip()
|
||
sys.stderr.buf = ''
|
||
else:
|
||
title = ''
|
||
if title:
|
||
title = title.split('\n')[-1]
|
||
main_draw(stdscr, pad, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, title)
|
||
curses.noecho()
|
||
curses.cbreak()
|
||
sys.stderr.flush()
|
||
sys.stdout.flush()
|
||
if not _previous_sigcont_handler:
|
||
_previous_sigcont_handler = signal.getsignal(signal.SIGCONT)
|
||
|
||
try:
|
||
c = stdscr.getkey()
|
||
except:
|
||
quit_scr()
|
||
break
|
||
|
||
if c == '0':
|
||
quit_scr(out) #on 'Done', write output and exit
|
||
break
|
||
if c in ['', '9', chr(27)]: # Quit
|
||
quit_scr() #on 'Cancel', exit without writing output
|
||
break
|
||
if c == '8': # Hide
|
||
hide_out -= hide_out+hide_out
|
||
hide_out += 1
|
||
elif ((c == '7')&(len(out)>=1)) or ((c == 'KEY_BACKSPACE')&(len(out)>=1)):
|
||
out = out[:len(out)-1] # Delete
|
||
elif Dialog is not None and c == '6': # File Contents
|
||
|
||
# you will first need to save the tty modes with a call to def_prog_mode()
|
||
# and then call endwin() to end the curses mode. This will leave you in the
|
||
# original tty mode. To get back to curses once you are done, call
|
||
# reset_prog_mode() . This function returns the tty to the state stored by
|
||
# def_prog_mode(). Then do refresh(), and you are back to the curses mode.
|
||
_curses.def_prog_mode() ; _curses.endwin()
|
||
sBut, sCode = lDialogFselect(oLAST_DIR, height=10, width=50, help_button=False)
|
||
if sBut == 'ok':
|
||
sOut = sCode
|
||
else:
|
||
return
|
||
sBut = 'ok'; sCode = '='
|
||
if sOut and out:
|
||
sText = sOut # "New text at Beginning, Replace or End"
|
||
sBut, sCode = lDialogCode(sText)
|
||
if sBut != 'ok':
|
||
pass
|
||
elif sCode == '<':
|
||
out = sOut + ' ' + out
|
||
elif sCode == '>':
|
||
out = out + ' ' + sOut
|
||
elif sCode == '=':
|
||
out = sOut
|
||
elif sCode == '':
|
||
out = ''
|
||
|
||
# oDIALOG.msgbox("Bug Alert: If the screen goes black, suspend to shell with ^Z and then use fg to come back to Gridfire " +sCode, height=8, width=40)
|
||
|
||
main_draw(stdscr, pad, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, '')
|
||
_curses.reset_prog_mode()
|
||
stdscr.refresh()
|
||
|
||
elif Dialog is not None and c == '5': # File Name
|
||
|
||
# you will first need to save the tty modes with a call to def_prog_mode()
|
||
# and then call endwin() to end the curses mode. This will leave you in the
|
||
# original tty mode. To get back to curses once you are done, call
|
||
# reset_prog_mode() . This function returns the tty to the state stored by
|
||
# def_prog_mode(). Then do refresh(), and you are back to the curses mode.
|
||
_curses.def_prog_mode() ; _curses.endwin()
|
||
sOut = sDialogFget()
|
||
sBut = 'ok'; sCode = '='
|
||
if sOut and out:
|
||
sText = sOut # "New text at Beginning, Replace or End"
|
||
sBut, sCode = lDialogCode(sText)
|
||
if sBut != 'ok':
|
||
pass
|
||
elif sCode == '<':
|
||
out = sOut + ' ' + out
|
||
elif sCode == '>':
|
||
out = out + ' ' + sOut
|
||
elif sCode == '=':
|
||
out = sOut
|
||
elif sCode == '':
|
||
out = ''
|
||
|
||
# oDIALOG.msgbox("Bug Alert: If the screen goes black, suspend to shell with ^Z and then use fg to come back to Gridfire " +sCode, height=8, width=40)
|
||
|
||
main_draw(stdscr, pad, use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct, header, '')
|
||
_curses.reset_prog_mode()
|
||
stdscr.refresh()
|
||
|
||
elif Dialog is not None and c == '4': # Shell
|
||
if oDIALOG is None:
|
||
oDIALOG = Dialog()
|
||
sCode = '='
|
||
sOut = ''
|
||
yStdoutdata = yStderrdata = b''
|
||
|
||
# you will first need to save the tty modes with a call to def_prog_mode()
|
||
# and then call endwin() to end the curses mode. This will leave you in the
|
||
# original tty mode. To get back to curses once you are done, call
|
||
# reset_prog_mode() . This function returns the tty to the state stored by
|
||
# def_prog_mode(). Then do refresh(), and you are back to the curses mode.
|
||
_curses.def_prog_mode() ; _curses.endwin()
|
||
sCode, sAnswer = oDIALOG.inputbox("Run command (erase to cancel)",
|
||
init='')
|
||
if sCode == 'ok' and sAnswer:
|
||
|
||
try:
|
||
devnull = subprocess.DEVNULL
|
||
except AttributeError: # Python < 3.3
|
||
devnull_context = devnull = open(os.devnull, "wb")
|
||
else:
|
||
devnull_context = DummyContextManager()
|
||
|
||
with devnull_context:
|
||
p = subprocess.Popen(sAnswer,
|
||
stdout=subprocess.PIPE,
|
||
stderr=devnull, close_fds=True)
|
||
if True:
|
||
( yStdoutdata, yStderrdata ) = p.communicate()
|
||
retcode = p.returncode
|
||
oDIALOG.scrollbox(yStdoutdata.decode().strip(),
|
||
15,50)
|
||
else:
|
||
retcode = p.wait()
|
||
ystdoutdata = p.stdout.read()
|
||
# One could use title=... instead of text=... to put the text
|
||
# in the title bar.
|
||
oDIALOG.programbox(fd=p.stdout.fileno(),
|
||
text="Output of command: " +sAnswer)
|
||
|
||
# oDIALOG.msgbox("Bug Alert: If the screen goes black, suspend to shell with ^Z and then use fg to come back to Gridfire " +str(retcode), height=8, width=40)
|
||
|
||
if retcode == 0:
|
||
sOut = yStdoutdata.decode().strip()
|
||
sBut = 'ok'; sCode = '='
|
||
if sOut and out:
|
||
sText = sOut # "New text at Beginning, Replace or End"
|
||
sBut, sCode = lDialogCode(sText)
|
||
if sBut != 'ok':
|
||
pass
|
||
elif sCode == '<':
|
||
out = sOut + ' ' + out
|
||
elif sCode == '>':
|
||
out = out + ' ' + sOut
|
||
elif sCode == '=':
|
||
out = sOut
|
||
elif sCode == '':
|
||
out = ''
|
||
_curses.reset_prog_mode()
|
||
stdscr.refresh()
|
||
|
||
elif c == '3': # was Ignore
|
||
# now 'Ignore' is a keypair to fool keystroke counting
|
||
pass
|
||
elif PinEntry is not None and c == '2': # unused
|
||
if oPinEntry is None:
|
||
oPinEntry = PinEntry()
|
||
oPinEntry.run()
|
||
# unfinished
|
||
#handle row/column selections
|
||
elif (uppercase.find(c)+1):
|
||
upperchar = c
|
||
elif (row_index[:total_rows(use_lowercases, use_uppercases, use_decdigits, use_whitespace, use_punct)].find(c)+1):
|
||
lowerchar = c
|
||
|
||
#if selection completes a pair, find choice, add to output, reset selection
|
||
if len(upperchar)&len(lowerchar):
|
||
#if selected pair chooses 'Done' or 'Cancel'
|
||
if((upperchar==col_index[0])&(lowerchar==row_index[len(grid)+1])):
|
||
#on 'Ignore', do nothing
|
||
pass
|
||
# :Space :Tab :Enter
|
||
elif((upperchar==col_index[0])&(lowerchar==row_index[len(grid)])):
|
||
out += ' ' #on 'Space'
|
||
elif((upperchar==col_index[1])&(lowerchar==row_index[len(grid)])):
|
||
out += ' ' #on 'Tab'
|
||
elif((upperchar==col_index[2])&(lowerchar==row_index[len(grid)])):
|
||
out += '\n' #on 'Enter'
|
||
# elif((upperchar==col_index[2])&(lowerchar==row_index[len(grid)])):
|
||
# out = out[:len(out)-1] #on 'Delete'
|
||
else:
|
||
rowchoice = row_index.find(lowerchar)
|
||
if rowchoice < len(grid):
|
||
if (grid[rowchoice].find("A") == 0):
|
||
out += uppercase[col_index.find(upperchar)]
|
||
if (grid[rowchoice].find("a") == 0):
|
||
out += lowercase[col_index.find(upperchar)]
|
||
if (grid[rowchoice].find("0") == 0)&(spaceout(col_index).find(upperchar) < len(numbers)):
|
||
out += numbers[spaceout(col_index).find(upperchar)]
|
||
if (grid[rowchoice].find("!") == 0):
|
||
out += punct[spaceout(col_index).find(upperchar)]
|
||
if (grid[rowchoice].find("_") == 0)&(spaceout(col_index).find(upperchar) < len(punct2)):
|
||
out += punct2[spaceout(col_index).find(upperchar)]
|
||
|
||
upperchar = ''
|
||
lowerchar = ''
|
||
cycle_index(cyclemethod)
|
||
|
||
single = args['single'] or ''
|
||
double = args['double'] or ''
|
||
repeat = args['repeat'] or ''
|
||
if single:
|
||
thing = ['-'+single, password]
|
||
sStdIn = None
|
||
elif double:
|
||
thing = ['--'+double, password]
|
||
sStdIn = None
|
||
elif repeat:
|
||
thing = []
|
||
sStdIn = password +'\n' +password
|
||
else:
|
||
thing = []
|
||
sStdIn = password
|
||
|
||
if exit_code == 0 and sargs and password:
|
||
# sys.stderr.write('EXIT: ' + repr(password) + '\n')
|
||
if type(sargs) == str:
|
||
#? quote
|
||
largs = sargs.split(' ')
|
||
elif type(sargs) == list and len(sargs) == 1 and sargs[0].find(' ') > 0:
|
||
largs = sargs[0].strip().split()
|
||
elif type(sargs) == list and len(sargs) > 1:
|
||
largs = sargs
|
||
else:
|
||
raise RuntimeError('ERROR: not list or str type(sargs) ' + str(type(sargs) ))
|
||
|
||
if thing:
|
||
pos = args['pos']
|
||
if pos == -1:
|
||
largs.append(thing)
|
||
else:
|
||
largs.insert(pos, thing)
|
||
shell = False
|
||
executable = '' # largs[0] sargs = ' '.join(largs[1:])
|
||
(exit_code, password,) = lSubprocess(largs, shell, executable, sStdIn=sStdIn)
|
||
else:
|
||
debug('len(password)=%d' % len(password))
|
||
|
||
# del password, exit_code
|
||
stdscr = None
|
||
cycle_index(cyclemethod) #Don't start the program with unrandomized indexes
|
||
retval = [exit_code, password][:]
|
||
out = ''
|
||
exit_code = -1
|
||
|
||
return retval
|
||
|
||
# should be getpass.getpass compatible
|
||
def getpass(prompt="", stream=None, tty=None, main_args=None):
|
||
# what should stream be if curses should be a tty
|
||
if tty is not None:
|
||
pass
|
||
elif stream is not None:
|
||
tty = os.ttyname(stream.fileno())
|
||
elif 'GPG_TTY' in os.environ:
|
||
tty = os.environ['GPG_TTY']
|
||
else:
|
||
tty = os.ttyname(sys.stdin.fileno())
|
||
with open(tty, 'r') as fd:
|
||
assert fd.isatty(), "The input stream must be a tty: %r" % (tty,)
|
||
|
||
if not hasattr(sys, '_stderr'):
|
||
sys._stderr = sys.stderr
|
||
sys.stderr = StringIO()
|
||
|
||
password = ''
|
||
exit_code = -1
|
||
if main_args is None:
|
||
main_args = ['-H', prompt]
|
||
else:
|
||
main_args += ['-H', prompt]
|
||
try:
|
||
(exit_code, password,) = main(main_args)
|
||
info('EXIT: getpass ' + prompt +' ' + repr(password))
|
||
except KeyboardInterrupt:
|
||
info('EXIT: getpass KeyboardInterrupt')
|
||
|
||
if hasattr(sys, '_stderr'):
|
||
sys.stderr = sys._stderr
|
||
|
||
return password
|
||
|
||
def sDialogFget():
|
||
global oLAST_DIR
|
||
sBut, sCode = lDialogFselect(oLAST_DIR, height=10, width=50, help_button=False)
|
||
sOut = ''
|
||
if sBut == 'ok':
|
||
sFile = sCode
|
||
try:
|
||
with open( sFile, 'rt') as iFd:
|
||
sOut = iFd.read().strip()
|
||
except Exception as e:
|
||
# dunno
|
||
pass
|
||
else:
|
||
if os.path.isdir(sFile):
|
||
oLAST_DIR = sFile
|
||
else:
|
||
oLAST_DIR = os.path.dirname(sFile)
|
||
oLAST_DIR = oLAST_DIR + '/'
|
||
return sOut
|
||
|
||
def lDialogFselect(sLastDir=None, height=10, width=50, help_button=False, prompt=None):
|
||
global oDIALOG, oLAST_DIR
|
||
if oDIALOG is None:
|
||
oDIALOG = Dialog()
|
||
if not sLastDir:
|
||
sLastDir = oLAST_DIR
|
||
sBut, sCode = oDIALOG.fselect(sLastDir, height, width, help_button=False)
|
||
return (sBut, sCode,)
|
||
|
||
def lDialogCode(sText):
|
||
lChoices = [("<", "Begin - Add the text at the beginning"),
|
||
("=", "All - Replace all with the text"),
|
||
(">", "End - Add the text at the end")]
|
||
sCode = '='
|
||
sBut, sCode = oDIALOG.menu(sText, height=16, width=50,
|
||
choices=lChoices,
|
||
title="Replace the buffer with file contents",
|
||
help_button=False)
|
||
return (sBut, sCode,)
|
||
|
||
def lSubprocess(largs, shell=False, executable='', sStdIn=''):
|
||
|
||
env = os.environ.copy()
|
||
if 'PYTHONPATH' in env:
|
||
del env['PYTHONPATH']
|
||
debug('executable=' + repr(executable) +' largs=' + repr(largs) ) # + ' len(password)=%d' % len(password)
|
||
|
||
yStdoutData = stderrdata = ''
|
||
try:
|
||
oProcess = subprocess.Popen(largs,
|
||
stdin=subprocess.PIPE,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
env=env,
|
||
shell=shell)
|
||
sleep(3)
|
||
except Exception as e:
|
||
error('Popen executable=' + repr(executable) +' largs=' + repr(largs) + '\n' + str(e))
|
||
exit_code = 1
|
||
sStdOut = ''
|
||
else:
|
||
if sStdIn:
|
||
(yStdoutData, stderrdata,) = oProcess.communicate(sStdIn.encode())
|
||
else:
|
||
#? wait
|
||
(yStdoutData, stderrdata,) = oProcess.communicate()
|
||
#? oProcess.close()
|
||
if stderrdata:
|
||
sys.stderr.write(stderrdata.decode() + '\n')
|
||
exit_code = oProcess.returncode
|
||
sStdOut = yStdoutData.decode()
|
||
return (exit_code, sStdOut,)
|
||
|
||
def main():
|
||
sargs = sys.argv[1:]
|
||
if type(sargs) == list and len(sargs) == 1 and sargs[0].find(' ') > 0:
|
||
lSysArgv = sargs[0].strip().split()
|
||
else:
|
||
lSysArgv = sargs
|
||
# print('DEBUG: ' +repr(sys.argv[1:]) +' ' +repr(type(sys.argv[1:])))
|
||
if hasattr(sys, 'frozen') and hasattr(sys, '_MEIPASS'):
|
||
print('DEBUG: running in a PyInstaller bundle')
|
||
(exit_code, password,) = iMain(lSysArgv)
|
||
iOUT_FD.write(password+('\n'*newline))
|
||
password = ''
|
||
sys.stderr.flush()
|
||
return exit_code
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|