Esempio n. 1
0
    def publish_passcode(self):
        passcommand = self.passcommand
        passcode = self.passphrase

        # process passcomand
        if passcommand:
            if passcode:
                warn('passphrase unneeded.', culprit='passcommand')
                return dict(BORG_PASSCOMMAND=passcommand)

        # get passphrase from avendesora
        if not passcode and self.avendesora_account:
            narrate('running avendesora to access passphrase.')
            try:
                from avendesora import PasswordGenerator
                pw = PasswordGenerator()
                account = pw.get_account(self.value('avendesora_account'))
                field = self.value('avendesora_field', None)
                passcode = str(account.get_value(field))
            except ImportError:
                raise Error('Avendesora is not available',
                            'you must specify passphrase in settings.',
                            sep=', ')

        if passcode:
            return dict(BORG_PASSPHRASE=passcode)

        if self.encryption is None:
            self.encryption = 'none'
        if self.encryption == 'none':
            narrate('passphrase is not available, encryption disabled.')
            return {}
        raise Error('Cannot determine the encryption passphrase.')
Esempio n. 2
0
    def from_text(cls, text):
        """
        Read a protocol from the given text.

        See `Protocol.parse()` for more information.  This function adds some 
        additional error handling and sanity checking.
        """
        io = cls()

        try:
            io.protocol = Protocol.parse(text)

        except ParseError as err:
            if not cls.all_errors:
                warn(
                    "the protocol could not be properly rendered due to error(s):"
                )
            err.report(informant=warn)

            io.protocol = err.content
            io.errors = 1

        else:
            io.protocol.set_current_date()

            if not io.protocol.steps:
                warn("protocol is empty.", culprit=inform.get_culprit())

        return io
Esempio n. 3
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline["<mount_point>"]
        if mount_point:
            mount_point = settings.to_path(mount_point, resolve=False)
        else:
            mount_point = settings.as_path("default_mount_point")
        if not mount_point:
            raise Error("must specify directory to use as mount point")

        # run borg
        try:
            settings.run_borg(
                cmd="umount",
                args=[mount_point],
                emborg_opts=options,
            )
            try:
                mount_point.rmdir()
            except OSError as e:
                warn(os_error(e))
        except Error as e:
            if "busy" in str(e):
                e.reraise(
                    codicil=
                    f"Try running 'lsof +D {mount_point!s}' to find culprit.")
Esempio n. 4
0
    def run_borg(self, cmd, args='', borg_opts=None, emborg_opts=()):

        # prepare the command
        os.environ.update(self.publish_passcode())
        os.environ['BORG_DISPLAY_PASSPHRASE'] = 'no'
        if self.ssh_command:
            os.environ['BORG_RSH'] = self.ssh_command
        executable = self.value('borg_executable', BORG)
        if borg_opts is None:
            borg_opts = self.borg_options(cmd, emborg_opts)
        command = ([executable] + cmd.split() + borg_opts +
                   (args.split() if is_str(args) else args))
        environ = {
            k: v
            for k, v in os.environ.items() if k.startswith('BORG_')
        }
        if 'BORG_PASSPHRASE' in environ:
            environ['BORG_PASSPHRASE'] = '<redacted>'
        narrate('setting environment variables:', render(environ))

        # check if ssh agent is present
        if self.needs_ssh_agent:
            for ssh_var in 'SSH_AGENT_PID SSH_AUTH_SOCK'.split():
                if ssh_var not in os.environ:
                    warn(
                        'environment variable not found, is ssh-agent running?',
                        culprit=ssh_var)

        # run the command
        narrate('running:\n{}'.format(
            indent(render_command(command, borg_options_arg_count))))
        narrating = 'verbose' in emborg_opts or 'narrate' in emborg_opts
        modes = 'soeW' if narrating else 'sOEW'
        return Run(command, modes=modes, stdin='', env=os.environ, log=False)
Esempio n. 5
0
 def add_songs(self, paths, cwd='.'):
     for path in paths:
         path = Path(cwd, path).expanduser()
         if path.is_file():
             ext = path.suffix.lower()
             if ext in media_file_extensions:
                 self.songs += [path]
             elif ext == '.m3u':
                 try:
                     playlist = path.read_text()
                     lines = [l.strip() for l in playlist.splitlines()]
                     self.add_songs(
                         [l for l in lines if l and l[0] != '#'],
                         path.parent
                     )
                 except OSError as e:
                     raise Error(os_error(e))
             elif path.stem != restart_path.stem:
                 if not self.informer.quiet:
                     warn('skipping descriptor of unknown type.', culprit=path)
         elif path.is_dir():
             self.add_songs(path.iterdir())
         else:
             if not self.informer.quiet:
                 warn('not found.', culprit=path)
     if not self.songs:
         raise Error('playlist is empty.')
Esempio n. 6
0
    def read_manifests(self):  # {{{2
        if self.name_index:
            return
        cache_dir = get_setting('cache_dir')
        manifests_path = cache_dir / MANIFESTS_FILENAME
        try:
            encrypted = manifests_path.read_bytes()

            user_key = get_setting('user_key')
            if not user_key:
                raise Error('no user key.')
            key = base64.urlsafe_b64encode(sha256(user_key.encode('ascii')).digest())
            fernet = Fernet(key)
            contents = fernet.decrypt(encrypted)

            try:
                cache = pickle.loads(contents, **PICKLE_ARGS)
                self.name_manifests = cache['names']
                self.url_manifests = cache['urls']
                self.title_manifests = cache['titles']

                # build the name_index by inverting the name_manifests
                self.name_index = {
                    h:n for n,l in self.name_manifests.items() for h in l
                }
            except (ValueError, pickle.UnpicklingError) as e:
                warn('garbled manifest.', culprit=manifests_path, codicil=str(e))
                manifests_path.unlink()
            assert isinstance(self.name_index, dict)
        except OSErrors as e:
            comment(os_error(e))
        except Exception as e:
            comment(e)
