Subversion Repositories pub

Compare Revisions

Ignore whitespace Rev 136 → Rev 137

/relevation/branches/0.3/relevation.py
53,41 → 53,104
__revision__ = '$Rev$'
__version_info__ = ( 0, 3, 0 )
__version__ = '.'.join(map(str, __version_info__))
RELEASE=True
RELEASE=not True
 
def usage(channel):
' Print help message '
def p(s):
channel.write(s)
p('%s {-f passwordfile} {-p password | -0} search [search2] [...]\n' % sys.argv[0])
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(' -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(' -t TYPE, --type=TYPE Print only entries of type TYPE.\n')
p(' With no search string, prints all entries of\n')
p(' type TYPE.\n')
p(' -x, --xml Dump unencrypted XML document.\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):
def get_entries(document, xpath):
"""
get_entries(xmlDoc, str xpath expression) -> list of xmlNode's
 
Get entry nodes that match xpath
"""
ctx = document.xpathNewContext()
try:
res = ctx.xpathEval(xpath)
except libxml2.xpathError:
if not RELEASE:
sys.stderr.write('Failed with xpath expression: %s\n' % xpath)
raise
finally:
ctx.xpathFreeContext()
return res
 
def make_xpath_query(search_text=None, type_filter=None, ignore_case=True, negate_filter=False):
'''
make_xpath_query(str, str, bool, bool) -> str
'''
xpath = '/revelationdata//entry'
if type_filter:
sign = '='
if negate_filter:
sign = '!='
xpath = '%s[@type%s"%s"]' % ( xpath, sign, type_filter )
if search_text:
xpath = xpath + '//text()'
if ignore_case:
xpath = '%s[contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "%s")]/../..' % ( xpath, search_text )
else:
xpath = '%s[contains(., "%s")]/../..' % ( xpath, search_text )
return xpath
 
def dump_all_entries(xmldata):
' Dump all entries from xmldata, with no filter at all '
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,
res = get_entries(doc, '//entry')
nr = dump_result(res, 'all')
doc.freeDoc()
return nr
 
def dump_entries(xmldata, search_text=None, type_filter=None, ignore_case=True, negate_filter=False):
' Dump entries from xmldata that match criteria '
doc = libxml2.parseDoc(xmldata)
xpath = make_xpath_query(search_text, type_filter, ignore_case, negate_filter)
res = get_entries(doc, xpath)
query_desc = ''
if search_text:
query_desc = '"%s"' % search_text
if type_filter:
neg = ''
if negate_filter:
neg = 'not '
if search_text:
query_desc = '%s (\'%s%s\' entries)' % ( query_desc, neg, type_filter )
else:
query_desc = '%s%s entries' % ( neg, type_filter )
nr = dump_result(res, query_desc)
doc.freeDoc()
return nr
 
def dump_result(res, query_desc):
''' Print query results.
dump_result(list of entries, query description) -> int
Note the XML document can't be freed before calling this function.
'''
print '-> Search %s: ' % query_desc,
if not len(res):
print 'No results'
sys.exit(80)
return False
print '%d matches' % len(res)
tagnames ={ 'generic-url': 'Url:',
'generic-username': 'Username:',
96,7 → 159,7
'generic-hostname': 'Hostname:',
}
for x in res:
print '- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - '
sys.stderr.write('-------------------------------------------------------------------------------\n')
print ''
for attr in x.properties: # Is it accessible directly?
if attr.name == 'type':
121,11 → 184,12
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()
sys.stderr.write('-------------------------------------------------------------------------------\n')
sys.stderr.write('<- (end of %d result%s for {%s})\n\n' % ( nr, plural, query_desc ))
return nr
 
def world_readable(path):
' Check if a file is readable by everyone '
assert os.path.exists(path)
if sys.platform == 'win32':
return True
161,26 → 225,30
def main():
datafile = None
password = None
searches = []
# values to search for
needles = []
caseInsensitive = True
# individual search: ( 'value to search', 'type of search', 'type of entry to filter' )
searchTypes = []
dump_xml = False
 
sys.stderr.write('Relevation v%s, (c) 2011 Toni Corvera\n\n' % __version__)
 
# ---------- OPTIONS ---------- #
( datafile, password ) = load_config()
 
try:
# gnu_getopt requires py >= 2.3
ops, args = getopt.gnu_getopt(sys.argv[1:], 'f:p:s:0ciah',
ops, args = getopt.gnu_getopt(sys.argv[1:], 'f:p:s:0ciaht:x',
[ 'file=', 'password=', 'search=', 'stdin',
'case-sensitive', 'case-insensitive', 'ask',
'help', 'version' ])
'help', 'version', 'type=', 'xml' ])
except getopt.GetoptError, err:
print str(err)
usage(sys.stderr)
sys.exit(os.EX_USAGE)
if args:
searches = args
 
needles = args
if ( '-h', '' ) in ops or ( '--help', '' ) in ops:
usage(sys.stdout)
sys.exit(os.EX_OK)
191,7 → 259,7
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
203,11 → 271,23
password = sys.stdin.readline()
password = password[:-1]
elif opt in ( '-s', '--search' ):
searches.append(arg)
needles.append(arg)
elif opt in ( '-i', '--case-insensitive' ):
caseInsensitive = True
elif opt in ( '-c', '--case-sensitive' ):
caseInsensitive = False
elif opt in ( '-t', '--type' ):
iarg = arg.lower()
neg = False
if iarg.startswith('-'):
iarg = iarg[1:]
neg = True
if not iarg in ( 'creditcard', 'cryptokey', 'database', 'door', 'email',
'folder', 'ftp', 'generic', 'phone', 'shell', 'website' ):
sys.stderr.write('Warning: Type "%s" is not known by relevation.\n' % arg)
searchTypes.append( ( iarg, neg ) )
elif opt in ( '-x', '--xml' ):
dump_xml = True
else:
sys.stderr.write('Unhandled option: %s\n' % opt)
assert False, "internal error parsing options"
218,12 → 298,14
if not password:
sys.stderr.write('Password is required\n')
sys.exit(os.EX_USAGE)
# Encrypted data
# ---------- PASSWORDS FILE DECRYPTION ---------- #
f = None
try:
if not os.access(datafile, os.R_OK):
raise IOError('File \'%s\' not accessible' % datafile)
f = open(datafile, "rb")
# Encrypted data
data = f.read()
finally:
if f:
240,12 → 322,30
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:
# ---------- QUERIES ---------- #
if dump_xml:
print xmldata
sys.exit(os.EX_OK)
for s in searches:
search(xmldata, s, caseInsensitive)
# Multiply values to search by type of searches
numhits = 0
 
if not ( needles or searchTypes ): # No search nor filters, print all
numhits = dump_all_entries(xmldata)
elif not searchTypes: # Simple case, all searches are text searches
for text in needles:
numhits += dump_entries(xmldata, text, 'folder', caseInsensitive, True)
elif needles: # Do a search filtered for each type
for text in needles:
for ( sfilter, negate ) in searchTypes:
numhits += dump_entries(xmldata, text, sfilter, caseInsensitive,
negate_filter=negate)
else: # Do a search only of types
for ( sfilter, negate ) in searchTypes:
numhits += dump_entries(xmldata, None, sfilter, negate_filter=negate)
if numhits == 0:
sys.exit(80)
 
if __name__ == '__main__':
try:
main()