ed4deec100e8 — Frédéric Lespez 12 years ago
Add the ability to handle custom locations for EncFS config files

If you moved EncFS config files (.encfs5 or .encfs6.xml) outside their EncFS
folders to increase security (for example when storing data online), you can
now provide this custom locations to gnome-encfs.
5 files changed, 223 insertions(+), 17 deletions(-)

M README.md
M gnome-encfs
M tests/tenv.tar
M tests/test.exp
M tests/test.sh
M README.md +22 -1
@@ 10,7 10,9 @@ mounting them painlessly (i.e. no passwo
 automount solutions like *pam-encfs* and *pam-mount* which require to use the
 same password for EncFS folders as for your local user account. This is bad
 because local account passwords usually are weaker than those one should use
-for encrypting online stored data, e.g. in a [Dropbox][dbx].
+for encrypting online stored data, e.g. in a [Dropbox][dbx]. In this case, you
+can also move your EncFs config files (.encfs5 or .encfs6.xml) outside their
+EncFS folders to increase security so they are not stored online (See below).
 
 [![Flattr this][flattr-img]][flattr-url]
 

          
@@ 50,6 52,25 @@ mounted to `~/Private`. Make it known to
 This adds the EncFS path, its mount location and password to the GNOME keyring
 and sets up a GNOME autostart entry to mount it at GNOME login (if enabled).
 
+### Add an EncFS folder with a custom location EncFs config file
+
+Suppose you have an EncFS folder at `~/.Private.encrypted` which should get
+mounted to `~/Private`.
+
+And suppose you move the EncFS config file (.encfs5 or .encfs6.xml), from 
+`~/.Private.encrypted/.encfs6.xml` to `~/Private_encfs6.xml`.
+
+Make it known to *gnome-encfs*:
+
+    $ gnome-encfs -a ~/.Private.encrypted ~/Private
+    Custom EncFS config file path [**Default location**]: ~/Private_encfs6.xml
+    EncFS password: <enter encfs password>
+    Mount at login [Y/n]: <say 'y' or 'n'>
+
+This adds the EncFS path, its mount location, the location of its EncFS config
+file and password to the GNOME keyring and sets up a GNOME autostart entry to
+mount it at GNOME login (if enabled).
+
 ### Mount an EncFS folder
 
 If you said *y* above to the login mount question, the EncFS folder gets

          
M gnome-encfs +70 -9
@@ 47,6 47,7 @@ class preset:
     epath = None
     mpoint = None
     amount = None
+    encfs_config =None
 
 # =============================================================================
 # constants

          
@@ 113,6 114,8 @@ def _options():
                   help="Input for mount point edit")
     og.add_option("", "--amount", default=None,
                   help="Input for auto mount question")
+    og.add_option("", "--encfs-config", default=None,
+                  help="Input for custom EncFS config file path (Use 'default' to reset path to its default value during an edit)")
     op.add_option_group(og)
 
     opts, args = op.parse_args()

          
@@ 149,6 152,7 @@ def _options():
     preset.epath = opts.epath
     preset.mpoint = opts.mpoint
     preset.amount = opts.amount
+    preset.encfs_config = opts.encfs_config
 
     return opts
 

          
@@ 172,6 176,21 @@ def _pathify(path):
     path = os.path.realpath(path)
     return path
 
+def _setup_env(encfs_config):
+    """Set up a custom environnemnt for encfs process"""
+
+    custom_env = None
+    if encfs_config:
+        custom_env = os.environ.copy()
+        cmd = ["file", "-b", "--mime-type", encfs_config]
+        p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+        mime_type = p.communicate()[0].strip()
+        if mime_type == 'application/xml':
+            custom_env["ENCFS6_CONFIG"] = encfs_config
+        else:
+            custom_env["ENCFS5_CONFIG"] = encfs_config
+    return custom_env
+
 def _is_mounted(mpoint):
     """Check of something is mounted at given mount point."""
 

          
@@ 182,11 201,12 @@ def _is_mounted(mpoint):
     points = [os.path.abspath(p) for p in points]
     return os.path.abspath(mpoint) in points
 
-def _is_encfs(epath):
+def _is_encfs(epath, encfs_config=None):
     """Check if 'epath' points to an EncFS directory."""
 
+    encfs_env = _setup_env(encfs_config)
     p = subprocess.Popen(["encfsctl", "info", epath], stdout=subprocess.PIPE,
-                         stderr=subprocess.PIPE)
+                         stderr=subprocess.PIPE, env=encfs_env)
     p.communicate()
     return p.returncode == 0
 

          
