File indexing completed on 2024-03-24 17:02:42

0001 /*
0002     SPDX-FileCopyrightText: 2014 Alejandro Fiestas Olivares <afiestas@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include <fcntl.h>
0008 #include <gcrypt.h>
0009 #include <stdio.h>
0010 #include <signal.h>
0011 #include <unistd.h>
0012 #include <fcntl.h>
0013 #include <stdlib.h>
0014 #include <string.h>
0015 #include <errno.h>
0016 #include <grp.h>
0017 
0018 #define PAM_SM_PASSWORD
0019 #define PAM_SM_SESSION
0020 #define PAM_SM_AUTH
0021 #include <pwd.h>
0022 #include <sys/stat.h>
0023 #include <sys/syslog.h>
0024 #include <sys/wait.h>
0025 #include <sys/types.h>
0026 #include <sys/socket.h>
0027 #include <sys/un.h>
0028 
0029 /* PAM headers.
0030  *
0031  * There are three styles in play:
0032  *  - Apple, which has no pam_ext.h, does have pam_appl.h, does have pam_syslog
0033  *  - Linux, which has pam_ext.h, does have pam_appl.h, does have pam_syslog
0034  *  - BSD, which has no pam_ext.h, does have pam_appl.h, but no pam_syslog
0035  * In the latter case, #define pam_syslog away.
0036  */
0037 #ifdef __APPLE__
0038 #include "pam_darwin.h"
0039 #include <security/pam_appl.h>
0040 #else
0041 #include <security/pam_modules.h>
0042 #ifdef HAVE_PAM_EXT
0043 /* "Linux style" */
0044 #include <security/pam_ext.h>
0045 #include <security/_pam_types.h>
0046 #endif
0047 #ifdef HAVE_PAM_APPL
0048 /* "BSD style" .. see also __APPLE__, above */
0049 #include <security/pam_appl.h>
0050 #ifndef HAVE_PAM_EXT
0051 /* FreeBSD has no pam_syslog(), va-macro it away */
0052 #define pam_syslog(...)
0053 #endif
0054 #endif
0055 #endif
0056 
0057 #define KWALLET_PAM_KEYSIZE 56
0058 #define KWALLET_PAM_SALTSIZE 56
0059 #define KWALLET_PAM_ITERATIONS 50000
0060 
0061 // Parameters
0062 const static char *kdehome = NULL;
0063 const static char *kwalletd = NULL;
0064 const static char *socketPath = NULL;
0065 static int force_run = 0;
0066 
0067 const static char * const kwalletPamDataKey = "kwallet5_key";
0068 const static char * const logPrefix = "pam_kwallet5";
0069 const static char * const envVar = "PAM_KWALLET5_LOGIN";
0070 
0071 static int argumentsParsed = -1;
0072 
0073 int kwallet_hash(pam_handle_t *pamh, const char *passphrase, struct passwd *userInfo, char *key);
0074 
0075 static void parseArguments(int argc, const char **argv)
0076 {
0077     //If already parsed
0078     if (argumentsParsed != -1) {
0079         return;
0080     }
0081 
0082     int x = 0;
0083     for (;x < argc; ++x) {
0084         if (strstr(argv[x], "kdehome=") != NULL) {
0085             kdehome = argv[x] + 8;
0086         } else if (strstr(argv[x], "kwalletd=") != NULL) {
0087             kwalletd = argv[x] + 9;
0088         } else if (strstr(argv[x], "socketPath=") != NULL) {
0089             socketPath= argv[x] + 11;
0090         } else if (strcmp(argv[x], "force_run") == 0) {
0091             force_run = 1;
0092         }
0093     }
0094     if (kdehome == NULL) {
0095         kdehome = ".local/share";
0096     }
0097     if (kwalletd == NULL) {
0098         kwalletd = KWALLETD_BIN_PATH;
0099     }
0100 }
0101 
0102 static const char* get_env(pam_handle_t *ph, const char *name)
0103 {
0104     const char *env = pam_getenv (ph, name);
0105     if (env && env[0]) {
0106         return env;
0107     }
0108 
0109     env = getenv (name);
0110     if (env && env[0]) {
0111         return env;
0112     }
0113 
0114     return NULL;
0115 }
0116 
0117 static int set_env(pam_handle_t *pamh, const char *name, const char *value)
0118 {
0119     if (setenv(name, value, 1) < 0) {
0120         pam_syslog(pamh, LOG_WARNING, "%s: Couldn't setenv %s = %s", logPrefix, name, value);
0121         //We do not return because pam_putenv might work
0122     }
0123 
0124     size_t pamEnvSize = strlen(name) + strlen(value) + 2; //2 is for = and \0
0125     char *pamEnv = malloc(pamEnvSize);
0126     if (!pamEnv) {
0127         pam_syslog(pamh, LOG_WARNING, "%s: Impossible to allocate memory for pamEnv", logPrefix);
0128         return -1;
0129     }
0130 
0131     snprintf (pamEnv, pamEnvSize, "%s=%s", name, value);
0132     int ret = pam_putenv(pamh, pamEnv);
0133     free(pamEnv);
0134 
0135     return ret;
0136 }
0137 
0138 /**
0139  * Code copied from gkr-pam-module.c, GPL2+
0140  */
0141 static void wipeString(char *str)
0142 {
0143     if (!str) {
0144         return;
0145     }
0146 
0147     const size_t len = strlen (str);
0148 #if HAVE_EXPLICIT_BZERO
0149     explicit_bzero(str, len);
0150 #else
0151     volatile char *vp;
0152 
0153     /* Defeats some optimizations */
0154     memset (str, 0xAA, len);
0155     memset (str, 0xBB, len);
0156 
0157     /* Defeats others */
0158     vp = (volatile char*)str;
0159     while (*vp) {
0160         *(vp++) = 0xAA;
0161     }
0162 #endif
0163 
0164     free (str);
0165 }
0166 
0167 static int prompt_for_password(pam_handle_t *pamh)
0168 {
0169     int result;
0170 
0171     //Get the function we have to call
0172     const struct pam_conv *conv;
0173     result = pam_get_item(pamh, PAM_CONV, (const void**)&conv);
0174     if (result != PAM_SUCCESS) {
0175         return result;
0176     }
0177 
0178     //prepare the message
0179     struct pam_message message;
0180     memset (&message, 0, sizeof(message));
0181     message.msg_style = PAM_PROMPT_ECHO_OFF;
0182     message.msg = "Password: ";
0183 
0184     //We only need one message, but we still have to send it in an array
0185     const struct pam_message *msgs[1];
0186     msgs[0] = &message;
0187 
0188 
0189     //Sending the message, asking for password
0190     struct pam_response *response = NULL;
0191     memset (&response, 0, sizeof(response));
0192     result = (conv->conv) (1, msgs, &response, conv->appdata_ptr);
0193     if (result != PAM_SUCCESS) {
0194         goto cleanup;
0195     }
0196 
0197     //If we got no password, just return;
0198     if (response[0].resp == NULL) {
0199         result = PAM_CONV_ERR;
0200         goto cleanup;
0201     }
0202 
0203     //Set the password in PAM memory
0204     char *password = response[0].resp;
0205     result = pam_set_item(pamh, PAM_AUTHTOK, password);
0206     wipeString(password);
0207 
0208     if (result != PAM_SUCCESS) {
0209         goto cleanup;
0210     }
0211 
0212 cleanup:
0213     free(response);
0214     return result;
0215 }
0216 
0217 static void cleanup_free(pam_handle_t *pamh, void *ptr, int error_status)
0218 {
0219     free(ptr);
0220 }
0221 
0222 static int is_graphical_session(pam_handle_t *pamh)
0223 {
0224     //Detect a graphical session
0225     const char *pam_tty = NULL, *pam_xdisplay = NULL,
0226                *xdg_session_type = NULL;
0227 
0228     pam_get_item(pamh, PAM_TTY, (const void**) &pam_tty);
0229 #ifdef PAM_XDISPLAY
0230     pam_get_item(pamh, PAM_XDISPLAY, (const void**) &pam_xdisplay);
0231 #endif
0232     xdg_session_type = get_env(pamh, "XDG_SESSION_TYPE");
0233 
0234     return (pam_xdisplay && strlen(pam_xdisplay) != 0)
0235            || (pam_tty && pam_tty[0] == ':')
0236            || (xdg_session_type && strcmp(xdg_session_type, "x11") == 0)
0237            || (xdg_session_type && strcmp(xdg_session_type, "wayland") == 0);
0238 }
0239 
0240 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
0241 {
0242     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_authenticate\n", logPrefix);
0243     if (get_env(pamh, envVar) != NULL) {
0244         pam_syslog(pamh, LOG_INFO, "%s: we were already executed", logPrefix);
0245         return PAM_IGNORE;
0246     }
0247 
0248     parseArguments(argc, argv);
0249 
0250     int result;
0251 
0252     //Fetch the user, needed to get user information
0253     const char *username;
0254     result = pam_get_user(pamh, &username, NULL);
0255     if (result != PAM_SUCCESS) {
0256         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get username %s",
0257                    logPrefix, pam_strerror(pamh, result));
0258         return PAM_IGNORE;//Since we are not an essential module, just make pam ignore us
0259     }
0260 
0261     struct passwd *userInfo;
0262     userInfo = getpwnam(username);
0263     if (!userInfo) {
0264         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get user info (passwd) info", logPrefix);
0265         return PAM_IGNORE;
0266     }
0267 
0268     if (userInfo->pw_uid == 0) {
0269         pam_syslog(pamh, LOG_DEBUG, "%s: Refusing to do anything for the root user", logPrefix);
0270         return PAM_IGNORE;
0271     }
0272 
0273     const char *password;
0274     result = pam_get_item(pamh, PAM_AUTHTOK, (const void**)&password);
0275 
0276     if (result != PAM_SUCCESS) {
0277         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get password %s", logPrefix,
0278                    pam_strerror(pamh, result));
0279         return PAM_IGNORE;
0280     }
0281 
0282     if (!password) {
0283         pam_syslog(pamh, LOG_NOTICE, "%s: Couldn't get password (it is empty)", logPrefix);
0284         //Asking for the password ourselves
0285         result = prompt_for_password(pamh);
0286         if (result != PAM_SUCCESS) {
0287             pam_syslog(pamh, LOG_ERR, "%s: Prompt for password failed %s",
0288                        logPrefix, pam_strerror(pamh, result)
0289             );
0290             return PAM_IGNORE;
0291         }
0292     }
0293 
0294     //even though we just set it, better check to be 100% sure
0295     result = pam_get_item(pamh, PAM_AUTHTOK, (const void**)&password);
0296     if (result != PAM_SUCCESS || !password) {
0297         pam_syslog(pamh, LOG_ERR, "%s: Password is not there even though we set it %s", logPrefix,
0298                    pam_strerror(pamh, result));
0299         return PAM_IGNORE;
0300     }
0301 
0302     if (password[0] == '\0') {
0303         pam_syslog(pamh, LOG_NOTICE, "%s: Empty or missing password, doing nothing", logPrefix);
0304         return PAM_IGNORE;
0305     }
0306 
0307     char *key = strdup(password);
0308     result = pam_set_data(pamh, kwalletPamDataKey, key, cleanup_free);
0309 
0310     if (result != PAM_SUCCESS) {
0311         free(key);
0312         pam_syslog(pamh, LOG_ERR, "%s: Impossible to store the password: %s", logPrefix
0313             , pam_strerror(pamh, result));
0314         return PAM_IGNORE;
0315     }
0316 
0317     //if sm_open_session has already been called (but we did not have password), call it now
0318     const char *session_bit;
0319     result = pam_get_data(pamh, "sm_open_session", (const void **)&session_bit);
0320     if (result == PAM_SUCCESS) {
0321         pam_syslog(pamh, LOG_ERR, "%s: open_session was called before us, calling it now", logPrefix);
0322         return pam_sm_open_session(pamh, flags, argc, argv);
0323     }
0324 
0325     //TODO unlock kwallet that is already executed
0326     return PAM_IGNORE;
0327 }
0328 
0329 static int drop_privileges(struct passwd *userInfo)
0330 {
0331     /* When dropping privileges from root, the `setgroups` call will
0332     * remove any extraneous groups. If we don't call this, then
0333     * even though our uid has dropped, we may still have groups
0334     * that enable us to do super-user things. This will fail if we
0335     * aren't root, so don't bother checking the return value, this
0336     * is just done as an optimistic privilege dropping function.
0337     */
0338     setgroups(0, NULL);
0339 
0340     //Change to the user in case we are not it yet
0341     if (setgid (userInfo->pw_gid) < 0 || setuid (userInfo->pw_uid) < 0 ||
0342         setegid (userInfo->pw_gid) < 0 || seteuid (userInfo->pw_uid) < 0) {
0343         return -1;
0344     }
0345 
0346     return 0;
0347 }
0348 
0349 static void execute_kwallet(pam_handle_t *pamh, struct passwd *userInfo, int toWalletPipe[2], char *fullSocket)
0350 {
0351     //In the child pam_syslog does not work, using syslog directly
0352 
0353     //keep stderr open so socket doesn't returns us that fd
0354     int x = 3;
0355     //Set FD_CLOEXEC on fd that are not of interest of kwallet
0356     for (; x < 64; ++x) {
0357         if (x != toWalletPipe[0]) {
0358             fcntl(x, F_SETFD, FD_CLOEXEC);
0359         }
0360     }
0361 
0362     //This is the side of the pipe PAM will send the hash to
0363     close (toWalletPipe[1]);
0364 
0365     //Change to the user in case we are not it yet
0366     if (drop_privileges(userInfo) < 0) {
0367         syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for kwalletd", logPrefix);
0368         goto cleanup;
0369     }
0370 
0371     int envSocket;
0372     if ((envSocket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
0373         syslog(LOG_ERR, "%s: couldn't create socket", logPrefix);
0374         goto cleanup;
0375     }
0376 
0377     struct sockaddr_un local = {};
0378     local.sun_family = AF_UNIX;
0379 
0380     if (strlen(fullSocket) > sizeof(local.sun_path)) {
0381         syslog(LOG_ERR, "%s: socket path %s too long to open",
0382                    logPrefix, fullSocket);
0383         free(fullSocket);
0384         goto cleanup;
0385     }
0386     strcpy(local.sun_path, fullSocket);
0387     unlink(local.sun_path);//Just in case it exists from a previous login
0388 
0389     syslog(LOG_DEBUG, "%s: final socket path: %s", logPrefix, local.sun_path);
0390 
0391     if (bind(envSocket, (struct sockaddr *)&local, sizeof(local)) == -1) {
0392         syslog(LOG_INFO, "%s-kwalletd: Couldn't bind to local file\n", logPrefix);
0393         goto cleanup;
0394     }
0395 
0396     if (listen(envSocket, 5) == -1) {
0397         syslog(LOG_INFO, "%s-kwalletd: Couldn't listen in socket: %d-%s\n", logPrefix, errno, strerror(errno));
0398         goto cleanup;
0399     }
0400     //finally close stderr
0401     close(2);
0402 
0403     // Fork twice to daemonize kwallet
0404     setsid();
0405     pid_t pid = fork();
0406     if (pid != 0) {
0407         if (pid == -1) {
0408             exit(EXIT_FAILURE);
0409         } else {
0410             exit(0);
0411         }
0412     }
0413 
0414     //TODO use a pam argument for full path kwalletd
0415     char pipeInt[4];
0416     sprintf(pipeInt, "%d", toWalletPipe[0]);
0417     char sockIn[4];
0418     sprintf(sockIn, "%d", envSocket);
0419 
0420     char *args[] = {strdup(kwalletd), "--pam-login", pipeInt, sockIn, NULL, NULL};
0421     execve(args[0], args, pam_getenvlist(pamh));
0422     syslog(LOG_ERR, "%s: could not execute kwalletd from %s", logPrefix, kwalletd);
0423 
0424 cleanup:
0425     exit(EXIT_FAILURE);
0426 }
0427 
0428 static int better_write(int fd, const char *buffer, int len)
0429 {
0430     size_t writtenBytes = 0;
0431     while(writtenBytes < len) {
0432         ssize_t result = write(fd, buffer + writtenBytes, len - writtenBytes);
0433         if (result < 0) {
0434             if (errno != EAGAIN && errno != EINTR) {
0435                 return -1;
0436             }
0437         }
0438         writtenBytes += result;
0439     }
0440 
0441     return writtenBytes;
0442 }
0443 
0444 static void start_kwallet(pam_handle_t *pamh, struct passwd *userInfo, const char *kwalletKey)
0445 {
0446     //Just in case we get broken pipe, do not break the pam process..
0447     struct sigaction sigPipe, oldSigPipe;
0448     memset (&sigPipe, 0, sizeof (sigPipe));
0449     memset (&oldSigPipe, 0, sizeof (oldSigPipe));
0450     sigPipe.sa_handler = SIG_IGN;
0451     sigaction (SIGPIPE, &sigPipe, &oldSigPipe);
0452 
0453     int toWalletPipe[2] = { -1, -1};
0454     if (pipe(toWalletPipe) < 0) {
0455         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create pipes", logPrefix);
0456     }
0457 
0458     const char *socketPrefix = "kwallet5";
0459 
0460     char *fullSocket = NULL;
0461     if (socketPath) {
0462         size_t needed = snprintf(NULL, 0, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
0463         needed += 1;
0464         fullSocket = malloc(needed);
0465         snprintf(fullSocket, needed, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
0466     } else {
0467         socketPath = get_env(pamh, "XDG_RUNTIME_DIR");
0468         // Check whether XDG_RUNTIME_DIR is usable
0469         if (socketPath) {
0470             struct stat rundir_stat;
0471             if (stat(socketPath, &rundir_stat) != 0) {
0472                 pam_syslog(pamh, LOG_ERR, "%s: Failed to stat %s", logPrefix, socketPath);
0473                 socketPath = NULL;
0474             } else if(!S_ISDIR(rundir_stat.st_mode) || (rundir_stat.st_mode & ~S_IFMT) != 0700
0475                       || rundir_stat.st_uid != userInfo->pw_uid) {
0476                 pam_syslog(pamh, LOG_ERR, "%s: %s has wrong type, perms or ownership", logPrefix, socketPath);
0477                 socketPath = NULL;
0478             }
0479         }
0480 
0481         if (socketPath) {
0482             size_t needed = snprintf(NULL, 0, "%s/%s%s", socketPath, socketPrefix, ".socket");
0483             needed += 1;
0484             fullSocket = malloc(needed);
0485             snprintf(fullSocket, needed, "%s/%s%s", socketPath, socketPrefix, ".socket");
0486         } else {
0487             size_t needed = snprintf(NULL, 0, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
0488             needed += 1;
0489             fullSocket = malloc(needed);
0490             snprintf(fullSocket, needed, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
0491         }
0492     }
0493 
0494     int result = set_env(pamh, envVar, fullSocket);
0495     if (result != PAM_SUCCESS) {
0496         pam_syslog(pamh, LOG_ERR, "%s: Impossible to set %s env, %s",
0497                    logPrefix, envVar, pam_strerror(pamh, result));
0498         free(fullSocket);
0499         return;
0500     }
0501 
0502     pid_t pid;
0503     int status;
0504     switch (pid = fork ()) {
0505     case -1:
0506         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
0507         free(fullSocket);
0508         return;
0509 
0510     //Child fork, will contain kwalletd
0511     case 0:
0512         execute_kwallet(pamh, userInfo, toWalletPipe, fullSocket);
0513         /* Should never be reached */
0514         break;
0515 
0516     //Parent
0517     default:
0518         waitpid(pid, &status, 0);
0519         if (status != 0) {
0520             pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
0521             return;
0522         }
0523         break;
0524     };
0525 
0526     free(fullSocket);
0527 
0528     close(toWalletPipe[0]);//Read end of the pipe, we will only use the write
0529     if (better_write(toWalletPipe[1], kwalletKey, KWALLET_PAM_KEYSIZE) < 0) {
0530         pam_syslog(pamh, LOG_ERR, "%s: Impossible to write walletKey to walletPipe", logPrefix);
0531         return;
0532     }
0533 
0534     close(toWalletPipe[1]);
0535 }
0536 
0537 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
0538 {
0539     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_open_session\n", logPrefix);
0540 
0541     if (get_env(pamh, envVar) != NULL) {
0542         pam_syslog(pamh, LOG_INFO, "%s: we were already executed", logPrefix);
0543         return PAM_SUCCESS;
0544     }
0545 
0546     parseArguments(argc, argv);
0547 
0548     if (!force_run && !is_graphical_session(pamh)) {
0549         pam_syslog(pamh, LOG_INFO, "%s: not a graphical session, skipping. Use force_run parameter to ignore this.", logPrefix);
0550         return PAM_IGNORE;
0551     }
0552 
0553     int result;
0554     result = pam_set_data(pamh, "sm_open_session", "1", NULL);
0555     if (result != PAM_SUCCESS) {
0556         pam_syslog(pamh, LOG_ERR, "%s: Impossible to store sm_open_session: %s",
0557                    logPrefix, pam_strerror(pamh, result));
0558         return PAM_IGNORE;
0559     }
0560 
0561      //Fetch the user, needed to get user information
0562     const char *username;
0563     result = pam_get_user(pamh, &username, NULL);
0564     if (result != PAM_SUCCESS) {
0565         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get username %s",
0566                    logPrefix, pam_strerror(pamh, result));
0567         return PAM_IGNORE;//Since we are not an essential module, just make pam ignore us
0568     }
0569 
0570     struct passwd *userInfo;
0571     userInfo = getpwnam(username);
0572     if (!userInfo) {
0573         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get user info (passwd) info", logPrefix);
0574         return PAM_IGNORE;
0575     }
0576 
0577     if (userInfo->pw_uid == 0) {
0578         pam_syslog(pamh, LOG_DEBUG, "%s: Refusing to do anything for the root user", logPrefix);
0579         return PAM_IGNORE;
0580     }
0581 
0582     char *password;
0583     result = pam_get_data(pamh, kwalletPamDataKey, (const void **)&password);
0584 
0585     if (result != PAM_SUCCESS) {
0586         pam_syslog(pamh, LOG_INFO, "%s: open_session called without %s", logPrefix, kwalletPamDataKey);
0587         return PAM_SUCCESS;//We will wait for pam_sm_authenticate
0588     }
0589 
0590     char *key = malloc(KWALLET_PAM_KEYSIZE);
0591     if (!key || kwallet_hash(pamh, password, userInfo, key) != 0) {
0592         free(key);
0593         pam_syslog(pamh, LOG_ERR, "%s: Fail into creating the hash", logPrefix);
0594         return PAM_IGNORE;
0595     }
0596 
0597     start_kwallet(pamh, userInfo, key);
0598 
0599     return PAM_SUCCESS;
0600 }
0601 
0602 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
0603 {
0604     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_close_session", logPrefix);
0605     return PAM_SUCCESS;
0606 }
0607 
0608 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
0609 {
0610     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_setcred", logPrefix);
0611     return PAM_SUCCESS;
0612 }
0613 
0614 
0615 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
0616 {
0617     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_chauthtok", logPrefix);
0618     return PAM_SUCCESS;
0619 }
0620 
0621 static int mkpath(char *path)
0622 {
0623     struct stat sb;
0624     char *slash;
0625     int done = 0;
0626 
0627     slash = path;
0628 
0629     while (!done) {
0630         slash += strspn(slash, "/");
0631         slash += strcspn(slash, "/");
0632 
0633         done = (*slash == '\0');
0634         *slash = '\0';
0635 
0636         if (stat(path, &sb)) {
0637             if (errno != ENOENT || (mkdir(path, 0777) &&
0638                 errno != EEXIST)) {
0639                 syslog(LOG_ERR, "%s: Couldn't create directory: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0640                 return (-1);
0641             }
0642         } else if (!S_ISDIR(sb.st_mode)) {
0643             return (-1);
0644         }
0645 
0646         *slash = '/';
0647     }
0648 
0649     return (0);
0650 }
0651 
0652 static void createNewSalt(pam_handle_t *pamh, const char *path, struct passwd *userInfo)
0653 {
0654     const pid_t pid = fork();
0655     if (pid == -1) {
0656         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to create salt file", logPrefix);
0657     } else if (pid == 0) {
0658         // Child process
0659         if (drop_privileges(userInfo) < 0) {
0660             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file creation", logPrefix);
0661             exit(-1);
0662         }
0663 
0664         // Don't re-create it if it already exists
0665         struct stat info;
0666         if (stat(path, &info) == 0 &&
0667             info.st_size != 0 &&
0668             S_ISREG(info.st_mode)) {
0669             exit(0);
0670         }
0671 
0672         unlink(path);//in case the file already exists
0673 
0674         char *dir = strdup(path);
0675         dir[strlen(dir) - 14] = '\0';//remove kdewallet.salt
0676         mkpath(dir); //create the path in case it does not exists
0677         free(dir);
0678 
0679         char *salt = gcry_random_bytes(KWALLET_PAM_SALTSIZE, GCRY_STRONG_RANDOM);
0680         const int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0600);
0681 
0682         //If the file can't be created
0683         if (fd == -1) {
0684             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0685             exit(-2);
0686         }
0687 
0688         const ssize_t wlen = write(fd, salt, KWALLET_PAM_SALTSIZE);
0689         close(fd);
0690         if (wlen != KWALLET_PAM_SALTSIZE) {
0691             syslog(LOG_ERR, "%s: Short write to file: %s", logPrefix, path);
0692             unlink(path);
0693             exit(-2);
0694         }
0695 
0696         exit(0); // success
0697     } else {
0698         // pam process, just wait for child to finish
0699         int status;
0700         waitpid(pid, &status, 0);
0701         if (status != 0) {
0702             pam_syslog(pamh, LOG_ERR, "%s: Couldn't create salt file", logPrefix);
0703         }
0704     }
0705 }
0706 
0707 static int readSaltFile(pam_handle_t *pamh, char *path, struct passwd *userInfo, char *saltOut)
0708 {
0709     int readSaltPipe[2];
0710     if (pipe(readSaltPipe) < 0) {
0711         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create read salt pipes", logPrefix);
0712         return 0;
0713     }
0714 
0715     const pid_t pid = fork();
0716     if (pid == -1) {
0717         syslog(LOG_ERR, "%s: Couldn't fork to read salt file", logPrefix);
0718         close(readSaltPipe[0]);
0719         close(readSaltPipe[1]);
0720         return 0;
0721     } else if (pid == 0) {
0722         // Child process
0723         close(readSaltPipe[0]); // we won't be reading from the pipe
0724         if (drop_privileges(userInfo) < 0) {
0725             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file reading", logPrefix);
0726             free(path);
0727             close(readSaltPipe[1]);
0728             exit(-1);
0729         }
0730 
0731         struct stat info;
0732         if (stat(path, &info) != 0 || info.st_size == 0 || !S_ISREG(info.st_mode)) {
0733             syslog(LOG_ERR, "%s: Failed to ensure %s looks like a salt file", logPrefix, path);
0734             free(path);
0735             close(readSaltPipe[1]);
0736             exit(-1);
0737         }
0738 
0739         const int fd = open(path, O_RDONLY | O_CLOEXEC);
0740         if (fd == -1) {
0741             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0742             free(path);
0743             close(readSaltPipe[1]);
0744             exit(-1);
0745         }
0746         free(path);
0747         char salt[KWALLET_PAM_SALTSIZE] = {};
0748         const ssize_t bytesRead = read(fd, salt, KWALLET_PAM_SALTSIZE);
0749         close(fd);
0750         if (bytesRead != KWALLET_PAM_SALTSIZE) {
0751             syslog(LOG_ERR, "%s: Couldn't read the full salt file contents from file. %d:%d", logPrefix, bytesRead, KWALLET_PAM_SALTSIZE);
0752             exit(-1);
0753         }
0754 
0755         const ssize_t written = better_write(readSaltPipe[1], salt, KWALLET_PAM_SALTSIZE);
0756 
0757         close(readSaltPipe[1]);
0758         if (written != KWALLET_PAM_SALTSIZE) {
0759             syslog(LOG_ERR, "%s: Couldn't write the full salt file contents to pipe", logPrefix);
0760             exit(-1);
0761         }
0762 
0763         exit(0);
0764     }
0765 
0766     close(readSaltPipe[1]); // we won't be writing from the pipe
0767 
0768     // pam process, just wait for child to finish
0769     int status;
0770     waitpid(pid, &status, 0);
0771     int success = 1;
0772     if (status == 0) {
0773         const ssize_t readBytes = read(readSaltPipe[0], saltOut, KWALLET_PAM_SALTSIZE);
0774         if (readBytes != KWALLET_PAM_SALTSIZE) {
0775             pam_syslog(pamh, LOG_ERR, "%s: Couldn't read the full salt file contents from pipe", logPrefix);
0776             success = 0;
0777         }
0778     } else {
0779         pam_syslog(pamh, LOG_ERR, "%s: Couldn't read salt file", logPrefix);
0780         success = 0;
0781     }
0782 
0783     close(readSaltPipe[0]);
0784 
0785     return success;
0786 }
0787 
0788 int kwallet_hash(pam_handle_t *pamh, const char *passphrase, struct passwd *userInfo, char *key)
0789 {
0790     if (!gcry_check_version("1.5.0")) {
0791         syslog(LOG_ERR, "%s-kwalletd: libcrypt version is too old", logPrefix);
0792         return 1;
0793     }
0794 
0795     struct stat info;
0796     if (stat(userInfo->pw_dir, &info) != 0 || !S_ISDIR(info.st_mode)) {
0797         syslog(LOG_ERR, "%s-kwalletd: user home folder does not exist", logPrefix);
0798         return 1;
0799     }
0800 
0801     const char *fixpath = "kwalletd/kdewallet.salt";
0802     size_t pathSize = strlen(userInfo->pw_dir) + strlen(kdehome) + strlen(fixpath) + 3;//3 == /, / and \0
0803     char *path = (char*) malloc(pathSize);
0804     sprintf(path, "%s/%s/%s", userInfo->pw_dir, kdehome, fixpath);
0805 
0806     createNewSalt(pamh, path, userInfo);
0807 
0808     char salt[KWALLET_PAM_SALTSIZE] = {};
0809     const int readSaltSuccess = readSaltFile(pamh, path, userInfo, salt);
0810     free(path);
0811     if (!readSaltSuccess) {
0812         syslog(LOG_ERR, "%s-kwalletd: Couldn't create or read the salt file", logPrefix);
0813         return 1;
0814     }
0815 
0816     gcry_error_t error;
0817 
0818     /* We cannot call GCRYCTL_INIT_SECMEM as it drops privileges if getuid() != geteuid().
0819      * PAM modules are in many cases executed through setuid binaries, which this call
0820      * would break.
0821      * It was never effective anyway as neither key nor passphrase are in secure memory,
0822      * which is a prerequisite for secure operation...
0823     error = gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0);
0824     if (error != 0) {
0825         free(salt);
0826         syslog(LOG_ERR, "%s-kwalletd: Can't get secure memory: %d", logPrefix, error);
0827         return 1;
0828     }
0829     */
0830 
0831     gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
0832 
0833     error = gcry_kdf_derive(passphrase, strlen(passphrase),
0834                             GCRY_KDF_PBKDF2, GCRY_MD_SHA512,
0835                             salt, KWALLET_PAM_SALTSIZE,
0836                             KWALLET_PAM_ITERATIONS,KWALLET_PAM_KEYSIZE, key);
0837 
0838     return (int) error; // gcry_kdf_derive returns 0 on success
0839 }