def test_catlessPassword(self): 'Saving and loading password without category' tree = self.pdb.getTree() pwd = FigaroPassword(title='CLHost', password='******') tree.addNode(pwd) self.pdb.save(fname='fpm.saved') self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') tlnodes = self.pdb.getTree().getNodes() self.assertEqual(len(tlnodes), 1) self.assertEqual(tlnodes[0].title, 'CLHost') self.assertEqual(tlnodes[0]['password'], 'CLPass') self.assertEqual(tlnodes[0]['url'], '')
def test_tooLongPassword(self): pwd = FigaroPassword() longpswd = "1234567890" * 3 self.assertRaises(FigaroPasswordTooLongError, pwd.__setitem__, 'password', longpswd) self.assertRaises(FigaroPasswordTooLongError, pwd.update, {'password': longpswd}) pwd.store_long_password = 1 pwd['title'] = "Long password" pwd['password'] = longpswd self.pdb.getTree()['Test'].addNode(pwd) self.pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') saved_pwd = self.pdb.getTree()['Test'].locate('Long password')[0] self.assertEqual(saved_pwd['password'], longpswd)
def openDatabase(self): self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) dlg = LoginDialog(pdb=self.pdb) password = dlg['password'] try: res = dlg.run() if res != Gtk.ResponseType.OK: print("Good bye.") sys.exit(1) except DatabaseNotExist: dlg.destroyDialog() newpass = self.createNewDatabase() if newpass is None: sys.exit(1) self.pdb.open(newpass)
def openDatabase(self): ''' Open database amd prompt for password if necessary ''' self.verbose = self.conf.options['verbose'] self.force_single = self.conf.options['force-single'] self.confirm_deletes = self.conf.options['confirm-deletes'] self.pdb = PDBFigaro(filename = expanduser(self.conf.options['fpm-database'])) password = "" self.printMessage(_("Ked Password Manager (version %s)"), __version__) while 1: try: self.pdb.open(password) break except WrongPassword: if password: print _("Error! Wrong password.") else: print _("Provide password to access the database (Ctrl-C to exit)") password = getpass(_("Password: "******"Password accepted.")) print
class Application(object, Frontend): pdb = None wnd_main = None def openDatabase(self): self.pdb = PDBFigaro(filename = expanduser(self.conf.options['fpm-database'])) dlg = LoginDialog(pdb = self.pdb) password = dlg['password'] try: res = dlg.run() if res != gtk.RESPONSE_OK: print "Good bye." sys.exit(1) except DatabaseNotExist: dlg.destroyDialog() newpass = self.createNewDatabase() if newpass is None: sys.exit(1) self.pdb.open(newpass) def createNewDatabase(self): """Create new password database and return password for created database""" dlg = NewDatabaseDialog() newpass = dlg.run() self.pdb.create(newpass, expanduser(self.conf.options['fpm-database'])) return newpass def mainLoop(self): globals.app = self # Make application instance available to all modules self.wnd_main = MainWindow() gtk.main() def showMessage(self, message): dialog = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, message); dialog.run(); dialog.destroy(); def _run(self): globals.app = self # Make application instance available to all modules self.openDatabase() self.wnd_main = MainWindow() gtk.main()
class Application(object, Frontend): pdb = None wnd_main = None def openDatabase(self): self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) dlg = LoginDialog(pdb=self.pdb) password = dlg['password'] try: res = dlg.run() if res != gtk.RESPONSE_OK: print "Good bye." sys.exit(1) except DatabaseNotExist: dlg.destroyDialog() newpass = self.createNewDatabase() if newpass is None: sys.exit(1) self.pdb.open(newpass) def createNewDatabase(self): """Create new password database and return password for created database""" dlg = NewDatabaseDialog() newpass = dlg.run() self.pdb.create(newpass, expanduser(self.conf.options['fpm-database'])) return newpass def mainLoop(self): globals.app = self # Make application instance available to all modules self.wnd_main = MainWindow() gtk.main() def showMessage(self, message): dialog = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, message) dialog.run() dialog.destroy() def _run(self): globals.app = self # Make application instance available to all modules self.openDatabase() self.wnd_main = MainWindow() gtk.main()
class Application(Frontend): pdb = None wnd_main = None def openDatabase(self): self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) dlg = LoginDialog(pdb=self.pdb) password = dlg['password'] try: res = dlg.run() if res != Gtk.ResponseType.OK: print("Good bye.") sys.exit(1) except DatabaseNotExist: dlg.destroyDialog() newpass = self.createNewDatabase() if newpass is None: sys.exit(1) self.pdb.open(newpass) def createNewDatabase(self): """Create new password database and return password for created database""" dlg = NewDatabaseDialog() newpass = dlg.run() self.pdb.create(newpass, expanduser(self.conf.options['fpm-database'])) return newpass def mainLoop(self): #globals.app = self # Make application instance available to all modules self.wnd_main = MainWindow(self) Gtk.main() def showMessage(self, message): dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.CLOSE, message) dialog.run() dialog.destroy() def _run(self): globals.app = self # Make application instance available to all modules self.openDatabase() self.wnd_main = MainWindow() Gtk.main()
def openDatabase(self): ''' Open database amd prompt for password if nesessary ''' self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) password = "" print "Ked Password Manager (version %s)" % __version__ while 1: try: self.pdb.open(password) break except WrongPassword: if password: print "Error! Wrong password." else: print "Provide password to access the database (Ctrl-C to exit)" password = getpass("Password: "******"Password accepted." print
def openDatabase(self): self.pdb = PDBFigaro(filename = expanduser(self.conf.options['fpm-database'])) dlg = LoginDialog(pdb = self.pdb) password = dlg['password'] try: res = dlg.run() if res != gtk.RESPONSE_OK: print "Good bye." sys.exit(1) except DatabaseNotExist: dlg.destroyDialog() newpass = self.createNewDatabase() if newpass is None: sys.exit(1) self.pdb.open(newpass)
def test_tooLongPassword(self): pwd = FigaroPassword() longpswd = "1234567890"*3; self.assertRaises(FigaroPasswordTooLongError, pwd.__setitem__, 'password', longpswd) self.assertRaises(FigaroPasswordTooLongError, pwd.update, {'password': longpswd}) pwd.store_long_password = 1 pwd['title'] = "Long password" pwd['password'] = longpswd self.pdb.getTree()['Test'].addNode(pwd) self.pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') saved_pwd = self.pdb.getTree()['Test'].locate('Long password')[0] self.assertEqual(saved_pwd['password'], longpswd)
def test_unrotate(self): pdb = PDBFigaro() unrotated = pdb._unrotate('FIGARO\x00\xe3') self.assertEqual(unrotated, 'FIGARO')
def setUp(self): self.pdb = PDBFigaro() self.pdb.open(self.password, fname='test/fpm.sample')
class PDBFigaroTestCase(unittest.TestCase): password = '******' def setUp(self): self.pdb = PDBFigaro() self.pdb.open(self.password, fname='test/fpm.sample') def test_create(self): assert isinstance(self.pdb, PDBFigaro) def test_tree(self): 'Tree should be formed correctly' ptree = self.pdb.getTree() branches = ptree.getBranches() bkeys = branches.keys() bkeys.sort() self.assertEqual(bkeys, ['Kedder', 'Test']) tree_test = ptree['Test'] assert tree_test self.assertEqual(len(tree_test.getNodes()), 2) tree_kedder = ptree['Kedder'] assert tree_kedder self.assertEqual(len(tree_kedder.getNodes()), 1) def test_fpmCompatibilty(self): ptree = self.pdb.getTree() pwd = ptree['Test'].locate('test2')[0] self.assertEqual(pwd.default, 1) pwd = ptree['Test'].locate('test1')[0] self.assertEqual(pwd.default, 0) pwd = ptree['Kedder'].locate('kedder1')[0] self.assertEqual(pwd.launcher, 'ssh') def test_decryption(self): 'Test how passwords are decrypted' tree_test = self.pdb.getTree()['Test'] pwds = tree_test.locate('url') self.assertEqual(len(pwds), 2) pwd_test2 = tree_test.locate('test2') self.assertEqual(pwd_test2[0].password, 'test2 password') def test_encriptionUtils(self): str = "FIGARO ENCRYPTION TEST" noised = self.pdb._addNoise(str) self.assertEqual(len(noised) % Blowfish.block_size, 0) rotated = self.pdb._rotate(noised) self.assertEqual(len(rotated) % Blowfish.block_size, 0) hex = self.pdb._bin_to_hex(rotated) bin = self.pdb._hex_to_bin(hex) self.assertEqual(bin, rotated) unrotated = self.pdb._unrotate(bin) self.assertEqual(str, unrotated) def test_encription(self): strings = ["FIGARO ENCRYPTION TEST", "small"] for str in strings: encrypted = self.pdb.encrypt(str) decrypted = self.pdb.decrypt(encrypted) self.assertEqual(str, decrypted) def test_passwordEncryption(self): 'Encrypted password should be 48 character long' pwdstr = 'shortpass' encrypted = self.pdb.encrypt(pwdstr, 1) self.assertEqual(len(encrypted), FPM_PASSWORD_LEN * 2) decrypted = self.pdb.decrypt(encrypted) self.assertEqual(pwdstr, decrypted) def test_native(self): self.assertEqual(self.pdb.native, 0) def test_versions(self): self.assertEqual(self.pdb.FULL_VERSION, '00.53.00') self.assertEqual(self.pdb.MIN_VERSION, '00.50.00') self.assertEqual(self.pdb.DISPLAY_VERSION, '0.53')
#! /usr/bin/python2 from kedpm.plugins.pdb_figaro import PDBFigaro, FigaroPassword import getpass import sys import os import json f = os.path.join(os.environ["HOME"],".fpm/fpm") if len(sys.argv) > 1: f = sys.argv[1] db = PDBFigaro(filename=f) pw = getpass.getpass('Please give password : '******'title']] = { 'user': n['user'], 'password' : n['password'], 'url' : n['url'], 'notes' : n['notes']} branches = t.getBranches() folders = {}
def setUp(self): pdb = PDBFigaro() pdb.open(self.password, fname='test/fpm.sample') pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved')
class SavedFigaroTestCase(PDBFigaroTestCase): def setUp(self): pdb = PDBFigaro() pdb.open(self.password, fname='test/fpm.sample') pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') def tearDown(self): os.remove('fpm.saved') def test_catlessPassword(self): 'Saving and loading password without category' tree = self.pdb.getTree() pwd = FigaroPassword(title='CLHost', password='******') tree.addNode(pwd) self.pdb.save(fname='fpm.saved') self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') tlnodes = self.pdb.getTree().getNodes() self.assertEqual(len(tlnodes), 1) self.assertEqual(tlnodes[0].title, 'CLHost') self.assertEqual(tlnodes[0]['password'], 'CLPass') self.assertEqual(tlnodes[0]['url'], '') def test_native(self): self.assertEqual(self.pdb.native, 1) def test_tooLongPassword(self): pwd = FigaroPassword() longpswd = "1234567890" * 3 self.assertRaises(FigaroPasswordTooLongError, pwd.__setitem__, 'password', longpswd) self.assertRaises(FigaroPasswordTooLongError, pwd.update, {'password': longpswd}) pwd.store_long_password = 1 pwd['title'] = "Long password" pwd['password'] = longpswd self.pdb.getTree()['Test'].addNode(pwd) self.pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') saved_pwd = self.pdb.getTree()['Test'].locate('Long password')[0] self.assertEqual(saved_pwd['password'], longpswd)
class Application(Cmd, Frontend): PS1 = "kedpm:%s> " # prompt template cwd = [] # Current working directory intro = """Ked Password Manager is ready for operation. try 'help' for brief description of available commands """ modified = 0 histfile = os.getenv("HOME") + '/.kedpm/history' def __init__(self): Cmd.__init__(self) if sys.stdout.isatty(): self.PS1 = "\x1b[1m" + self.PS1 + "\x1b[0m" # colored prompt template def openDatabase(self): ''' Open database amd prompt for password if nesessary ''' self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) password = "" print "Ked Password Manager (version %s)" % __version__ while 1: try: self.pdb.open(password) break except WrongPassword: if password: print "Error! Wrong password." else: print "Provide password to access the database (Ctrl-C to exit)" password = getpass("Password: "******"Password accepted." print def createNewDatabase(self): 'Create new password database and return password for created database' print "Creating new password database." pass1 = pass2 = "" while pass1 != pass2 or pass1 == "": pass1 = getpass("Provide password: "******"Repeat password: "******"Empty passwords are really insecure. You shoud create one." if pass1 != pass2: print "Passwords don't match! Please repeat." self.pdb.create(pass1) return pass1 def updatePrompt(self): self.prompt = self.PS1 % ('/' + '/'.join(self.cwd)) def getCwd(self): 'Return current working password tree instance' return self.pdb.getTree().getTreeFromPath(self.cwd) def listPasswords(self, passwords, show_numbers=0): '''display given passwords in nicely formed table''' # we assume that all passwords in list have the same fields # the same as in first one if not passwords: return prot = passwords[0] lengths = show_numbers and {'nr': 3} or {} headers = show_numbers and ['Nr'] or [] fstr = show_numbers and "%%%ds " or "" listable = prot.getFieldsOfType([password.TYPE_STRING]) # determine maximum space needed by each column for fld in listable: ftitle = prot.getFieldTitle(fld) lengths[fld] = len(ftitle) fstr = fstr + "%%%ds " headers.append(ftitle) ptuples = [] num = 1 for pwd in passwords: ptup = [] if show_numbers: ptup.append("%d)" % num) for fld in listable: ptup.append(getattr(pwd, fld)) newlen = len(getattr(pwd, fld)) if newlen > lengths[fld]: lengths[fld] = newlen ptuples.append(tuple(ptup)) num = num + 1 # form format string if show_numbers: listable = ['nr'] + listable fstr = fstr % tuple([lengths[x] + 1 for x in listable]) print fstr % tuple(headers) print fstr % tuple(["=" * lengths[x] for x in listable]) for ptup in ptuples: print fstr % ptup def pickPassword(self, regexp, tree=None): '''Prompt user to pick one password from given password tree. Password tree, provided by "tree" argument filtered using "regexp". If resulted list contains only one password, return it without prompting. If no passwords were located, or user desides to cancel operation, return None''' if tree is None: tree = self.getCwd() passwords = tree.locate(regexp) if not passwords: print "No passwords matching \"%s\" were found" % regexp return None if len(passwords) > 1: self.listPasswords(passwords, 1) print "Enter number. Enter 0 to cancel." try: showstr = raw_input('show: ') except (KeyboardInterrupt, EOFError): # user has cancelled selection showstr = "0" try: shownr = int(showstr) if not shownr: return None selected_password = passwords[shownr - 1] except (ValueError, IndexError): return None else: selected_password = passwords[0] return selected_password def inputString(self, prompt): '''Input string from user''' input = raw_input(prompt) return input def inputText(self, prompt): '''Input long text from user''' return self.inputString(prompt) def inputPassword(self, prompt): '''Input long text from user''' pwd = None while pwd is None: pass1 = getpass(prompt) if pass1 == '': return '' pass2 = getpass('Repeat: ') if pass1 == pass2: pwd = pass1 else: print "Passwords don't match. Try again." return pwd def editPassword(self, pwd): '''Prompt user for each field of the password. Respect fields' type.''' input = {} for field, fieldinfo in pwd.fields_type_info: field_type = fieldinfo['type'] new_value = "" if field_type == password.TYPE_STRING: new_value = self.inputString( "Enter %s (\"%s\"): " % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_TEXT: new_value = self.inputText( "Enter %s (\"%s\"): " % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_PASSWORD: new_value = self.inputPassword("Enter %s: " % pwd.getFieldTitle(field)) else: print """Error. Type %s is unsupported yet. This field will retain an old value.""" % field_type if new_value != "": input[field] = new_value try: pwd.update(input) except FigaroPasswordTooLongError: print "WARNING! Your password is too long for Figaro Password Manager." print "Figaro Password Manager can handle only passwords shorter than 24 characters." print """However, KedPM can store this password for you, but it will break fpm compatibility. fpm will not be able to handle such long password correctly.""" answer = raw_input( "Do you still want to save your password? [Y/n]: ") if answer.lower().startswith('n'): raise KeyboardInterrupt pwd.store_long_password = 1 pwd.update(input) #return pwd def tryToSave(self): self.modified = 1 savemode = self.conf.options["save-mode"] if savemode == 'no': return answer = 'y' if self.conf.options["save-mode"] == "ask": answer = raw_input( "Database was modified. Do you want to save it now? [Y/n]: ") if answer == '' or answer.lower().startswith('y'): self.do_save('') def complete_dirs(self, text, line, begidx, endidx): completing = line[:endidx].split(' ')[-1] base = completing[:completing.rfind('/') + 1] abspath = self.getAbsolutePath(base) dirs = self.pdb.getTree().getTreeFromPath(abspath).getBranches() compl = [] for dir in dirs.keys(): if dir.startswith(text): compl.append(dir + '/') return compl def getEditorInput(self, content=''): """Fire up default editor and read user input from temporary file""" default_editor = "vi" if os.environ.has_key('VISUAL'): editor = os.environ['VISUAL'] elif os.environ.has_key('EDITOR'): editor = os.environ['EDITOR'] else: editor = default_editor print "running %s" % editor # create temporary file handle, tmpfname = tempfile.mkstemp(prefix="kedpm_") tmpfile = open(tmpfname, 'w') tmpfile.write(content) tmpfile.close() os.system(editor + " " + tmpfname) tmpfile = open(tmpfname, 'r') text = tmpfile.read() tmpfile.close() os.remove(tmpfname) return text def getAbsolutePath(self, arg): """Return absolute path from potentially relative (cat)""" root = self.pdb.getTree() if not arg: return self.cwd if (arg[0] == '/'): path = root.normalizePath(arg.split('/')) else: path = root.normalizePath(self.cwd + arg.split('/')) return path def getTreeFromRelativePath(self, path): """Get tree object from given relative path and current working directory""" root = self.pdb.getTree() abspath = self.getAbsolutePath(path) return root.getTreeFromPath(abspath) ########################################## # Command implementations below. # def emptyline(self): pass def do_exit(self, arg): '''Quit KED Password Manager''' readline.write_history_file(self.histfile) if self.modified: self.tryToSave() print "Exiting." sys.exit(0) def do_EOF(self, arg): '''The same as 'exit' command''' print self.do_exit(arg) def do_help(self, arg): '''Print help message''' Cmd.do_help(self, arg) def do_ls(self, arg): '''List available catalogs and passwords Syntax: ls [<category>] ''' try: tree = self.getTreeFromRelativePath(arg) except KeyError: print "ls: %s: No such catalog" % arg return print "=== Directories ===" for bname in tree.getBranches().keys(): print bname + "/" print "==== Passwords ====" self.listPasswords(tree.getNodes()) def complete_ls(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_cd(self, arg): '''change directory (category) Syntax: cd <category> ''' root = self.pdb.getTree() cdpath = self.getAbsolutePath(arg) try: newpath = root.getTreeFromPath(cdpath) except KeyError: print "cd: %s: No such catalog" % arg else: self.cwd = cdpath self.updatePrompt() def complete_cd(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_pwd(self, arg): '''print name of current/working directory''' print '/' + '/'.join(self.cwd) def do_show(self, arg): '''display password information. Syntax: show [-r] <regexp> -r - recursive search. search all subtree for matching passwords This will display contents of a password item in current category or whole subtree, if -r flag was specified. If several items matched by <regexp>, list of them will be printed and you will be prompted to enter a number, pointing to password you want to look at. After receiving that number, KedPM will show you the password.''' argv = arg.split() tree = None if argv and argv[0] == '-r': tree = self.getCwd().flatten() if len(argv) > 1: arg = " ".join(argv[1:]) else: arg = "" selected_password = self.pickPassword(arg, tree) if selected_password: print "---------------------------------------" print selected_password.asText() print "---------------------------------------" def do_edit(self, arg): '''edit password information. Syntax: edit <regexp> This will prompt you for editing of a password item in current category. If several items matched by <regexp>, list of them will be printed and you will be prompted to enter a number, pointing to password you want to edit. After receiving that number, you will be able to edit picked password. ''' selected_password = self.pickPassword(arg) if selected_password: try: self.editPassword(selected_password) #self.modified = 1 self.tryToSave() except (KeyboardInterrupt, EOFError): print "Cancelled" else: print "No password selected" def do_new(self, arg): '''Add new password to current category. You will be prompted to enter fields. Syntax: new [-p] -p - Get properties by parsing provided text. Will open default text editor for you to paste text in. ''' new_pass = FigaroPassword( ) # FIXME: Password type shouldn't be hardcoded. argv = arg.split() if "-p" in argv: text = self.getEditorInput() choosendict = parser.parseMessage(text, self.conf.patterns) new_pass.update(choosendict) try: self.editPassword(new_pass) except (KeyboardInterrupt, EOFError): print "Cancelled" else: tree = self.getCwd() tree.addNode(new_pass) self.tryToSave() def do_save(self, arg): '''Save current password tree''' sys.stdout.write("Saving...") sys.stdout.flush() self.pdb.save() print "OK" self.modified = 0 def do_mkdir(self, arg): '''create new category (directory) Syntax: mkdir <category> Creates new password category in current one. ''' if not arg: print "mkdir: too few arguments" print "try 'help mkdir' for more information" return pwd = self.getCwd() pwd.addBranch(arg.strip()) def do_rename(self, arg): '''rename category Syntax: rename <category> <new_name> ''' args = arg.split() if len(args) != 2: print '''Syntax: rename <category> <new_name> ''' return oldname = args[0] newname = args[1] try: self.pdb.getTree().renameBranch(self.cwd + [oldname], newname) except RenameError: print "rename: category %s already exists" % newname return except KeyError: print "rename: %s: no such category" % oldname return self.tryToSave() def complete_rename(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_help(self, arg): """Print help topic""" argv = arg.split() if argv and argv[0] in ['set']: # Provide extended help help_def = getattr(self, "help_" + argv[0]) if help_def: help_def(' '.join(argv[1:])) else: Cmd.do_help(self, arg) else: Cmd.do_help(self, arg) def do_set(self, arg): """Set KedPM options Syntax: set -- show all options set <option> -- show value of option set <option> = <value> -- set value to option for boolean values 1, 'on' or 'true' are considered as True; 0, 'off' or 'false' are considered as False. enter help set <option> for more info on particular option.""" opts = self.conf.options if not arg: # show all options for opt, value in opts.items(): print "%s = %s" % (opt, value) return tokens = arg.split('=') opt_name = tokens[0] try: opt_value = opts[opt_name] except KeyError: print "set: no such option: %s" % arg return if len(tokens) == 1: # show value of option print "%s = %s" % (opt_name, opt_value) else: # set the value try: opts[opt_name] = ' '.join(tokens[1:]) except OptionError, e: print "set: %s" % e # save confuguration file self.conf.save()
class Application (Cmd, Frontend): PS1 = "kedpm:%s> " # prompt template cwd = [] # Current working directory intro = _("""Ked Password Manager is ready for operation. try 'help' for brief description of available commands. """) modified = 0 histfile = os.path.join(expanduser('~'), '.kedpm', 'history') def __init__(self): Cmd.__init__(self) # This method of setting colored prompt doesn't work on windows. We # need to figure out how to put color in cross-platform way and # re-enable it here. #if sys.stdout.isatty(): # self.PS1 = "\x1b[1m"+self.PS1+"\x1b[0m" # colored prompt template def printMessage(self, message, *vars): if self.verbose: print (message) % vars def openDatabase(self): ''' Open database amd prompt for password if necessary ''' self.verbose = self.conf.options['verbose'] self.force_single = self.conf.options['force-single'] self.confirm_deletes = self.conf.options['confirm-deletes'] self.pdb = PDBFigaro(filename = expanduser(self.conf.options['fpm-database'])) password = "" self.printMessage(_("Ked Password Manager (version %s)"), __version__) while 1: try: self.pdb.open(password) break except WrongPassword: if password: print _("Error! Wrong password.") else: print _("Provide password to access the database (Ctrl-C to exit)") password = getpass(_("Password: "******"Password accepted.")) print def createNewDatabase(self): 'Create new password database and return password for created database' print _("Creating new password database.") pass1 = pass2 = "" while pass1 != pass2 or pass1 == "": pass1 = getpass(_("Provide password: "******"Repeat password: "******"Empty passwords are really insecure. You should " \ "create one.") if pass1!=pass2: print _("Passwords don't match! Please repeat.") self.pdb.create(pass1) return pass1 def updatePrompt(self): self.prompt = self.PS1 % ('/'+'/'.join(self.cwd)) def getCwd(self): 'Return current working password tree instance' return self.pdb.getTree().getTreeFromPath(self.cwd) def listPasswords(self, passwords, show_numbers=0): '''display given passwords in nicely formed table''' # we assume that all passwords in list have the same fields # the same as in first one if not passwords: return prot = passwords[0] lengths = show_numbers and {'nr': 3} or {} headers = show_numbers and ['Nr'] or [] fstr = show_numbers and "%%%ds " or "" listable = prot.getFieldsOfType([password.TYPE_STRING]) # determine maximum space needed by each column for fld in listable: ftitle = prot.getFieldTitle(fld) lengths[fld] = len(ftitle) fstr = fstr + "%%%ds " headers.append(ftitle) ptuples = [] num = 1 for pwd in passwords: ptup = [] if show_numbers: ptup.append("%d)" %num) for fld in listable: ptup.append(getattr(pwd, fld)) newlen = len(getattr(pwd, fld)) if newlen > lengths[fld]: lengths[fld] = newlen ptuples.append(tuple(ptup)) num = num + 1 # form format string if show_numbers: listable = ['nr'] + listable fstr = fstr % tuple([lengths[x]+1 for x in listable]) print fstr % tuple(headers) print fstr % tuple(["="*lengths[x]for x in listable]) for ptup in ptuples: print fstr % ptup def filterPasswords(self, regexp, tree = None): '''Returns a list of passwords, filtered by REGEXP''' if tree is None: tree = self.getCwd() return(tree.locate(regexp)) def getPasswords(self, regexp, tree = None): '''Returns a list of passwords, filtered by REGEXP. Calls pickPassword if program has been configured to force single selection''' if self.force_single: return [self.pickPassword(regexp, tree)] else: return(self.filterPasswords(regexp, tree)) def pickPassword(self, regexp, tree = None): '''Prompt user to pick one password from given password tree. Password tree, provided by "tree" argument filtered using "regexp". If resulted list contains only one password, return it without prompting. If no passwords were located, or user desides to cancel operation, return None''' passwords = self.filterPasswords(regexp, tree) if not passwords: self.printMessage(_("No passwords matching \"%s\" were found"), regexp) return None if len(passwords) > 1: self.listPasswords(passwords, 1) print _("Enter number. Enter 0 to cancel.") try: showstr = raw_input(_('show: ')) except (KeyboardInterrupt, EOFError): # user has cancelled selection showstr = "0" try: shownr = int(showstr) if not shownr: return None selected_password = passwords[shownr-1] except (ValueError, IndexError): return None else: selected_password = passwords[0] return selected_password def inputString(self, prompt): '''Input string from user''' input = raw_input(prompt) return input def inputText(self, prompt): '''Input long text from user''' return self.inputString(prompt) def inputPassword(self, prompt): '''Input long text from user''' pwd = None while pwd is None: pass1 = getpass(prompt) if pass1=='': return '' pass2 = getpass('Repeat: ') if pass1==pass2: pwd = pass1 else: print _("Passwords don't match. Try again.") return pwd def editPassword(self, pwd): '''Prompt user for each field of the password. Respect fields\' type.''' input = {} for field, fieldinfo in pwd.fields_type_info: field_type = fieldinfo['type'] new_value = "" if field_type == password.TYPE_STRING: new_value = self.inputString(_("Enter %s (\"%s\"): ") % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_TEXT: new_value = self.inputText(_("Enter %s (\"%s\"): ") % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_PASSWORD: new_value = self.inputPassword(_("Enter %s: ") % pwd.getFieldTitle(field)) else: print _("Error. Type %s is unsupported yet. " \ "This field will retain an old value.") % field_type if new_value!="": input[field] = new_value try: pwd.update(input) except FigaroPasswordTooLongError: print _("WARNING! Your password is too long for Figaro Password Manager.") print _("Figaro Password Manager can handle only passwords shorter than 24 characters.") print _("""However, KedPM can store this password for you, but it will break fpm compatibility. fpm will not be able to handle such long password correctly.""") answer = raw_input(_("Do you still want to save your password? [Y/n]: ")) if answer.lower().startswith('n'): raise KeyboardInterrupt pwd.store_long_password = 1 pwd.update(input) #return pwd def tryToSave(self): self.modified = 1 savemode = self.conf.options["save-mode"] if savemode == 'no': return answer = 'y' if self.conf.options["save-mode"] == "ask": answer = raw_input(_("Database was modified. Do you want to save it now? [Y/n]: ")) if answer=='' or answer.lower().startswith('y'): self.do_save('') def complete_dirs(self, text, line, begidx, endidx): completing = line[:endidx].split(' ')[-1] base = completing[:completing.rfind('/')+1] abspath = self.getAbsolutePath(base) dirs = self.pdb.getTree().getTreeFromPath(abspath).getBranches() compl = [] for dir in dirs.keys(): if dir.startswith(text): compl.append(dir+'/') return compl def getEditorInput(self, content=''): """Fire up an editor and read user input from temporary file""" for name in ('VISUAL', 'EDITOR'): editor = os.environ.get(name) if editor: break else: if editor is None: editor = 'vi' self.printMessage(_("running %s"), editor) # create temporary file handle, tmpfname = tempfile.mkstemp(prefix="kedpm_") tmpfile = open(tmpfname, 'w') tmpfile.write(content) tmpfile.close() os.system(editor + " " + tmpfname) tmpfile = open(tmpfname, 'r') text = tmpfile.read() tmpfile.close() os.remove(tmpfname) return text def getAbsolutePath(self, arg): """Return absolute path from potentially relative (cat)""" root = self.pdb.getTree() if not arg: return self.cwd if(arg[0] == '/'): path = root.normalizePath(arg.split('/')) else: path = root.normalizePath(self.cwd + arg.split('/')) return path def getTreeFromRelativePath(self, path): """Get tree object from given relative path and current working directory""" root = self.pdb.getTree() abspath = self.getAbsolutePath(path) return root.getTreeFromPath(abspath) def shiftArgv(self, argv, count = 1): if len(argv) > count: arg = " ".join(argv[count:]) else: arg = "" return arg ########################################## # Command implementations below. # def emptyline(self): pass def do_exit(self, arg): '''Quit KED Password Manager''' readline.write_history_file(self.histfile) if self.modified: self.tryToSave() self.printMessage(_("Exiting.")) sys.exit(0) def do_EOF(self, arg): '''The same as 'exit' command''' print self.do_exit(arg) def do_quit(self, arg): '''The same as 'exit' command''' print self.do_exit(arg) def do_help(self, arg): '''Print help message''' Cmd.do_help(self, arg) def do_ls(self, arg): '''List available catalogs and passwords Syntax: ls [<category>] ''' try: tree = self.getTreeFromRelativePath(arg) except KeyError: print _("ls: %s: No such catalog") % arg return print _("=== Directories ===") for bname in tree.getBranches().keys(): print bname+"/" print _("==== Passwords ====") self.listPasswords(tree.getNodes()) def complete_ls(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_cd(self, arg): '''change directory (category) Syntax: cd <category> ''' root = self.pdb.getTree() cdpath = self.getAbsolutePath(arg) try: newpath = root.getTreeFromPath(cdpath) except KeyError: print _("cd: %s: No such catalog") % arg else: self.cwd = cdpath self.updatePrompt() def complete_cd(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_pwd(self, arg): '''print name of current/working directory''' print '/'+'/'.join(self.cwd) def do_show(self, arg): '''display password information. Syntax: show [-r] <regexp> -r - recursive search. search all subtree for matching passwords This will display contents of a password item in current category or whole subtree, if -r flag was specified. If several items matched by <regexp> and the program has been configured to prompt for a single entry, a list of them will be printed and you will be prompted to enter a number, pointing to password you want to look at. After receiving that number, KedPM will show you the password. Otherwise all matching entries will be displayed''' argv = arg.split() tree = None if argv and argv[0] == '-r': tree = self.getCwd().flatten() arg = self.shiftArgv(argv) selected_passwords = self.getPasswords(arg, tree) for record in selected_passwords: if record: print "---------------------------------------" for key, fieldinfo in record.fields_type_info: if record[key] == '': continue if key == 'password': # this is a password, hide it with colors hide = "\x1b[%02im\x1b[%02im" % (31, 41) reset = "\x1b[%02im" % 0 print "%s: %s" % (fieldinfo['title'], hide + record[key] + reset) else: print "%s: %s" % (fieldinfo['title'], record[key]) print "---------------------------------------" def do_edit(self, arg): '''edit password information. Syntax: edit [-p] <regexp> Prompts the user to edit a password item in the current category. If <regexp> matches multiple items, the list of matches will be printed and user is prompted to select one. If the optional '-p' flag is specified, the password will be edited using the the editor specified in the VISUAL or EDITOR environment variables, defaulting to "vi" if neither is found. Otherwise the user is prompted to edit each entry of the password entry on the command line. ''' argv = arg.split() if argv and argv[0] == '-p': use_editor = True arg = self.shiftArgv(argv) else: use_editor = False selected_password = self.pickPassword(arg) if selected_password: try: if use_editor: text = self.getEditorInput(selected_password.asEditText()) patterns = [selected_password.getEditPattern()] chosendict = parser.parseMessage(text, patterns) selected_password.update(chosendict) else: self.editPassword(selected_password) self.tryToSave() except (KeyboardInterrupt, EOFError): self.printMessage(_("Cancelled")) else: self.printMessage(_("No password selected")) def do_new(self, arg): '''Add new password to current category. You will be prompted to enter fields. Syntax: new [-p | -t] -p - Get properties by parsing provided text. Will open default text editor for you to paste text in. Mutually exclusive with -t option. -t - Display editor template in default text editor. Mutually exclusive with -p option. ''' new_pass = FigaroPassword() # FIXME: Password type shouldn't be hardcoded. argv = arg.split() if "-p" in argv and "-t" in argv: print _("new: -p and -t arguments are mutually exclusive.") print _("try 'help new' for more information") elif "-p" in argv: text = self.getEditorInput() choosendict = parser.parseMessage(text, self.conf.patterns) new_pass.update(choosendict) elif "-t" in argv: text = self.getEditorInput(new_pass.asEditText()) choosendict = parser.parseMessage(text, [new_pass.getEditPattern()]) new_pass.update(choosendict) try: self.editPassword(new_pass) except (KeyboardInterrupt, EOFError): self.printMessage(_("Cancelled")) else: tree = self.getCwd() tree.addNode(new_pass) self.tryToSave() def do_import(self, arg): '''Imports new password records into current category. Syntax: import Get properties by parsing provided text. Will open default text editor for you to paste text in. ''' argv = arg.split() tree = self.getCwd() text = self.getEditorInput() for line in text.split("\n"): new_pass = FigaroPassword() # FIXME: Password type shouldn't be hardcoded. choosendict = parser.parseMessage(line, self.conf.patterns) new_pass.update(choosendict) tree.addNode(new_pass) self.tryToSave() def do_save(self, arg): '''Save current password tree''' sys.stdout.write(_("Saving...")) sys.stdout.flush() self.pdb.save() print "OK" self.modified = 0 def do_mkdir(self, arg): '''create new category (directory) Syntax: mkdir <category> Creates new password category in current one. ''' if not arg: print _("mkdir: too few arguments") print _("try 'help mkdir' for more information") return pwd = self.getCwd() pwd.addBranch(arg.strip()) def do_rename(self, arg): '''rename category Syntax: rename <category> <new_name> ''' args = arg.split() if len(args) != 2: print '''Syntax: rename <category> <new_name> ''' return oldname = args[0] newname = args[1] try: self.pdb.getTree().renameBranch(self.cwd+[oldname], newname) except RenameError: print _("rename: category %s already exists") % newname return except KeyError: print _("rename: %s: no such category") % oldname return self.tryToSave() def complete_rename(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_passwd(self, arg): """Change master password for opened database Syntax: password [new password] If new password is not provided with command, you will be promted to enter new one. """ if not arg: # Password is not provided with command. Ask user for it pass1 = getpass(_("New password: "******"Repeat password: "******"Empty passwords are really insecure. You should " \ "create one.") return if pass1!=pass2: print _("Passwords don't match! Please repeat.") return new_pass = pass1 else: new_pass = arg self.pdb.changePassword(new_pass) self.printMessage(_("Password changed.")) def do_help(self, arg): """Print help topic""" argv = arg.split() if argv and argv[0] in ['set']: # Provide extended help help_def = getattr(self, "help_"+argv[0]) if help_def: help_def(' '.join(argv[1:])) else: Cmd.do_help(self, arg) else: Cmd.do_help(self, arg) def do_set(self, arg): """Set KedPM options Syntax: set -- show all options set <option> -- show value of option set <option> = <value> -- set value to option For boolean values: 1, 'on' or 'true' are synonyms for True and 0, 'off' or 'false' are synonyms for False. Enter help set <option> for more info on a particular option. """ opts = self.conf.options if not arg: # show all options for opt, value in opts.items(): print "%s = %s" % (opt, value) return tokens = arg.split('=') opt_name = tokens[0].strip() try: opt_value = opts[opt_name] except KeyError: print _("set: no such option: [%s]") % arg return if len(tokens) == 1: # show value of option print "%s = %s" % (opt_name, opt_value) else: # set the value try: opt_value = ' '.join(tokens[1:]) opts[opt_name] = opt_value.strip() except OptionError, e: print "set: %s" % e # save confuguration file self.conf.save()
class PDBFigaroTestCase(unittest.TestCase): password = '******' def setUp(self): self.pdb = PDBFigaro() self.pdb.open(self.password, fname='test/fpm.sample') def test_create(self): assert isinstance(self.pdb, PDBFigaro) def test_tree(self): 'Tree should be formed correctly' ptree = self.pdb.getTree() branches = ptree.getBranches() bkeys = branches.keys() bkeys.sort() self.assertEqual(bkeys, ['Kedder', 'Test']) tree_test = ptree['Test'] assert tree_test self.assertEqual(len(tree_test.getNodes()), 2) tree_kedder = ptree['Kedder'] assert tree_kedder self.assertEqual(len(tree_kedder.getNodes()), 1) def test_fpmCompatibilty(self): ptree = self.pdb.getTree() pwd = ptree['Test'].locate('test2')[0] self.assertEqual(pwd.default, 1) pwd = ptree['Test'].locate('test1')[0] self.assertEqual(pwd.default, 0) pwd = ptree['Kedder'].locate('kedder1')[0] self.assertEqual(pwd.launcher, 'ssh') def test_decryption(self): 'Test how passwords are decrypted' tree_test = self.pdb.getTree()['Test'] pwds = tree_test.locate('url') self.assertEqual(len(pwds), 2) pwd_test2 = tree_test.locate('test2') self.assertEqual(pwd_test2[0].password, 'test2 password') def test_encriptionUtils(self): str = "FIGARO ENCRYPTION TEST" noised = self.pdb._addNoise(str) self.assertEqual(len(noised) % Blowfish.block_size, 0) rotated = self.pdb._rotate(noised) self.assertEqual(len(rotated) % Blowfish.block_size, 0) hex = self.pdb._bin_to_hex(rotated) bin = self.pdb._hex_to_bin(hex) self.assertEqual(bin, rotated) unrotated = self.pdb._unrotate(bin) self.assertEqual(str, unrotated) def test_encription(self): strings = ["FIGARO ENCRYPTION TEST", "small"] for str in strings: encrypted = self.pdb.encrypt(str) decrypted = self.pdb.decrypt(encrypted) self.assertEqual(str, decrypted) def test_passwordEncryption(self): 'Encrypted password should be 48 character long' pwdstr = 'shortpass' encrypted = self.pdb.encrypt(pwdstr, 1) self.assertEqual(len(encrypted), FPM_PASSWORD_LEN*2) decrypted = self.pdb.decrypt(encrypted) self.assertEqual(pwdstr, decrypted) def test_native(self): self.assertEqual(self.pdb.native, 0) def test_versions(self): self.assertEqual(self.pdb.FULL_VERSION, '00.53.00') self.assertEqual(self.pdb.MIN_VERSION, '00.50.00') self.assertEqual(self.pdb.DISPLAY_VERSION, '0.53') def test_changePassword(self): new_pwd = 'new_password' # Copy test database to new location shutil.copyfile('test/fpm.sample', 'test/fpm.passwdtest') try: self.pdb.open(self.password, fname='test/fpm.passwdtest') self.pdb.changePassword(new_pwd) # Try to open database with old password self.assertRaises(WrongPassword, self.pdb.open, self.password, fname='test/fpm.passwdtest') self.pdb.open(new_pwd, fname='test/fpm.passwdtest') finally: os.unlink('test/fpm.passwdtest')
class SavedFigaroTestCase(PDBFigaroTestCase): def setUp(self): pdb = PDBFigaro() pdb.open(self.password, fname='test/fpm.sample') pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') def tearDown(self): os.remove('fpm.saved') def test_catlessPassword(self): 'Saving and loading password without category' tree = self.pdb.getTree() pwd = FigaroPassword(title='CLHost', password='******') tree.addNode(pwd) self.pdb.save(fname='fpm.saved') self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') tlnodes = self.pdb.getTree().getNodes() self.assertEqual(len(tlnodes), 1) self.assertEqual(tlnodes[0].title, 'CLHost') self.assertEqual(tlnodes[0]['password'], 'CLPass') self.assertEqual(tlnodes[0]['url'], '') def test_native(self): self.assertEqual(self.pdb.native, 1) def test_tooLongPassword(self): pwd = FigaroPassword() longpswd = "1234567890"*3; self.assertRaises(FigaroPasswordTooLongError, pwd.__setitem__, 'password', longpswd) self.assertRaises(FigaroPasswordTooLongError, pwd.update, {'password': longpswd}) pwd.store_long_password = 1 pwd['title'] = "Long password" pwd['password'] = longpswd self.pdb.getTree()['Test'].addNode(pwd) self.pdb.save(fname="fpm.saved") self.pdb = PDBFigaro() self.pdb.open(self.password, fname='fpm.saved') saved_pwd = self.pdb.getTree()['Test'].locate('Long password')[0] self.assertEqual(saved_pwd['password'], longpswd)
class Application(Cmd, Frontend): PS1 = "kedpm:%s> " # prompt template cwd = [] # Current working directory intro = _("""Ked Password Manager is ready for operation. try 'help' for brief description of available commands. """) modified = 0 histfile = os.path.join(expanduser('~'), '.kedpm', 'history') # console supports escape sequences? use_console_escapes = False def __init__(self): Cmd.__init__(self) if (hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()): if hasattr(ctypes, 'windll'): # skip windows for now, but see # http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/ # with a possible solution here: http://pypi.python.org/pypi/colorama pass else: self.use_console_escapes = True if self.use_console_escapes: self.PS1 = "\x1b[1m" + self.PS1 + "\x1b[0m" # bold prompt def printMessage(self, message, *vars): if self.conf.options['verbose']: print(message) % vars def openDatabase(self): ''' Open database amd prompt for password if necessary ''' self.pdb = PDBFigaro( filename=expanduser(self.conf.options['fpm-database'])) password = "" self.printMessage(_("Ked Password Manager (version %s)"), __version__) while 1: try: self.pdb.open(password) break except WrongPassword: if password: print _("Error! Wrong password.") else: print _( "Provide password to access the database (Ctrl-C to exit)" ) password = getpass(_("Password: "******"Password accepted.")) print def createNewDatabase(self): 'Create new password database and return password for created database' print _("Creating new password database.") pass1 = pass2 = "" while pass1 != pass2 or pass1 == "": pass1 = getpass(_("Provide password: "******"Repeat password: "******"Empty passwords are really insecure. You should " \ "create one.") if pass1 != pass2: print _("Passwords don't match! Please repeat.") self.pdb.create(pass1) return pass1 def updatePrompt(self): self.prompt = self.PS1 % ('/' + '/'.join(self.cwd)) def getCwd(self): 'Return current working password tree instance' return self.pdb.getTree().getTreeFromPath(self.cwd) def listPasswords(self, passwords, show_numbers=0): '''display given passwords in nicely formed table''' # we assume that all passwords in list have the same fields # the same as in first one if not passwords: return prot = passwords[0] lengths = show_numbers and {'nr': 3} or {} headers = show_numbers and ['Nr'] or [] fstr = show_numbers and "%%%ds " or "" listable = prot.getFieldsOfType([password.TYPE_STRING]) # determine maximum space needed by each column for fld in listable: ftitle = prot.getFieldTitle(fld) lengths[fld] = len(ftitle) fstr = fstr + "%%%ds " headers.append(ftitle) ptuples = [] num = 1 for pwd in passwords: ptup = [] if show_numbers: ptup.append("%d)" % num) for fld in listable: ptup.append(getattr(pwd, fld)) newlen = len(getattr(pwd, fld)) if newlen > lengths[fld]: lengths[fld] = newlen ptuples.append(tuple(ptup)) num = num + 1 # form format string if show_numbers: listable = ['nr'] + listable fstr = fstr % tuple([lengths[x] + 1 for x in listable]) print fstr % tuple(headers) print fstr % tuple(["=" * lengths[x] for x in listable]) for ptup in ptuples: print fstr % ptup def filterPasswords(self, regexp, tree=None): '''Returns a list of passwords, filtered by REGEXP''' if tree is None: tree = self.getCwd() return (tree.locate(regexp)) def getPasswords(self, regexp, tree=None): '''Returns a list of passwords, filtered by REGEXP. Calls pickPassword if program has been configured to force single selection''' if self.conf.options['force-single']: return [self.pickPassword(regexp, tree)] else: return (self.filterPasswords(regexp, tree)) def pickPassword(self, regexp, tree=None): '''Prompt user to pick one password from given password tree. Password tree, provided by "tree" argument filtered using "regexp". If resulted list contains only one password, return it without prompting. If no passwords were located, or user desides to cancel operation, return None''' passwords = self.filterPasswords(regexp, tree) if not passwords: self.printMessage(_("No passwords matching \"%s\" were found"), regexp) return None if len(passwords) > 1: self.listPasswords(passwords, 1) print _("Enter number. Enter 0 to cancel.") try: showstr = raw_input(_('show: ')) except (KeyboardInterrupt, EOFError): # user has cancelled selection showstr = "0" try: shownr = int(showstr) if not shownr: return None selected_password = passwords[shownr - 1] except (ValueError, IndexError): return None else: selected_password = passwords[0] return selected_password def inputString(self, prompt): '''Input string from user''' input = raw_input(prompt) return input def inputText(self, prompt): '''Input long text from user''' return self.inputString(prompt) def inputPassword(self, prompt): '''Input long text from user''' pwd = None while pwd is None: pass1 = getpass(prompt) if pass1 == '': return '' pass2 = getpass('Repeat: ') if pass1 == pass2: pwd = pass1 else: print _("Passwords don't match. Try again.") return pwd def editPassword(self, pwd): '''Prompt user for each field of the password. Respect fields\' type.''' input = {} for field, fieldinfo in pwd.fields_type_info: field_type = fieldinfo['type'] new_value = "" if field_type == password.TYPE_STRING: new_value = self.inputString( _("Enter %s (\"%s\"): ") % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_TEXT: new_value = self.inputText( _("Enter %s (\"%s\"): ") % (pwd.getFieldTitle(field), pwd[field])) elif field_type == password.TYPE_PASSWORD: new_value = self.inputPassword( _("Enter %s: ") % pwd.getFieldTitle(field)) else: print _("Error. Type %s is unsupported yet. " \ "This field will retain an old value.") % field_type if new_value != "": input[field] = new_value try: pwd.update(input) except FigaroPasswordTooLongError: print _( "WARNING! Your password is too long for Figaro Password Manager." ) print _( "Figaro Password Manager can handle only passwords shorter than 24 characters." ) print _("""However, KedPM can store this password for you, but it will break fpm compatibility. fpm will not be able to handle such long password correctly.""") answer = raw_input( _("Do you still want to save your password? [Y/n]: ")) if answer.lower().startswith('n'): raise KeyboardInterrupt pwd.store_long_password = 1 pwd.update(input) #return pwd def tryToSave(self): self.modified = 1 savemode = self.conf.options["save-mode"] if savemode == 'no': return answer = 'y' if self.conf.options["save-mode"] == "ask": answer = raw_input( _("Database was modified. Do you want to save it now? [Y/n]: ") ) if answer == '' or answer.lower().startswith('y'): self.do_save('') def complete_dirs(self, text, line, begidx, endidx): completing = line[:endidx].split(' ')[-1] base = completing[:completing.rfind('/') + 1] abspath = self.getAbsolutePath(base) dirs = self.pdb.getTree().getTreeFromPath(abspath).getBranches() compl = [] for dir in dirs.keys(): if dir.startswith(text): compl.append(dir + '/') return compl def getEditorInput(self, content=''): """Fire up an editor and read user input from temporary file""" for name in ('VISUAL', 'EDITOR'): editor = os.environ.get(name) if editor: break else: if editor is None: editor = 'vi' self.printMessage(_("running %s"), editor) # create temporary file handle, tmpfname = tempfile.mkstemp(prefix="kedpm_") tmpfile = open(tmpfname, 'w') tmpfile.write(content) tmpfile.close() os.system(editor + " " + tmpfname) tmpfile = open(tmpfname, 'r') text = tmpfile.read() tmpfile.close() os.remove(tmpfname) return text def getAbsolutePath(self, arg): """Return absolute path from potentially relative (cat)""" root = self.pdb.getTree() if not arg: return self.cwd if (arg[0] == '/'): path = root.normalizePath(arg.split('/')) else: path = root.normalizePath(self.cwd + arg.split('/')) return path def getTreeFromRelativePath(self, path): """Get tree object from given relative path and current working directory""" root = self.pdb.getTree() abspath = self.getAbsolutePath(path) return root.getTreeFromPath(abspath) def shiftArgv(self, argv, count=1): if len(argv) > count: arg = " ".join(argv[count:]) else: arg = "" return arg def printRecord(self, record): obscure_passwords = self.conf.options['obscure-passwords'] for key, fieldinfo in record.fields_type_info: if record[key] == '': continue if fieldinfo[ 'type'] == password.TYPE_PASSWORD and obscure_passwords and self.use_console_escapes: print "%s: \x1b[31m\x1b[41m%s\x1b[00m" % (fieldinfo['title'], record[key]) else: print "%s: %s" % (fieldinfo['title'], record[key]) ########################################## # Command implementations below. # def emptyline(self): pass def do_exit(self, arg): '''Quit KED Password Manager''' readline.write_history_file(self.histfile) if self.modified: self.tryToSave() self.printMessage(_("Exiting.")) sys.exit(0) def do_EOF(self, arg): '''The same as 'exit' command''' print self.do_exit(arg) def do_quit(self, arg): '''The same as 'exit' command''' print self.do_exit(arg) def do_help(self, arg): '''Print help message''' Cmd.do_help(self, arg) def do_ls(self, arg): '''List available catalogs and passwords Syntax: ls [<category>] ''' try: tree = self.getTreeFromRelativePath(arg) except KeyError: print _("ls: %s: No such catalog") % arg return print _("=== Directories ===") for bname in tree.getBranches().keys(): print bname + "/" print _("==== Passwords ====") self.listPasswords(tree.getNodes()) def complete_ls(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_cd(self, arg): '''change directory (category) Syntax: cd <category> ''' root = self.pdb.getTree() cdpath = self.getAbsolutePath(arg) try: newpath = root.getTreeFromPath(cdpath) except KeyError: print _("cd: %s: No such catalog") % arg else: self.cwd = cdpath self.updatePrompt() def complete_cd(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_pwd(self, arg): '''print name of current/working directory''' print '/' + '/'.join(self.cwd) def do_show(self, arg): '''display password information. Syntax: show [-r] <regexp> -r - recursive search. search all subtree for matching passwords This will display contents of a password item in current category or whole subtree, if -r flag was specified. If several items matched by <regexp> and the program has been configured to prompt for a single entry, a list of them will be printed and you will be prompted to enter a number, pointing to password you want to look at. After receiving that number, KedPM will show you the password. Otherwise all matching entries will be displayed''' argv = arg.split() tree = None if argv and argv[0] == '-r': tree = self.getCwd().flatten() arg = self.shiftArgv(argv) selected_passwords = self.getPasswords(arg, tree) for record in selected_passwords: if record: print "---------------------------------------" self.printRecord(record) print "---------------------------------------" def do_edit(self, arg): '''edit password information. Syntax: edit [-p] <regexp> Prompts the user to edit a password item in the current category. If <regexp> matches multiple items, the list of matches will be printed and user is prompted to select one. If the optional '-p' flag is specified or the 'force-editor' config option is set, the password will be edited using the editor specified in the VISUAL or EDITOR environment variables, defaulting to "vi" if neither is found. Otherwise the user is prompted to edit each entry of the password entry on the command line. ''' argv = arg.split() if argv and argv[0] == '-p': use_editor = True arg = self.shiftArgv(argv) elif self.conf.options['force-editor']: use_editor = True else: use_editor = False selected_password = self.pickPassword(arg) if selected_password: try: if use_editor: text = self.getEditorInput(selected_password.asEditText()) patterns = [selected_password.getEditPattern()] chosendict = parser.parseMessage(text, patterns) selected_password.update(chosendict) else: self.editPassword(selected_password) self.tryToSave() except (KeyboardInterrupt, EOFError): self.printMessage(_("Cancelled")) else: self.printMessage(_("No password selected")) def do_new(self, arg): '''Add new password to current category. You will be prompted to enter fields. Syntax: new [-p | -t] -p - Get properties by parsing provided text. Will open default text editor for you to paste text in. Mutually exclusive with -t option. -t - Display editor template in default text editor. Mutually exclusive with -p option. If the config option 'force-editor is set, this command defaults to the -t option when no options are provided. ''' new_pass = FigaroPassword( ) # FIXME: Password type shouldn't be hardcoded. argv = arg.split() use_visual_editor = len( argv) == 0 and self.conf.options["force-editor"] if "-p" in argv and "-t" in argv: print _("new: -p and -t arguments are mutually exclusive.") print _("try 'help new' for more information") elif "-p" in argv: text = self.getEditorInput() choosendict = parser.parseMessage(text, self.conf.patterns) new_pass.update(choosendict) elif "-t" in argv or use_visual_editor: text = self.getEditorInput(new_pass.asEditText()) choosendict = parser.parseMessage(text, [new_pass.getEditPattern()]) new_pass.update(choosendict) try: if not use_visual_editor: self.editPassword(new_pass) except (KeyboardInterrupt, EOFError): self.printMessage(_("Cancelled")) else: tree = self.getCwd() tree.addNode(new_pass) self.tryToSave() def do_import(self, arg): '''Imports new password records into current category. Syntax: import Get properties by parsing provided text. Will open default text editor for you to paste text in. ''' argv = arg.split() tree = self.getCwd() text = self.getEditorInput() for line in [x for x in text.splitlines() if x]: new_pass = FigaroPassword( ) # FIXME: Password type shouldn't be hardcoded. choosendict = parser.parseMessage(line, self.conf.patterns) new_pass.update(choosendict) tree.addNode(new_pass) self.tryToSave() def do_save(self, arg): '''Save current password tree''' sys.stdout.write(_("Saving...")) sys.stdout.flush() self.pdb.save() print "OK" self.modified = 0 def do_mkdir(self, arg): '''create new category (directory) Syntax: mkdir <category> Creates new password category in current one. ''' if not arg: print _("mkdir: too few arguments") print _("try 'help mkdir' for more information") return pwd = self.getCwd() pwd.addBranch(arg.strip()) def do_rename(self, arg): '''rename category Syntax: rename <category> <new_name> ''' args = arg.split() if len(args) != 2: print '''Syntax: rename <category> <new_name> ''' return oldname = args[0] newname = args[1] try: self.pdb.getTree().renameBranch(self.cwd + [oldname], newname) except RenameError: print _("rename: category %s already exists") % newname return except KeyError: print _("rename: %s: no such category") % oldname return self.tryToSave() def complete_rename(self, text, line, begidx, endidx): return self.complete_dirs(text, line, begidx, endidx) def do_passwd(self, arg): """Change master password for opened database Syntax: password [new password] If new password is not provided with command, you will be promted to enter new one. """ if not arg: # Password is not provided with command. Ask user for it pass1 = getpass(_("New password: "******"Repeat password: "******"Empty passwords are really insecure. You should " \ "create one.") return if pass1 != pass2: print _("Passwords don't match! Please repeat.") return new_pass = pass1 else: new_pass = arg self.pdb.changePassword(new_pass) self.printMessage(_("Password changed.")) def do_help(self, arg): """Print help topic""" argv = arg.split() if argv and argv[0] in ['set']: # Provide extended help help_def = getattr(self, "help_" + argv[0]) if help_def: help_def(' '.join(argv[1:])) else: Cmd.do_help(self, arg) else: Cmd.do_help(self, arg) def do_set(self, arg): """Set KedPM options Syntax: set -- show all options set <option> -- show value of option set <option> = <value> -- set value to option For boolean values: 1, 'on' or 'true' are synonyms for True and 0, 'off' or 'false' are synonyms for False. Enter help set <option> for more info on a particular option. """ opts = self.conf.options if not arg: # show all options for opt, value in opts.items(): print "%s = %s" % (opt, value) return tokens = arg.split('=') opt_name = tokens[0].strip() try: opt_value = opts[opt_name] except KeyError: print _("set: no such option: [%s]") % arg return if len(tokens) == 1: # show value of option print "%s = %s" % (opt_name, opt_value) else: # set the value try: opt_value = ' '.join(tokens[1:]) opts[opt_name] = opt_value.strip() except OptionError, e: print "set: %s" % e # save configuration file self.conf.save()