def runCommand(cmdName, arg): log.verbose(f"runCommand {cmdName} {arg}") before_mtime = 0 cmd = config.CONFIG['commands'][cmdName] if cmd is None: log.error("no command configured for 'deleteFile'") log.warning(f"path {path} was not deleted") return cmdArgs = cmd.replace("{}", arg).split() log.verbose(f"cmdArgs: {cmdArgs}") if os.path.exists(arg): before_mtime = os.path.getmtime(arg) try: ret = subprocess.call(args=cmdArgs, shell=False) log.verbose(f"ret: {ret}") if ret != 0: log.warning("process exited with error") except Exception as e: log.error(f"error running command '{cmd} {arg}' -> {e}") return False if not os.path.exists(arg): return False after_mtime = os.path.getmtime(arg) log.verbose(f"before mtime: {before_mtime}") log.verbose(f"after mtime: {after_mtime}") return after_mtime > before_mtime
def encryptVPath(path): log.verbose(f"encryptVPath path={path}") (plainPath, vpath) = validateEncryptVPath(path) log.verbose(f"encryptVpath plainPath={plainPath}, vpath={vpath}") pp64 = getPassPhrase(config.CONFIG['general']['group'], True) crypto.encryptFile(plainPath, vpath, pp64) file.deletePath(plainPath)
def cmd_reencrypt(cmd, path): log.verbose(f"cmd_reencrypt cmd={cmd}, path={path}") tmpdir = utils.getTmpDir() try: tmppath = os.path.join(tmpdir, str(os.getpid())) vault.reencryptVPath(path, tmppath) finally: file.deleteDir(tmpdir)
def openVPath(path, destpath, cmd): log.verbose(f"openVPath {path} {destpath} {cmd}") (plainPath, vpath) = validateOpenVPath(path) pp64 = getPassPhrase(config.CONFIG['general']['group'], False) crypto.decryptFile(vpath, destpath, pp64) if destpath is not None and cmd is not None: if utils.runCommand(cmd, destpath): log.warning("file has been modified; updates discarded")
def cmd_edit(cmd, path): log.verbose(f"cmd_edit {cmd}, {path}") tmpdir = utils.getTmpDir() try: tmppath = os.path.join(tmpdir, str(os.getpid())) vault.editVPath(path, tmppath, cmd) finally: file.deleteDir(tmpdir)
def deletePath(path): log.verbose(f"deletePath path={path}") if not os.path.exists(path): log.info("path " + path + " does not exist") else: utils.runCommand('delete_file', path) if os.path.exists(path): raise errors.VaultError(f"error deleting file {path}")
def connect(sock, port): try: log.verbose(f"connecting to server on port {port}") sock.connect(('localhost', int(port))) except socket.error as e: if e.errno == errno.ECONNREFUSED: return False log.error(f"error connecting to server: {e}") raise e return True
def getTmpDir(): td = getVaultDir() prefix = config.CONFIG['internal']['tmp.dir.prefix'] p = "%s%s" % (prefix, str(os.getpid())) path = os.path.join(td, p) log.verbose(f"tmp tmp dir: {path}") if os.path.exists(path): log.error(f"tempory directory {path} already exists") os.mkdir(path) if not os.path.exists(path): log.error(f"tempory directory {path} was not created") return path
def reencryptVPath(path, destpath): log.verbose(f"reencryptVPath path={path}, destpath={destpath}") (plainPath, vpath) = validateReencryptVPath(path) log.verbose(f"reencryptVpath plainPath={plainPath}, vpath={vpath}") group = config.CONFIG['general']['group'] group_current = group + ".current" group_new = group + ".new" pp64 = getPassPhrase(group_current, False, " (current)") crypto.decryptFile(vpath, destpath, pp64) pp64 = getPassPhrase(group_new, True, " (new)") crypto.encryptFile(destpath, vpath, pp64)
def deleteDir(path): if not re.match("^/\w+/\w+", path): raise errors.VaultError(f"invalid vault directory") log.verbose(f"deleteDir path={path}") if not os.path.exists(path): log.info(f"path {path} does not exist") else: utils.runCommand('delete_dir', path) if os.path.exists(path): raise errors.VaultError(f"error deleting directory {path}")
def getServerPort(vdir): log.verbose(f"getServerPort vdir={vdir}") pidFile = os.path.join(vdir, 'server.pid') if not os.path.exists(pidFile): log.verbose(f"pidfile {pidFile} does not exist") log.info("launching server") args = ['gpg-vault-server', vdir] global pid pid = subprocess.Popen(args=args, shell=False).pid log.verbose(f"pid: {pid}") count = 10 while not os.path.exists(pidFile): log.info("waiting for server to start") time.sleep(1) f = open(pidFile, "r") lines = f.readlines() if lines is None or len(lines) != 2: log.error(f"cannot read pidFile {pidFile}") exit(1) port = int(lines[1]) log.verbose(f"found server port={port}") return port
def getPassPhrase(group, confirm, tag=""): log.verbose(f"getting passphrase group={group}, comfirm={confirm}") pp = client.sendRequest(['get', group]) if len(pp) >= 1: log.verbose("got passphrase from server") log.sensitive(f"passphrase (base64): {pp[0]}") return pp[0] pp = '' try: while pp == '': pp = getpass.getpass(f"Passphrase{tag}: ") if confirm: pp2 = '' while pp2 == '': pp2 = getpass.getpass(f"Confirm Passphrase{tag}: ") if pp != pp2: del pp del pp2 raise errors.VaultSecurityError("Passphrases do not match") del pp2 log.sensitive(f"passphrase |{pp}|") pp64 = utils.str2b64( pp) # base64.b64encode(pp.encode('utf-8')).decode('utf-8') log.sensitive(f"base64 |{pp64}|") client.sendRequest(['set', group, pp64]) del pp return pp64 except EOFError as e: log.verbose(f"exception raised: {e}") raise errors.VaultQuit(str(e)) except Exception as e: log.verbose(f"exception raised: {e}") raise errors.VaultQuit(str(e))
def validateVPaths(path): (plainPath, vpath) = validateVPath(path) log.verbose(f"validating plainPath={plainPath}, vpath={vpath}") if os.path.islink(plainPath): raise errors.VaultError("'" + plainPath + "' is a symbolic link") if os.path.exists(plainPath) and os.path.exists(vpath): if os.path.getmtime(plainPath) > os.path.getmtime(vpath): older = f"{plainPath} is more recent" else: older = f"{vpath} is more recent" raise errors.VaultError( f"both '{plainPath}' and '{vpath}' exist ({older})") if os.path.islink(vpath): raise errors.VaultError(f"'{vpath}' is a symbolic link") return (plainPath, vpath)
def run(cmd, files): client.sendRequest(['ping']) if files is None: runForFile(cmd, None) else: for file in config.CONFIG['files']: log.verbose("running command '" + cmd + "' on file '" + file + "'") runForFile(cmd, file) if utils.isTrue(config.CONFIG['general']['kill_server']): client.killServer() exit(0)
def backupFile(path): log.verbose(f"backupFile path={path}") if not os.path.exists(path): return backup_path = path + config.CONFIG['internal']['ext.backup'] if os.path.exists(backup_path): deletePath(backup_path) log.verbose(f"backupFile renaming {path} to {backup_path}") os.rename(path, backup_path) if os.path.exists(path): raise VaultError( f"error creating backup of {path}; file still exists after renaming to {backup_path}" ) if not os.path.exists(backup_path): raise VaultError( f"error creating backup of {path}; backup file {backup_path} not created" )
def decryptFile(vpath, destPath, pp64): log.verbose(f"decyptFile vpath={vpath} destPath={destPath}") passphrase = utils.b642str(pp64) log.sensitive(f"passphrase |{passphrase}|") args = [ 'gpg', '--quiet', '--batch', '--ignore-mdc-error', '--decrypt', '--passphrase-fd', '0' ] if destPath is not None: args.extend(['--output', destPath]) args.append(vpath) log.verbose(f"args: {args}") gpgErrorLog = os.path.join(utils.getVaultDir(), "gpg.error.log") try: gpgError = open(gpgErrorLog, "a") except IOError: raise errors.VaultError( f"Unable to open gpg error log file '{gpgErrorlog}'") try: gpg = subprocess.Popen(args=args, shell=False, stdin=subprocess.PIPE, stderr=gpgError) gpg.stdin.write(passphrase.encode('utf-8')) gpg.stdin.close() gpg.wait() if gpg.returncode != 0: raise errors.VaultSecurityError("Failed to decrypt file '" + vpath + "'") finally: log.verbose("closing gpg error file") if gpgError: gpgError.close
def encryptFile(srcPath, destPath, pp64): log.verbose(f"encyptFile srcPath={srcPath} destPath={destPath}") passphrase = utils.b642str(pp64) log.sensitive(f"passphrase |{passphrase}|") if os.path.exists(destPath): file.deletePath(destPath) args = [ 'gpg', '--quiet', '--batch', '-c', '--symmetric', '--passphrase-fd', '0', '--output', destPath, srcPath ] log.verbose(f"args: {args}") gpgErrorLog = os.path.join(utils.getVaultDir(), "gpg.stderr.log") log.info(f"encrypting file {srcPath} to {destPath}") try: gpgError = open(gpgErrorLog, "a") except IOError: raise errors.VaultError("Unable to open gpg error log file '" + gpgErrorLog + "'") try: gpg = subprocess.Popen(args=args, shell=False, stdin=subprocess.PIPE, stderr=gpgError) gpg.stdin.write(passphrase.encode('utf-8')) gpg.stdin.close() gpg.wait() if gpg.returncode != 0: raise errors.VaultSecurityError( "Failed to encrypt file '" + srcPath + "'", []) finally: log.verbose("closing gpg error file") if gpgError: gpgError.close
def sendRequest(args): log.verbose(f"sendRequest args={args}") req = " ".join(args) tryAgain = True while tryAgain: tryAgain = False port = getServerPort(utils.getVaultDir()) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: if connect(sock, port): log.verbose("sending request to server") sock.sendall((req + "\n").encode('utf-8')) reply = sock.recv(1024) else: # Assume the server is not running log.info("cannot connect; trying again") time.sleep(1) deleteServerPort(utils.getVaultDir()) tryAgain = True continue finally: sock.close() ret = re.split('\s+', reply.decode('utf-8')) if ret is None or len(ret) < 1: raise VaultError('unable to process reply') log.sensitive(f"response: {ret}") if ret[0] == 'ok': log.verbose('reply ok') return ret[1:] elif ret[0] == 'error': log.verbose(f"reply {ret}") raise errors.VaultSecurityError(str(ret)) else: log.verbose(f"unexpected reply {ret}") raise errors.VaultError('unable to process reply')
def getVPathExt(path): for vext in config.CONFIG['internal']['vexts']: vpath = path + vext log.verbose(f"checking for vpath {vpath}") if os.path.exists(vpath): log.verbose(f"found {vpath}") return vext log.verbose("no files found; using default extension") return config.CONFIG['internal']['vext.default']
def editVPath(path, destpath, cmd): log.verbose(f"editVPath {path} {destpath} {cmd}") (plainPath, vpath) = validateEditVPath(path) log.verbose(f"editVpath plainPath={plainPath}, vpath={vpath}") if os.path.exists(vpath): log.verbose(f"editVpath vpath {vpath} exists; decrypting") pp64 = getPassPhrase(config.CONFIG['general']['group'], False) crypto.decryptFile(vpath, destpath, pp64) else: log.verbose(f"editVpath vpath {vpath} does not exist") pp64 = getPassPhrase(config.CONFIG['general']['group'], True) if utils.runCommand(cmd, destpath): file.backupFile(path) crypto.encryptFile(destpath, vpath, pp64) else: if os.path.exists(destpath): log.warning("file has not been modified") else: log.warning("file has not been created") file.deletePath(destpath)
def validateVPath(path): log.verbose(f"validating path='{path}'") (base, ext) = utils.splitPath(path) log.verbose(f"base={base}, ext={ext}") (vext) = getVPathExt(base + ext) log.verbose(f"vpath ext={vext}") if base == '': raise errors.VaultError(f"Invalid path '{path}' (base)") if ext == '': raise errors.VaultError(f"Invalid path '{path}' (ext)") if vext == '': raise errors.VaultError(f"Invalid path '{path}' (vext)") return (base + ext, base + ext + vext)
def getVaultDir(): home = os.path.expanduser("~") log.verbose(f"home dir: {home}") if not os.path.exists(home): error(f"Directory {home} does not exist") t = config.WORK_DIR_NAME vdir = os.path.join(home, t) log.verbose(f"vault directory: {vdir}") if not os.path.exists(vdir): log.verbose(f"creating directory {vdir}") os.mkdir(vdir) if not os.path.exists(vdir): log.error(f"failed to create directory {vdir}") return vdir
def cmd_encrypt(cmd, path): log.verbose(f"cmd_encrypt cmd={cmd}, path={path}") vault.encryptVPath(path)
def cmd_clear(cmd, path): log.verbose(f"cmd_clear {cmd}, {path}") cmd_reset()
def __init__(self, e): log.verbose(f"VaultQuit: {e}") self.error = e
def __init__(self, e): log.verbose(f"VaultSecurityError: {e}") self.error = e
def cmd_reset(): log.verbose("sending reset request") client.sendRequest(['reset'])
def cmd_cat(cmd, path): log.verbose(f"cmd_cat {cmd}, {path}") vault.openVPath(path, None, None) print("") utils.oswarning()