Subversion Repositories pub

Compare Revisions

No changes between revisions

Ignore whitespace Rev 130 → Rev 131

/relevation/trunk/relevation.py
0,0 → 1,203
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
 
"""
Relevation Password Printer
a command line interface to Revelation Password Manager.
 
This program is published under a BSD 3-clause license,
the full license text is included in a separate file.
 
Code based on Revelation's former BTS (no longer online, not archived?):
(ref1) code:
http://oss.wired-networks.net/bugzilla/attachment.cgi?id=13&action=view
(ref2) bug report:
http://oss.wired-networks.net/bugzilla/show_bug.cgi?id=111
-> http://web.archive.org/http://oss.wired-networks.net/bugzilla/show_bug.cgi?id=111
(ref3) http://docs.python.org/library/zlib.html
"""
import getopt
import libxml2
import os
import sys
import zlib
from Crypto.Cipher import AES
 
 
__author__ = 'Toni Corvera'
__date__ = '$Date$'
__revision__ = '$Rev$'
__version_info__ = ( 0, 2, 0 )
__version__ = '.'.join(map(str, __version_info__))
RELEASE=True
 
def usage(channel):
def p(s):
channel.write(s)
p('%s {-f passwordfile} {-p password | -0} search [search2] [...]\n' % sys.argv[0])
p('\nOptions:\n')
p(' -f <FILE>, --file=<FILE> Revelation password file\n')
p(' -p <PASS>, --password=<PASS> Master password\n')
p(' -s <SEARCH>, --search=<SEARCH> Search for string\n')
p(' -i, --case-insensitive Case insensitive search [default]\n')
p(' -c, --case-sensitive Case sensitive search\n')
p(' -a, --ask Interactively ask for password.\n')
p(' Note it will be displayed in clear as you\n')
p(' type it.\n')
p(' -0, --stdin Read password from standard input\n')
p(' -h, --help Print help (this message).\n')
p(' --version Print the program\'s version information.\n')
p('\n')
 
def search(xmldata, search, caseInsensitive=True):
doc = libxml2.parseDoc(xmldata)
ctxt = doc.xpathNewContext()
commonXPath = '//revelationdata//entry[@type!="folder"]//text()'
caseSensitiveXPath = '[contains(., "%s")]/../..' % search
caseInsensitiveXPath = '[contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "%s")]/../..' % search
if caseInsensitive:
xpath = commonXPath + caseInsensitiveXPath
else:
xpath = commonXPath + caseSensitiveXPath
res = ctxt.xpathEval(xpath)
print '-> Search "%s": ' % search,
if not len(res):
print 'No results'
sys.exit(80)
print '%d matches' % len(res)
tagnames ={ 'generic-url': 'Url:',
'generic-username': 'Username:',
'generic-password': 'Password:',
'generic-email': 'Email:',
'generic-hostname': 'Hostname:',
}
for x in res:
print '- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - '
print ''
for attr in x.properties: # Is it accessible directly?
if attr.name == 'type':
print 'Type:',attr.children
for chld in x.children:
n = chld.name
val = chld.content
if n == 'name':
print 'Name:',val
elif n == 'description':
print 'Description:',val
elif n == 'field':
for attr in chld.properties:
if attr.name == 'id':
idv = attr.content
if idv in tagnames:
idv = tagnames[idv]
print idv,chld.content
print ''
# / for chld in x.children
nr = len(res)
plural = ''
if nr > 1:
plural = 's'
sys.stderr.write('<- (end of %d result%s for "%s")\n\n' % ( nr, plural, search ))
doc.freeDoc()
ctxt.xpathFreeContext()
 
def main():
datafile = None
password = None
searches = []
caseInsensitive = True
 
sys.stderr.write('Relevation v%s, (c) 2011 Toni Corvera\n\n' % __version__)
try:
# gnu_getopt requires py >= 2.3
ops, args = getopt.gnu_getopt(sys.argv[1:], 'f:p:s:0ciah',
[ 'file=', 'password=', 'search=', 'stdin',
'case-sensitive', 'case-insensitive', 'ask',
'help', 'version' ])
except getopt.GetoptError, err:
print str(err)
usage(sys.stderr)
sys.exit(os.EX_USAGE)
if args:
searches = args
 
if ( '-h', '' ) in ops or ( '--help', '' ) in ops:
usage(sys.stdout)
sys.exit(os.EX_OK)
if ( '--version', '' ) in ops:
release=''
if not RELEASE:
release=' [DEBUG]'
print 'Relevation version %s%s' % ( __version__, release )
print 'Python version %s' % sys.version
sys.exit(os.EX_OK)
 
for opt, arg in ops:
if opt in ( '-f', '--file' ):
datafile = arg
elif opt in ( '-p', '--password' ):
password = arg
elif opt in ( '-a', '--ask', '-0', '--stdin' ):
if opt in ( '-a', '--ask' ):
sys.stderr.write('File password: ')
password = sys.stdin.readline()
password = password[:-1]
elif opt in ( '-s', '--search' ):
searches.append(arg)
elif opt in ( '-i', '--case-insensitive' ):
caseInsensitive = True
elif opt in ( '-c', '--case-sensitive' ):
caseInsensitive = False
else:
sys.stderr.write('Unhandled option: %s\n' % opt)
assert False, "internal error parsing options"
if not datafile or not password:
usage(sys.stderr)
if not datafile:
sys.stderr.write('Input password filename is required\n')
if not password:
sys.stderr.write('Password is required\n')
sys.exit(os.EX_USAGE)
# Encrypted data
f = None
try:
if not os.access(datafile, os.R_OK):
raise IOError('File \'%s\' not accessible' % datafile)
f = open(datafile, "rb")
data = f.read()
finally:
if f:
f.close()
# Pad password
password += (chr(0) * (32 - len(password)))
# Data IV
c = AES.new(password)
iv = c.decrypt(data[12:28])
# Decrypt. Decrypted data is compressed
c = AES.new(password, AES.MODE_CBC, iv)
cleardata_gz = c.decrypt(data[28:])
# Length of data padding
padlen = ord(cleardata_gz[-1])
# Decompress actual data (15 is wbits [ref3] DON'T CHANGE, 2**15 is the (initial) buf size)
xmldata = zlib.decompress(cleardata_gz[:-padlen], 15, 2**15)
if not searches:
print xmldata
sys.exit(os.EX_OK)
for s in searches:
search(xmldata, s, caseInsensitive)
 
if __name__ == '__main__':
try:
main()
except libxml2.parserError as e:
sys.stderr.write('XML parsing error\n')
if not RELEASE:
traceback.print_exc()
sys.exit(os.EX_DATAERR)
except IOError as e:
if not RELEASE:
traceback.print_exc()
sys.stderr.write(str(e)+"\n")
sys.exit(os.EX_IOERR)
 
# vim:set ts=4 et ai fileencoding=utf-8: #
Property changes:
Added: svn:executable
Added: svn:keywords
+Rev Id Date
\ No newline at end of property