/* * pam_mount * Copyright (C) Elvis Pfützenreuter , 2000 * Copyright © Jan Engelhardt, 2005 - 2010 * Copyright © Bastian Kleineidam, 2005 * * This file is part of pam_mount; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. */ #define PAM_SM_ACCOUNT 1 #define PAM_SM_AUTH 1 #define PAM_SM_SESSION 1 #define PAM_SM_PASSWORD 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pam_mount.h" #ifndef PAM_EXTERN # define PAM_EXTERN #endif #if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # define CONFIGFILE "/etc/pam_mount.conf.xml" #elif defined(__FreeBSD__) || defined(__DragonFly__) # define CONFIGFILE "/usr/local/etc/security/pam_mount.conf.xml" #else # define CONFIGFILE "/etc/security/pam_mount.conf.xml" #endif struct pam_args { bool get_pw_from_pam, get_pw_interactive, propagate_pw; }; /* Functions */ static void clean_config(pam_handle_t *, void *, int); static int converse(pam_handle_t *, int, const struct pam_message **, struct pam_response **); static int modify_pm_count(struct config *, char *, char *); static void parse_pam_args(int, const char **); static int read_password(pam_handle_t *, const char *, char **); /* Variables */ static const char *envpath_saved; struct config Config; struct pam_args Args; //----------------------------------------------------------------------------- /** * parse_pam_args - * @argv: NULL-terminated argument vector * @argc: number of elements in @argc * * Global @Args is initialized, based on @argv. */ static void parse_pam_args(int argc, const char **argv) { int i; assert(argc >= 0); for (i = 0; i < argc; i++) assert(argv[i] != NULL); /* first, set default values */ Args.get_pw_from_pam = true; Args.get_pw_interactive = true; Args.propagate_pw = true; for (i = 0; i < argc; ++i) { if (strcasecmp("enable_pam_password", argv[i]) == 0) Args.get_pw_from_pam = true; else if (strcasecmp("disable_pam_password", argv[i]) == 0) Args.get_pw_from_pam = false; else if (strcasecmp("enable_interactive", argv[i]) == 0) Args.get_pw_interactive = true; else if (strcasecmp("disable_interactive", argv[i]) == 0) Args.get_pw_interactive = false; else if (strcasecmp("enable_propagate_password", argv[i]) == 0) Args.propagate_pw = true; else if (strcasecmp("disable_propagate_password", argv[i]) == 0) Args.propagate_pw = false; else if (strcasecmp("debug", argv[i]) == 0) Debug = true; else w4rn("unknown pam_mount option \"%s\"\n", argv[i]); } } /** * clean_config - * @pamh: PAM handle * @data: custom data pointer * @err: * * Free data from a struct config variable. * Note: This is registered as a PAM callback function and is called directly. */ static void clean_config(pam_handle_t *pamh, void *data, int err) { w4rn("Clean global config (%d)\n", err); freeconfig(data); } /** * clean_system_authtok - * @pamh: PAM handle * @data: custom data pointer * @err: * * Zero and free @data if it is not %NULL. * Note: This is registered as a PAM callback function and is called directly. * * FIXME: Not binary-password safe. */ static void clean_system_authtok(pam_handle_t *pamh, void *data, int errcode) { w4rn("clean system authtok=%p (%d)\n", data, errcode); if (data != NULL) { unsigned int len = strlen(data) + 1; memset(data, 0, len); munlock(data, len); free(data); } } /** * converse - * @pamh: PAM handle * @nargs: number of messages * @message: PAM message array * @resp: user response array * * Note: Adapted from pam_unix/support.c. */ static int converse(pam_handle_t *pamh, int nargs, const struct pam_message **message, struct pam_response **resp) { int retval; struct pam_conv *conv; assert(pamh != NULL); assert(nargs >= 0); assert(resp != NULL); *resp = NULL; retval = pam_get_item(pamh, PAM_CONV, static_cast(const void **, static_cast(void *, &conv))); if (retval != PAM_SUCCESS) { l0g("pam_get_item: %s\n", pam_strerror(pamh, retval)); } else if (conv == NULL || conv->conv == NULL) { w4rn("No converse function available\n"); } else { retval = conv->conv(nargs, message, resp, conv->appdata_ptr); if (retval != PAM_SUCCESS) l0g("conv->conv(...): %s\n", pam_strerror(pamh, retval)); } if (resp == NULL || *resp == NULL || (*resp)->resp == NULL) retval = PAM_AUTH_ERR; assert(retval != PAM_SUCCESS || (resp != NULL && *resp != NULL && (*resp)->resp != NULL)); return retval; /* propagate error status */ } /** * read_password - * @pamh: PAM handle * @prompt: a prompt message * @pass: space for entered password * * Returns PAM error code or %PAM_SUCCESS. * Note: Adapted from pam_unix/support.c:_unix_read_password(). */ static int read_password(pam_handle_t *pamh, const char *prompt, char **pass) { int retval; struct pam_message msg; const struct pam_message *pmsg = &msg; struct pam_response *resp = NULL; assert(pamh != NULL); assert(pass != NULL); *pass = NULL; msg.msg_style = PAM_PROMPT_ECHO_OFF; msg.msg = (prompt == NULL) ? "Password: " : prompt; retval = converse(pamh, 1, &pmsg, &resp); if (retval == PAM_SUCCESS) *pass = xstrdup(resp->resp); assert(retval != PAM_SUCCESS || (pass != NULL && *pass != NULL)); return retval; } static void pmt_sigpipe_setup(bool block_it) { static pthread_mutex_t sp_lock = PTHREAD_MUTEX_INITIALIZER; static int sp_blocked = 0; static bool sp_previous; sigset_t set, oldset; pthread_mutex_lock(&sp_lock); if (block_it) { if (++sp_blocked == 1) { sigemptyset(&set); sigaddset(&set, SIGPIPE); sigprocmask(SIG_BLOCK, &set, &oldset); sp_previous = sigismember(&oldset, SIGPIPE); } } else { if (--sp_blocked == 0 && sp_previous) { sigemptyset(&set); sigaddset(&set, SIGPIPE); sigtimedwait(&set, NULL, &(struct timespec){0, 0}); sigprocmask(SIG_UNBLOCK, &set, NULL); } } pthread_mutex_unlock(&sp_lock); } static int common_init(pam_handle_t *pamh, int argc, const char **argv) { const char *pam_user; char buf[8]; int ret; pmtlog_prefix = "pam_mount"; pmtlog_path[PMTLOG_ERR][PMTLOG_SYSLOG] = true; pmtlog_path[PMTLOG_ERR][PMTLOG_STDERR] = true; pmtlog_path[PMTLOG_DBG][PMTLOG_SYSLOG] = Debug; pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug; ret = HX_init(); if (ret <= 0) l0g("libHX init failed: %s\n", strerror(errno)); initconfig(&Config); parse_pam_args(argc, argv); /* * call pam_get_user again because ssh calls PAM fns from seperate * processes. */ ret = pam_get_user(pamh, &pam_user, NULL); if (ret != PAM_SUCCESS) { l0g("could not get user"); /* * do NOT return %PAM_SERVICE_ERR or root will not be able to * su to other users. * Also, if we could not get the user's info, an earlier auth * module (like pam_unix2) likely blocked login already. */ return PAM_SUCCESS; } /* * FIXME: free me! the dup is requried because result of pam_get_user() * disappears (valgrind) */ Config.user = relookup_user(pam_user); if (!readconfig(CONFIGFILE, true, &Config)) return PAM_SERVICE_ERR; /* reinitialize after @Debug may have changed */ pmtlog_path[PMTLOG_DBG][PMTLOG_STDERR] = Debug; pmtlog_path[PMTLOG_DBG][PMTLOG_SYSLOG] = Debug; snprintf(buf, sizeof(buf), "%u", Debug); setenv("_PMT_DEBUG_LEVEL", buf, true); pmt_sigpipe_setup(true); return -1; } static void common_exit(void) { pmt_sigpipe_setup(false); HX_exit(); } static void auth_grab_authtok(pam_handle_t *pamh, struct config *config) { char *authtok = NULL; int ret; if (Args.get_pw_from_pam) { char *ptr = NULL; ret = pam_get_item(pamh, PAM_AUTHTOK, static_cast(const void **, static_cast(void *, &ptr))); if (ret == PAM_SUCCESS && ptr != NULL) authtok = xstrdup(ptr); } if (authtok == NULL && Args.get_pw_interactive) { ret = read_password(pamh, config->msg_authpw, &authtok); if (ret == PAM_SUCCESS && Args.propagate_pw) { /* * pam_set_item() copies to PAM-internal memory. * * Using pam_set_item(PAM_AUTHTOK) here to make the * password that was just entered available to further * PAM modules. */ ret = pam_set_item(pamh, PAM_AUTHTOK, authtok); if (ret != PAM_SUCCESS) l0g("warning: failure to export password (%s)\n", pam_strerror(pamh, ret)); } } /* * Save auth token for pam_mount itself, since PAM_AUTHTOK * will be gone when the auth stage exits. */ if (authtok != NULL) { ret = pam_set_data(pamh, "pam_mount_system_authtok", authtok, clean_system_authtok); if (ret == PAM_SUCCESS) { if (mlock(authtok, strlen(authtok) + 1) < 0) w4rn("mlock authtok: %s\n", strerror(errno)); } else { l0g("error trying to save authtok for session code\n"); } } } /** * pam_sm_authenticate - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * Called by the PAM layer. The user's system password is added to PAM's * global module data. This is because pam_sm_open_session() does not allow * access to the user's password. Returns the PAM error code or %PAM_SUCCESS. */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { int ret = PAM_SUCCESS; assert(pamh != NULL); if ((ret = common_init(pamh, argc, argv)) != -1) return ret; w4rn(PACKAGE_STRING ": entering auth stage\n"); auth_grab_authtok(pamh, &Config); common_exit(); /* * pam_mount is not really meant to be an auth module. So we should not * hinder the login process. */ return PAM_SUCCESS; } /** * On login, $PATH is correctly set to ENV_ROOTPATH (from /etc/login.defs), * while on logout, it happens to be ENV_PATH only. This is problematic, * since some programs are in /sbin and /usr/sbin which is * often not contained in ENV_PATH. * * In short: Another workaround for coreutils. */ static void envpath_init(const char *new_path) { envpath_saved = getenv("PATH"); setenv("PATH", new_path, true); } static void envpath_restore(void) { if (envpath_saved == NULL) unsetenv("PATH"); else setenv("PATH", envpath_saved, true); } /** * modify_pm_count - * @config: * @user: * @operation: string specifying numerical increment * * Calls out to the `pmvarrun` helper utility to adjust the mount reference * count in /var/run/pam_mount/@user for the specified user. * Returns the new reference count value on success, or -1 on error. * * Note: Modified version of pam_console.c:use_count() */ static int modify_pm_count(struct config *config, char *user, char *operation) { FILE *fp = NULL; struct HXformat_map *vinfo; struct HXdeque *argv; struct HXproc proc; int ret = -1, use_count; assert(user != NULL); assert(operation != NULL); if ((vinfo = HXformat_init()) == NULL) goto out; format_add(vinfo, "USER", user); format_add(vinfo, "OPERATION", operation); misc_add_ntdom(vinfo, user); argv = arglist_build(config->command[CMD_PMVARRUN], vinfo); memset(&proc, 0, sizeof(proc)); proc.p_flags = HXPROC_VERBOSE | HXPROC_STDOUT; proc.p_ops = &pmt_dropprivs_ops; if ((ret = pmt_spawn_dq(argv, &proc)) <= 0) { l0g("error executing pmvarrun: %s\n", strerror(-ret)); goto out; } ret = -1; if ((fp = fdopen(proc.p_stdout, "r")) == NULL) goto out2; if (fscanf(fp, "%d", &use_count) != 1) w4rn("error reading login count from pmvarrun\n"); else w4rn("pmvarrun says login count is %d\n", use_count); out2: if (fp != NULL) fclose(fp); else close(proc.p_stdout); if (HXproc_wait(&proc) >= 0 && proc.p_exited && proc.p_status == 0) ret = use_count; out: if (vinfo != NULL) HXformat_free(vinfo); return ret; } /** * ses_grab_authtok - get the password from PAM * * Session stage: reretrieve password that the auth stage stored. * If that does not work, use interactive prompting if enabled. */ static char *ses_grab_authtok(pam_handle_t *pamh) { char *authtok = NULL; int ret; ret = pam_get_data(pamh, "pam_mount_system_authtok", static_cast(const void **, static_cast(void *, &authtok))); if (ret == PAM_SUCCESS) return authtok; /* No stored password, get one, if allowed to. */ if (Args.get_pw_interactive) { ret = read_password(pamh, Config.msg_sessionpw, &authtok); if (ret != PAM_SUCCESS) /* authtok is %NULL now */ l0g("warning: could not obtain password " "interactively either\n"); } if (authtok != NULL) { ret = pam_set_data(pamh, "pam_mount_system_authtok", authtok, clean_system_authtok); if (ret == PAM_SUCCESS) { if (mlock(authtok, strlen(authtok) + 1) < 0) w4rn("mlock authtok: %s\n", strerror(errno)); } else { l0g("error trying to save authtok for session code\n"); } } /* * Always proceed, even if there is no password. Some volumes may not * need one, e.g. bind mounts and networked/unencrypted volumes. */ return authtok; } static int process_volumes(struct config *config, const char *authtok) { int ret = PAM_SUCCESS; struct vol *vol; HXlist_for_each_entry(vol, &config->volume_list, list) { /* * Remember what we processed already - the function can * be called multiple times. */ if (vol->mnt_processed) continue; vol->mnt_processed = true; /* * luserconf_volume_record_sane() is called here so that a user * can nest loopback images. otherwise ownership tests will * fail if parent loopback image not yet mounted. * volume_record_sane() is here to be consistent. */ if (!volume_record_sane(config, vol)) continue; if (!vol->globalconf && !luserconf_volume_record_sane(config, vol)) continue; if (!mount_op(do_mount, config, vol, authtok)) { l0g("mount of %s failed\n", znul(vol->volume)); ret = PAM_SERVICE_ERR; } } return ret; } /** * pam_sm_open_session - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * Entrypoint from the PAM layer. Starts the wheels and eventually mounts the * user's directories according to pam_mount.conf.xml. Returns the PAM error * code or %PAM_SUCCESS. */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int ret; const char *krb5; char *system_authtok = NULL; const void *tmp; int getval; assert(pamh != NULL); if ((ret = common_init(pamh, argc, argv)) != -1) return ret; w4rn(PACKAGE_STRING ": entering session stage\n"); /* * Environment variables set with setenv() only last while PAM is * active, i.e. disappear when the shell is started. On the other hand, * variabled fed to pam_putenv() are only visible once the shell * started. */ /* * Get the Kerberos CCNAME so we can make it available to the * mount command later on. */ krb5 = pam_getenv(pamh, "KRB5CCNAME"); if (krb5 != NULL && setenv("KRB5CCNAME", krb5, true) < 0) l0g("KRB5CCNAME setenv failed\n"); /* Store initialized config as PAM data */ getval = pam_get_data(pamh, "pam_mount_config", &tmp); if (getval == PAM_NO_MODULE_DATA) { ret = pam_set_data(pamh, "pam_mount_config", &Config, clean_config); if (ret != PAM_SUCCESS) { l0g("error trying to save config structure\n"); goto out; } /* Up the reference count by one, for freeconfig */ HX_init(); } if (!expandconfig(&Config)) { l0g("error expanding configuration\n"); ret = PAM_SERVICE_ERR; goto out; } if (Config.volume_list.items > 0) /* There are some volumes, so grab a password. */ system_authtok = ses_grab_authtok(pamh); misc_dump_id("Session open"); envpath_init(Config.path); ret = process_volumes(&Config, system_authtok); /* * Read luserconf after mounting of initial volumes. This makes it * possible to store luserconfs on net volumes themselves. */ if (Config.luserconf != NULL && *Config.luserconf != '\0' && pmt_fileop_exists(Config.luserconf)) { w4rn("going to readconfig %s\n", Config.luserconf); if (!pmt_fileop_owns(Config.user, Config.luserconf)) { w4rn("%s does not exist or is not owned by user\n", Config.luserconf); } else if (!readconfig(Config.luserconf, false, &Config)) { ret = PAM_SERVICE_ERR; } else if (!expandconfig(&Config)) { ret = PAM_SERVICE_ERR; l0g("error expanding configuration\n"); } } if (Config.volume_list.items == 0) { w4rn("no volumes to mount\n"); ret = PAM_SUCCESS; } else { /* * If there are no global volumes, but luserconf volumes, * and we still have no password, ask for one now. */ if (system_authtok == NULL) system_authtok = ses_grab_authtok(pamh); ret = process_volumes(&Config, system_authtok); } modify_pm_count(&Config, Config.user, "1"); envpath_restore(); if (getuid() == 0) /* Make sure root can always log in. */ /* NB: I don't even wanna think of SELINUX's ambiguous UIDs... */ ret = PAM_SUCCESS; /* * If mounting something failed, e.g. ret = %PAM_SERVICE_ERR, we have * to unravel everything and umount all volumes. But *only* if * pam_mount was configured as a "required" module. How can this info * be obtained? * For now, always assume "optional", so that the volumes are * definitely unmounted when the user logs out again. */ ret = PAM_SUCCESS; out: if (krb5 != NULL) unsetenv("KRB5CCNAME"); w4rn("done opening session (ret=%d)\n", ret); common_exit(); return ret; } /** * pam_sm_chauthtok - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * This is a placeholder function so PAM does not get mad. */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { return pam_sm_authenticate(pamh, flags, argc, argv); } /** * pam_sm_close_session - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * Entrypoint from the PAM layer. Stops all wheels and eventually unmounts the * user's directories. Returns the PAM error code or %PAM_SUCCESS. * * FIXME: This function currently always returns %PAM_SUCCESS. Should it * return soemthing else when errors occur and all unmounts have been * attempted? */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *pam_user = NULL; int ret; assert(pamh != NULL); ret = HX_init(); if (ret <= 0) l0g("libHX init failed: %s\n", strerror(errno)); ret = PAM_SUCCESS; w4rn("received order to close things\n"); if (Config.volume_list.items == 0) { w4rn("No volumes to umount\n"); goto out; } misc_dump_id("Session close"); /* * call pam_get_user() again because ssh calls PAM fns from seperate * processes. */ ret = pam_get_user(pamh, &pam_user, NULL); if (ret != PAM_SUCCESS) { l0g("could not get user\n"); goto out; } /* * FIXME: free me! the dup is requried because result of pam_get_user * disappears (valgrind) */ Config.user = relookup_user(pam_user); /* if our CWD is in the home directory, it might not get umounted */ if (chdir("/") != 0) l0g("could not chdir\n"); out: envpath_init(Config.path); if (modify_pm_count(&Config, Config.user, "-1") > 0) w4rn("%s seems to have other remaining open sessions\n", Config.user); else umount_final(&Config); envpath_restore(); /* * Note that PMConfig is automatically freed later in clean_config() */ w4rn("pam_mount execution complete\n"); HX_exit(); return ret; } /** * pam_sm_setcred - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * This is a placeholder function so PAM does not get mad. */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } /** * pam_sm_acct_mgmt - * @pamh: PAM handle * @flags: PAM flags * @argc: number of elements in @argv * @argv: NULL-terminated argument vector * * This is a placeholder function so PAM does not get mad. */ PAM_EXTERN EXPORT_SYMBOL int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_IGNORE; } #ifdef PAM_STATIC /* static module data */ EXPORT_SYMBOL struct pam_module _pam_mount_modstruct = { .name = "pam_mount", .pam_sm_authenticate = pam_sm_authenticate, .pam_sm_setcred = pam_sm_setcred, .pam_sm_acct_mgmt = pam_sm_acct_mgmt, .pam_sm_open_sesion = pam_sm_open_session, .pam_sm_close_session = pam_sm_close_session, .pam_sm_chauthtok = pam_sm_chauthtok, }; #endif