2 * This is an ecryptfs private directory mount/unmount helper program
5 * Copyright (C) 2008 Canonical Ltd.
7 * This code was originally written by Dustin Kirkland <kirkland@ubuntu.com>.
8 * Enhanced by Serge Hallyn <hallyn@ubuntu.com>.
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, see <http://www.gnu.org/licenses/>.
23 * On Debian-based systems, the complete text of the GNU General Public
24 * License can be found in /usr/share/common-licenses/GPL-2
29 #include <sys/mount.h>
30 #include <sys/param.h>
32 #include <sys/types.h>
44 #include "../include/ecryptfs.h"
46 /* Perhaps a future version of this program will allow these to be configurable
47 * by the system administrator (or user?) at run time. For now, these are set
48 * to reasonable values to reduce the burden of input validation.
51 #define KEY_CIPHER "aes"
52 #define FSTYPE "ecryptfs"
53 #define TMP "/dev/shm"
55 int read_config(char *pw_dir, uid_t uid, char *alias, char **s, char **d, char **o) {
56 /* Read an fstab(5) style config file */
61 if (asprintf(&fnam, "%s/.ecryptfs/%s.conf", pw_dir, alias) < 0) {
65 if (stat(fnam, &mstat)!=0 || (!S_ISREG(mstat.st_mode) || mstat.st_uid!=uid)) {
66 fputs("Bad file\n", stderr);
70 fin = fopen(fnam, "r");
82 if (strcmp(e->mnt_type, "ecryptfs") != 0) {
83 fputs("Bad fs type\n", stderr);
86 *o = strdup(e->mnt_opts);
89 *d = strdup(e->mnt_dir);
92 *s = strdup(e->mnt_fsname);
99 int check_username(char *u) {
100 /* We follow the username guidelines used by the adduser program. Quoting its
102 * adduser: To avoid problems, the username should consist only of
103 * letters, digits, underscores, periods, at signs and dashes, and not start
104 * with a dash (as defined by IEEE Std 1003.1-2001). For compatibility with
105 * Samba machine accounts $ is also supported at the end of the username
117 for (i=0; i<len; i++) {
119 if ( !(c>='a' && c<='z') && !(c>='A' && c<='Z') &&
120 !(c>='0' && c<='9') &&
121 !(c=='_') && !(c=='.') && !(c=='@') &&
123 !(c=='$' && i==(len-1))
125 fputs("Username has unsupported characters\n", stderr);
131 fputs("Username is empty\n", stderr);
135 char **fetch_sig(char *pw_dir, char *alias, int mounting) {
136 /* Read ecryptfs signature from file and validate
137 * Return signature as a string, or NULL on failure
143 /* Construct sig file name */
144 if (asprintf(&sig_file, "%s/.ecryptfs/%s.sig", pw_dir, alias) < 0) {
148 fh = fopen(sig_file, "r");
153 /* Read up to 2 lines from the file */
154 if ((sig = malloc(sizeof(char*) * 2)) == NULL) {
161 for (i=0; i<2; i++) {
162 if ((sig[i] = (char *)malloc(KEY_BYTES*sizeof(char)+2)) == NULL) {
166 memset(sig[i], '\0', KEY_BYTES+2);
167 /* Read KEY_BYTES characters from line */
168 if (fgets(sig[i], KEY_BYTES+2, fh) == NULL) {
170 fputs("Missing file encryption signature", stderr);
173 /* Filename encryption isn't in use */
179 /* Validate hex signature */
180 for (j=0; j<strlen(sig[i]); j++) {
181 if (isxdigit(sig[i][j]) == 0 && isspace(sig[i][j]) == 0) {
182 fputs("Invalid hex signature\n", stderr);
185 if (isspace(sig[i][j]) != 0) {
186 /* truncate at first whitespace */
190 if (strlen(sig[i]) > 0 && strlen(sig[i]) != KEY_BYTES) {
191 fputs("Invalid hex signature length\n", stderr);
194 /* Validate that signature is in the current keyring,
195 * compile with -lkeyutils
197 if (keyctl_search(KEY_SPEC_USER_KEYRING, "user", sig[i], 0) < 0) {
199 fputs("Signature not found in user keyring\n"
200 "Perhaps try the interactive "
201 "'ecryptfs-mount-private'\n", stderr);
214 /* Clean up malloc'd memory if failure */
223 int check_ownership_mnt(uid_t uid, char **mnt) {
224 /* Check ownership of mount point, chdir into it, and
225 * canonicalize the path for use in mtab updating.
226 * Return 0 if everything is in order, 1 on error.
231 /* From here on, we'll refer to "." as our mountpoint, to avoid
234 if (chdir(*mnt) != 0) {
235 fputs("Cannot chdir into mountpoint.\n", stderr);
238 if (stat(".", &s) != 0) {
239 fputs("Cannot examine mountpoint.\n", stderr);
242 if (!S_ISDIR(s.st_mode)) {
243 fputs("Mountpoint is not a directory.\n", stderr);
246 if (s.st_uid != uid) {
247 fputs("You do not own that mountpoint.\n", stderr);
251 /* Canonicalize our pathname based on the current directory to
254 cwd = getcwd(NULL, 0);
256 fputs("Failed to get current directory\n", stderr);
264 int check_ownerships(uid_t uid, char *path) {
265 /* Check ownership of device and mount point.
266 * Return 0 if everything is in order, 1 on error.
269 if (stat(path, &s) != 0) {
270 fputs("Cannot examine encrypted directory\n", stderr);
273 if (!S_ISDIR(s.st_mode)) {
274 fputs("Device or mountpoint is not a directory\n", stderr);
277 if (s.st_uid != uid) {
278 fputs("You do not own that encrypted directory\n", stderr);
285 int update_mtab(char *dev, char *mnt, char *opt) {
286 /* Update /etc/mtab with new mount entry unless it is a symbolic link
287 * Return 0 on success, 1 on failure.
291 /* Check if mtab is a symlink */
292 useMtab = (readlink("/etc/mtab", &dummy, 1) < 0);
294 /* No need updating mtab */
299 FILE *old_mtab, *new_mtab;
300 struct mntent *old_ent, new_ent;
303 /* Make an attempt to play nice with other mount helpers
304 * by creating an /etc/mtab~ lock file. Of course this
305 * only works if those other helpers actually check for
308 old_umask = umask(033);
309 fd = open("/etc/mtab~", O_RDONLY | O_CREAT | O_EXCL, 0644);
316 old_mtab = setmntent("/etc/mtab", "r");
317 if (old_mtab == NULL) {
322 new_mtab = setmntent("/etc/mtab.tmp", "w");
323 if (new_mtab == NULL) {
328 while ((old_ent = getmntent(old_mtab))) {
329 if (addmntent(new_mtab, old_ent) != 0) {
336 new_ent.mnt_fsname = dev;
337 new_ent.mnt_dir = mnt;
338 new_ent.mnt_type = FSTYPE;
339 new_ent.mnt_opts = opt;
340 new_ent.mnt_freq = 0;
341 new_ent.mnt_passno = 0;
343 if (addmntent(new_mtab, &new_ent) != 0) {
348 if (fchmod(fileno(new_mtab), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) {
354 if (rename("/etc/mtab.tmp", "/etc/mtab") < 0) {
359 unlink("/etc/mtab~");
368 unlink("/etc/mtab.tmp");
371 unlink("/etc/mtab~");
376 FILE *lock_counter(char *u, uid_t uid, char *alias) {
382 /* We expect TMP to exist, be writeable by the user,
383 * and to be cleared on boot */
384 if (asprintf(&f, "%s/%s-%s-%s", TMP, FSTYPE, u, alias) < 0) {
388 /* If the counter path exists, and it's either not a regular
389 * file, or it's not owned by the current user, append iterator
390 * until we find a filename we can use.
393 if (((fd = open(f, O_RDWR | O_CREAT | O_NOFOLLOW, 0600)) >= 0) &&
394 (fstat(fd, &s)==0 && (S_ISREG(s.st_mode) && s.st_uid==uid))) {
402 if (asprintf(&f, "%s/%s-%s-%s-%d", TMP, FSTYPE, u,
416 fh = fdopen(fd, "r+");
425 void unlock_counter(FILE *fh) {
427 /* This should remove the lock too */
432 int bump_counter(FILE *fh, int delta) {
433 /* Maintain a mount counter
434 * increment on delta = 1
435 * decrement on delta = -1
436 * remove the counter file on delta = 0
437 * return the updated count, negative on error
440 /* Read the count from file, default to 0 */
442 if (fscanf(fh, "%d\n", &count) != 1) {
445 /* Increment/decrement the counter */
448 /* Never set a count less than 0 */
451 /* Write the count to file */
453 fprintf(fh, "%d\n", count);
459 int increment(FILE *fh) {
460 /* Bump counter up */
461 return bump_counter(fh, 1);
465 int decrement(FILE *fh) {
466 /* Bump counter down */
467 return bump_counter(fh, -1);
471 /* Zero the counter file */
472 return bump_counter(fh, -MAXINT+1);
476 /* This program is a setuid-executable allowing a non-privileged user to mount
477 * and unmount an ecryptfs private directory. This program is necessary to
478 * keep from adding such entries to /etc/fstab.
480 * A single executable is created and hardlinked to two different names.
481 * The mode of operation (mounting|unmounting) is determined by examining
482 * the name of the executable. "Mounting" mode is assumed, unless the
483 * executable contains the string "umount".
485 * /sbin/mount.ecryptfs_private
486 * /sbin/umount.ecryptfs_private
488 * At the moment, this program:
489 * - mounts ~/.Private onto ~/Private
490 * - as an ecryptfs filesystem
491 * - using the AES cipher
492 * - with a key length of 16 bytes
493 * - and using the signature defined in ~/.ecryptfs/Private.sig
495 * - has the signature's key in his keyring
496 * - owns both ~/.Private and ~/Private
497 * - is not already mounted
498 * - unmounts ~/.Private from ~/Private
499 * - using the signature defined in ~/.ecryptfs/Private.sig
501 * - owns both ~/.Private and ~/Private
502 * - is currently ecryptfs mounted
504 * The only setuid operations in this program are:
507 * c) updating /etc/mtab
509 int main(int argc, char *argv[]) {
515 char *alias, *src, *dest, *opt, *opts2;
516 char *sig_fekek = NULL, *sig_fnek = NULL;
518 FILE *fh_counter = NULL;
522 /* Non-privileged effective uid is sufficient for all but the code
523 * that mounts, unmounts, and updates /etc/mtab.
524 * Run at a lower privilege until we need it.
526 if (seteuid(uid)<0 || geteuid()!=uid) {
530 if ((pwd = getpwuid(uid)) == NULL) {
535 /* If no arguments, default to private dir; but accept at most one
536 argument, an alias for the configuration to read and use.
539 /* Use default source and destination dirs */
540 alias = ECRYPTFS_PRIVATE_DIR;
541 if ((asprintf(&src, "%s/.%s", pwd->pw_dir, alias) < 0) || src == NULL) {
542 perror("asprintf (src)");
545 dest = ecryptfs_fetch_private_mnt(pwd->pw_dir);
547 perror("asprintf (dest)");
550 } else if (argc == 2) {
552 /* Read the source and destination dirs from .conf file */
553 if (read_config(pwd->pw_dir, uid, alias, &src, &dest, &opts2) < 0) {
554 fputs("Error reading configuration file\n", stderr);
557 if (opts2 != NULL && strlen(opts2) != 0 && strcmp(opts2, "none") != 0) {
558 fputs("Mount options are not supported here\n", stderr);
562 fputs("Too many arguments\n", stderr);
566 if (strstr(alias, "..")) {
567 fputs("Invalid alias", stderr);
571 /* Lock the counter through the rest of the program */
572 fh_counter = lock_counter(pwd->pw_name, uid, alias);
573 if (fh_counter == NULL) {
574 fputs("Error locking counter\n", stderr);
578 if (check_username(pwd->pw_name) != 0) {
579 /* Must protect against a crafted user=john,suid from entering
585 /* Determine if mounting or unmounting by looking at the invocation */
586 if (strstr(argv[0], "umount") == NULL) {
590 /* Determine if unmounting is forced */
591 if (argv[1] != NULL && strncmp(argv[1], "-f", 2) == 0) {
598 /* Fetch signatures from file */
599 /* First line is the file content encryption key signature */
600 /* Second line, if present, is the filename encryption key signature */
601 sigs = fetch_sig(pwd->pw_dir, alias, mounting);
602 if (!sigs && mounting) {
603 /* if umounting, no sig is ok */
610 /* Build mount options */
612 (asprintf(&opt, "ecryptfs_check_dev_ruid,ecryptfs_cipher=%s,ecryptfs_key_bytes=%d,ecryptfs_unlink_sigs%s%s%s%s",
615 sig_fekek ? ",ecryptfs_sig=" : "",
616 sig_fekek ? sig_fekek : "",
617 sig_fnek ? ",ecryptfs_fnek_sig=" : "",
618 sig_fnek ? sig_fnek : ""
622 perror("asprintf (opt)");
626 /* Check ownership of the mountpoint. From here on, dest refers
627 * to a canonicalized path, and the mountpoint is the cwd. */
628 if (check_ownership_mnt(uid, &dest) != 0) {
633 /* Increment mount counter, errors non-fatal */
634 if (increment(fh_counter) < 0) {
635 fputs("Error incrementing mount counter\n", stderr);
637 /* Mounting, so exit if already mounted */
638 if (ecryptfs_private_is_mounted(src, dest, sig_fekek, mounting) == 1) {
641 /* We must maintain our real uid as the user who called this
642 * program in order to have access to their kernel keyring.
643 * Even though root has the power to mount, only a user with
644 * the correct key in their keyring can mount an ecryptfs
645 * directory correctly.
646 * Root does not necessarily have the user's key, so we need
647 * the real uid to be that of the user.
648 * And we need the effective uid to be root in order to mount.
650 if (setreuid(-1, 0) < 0) {
654 if (setregid(-1, 0) < 0) {
659 if (mount(src, ".", FSTYPE, MS_NOSUID | MS_NODEV, opt) == 0) {
660 if (update_mtab(src, dest, opt) != 0) {
665 /* Drop privileges since the mount did not succeed */
666 if (setreuid(uid, uid) < 0) {
669 if (setregid(gid, gid) < 0) {
676 /* Decrement counter, exiting if >0, and non-forced unmount */
679 } else if (decrement(fh_counter) > 0) {
680 fputs("Sessions still open, not unmounting\n", stderr);
683 /* Attempt to clear the user's keys from the keyring,
684 to prevent root from mounting without the user's key.
685 This is a best-effort basis, so we'll just print messages
687 if (sig_fekek != NULL) {
688 rc = ecryptfs_remove_auth_tok_from_keyring(sig_fekek);
689 if (rc != 0 && rc != ENOKEY)
690 fputs("Could not remove key from keyring, try 'ecryptfs-umount-private'\n", stderr);
692 if (sig_fnek != NULL) {
693 rc = ecryptfs_remove_auth_tok_from_keyring(sig_fnek);
694 if (rc != 0 && rc != ENOKEY)
695 fputs("Could not remove key from keyring, try 'ecryptfs-umount-private'\n", stderr);
697 /* Unmounting, so exit if not mounted */
698 if (ecryptfs_private_is_mounted(src, dest, sig_fekek, mounting) == 0) {
701 /* The key is not needed for unmounting, so we set res=0.
702 * Perform umount by calling umount utility. This execl will
703 * update mtab for us, and replace the current process.
704 * Do not use the umount.ecryptfs helper (-i).
710 /* Since we're doing a lazy unmount anyway, just unmount the current
711 * directory. This avoids a lot of complexity in dealing with race
712 * conditions, and guarantees that we're only unmounting a filesystem
714 execl("/bin/umount", "umount", "-i", "-l", ".", NULL);
715 perror("execl unmount failed");
719 unlock_counter(fh_counter);
722 unlock_counter(fh_counter);