diff options
Diffstat (limited to 'src/pam_ecryptfs/pam_ecryptfs.c')
-rw-r--r-- | src/pam_ecryptfs/pam_ecryptfs.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/pam_ecryptfs/pam_ecryptfs.c b/src/pam_ecryptfs/pam_ecryptfs.c new file mode 100644 index 0000000..49b42b5 --- /dev/null +++ b/src/pam_ecryptfs/pam_ecryptfs.c @@ -0,0 +1,519 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- + * + * pam_ecryptfs.c: PAM module that sends the user's authentication + * tokens into the kernel keyring. + * + * Copyright (C) 2007 International Business Machines + * Author(s): Michael Halcrow <mhalcrow@us.ibm.com> + * Dustin Kirkland <kirkland@ubuntu.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 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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <syslog.h> +#include <limits.h> +#include <pwd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/fsuid.h> +#include <grp.h> +#include <fcntl.h> +#include <security/pam_modules.h> +#include <security/pam_ext.h> +#include "../include/ecryptfs.h" + +#define PRIVATE_DIR "Private" + +/* returns: 0 if file does not exist, 1 if it exists, <0 for error */ +static int file_exists_dotecryptfs(const char *homedir, char *filename) +{ + char *file_path; + int rc = 0; + struct stat s; + if (asprintf(&file_path, "%s/.ecryptfs/%s", homedir, filename) == -1) + return -ENOMEM; + if (stat(file_path, &s) != 0) { + if (errno != ENOENT) + rc = -errno; + goto out; + } + rc = 1; +out: + free(file_path); + return rc; +} + +static int wrap_passphrase_if_necessary(const char *username, uid_t uid, char *wrapped_pw_filename, char *passphrase, char *salt) +{ + char *unwrapped_pw_filename = NULL; + struct stat s; + int rc = 0; + + rc = asprintf(&unwrapped_pw_filename, "/dev/shm/.ecryptfs-%s", username); + if (rc == -1) { + syslog(LOG_ERR, "pam_ecryptfs: Unable to allocate memory\n"); + return -ENOMEM; + } + /* If /dev/shm/.ecryptfs-$USER exists and owned by the user + and ~/.ecryptfs/wrapped-passphrase does not exist + and a passphrase is set: + wrap the unwrapped passphrase file */ + if (stat(unwrapped_pw_filename, &s) == 0 && (s.st_uid == uid) && + stat(wrapped_pw_filename, &s) != 0 && + passphrase != NULL && *passphrase != '\0' && + username != NULL && *username != '\0') { + setuid(uid); + rc = ecryptfs_wrap_passphrase_file(wrapped_pw_filename, passphrase, salt, unwrapped_pw_filename); + if (rc != 0) { + syslog(LOG_ERR, "pam_ecryptfs: Error wrapping cleartext password; " "rc = [%d]\n", rc); + } + return rc; + } + return 0; +} + +PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + uid_t uid = 0, oeuid = 0; + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + gid_t gid = 0, oegid = 0, groups[ngroups_max+1]; + int ngids = 0; + char *homedir = NULL; + const char *username; + char *passphrase = NULL; + char salt[ECRYPTFS_SALT_SIZE]; + char salt_hex[ECRYPTFS_SALT_SIZE_HEX]; + char *auth_tok_sig = NULL; + char *private_mnt = NULL; + pid_t child_pid, tmp_pid; + long rc; + + rc = pam_get_user(pamh, &username, NULL); + if (rc == PAM_SUCCESS) { + struct passwd *pwd; + + pwd = getpwnam(username); + if (pwd) { + uid = pwd->pw_uid; + gid = pwd->pw_gid; + homedir = pwd->pw_dir; + } + } else { + syslog(LOG_ERR, "pam_ecryptfs: Error getting passwd info for user [%s]; rc = [%ld]\n", username, rc); + goto out; + } + + oeuid = geteuid(); + oegid = getegid(); + if ((ngids = getgroups(sizeof(groups)/sizeof(gid_t), groups)) < 0) { + syslog(LOG_ERR, "pam_ecryptfs: geteuid error"); + goto outnouid; + } + + if (setegid(gid) < 0 || setgroups(1, &gid) < 0 || seteuid(uid) < 0) { + syslog(LOG_ERR, "pam_ecryptfs: seteuid error"); + goto out; + } + + if (!file_exists_dotecryptfs(homedir, "auto-mount")) + goto out; + private_mnt = ecryptfs_fetch_private_mnt(homedir); + if (ecryptfs_private_is_mounted(NULL, private_mnt, NULL, 1)) { + syslog(LOG_DEBUG, "pam_ecryptfs: %s: %s is already mounted\n", __FUNCTION__, homedir); + /* If private/home is already mounted, then we can skip + costly loading of keys */ + goto out; + } + if(file_exists_dotecryptfs(homedir, "wrapping-independent") == 1) + rc = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &passphrase, "Encryption passphrase: "); + else + rc = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&passphrase); + if (rc != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_ecryptfs: Error retrieving passphrase; rc = [%ld]\n", + rc); + goto out; + } + auth_tok_sig = malloc(ECRYPTFS_SIG_SIZE_HEX + 1); + if (!auth_tok_sig) { + rc = -ENOMEM; + syslog(LOG_ERR, "pam_ecryptfs: Out of memory\n"); + goto out; + } + rc = ecryptfs_read_salt_hex_from_rc(salt_hex); + if (rc) { + from_hex(salt, ECRYPTFS_DEFAULT_SALT_HEX, ECRYPTFS_SALT_SIZE); + } else + from_hex(salt, salt_hex, ECRYPTFS_SALT_SIZE); + if ((child_pid = fork()) == 0) { + /* temp regain uid 0 to drop privs */ + seteuid(oeuid); + /* setgroups() already called */ + if (setgid(gid) < 0 || setuid(uid) < 0) + goto out_child; + + if (passphrase == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: NULL passphrase; aborting\n"); + rc = -EINVAL; + goto out_child; + } + if ((rc = ecryptfs_validate_keyring())) { + syslog(LOG_WARNING, "pam_ecryptfs: Cannot validate keyring integrity\n"); + } + rc = 0; + if ((argc == 1) + && (memcmp(argv[0], "unwrap\0", 7) == 0)) { + char *wrapped_pw_filename; + + rc = asprintf( + &wrapped_pw_filename, "%s/.ecryptfs/%s", + homedir, + ECRYPTFS_DEFAULT_WRAPPED_PASSPHRASE_FILENAME); + if (rc == -1) { + syslog(LOG_ERR, "pam_ecryptfs: Unable to allocate memory\n"); + rc = -ENOMEM; + goto out_child; + } + if (wrap_passphrase_if_necessary(username, uid, wrapped_pw_filename, passphrase, salt) == 0) { + syslog(LOG_DEBUG, "pam_ecryptfs: Passphrase file wrapped"); + } else { + goto out_child; + } + rc = ecryptfs_insert_wrapped_passphrase_into_keyring( + auth_tok_sig, wrapped_pw_filename, passphrase, + salt); + free(wrapped_pw_filename); + } else { + rc = ecryptfs_add_passphrase_key_to_keyring( + auth_tok_sig, passphrase, salt); + } + if (rc == 1) { + goto out_child; + } + if (rc) { + syslog(LOG_ERR, "pam_ecryptfs: Error adding passphrase key token to user session keyring; rc = [%ld]\n", rc); + goto out_child; + } + if (fork() == 0) { + if ((rc = ecryptfs_set_zombie_session_placeholder())) { + syslog(LOG_ERR, "pam_ecryptfs: Error attempting to create and register zombie process; rc = [%ld]\n", rc); + } + } +out_child: + free(auth_tok_sig); + exit(0); + } + tmp_pid = waitpid(child_pid, NULL, 0); + if (tmp_pid == -1) + syslog(LOG_WARNING, "pam_ecryptfs: waitpid() returned with error condition\n"); +out: + + seteuid(oeuid); + setegid(oegid); + setgroups(ngids, groups); + +outnouid: + if (private_mnt != NULL) + free(private_mnt); + if (auth_tok_sig != NULL) + free(auth_tok_sig); + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + +static struct passwd *fetch_pwd(pam_handle_t *pamh) +{ + long rc; + const char *username = NULL; + struct passwd *pwd = NULL; + + rc = pam_get_user(pamh, &username, NULL); + if (rc != PAM_SUCCESS || username == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: Error getting passwd info for user [%s]; rc = [%ld]\n", username, rc); + return NULL; + } + pwd = getpwnam(username); + if (pwd == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: Error getting passwd info for user [%s]; rc = [%ld]\n", username, rc); + return NULL; + } + return pwd; +} + +static int private_dir(pam_handle_t *pamh, int mount) +{ + int rc, fd; + struct passwd *pwd = NULL; + char *sigfile = NULL; + char *autofile = NULL; + char *recorded = NULL; + char *a; + char *automount = "auto-mount"; + char *autoumount = "auto-umount"; + struct stat s; + pid_t pid; + + if ((pwd = fetch_pwd(pamh)) == NULL) { + /* fetch_pwd() logged a message */ + return 1; + } + if (mount == 1) { + a = automount; + } else { + a = autoumount; + } + if ( + (asprintf(&autofile, "%s/.ecryptfs/%s", pwd->pw_dir, a) < 0) + || autofile == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: Error allocating memory for autofile name"); + return 1; + } + if ( + (asprintf(&sigfile, "%s/.ecryptfs/%s.sig", pwd->pw_dir, + PRIVATE_DIR) < 0) || sigfile == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: Error allocating memory for sigfile name"); + return 1; + } + if (stat(sigfile, &s) != 0) { + /* No sigfile, no need to mount private dir */ + goto out; + } + if (!S_ISREG(s.st_mode)) { + /* No sigfile, no need to mount private dir */ + goto out; + } + if ((pid = fork()) < 0) { + syslog(LOG_ERR, "pam_ecryptfs: Error setting up private mount"); + return 1; + } + if (pid == 0) { + if (mount == 1) { + if ((asprintf(&recorded, + "%s/.ecryptfs/.wrapped-passphrase.recorded", + pwd->pw_dir) < 0) || recorded == NULL) { + syslog(LOG_ERR, "pam_ecryptfs: Error allocating memory for recorded name"); + exit(1); + } + if (stat(recorded, &s) != 0 && stat("/usr/share/ecryptfs-utils/ecryptfs-record-passphrase", &s) == 0) { + /* User has not recorded their passphrase */ + unlink("/var/lib/update-notifier/user.d/ecryptfs-record-passphrase"); + symlink("/usr/share/ecryptfs-utils/ecryptfs-record-passphrase", "/var/lib/update-notifier/user.d/ecryptfs-record-passphrase"); + fd = open("/var/lib/update-notifier/dpkg-run-stamp", O_WRONLY|O_CREAT|O_NONBLOCK, 0666); + if (fd != -1) + close(fd); + } + if (stat(autofile, &s) != 0) { + /* User does not want to auto-mount */ + syslog(LOG_DEBUG, "pam_ecryptfs: Skipping automatic eCryptfs mount"); + exit(0); + } + clearenv(); + if (setgroups(1, &pwd->pw_gid) < 0 || setgid(pwd->pw_gid) < 0) + return -1; + /* run mount.ecryptfs_private as the user */ + if (setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid) < 0) + return -1; + execl("/sbin/mount.ecryptfs_private", + "mount.ecryptfs_private", NULL); + } else { + if (stat(autofile, &s) != 0) { + /* User does not want to auto-unmount */ + syslog(LOG_DEBUG, "pam_ecryptfs: Skipping automatic eCryptfs unmount"); + exit(0); + } + clearenv(); + if (setgroups(1, &pwd->pw_gid) < 0 || setgid(pwd->pw_gid) < 0) + return -1; + /* run umount.ecryptfs_private as the user */ + if (setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid) < 0) + return -1; + execl("/sbin/umount.ecryptfs_private", + "umount.ecryptfs_private", NULL); + exit(1); + } + exit(1); + } else { + waitpid(pid, &rc, 0); + } +out: + return 0; +} + +static int mount_private_dir(pam_handle_t *pamh) +{ + return private_dir(pamh, 1); +} + +static int umount_private_dir(pam_handle_t *pamh) +{ + return private_dir(pamh, 0); +} + +PAM_EXTERN int +pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + mount_private_dir(pamh); + return PAM_SUCCESS; +} + +PAM_EXTERN int +pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + umount_private_dir(pamh); + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + uid_t uid = 0, oeuid = 0; + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + gid_t gid = 0, oegid = 0, groups[ngroups_max+1]; + int ngids = 0; + char *homedir = NULL; + const char *username; + char *old_passphrase = NULL; + char *new_passphrase = NULL; + char *wrapped_pw_filename; + char salt[ECRYPTFS_SALT_SIZE]; + char salt_hex[ECRYPTFS_SALT_SIZE_HEX]; + pid_t child_pid, tmp_pid; + int rc = PAM_SUCCESS; + + rc = pam_get_user(pamh, &username, NULL); + if (rc == PAM_SUCCESS) { + struct passwd *pwd; + + pwd = getpwnam(username); + if (pwd) { + uid = pwd->pw_uid; + gid = pwd->pw_gid; + homedir = pwd->pw_dir; + } + } else { + syslog(LOG_ERR, "pam_ecryptfs: Error getting passwd info for user [%s]; rc = [%d]\n", username, rc); + goto out; + } + + oeuid = geteuid(); + oegid = getegid(); + if ((ngids = getgroups(sizeof(groups)/sizeof(gid_t), groups)) < 0) { + syslog(LOG_ERR, "pam_ecryptfs: geteuid error"); + goto outnouid; + } + + if (setegid(gid) < 0 || setgroups(1, &gid) < 0 || seteuid(uid) < 0) { + syslog(LOG_ERR, "pam_ecryptfs: seteuid error"); + goto out; + } + + if ((rc = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **)&old_passphrase)) + != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_ecryptfs: Error retrieving old passphrase; rc = [%d]\n", rc); + goto out; + } + /* On the first pass, do nothing except check that we have a password */ + if ((flags & PAM_PRELIM_CHECK)) { + if (!old_passphrase) + { + syslog(LOG_WARNING, "pam_ecryptfs: PAM passphrase change module retrieved a NULL passphrase; nothing to do\n"); + rc = PAM_AUTHTOK_RECOVER_ERR; + } + goto out; + } + if ((rc = pam_get_item(pamh, PAM_AUTHTOK, + (const void **)&new_passphrase)) + != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_ecryptfs: Error retrieving new passphrase; rc = [%d]\n", rc); + goto out; + } + if ((rc = asprintf(&wrapped_pw_filename, "%s/.ecryptfs/%s", homedir, + ECRYPTFS_DEFAULT_WRAPPED_PASSPHRASE_FILENAME)) + == -1) { + syslog(LOG_ERR, "pam_ecryptfs: Unable to allocate memory\n"); + rc = -ENOMEM; + goto out; + } + if ((rc = ecryptfs_read_salt_hex_from_rc(salt_hex))) { + from_hex(salt, ECRYPTFS_DEFAULT_SALT_HEX, ECRYPTFS_SALT_SIZE); + } else { + from_hex(salt, salt_hex, ECRYPTFS_SALT_SIZE); + } + if (wrap_passphrase_if_necessary(username, uid, wrapped_pw_filename, new_passphrase, salt) == 0) { + syslog(LOG_DEBUG, "pam_ecryptfs: Passphrase file wrapped"); + } else { + goto out; + } + + if (!old_passphrase || !new_passphrase || *new_passphrase == '\0') { + syslog(LOG_WARNING, "pam_ecryptfs: PAM passphrase change module retrieved at least one NULL passphrase; nothing to do\n"); + rc = PAM_AUTHTOK_RECOVER_ERR; + goto out; + } + rc = PAM_SUCCESS; + if ((child_pid = fork()) == 0) { + char passphrase[ECRYPTFS_MAX_PASSWORD_LENGTH + 1]; + + /* temp regain uid 0 to drop privs */ + seteuid(oeuid); + /* setgroups() already called */ + if (setgid(gid) < 0 || setuid(uid) < 0) + goto out_child; + + if ((rc = ecryptfs_unwrap_passphrase(passphrase, + wrapped_pw_filename, + old_passphrase, salt))) { + syslog(LOG_ERR, "pam_ecryptfs: Error attempting to unwrap passphrase; rc = [%d]\n", rc); + goto out_child; + } + if ((rc = ecryptfs_wrap_passphrase(wrapped_pw_filename, + new_passphrase, salt, + passphrase))) { + syslog(LOG_ERR, "pam_ecryptfs: Error attempting to wrap passphrase; rc = [%d]", rc); + goto out_child; + } +out_child: + exit(0); + } + if ((tmp_pid = waitpid(child_pid, NULL, 0)) == -1) + syslog(LOG_WARNING, "pam_ecryptfs: waitpid() returned with error condition\n"); + free(wrapped_pw_filename); +out: + + seteuid(oeuid); + setegid(oegid); + setgroups(ngids, groups); + +outnouid: + return rc; +} |