Esempio n. 7
0
 def fail(self, *msg, comment=''):
     msg = full_stop(' '.join(str(m) for m in msg))
     try:
         if self.notify:
             Run(['mail', f'-s "{PROGRAM_NAME}: {msg}"', self.notify],
                 stdin=dedent(f'''
                     {msg}
                     {comment}
                     config = {self.config_name}
                     source = {hostname}:{self.src_dir}
                     destination = {self.dest_server}:{self.dest_dir}
                 ''').lstrip(),
                 modes='soeW')
     except OSError as e:
         pass
     try:
         if self.notifier:
             Run(self.notifier.format(
                 msg=msg,
                 host_name=hostname,
                 user_name=username,
                 prog_name=PROGRAM_NAME,
             ),
                 modes='soeW')
     except OSError as e:
         pass
     except KeyError as e:
         warn('unknown key.', culprit=(self.settings_file, 'notifier', e))
     raise Error(msg)
Esempio n. 8
0
    def read_confs(self):
        # read the .conf files in our config directory (except for hosts.conf)
        for name in "ssh networks locations proxies".split():
            conf_file = to_path(CONFIG_DIR, name + ".conf")
            if conf_file.exists():
                settings = PythonFile(conf_file).run()
                overlap = settings.keys() & self.settings.keys()
                overlap -= sshconfig_names
                overlap = [k for k in overlap if not k.startswith("_")]
                if overlap:
                    warn("conflicting settings:",
                         conjoin(overlap),
                         culprit=conf_file)
                self.settings.update(settings)

        self.ssh_config_file = to_path(
            self.settings.get("CONFIG_FILE", SSH_CONFIG_FILE))
        if not self.ssh_config_file.is_absolute():
            raise Error(
                "path to SSH config file should be absolute.",
                culprit=self.ssh_config_file,
            )
        self.ssh_defaults = self.settings.get("DEFAULTS", "")
        self.ssh_overrides = self.settings.get("OVERRIDES", "")
        self.preferred_networks = self.settings.get("PREFERRED_NETWORKS", [])
        self.locations = self.settings.get("LOCATIONS", {})
        self.proxies = self.settings.get("PROXIES", {})

        self.available_ciphers = self.settings.get("AVAILABLE_CIPHERS")
        self.available_macs = self.settings.get("AVAILABLE_MACS")
        self.available_host_key_algorithms = self.settings.get(
            "AVAILABLE_HOST_KEY_ALGORITHMS")
        self.available_kex_algorithms = self.settings.get(
            "AVAILABLE_KEX_ALGORITHMS")
Esempio n. 9
0
    def initialize(cls,
        gpg_path=None, gpg_home=None, armor=None
    ):
        from .config import get_setting, override_setting

        cls.gpg_path = to_path(
            gpg_path if gpg_path else get_setting('gpg_executable')
        )
        override_setting('gpg_executable', cls.gpg_path)

        cls.gpg_home = to_path(
            gpg_home if gpg_home else get_setting('gpg_home')
        )
        override_setting('gpg_home', cls.gpg_home)

        armor = armor if armor is not None else get_setting('gpg_armor')
        if armor not in ARMOR_CHOICES:
            warn(
                "'%s' is not valid, choose from %s." % (
                    armor, conjoin(ARMOR_CHOICES)
                ), culprit=(get_setting('config_file'), 'gpg_armor')
            )
            armor = None
        cls.armor = armor
        override_setting('gpg_armor', armor)

        gpg_args = {}
        if cls.gpg_path:
            gpg_args.update({'gpgbinary': str(cls.gpg_path)})
        if cls.gpg_home:
            gpg_args.update({'gnupghome': str(cls.gpg_home)})
        cls.gpg = gnupg.GPG(**gpg_args)
Esempio n. 10
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        mount_point = cmdline['<mount_point>']
        if not mount_point:
            mount_point = settings.value('default_mount_point')
            if not mount_point:
                raise Error('must specify directory to use as mount point')
        mount_point = to_path(mount_point)

        # run borg
        try:
            settings.run_borg(
                cmd='umount',
                args=[mount_point],
                emborg_opts=options,
            )
            try:
                to_path(mount_point).rmdir()
            except OSError as e:
                warn(os_error(e))
        except Error as e:
            if 'busy' in str(e):
                e.reraise(
                    codicil=
                    f"Try running 'lsof +D {mount_point}' to find culprit.")
Esempio n. 11
0
    def __new__(cls, server, include_file, bypass, trial_run):
        if server in AuthKeys.known:
            self = AuthKeys.known[server]
            if include_file != self.include_file:
                warn(
                    'inconsistent remote include file:',
                    fmt('{include_file} != {self.include_file} in {server}.')
                )
            return self
        self = super(AuthKeys, cls).__new__(cls)
        AuthKeys.known[server] = self
        self.server = server
        self.bypass = bypass
        self.trial_run = trial_run
        self.keys = {}
        self.comment = {}
        self.restrictions = {}
        self.include_file = include_file
        self.include = None

        # get remote include file if it exists
        if include_file and not bypass:
            narrate(fmt('    retrieving remote include file from {server}.'))
            try:
                try:
                    run_sftp(self.server, [
                        fmt('get .ssh/{inc} {inc}.{server}', inc=include_file)
                    ])
                    self.include = to_path(include_file + '.' + server).read_text()
                except OSError as err:
                    comment(fmt('    sftp {server}: {include_file} not found.'))
            except OSError as err:
                error(os_error(err))

        return self