@@ 264,26 284,45 @@ def list_items(path=None):
         epath = item.attributes["encfs-path"]
         mpoint = item.attributes["mount-point"]
         amount = item.attributes["auto-mount"]
+        encfs_config = item.attributes.get("encfs-config", None)
         print("* encfs path     : %s" % epath)
         print("  mount point    : %s" % mpoint)
         print("  mount at login : %s" % (amount == "y" and "yes" or "no"))
+        if encfs_config:
+            print("  Custom EncFS config file path : %s" % encfs_config)
 
     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")
+    
+    encfs_config = None
+    if not _is_encfs(epath):
+        # Either epath is not an EncFS stash, or EncFS config file is elsewhere
+        encfs_config = preset.encfs_config or raw_input("Custom EncFS config file path [**Default location**]: ") or None
+        if ''.join(encfs_config).strip().lower() == "default":
+            encfs_config = None
+        if encfs_config:
+            encfs_config = _pathify(encfs_config)
+            if not os.path.isfile(encfs_config):
+                _proceed("Custom EncFS config file path is not a file")
+    if not _is_encfs(epath, encfs_config):
+        _proceed("no EncFS at given path")
 
     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}
+    if encfs_config:
+        attr = {"encfs-path": epath, "mount-point": mpoint,
+                "auto-mount": amount, "encfs-config": encfs_config}
+    else:
+        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)

          
@@ 309,20 348,33 @@ def edit_item(mpoint):
         epath = item.attributes["encfs-path"]
         mpoint = item.attributes["mount-point"]
         amount = item.attributes["auto-mount"]
+        encfs_config = item.attributes.get("encfs-config", "default")
+            
         epath = preset.epath or raw_input("EncFS path [%s]: " % epath) or epath
         mpoint = preset.mpoint or raw_input("Mount point [%s]: " % mpoint) or mpoint
+        hint = encfs_config if (encfs_config != "default")  else "**Default location**"
+        encfs_config = preset.encfs_config \
+                        or raw_input("Custom EncFS config file path - Type 'default' for the default " +
+                                     "location under EncFS path [%s]: " % hint) \
+                        or encfs_config
+        if ''.join(encfs_config).strip().lower() == "default":
+            encfs_config = None
         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)
-
+        if encfs_config:
+            encfs_config = _pathify(encfs_config)
+        
         # 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):
+        if encfs_config and not os.path.isfile(encfs_config):
+            _proceed("EncFS config file path is not a file")
+        if not _is_encfs(epath, encfs_config):
             _proceed("no EncFS at given path")
         if not os.path.isdir(mpoint):
             _proceed("mount point is not a directory")

          
@@ 332,6 384,8 @@ def edit_item(mpoint):
         attributes["encfs-path"] = epath
         attributes["mount-point"] = mpoint
         attributes["auto-mount"] = amount
+        if encfs_config:
+            attributes["encfs-config"] = encfs_config
         gk.item_set_attributes_sync(KEYRING, item.item_id, attributes)
         info = gk.item_get_info_sync(KEYRING, item.item_id)
         info.set_secret(secret)

          
@@ 378,15 432,22 @@ def mount_items(path, autostart):
     for item in items:
         epath = item.attributes["encfs-path"]
         mpoint = item.attributes["mount-point"]
-        msg = "Mounting %s at %s: " % (epath, mpoint)
+        encfs_config = item.attributes.get("encfs-config", None)
+
+        if encfs_config:
+            msg = "Mounting %s at %s with custom EncFS config file %s: " % (epath, mpoint, encfs_config)
+        else:
+            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:
+            encfs_env = _setup_env(encfs_config)
             cmd = ["encfs", "-o", "nonempty", "-S", epath, mpoint]
-            p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+            p = subprocess.Popen(cmd, stdin=subprocess.PIPE, env=encfs_env)
             p.communicate(input="%s\n" % item.secret)
             msg += p.returncode and "FAILED" or "OK"
             rc &= not p.returncode

          
M tests/tenv.tar +0 -0

        
M tests/test.exp +48 -0
@@ 145,6 145,54 @@ Icon=folder
 # EXPECT: 1 succeeding edits
 # EXPECT: autostart off
 autostart off
