kedpm-1.0/0000755000000000000000000000000012056215332007337 5ustar kedpm-1.0/kedpm/0000755000000000000000000000000012056216137010443 5ustar kedpm-1.0/kedpm/password_tree.py0000644000000000000000000002134212056215332013674 0ustar # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: password_tree.py,v 1.19 2010/03/09 15:49:26 eg1981 Exp $ """Password items organized in recursive tree.""" import re from password import Password from exceptions import RenameError class PasswordTreeIterator: def __init__(self, tree, parent=None): self.path = [] self.tree = tree self.flat=self.buildFlatTree(tree) + [[]] self.tree_index=0 self.pass_index=0 def buildFlatTree(self, tree, path=[]): flat = [] for name, subtree in tree.getBranches().items(): flat.append(path+[name]) flat += self.buildFlatTree(subtree, path+[name]) return flat def getCurrentTree(self): return self.tree.getTreeFromPath(self.flat[self.tree_index]) def getCurrentCategory(self): if self.tree_index >= len(self.flat): return None return self.flat[self.tree_index] def next(self): """This will iterate through whole password tree. next() method will consequently return every item in password tree. Return None if no more passwords exists.""" if self.tree_index >= len(self.flat): return None while 1: curtree = self.getCurrentTree() if len(curtree.getNodes()) > self.pass_index: pwd = curtree.getNodes()[self.pass_index] self.pass_index += 1 return pwd else: # next tree self.pass_index = 0 self.tree_index += 1 if self.tree_index >= len(self.flat): return None return None class _PasswordTreeIterator: def __init__(self, tree, parent=None): self.parent = None self.pass_index = 0 self.branch_index = 0 self.branches = tree.getBranches().keys() self.branches.sort() self.br_iterator = None self.stopped = 0 self.tree = tree def getCurrentCategory(self): try: return self.branches[self.branch_index] except IndexError: return "" def next(self): """This will iterate through whole password tree. next() method will consequently return every item in password tree.""" if len(self.branches) > self.branch_index or self.branch_index==-1: if self.br_iterator: nxt = self.br_iterator.next() if nxt: return nxt self.branch_index += 1 self.br_iterator = None return self.next() else: self.br_iterator = self.tree[self.branches[self.branch_index]].getIterator() return self.next() else: # iterate on passwords nodes = self.tree.getNodes() if len(nodes) > self.pass_index: pwd = nodes[self.pass_index] self.pass_index += 1 return pwd else: return None class PasswordTree: _nodes = [] _branches = {} def __init__(self): """Create named tree instance.""" self._nodes = [] self._branches = {} def addNode(self, node): """Add node to the tree.""" self._nodes.append(node) def addBranch(self, name): """Add new branch to the tree.""" if self._branches.has_key(name): raise AttributeError, "Branch already exists" branch = PasswordTree() self._branches[name] = branch return branch def getNodes(self): """Return all non-tree nodes of the tree.""" return self._nodes def getBranches(self): """Return all branch nodes of the tree.""" return self._branches def get(self, branch, default=None): """Return branch if such exists, or default otherwise""" return self._branches.get(branch, default) def __getitem__(self, key): return self._branches[key] def locate(self, pattern): """Return list of passwords, matching pattern.""" try: re_search = re.compile(".*"+pattern+".*", re.IGNORECASE) except re.error: re_search = re.compile(".*"+re.escape(pattern)+".*", re.IGNORECASE) results = [] for password in self._nodes: for field in password.getSearhableFields(): fval = getattr(password, field) if re_search.match(fval): results.append(password) break return results def rlocate(self, regexp = '', curdir = '.'): '''Locate passwords in this tree recursively. This looks for passwords in all branches of this tree, using locate(). It returns a { path, password } dictionnary.''' paths = {} for result in self.locate(regexp): paths[curdir + '/' + result['title']] = result for name, branch in self.getBranches().iteritems(): paths.update(branch.rlocate(regexp, curdir + '/' + name)) return paths def getTreeFromPath(self, path): """Return password tree from given path. path is list of path items.""" path = self.normalizePath(path) tree = self if path == []: return tree for pathitem in path: tree = tree[pathitem] return tree def renameBranch(self, path, newname): """Set new name for the given branch. Do not rename tree root - just leave it as is. Tree root don't have any name anyway.""" path = self.normalizePath(path) if not path: return parent_tree = self.getTreeFromPath(path[:-1]) oldname = path[-1] try: self.getTreeFromPath(path[:-1] + [newname]) except KeyError: pass else: raise RenameError, "Category \"%s\" already exists." % newname branches = parent_tree.getBranches() cat = branches[oldname] del branches[oldname] branches[newname] = cat def normalizePath(self, path): """Reduce .. and . items from path. path is list of path items.""" normal = [] for pathitem in path: if pathitem == "." or pathitem == "": continue if pathitem == "..": normal = normal[:-1] continue normal.append(pathitem) return normal def getIterator(self): return PasswordTreeIterator(self) def flatten(self): """Traverse the whole tree and return flat representation of it as PasswordTree instance.""" result = PasswordTree() iter = self.getIterator() while 1: pwd = iter.next() if pwd is None: break result.addNode(pwd) return result def asString(self, indent = 0): output = "" indstr = " " * (indent*4) for (bname, branch) in self._branches.items(): output = output + indstr + bname+"\n" output = output + branch.asString(indent+1) for password in self._nodes: output = output + indstr + str(password) + "\n" return output def __str__(self): return self.asString() def getBranchContainingNode(self, node): if node in self._nodes: return self for branch in self._branches.values(): res = branch.getBranchContainingNode(node) if res: return res else: return None def removeNode(self, node): """Removes a node from the tree. If node is not found in the current branch, search for node recursively in whole subtree.""" container = self.getBranchContainingNode(node) container._nodes.remove(node) def removeBranch(self, name): """Removes a branch from the tree.""" try: del(self._branches[name]) except KeyError: pass kedpm-1.0/kedpm/exceptions.py0000644000000000000000000000221312056215332013170 0ustar # KED Password Manager # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: exceptions.py,v 1.2 2003/09/04 20:28:59 kedder Exp $ class DataRequired (Exception): """ Raised when additional data is required for operation""" pass class WrongPassword (DataRequired): """ Raised when supplied password is incorrect """ pass class RenameError(KeyError): """Raised when category renamed to something, that already exists""" pass kedpm-1.0/kedpm/passdb.py0000644000000000000000000000304612056215332012270 0ustar # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: passdb.py,v 1.5 2005/03/05 21:44:32 kedder Exp $ """ Password Database """ from password_tree import PasswordTree class DatabaseNotExist(IOError): pass class PasswordDatabase: """ Base class for password databases. Real databases should be implemented in plugins """ def __init__(self, **args): self._pass_tree = PasswordTree() def open(self, password = ""): """ Open database from external source """ pass def save(self, fname=""): """ Save database to external source """ pass def create(self, password, fname=""): """Create new password database""" pass def changePassword(self, newpassword): """Change password for database""" pass def getTree(self): return self._pass_tree kedpm-1.0/kedpm/__init__.py0000644000000000000000000000314012056215332012546 0ustar # KED Password Manager # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: __init__.py,v 1.18 2010/02/09 23:57:45 eg1981 Exp $ ''' KED Password Manager Simple to use, extensible and secure password manager ''' import os __version__ = '1.0' data_files_dir = None def setupPrefix(): """Figure out base location where kedpm data files were installed by examining __file__ value. On UNIX data files shoud be stored in /share/kedpm directory.""" global data_files_dir prefix_dir = __file__ for i in range(5): prefix_dir, f = os.path.split(prefix_dir) data_files_dir = os.path.join(prefix_dir, 'share', 'kedpm') if not os.access(data_files_dir, os.F_OK): # We are in the distribution dir data_files_dir = "." if data_files_dir is None: setupPrefix() # Install gettext _() function import gettext gettext.install('kedpm', './po', unicode=0) kedpm-1.0/kedpm/plugins/0000755000000000000000000000000012056215332012120 5ustar kedpm-1.0/kedpm/plugins/pdb_figaro.py0000644000000000000000000003223112056215332014567 0ustar # Copyright (C) 2003-2005 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: pdb_figaro.py,v 1.21 2006/09/06 03:29:41 gyepi Exp $ """ Figaro password manager database plugin """ import os from xml.dom import minidom from string import strip from Crypto.Cipher import Blowfish from Crypto.Hash import MD5 from random import randint from kedpm.exceptions import WrongPassword from kedpm.passdb import PasswordDatabase, DatabaseNotExist from kedpm.password_tree import PasswordTree from kedpm.password import Password, TYPE_STRING, TYPE_TEXT, TYPE_PASSWORD FPM_PASSWORD_LEN = 24 class FigaroPasswordTooLongError(ValueError): pass class FigaroPassword (Password): fields_type_info = [ ('title', {'title': 'Title', 'type': TYPE_STRING}), ('user', {'title': 'Username', 'type': TYPE_STRING}), ('password', {'title': 'Password', 'type': TYPE_PASSWORD}), ('url', {'title': 'URL', 'type': TYPE_STRING}), ('notes', {'title': 'Notes', 'type': TYPE_TEXT}), ] default = 0 store_long_password = 0 launcher = "" def __init__(self, **kw): Password.__init__(self, **kw) self.default = kw.get('default', 0) self.launcher = kw.get('launcher', '') def __setitem__(self, key, value): if key=='password' and len(value) > FPM_PASSWORD_LEN and not self.store_long_password: raise FigaroPasswordTooLongError, "Password is too long" Password.__setitem__(self, key, value) class PDBFigaro (PasswordDatabase): default_db_filename = os.path.join(os.path.expanduser("~"), '.fpm', 'fpm') launcherlist = None filename = None native = 0 #default_db_filename = 'test/fpm.sample' # default versions FULL_VERSION = "00.53.00" DISPLAY_VERSION = "0.53" MIN_VERSION = "00.50.00" def __init__(self, **args): self._pass_tree = PasswordTree() if args.has_key('filename'): self.default_db_filename = args['filename'] def open(self, password, fname=""): """ Open figaro password database and construct password tree """ self._password = password self.filename = fname or self.default_db_filename # Check existance of database file if not os.access(self.filename, os.F_OK): raise DatabaseNotExist, 'File %s is not found' % self.filename fpm = minidom.parse(self.filename) generator = fpm.documentElement.getAttribute('generator') if generator.startswith('kedpm'): self.native=1 self.convDomToTree(fpm) def changePassword(self, password): """Change password for database. Database will be saved and reopened with new password.""" self._password = password self.save() self.open(password, fname = self.filename) def convDomToTree(self, fpm): 'Read figaro xml database and create password tree from it' root = fpm.documentElement # Save version information self.FULL_VERSION = root.getAttribute('full_version') self.MIN_VERSION = root.getAttribute('min_version') self.DISPLAY_VERSION = root.getAttribute('display_version') # Support long passwords of fpm-0.58 if self.MIN_VERSION >="00.58.00": global FPM_PASSWORD_LEN FPM_PASSWORD_LEN = 256 keyinfo = fpm.documentElement.getElementsByTagName("KeyInfo")[0] self._salt = keyinfo.getAttribute('salt') vstring = keyinfo.getAttribute('vstring') if self.decrypt(vstring) != "FIGARO": raise WrongPassword, "Wrong password" # Save LauncherList xml element. Although kedpm don't use launchers # yet, this list will be inserted into saved database to preserve # compatibility with fpm. nodes = fpm.documentElement.getElementsByTagName("LauncherList") if nodes: assert len(nodes) == 1 self.launcherlist = nodes[0] nodes = fpm.documentElement.getElementsByTagName("PasswordItem") for node in nodes: category = self._getTagData(node, "category") if category=="": branch = self._pass_tree else: branch = self._pass_tree path = category.split('/') for pelem in path: subbranch = branch.get(pelem) if not subbranch: branch = branch.addBranch(pelem) else: branch = subbranch branch.addNode(self._getPasswordFromNode(node)) def save(self, fname=""): """Save figaro password database""" # Create new salt for each save self._salt = self.generateSalt() doc = self.convTreeToDom() filename = fname or self.filename or self.default_db_filename f = open(filename, 'w') f.write(doc.toxml()) f.close() os.chmod(filename, 0600) def generateSalt(self): """Generate salt, that consists of 8 small latin characters""" salt = "" for i in range(8): salt += chr(randint(ord('a'), ord('z'))) return salt def convTreeToDom(self): """Build and return DOM document from current password tree""" domimpl = minidom.getDOMImplementation() document= domimpl.createDocument("http://kedpm.sourceforge.net/xml/fpm", "FPM", None) root = document.documentElement root.setAttribute('full_version', self.FULL_VERSION) root.setAttribute('min_version', self.MIN_VERSION) root.setAttribute('display_version', self.DISPLAY_VERSION) root.setAttribute('generator', 'kedpm') # KeyInfo tag keyinfo = document.createElement('KeyInfo') keyinfo.setAttribute('salt', self._salt) keyinfo.setAttribute('vstring', self.encrypt('FIGARO')) root.appendChild(keyinfo) # Add LauncherList for fpm compatibility if self.launcherlist: root.appendChild(self.launcherlist) # PasswordList tag passwordlist = document.createElement('PasswordList') props = ['title', 'user', 'url', 'notes'] iter = self._pass_tree.getIterator() while 1: pwd = iter.next() if pwd is None: break pwitem = document.createElement('PasswordItem') for prop in props: pr_node_text = document.createTextNode(self.encrypt(pwd[prop])) pr_node = document.createElement(prop) pr_node.appendChild(pr_node_text) pwitem.appendChild(pr_node) password = document.createElement('password') text = document.createTextNode(self.encrypt(pwd['password'], 1)) password.appendChild(text) pwitem.appendChild(password) category = document.createElement('category') text = document.createTextNode(self.encrypt('/'.join(iter.getCurrentCategory()))) category.appendChild(text) pwitem.appendChild(category) # Following launcher and default tags for fpm compatibility launcher = document.createElement('launcher') text = document.createTextNode(self.encrypt(pwd.launcher)) launcher.appendChild(text) pwitem.appendChild(launcher) if pwd.default: pwitem.appendChild(document.createElement('default')) passwordlist.appendChild(pwitem) root.appendChild(passwordlist) return document def create(self, password, fname=""): filename = fname or self.default_db_filename dirname, fname = os.path.split(filename) if not os.access(dirname, os.F_OK): print "Creating directory %s" % dirname os.mkdir(dirname, 0700) newdb = PDBFigaro() newdb._password = password newdb.save(filename) def _getPasswordFromNode(self, node): """ Create password instance from given fpm node """ fields = ["title", "user", "url", "notes", "password"] params = {} for field in fields: params[field] = self._getTagData(node, field) # save default and launcher fields for fpm compatibility chnode = node.getElementsByTagName('default') if len(chnode)==1: params['default'] = 1 params['launcher'] = self._getTagData(node, 'launcher') return FigaroPassword(**params) def _getTagData(self, node, tag): chnode = node.getElementsByTagName(tag) if chnode and node.hasChildNodes(): datanode= chnode.pop() encrypted = "" # datanode can have more than one text chunk for child in datanode.childNodes: encrypted += child.data assert len(encrypted) % 8 == 0 return self.decrypt(encrypted) else: return "" def encrypt(self, field, field_is_password=0): """ Encrypt FPM encoded field """ hash=MD5.new() hash.update(self._salt + self._password) key = hash.digest() bf = Blowfish.new(key) # Allow passwords that are longer than 24 characters. Unfortunately # this will break fpm compatibility somewhat - fpm will not be able to # handle such long password correctly. noised = self._addNoise(field, field_is_password and (len(field) / FPM_PASSWORD_LEN + 1) * FPM_PASSWORD_LEN) rotated = self._rotate(noised) encrypted = bf.encrypt(rotated) hexstr = self._bin_to_hex(encrypted) return hexstr def decrypt(self, field): """ Decrypt FPM encoded field """ hash=MD5.new() hash.update(self._salt + self._password) key = hash.digest() bf = Blowfish.new(key) binstr = self._hex_to_bin(field) rotated = bf.decrypt(binstr) plaintext = self._unrotate(rotated) return plaintext def _bin_to_hex(self, strin): """Used in encrypt""" strout = "" for i in range(len(strin)): data = strin[i] high = ord(data) / 16 low = ord(data) % 16 strout += chr(ord('a')+high) + chr(ord('a')+low) assert (2*len(strin) == len(strout)) return strout def _hex_to_bin(self, strin): """Used in decrypt""" strout = "" for i in range(len(strin) / 2): high = ord(strin[i * 2]) - ord('a') low = ord(strin[i * 2 + 1]) - ord('a') data = high * 16 + low assert data < 256 strout = strout + chr(data) return strout def _addNoise(self, field, reslen = 0): """If we have a short string, I add noise after the first null prior to encrypting. This prevents empty blocks from looking identical to eachother in the encrypted file.""" block_size = Blowfish.block_size field += '\x00' reslen = reslen or (len(field) / block_size + 1) * block_size while len(field) < reslen: rchar = chr(randint(0, 255)) field += rchar return field def _rotate(self, field): """After we use _addNoise (above) we ensure blocks don't look identical unless all 8 chars in the block are part of the password. This routine makes us use all three blocks equally rather than fill the first, then the second, etc. This makes it so none of the blocks in the password will remain constant from save to save, even if the password is from 7-20 characters long. Note that passwords from 21-24 characters start to fill blocks, and so will be constant. """ plaintext = "" tmp = {} block_size = Blowfish.block_size num_blocks = len(field)/block_size for b in range(num_blocks): for i in range(block_size): tmp[b*block_size+i] = field[i*num_blocks+b] for c in range(len(tmp)): plaintext = plaintext + tmp[c] return str(plaintext) def _unrotate(self, field): plaintext = "" tmp = {} block_size = Blowfish.block_size num_blocks = len(field)/block_size for b in range(num_blocks): for i in range(block_size): tmp[i*num_blocks+b] = field[b*block_size+i] for c in range(len(tmp)): if tmp[c] == chr(0): break plaintext = plaintext + tmp[c] return str(plaintext) kedpm-1.0/kedpm/plugins/__init__.py0000644000000000000000000000167612056215332014243 0ustar # KED Password Manager # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: __init__.py,v 1.1 2003/08/05 18:32:18 kedder Exp $ ''' KED Password Manager Simple to use, extensible and secure password manager Plugins ''' __version__ = '0.0.1' kedpm-1.0/kedpm/crypt.py0000644000000000000000000000222712056215332012155 0ustar # KED Password Manager # Copyright (C) 2003 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: crypt.py,v 1.1 2003/08/05 18:32:18 kedder Exp $ """ Cryptography algorythms for KED Password Manager """ class Crypt: """ Base class for ctypting plugins """ def encrypt(self, data): """ Encrypt given data """ return data def decrypt(self, data): """ Return parameter list needed for encription """ return data kedpm-1.0/kedpm/frontends/0000755000000000000000000000000012056215332012441 5ustar kedpm-1.0/kedpm/frontends/cli.py0000644000000000000000000007332212056215332013571 0ustar # Copyright (C) 2003-2005 Andrey Lebedev # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id: cli.py,v 1.50 2011/03/21 00:27:36 anarcat Exp $ "Command line interface for Ked Password Manager" from kedpm import __version__ from kedpm.plugins.pdb_figaro import PDBFigaro, FigaroPassword, FigaroPasswordTooLongError from kedpm.passdb import DatabaseNotExist from kedpm.exceptions import WrongPassword, RenameError from kedpm.frontends.frontend import Frontend from kedpm.config import OptionError from kedpm import password from kedpm import parser from getpass import getpass from cmd import Cmd import os, sys, tempfile from os.path import expanduser import readline 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: ")) except DatabaseNotExist: password = self.createNewDatabase() self.printMessage(_("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: ")) pass2 = getpass(_("Repeat password: ")) if pass1 == '': print _("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 [] ''' 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 ''' 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] -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 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] Prompts the user to edit a password item in the current category. If 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 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 ''' args = arg.split() if len(args) != 2: print '''Syntax: rename ''' 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: ")) pass2 = getpass(_("Repeat password: ")) if pass1 == '': print _("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