Esempio n. 12
0
    def initialize(cls, gpg_path=None, gpg_home=None, armor=None):

        cls.gpg_path = to_path(
            gpg_path if gpg_path else get_setting('gpg_executable'))
        override_setting('gpg_executable', cls.gpg_path)

        cls.gpg_home = to_path(
            gpg_home if gpg_home else get_setting('gpg_home'))
        override_setting('gpg_home', cls.gpg_home)

        armor = armor if armor is not None else get_setting('gpg_armor')
        if armor not in ARMOR_CHOICES:
            warn("'%s' is not valid, choose from %s." %
                 (armor, conjoin(ARMOR_CHOICES)),
                 culprit=setting_path('gpg_armor'))
            armor = 'extension'
        cls.armor = armor
        override_setting('gpg_armor', armor)

        gpg_args = {}
        if cls.gpg_path:
            gpg_args.update({'gpgbinary': str(cls.gpg_path)})
        if cls.gpg_home:
            gpg_args.update({'gnupghome': str(cls.gpg_home)})
        try:
            cls.gpg = gnupg.GPG(**gpg_args)
        except ValueError as e:
            fatal(e)
Esempio n. 13
0
    def __init__(self, config=None, emborg_opts=(), _queue=None):
        self.settings = dict()
        self.do_not_expand = ()
        self.emborg_opts = emborg_opts
        self.version = tuple(int(p) for p in __version__.split('.'))

        # reset the logfile so anything logged after this is placed in the
        # logfile for this config
        get_informer().set_logfile(LoggingCache())
        self.config_dir = to_path(CONFIG_DIR)
        self.read_config(name=config, queue=_queue)
        self.check()
        set_shlib_prefs(
            encoding=self.encoding if self.encoding else DEFAULT_ENCODING)
        self.hooks = Hooks(self)
        self.borg_ran = False

        # set colorscheme
        if self.colorscheme:
            colorscheme = self.colorscheme.lower()
            if colorscheme == 'none':
                get_informer().colorscheme = None
            elif colorscheme in ('light', 'dark'):
                get_informer().colorscheme = colorscheme
            else:
                warn(f'unknown colorscheme: {self.colorscheme}.')
Esempio n. 14
0
 def read_defaults(self):
     settings = {}
     try:
         from appdirs import user_config_dir
         config_file = to_path(user_config_dir('vdiff'), 'config')
         try:
             code = config_file.read_text()
             try:
                 compiled = compile(code, str(config_file), 'exec')
                 exec(compiled, settings)
             except Exception as e:
                 error(e, culprit=config_file)
         except FileNotFoundError:
             pass
         except OSError as e:
             warn(os_error(e))
         if self.useGUI is not None:
             settings['gui'] = self.useGUI
     except ImportError:
         pass
     if settings.get('gui', DEFAULT_GUI):
         if 'DISPLAY' not in os.environ:
             warn('$DISPLAY not set, ignoring request for gvim.')
         else:
             self.cmd = settings.get('gvimdiff', DEFAULT_GVIM)
             return
     self.cmd = settings.get('vimdiff', DEFAULT_VIM)
Esempio n. 15
0
 def fail(self, *msg, comment=''):
     msg = full_stop(' '.join(str(m) for m in msg))
     try:
         if self.notify:
             Run(['mail', '-s', f'{PROGRAM_NAME} on {hostname}: {msg}'] +
                 self.notify.split(),
                 stdin=dedent(f'''
                     {msg}
                     {comment}
                     config = {self.config_name}
                     source = {username}@{hostname}:{', '.join(str(d) for d in self.src_dirs)}
                     destination = {self.repository}
                 ''').lstrip(),
                 modes='soeW')
     except Error:
         pass
     try:
         if self.notifier:
             Run(self.notifier.format(
                 msg=msg,
                 hostname=hostname,
                 user_name=username,
                 prog_name=PROGRAM_NAME,
             ),
                 modes='soeW')
     except Error:
         pass
     except KeyError as e:
         warn('unknown key.', culprit=(self.settings_file, 'notifier', e))
     raise Error(msg)
Esempio n. 16
0
    def generate(self, field_name, field_key, account):
        try:
            if self.secret:
                return
        except AttributeError:
            pass
        account_name = account.get_name()
        account_seed = account.get_seed()
        if self.master is None:
            master = account.get_field("master", default=None)
            master_source = account.get_field("_master_source", default=None)
        else:
            master = self.master
            master_source = "secret"
        if not master:
            master = get_setting("user_key")
            master_source = "user_key"
        if not master:
            try:
                try:
                    master = getpass.getpass("master password for %s: " % account_name)
                    master_source = "user"
                except EOFError:
                    output()
                if not master:
                    warn("master password is empty.")
            except (EOFError, KeyboardInterrupt):
                terminate()
        log("Generating secret, source of master seed:", master_source)
        field_key = self.get_key(field_key)
        if self.version:
            version = self.version
        else:
            version = account.get_field("version", default="")

        if account.request_seed():
            try:
                try:
                    interactive_seed = getpass.getpass("seed for %s: " % account_name)
                except EOFError:
                    output()
                if not interactive_seed:
                    warn("seed is empty.")
            except (EOFError, KeyboardInterrupt):
                terminate()
        else:
            interactive_seed = ""

        seeds = [master, account_seed, field_name, field_key, version, interactive_seed]
        key = " ".join([str(seed) for seed in seeds])

        # Convert the key into 512 bit number
        digest = hashlib.sha512((key).encode("utf-8")).digest()
        bits_per_byte = 8
        radix = 1 << bits_per_byte
        bits = 0
        for byte in digest:
            bits = radix * bits + byte
        self.pool = bits