+# EXPECT: succeeding add with a custom EncFS5 config file (1)
+# EXPECT: 1 listed item (1)
+* encfs path     : ./tenv/e1
+  mount point    : ./tenv/m1
+  mount at login : no
+  Custom EncFS config file path : ./tenv/e1_encfs5
+# EXPECT: 1 succeeding mounts (1)
+Mounting ./tenv/e1_encfs5: OK
+# EXPECT: 1 mounted paths (1)
+encfs on ./tenv/m1 type fuse.encfs (rw,nosuid,nodev,default_permissions,)
+# EXPECT: no mounted paths - all unmounted
+# EXPECT: succeeding edit to set back EncFS5 config file to its default location (1)
+# EXPECT: 1 listed item (1)
+* encfs path     : ./tenv/e1
+  mount point    : ./tenv/m1
+  mount at login : no
+# EXPECT: succeeding edit with a custom EncFS5 config file (1)
+# EXPECT: 1 listed item (1)
+* encfs path     : ./tenv/e1
+  mount point    : ./tenv/m1
+  mount at login : no
+  Custom EncFS config file path : ./tenv/e1_encfs5
+# EXPECT: succeeding remove (1)
+# EXPECT: 0 items
+# EXPECT: succeeding add with a custom EncFS6 config file (1)
+# EXPECT: 1 listed item (2)
+* encfs path     : ./tenv/e2
+  mount point    : ./tenv/m2
+  mount at login : no
+  Custom EncFS config file path : ./tenv/e2_encfs6.xml
+# EXPECT: 1 succeeding mounts (2)
+Mounting ./tenv/e2_encfs6.xml: OK
+# EXPECT: 1 mounted paths (2)
+encfs on ./tenv/m2 type fuse.encfs (rw,nosuid,nodev,default_permissions,)
+# EXPECT: no mounted paths - all unmounted
+# EXPECT: succeeding edit to set back EncFS6 config file to its default location (2)
+# EXPECT: 1 listed item (2)
+* encfs path     : ./tenv/e2
+  mount point    : ./tenv/m2
+  mount at login : no
+# EXPECT: succeeding edit with a custom EncFS6 config file (2)
+# EXPECT: 1 listed item (2)
+* encfs path     : ./tenv/e2
+  mount point    : ./tenv/m2
+  mount at login : no
+  Custom EncFS config file path : ./tenv/e2_encfs6.xml
+# EXPECT: succeeding remove (2)
+# EXPECT: 0 items
 # EXPECT: no listed items
 # EXPECT: autostart off
 autostart off

          
M tests/test.sh +83 -7
@@ 84,11 84,11 @@ mount | grep "/tenv/m[0-9]" | sed -e "s/
 expect "3 items (1,2,3b)"
 $GENCFS -l
 
-$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m3a --amount y
+$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m3a --amount y --encfs-config default
 expect "3 items (1,2,3a)"
 $GENCFS -l
 
-$GENCFS -e $TENV/m3a --password px --epath $TENV/e3 --mpoint $TENV/m3a --amount y
+$GENCFS -e $TENV/m3a --password px --epath $TENV/e3 --mpoint $TENV/m3a --amount y --encfs-config default
 expect "3 items (1,2,3a)"
 $GENCFS -l
 

          
@@ 97,7 97,7 @@ expect "1 failing mount (3a) - wrong pas
 expect "no mounted paths"
 mount | grep "/tenv/m[0-9]" | sed -e "s/user=\w\+//"
 
-$GENCFS -e $TENV/m3a --password p3 --epath $TENV/e3 --mpoint $TENV/m3b --amount y
+$GENCFS -e $TENV/m3a --password p3 --epath $TENV/e3 --mpoint $TENV/m3b --amount y --encfs-config default
 expect "3 items (1,2,3b)"
 $GENCFS -l
 

          
@@ 112,20 112,20 @@ expect "no mounted paths - all unmounted
 mount | grep "/tenv/m[0-9]" | sed -e "s/user=\w\+//"
 
 expect "failing edit (3b->2) - mount point in use"
-$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m2 --proceed n --amount y
+$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m2 --proceed n --amount y --encfs-config default
 expect "3 items (1,2,3b)"
 $GENCFS -l
 expect "autostart on"
 test -e autostart.desktop && echo "autostart on" ||  echo "autostart off"
 expect "2 succeeding edits"
-$GENCFS -e $TENV/m1 --password p1 --epath $TENV/e1 --mpoint $TENV/m1 --proceed n --amount n
-$GENCFS -e $TENV/m2 --password p2 --epath $TENV/e2 --mpoint $TENV/m2 --proceed n --amount n
+$GENCFS -e $TENV/m1 --password p1 --epath $TENV/e1 --mpoint $TENV/m1 --proceed n --amount n --encfs-config default
+$GENCFS -e $TENV/m2 --password p2 --epath $TENV/e2 --mpoint $TENV/m2 --proceed n --amount n --encfs-config default
 expect "autostart on"
 test -e autostart.desktop && echo "autostart on" ||  echo "autostart off"
 expect "autostart content"
 cat autostart.desktop
 expect "1 succeeding edits"
