Source code for dispass.filehandler

'''Dispass labelfile handler'''

# Copyright (c) 2012-2016  Tom Willemse <tom@ryuslash.org>
# Copyright (c) 2011-2018  Benjamin Althues <benjamin@babab.nl>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import datetime
import os
from os.path import expanduser, exists

from dispass.dispass import __version__


[docs]class Filehandler: '''Parsing of labelfiles and writing to labelfiles''' filehandle = None '''File object, set on init if labelfile is found''' file_found = None '''Boolean value set on init''' file_location = None '''String of labelfile location, set on init''' labelfile = [] '''List of [(labelname, length, algorithm, seqno, disabled), ... ]''' longest_label = None '''Int. Length of the longest labelname of `labelfile`. Set on refresh()''' def __init__(self, settings, file_location=None): '''Open file; if file is found: strip comments and parse()''' self.settings = settings if file_location: self.file_location = expanduser(file_location) else: self.file_location = expanduser(self.getDefaultFileLocation()) self.parse()
[docs] def getDefaultFileLocation(self): """Scan default labelfile paths""" label_env = os.getenv('DISPASS_LABELFILE') std_env = os.getenv('XDG_DATA_HOME') or os.getenv('APPDATA') home_file = '~/.dispass/labels' if label_env: return label_env if not exists(home_file) and std_env: return std_env + '/dispass/labels' else: return home_file
[docs] def parse(self): '''Create dictionary {algorithm: (label, (length, seqno, disabled))}''' file_stripped = [] self.labelfile = [] try: self.filehandle = open(self.file_location, 'r') self.file_found = True except IOError: self.file_found = False return # Strip comments and blank lines for i in self.filehandle: if i[0] != '\n' and i[0] != '#': file_stripped.append(i) if self.file_found: self.filehandle.close() else: return labels = [] for i in file_stripped: wordlist = [] line = i.rsplit(' ') for word in line: if word != '': wordlist.append(word.strip('\n')) labels.append(wordlist) for line in labels: labelname = line.pop(0) length = self.settings.passphrase_length seqno = self.settings.sequence_number algo = self.settings.algorithm disabled = self.settings.disabled for arg in line: if 'length=' in arg: try: length = int(arg.strip('length=')) except ValueError: print("Warning: Invalid length in: '{}'".format(line)) elif 'algo=' in arg: algo = arg.strip('algo=') elif 'seqno=' in arg: seqno = arg.strip('seqno=') elif 'disabled=' in arg: disabled = arg.lstrip('disabled=') == 'True' self.labelfile.append((labelname, length, algo, seqno, disabled)) return self
def find(self, labelname): for label in self.labelfile: if labelname == label[0]: return label
[docs] def add(self, labelname, length=None, algo=None, seqno=None, disabled=False): '''Add label to `labelfile`''' length = length if length else self.settings.passphrase_length algo = algo if algo else self.settings.algorithm seqno = seqno if seqno else self.settings.sequence_number disabled = disabled if disabled else self.settings.disabled if self.find(labelname): return False self.labelfile.append((labelname, length, algo, seqno, disabled)) return True
[docs] def update(self, labelname, length=None, algo=None, seqno=None, disabled=None): '''Update label in `labelfile`''' label = self.find(labelname) if not label: return False params = {'length': length if length else label[1], 'algo': algo if algo else label[2], 'seqno': seqno if seqno else label[3], 'disabled': disabled if disabled is not None else label[4]} return self.remove(labelname) and self.add(labelname, **params)
[docs] def increment(self, labelname): '''Increment sequence number of `labelfile`''' label = self.find(labelname) if not label or label[2] == 'dispass1': return False return self.update(labelname, seqno=int(label[3]) + 1)
[docs] def disable(self, labelname, disabled=True): '''Disable or enable a label''' label = self.find(labelname) if not label: return False return self.update(labelname, disabled=disabled)
[docs] def remove(self, labelname): '''Remove label from `labelfile`''' removed = False for i in range(len(self.labelfile)): if self.labelfile[i][0] == labelname: del self.labelfile[i] removed = True break return removed
[docs] def refresh(self, sort=True): '''Sort `labelfile` on labelname and get longest label''' if sort: self.labelfile.sort() labelnames = [] for label in self.labelfile: labelnames.append(label[0]) if labelnames: self.longest_label = len(max(labelnames, key=len))
[docs] def save(self): '''Save `labelfile` to file''' self.refresh() labelfile = ('# Generated by DisPass {version} on {datetime}\n\n' .format(version=__version__, datetime=datetime.datetime.now())) for label in self.labelfile: if label[2] == 'dispass1': options = ('length={length} algo={algo} disabled={disabled}' .format(length=label[1], algo=label[2], disabled=label[4])) else: options = (('length={length} algo={algo} seqno={seqno} ' 'disabled={disabled}') .format(length=label[1], algo=label[2], seqno=label[3], disabled=label[4])) labelfile += ('{label:{divlen}} {options}\n' .format(label=label[0], options=options, divlen=self.longest_label)) try: self.filehandle = open(self.file_location, 'w') self.filehandle.write(labelfile) self.filehandle.close() except IOError: return False return True
[docs] def labeltup(self, label): '''Get labeltup for `label` :Parameters: - `label`: The labelname :Returns: - A tuple with 5 values ``(label, length, algo, seqno, disabled))`` - `label`: Label to use for passphrase generation - `length`: Length to use for passphrase generation - `algo`: Algorithm to use for passphrase generation - `seqno`: Sequence number to use for passphrase generation - `disabled`: Whether or not the passphrase is disabled ''' for labeltup in self.labelfile: if label == labeltup[0]: return labeltup return False
[docs] def printLabels(self, fixed_columns=False, labels_only=False, all_=False): '''Print a formatted table of labelfile contents :Parameters: - `fixed_columns`: Boolean. - `labels_only`: Boolean. If `labels_only` is True, only the labelnames will be printed. If `fixed_columns` is true the output will be optimized for easy parsing by other programs and scripts by not printing the header and always printing one entry on a single line using the following positions: * Column 1-50: labelname (50 chars) * Column 52-54: length (3 chars wide) * Column 56-70: hash algo (15 chars wide) * Column 72-74: sequence number (3 chars wide) * Column 76-77: disabled (1 char wide) If fixed columns is false an ascii table is printed with a variable width depending on the length of the longest label. ''' self.refresh() if labels_only: for label in self.labelfile: if all_ or not label[4]: print(label[0]) return if fixed_columns: for label in self.labelfile: if all_ or not label[4]: print('{:50} {:3} {:15} {:3} {}' .format(label[0][:50], str(label[1])[:3], label[2][:15], str(label[3]), 'y' if label[4] else 'n')) else: divlen = self.longest_label if not divlen: return divtitle = 'Label' divlen = max(divlen, len(divtitle)) print('+-{spacer:{fill}}-+--------+----------+--------+---+\n' '| {title:{fill}} | Length | Algo | Number | X |\n' '+-{spacer:{fill}}-+--------+----------+--------+---+' .format(spacer='-' * divlen, title=divtitle, fill=divlen)) for label in self.labelfile: if all_ or not label[4]: print('| {:{fill}} | {:3} | {:8} | {:3>} | {} |' .format(label[0], label[1], label[2], int(label[3]), 'y' if label[4] else 'n', fill=divlen)) print('+-{:{fill}}-+--------+----------+--------+---+' .format('-' * divlen, fill=divlen))
[docs] def promptForCreation(self, silent=False): '''Create the labelfile, optionally warning the user beforehand :Parameters: - `silent`: When True, the user will not be warned. :Returns: Boolean. Indicating if the labelfile was created succesfully. ''' if silent: if self.save(): return True else: return False print('error: could not load labelfile at "{loc}"' .format(loc=self.file_location)) inp = input('Do you want to create it? Y/n ') if inp == '' or inp[0].lower() == 'y': # create directories for file_location if they don't exist dir = os.path.abspath(os.path.dirname(self.file_location)) if not os.path.exists(dir): os.makedirs(dir) # save file if not self.save(): print('error: could not save to "{loc}"\n' .format(loc=self.file_location)) return False else: return False return True
def is_writeable(self): return os.access(self.file_location, os.W_OK)