Esempio n. 17
0
def read_config():
    if Config.get('READ'):
        return  # already read

    # First open the config file
    from .gpg import PythonFile
    path = get_setting('config_file')
    assert path.suffix.lower() not in ['.gpg', '.asc']
    config_file = PythonFile(path)
    try:
        contents = config_file.run()
        for k, v in contents.items():
            if k.startswith('_'):
                continue
            if k not in CONFIG_DEFAULTS:
                warn('%s: unknown.' % k, culprit=config_file)
                continue
            if k.endswith('_executable'):
                argv = v.split() if is_str(v) else list(v)
                path = to_path(argv[0])
                if not path.is_absolute():
                    warn(
                        'should use absolute path for executables.',
                        culprit=(config_file, k)
                    )
            Config[k] = v
        Config['READ'] = True
    except Error as err:
        comment('not found.', culprit=config_file)

    # Now open the hashes file
    hashes_file = PythonFile(get_setting('hashes_file'))
    try:
        contents = hashes_file.run()
        Config.update({k.lower(): v for k,v in contents.items()})
    except Error as err:
        pass

    # Now open the account list file
    account_list_file = PythonFile(get_setting('account_list_file'))
    try:
        contents = account_list_file.run()
        Config.update({k.lower(): v for k,v in contents.items()})
    except Error as err:
        pass

    # initilize GPG
    from .gpg import GnuPG
    GnuPG.initialize()

    # Now read the user key file
    user_key_file = get_setting('user_key_file')
    if user_key_file:
        user_key_file = PythonFile(get_setting('user_key_file'))
        try:
            contents = user_key_file.run()
            Config.update({k.lower(): v for k,v in contents.items()})
        except Error as err:
            pass
Esempio n. 18
0
def run_duplicity(cmd, settings, narrating):
    os.environ.update(publish_passcode(settings))
    for ssh_var in 'SSH_AGENT_PID SSH_AUTH_SOCK'.split():
        if ssh_var not in os.environ:
            warn('environment variable not found, is ssh-agent running?',
                 culprit=ssh_var)
    narrate('running:\n{}'.format(indent(render_command(cmd))))
    modes = 'soeW' if narrating else 'sOEW'
    Run(cmd, modes=modes, env=os.environ)
Esempio n. 19
0
 def now_playing(self):
     if self.now_playing_path:
         out = [each for each in [self.artist, self.title] if each]
         try:
             self.now_playing_path.write_text(' - '.join(out))
         except OSError as e:
             if not self.warned:
                 warn(os_error(e))
                 self.warned = True
Esempio n. 20
0
    def gather_public_keys(self):
        comment('    gathering public keys')
        keyname = self.keyname
        data = self.data
        clients = conjoin(self.data.get('clients', []))
        default_purpose = fmt('This key allows access from {clients}.')
        purpose = self.data.get('purpose', default_purpose)
        servers = self.data.get('servers', [])
        prov = '.provisional' if self.trial_run else ''

        # read contents of public key
        try:
            pubkey = to_path(keyname + '.pub')
            key = pubkey.read_text().strip()
        except OSError as err:
            narrate('%s, skipping.' % os_error(err))
            return

        # get fingerprint of public key
        try:
            keygen = Run(['ssh-keygen', '-l', '-f', pubkey], modes='wOeW')
            fields = keygen.stdout.strip().split()
            fingerprint = ' '.join([fields[0], fields[1], fields[-1]])
        except OSError as err:
            error(os_error(err))
            return

        # contribute commented and restricted public key to the authorized_key 
        # file for each server
        for server in servers:
            if self.update and server not in self.update:
                continue
            if server in self.skip:
                continue
            server_data = servers[server]
            description = server_data.get('description', None)
            restrictions = server_data.get('restrictions', [])
            remarks = [
                '# %s' % t
                for t in cull([purpose, description, self.warning, fingerprint])
                if t
            ]

            include_file = server_data.get(
                'remote-include-filename', data['remote-include-filename']
            )
            bypass = server_data.get('bypass')
            authkeys = AuthKeys(server, include_file, bypass, self.trial_run)
            authkeys.add_public_key(keyname, key, remarks, restrictions)

        if not servers:
            warn(
                'no servers specified, you must update them manually.', 
                culprit=keyname
            )
Esempio n. 21
0
 def set_location(self, given=None):
     locations.set_location(given if given else self.network.location)
     unknown = locations.unknown_locations(self.locations)
     if unknown:
         warn(
             "the following locations are unknown (add them to LOCATIONS):")
         codicil(*sorted(unknown), sep="\n")
     self.location = self.locations.get(locations.my_location)
     if locations.my_location and not self.location:
         raise Error("unknown location, choose from:",
                     conjoin(self.locations))
Esempio n. 22
0
def test_slice():
    with messenger() as (msg, stdout, stderr, logfile):
        warn('aaa bbb ccc', codicil=('000 111 222', '!!! @@@ ###'))
        assert msg.errors_accrued() == 0
        assert errors_accrued(True) == 0
        assert strip(stdout) == dedent('''
            warning: aaa bbb ccc
                000 111 222
                !!! @@@ ###
        ''').strip()
        assert strip(stderr) == ''
Esempio n. 23
0
 def initialize(cls, interactive_seed=False):
     cls._interactive_seed = interactive_seed
     log("initializing", cls.get_name())
     try:
         if cls.master.is_secure():
             if not cls._file_info.encrypted:
                 warn(
                     "high value master password not contained in encrypted", "account file.", culprit=cls.get_name()
                 )
     except AttributeError as err:
         pass