-$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m3b --proceed n --amount n
+$GENCFS -e $TENV/m3b --password p3 --epath $TENV/e3 --mpoint $TENV/m3b --proceed n --amount n --encfs-config default
 expect "autostart off"
 test -e autostart.desktop && echo "autostart on" ||  echo "autostart off"
 

          
@@ 133,6 133,82 @@ test -e autostart.desktop && echo "autos
 $GENCFS -l | grep "mount point" | grep "/tenv/m[0-9]" | awk {'print $4'} | \
     while read MP ; do $GENCFS -r $MP ; done
 
+# test EncFS config file custom location (encfs5)
+expect "succeeding add with a custom EncFS5 config file (1)"
+mv $TENV/e1/.encfs5 $TENV/e1_encfs5
+$GENCFS -a $TENV/e1 $TENV/m1 --password p1 --proceed n --amount n --encfs-config $TENV/e1_encfs5
+expect "1 listed item (1)"
+$GENCFS -l
+
+expect "1 succeeding mounts (1)"
+$GENCFS -m $TENV/e1
+expect "1 mounted paths (1)"
+mount | grep "/tenv/m[0-9]" | sed -e "s/user=\w\+//"
+
+for MPOINT in $TENV/m1* ; do
+        fusermount -u $MPOINT 2>&1
+done
+expect "no mounted paths - all unmounted"
+mount | grep "/tenv/m[0-9]"
+
+expect "succeeding edit to set back EncFS5 config file to its default location (1)"
+mv $TENV/e1_encfs5 $TENV/e1/.encfs5
+$GENCFS -e $TENV/m1 --password p1 --epath $TENV/e1 --mpoint $TENV/m1 --proceed n --amount n --encfs-config default
+expect "1 listed item (1)"
+$GENCFS -l
+
+expect "succeeding edit with a custom EncFS5 config file (1)"
+mv $TENV/e1/.encfs5 $TENV/e1_encfs5
+$GENCFS -e $TENV/m1 --password p1 --epath $TENV/e1 --mpoint $TENV/m1 --proceed n --amount n --encfs-config $TENV/e1_encfs5
+expect "1 listed item (1)"
+$GENCFS -l
+
+expect "succeeding remove (1)"
+$GENCFS -r $TENV/m1
+mv $TENV/e1_encfs5 $TENV/e1/.encfs5
+expect "0 items"
+$GENCFS -l
+
+# test EncFS config file custom location (encfs6.xml)
+expect "succeeding add with a custom EncFS6 config file (1)"
+mv $TENV/e2/.encfs6.xml $TENV/e2_encfs6.xml
+$GENCFS -a $TENV/e2 $TENV/m2 --password p2 --proceed n --amount n --encfs-config $TENV/e2_encfs6.xml
+expect "1 listed item (2)"
+$GENCFS -l
+
+expect "1 succeeding mounts (2)"
+$GENCFS -m $TENV/e2
+expect "1 mounted paths (2)"
+mount | grep "/tenv/m[0-9]" | sed -e "s/user=\w\+//"
+
+for MPOINT in $TENV/m2* ; do
+        fusermount -u $MPOINT 2>&1
+done
+expect "no mounted paths - all unmounted"
+mount | grep "/tenv/m[0-9]"
+
+expect "succeeding edit to set back EncFS6 config file to its default location (2)"
+mv $TENV/e2_encfs6.xml $TENV/e2/.encfs6.xml
+$GENCFS -e $TENV/m2 --password p2 --epath $TENV/e2 --mpoint $TENV/m2 --proceed n --amount n --encfs-config default
+expect "1 listed item (2)"
+$GENCFS -l
+
+expect "succeeding edit with a custom EncFS6 config file (2)"
+mv $TENV/e2/.encfs6.xml $TENV/e2_encfs6.xml
+$GENCFS -e $TENV/m2 --password p2 --epath $TENV/e2 --mpoint $TENV/m2 --proceed n --amount n --encfs-config $TENV/e2_encfs6.xml
+expect "1 listed item (2)"
+$GENCFS -l
+
+expect "succeeding remove (2)"
+$GENCFS -r $TENV/m2
+mv $TENV/e2_encfs6.xml $TENV/e2/.encfs6.xml
+expect "0 items"
+$GENCFS -l
+
+# clean up keyring
+$GENCFS -l | grep "mount point" | grep "/tenv/m[0-9]" | awk {'print $4'} | \
+    while read MP ; do $GENCFS -r $MP ; done
+
 expect "no listed items"
 $GENCFS -l
 expect "autostart off"