/* * Copyright (C) 2013 Orion Poplawski * * based on gkr-pam-module.c, Copyright (C) 2007 Stef Walter * * 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 3 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 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include /* #include #include #include */ #include #include #include #include #include "cifskey.h" #include "mount.h" #include "resolve_host.h" #include "util.h" /** * Flags that can be passed to the PAM module */ enum { ARG_DOMAIN = 1 << 0, /** Set domain password */ ARG_DEBUG = 1 << 1 /** Print debug messages */ }; /** * Parse the arguments passed to the PAM module. * * @param ph PAM handle * @param argc number of arguments * @param argv array of arguments * @param kwalletopener kwalletopener argument, path to the kwalletopener binary * @return ORed flags that have been parsed */ static uint parse_args (pam_handle_t *ph, int argc, const char **argv, const char **hostdomain) { uint args = 0; const void *svc; int i; const char *host = NULL; const char *domain = NULL; svc = NULL; if (pam_get_item (ph, PAM_SERVICE, &svc) != PAM_SUCCESS) { svc = NULL; } size_t host_len = strlen("host="); size_t domain_len = strlen("domain="); /* Parse the arguments */ for (i = 0; i < argc; i++) { if (strncmp(argv[i], "host=", host_len) == 0) { host = (argv[i]) + host_len; if (*host == '\0') { host = NULL; pam_syslog(ph, LOG_ERR, "" "host= specification missing argument"); } else { *hostdomain = host; } } else if (strncmp(argv[i], "domain=", domain_len) == 0) { domain = (argv[i]) + domain_len; if (*domain == '\0') { domain = NULL; pam_syslog(ph, LOG_ERR, "" "domain= specification missing argument"); } else { *hostdomain = domain; args |= ARG_DOMAIN; } } else if (strcmp(argv[i], "debug") == 0) { args |= ARG_DEBUG; } else { pam_syslog(ph, LOG_ERR, "invalid option %s", argv[i]); } } if (host && domain) { pam_syslog(ph, LOG_ERR, "cannot specify both host= and " "domain= arguments"); } return args; } static void free_password (char *password) { volatile char *vp; size_t len; if (!password) { return; } /* Defeats some optimizations */ len = strlen (password); memset (password, 0xAA, len); memset (password, 0xBB, len); /* Defeats others */ vp = (volatile char*)password; while (*vp) { *(vp++) = 0xAA; } free (password); } static void cleanup_free_password (pam_handle_t *ph __attribute__((unused)), void *data, int pam_end_status __attribute__((unused))) { free_password (data); } /** * Set the cifs credentials * * @param ph PAM handle * @param user * @param password * @param args ORed flags for this module * @param hostdomain hostname or domainname */ static int cifscreds_pam_add(pam_handle_t *ph, const char *user, const char *password, uint args, const char *hostdomain) { int ret = PAM_SUCCESS; char addrstr[MAX_ADDR_LIST_LEN]; char *currentaddress, *nextaddress; char keytype = ((args & ARG_DOMAIN) == ARG_DOMAIN) ? 'd' : 'a'; assert(user); assert(password); assert(hostdomain); if (keytype == 'd') { if (strpbrk(hostdomain, DOMAIN_DISALLOWED_CHARS)) { pam_syslog(ph, LOG_ERR, "Domain name contains invalid characters"); return PAM_SERVICE_ERR; } strlcpy(addrstr, hostdomain, MAX_ADDR_LIST_LEN); } else { ret = resolve_host(hostdomain, addrstr); } switch (ret) { case EX_USAGE: pam_syslog(ph, LOG_ERR, "Could not resolve address for %s", hostdomain); return PAM_SERVICE_ERR; case EX_SYSERR: pam_syslog(ph, LOG_ERR, "Problem parsing address list"); return PAM_SERVICE_ERR; } if (strpbrk(user, USER_DISALLOWED_CHARS)) { pam_syslog(ph, LOG_ERR, "Incorrect username"); return PAM_SERVICE_ERR; } /* search for same credentials stashed for current host */ currentaddress = addrstr; nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; while (currentaddress) { if (key_search(currentaddress, keytype) > 0) { pam_syslog(ph, LOG_WARNING, "You already have stashed credentials " "for %s (%s)", currentaddress, hostdomain); return PAM_SERVICE_ERR; } switch(errno) { case ENOKEY: /* success */ break; default: pam_syslog(ph, LOG_ERR, "Unable to search keyring for %s (%s)", currentaddress, strerror(errno)); return PAM_SERVICE_ERR; } currentaddress = nextaddress; if (currentaddress) { *(currentaddress - 1) = ','; nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; } } /* Set the password */ currentaddress = addrstr; nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; while (currentaddress) { key_serial_t key = key_add(currentaddress, user, password, keytype); if (key <= 0) { pam_syslog(ph, LOG_ERR, "error: Add credential key for %s: %s", currentaddress, strerror(errno)); } else { if ((args & ARG_DEBUG) == ARG_DEBUG) { pam_syslog(ph, LOG_DEBUG, "credential key for \\\\%s\\%s added", currentaddress, user); } if (keyctl(KEYCTL_SETPERM, key, CIFS_KEY_PERMS) < 0) { pam_syslog(ph, LOG_ERR,"error: Setting permissons " "on key, attempt to delete..."); if (keyctl(KEYCTL_UNLINK, key, DEST_KEYRING) < 0) { pam_syslog(ph, LOG_ERR, "error: Deleting key from " "keyring for %s (%s)", currentaddress, hostdomain); } } } currentaddress = nextaddress; if (currentaddress) { nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; } } return PAM_SUCCESS; } /** * Update the cifs credentials * * @param ph PAM handle * @param user * @param password * @param args ORed flags for this module * @param hostdomain hostname or domainname */ static int cifscreds_pam_update(pam_handle_t *ph, const char *user, const char *password, uint args, const char *hostdomain) { int ret = PAM_SUCCESS; char addrstr[MAX_ADDR_LIST_LEN]; char *currentaddress, *nextaddress; int id, count = 0; char keytype = ((args & ARG_DOMAIN) == ARG_DOMAIN) ? 'd' : 'a'; assert(user); assert(password); assert(hostdomain); if (keytype == 'd') { if (strpbrk(hostdomain, DOMAIN_DISALLOWED_CHARS)) { pam_syslog(ph, LOG_ERR, "Domain name contains invalid characters"); return PAM_SERVICE_ERR; } strlcpy(addrstr, hostdomain, MAX_ADDR_LIST_LEN); } else { ret = resolve_host(hostdomain, addrstr); } switch (ret) { case EX_USAGE: pam_syslog(ph, LOG_ERR, "Could not resolve address for %s", hostdomain); return PAM_SERVICE_ERR; case EX_SYSERR: pam_syslog(ph, LOG_ERR, "Problem parsing address list"); return PAM_SERVICE_ERR; } if (strpbrk(user, USER_DISALLOWED_CHARS)) { pam_syslog(ph, LOG_ERR, "Incorrect username"); return PAM_SERVICE_ERR; } /* search for necessary credentials stashed in session keyring */ currentaddress = addrstr; nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; while (currentaddress) { if (key_search(currentaddress, keytype) > 0) count++; currentaddress = nextaddress; if (currentaddress) { nextaddress = strchr(currentaddress, ','); if (nextaddress) *nextaddress++ = '\0'; } } if (!count) { pam_syslog(ph, LOG_ERR, "You have no same stashed credentials for %s", hostdomain); return PAM_SERVICE_ERR; } for (id = 0; id < count; id++) { key_serial_t key = key_add(currentaddress, user, password, keytype); if (key <= 0) { pam_syslog(ph, LOG_ERR, "error: Update credential key for %s: %s", currentaddress, strerror(errno)); } } return PAM_SUCCESS; } /** * PAM function called during authentication. * * This function first tries to get a password from PAM. Afterwards two * scenarios are possible: * * - A session is already available which usually means that the user is already * logged on and PAM has been used inside the screensaver. In that case, no need to * do anything(?). * * - A session is not yet available. Store the password inside PAM data so * it can be retrieved during pam_open_session to set the credentials. * * @param ph PAM handle * @param unused unused * @param argc number of arguments for this PAM module * @param argv array of arguments for this PAM module * @return any of the PAM return values */ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *ph, int unused __attribute__((unused)), int argc, const char **argv) { const char *hostdomain; const char *user; const char *password; uint args; int ret; args = parse_args(ph, argc, argv, &hostdomain); /* Figure out and/or prompt for the user name */ ret = pam_get_user(ph, &user, NULL); if (ret != PAM_SUCCESS || !user) { pam_syslog(ph, LOG_ERR, "couldn't get the user name: %s", pam_strerror(ph, ret)); return PAM_SERVICE_ERR; } /* Lookup the password */ ret = pam_get_item(ph, PAM_AUTHTOK, (const void**)&password); if (ret != PAM_SUCCESS || password == NULL) { if (ret == PAM_SUCCESS) { pam_syslog(ph, LOG_WARNING, "no password is available for user"); } else { pam_syslog(ph, LOG_WARNING, "no password is available for user: %s", pam_strerror(ph, ret)); } return PAM_SUCCESS; } /* set password as pam data and launch during open_session. */ if (pam_set_data(ph, "cifscreds_password", strdup(password), cleanup_free_password) != PAM_SUCCESS) { pam_syslog(ph, LOG_ERR, "error storing password"); return PAM_AUTHTOK_RECOVER_ERR; } if ((args & ARG_DEBUG) == ARG_DEBUG) { pam_syslog(ph, LOG_DEBUG, "password stored"); } return PAM_SUCCESS; } /** * PAM function called during opening the session. * * Retrieves the password stored during authentication from PAM data, then uses * it set the cifs key. * * @param ph PAM handle * @param flags currently unused, TODO: check for silent flag * @param argc number of arguments for this PAM module * @param argv array of arguments for this PAM module * @return any of the PAM return values */ PAM_EXTERN int pam_sm_open_session(pam_handle_t *ph, int flags __attribute__((unused)), int argc, const char **argv) { const char *user = NULL; const char *password = NULL; const char *hostdomain = NULL; uint args; int retval; key_serial_t ses_key, uses_key; args = parse_args(ph, argc, argv, &hostdomain); /* Figure out the user name */ retval = pam_get_user(ph, &user, NULL); if (retval != PAM_SUCCESS || !user) { pam_syslog(ph, LOG_ERR, "couldn't get the user name: %s", pam_strerror(ph, retval)); return PAM_SERVICE_ERR; } /* retrieve the stored password */ if (pam_get_data(ph, "cifscreds_password", (const void**)&password) != PAM_SUCCESS) { /* * No password, no worries, maybe this (PAM using) application * didn't do authentication, or is hopeless and wants to call * different PAM callbacks from different processes. * * */ password = NULL; if ((args & ARG_DEBUG) == ARG_DEBUG) { pam_syslog(ph, LOG_DEBUG, "no stored password found"); } return PAM_SUCCESS; } /* make sure we have a host or domain name */ if (!hostdomain) { pam_syslog(ph, LOG_ERR, "one of host= or domain= must be specified"); return PAM_SERVICE_ERR; } /* make sure there is a session keyring */ ses_key = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0); if (ses_key == -1) { if (errno == ENOKEY) pam_syslog(ph, LOG_ERR, "you have no session keyring. " "Consider using pam_keyinit to " "install one."); else pam_syslog(ph, LOG_ERR, "unable to query session " "keyring: %s", strerror(errno)); } /* A problem querying the user-session keyring isn't fatal. */ uses_key = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0); if ((uses_key >= 0) && (ses_key == uses_key)) pam_syslog(ph, LOG_ERR, "you have no persistent session " "keyring. cifscreds keys will not persist."); return cifscreds_pam_add(ph, user, password, args, hostdomain); } /** * This is called when the PAM session is closed. * * Currently it does nothing. The session closing should remove the passwords * * @param ph PAM handle * @param flags currently unused, TODO: check for silent flag * @param argc number of arguments for this PAM module * @param argv array of arguments for this PAM module * @return PAM_SUCCESS */ PAM_EXTERN int pam_sm_close_session(pam_handle_t *ph __attribute__((unused)), int flags __attribute__((unused)), int argc __attribute__((unused)), const char **argv __attribute__((unused))) { return PAM_SUCCESS; } /** * This is called when pam_set_cred() is invoked. * * @param ph PAM handle * @param flags currently unused, TODO: check for silent flag * @param argc number of arguments for this PAM module * @param argv array of arguments for this PAM module * @return PAM_SUCCESS */ PAM_EXTERN int pam_sm_setcred(pam_handle_t *ph __attribute__((unused)), int flags __attribute__((unused)), int argc __attribute__((unused)), const char **argv __attribute__((unused))) { return PAM_SUCCESS; } /** * This is called when the user's password is changed * * @param ph PAM handle * @param flags currently unused, TODO: check for silent flag * @param argc number of arguments for this PAM module * @param argv array of arguments for this PAM module * @return PAM_SUCCESS */ PAM_EXTERN int pam_sm_chauthtok (pam_handle_t *ph, int flags, int argc, const char **argv) { const char *hostdomain = NULL; const char *user = NULL; const char *password = NULL; uint args; int ret; args = parse_args(ph, argc, argv, &hostdomain); if (flags & PAM_UPDATE_AUTHTOK) { /* Figure out the user name */ ret = pam_get_user(ph, &user, NULL); if (ret != PAM_SUCCESS) { pam_syslog(ph, LOG_ERR, "couldn't get the user name: %s", pam_strerror (ph, ret)); return PAM_SERVICE_ERR; } ret = pam_get_item(ph, PAM_AUTHTOK, (const void**)&password); if (ret != PAM_SUCCESS || password == NULL) { if (ret == PAM_SUCCESS) { pam_syslog(ph, LOG_WARNING, "no password is available for user"); } else { pam_syslog(ph, LOG_WARNING, "no password is available for user: %s", pam_strerror(ph, ret)); } return PAM_AUTHTOK_RECOVER_ERR; } return cifscreds_pam_update(ph, user, password, args, hostdomain); } else return PAM_IGNORE; }