Esempio n. 24
0
    def publish_passcode(self):
        for v in ['BORG_PASSPHRASE', 'BORG_PASSCOMMAND', 'BORG_PASSPHRASE_FD']:
            if v in os.environ:
                narrate(f"Using existing {v}.")
                return

        passcommand = self.value('passcommand')
        passcode = self.passphrase

        # process passcomand
        if passcommand:
            if passcode:
                warn("passphrase unneeded.", culprit="passcommand")
            narrate(f"Setting BORG_PASSCOMMAND.")
            os.environ['BORG_PASSCOMMAND'] = passcommand
            self.borg_passcode_env_var_set_by_emborg = 'BORG_PASSCOMMAND'
            return

        # get passphrase from avendesora
        if not passcode and self.avendesora_account:
            narrate("running avendesora to access passphrase.")
            try:
                from avendesora import PasswordGenerator

                pw = PasswordGenerator()
                account_spec = self.value("avendesora_account")
                if ':' in account_spec:
                    passcode = str(pw.get_value(account_spec))
                else:
                    account = pw.get_account(self.value("avendesora_account"))
                    field = self.value("avendesora_field", None)
                    passcode = str(account.get_value(field))
            except ImportError:
                raise Error(
                    "Avendesora is not available",
                    "you must specify passphrase in settings.",
                    sep=", ",
                )

        if passcode:
            os.environ['BORG_PASSPHRASE'] = passcode
            narrate(f"Setting BORG_PASSPHRASE.")
            self.borg_passcode_env_var_set_by_emborg = 'BORG_PASSPHRASE'
            return

        if self.encryption is None:
            self.encryption = "none"
        if self.encryption == "none" or self.encryption.startswith(
                'authenticated'):
            comment("Encryption is disabled.")
            return
        raise Error("Cannot determine the encryption passphrase.")
Esempio n. 25
0
def get_psf_filename(psf_file):
    if not psf_file:
        try:
            with open(saved_psf_file_filename) as f:
                psf_file = f.read().strip()
            display('Using PSF file:', psf_file)
        except OSError:
            fatal('missing PSF file name.')
    try:
        with open(saved_psf_file_filename, 'w') as f:
            f.write(psf_file)
    except OSError as e:
        warn(os_error(e))
    return psf_file
Esempio n. 26
0
    def publish(self):
        narrate('publishing authorized_keys to', self.server)
        prov = '.provisional' if self.trial_run else ''
        entries = [
            fmt("# This file was generated by sshdeploy on {date}.")
        ]
        if self.include:
            entries += [
                '\n'.join([
                    fmt('# Contents of {self.include_file}:'),
                    self.include
                ])
            ]
        for name in sorted(self.keys.keys()):
            key = self.keys[name]
            comment = self.comment[name]
            comment = [comment] if is_str(comment) else comment
            restrictions = self.restrictions[name]
            if not is_str(restrictions):
                restrictions = ','.join(restrictions)
            restricted_key = ' '.join(cull([restrictions, key]))
            entries.append('\n'.join(comment + [restricted_key]))

        # delete any pre-existing provisional files
        # the goal here is to leave a clean directory when not trial-run
        try:
            run_sftp(self.server, [
                fmt('rm .ssh/authorized_keys.provisional')
            ])
        except OSError as err:
            pass

        # now upload the new authorized_keys file
        try:
            authkey = to_path('authorized_keys.%s' % self.server)
            with authkey.open('w') as f:
                f.write('\n\n'.join(entries) + '\n')
            authkey.chmod(0o600)
            if self.bypass:
                warn(
                    'You must manually upload',
                    fmt('<keydir>/authorized_keys.{self.server}.'),
                    culprit=self.server
                )
            else:
                run_sftp(self.server, [
                    fmt('put -p {authkey} .ssh/authorized_keys{prov}')
                ])
        except OSError as err:
            error(os_error(err))
Esempio n. 27
0
    def borg_options(self, cmd, options):
        # handle special cases first {{{3
        args = []
        if 'verbose' in options:
            args.append('--verbose')
        if 'trial-run' in options and cmd in commands_with_dryrun:
            args.append('--dry-run')
        if cmd == 'create':
            if 'verbose' in options:
                args.append('--list')
                if 'trial-run' not in options:
                    args.append('--stats')
            for path in render_paths(self.values('excludes')):
                args.extend(['--exclude', path])

        if cmd == 'extract':
            if 'verbose' in options:
                args.append('--list')

        if cmd == 'init':
            if self.passphrase or self.avendesora_account:
                encryption = self.encryption if self.encryption else 'repokey'
                args.append(f'--encryption={encryption}')
                if encryption == 'none':
                    warn(
                        'passphrase given but not needed as encryption set to none.'
                    )
                if encryption in 'keyfile keyfile-blake2'.split():
                    warn("you should use 'borg export key' to export the",
                         "encryption key, and then keep that key in a safe",
                         "place.  If you lose the key you will lose access to",
                         "your backups.",
                         wrap=True)
            else:
                encryption = self.encryption if self.encryption else 'none'
                if encryption != 'none':
                    raise Error('passphrase not specified.')
                args.append(f'--encryption={encryption}')

        # add the borg command line options appropriate to this command {{{3
        for name, attrs in BORG_SETTINGS.items():
            if cmd in attrs['cmds'] or 'all' in attrs['cmds']:
                opt = convert_name_to_option(name)
                val = self.settings.get(name)
                if val:
                    if 'arg' in attrs and attrs['arg']:
                        args.extend([opt, str(val)])
                    else:
                        args.extend([opt])
        return args
Esempio n. 28
0
def test_culprits(culprits, culprits_as_str):
    with messenger(culprit_sep='.') as (msg, stdout, stderr, logfile):
        stimulus = 'hey now!'
        expected = 'warning: {}{}'.format(culprits_as_str, stimulus)
        warn(stimulus, culprit=culprits)
        assert msg.errors_accrued() == 0
        assert errors_accrued() == 0
        assert strip(stdout) == expected
        assert strip(stderr) == ''
        assert log_strip(logfile) == dedent('''
            ack: invoked as: <exe>
            ack: log opened on <date>
            {expected}
        ''').strip().format(expected=expected)
