diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-28 11:38:40 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2012-07-28 11:38:40 +0100 |
commit | 423ce84b1877b8efca404221b1fdb0488e6cb6db (patch) | |
tree | 4f47243e47f54cc571758af0ab66db6936af8585 /tools/gnome-encfs | |
download | resources-423ce84b1877b8efca404221b1fdb0488e6cb6db.tar.bz2 |
RES: Initial crud
Diffstat (limited to 'tools/gnome-encfs')
-rwxr-xr-x | tools/gnome-encfs | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/tools/gnome-encfs b/tools/gnome-encfs new file mode 100755 index 0000000..f760797 --- /dev/null +++ b/tools/gnome-encfs @@ -0,0 +1,423 @@ +#!/usr/bin/python + +# ============================================================================= +# +# gnome-encfs - GNOME keyring and auto-mount integration of EncFS folders. +# Copyright (C) 2010 Oben Sonne <obensonne@googlemail.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ============================================================================= + +import getpass +import os +import os.path +import optparse +import subprocess +import sys + +from xdg.DesktopEntry import DesktopEntry as xdg_de +from xdg.BaseDirectory import xdg_config_home as xdg_ch + +import gtk # automatically set application name +import gnomekeyring as gk + +# ============================================================================= +# test mode related +# ============================================================================= + +TEST = "GNOME_ENCFS_TEST" in os.environ + +class preset: + """Preset user input. Used for non-interactive testing mode.""" + + proceed = None + password = None + epath = None + mpoint = None + amount = None + +# ============================================================================= +# constants +# ============================================================================= + +# attributes to detect gnome-encfs items in keyring +GENCFS_ATTR = {"gnome-encfs": TEST and "test" or "" } + +KEYRING = gk.get_default_keyring_sync() + +ITYPE = gk.ITEM_GENERIC_SECRET + +MSG_NO_MATCH = ("No matching EncFS items in keyring.\n" + "Use option --list to show available items or " + "option --add to add items.") + +USAGE = """Usage: %prog --list + %prog --mount [ENCFS-PATH-or-MOUNT-POINT] + %prog --add ENCFS-PATH MOUNT-POINT + %prog --edit MOUNT-POINT + %prog --remove MOUNT-POINT""" + +DESCRIPTION = """Painlessly mount and manage EncFS folders using GNOME's +keyring.""" + +EPILOG = """This tool stores EncFS paths, +corresponding mount points as well as passwords in the GNOME keyring and +optionally mounts EncFS paths automatically on login to a GNOME session (works +only if gnome-encfs is installed in the system path, at /usr/bin or +/usr/local/bin). +""" + +VERSION="0.1" + +# ============================================================================= +# helper +# ============================================================================= + +def _options(): + """Parse command line options.""" + + op = optparse.OptionParser(usage=USAGE, version=VERSION, + description=DESCRIPTION, epilog=EPILOG) + + op.add_option("-l", "--list", action="store_true", default=False, + help="list all EncFS items stored in keyring") + op.add_option("-m", "--mount", action="store_true", default=False, + help="mount all or selected EncFS paths stored in keyring") + op.add_option("-a", "--add", action="store_true", default=False, + help="add a new EncFS item to keyring") + op.add_option("-e", "--edit", action="store_true", default=False, + help="edit an EncFS item in keyring") + op.add_option("-r", "--remove", action="store_true", default=False, + help="remove an EncFS item from keyring") + + og = optparse.OptionGroup(op, "Non-interactive") + og.add_option("", "--proceed", default=None, + help="Input for proceed question") + og.add_option("", "--password", default=None, metavar="PW", + help="Input for password prompt") + og.add_option("", "--epath", default=None, + help="Input for EncFS path edit") + og.add_option("", "--mpoint", default=None, + help="Input for mount point edit") + og.add_option("", "--amount", default=None, + help="Input for auto mount question") + op.add_option_group(og) + + opts, args = op.parse_args() + + try: + args.remove("autostart") + opts.autostart = True + opts.mount = True + except ValueError: + opts.autostart = False + + if opts.list + opts.mount + opts.add + opts.edit + opts.remove != 1: + op.print_help() + op.exit(1) + + # normalize paths + args = [_pathify(a) for a in args] + + opts.p1 = args and args.pop(0) or None + opts.p2 = args and args.pop(0) or None + + if opts.add and not (opts.p1 and opts.p2): + op.error("add needs an EncFS path and a moint point") + op.print_help() + op.exit(1) + + if opts.remove and not opts.p1: + op.error("remove needs a moint point") + op.print_help() + op.exit(1) + + preset.proceed = opts.proceed + preset.password = opts.password + preset.epath = opts.epath + preset.mpoint = opts.mpoint + preset.amount = opts.amount + + return opts + +def _exit(ec): + """Exit with additional check if autostart file is still needed.""" + + _autostart(_get_items(amount="y")) + sys.exit(ec) + +def _proceed(msg): + print("Warning: %s" % msg) + proceed = preset.proceed or raw_input("Proceed [y/N]: ") + if proceed.strip()[0].lower() != "y": + _exit(2) + +def _pathify(path): + + path = os.path.expanduser(path) + path = os.path.expandvars(path) + path = os.path.abspath(path) + path = os.path.realpath(path) + return path + +def _is_mounted(mpoint): + """Check of something is mounted at given mount point.""" + + p = subprocess.Popen(["mount"], stdout=subprocess.PIPE) + mount = p.communicate()[0] + lines = mount.strip('\n').split('\n') + points = map(lambda line: line.split()[2], lines) + points = [os.path.abspath(p) for p in points] + return os.path.abspath(mpoint) in points + +def _is_encfs(epath): + """Check if 'epath' points to an EncFS directory.""" + + p = subprocess.Popen(["encfsctl", "info", epath], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + p.communicate() + return p.returncode == 0 + +_items_cached = None + +def _get_items(mpoint=None, epath=None, anypath=None, amount=None): + """Get all EncFS items or those matching given attributes.""" + + global _items_cached + + if _items_cached is None: + try: + _items_cached = gk.find_items_sync(ITYPE, GENCFS_ATTR) + except gk.NoMatchError: + _items_cached = [] + + match = [] + for item in _items_cached: + if mpoint and item.attributes["mount-point"] != mpoint: + continue + if epath and item.attributes["encfs-path"] != epath: + continue + if anypath and (item.attributes["mount-point"] != anypath and + item.attributes["encfs-path"] != anypath): + continue + if amount and item.attributes["auto-mount"] != amount: + continue + match.append(item) + + return match + +def _autostart(enable): + """Set up XDG autostart file.""" + + if TEST: + fname = os.path.join(os.path.curdir, "autostart.desktop") + else: + fname = os.path.join(xdg_ch, "autostart", "gnome-encfs.desktop") + + if not enable: + if os.path.exists(fname): + os.remove(fname) + return + + content = { + "Exec": "gnome-encfs autostart", + "Name": "EncFS", + "Comment": "Mount EncFS folders configured in GNOME's keyring", + "Icon": "folder", + "Version": "1.0", + "X-GNOME-Autostart-enabled": "true" + } + + de = xdg_de(filename=fname) + for key, value in content.items(): + de.set(key, value) + de.validate() + de.write() + +# ============================================================================= +# actions +# ============================================================================= + +def list_items(path=None): + """List EncFS items in keyring.""" + + items = _get_items(anypath=path) + + if not items and path: + print(MSG_NO_MATCH) + return False + + for item in items: + epath = item.attributes["encfs-path"] + mpoint = item.attributes["mount-point"] + amount = item.attributes["auto-mount"] + print("* encfs path : %s" % epath) + print(" mount point : %s" % mpoint) + print(" mount at login : %s" % (amount == "y" and "yes" or "no")) + + return True + +def add_item(epath, mpoint): + """Add new EncFS item to keyring.""" + + if not _is_encfs(epath): + _proceed("no EncFS at given path") + if not os.path.isdir(mpoint): + _proceed("mount point is not a directory") + if _get_items(mpoint=mpoint): + _proceed("mount point already in keyring") + + secret = preset.password or getpass.getpass("EncFS password: ") + amount = preset.amount or raw_input("Mount at login [Y/n]: ") or "y" + amount = amount.strip()[0].lower() == "y" and "y" or "n" + attr = {"encfs-path": epath, "mount-point": mpoint, "auto-mount": amount} + attr.update(GENCFS_ATTR) + name = "EncFS mount at %s" % mpoint + gk.item_create_sync(KEYRING, ITYPE, name, attr, secret, False) + + global _items_cached + _items_cached = None + + return True + +def edit_item(mpoint): + """Edit EncFS item in keyring.""" + + items = _get_items() + edits = _get_items(mpoint=mpoint) + + if not edits: + print(MSG_NO_MATCH) + return False + + for item in edits: + + # get item data + epath = item.attributes["encfs-path"] + mpoint = item.attributes["mount-point"] + amount = item.attributes["auto-mount"] + epath = preset.epath or raw_input("EncFS path [%s]: " % epath) or epath + mpoint = preset.mpoint or raw_input("Mount point [%s]: " % mpoint) or mpoint + secret = preset.password or getpass.getpass("Password [**current**]: ") or item.secret + hint = amount == "y" and "Y/n" or "y/N" + amount = preset.amount or raw_input("Mount at login [%s]: " % hint) or amount + amount = amount.strip()[0].lower() == "y" and "y" or "n" + mpoint = _pathify(mpoint) + epath = _pathify(epath) + + # check item data + for other in [i for i in items if i.item_id != item.item_id]: + if other.attributes["mount-point"] == mpoint: + _proceed("mount point already in use") + if not _is_encfs(epath): + _proceed("no EncFS at given path") + if not os.path.isdir(mpoint): + _proceed("mount point is not a directory") + + # update item data + attributes = GENCFS_ATTR.copy() + attributes["encfs-path"] = epath + attributes["mount-point"] = mpoint + attributes["auto-mount"] = amount + gk.item_set_attributes_sync(KEYRING, item.item_id, attributes) + info = gk.item_get_info_sync(KEYRING, item.item_id) + info.set_secret(secret) + gk.item_set_info_sync(KEYRING, item.item_id, info) + + global _items_cached + _items_cached = None + + return True + +def remove_item(mpoint): + """Remove EncFS item from keyring.""" + + items = _get_items(mpoint=mpoint) + + if not items: + print(MSG_NO_MATCH) + return False + + for item in items: + gk.item_delete_sync(KEYRING, item.item_id) + + global _items_cached + _items_cached = None + + return True + +def mount_items(path, autostart): + """Mount selected items. + + If `path` is set, mount only those items where the EncFS path or mount + point equals `path`. If `autostart` is True, mount only those items where + auto-mount is set to 'y'. + + """ + items = _get_items(anypath=path, amount=(autostart and "y" or None)) + + if not items and path: + print(MSG_NO_MATCH) + return False + + rc = True + + for item in items: + epath = item.attributes["encfs-path"] + mpoint = item.attributes["mount-point"] + msg = "Mounting %s at %s: " % (epath, mpoint) + if _is_mounted(mpoint): + msg += "mount point already in use" + elif not os.path.isdir(mpoint): + msg += "mount point does not exist or is not a directory" + rc = False + else: + cmd = ["encfs", "-o", "nonempty", "-S", epath, mpoint] + p = subprocess.Popen(cmd, stdin=subprocess.PIPE) + p.communicate(input="%s\n" % item.secret) + msg += p.returncode and "FAILED" or "OK" + rc &= not p.returncode + + print(msg) + + return rc + +# ============================================================================= +# main +# ============================================================================= + +def main(): + + opts = _options() + + if opts.add: + ok = add_item(opts.p1, opts.p2) + elif opts.list: + ok = list_items(opts.p1) + elif opts.mount: + ok = mount_items(opts.p1, opts.autostart) + elif opts.edit: + ok = edit_item(opts.p1) + elif opts.remove: + ok = remove_item(opts.p1) + else: + assert False + + return ok + +if __name__ == '__main__': + + ret = main() + _exit(int(not ret)) |