/** * Copyright (C) 2006 International Business Machines * Author(s): Michael A. Halcrow * Stephan Mueller * Tyler Hicks * Michael C. Thompson * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ecryptfs.h" #include "decision_graph.h" #include "io.h" #define NUM_REQUIRED_ARGS 3 static void usage() { fprintf(stderr, "\teCryptfs mount helper\n\tusage: " "mount -t ecryptfs [lower directory] [ecryptfs mount point]\n" "\n" "See the README file in the ecryptfs-utils package for " "complete usage guidelines.\n" ); exit(-EINVAL); } /** * This function will malloc any argument that is passed in as non-NULL. * In the event of a failure, all returned pointers are invalid and no * memory is left allocated. * * returns 0 on success */ static int parse_arguments(int argc, char **argv, char **src, char **target, char **options) { int rc = 0; char *ptr; size_t len; if (src) *src = NULL; if (target) *target = NULL; if (options) *options = NULL; if (src) { ptr = argv[1]; len = strlen(ptr) + 1; /* + NULL terminator */ *src = malloc(len); if (!*src) { fprintf(stderr, "Unable to allocate source buffer\n"); rc = -ENOMEM; goto out; } memcpy(*src, ptr, len); if ((*src)[len - 1] == '/') (*src)[len - 1] = '\0'; } if (target) { ptr = argv[2]; len = strlen(ptr) + 1; /* + NULL-terminator */ *target = malloc(len); if (!*target) { fprintf(stderr, "Unable to allocate target buffer\n"); rc = -ENOMEM; goto out; } memcpy(*target, ptr, len); if ((*target)[len - 1] == '/') (*target)[len - 1] = '\0'; } if ((options) && (argc >= NUM_REQUIRED_ARGS)) { int i; ptr = NULL; for (i = 3; i < (argc-1); i++) if (!strcmp("-o", argv[i])) { ptr = argv[i+1]; break; } if (!ptr) { fprintf(stderr, "Unable to find a list of options to " "parse, defaulting to interactive " "mount\n"); return 0; } len = strlen(ptr) + 1; /* + NULL-terminator */ *options = malloc(len); if (!*options){ fprintf(stderr, "Unable to allocate memory for options " "buffer\n"); rc = -ENOMEM; goto out; } memcpy(*options, ptr, len); } return 0; out: if (src && *src) free(*src); if (target && *target) free(*target); if (options && *options) free(*options); return rc; } char *parameters_to_scrub[] = { "key=", "cipher=", "passthrough", "ecryptfs_passthrough", "hmac", "ecryptfs_hmac", "xattr", "ecryptfs_xattr", "encrypted_view", "ecryptfs_encrypted_view", "user", "sig", "no_sig_cache", "verbose", "verbosity", "ecryptfs_enable_filename_crypto", NULL }; char *parameters_to_not_scrub[] = { "xattr_user", NULL }; static int parameter_should_not_be_scrubbed(char *str) { int i; for (i = 0; parameters_to_not_scrub[i]; i++) if (strstr(str, parameters_to_not_scrub[i]) == str) return 1; return 0; } static int parameter_should_be_scrubbed(char *str) { int i; for (i = 0; parameters_to_scrub[i]; i++) if (strstr(str, parameters_to_scrub[i]) == str && !parameter_should_not_be_scrubbed(str)) return 1; return 0; } /** * Remove from the options string known options which should not be passed * into the kernel. Any options that are unknown will be passed in. * This is to account for options like "rw". * * Returns zero on success, non-zero otherwise */ static int strip_userland_opts(char *options) { char *cur = NULL, *next = NULL; char *temp, *temp_end; size_t len; int used = 0, first = 1; if (!options) return 0; len = (strlen(options) + 1); if ((temp = (char*)malloc(len)) == NULL) { fprintf(stderr, "Out of memory\n"); return -1; } temp_end = temp; memset(temp, 0, len); cur = options; while (cur) { int opt_len; next = strstr(cur, ","); if (next) { *next='\0'; next++; } if (!parameter_should_be_scrubbed(cur)) { if (!first) { memcpy(temp_end, ",", 1); temp_end++; } opt_len = strlen(cur); memcpy(temp_end, cur, opt_len); temp_end = temp_end + opt_len; used += opt_len; first = 0; } cur = next; } memcpy(options,temp,len); free(temp); return 0; } static int process_sig(char *auth_tok_sig, struct passwd *pw) { char *home; char *sig_cache_filename = NULL; char *dot_ecryptfs_dir; int flags; char *yesno = NULL; int rc; int tries; home = pw->pw_dir; rc = asprintf(&dot_ecryptfs_dir, "%s/.ecryptfs", home); if (rc == -1) { rc = -ENOMEM; goto out; } (void)mkdir(dot_ecryptfs_dir, S_IRWXU); if (chown(dot_ecryptfs_dir, getuid(), getgid()) == -1) printf("Can't change ownership of sig file; " "errno = [%d]; [%m]\n", errno); free(dot_ecryptfs_dir); rc = asprintf(&sig_cache_filename, "%s/.ecryptfs/sig-cache.txt", home); if (rc == -1) { rc = -ENOMEM; goto out; } flags = 0; if ((rc = ecryptfs_check_sig(auth_tok_sig, sig_cache_filename, &flags))) goto out; if (flags & ECRYPTFS_SIG_FLAG_NOENT) { printf("WARNING: Based on the contents of [%s],\n" "it looks like you have never mounted with this key \n" "before. This could mean that you have typed your \n" "passphrase wrong.\n\n", sig_cache_filename); tries = 0; yesno = NULL; do { free(yesno); yesno = NULL; if ((rc = get_string_stdin(&yesno, "Would you like to proceed with " "the mount (yes/no)? ",ECRYPTFS_ECHO_ON))) goto out; } while ((rc = strcmp(yesno, "yes")) && strcmp(yesno, "no") && (++tries < 5)); if (rc == 0) { tries = 0; do { free(yesno); printf("Would you like to append sig [%s] to\n" "[%s] \nin order to avoid this warning " "in the future", auth_tok_sig, sig_cache_filename); if ((rc = get_string_stdin(&yesno," (yes/no)? ", ECRYPTFS_ECHO_ON))) goto out; } while ((rc = strcmp(yesno, "yes")) && strcmp(yesno, "no") && (++tries < 5)); if (rc == 0) { if ((rc = ecryptfs_append_sig( auth_tok_sig, sig_cache_filename))) { printf("Error appending to [%s]; rc = " "[%d]. Aborting mount.\n", sig_cache_filename, rc); goto out; } printf("Successfully appended new sig to user " "sig cache file\n"); } else { if (strcmp(yesno,"no")) rc = -EINVAL; else { printf("Not adding sig to user sig " "cache file; continuing with " "mount.\n"); rc = 0; } } } else { rc = ECANCELED; if (strcmp(yesno,"no")) rc = -EINVAL; printf("Aborting mount.\n"); goto out; } } out: free(yesno); free(sig_cache_filename); return rc; } static int opts_str_contains_option(char *options, char *option) { char *opt; char *next_opt; if (!options || !option) return 0; int option_len = strlen(option); opt = options; while (opt) { if ((next_opt = strchr(opt, ','))) next_opt++; if (!strncmp(opt, option, option_len)) return 1; else opt = next_opt; } return 0; } char *required_mount_opts[] = { "ecryptfs_key_bytes=", NULL }; static int ecryptfs_validate_mount_opts(char *opts) { int i = 0; int rc = 0; while (required_mount_opts[i]) { if (!opts_str_contains_option(opts, required_mount_opts[i])) { printf("Required mount option not provided: [%s]\n", required_mount_opts[i]); rc = -EINVAL; goto out; } i++; } out: return rc; } int ecryptfs_mount(char *source, char *target, char *opts) { pid_t pid, pid_child; char *fullpath_source = NULL; char *fullpath_target = NULL; int rc, status; if (!source) { rc = -EINVAL; syslog(LOG_ERR, "Invalid source directory\n"); goto out; } if (!target) { rc = -EINVAL; syslog(LOG_ERR, "Invalid target directory\n"); goto out; } /* source & target are canonicalized here, so the correct error * is sent to syslog. * /bin/mount tells you the error on normal output only, not to syslog. */ fullpath_source = realpath(source, NULL); if (!fullpath_source) { rc = -errno; syslog(LOG_ERR, "could not resolve full path for source %s [%d]", source, -errno); goto out; } fullpath_target = realpath(target, NULL); if (!fullpath_target) { rc = -errno; syslog(LOG_ERR, "could not resolve full path for target %s [%d]", target, -errno); goto out; } pid = fork(); if (pid == -1) { syslog(LOG_ERR, "Could not fork process to mount eCryptfs: [%d]\n", -errno); rc = -errno; } else if (pid == 0) { execl("/bin/mount", "mount", "-i", "--no-canonicalize", "-t", "ecryptfs", fullpath_source, fullpath_target, "-o", opts, NULL); /* error message shown in console to let users know what was wrong */ /* i.e. /bin/mount does not exist */ perror("Failed to execute /bin/mount command"); exit(errno); } else { pid_child = waitpid(pid, &status, 0); if (pid_child == -1) { syslog(LOG_ERR, "Failed waiting for /bin/mount process: [%d]\n", -errno); rc = -errno; goto out; } rc = -EPERM; if (WIFEXITED(status)) rc = -WEXITSTATUS(status); if (rc) { syslog(LOG_ERR, "Failed to perform eCryptfs mount: [%d]\n", rc); if (-EPIPE == rc) { rc = -EPERM; } } } out: free(fullpath_source); free(fullpath_target); return rc; } /** * ecryptfs_do_mount * @params: * * The mount options actually sent to the kernel are in the @params * linked list of struct val_node objects. params <- * ecryptfs_process_decision_graph(head) -> decision_graph_mount(head) * -> eval_param_tree(val_stack_head) -> do_transition(val_stack_head) * -> trans_func(head). */ static int ecryptfs_do_mount(int argc, char **argv, struct val_node *mnt_params, int sig_cache, struct passwd *pw) { int rc; char *src = NULL, *targ = NULL, *opts = NULL, *new_opts = NULL, *temp; char *val; if ((rc = parse_arguments(argc, argv, &src, &targ, &opts))) { fprintf(stderr, "Unable to understand the mount options\n"); goto out; } rc = strip_userland_opts(opts); if (rc) goto out; if (!(temp = strdup("ecryptfs_unlink_sigs"))) { rc = -ENOMEM; goto out; } if ((rc = stack_push(&mnt_params, (void *)temp))) goto out; printf("Attempting to mount with the following options:\n"); new_opts = opts; opts = NULL; while (!stack_pop_val(&mnt_params, (void *)&val)) { if(!val) break; temp = new_opts; printf(" %s\n", val); if (sig_cache && memcmp(val, "ecryptfs_sig=", 13) == 0) { if ((rc = process_sig(&val[13], pw))) { if (rc != ECANCELED) printf("Error processing sig; " "rc = [%d]\n", rc); goto out; } } if (!temp || !strstr(temp, val)) { rc = asprintf(&new_opts, "%s%c%s", val, ((temp && *temp) ? ',' : '\0'), temp); if (rc == -1) { new_opts = NULL; rc = -ENOMEM; goto out; } free(temp); } rc = 0; } if ((rc = ecryptfs_validate_mount_opts(new_opts)) != 0) { printf("Invalid mount options; aborting. rc = [%d]\n", rc); goto out; } rc = ecryptfs_mount(src, targ, new_opts); out: free(src); free(targ); free(opts); free(new_opts); return rc; } static int dump_args = 0; int main(int argc, char **argv) { uint32_t version; char *opts_str; struct val_node *mnt_params; struct ecryptfs_ctx ctx; int sig_cache = 1; int rc; struct passwd *pw; rc = mlockall(MCL_FUTURE); if (rc) { fprintf(stderr, "Exiting. Unable to mlockall address space: %m\n"); return -1; } pw = getpwuid(getuid()); if (!pw) { fprintf(stderr, "Exiting. Unable to obtain passwd info\n"); rc = -EIO; goto out; } if (dump_args) { int i; for (i = 0; i < argc; i++) printf("argv[%d] = [%s]\n", i, argv[i]); } if (argc < NUM_REQUIRED_ARGS) { fprintf(stderr, "Insufficient number of arguments\n"); usage(); rc = -EINVAL; goto out; } rc = ecryptfs_get_version(&version); if (rc) { printf("\nUnable to get the version number of the kernel\n"); printf("module. Please make sure that you have the eCryptfs\n"); printf("kernel module loaded, you have sysfs mounted, and\n"); printf("the sysfs mount point is in /etc/mtab. This is\n"); printf("necessary so that the mount helper knows which \n"); printf("kernel options are supported.\n\n"); printf("Make sure that your system is set up to auto-load\n" "your filesystem kernel module on mount.\n\n"); printf("Enabling passphrase-mode only for now.\n\n"); version = ECRYPTFS_VERSIONING_PASSPHRASE; } if ((rc = ecryptfs_validate_keyring())) { printf("Unable to link the KEY_SPEC_USER_KEYRING into the " "KEY_SPEC_SESSION_KEYRING; there is something wrong " "with your kernel keyring. Did you build key retention " "support into your kernel?\n"); goto out; } mnt_params = malloc(sizeof(struct val_node)); if (!mnt_params) { fprintf(stderr, "Unable to allocate memory for mount " "parameters buffer\n"); rc = -ENOMEM; goto out; } memset(mnt_params, 0, sizeof(struct val_node)); memset(&ctx, 0, sizeof(struct ecryptfs_ctx)); ctx.get_string = &get_string_stdin; if ((rc = parse_arguments(argc, argv, NULL, NULL, &opts_str))) goto out; if (opts_str_contains_option(opts_str, "verbose")) ecryptfs_verbosity = 1; if (!opts_str_contains_option(opts_str, "remount")) { if (opts_str_contains_option(opts_str, "no_sig_cache")) sig_cache = 0; if (opts_str_contains_option(opts_str, "no_prompt") || opts_str_contains_option(opts_str, "wild_ass_guess")) { if (!opts_str_contains_option(opts_str, "verbosity=0")) { char *tmp; rc = asprintf(&tmp, "%s,verbosity=0", opts_str); if (rc == -1) { rc = -ENOMEM; goto out; } rc = 0; opts_str = tmp; } } if (opts_str_contains_option(opts_str, "verbosity=0")) sig_cache = 0; rc = ecryptfs_process_decision_graph( &ctx, &mnt_params, version, opts_str, ECRYPTFS_ASK_FOR_ALL_MOUNT_OPTIONS); if (rc) { if (rc > 0) rc = -EINVAL; printf("Error attempting to evaluate mount options: " "[%d] %s\nCheck your system logs for details " "on why this happened.\nTry updating your " "ecryptfs-utils package, and/or\nsubmit a bug " "report on https://bugs.launchpad.net/ecryptfs\n", rc, strerror(-rc)); goto out; } rc = ecryptfs_do_mount(argc, argv, mnt_params, sig_cache, pw); if (rc == ECANCELED) { rc = 0; goto out; } if (rc) { if (rc > 0) rc = -rc; printf("Error mounting eCryptfs: [%d] %s\n" "Check your system logs; visit " "\n", rc, strerror(-rc)); if (rc == -ENODEV) printf("Try ``modprobe ecryptfs''\n"); } else printf("Mounted eCryptfs\n"); } else { fprintf(stderr, "When remounting eCryptfs, you need " "to pass the mount utility the -i parameter to avoid " "calling the mount helper\n"); rc = -EINVAL; } out: munlockall(); return rc; }