Esempio n. 29
0
    def display_field(self, account, field):
        # get string to display
        value, is_secret, name, desc = tuple(account.get_value(field))
        label = '%s (%s)' % (name, desc) if desc else name
        value = dedent(str(value)).strip()
        label_color = get_setting('_label_color')

        # indent multiline outputs
        sep = ' '
        if '\n' in value:
            if is_secret:
                warn('secret contains newlines, will not be fully concealed.')
            value = indent(value, get_setting('indent')).strip('\n')
            sep = '\n'

        if label:
            if label[0] == '_':
                # hidden field
                label = '!' + label[1:]
            text = label_color(label.replace('_', ' ') + ':') + sep + value
        else:
            text = value
            label = field
        log('Writing to TTY:', label)

        if is_secret:
            if Color.isTTY():
                # Write only if output is a TTY. This is a security feature.
                # The ideas is that when the TTY writer is called it is because
                # the user is expecting the output to go to the tty. This
                # eliminates the chance that the output can be intercepted and
                # recorded by replacing Avendesora with an alias or shell
                # script. If the user really want the output to go to something
                # other than the TTY, the user should use the --stdout option.
                try:
                    cursor.write(text)
                    cursor.conceal()
                    sleep(get_setting('display_time'))
                except KeyboardInterrupt:
                    pass
                cursor.reveal()
                cursor.clear()
            else:
                error('output is not a TTY.')
                codicil(
                    'Use --stdout option if you want to send secret',
                    'to a file or a pipe.'
                )
        else:
            output(text)
Esempio n. 30
0
def list_signals():
    # Read command line {{{2
    cmdline = docopt(__doc__)
    args = cmdline['<signal>']
    if not args:
        args = ['*']
    psf_file = get_psf_filename(cmdline['--psf-file'])
    show_meta = cmdline['--long']
    use_cache = not cmdline['--no-cache']

    # List signals {{{2
    try:
        psf = PSF(psf_file, sep=':', use_cache=use_cache)

        if show_meta:
            nw = uw = kw = 0  # name width, units width, kind width
            data = []
            for name in expand_args(psf.signals.keys(), args, allow_diff=False):
                if name not in psf.signals:
                    warn('not found.', culprit=name)
                signal = psf.get_signal(name)
                if len(signal.name) > nw:
                    nw = len(signal.name)
                units = psf.units_to_unicode(signal.units)
                if len(units) > uw:
                    uw = len(units)
                kind = signal.type.kind
                kind = kinds.get(kind, kind)
                if len(kind) > kw:
                    kw = len(kind)
                try:
                    points = len(signal.ordinate)
                except TypeError:
                    points = None
                data.append((signal.name, units, kind, points))
            if not data:
                raise Error(f'{plural(args):no match/es}.', culprit=args)
            for name, units, kind, points in data:
                if points is None:
                    display(f'    {name:<{nw}}  {units:<{uw}}  {kind}')
                else:
                    display(f'    {name:<{nw}}  {units:<{uw}}  {kind:<{kw}}  ({points} points)')
        else:
            signals = expand_args(psf.signals.keys(), args, allow_diff=False)
            if not signals:
                raise Error(f'{plural(args):no match/es}.', culprit=args)
            display(columns(signals))
    except Error as e:
        e.terminate()
Esempio n. 31
0
    def initialize_network(self):
        network = self.network

        # run the init script if given
        try:
            if network.init_script:
                script = Run(network.init_script, "sOEW")
                if script.stdout:
                    display(script.stdout.rstrip())
        except AttributeError:
            pass
        except Error as e:
            warn("{} network init_script failed: {}".format(
                network.name(), network.init_script))
            codicil(e.get_message())
Esempio n. 32
0
    def get_seed(cls):
        # need to handle case where stdin/stdout is not available.
        # perhaps write generic password getter that supports both gui and tui.
        # Then have global option that indicates which should be used.
        # Separate name from seed. Only request seed when generating a password.
        import getpass

        try:
            name = getpass.getpass("account name: ")
        except EOFError:
            output()
            name = ""
        if not name:
            warn("null account name.")
        return name
