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(' -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(' -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 |
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: |
xpath = commonXPath + caseSensitiveXPath |
res = ctxt.xpathEval(xpath) |
print '-> Search "%s": ' % search, |
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,25 → 225,29 |
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) |
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() |