/relevation/trunk/gui.py |
---|
File deleted |
Property changes: |
Deleted: svn:executable |
-* |
\ No newline at end of property |
Deleted: svn:keywords |
-Rev Id Date |
\ No newline at end of property |
/relevation/trunk/relevation.py |
---|
File deleted |
Property changes: |
Deleted: svn:executable |
Deleted: svn:keywords |
-Rev Id Date |
\ No newline at end of property |
/relevation/trunk/relevation.spec.in |
---|
1,3 → 1,14 |
# $Id$ |
# See http://fedoraproject.org/wiki/Packaging:Python |
# Downstream specfile can be found at |
# http://pkgs.fedoraproject.org/cgit/relevation.git/ |
%if 0%{?rhel} && 0%{?rhel} <= 6 |
%{!?__python2: %global __python2 /usr/bin/python2} |
%{!?python2_sitelib: %global python2_sitelib %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} |
%{!?python2_sitearch: %global python2_sitearch %(%{__python2} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} |
%endif |
Summary: Command-line search for Revelation Password Manager files |
Name: relevation |
Version: @VERSION@ |
9,6 → 20,7 |
Source: http://p.outlyer.net/relevation/files/relevation-%{version}.tar.gz |
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root |
BuildArch: noarch |
BuildRequires: python2-devel |
Requires: libxml2-python |
Requires: python-crypto |
Requires: python-lxml |
17,34 → 29,33 |
Relevation is a tool to retrieve passwords stored in a password file in the |
format used by Revelation, from the command-line instead of through a GUI. |
%prep |
%setup -q |
%build |
%install |
rm -rf %{buildroot} |
make install prefix=%{_prefix} DESTDIR=%{buildroot} |
make install prefix=%{_prefix} DESTDIR=%{buildroot} INSTALL_LAYOUT= |
# We include the extra tools as %%doc, remove from here |
rm -rf %{buildroot}%{_prefix}/share/doc/relevation/extra/ |
%clean |
rm -rf %{buildroot} |
%files |
%defattr(-,root,root,-) |
%doc CHANGELOG LICENSE |
%doc gui.py devtools/*.py |
%doc src/gui.py devtools/*.py |
%{_bindir}/relevation |
%{_mandir}/man1/relevation.1* |
%{python2_sitelib}/relevation/ |
%{python2_sitelib}/relevation-*.egg-info |
%changelog |
* Fri May 23 2014 Toni Corvera <outlyer@gmail.com> 1.3-1.pon |
- Handle installation of new module |
%changelog |
* Wed Oct 30 2013 Toni Corvera <outlyer@gmail.com> 1.2.1-1.pon |
- Integrated spec from Fedora into upstream source |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/trunk/CHANGELOG |
---|
1,5 → 1,11 |
$Date$ |
1.3 (?): |
- Check file magic [#230] and reject unsupported data formats |
- Support the new data file format [#228] |
- Added extra checks for data integrity |
- Actually import traceback in pre-releases |
1.2.1 (2013-11-05): |
- Minimal GUI Fixes: |
- Updated to the changes in 1.2 |
/relevation/trunk/GNUmakefile |
---|
4,7 → 4,7 |
DESTDIR:= |
PKG=relevation |
VERSION=$(shell printf 'import relevation\nprint relevation.__version__' | python - && $(RM) $(PKG).pyc) |
VERSION=$(shell bash devtools/relver.bash 2>/dev/null | awk '{ print $$1 }') |
PKGVER=$(PKG)-$(VERSION) |
PACKAGER:=$(shell finger -lp `echo $USER` 2>/dev/null | head -n1 | cut -d: -f3) |
ifeq ($(PACKAGER),) |
14,43 → 14,101 |
--exclude=*.swp --exclude=*.pyo --exclude=*.pyc \ |
--exclude=manpage.html --exclude=manpage.pdf |
IS_RELEASE=$(shell printf 'import relevation\nif not relevation.RELEASE:\n\traise Exception("RELEASE is False")' | python - ) |
INSTALLROOT=$(DESTDIR)$(prefix) |
MANROOT=$(INSTALLROOT)/share/man |
# Where to install additional packages |
PYTHONROOT=$(DESTDIR)$(shell python -c "import sys;from distutils import sysconfig; print sysconfig.get_python_lib(0,0,prefix='$(prefix)')") |
DUSETUP=python setup.py # Distutils' setup |
# Debian derived systems need special treatment for clean uninstalls |
# since it uses different than standard directories |
INSTALL_LAYOUT= |
ifeq ($(findstring dist-packages,$(PYTHONROOT)),dist-packages) |
INSTALL_LAYOUT=--install-layout=deb |
endif |
all: $(PKG).1 |
@echo |
@echo "########################################################" |
@echo "# TARGETS:" |
@echo "# install" |
@echo "# uninstall" |
@echo "# zip (-> $(PKGVER).zip)" |
@echo "# tarball (-> $(PKGVER).tar.gz)" |
@echo "# rpm" |
@echo "# dist (-> tarball + zip)" |
@echo "# clean" |
@echo "# distclean" |
@echo "# testman Displays compiled manpage" |
@echo "# test_min_python Find min. Python version required" |
@echo "########################################################" |
@echo |
testman: |
docbook-to-man manpage.sgml | nroff -man | less |
docbook-to-man manpage_source.sgml | nroff -man | less |
clean: |
-$(RM) *.pyc *.pyo manpage.html manpage.pdf $(PKG).spec |
testmake: |
@echo VERSION=$(VERSION) |
@echo PKGVER=$(PKGVER) |
@echo PACKAGER=$(PACKAGER) |
@echo prefix=$(prefix) |
@echo DESTDIR=$(DESTDIR) |
@echo INSTALLROOT=$(INSTALLROOT) |
@echo MANROOT=$(MANROOT) |
@echo PYTHONROOT=$(PYTHONROOT) |
@echo INSTALL_LAYOUT=$(INSTALL_LAYOUT) |
distclean: clean |
-$(RM) $(PKGVER).tar.gz $(PKGVER).zip |
-$(RM) -r dist build $(PKGVER)/ |
test_min_python: pyqver2.py |
python pyqver2.py -v -m 2.4 src/$(PKG).py src/$(PKG)/*.py |
install: |
install -D -m755 $(PKG).py $(INSTALLROOT)/bin/$(PKG) |
install -D -m644 $(PKG).1 $(MANROOT)/man1/$(PKG).1 |
# Extra tools |
install -d $(INSTALLROOT)/share/doc/$(PKG)/extra |
for tool in gui.py devtools/*.py; do \ |
install -D -m755 $$tool $(INSTALLROOT)/share/doc/$(PKG)/extra/ ; \ |
done |
install: setup.py |
$(DUSETUP) install --prefix=$(DESTDIR)$(prefix) $(INSTALL_LAYOUT) |
# There's no distutils uninstall |
uninstall: |
-$(RM) $(INSTALLROOT)/bin/$(PKG) $(INSTALLROOT)/share/man/man1/$(PKG).1 |
-for tool in gui.py devtools/*.py; do \ |
$(RM) $(INSTALLROOT)/share/doc/$(PKG)/extra/`basename $$tool` ; \ |
done |
-$(RM) $(PYTHONROOT)/$(PKG)/* |
-$(RM) $(PYTHONROOT)/$(PKGVER).egg-info |
-rmdir --parents $(INSTALLROOT)/share/doc/$(PKG)/extra/ \ |
$(MANROOT)/man1/ \ |
$(INSTALLROOT)/bin/ |
$(INSTALLROOT)/bin/ \ |
$(PYTHONROOT)/$(PKG)/ |
clean: |
-$(RM) *.pyc *.pyo manpage.html manpage.pdf $(PKG).spec setup.py |
distclean: clean |
-$(RM) $(PKGVER).tar.gz $(PKGVER).zip |
-$(RM) -r dist build $(PKGVER)/ pyqver2.py |
dist: tarball zip |
-$(RM) -r $(PKGVER) |
tarball: $(PKGVER).tar.gz |
zip: $(PKGVER).zip |
rpm: $(PKGVER).tar.gz $(PKG).spec |
@# XXX: ??? Combined doesn't catch Build-Requires |
rpmbuild -tb $< && rpmbuild -ts $< |
$(PKGVER).tar.gz: is_release distclean $(PKG).spec setup.py |
$(DUSETUP) sdist -u root -g root --formats=gztar && mv dist/$@ . |
-@rmdir dist |
$(PKGVER).zip: is_release distclean manpage.pdf manpage.html $(PKG).spec setup.py |
@# Specifial manifest with additional files |
cp MANIFEST.in MANIFEST.in.tmp |
echo include manpage.pdf manpage.html >> MANIFEST.in.tmp |
$(DUSETUP) sdist --formats=zip --template MANIFEST.in.tmp && mv dist/$@ . |
-$(RM) MANIFEST.in.tmp MANIFEST |
-@rmdir dist |
setup.py: devtools/setup.py.in |
sed 's/@VERSION@/$(VERSION)/g' < $< > $@ |
$(PKG).1: manpage_source.sgml |
docbook-to-man $< > $@ |
62,7 → 120,7 |
is_release: |
# Only allowed if RELEASE |
printf 'import relevation\nif not relevation.RELEASE:\n\traise Exception("RELEASE is False")' | python - |
bash devtools/relver.bash | grep -q -v 'DEBUG' |
package_copy: |
@# Make a temporary copy to package |
69,19 → 127,6 |
-mkdir $(PKGVER) |
tar c . $(TAR_EXCLUDES) | ( cd $(PKGVER) && tar x ) |
dist: $(PKGVER).tar.gz $(PKGVER).zip |
-$(RM) -r $(PKGVER) |
$(PKGVER).tar.gz: is_release distclean $(PKG).spec package_copy |
tar cv $(PKGVER) | gzip -c9 > $(PKGVER).tar.gz |
zip: $(PKGVER).zip |
$(PKGVER).zip: is_release distclean manpage.pdf manpage.html $(PKG).spec package_copy |
cp manpage.pdf manpage.html $(PKGVER) |
zip -9 -r $(PKGVER).zip $(PKGVER) |
-$(RM) $(PKGVER)/manpage.pdf $(PKGVER)/manpage.html |
$(PKG).spec: $(PKG).spec.in |
test -n "$(VERSION)" # Version (=$(VERSION)) must be defined |
@echo "[creating $@]" |
88,7 → 133,10 |
@cat "$<" | sed -e 's/@VERSION@/$(VERSION)/g' \ |
-e 's/@PACKAGER@/$(PACKAGER)/g' > "$@" |
pyqver2.py: |
wget https://github.com/ghewgill/pyqver/raw/master/pyqver2.py |
exe: |
python setup_py2exe.py py2exe |
python win/setup_py2exe.py py2exe |
.PHONY: testman clean distclean dist exe is_release zip |
/relevation/trunk/src/relevation.py |
---|
0,0 → 1,672 |
#!/usr/bin/env python |
# -*- coding: UTF-8 -*- |
""" |
Relevation Password Printer |
a command line interface to Revelation Password Manager. |
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 |
(ref4) http://pymotw.com/2/getpass/ |
$Id$ |
""" |
# Relevation Password Printer |
# |
# Copyright (c) 2011,2012,2013,2014 Toni Corvera |
# All rights reserved. |
# |
# Redistribution and use in source and binary forms, with or without |
# modification, are permitted provided that the following conditions are |
# met: |
# 1. Redistributions of source code must retain the above copyright |
# notice, this list of conditions and the following disclaimer. |
# 2. Redistributions in binary form must reproduce the above copyright |
# notice, this list of conditions and the following disclaimer in the |
# documentation and/or other materials provided with the distribution. |
# |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
# AND EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
# POSSIBILITY OF SUCH DAMAGE. |
import ConfigParser |
import getopt |
import getpass |
from lxml import etree |
import os |
import stat |
import string |
import sys |
import zlib |
# Help py2exe in packaging lxml |
# <http://www.py2exe.org/index.cgi/WorkingWithVariousPackagesAndModules> |
import lxml._elementpath as _dummy |
import gzip # py2exe again |
import hashlib # required by newer format |
# PBKDF2 stolen from Revelation |
from relevation import PBKDF2 |
USE_PYCRYPTO = True |
try: |
from Crypto.Cipher import AES |
except ImportError: |
USE_PYCRYPTO = False |
try: |
from crypto.cipher import rijndael, cbc |
from crypto.cipher.base import noPadding |
except ImportError: |
sys.stderr.write('Either PyCrypto or cryptopy is required\n') |
raise |
RELEASE=True |
__author__ = 'Toni Corvera' |
__date__ = '$Date$' |
__revision__ = '$Rev$' |
__version_info__ = ( 1, 3 ) #, 0 ) # Note: For x.y.0, only x and y are kept |
if not RELEASE: |
import traceback |
__version_info__ += ( '0-pre1', ) |
__version__ = '.'.join(map(str, __version_info__)) |
# These are pseudo-standardized exit codes, in Linux (*NIX?) they are defined |
#+in the header </usr/include/sysexits.h> and available as properties of 'os' |
#+In windows they aren't defined at all |
if 'EX_OK' not in dir(os): |
# If not defined set them manually |
codes = { 'EX_OK': 0, 'EX_USAGE': 64, 'EX_DATAERR': 65, |
'EX_NOINPUT': 66, 'EX_SOFTWARE': 70, 'EX_IOERR': 74, |
} |
for (k,v) in codes.items(): |
setattr(os, k, v) |
del codes, k, v |
TAGNAMES ={ 'generic-url': 'Url:', |
'generic-username': 'Username:', |
'generic-password': 'Password:', |
'generic-email': 'Email:', |
'generic-hostname': 'Hostname:', |
'generic-location': 'Location:', |
'generic-code': 'Code:', |
'generic-certificate': 'Certificate:', |
'generic-database': 'Database:', |
'generic-domain': 'Domain:', |
'generic-keyfile': 'Key file:', |
'generic-pin': 'PIN', |
'generic-port': 'Port' |
} |
MODE_AND='and' |
MODE_OR='or' |
# Errors |
class RlvError(Exception): |
def __str__(self): |
return self.msg |
class DecryptError(RlvError): |
exitCode = os.EX_DATAERR |
def __init__(self, msg = 'Failed to decrypt data. Wrong password?'): |
self.msg = msg |
class DecompressError(RlvError): |
exitCode = os.EX_DATAERR |
def __init__(self, msg = 'Failed to decompress data.'): |
self.msg = msg |
class DataFormatError(RlvError): |
exitCode = os.EX_DATAERR |
def __init__(self, msg = 'Incorrect data format'): |
self.msg = msg |
class DataVersionError(RlvError): |
exitCode = os.EX_DATAERR |
def __init__(self, msg = 'Data format version not supported'): |
self.msg = msg |
def printe(s): |
' Print to stderr ' |
sys.stderr.write(s+'\n') |
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('\nOptions:\n') |
# Reference: 80 characters |
# ------------------------------------------------------------------------------- |
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(' -A, --and When multiple search terms are used, use an AND\n') |
p(' operator to combine them.\n') |
p(' -O, --or When multiple search terms are used, use an OR\n') |
p(' operator to combine them.\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 make_xpath_query(search_text=None, type_filter=None, ignore_case=True, negate_filter=False): |
''' Construct the actual XPath expression |
make_xpath_query(str, str, bool, bool) -> str |
or |
make_xpath_query(list, str, bool, bool) -> str |
Passing a list as the second argument implies combining its elements |
in the search (AND) |
''' |
xpath = '/revelationdata//entry' |
if type_filter: |
sign = '=' |
if negate_filter: |
sign = '!=' |
xpath = '%s[@type%s"%s"]' % ( xpath, sign, type_filter ) |
if type_filter != 'folder': |
# Avoid printing folders since all their children are printed |
# alongside |
xpath += '[@type!="folder"]' |
if search_text: |
#xpath = xpath + '//text()' |
needles = [] |
if type(search_text) == str: |
needles = [ search_text, ] |
else: |
needles = search_text |
selector = '' |
for search in needles: |
if ignore_case: |
# must pass lowercase to actually be case insensitive |
search = string.lower(search) |
# XPath 2.0 has lower-case, upper-case, matches(..., -i) etc. |
selector += '//text()[contains(translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), "%s")]/../..' % search |
else: |
selector += '//text()[contains(., "%s")]/../..' % search |
xpath = '%s%s' % ( xpath, selector ) |
if not RELEASE: |
printe("> Xpath: %s\n" % xpath) |
return xpath |
def dump_all_entries(xmldata): |
' Dump all entries from xmldata, with no filter at all ' |
tree = etree.fromstring(xmldata) |
res = tree.xpath('//entry') |
return dump_result(res, 'all') |
def dump_entries(xmldata, search_text=None, type_filter=None, ignore_case=True, negate_filter=False): |
''' Dump entries from xmldata that match criteria |
dump_entries(str, str, str, bool, bool) -> int |
or |
dump_entries(str, list, str, bool, bool) -> int |
''' |
tree = etree.fromstring(xmldata) |
xpath = make_xpath_query(search_text, type_filter, ignore_case, negate_filter) |
try: |
res = tree.xpath(xpath) |
except etree.XPathEvalError: |
if not RELEASE: |
printe('Failed with xpath expression: %s' % xpath) |
raise |
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) |
return nr |
def dump_single_result(typeName, name, descr, notes, fields): |
printe('-------------------------------------------------------------------------------') |
s = '\n' |
s += 'Type: %s\n' % typeName |
s += 'Name: %s\n' % name |
s += 'Description: %s\n' % descr |
s += 'Notes: %s\n' % notes |
for field in fields: |
s += '%s %s\n' % field # field, value |
#s += '\n' |
print s |
def dump_result(res, query_desc, dumpfn=dump_single_result): |
''' Print query results. |
dump_result(list of entries, query description) -> int |
''' |
print '-> Search %s: ' % query_desc, |
if not len(res): |
print 'No results' |
return False |
print '%d matches' % len(res) |
for x in res: |
typeName = x.get('type') |
name = None |
descr = None |
fields = [] |
notes = None |
for chld in x.getchildren(): |
n = chld.tag |
val = chld.text |
if val is None: |
val = '' |
if n == 'name': |
name = val |
elif n == 'description': |
descr = val |
elif n == 'field': |
idv = chld.get('id') |
if idv in TAGNAMES: |
idv = TAGNAMES[idv] |
val = chld.text |
if val is None: |
val = '' |
# Maintain order => list |
fields += [ ( idv, val ), ] |
elif n == 'notes': |
notes = val |
dumpfn(typeName, name, descr, notes, fields) |
# / for chld in x.children |
nr = len(res) |
plural = '' |
if nr > 1: |
plural = 's' |
printe('-------------------------------------------------------------------------------') |
printe('<- (end of %d result%s for {%s})\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 |
st = os.stat(path) |
return bool(st.st_mode & stat.S_IROTH) |
def load_config(): |
''' Load configuration file if one is found |
load_config() -> ( str file, str pass ) |
''' |
cfg = os.path.join(os.path.expanduser('~'), '.relevation.conf') |
pw = None |
fl = None |
mode = MODE_OR |
if os.path.isfile(cfg): |
if os.access(cfg, os.R_OK): |
wr = world_readable(cfg) |
if wr and sys.platform != 'win32': |
printe('Configuration (~/.relevation.conf) is world-readable!!!') |
parser = ConfigParser.ConfigParser() |
parser.read(cfg) |
ops = parser.options('relevation') |
if 'file' in ops: |
fl = os.path.expanduser(parser.get('relevation', 'file')) |
if 'password' in ops: |
if wr: # TODO: how to check in windows? |
printe('Your password can be read by anyone!!!') |
pw = parser.get('relevation', 'password') |
if 'mode' in ops: |
mode = parser.get('relevation', 'mode') |
if mode not in [ MODE_AND, MODE_OR ]: |
printe('Warning: Unknown mode \'%s\' set in configuration' % mode) |
mode=MODE_OR |
else: # exists but not readable |
printe('Configuration file (~/.relevation.conf) is not readable!') |
return ( fl, pw, mode ) |
class _DataReaderBase(object): |
' Common methods for reading data files ' |
def validate_compressed_padding(self, data): |
''' Checks that the gzip-compressed 'data' is padded correctly. |
validate_compressed_padding(str) -> bool |
''' |
padlen = ord(data[-1]) |
for i in data[-padlen:]: |
if ord(i) != padlen: |
return False |
return True |
def validate_cipher_length(self, data): |
''' Checks that encrypted 'data' has an appropriate length. |
validate_cipher_length(str) -> bool |
Encrypted data length must be a multiple of 16 |
''' |
return ( len(data) % 16 == 0 ) |
def _aes_decrypt_ecb(self, key, data): |
''' Decrypt AES cipher text in ECB mode |
_aes_decrypt_ecb(str, str) -> str |
This function will use the underlying, available, cipher module. |
''' |
if USE_PYCRYPTO: |
c = AES.new(key) |
cleardata = c.decrypt(data) |
else: |
c = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) |
cleardata = c.decrypt(data) |
return cleardata |
def _aes_decrypt_cbc(self, key, iv, data): |
''' Decrypt AES cipher text in CBC mode |
_aes_decrypt_ecb(str, str, str) -> str |
This function will use the underlying, available, cipher module. |
''' |
if USE_PYCRYPTO: |
c = AES.new(key, AES.MODE_CBC, iv) |
cleardata = c.decrypt(data) |
else: |
bc = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) |
c = cbc.CBC(bc, padding=noPadding()) |
cleardata = c.decrypt(data, iv=iv) |
return cleardata |
def get_xml(self, data, password): |
''' Extract the XML contents from the encrypted and compressed input. |
get_xml(str, str) -> str |
''' |
pass |
class DataReaderV1(_DataReaderBase): |
''' Data reading for Revelation data files in the original format. |
Old format header: |
[0:12) 12B header: "rvl" 0x00, 0x01, 0x00 |
[12:28) 16B ECB encrypted IV (for CBC-encrypted data) |
[28:] CBC encrypted data |
''' |
def _decrypt_compressed_data(self, password, cipher_text): |
''' Decrypt cipher_text using password. |
_decrypt_compressed_data(str, str) -> cleartext (gzipped xml) |
''' |
# Minimum length of header |
if len(cipher_text) < 28: |
raise DataFormatError |
# Key <= Padded password |
key = password |
key += (chr(0) * (32 - len(password))) |
# Extract IV |
iv = self._aes_decrypt_ecb(key, cipher_text[12:28]) |
# Skip IV |
cipher_text = cipher_text[28:] |
# Input strings for decrypt must be a multiple of 16 in length |
if not self.validate_cipher_length(cipher_text): |
raise DataFormatError |
# Decrypt data, CBC mode |
return self._aes_decrypt_cbc(key, iv, cipher_text) |
def get_xml(self, data, password): |
# Decrypt. Decrypted data is compressed |
cleardata_gz = self._decrypt_compressed_data(password, data) |
# Validate padding for decompression |
if not self.validate_compressed_padding(cleardata_gz): |
raise DataFormatError |
# Decompress actual data (15 is wbits [ref3] DON'T CHANGE, 2**15 is the (initial) buf size) |
padlen = ord(cleardata_gz[-1]) |
try: |
return zlib.decompress(cleardata_gz[:-padlen], 15, 2**15) |
except zlib.error: |
raise DecompressError |
class DataReaderV2(_DataReaderBase): |
''' Data reading for Revelation data files in the new format. |
New format header: |
[0:12) 12B header: "rvl" 0x00, 0x02, 0x00 |
[12:20) 8B salt |
[20:36) 16B IV (for CBC-encrypted data) |
[36:] CBC encrypted data |
The encryption key is derived from the password and salt through the |
PBKDF2 module. |
''' |
def _decrypt_compressed_data(self, password, cipher_text): |
# Minimum length of header |
if len(cipher_text) < 36: |
raise DataFormatError |
salt = cipher_text[12:20] |
iv = cipher_text[20:36] |
key = PBKDF2.PBKDF2(password, salt, iterations=12000).read(32) |
# Skip encryption header |
cipher_text = cipher_text[36:] |
if not self.validate_cipher_length(cipher_text): |
raise DataFormatError |
# Decrypt data (CBC) |
decrypted = self._aes_decrypt_cbc(key, iv, cipher_text) |
sha256hash = decrypted[0:32] |
# Skip hash. decrypted <= Decrypted, Compressed data |
decrypted = decrypted[32:] |
# Validate hash |
if sha256hash != hashlib.sha256(decrypted).digest(): |
raise DecryptError |
return decrypted |
def get_xml(self, data, password): |
# Decrypt... |
cleardata_gz = self._decrypt_compressed_data(password, data) |
# Validate padding for decompression |
if not self.validate_compressed_padding(cleardata_gz): |
raise DataFormatError |
# Decompress |
padlen = ord(cleardata_gz[-1]) |
try: |
return zlib.decompress(cleardata_gz[:-padlen]) |
except zlib.error: |
raise DecompressError |
class DataReader(object): |
''' Interface to read Revelation's data files ''' |
def __init__(self, filename): |
''' Loads file data and checks file format and data version for compatibility |
DataReader(str) |
raises IOError If 'filename' not readable |
raises DataFormatError If 'filename' not in Revelation format |
raises DataVersionError If filename not in a supported data format version |
''' |
self._impl = None |
self._data = None |
self._filename = filename |
f = None |
try: |
if not os.access(filename, os.R_OK): |
raise IOError('File \'%s\' not accessible' % filename) |
f = open(filename, "rb") |
# Encrypted data |
self._data = f.read() |
finally: |
if f: |
f.close() |
self._check_header() |
def _check_header(self): |
''' Checks the file header for compatibility. |
_check_header() -> None |
raises DataFormatError If the file isn't a Revelation data file |
raises DataVersionError If the data format is in an unsupported version |
''' |
header = self._data[0:12] |
magic = header[0:4] |
if magic != "rvl\x00": |
raise DataFormatError |
data_version = header[4] |
app_version = header[6:9] |
if data_version == '\x01': |
self._impl = DataReaderV1() |
elif data_version == '\x02': |
self._impl = DataReaderV2() |
else: |
raise DataVersionError |
def get_xml(self, password): |
''' Decrypt and decompress file data |
get_xml(str) -> str |
''' |
return self._impl.get_xml(self._data, password) |
def main(argv): |
datafile = None |
password = None |
# values to search for |
needles = [] |
caseInsensitive = True |
# individual search: ( 'value to search', 'type of search', 'type of entry to filter' ) |
searchTypes = [] |
dump_xml = False |
mode = None |
printe('Relevation v%s, (c) 2011-2014 Toni Corvera\n' % __version__) |
# ---------- OPTIONS ---------- # |
( datafile, password, mode ) = load_config() |
try: |
# gnu_getopt requires py >= 2.3 |
ops, args = getopt.gnu_getopt(argv, 'f:p:s:0ciaht:xAO', |
[ 'file=', 'password=', 'search=', 'stdin', |
'case-sensitive', 'case-insensitive', 'ask', |
'help', 'version', 'type=', 'xml', |
'and', 'or' ]) |
except getopt.GetoptError, err: |
print str(err) |
usage(sys.stderr) |
sys.exit(os.EX_USAGE) |
if args: |
needles = 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 |
if USE_PYCRYPTO: |
import Crypto |
print 'PyCrypto version %s' % Crypto.__version__ |
else: |
# AFAIK cryptopy doesn't export version info |
print 'cryptopy' |
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' ): |
prompt = '' |
if opt in ( '-a', '--ask' ): |
prompt = 'File password: ' |
# see [ref4] |
if sys.stdin.isatty(): |
password = getpass.getpass(prompt=prompt, stream=sys.stderr) |
else: |
# Not a terminal, getpass won't work |
password = sys.stdin.readline(); |
password = password[:-1] # XXX: would .rstrip() be safe enough? |
elif opt in ( '-s', '--search' ): |
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' ): |
printe('Warning: Type "%s" is not known by relevation.' % arg) |
searchTypes.append( ( iarg, neg ) ) |
elif opt in ( '-x', '--xml' ): |
dump_xml = True |
elif opt in ( '-A', '--and' ): |
mode = MODE_AND |
elif opt in ( '-O', '--or' ): |
mode = MODE_OR |
else: |
printe('Unhandled option: %s' % opt) |
assert False, "internal error parsing options" |
if not datafile or not password: |
usage(sys.stderr) |
if not datafile: |
printe('Input password filename is required') |
if not password: |
printe('Password is required') |
sys.exit(os.EX_USAGE) |
# ---------- PASSWORDS FILE DECRYPTION AND DECOMPRESSION ---------- # |
xmldata = DataReader(datafile).get_xml(password) |
# ---------- QUERIES ---------- # |
if dump_xml: |
print xmldata |
sys.exit(os.EX_OK) |
# 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 |
if mode == MODE_OR: |
for text in needles: |
numhits += dump_entries(xmldata, text, 'folder', caseInsensitive, True) |
else: |
assert mode == MODE_AND, "Unknown boolean operation mode" |
numhits += dump_entries(xmldata, needles, 'folder', caseInsensitive, True) |
elif needles: |
if mode == MODE_OR: # 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 combined search, filter for each type |
assert mode == MODE_AND, "Unknown boolean operation mode" |
for ( sfilter, negate ) in searchTypes: |
numhits += dump_entries(xmldata, needles, 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(sys.argv[1:]) |
except RlvError as e: |
printe('Error: %s' % e.msg) |
if not RELEASE: |
traceback.print_exc() |
sys.exit(e.exitCode) |
except etree.XMLSyntaxError as e: |
printe('XML parsing error') |
if not RELEASE: |
traceback.print_exc() |
sys.exit(os.EX_DATAERR) |
except IOError as e: |
if not RELEASE: |
traceback.print_exc() |
printe(str(e)) |
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 |
/relevation/trunk/src/relevation/PBKDF2.py |
---|
0,0 → 1,297 |
#!/usr/bin/python |
# -*- coding: ascii -*- |
########################################################################### |
# pbkdf2 - PKCS#5 v2.0 Password-Based Key Derivation |
# |
# Copyright (C) 2007-2011 Dwayne C. Litzenberger <dlitz@dlitz.net> |
# |
# Permission is hereby granted, free of charge, to any person obtaining |
# a copy of this software and associated documentation files (the |
# "Software"), to deal in the Software without restriction, including |
# without limitation the rights to use, copy, modify, merge, publish, |
# distribute, sublicense, and/or sell copies of the Software, and to |
# permit persons to whom the Software is furnished to do so, subject to |
# the following conditions: |
# |
# The above copyright notice and this permission notice shall be |
# included in all copies or substantial portions of the Software. |
# |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
# |
# Country of origin: Canada |
# |
########################################################################### |
# Sample PBKDF2 usage: |
# from Crypto.Cipher import AES |
# from pbkdf2 import PBKDF2 |
# import os |
# |
# salt = os.urandom(8) # 64-bit salt |
# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key |
# iv = os.urandom(16) # 128-bit IV |
# cipher = AES.new(key, AES.MODE_CBC, iv) |
# ... |
# |
# Sample crypt() usage: |
# from pbkdf2 import crypt |
# pwhash = crypt("secret") |
# alleged_pw = raw_input("Enter password: ") |
# if pwhash == crypt(alleged_pw, pwhash): |
# print "Password good" |
# else: |
# print "Invalid password" |
# |
########################################################################### |
__version__ = "1.3" |
__all__ = ['PBKDF2', 'crypt'] |
from struct import pack |
from random import randint |
import string |
import sys |
try: |
# Use PyCrypto (if available). |
from Crypto.Hash import HMAC, SHA as SHA1 |
except ImportError: |
# PyCrypto not available. Use the Python standard library. |
import hmac as HMAC |
try: |
from hashlib import sha1 as SHA1 |
except ImportError: |
# hashlib not available. Use the old sha module. |
import sha as SHA1 |
# |
# Python 2.1 thru 3.2 compatibility |
# |
if sys.version_info[0] == 2: |
_0xffffffffL = long(1) << 32 |
def isunicode(s): |
return isinstance(s, unicode) |
def isbytes(s): |
return isinstance(s, str) |
def isinteger(n): |
return isinstance(n, (int, long)) |
def b(s): |
return s |
def binxor(a, b): |
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)]) |
def b64encode(data, chars="+/"): |
tt = string.maketrans("+/", chars) |
return data.encode('base64').replace("\n", "").translate(tt) |
from binascii import b2a_hex |
else: |
_0xffffffffL = 0xffffffff |
def isunicode(s): |
return isinstance(s, str) |
def isbytes(s): |
return isinstance(s, bytes) |
def isinteger(n): |
return isinstance(n, int) |
def callable(obj): |
return hasattr(obj, '__call__') |
def b(s): |
return s.encode("latin-1") |
def binxor(a, b): |
return bytes([x ^ y for (x, y) in zip(a, b)]) |
from base64 import b64encode as _b64encode |
def b64encode(data, chars="+/"): |
if isunicode(chars): |
return _b64encode(data, chars.encode('utf-8')).decode('utf-8') |
else: |
return _b64encode(data, chars) |
from binascii import b2a_hex as _b2a_hex |
def b2a_hex(s): |
return _b2a_hex(s).decode('us-ascii') |
xrange = range |
class PBKDF2(object): |
"""PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation |
This implementation takes a passphrase and a salt (and optionally an |
iteration count, a digest module, and a MAC module) and provides a |
file-like object from which an arbitrarily-sized key can be read. |
If the passphrase and/or salt are unicode objects, they are encoded as |
UTF-8 before they are processed. |
The idea behind PBKDF2 is to derive a cryptographic key from a |
passphrase and a salt. |
PBKDF2 may also be used as a strong salted password hash. The |
'crypt' function is provided for that purpose. |
Remember: Keys generated using PBKDF2 are only as strong as the |
passphrases they are derived from. |
""" |
def __init__(self, passphrase, salt, iterations=1000, |
digestmodule=SHA1, macmodule=HMAC): |
self.__macmodule = macmodule |
self.__digestmodule = digestmodule |
self._setup(passphrase, salt, iterations, self._pseudorandom) |
def _pseudorandom(self, key, msg): |
"""Pseudorandom function. e.g. HMAC-SHA1""" |
return self.__macmodule.new(key=key, msg=msg, |
digestmod=self.__digestmodule).digest() |
def read(self, bytes): |
"""Read the specified number of key bytes.""" |
if self.closed: |
raise ValueError("file-like object is closed") |
size = len(self.__buf) |
blocks = [self.__buf] |
i = self.__blockNum |
while size < bytes: |
i += 1 |
if i > _0xffffffffL or i < 1: |
# We could return "" here, but |
raise OverflowError("derived key too long") |
block = self.__f(i) |
blocks.append(block) |
size += len(block) |
buf = b("").join(blocks) |
retval = buf[:bytes] |
self.__buf = buf[bytes:] |
self.__blockNum = i |
return retval |
def __f(self, i): |
# i must fit within 32 bits |
assert 1 <= i <= _0xffffffffL |
U = self.__prf(self.__passphrase, self.__salt + pack("!L", i)) |
result = U |
for j in xrange(2, 1+self.__iterations): |
U = self.__prf(self.__passphrase, U) |
result = binxor(result, U) |
return result |
def hexread(self, octets): |
"""Read the specified number of octets. Return them as hexadecimal. |
Note that len(obj.hexread(n)) == 2*n. |
""" |
return b2a_hex(self.read(octets)) |
def _setup(self, passphrase, salt, iterations, prf): |
# Sanity checks: |
# passphrase and salt must be str or unicode (in the latter |
# case, we convert to UTF-8) |
if isunicode(passphrase): |
passphrase = passphrase.encode("UTF-8") |
elif not isbytes(passphrase): |
raise TypeError("passphrase must be str or unicode") |
if isunicode(salt): |
salt = salt.encode("UTF-8") |
elif not isbytes(salt): |
raise TypeError("salt must be str or unicode") |
# iterations must be an integer >= 1 |
if not isinteger(iterations): |
raise TypeError("iterations must be an integer") |
if iterations < 1: |
raise ValueError("iterations must be at least 1") |
# prf must be callable |
if not callable(prf): |
raise TypeError("prf must be callable") |
self.__passphrase = passphrase |
self.__salt = salt |
self.__iterations = iterations |
self.__prf = prf |
self.__blockNum = 0 |
self.__buf = b("") |
self.closed = False |
def close(self): |
"""Close the stream.""" |
if not self.closed: |
del self.__passphrase |
del self.__salt |
del self.__iterations |
del self.__prf |
del self.__blockNum |
del self.__buf |
self.closed = True |
def crypt(word, salt=None, iterations=None): |
"""PBKDF2-based unix crypt(3) replacement. |
The number of iterations specified in the salt overrides the 'iterations' |
parameter. |
The effective hash length is 192 bits. |
""" |
# Generate a (pseudo-)random salt if the user hasn't provided one. |
if salt is None: |
salt = _makesalt() |
# salt must be a string or the us-ascii subset of unicode |
if isunicode(salt): |
salt = salt.encode('us-ascii').decode('us-ascii') |
elif isbytes(salt): |
salt = salt.decode('us-ascii') |
else: |
raise TypeError("salt must be a string") |
# word must be a string or unicode (in the latter case, we convert to UTF-8) |
if isunicode(word): |
word = word.encode("UTF-8") |
elif not isbytes(word): |
raise TypeError("word must be a string or unicode") |
# Try to extract the real salt and iteration count from the salt |
if salt.startswith("$p5k2$"): |
(iterations, salt, dummy) = salt.split("$")[2:5] |
if iterations == "": |
iterations = 400 |
else: |
converted = int(iterations, 16) |
if iterations != "%x" % converted: # lowercase hex, minimum digits |
raise ValueError("Invalid salt") |
iterations = converted |
if not (iterations >= 1): |
raise ValueError("Invalid salt") |
# Make sure the salt matches the allowed character set |
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" |
for ch in salt: |
if ch not in allowed: |
raise ValueError("Illegal character %r in salt" % (ch,)) |
if iterations is None or iterations == 400: |
iterations = 400 |
salt = "$p5k2$$" + salt |
else: |
salt = "$p5k2$%x$%s" % (iterations, salt) |
rawhash = PBKDF2(word, salt, iterations).read(24) |
return salt + "$" + b64encode(rawhash, "./") |
# Add crypt as a static method of the PBKDF2 class |
# This makes it easier to do "from PBKDF2 import PBKDF2" and still use |
# crypt. |
PBKDF2.crypt = staticmethod(crypt) |
def _makesalt(): |
"""Return a 48-bit pseudorandom salt for crypt(). |
This function is not suitable for generating cryptographic secrets. |
""" |
binarysalt = b("").join([pack("@H", randint(0, 0xffff)) for i in range(3)]) |
return b64encode(binarysalt, "./") |
# vim:set ts=4 sw=4 sts=4 expandtab: |
/relevation/trunk/src/relevation/__init__.py |
---|
--- trunk/src/gui.py (nonexistent) |
+++ trunk/src/gui.py (revision 611) |
@@ -0,0 +1,194 @@ |
+#!/usr/bin/env python |
+# -*- coding: UTF-8 -*- |
+ |
+""" |
+Relevation Password Printer |
+a command line interface to Revelation Password Manager. |
+ |
+Simplistic Graphical User Interface. |
+This GUI is mainly intended to be used in systems where command-lines |
+are less common, like Windows. |
+ |
+$Id$ |
+""" |
+# Relevation Password Printer |
+# |
+# Copyright (c) 2011, 2013, Toni Corvera |
+# All rights reserved. |
+# |
+# Redistribution and use in source and binary forms, with or without |
+# modification, are permitted provided that the following conditions are |
+# met: |
+# 1. Redistributions of source code must retain the above copyright |
+# notice, this list of conditions and the following disclaimer. |
+# 2. Redistributions in binary form must reproduce the above copyright |
+# notice, this list of conditions and the following disclaimer in the |
+# documentation and/or other materials provided with the distribution. |
+# |
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
+# AND EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
+# POSSIBILITY OF SUCH DAMAGE. |
+ |
+import sys |
+import re |
+import Tkinter as tk |
+from Tkinter import Frame, Button, Entry, Listbox, Scrollbar, Label, Radiobutton, StringVar |
+ |
+import relevation |
+ |
+__author__ = 'Toni Corvera' |
+__date__ = '$Date$' |
+__revision__ = '$Rev$' |
+__version_info__ = ( 1, 2, 1 ) |
+__version__ = '.'.join(map(str, __version_info__)) |
+ |
+old_fn = relevation.dump_result |
+ |
+def append_result(typeName, name, descr, notes, fields): |
+ global gui |
+ gui.lst.insert(tk.END, name) |
+ s = '\n' |
+ s += 'Type: %s\n' % typeName |
+ s += 'Name: %s\n' % name |
+ s += 'Description: %s\n' % descr |
+ s += 'Notes: %s\n' % notes |
+ for field in fields: |
+ s += '%s %s\n' % field |
+ gui.items.append(s) |
+ |
+def dump_result_override(res, query_desc): |
+ return old_fn(res, query_desc, append_result) |
+ |
+relevation.dump_result = dump_result_override |
+ |
+class ResultDialog: |
+ def __init__(self, parent, result): |
+ top = self.top = tk.Toplevel(parent) |
+ |
+ self.value = tk.Text(top) |
+ self.value.insert(tk.END, result) |
+ self.value.config(state=tk.DISABLED) |
+ self.value.pack() |
+ b = Button(top, text='OK', command=self.ok) |
+ b.pack(pady=5) |
+ |
+ top.bind('<Return>', lambda event: self.ok()) |
+ top.focus_set() |
+ |
+ def ok(self): |
+ self.top.destroy() |
+ |
+class GUI(object): |
+ def do_find(self): |
+ global rootw |
+ search = self.search_text.get() |
+ self.lst.delete(0, tk.END) |
+ self.items = [] |
+ mode = '-O' |
+ args = sys.argv[1:] + [ '-s', ] |
+ if self.mode.get() == relevation.MODE_AND: |
+ mode = '-A' |
+ search = search.split(' ') |
+ args += search |
+ args += [ mode, ] |
+ else: |
+ args += [ search, mode ] |
+ print args |
+ try: |
+ relevation.main(args) |
+ except SystemExit: |
+ # No matches -> Exit with 80 |
+ gui.lst.insert(tk.END, '<No matches>') |
+ self.items.append('<No passwords matched search>') |
+ |
+ def display(self): |
+ global rootw |
+ selected = self.lst.curselection() |
+ if not selected: |
+ return |
+ selected = int(selected[0]) |
+ item = self.items[selected] |
+ print item |
+ dlg = ResultDialog(rootw, item) |
+ rootw.wait_window(dlg.top) |
+ |
+ def __init__(self, master=None): |
+ self.master = master |
+ frame = Frame(master) |
+ frame.pack(expand=1, fill=tk.BOTH) |
+ self.items = [] |
+ self.frame = frame |
+ #top = master |
+ top = frame.winfo_toplevel() |
+ top.rowconfigure(0, weight=1) |
+ top.columnconfigure(0, weight=1, pad=5) |
+ frame.rowconfigure(0, weight=1, pad=5) |
+ frame.columnconfigure(0, weight=1) |
+ |
+ # Avoid printing to stderr |
+ def ignoreme(s): |
+ pass |
+ relevation.printe = ignoreme |
+ |
+ FILL = tk.N+tk.S+tk.E+tk.W |
+ BTNROW = 3 |
+ MODEROW = 2 |
+ RESROW = 1 |
+ # Populate |
+ self.search_text = Entry(self.frame) |
+ #self.search_text.pack({'expand': 1, 'side': 'top'}) |
+ self.search_text.grid(row=0, column=0, columnspan=4, padx=5, sticky=tk.N+tk.E+tk.W) |
+ self.search_text.bind('<Return>', lambda event: self.do_find()) |
+ |
+ self.quit = Button(self.frame, text='Quit', fg='red', command=frame.quit) |
+ #self.quit.pack(side=tk.LEFT) |
+ self.quit.grid(row=BTNROW, column=0, padx=10) |
+ |
+ self.search = Button(self.frame, text='Search', command=self.do_find) |
+ #self.search.pack(side=tk.RIGHT) |
+ self.search.grid(row=BTNROW, column=2, padx=5) |
+ |
+ self.mode = StringVar() |
+ |
+ mode = relevation.load_config()[2] |
+ self.mode.set(mode) |
+ |
+ rbO = Radiobutton(self.frame, text="OR/Literal", variable=self.mode, value=relevation.MODE_OR) |
+ rbA = Radiobutton(self.frame, text="AND", variable=self.mode, value=relevation.MODE_AND) |
+ rbO.grid(row=MODEROW, column=0) |
+ rbA.grid(row=MODEROW, column=1) |
+ |
+ self.view = Button(self.frame, text='View', command=self.display) |
+ #self.view.pack(side=tk.RIGHT) |
+ self.view.grid(row=BTNROW, column=1) |
+ |
+ ## FIXME |
+ scrollbar = Scrollbar(self.frame, orient=tk.VERTICAL) |
+ scrollbar.grid(row=RESROW, column=4, sticky=FILL) |
+ |
+ self.lst = Listbox(self.frame) |
+ #self.lst.pack() |
+ self.lst.grid(row=RESROW, column=0, columnspan=3, sticky=FILL) |
+ self.lst.bind('<Double-Button-1>', lambda event: self.display()) |
+ |
+ self.lst.config(yscrollcommand=scrollbar.set) |
+ scrollbar.config(command=self.lst.yview) |
+ |
+ self.search_text.focus_set() |
+ |
+if __name__ == '__main__': |
+ rootw = tk.Tk() |
+ rootw.title('Relevation search v' + __version__) |
+ gui = GUI(master=rootw) |
+ rootw.mainloop() |
+ #rootw.destroy() |
+ |
+# vim:set ts=4 et ai fileencoding=utf-8: # |
/trunk/src/gui.py |
---|
Property changes: |
Added: svn:executable |
+* |
\ No newline at end of property |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/trunk/MANIFEST.in |
---|
0,0 → 1,6 |
# $Id$ |
include CHANGELOG GNUmakefile LICENSE manpage_source.sgml README.Windows.txt relevation.spec relevation.spec.in |
graft debian |
graft devtools |
graft win |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/trunk/devtools/sample.datav1.revelation |
---|
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes: |
Added: svn:mime-type |
+application/octet-stream |
\ No newline at end of property |
/relevation/trunk/devtools/sample.datav2.revelation |
---|
Cannot display: file marked as a binary type. |
svn:mime-type = application/octet-stream |
Property changes: |
Added: svn:mime-type |
+application/octet-stream |
\ No newline at end of property |
/relevation/trunk/devtools/relver.bash |
---|
0,0 → 1,25 |
#!/bin/bash |
# $Id$ |
# Quick and dirty script to retrieve the script version |
# (which is otherwise composed at runtime) |
# This scripts avoid having the same runtime requirements for package |
# creation |
r="$(dirname "$0")/../src/relevation.py" |
rel=$(grep '^RELEASE' $r | tail -1) |
verinfo=$(grep '^__version_info__' $r | head -1) |
verinfo_pre=$(grep '__version_info__ +=' $r | head -1 | sed 's/^[[:space:]]*//') |
ver=$(grep '^__version__' $r | head -1) |
python <<EOF |
$rel |
$verinfo |
release='' |
if not RELEASE: |
$verinfo_pre |
release=' [DEBUG]' |
$ver |
print "%s%s" % ( __version__, release ) |
EOF |
Property changes: |
Added: svn:executable |
+* |
\ No newline at end of property |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/trunk/devtools/setup.py.in |
---|
0,0 → 1,45 |
#!/usr/bin/env python |
# $Id$ |
# This script isn't used directly to generate the distributed files |
# since some files are generated from the source and the zip and tar.gz |
# differ. |
# GNU Make is used instead. |
from distutils.core import setup |
import distutils.command.install_scripts |
import shutil |
class my_install(distutils.command.install_scripts.install_scripts): |
''' Rename scripts that use the .py extension after installation. |
NOTE: breaks bdist_rpm |
Reference: http://stackoverflow.com/questions/4359231/rename-script-file-in-distutils |
''' |
def run(self): |
distutils.command.install_scripts.install_scripts.run(self) |
for script in self.get_outputs(): |
if script.endswith(".py"): |
shutil.move(script, script[:-3]) |
setup(name='relevation', |
version='@VERSION@', |
author='Toni Corvera', |
author_email='outlyer@gmail.com', |
url='http://p.outlyer.net/relevation', |
description='Command-line interface to query Revelation files', |
long_description= |
'''This is a command-line tool capable of retrieving passwords from a Revelation password file. |
Please note files can only be searched, not edited with this tool.''', |
license='BSD', |
package_dir={'': 'src'}, |
packages=['relevation'], |
scripts=['src/relevation.py'], |
cmdclass = {"install_scripts": my_install}, # hook |
data_files=[ |
('share/man/man1', ['relevation.1']), |
#('share/doc/relevation', ['CHANGELOG']), |
('share/doc/relevation/extra', ['src/gui.py','devtools/checkpw.py','devtools/genpw.py']), |
] |
) |
# vim:set ts=4 et ai: # |
Property changes: |
Added: svn:keywords |
+Rev Id Date |
\ No newline at end of property |
/relevation/trunk/devtools/README |
---|
0,0 → 1,2 |
The provided sample files use "1234" as password |
/relevation/trunk/debian/README.Debian |
---|
5,4 → 5,7 |
packaging scripts. Those are not written by a Debian developer |
so they may inadvertently break packaging rules or conventions. |
-- Toni Corvera <outlyer@gmail.com> Mon, 27 Jun 2011 01:44:10 +0200 |
The minimum required Python version is set based on pyqver |
(https://github.com/ghewgill/pyqver/tree/master) |
-- Toni Corvera <outlyer@gmail.com> Fri, 23 May 2014 01:04:59 +0200 |
/relevation/trunk/debian/control |
---|
2,15 → 2,16 |
Section: contrib/utils |
Priority: extra |
Maintainer: Toni Corvera <outlyer@gmail.com> |
Build-Depends: debhelper (>= 7.0.50~), python (>= 2.3) |
Build-Depends: debhelper (>= 7.0.50~), python-support (>= 0.90) |
X-Python-Version: >= 2.5 |
Standards-Version: 3.9.1 |
Homepage: http://p.outlyer.net/relevation/ |
#Vcs-Git: git://git.debian.org/collab-maint/relevation.git |
#Vcs-Browser: http://git.debian.org/?p=collab-maint/relevation.git;a=summary |
Vcs-Svn: https://svn.outlyer.net/svn/pub/relevation |
Vcs-Browser: https://svn.outlyer.net/websvn/wsvn/pub/relevation/ |
Package: relevation |
Architecture: all |
Depends: ${shlibs:Depends}, ${misc:Depends}, python (>= 2.3), python-lxml, python-crypto |
Depends: ${shlibs:Depends}, ${misc:Depends}, python-lxml, python-crypto, ${python:Depends} |
Recommends: revelation |
Description: Command-line interface to query Revelation files |
This is a command-line tool capable of retrieving passwords from |
/relevation/trunk/debian/changelog |
---|
1,3 → 1,9 |
relevation (1.3-pon.1) unstable; urgency=medium |
* New version |
-- Toni Corvera <outlyer@gmail.com> Wed, 21 May 2014 19:02:49 +0200 |
relevation (1.2.1-pon.1) unstable; urgency=low |
* New version |
/relevation/trunk/debian/rules |
---|
9,9 → 9,12 |
# Uncomment this to turn on verbose mode. |
#export DH_VERBOSE=1 |
# install dir shorthand |
RLVINST=$(CURDIR)/debian/relevation |
%: |
dh $@ |
override_dh_auto_install: |
$(MAKE) DESTDIR=$(CURDIR)/debian/relevation prefix=/usr install |
$(MAKE) DESTDIR=$(RLVINST) prefix=/usr install |
/relevation/trunk |
---|
Property changes: |
Modified: svn:ignore |
relevation.spec |
+setup.py |
+MANIFEST |
Modified: svn:mergeinfo |
## -0,0 +0,1 ## |
Merged /relevation/branches/1.3:r587-610 |