Esempio n. 33
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        paths = cmdline['<path>']
        archive = cmdline['--archive']
        date = cmdline['--date']

        # make sure source directories are given as absolute paths
        for src_dir in settings.src_dirs:
            if not src_dir.is_absolute():
                raise Error('restore command cannot be used',
                            'with relative source directories',
                            culprit=src_dir)

        # convert to absolute resolved paths
        paths = [to_path(p).resolve() for p in paths]

        # assure that paths correspond to src_dirs
        src_dirs = settings.src_dirs
        unknown_path = False
        for path in paths:
            if not any([str(path).startswith(str(sd)) for sd in src_dirs]):
                unknown_path = True
                warn('unknown path.', culprit=path)
        if unknown_path:
            codicil('Paths should start with:', conjoin(src_dirs,
                                                        conj=', or '))

        # remove leading / from paths
        paths = [str(p).lstrip('/') for p in paths]

        # get the desired archive
        if date and not archive:
            archive = get_name_of_nearest_archive(settings, date)
        if not archive:
            archive = get_name_of_latest_archive(settings)
        output('Archive:', archive)

        # run borg
        cd('/')
        borg = settings.run_borg(
            cmd='extract',
            args=[settings.destination(archive)] + paths,
            emborg_opts=options,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Esempio n. 34
0
 def _format_field(self, field):
     comment_leader = "\n        # "
     key, value, desc = field
     if key.lower() not in SSH_SETTINGS:
         warn('unknown SSH setting.', culprit=key)
     key = SSH_SETTINGS.get(key.lower(), key)
     if value is True:
         value = 'yes'
     elif value is False:
         value = 'no'
     text = "    {} {}".format(key, value)
     if desc:
         if not isinstance(desc, list):
             desc = [desc]
         text += comment_leader + comment_leader.join(desc)
     return text
Esempio n. 35
0
    def _validate_components(self):
        from pkg_resources import resource_filename

        # check permissions on the settings directory
        path = get_setting('settings_dir')
        mask = get_setting('config_dir_mask')
        try:
            permissions = getmod(path)
        except FileNotFoundError:
            raise PasswordError('missing, must run initialize.', culprit=path)
        violation = permissions & mask
        if violation:
            recommended = permissions & ~mask & 0o777
            warn("directory permissions are too loose.", culprit=path)
            codicil("Recommend running: chmod {:o} {}".format(
                recommended, path))

        # Check that files that are critical to the integrity of the generated
        # secrets have not changed
        for path, kind in [
            (to_path(resource_filename(__name__,
                                       'secrets.py')), 'secrets_hash'),
            (to_path(resource_filename(__name__,
                                       'charsets.py')), 'charsets_hash'),
            ('default', 'dict_hash'),
            ('mnemonic', 'mnemonic_hash'),
        ]:
            try:
                contents = path.read_text()
            except AttributeError:
                contents = '\n'.join(Dictionary(path).get_words())
            except OSErrors as e:
                raise PasswordError(os_error(e))
            md5 = hashlib.md5(contents.encode('utf-8')).hexdigest()
            # Check that file has not changed.
            if md5 != get_setting(kind):
                warn("file contents have changed.", culprit=path)
                lines = wrap(
                    dedent("""\
                        This could result in passwords that are inconsistent with
                        those created in the past.  Use 'avendesora changed' to
                        assure that nothing has changed. Then, to suppress this
                        message, change {hashes} to contain:
                    """.format(hashes=get_setting('hashes_file'))))
                lines.append("     {kind} = '{md5}'".format(kind=kind,
                                                            md5=md5))
                codicil(*lines, sep='\n')
Esempio n. 36
0
    def preprocess(cls, master, fileinfo, seen):

        # return if this account has already been processed
        if hasattr(cls, '_file_info_'):
            return  # account has already been processed

        # add fileinfo
        cls._file_info_ = fileinfo

        # dedent any string attributes
        for k, v in cls.__dict__.items():
            if is_str(v) and '\n' in v:
                setattr(cls, k, dedent(v))

        # add master seed
        if master and not hasattr(cls, '_%s__NO_MASTER' % cls.__name__):
            if not hasattr(cls, 'master_seed'):
                cls.master_seed = master
                cls._master_source_ = 'file'
            else:
                cls._master_source_ = 'account'

        # convert aliases to a list
        if hasattr(cls, 'aliases'):
            aliases = list(Collection(cls.aliases))
            cls.aliases = aliases
        else:
            aliases = []

        # canonicalize names and look for duplicates
        new = {}
        account_name = cls.get_name()
        path = cls._file_info_.path
        for name in [account_name] + aliases:
            canonical = canonicalize(name)
            Account._accounts[canonical] = cls
            if canonical in seen:
                if name == account_name:
                    warn('duplicate account name.', culprit=name)
                else:
                    warn('alias duplicates existing name.', culprit=name)
                codicil('Seen in %s in %s.' % seen[canonical])
                codicil('And in %s in %s.' % (account_name, path))
                break
            else:
                new[canonical] = (account_name, path)
        seen.update(new)
Esempio n. 37
0
    def run(cls, command, args, settings, options):
        # read command line
        cmdline = docopt(cls.USAGE, argv=[command] + args)
        paths = cmdline['<path>']
        archive = cmdline['--archive']
        date = cmdline['--date']

        # remove initial / from paths
        src_dirs = [str(p).lstrip('/') for p in settings.src_dirs]
        new_paths = [p.lstrip('/') for p in paths]
        if paths != new_paths:
            for path in paths:
                if path.startswith('/'):
                    warn('removing initial /.', culprit=path)
            paths = new_paths

        # assure that paths correspond to src_dirs
        unknown_path = False
        for path in paths:
            if not any([path.startswith(src_dir) for src_dir in src_dirs]):
                unknown_path = True
                warn('unknown path.', culprit=path)
        if unknown_path:
            codicil('Paths should start with:', conjoin(src_dirs))

        # get the desired archive
        if date and not archive:
            archive = get_nearest_archive(settings, date)
            if not archive:
                raise Error('archive not available.', culprit=date)
        if not archive:
            archives = get_available_archives(settings)
            if not archives:
                raise Error('no archives are available.')
            archive = archives[-1]['name']
        output('Archive:', archive)

        # run borg
        borg = settings.run_borg(
            cmd='extract',
            args=[settings.destination(archive)] + paths,
            emborg_opts=options,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Esempio n. 38
0
    def fail(self, *msg, cmd='<unknown>'):
        msg = join(*msg)
        try:
            msg = msg.decode('ascii', errors='replace')
        except AttributeError:
            pass
        try:
            if self.notify and not Color.isTTY():
                Run(
                    [
                        "mail", "-s",
                        f"{PROGRAM_NAME} failed on {username}@{hostname}"
                    ] + self.notify.split(),
                    stdin=dedent(f"""\
                        {PROGRAM_NAME} fails.

                        command: {cmd}
                        config: {self.config_name}
                        source: {username}@{fullhostname}:{', '.join(str(d) for d in self.src_dirs)}
                        destination: {self.repository!s}
                        error message:
                        """) + indent(msg) + "\n",
                    modes="soeW",
                    encoding="ascii",
                )
        except Error:
            pass
        try:
            notifier = self.settings.get("notifier")
            # don't use self.value as we don't want arguments expanded yet
            if notifier and not Color.isTTY():
                Run(self.notifier.format(
                    cmd=cmd,
                    msg=msg,
                    hostname=hostname,
                    user_name=username,
                    prog_name=PROGRAM_NAME,
                ),
                    modes="SoeW"
                    # need to use the shell as user will generally quote msg
                    )
        except Error:
            pass
        except KeyError as e:
            warn("unknown key.", culprit=(self.settings_file, "notifier", e))
Esempio n. 39
0
    def run(cls, command, args, settings, options):
        # read command line
        docopt(cls.USAGE, argv=[command] + args)

        # warn user about relative source directories.
        for src_dir in settings.src_dirs:
            if not src_dir.is_absolute():
                warn('relative source directory.', culprit=src_dir)

        # run borg
        borg = settings.run_borg(
            cmd='init',
            args=[settings.destination()],
            emborg_opts=options,
        )
        out = borg.stdout
        if out:
            output(out.rstrip())
Esempio n. 40
0
def test_possess():
    with messenger(stream_policy='header') as (msg, stdout, stderr, logfile):
        out = [
            'hey now!',
            'hey now!',
        ]
        err = [
            'Aiko aiko all day',
            'jockomo feeno na na nay',
            'jockomo feena nay.',
        ]
        display(*out)
        warn(*err, sep=', ')

        assert msg.errors_accrued() == 0
        assert errors_accrued(True) == 0
        assert strip(stdout) == ' '.join(out)
        assert strip(stderr) == 'warning: ' + ', '.join(err)
Esempio n. 41
0
 def get_seed(cls):
     if cls._stealth_name:
         # In this case we are running using API rather than running from
         # command line and the account names was specified to get_account().
         return cls._stealth_name
     # need to handle case where stdin/stdout is not available.
     # perhaps write generic password getter that supports both gui and tui.
     # Then have global option that indicates which should be used.
     # Separate name from seed. Only request seed when generating a password.
     import getpass
     try:
         name = getpass.getpass('account name: ')
     except EOFError:
         output()
         name = ''
     if not name:
         warn('null account name.')
     return name
Esempio n. 42
0
    def get_field(cls, name, key=None, default=False):
        """Get Field Value

        Return value from the account given a field name and key.
        """
        value = cls.__dict__.get(name)
        if value is None:
            if default is False:
                raise Error("not found.", culprit=(cls.get_name(), cls.combine_name(name, key)))
            else:
                return default

        if key is None:
            if is_collection(value):
                choices = []
                for k, v in Collection(value).items():
                    try:
                        choices.append("   %s: %s" % (k, v.get_key()))
                    except AttributeError:
                        choices.append("   %s:" % k)
                raise Error(
                    "composite value found, need key. Choose from:",
                    *choices,
                    sep="\n",
                    culprit=name,
                    is_collection=True,
                    collection=value
                )
        else:
            try:
                if is_collection(value):
                    value = value[key]
                else:
                    warn("not a composite value, key ignored.", culprit=name)
                    key = None
            except (IndexError, KeyError, TypeError):
                raise Error("not found.", culprit=cls.combine_name(name, key))

        # generate the value if needed
        try:
            value.generate(name, key, cls)
        except AttributeError as err:
            pass
        return value
Esempio n. 43
0
    def generate(self):
        comment('    creating key')
        keyname = self.keyname
        data = self.data
        servers = self.data.get('servers', [])

        opts = data['keygen-options']
        account_name = data['abraxas-account']
        if account_name:
            pw.get_account(account_name)
            passcode = pw.generate_password()
            description = fmt("{keyname} (created {date})")
        else:
            passcode = ''
            self.warning = 'This key is not protected with a passcode!'
            description = fmt("{keyname} (created {date} -- no passcode!)")

            # warn user if they have a key with no passcode and no restrictions
            for server in servers:
                server_data = servers[server]
                restrictions = server_data.get('restrictions')
                if not restrictions:
                    warn(
                        'unprotected key being sent to', server,
                        'without restrictions.'
                    )

        args = ['-C', description, '-f', keyname] + opts.split()
        try:
            comment('    running:', 'ssh-keygen', *args)
            keygen = pexpect.spawn('ssh-keygen', args, timeout=None)
            keygen.expect('Enter passphrase.*: ')
            keygen.sendline(passcode)
            keygen.expect('Enter same passphrase again: ')
            keygen.sendline(passcode)
            keygen.expect(pexpect.EOF)
            keygen.close()
        except pexpect.ExceptionPexpect as err:
            fatal(err)
        
        # remove group/other permissions from keyfiles
        to_path(keyname).chmod(0o600)
        to_path(keyname + '.pub').chmod(0o600)
Esempio n. 44
0
    def display_field(self, account, field):
        name, key = account.split_name(field)
        is_secret = account.is_secret(name, key)
        try:
            value = account.get_field(name, key)
            tvalue = dedent(str(value)).strip()
        except Error as err:
            err.terminate()
        sep = ' '
        if '\n' in tvalue:
            if is_secret:
                warn(
                    'secret contains newlines, will not be fully concealed.',
                    culprit=key
                )
            else:
                tvalue = indent(dedent(tvalue), get_setting('indent')).strip('\n')
                sep = '\n'

        # build output string
        label = account.combine_name(name, key)
        log('Writing to TTY:', label)
        try:
            alt_name = value.get_key()
            if alt_name:
                label += ' (%s)' % alt_name
        except AttributeError:
            pass
        text = LabelColor(label + ':') + sep + tvalue

        if is_secret:
            try:
                cursor.write(text)
                cursor.conceal()
                sleep(get_setting('display_time'))
            except KeyboardInterrupt:
                pass
            cursor.reveal()
            cursor.clear()
        else:
            output(text)