File indexing completed on 2024-04-14 05:21:25

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     //TODO unlock kwallet that is already executed
0318     return PAM_IGNORE;
0319 }
0320 
0321 static int drop_privileges(struct passwd *userInfo)
0322 {
0323     /* When dropping privileges from root, the `setgroups` call will
0324     * remove any extraneous groups. If we don't call this, then
0325     * even though our uid has dropped, we may still have groups
0326     * that enable us to do super-user things. This will fail if we
0327     * aren't root, so don't bother checking the return value, this
0328     * is just done as an optimistic privilege dropping function.
0329     */
0330     setgroups(0, NULL);
0331 
0332     //Change to the user in case we are not it yet
0333     if (setgid (userInfo->pw_gid) < 0 || setuid (userInfo->pw_uid) < 0 ||
0334         setegid (userInfo->pw_gid) < 0 || seteuid (userInfo->pw_uid) < 0) {
0335         return -1;
0336     }
0337 
0338     return 0;
0339 }
0340 
0341 static void execute_kwallet(pam_handle_t *pamh, struct passwd *userInfo, int toWalletPipe[2], char *fullSocket)
0342 {
0343     //In the child pam_syslog does not work, using syslog directly
0344 
0345     //keep stderr open so socket doesn't returns us that fd
0346     int x = 3;
0347     //Set FD_CLOEXEC on fd that are not of interest of kwallet
0348     for (; x < 64; ++x) {
0349         if (x != toWalletPipe[0]) {
0350             fcntl(x, F_SETFD, FD_CLOEXEC);
0351         }
0352     }
0353 
0354     //This is the side of the pipe PAM will send the hash to
0355     close (toWalletPipe[1]);
0356 
0357     //Change to the user in case we are not it yet
0358     if (drop_privileges(userInfo) < 0) {
0359         syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for kwalletd", logPrefix);
0360         goto cleanup;
0361     }
0362 
0363     int envSocket;
0364     if ((envSocket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
0365         syslog(LOG_ERR, "%s: couldn't create socket", logPrefix);
0366         goto cleanup;
0367     }
0368 
0369     struct sockaddr_un local = {};
0370     local.sun_family = AF_UNIX;
0371 
0372     if (strlen(fullSocket) > sizeof(local.sun_path)) {
0373         syslog(LOG_ERR, "%s: socket path %s too long to open",
0374                    logPrefix, fullSocket);
0375         free(fullSocket);
0376         goto cleanup;
0377     }
0378     strcpy(local.sun_path, fullSocket);
0379     unlink(local.sun_path);//Just in case it exists from a previous login
0380 
0381     syslog(LOG_DEBUG, "%s: final socket path: %s", logPrefix, local.sun_path);
0382 
0383     if (bind(envSocket, (struct sockaddr *)&local, sizeof(local)) == -1) {
0384         syslog(LOG_INFO, "%s-kwalletd: Couldn't bind to local file\n", logPrefix);
0385         goto cleanup;
0386     }
0387 
0388     if (listen(envSocket, 5) == -1) {
0389         syslog(LOG_INFO, "%s-kwalletd: Couldn't listen in socket: %d-%s\n", logPrefix, errno, strerror(errno));
0390         goto cleanup;
0391     }
0392     //finally close stderr
0393     close(2);
0394 
0395     // Fork twice to daemonize kwallet
0396     setsid();
0397     pid_t pid = fork();
0398     if (pid != 0) {
0399         if (pid == -1) {
0400             exit(EXIT_FAILURE);
0401         } else {
0402             exit(0);
0403         }
0404     }
0405 
0406     //TODO use a pam argument for full path kwalletd
0407     char pipeInt[4];
0408     sprintf(pipeInt, "%d", toWalletPipe[0]);
0409     char sockIn[4];
0410     sprintf(sockIn, "%d", envSocket);
0411 
0412     char *args[] = {strdup(kwalletd), "--pam-login", pipeInt, sockIn, NULL, NULL};
0413     execve(args[0], args, pam_getenvlist(pamh));
0414     syslog(LOG_ERR, "%s: could not execute kwalletd from %s", logPrefix, kwalletd);
0415 
0416 cleanup:
0417     exit(EXIT_FAILURE);
0418 }
0419 
0420 static int better_write(int fd, const char *buffer, int len)
0421 {
0422     size_t writtenBytes = 0;
0423     while(writtenBytes < len) {
0424         ssize_t result = write(fd, buffer + writtenBytes, len - writtenBytes);
0425         if (result < 0) {
0426             if (errno != EAGAIN && errno != EINTR) {
0427                 return -1;
0428             }
0429         }
0430         writtenBytes += result;
0431     }
0432 
0433     return writtenBytes;
0434 }
0435 
0436 static void start_kwallet(pam_handle_t *pamh, struct passwd *userInfo, const char *kwalletKey)
0437 {
0438     //Just in case we get broken pipe, do not break the pam process..
0439     struct sigaction sigPipe, oldSigPipe;
0440     memset (&sigPipe, 0, sizeof (sigPipe));
0441     memset (&oldSigPipe, 0, sizeof (oldSigPipe));
0442     sigPipe.sa_handler = SIG_IGN;
0443     sigaction (SIGPIPE, &sigPipe, &oldSigPipe);
0444 
0445     int toWalletPipe[2] = { -1, -1};
0446     if (pipe(toWalletPipe) < 0) {
0447         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create pipes", logPrefix);
0448     }
0449 
0450     const char *socketPrefix = "kwallet5";
0451 
0452     char *fullSocket = NULL;
0453     if (socketPath) {
0454         size_t needed = snprintf(NULL, 0, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
0455         needed += 1;
0456         fullSocket = malloc(needed);
0457         snprintf(fullSocket, needed, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
0458     } else {
0459         socketPath = get_env(pamh, "XDG_RUNTIME_DIR");
0460         // Check whether XDG_RUNTIME_DIR is usable
0461         if (socketPath) {
0462             struct stat rundir_stat;
0463             if (stat(socketPath, &rundir_stat) != 0) {
0464                 pam_syslog(pamh, LOG_ERR, "%s: Failed to stat %s", logPrefix, socketPath);
0465                 socketPath = NULL;
0466             } else if(!S_ISDIR(rundir_stat.st_mode) || (rundir_stat.st_mode & ~S_IFMT) != 0700
0467                       || rundir_stat.st_uid != userInfo->pw_uid) {
0468                 pam_syslog(pamh, LOG_ERR, "%s: %s has wrong type, perms or ownership", logPrefix, socketPath);
0469                 socketPath = NULL;
0470             }
0471         }
0472 
0473         if (socketPath) {
0474             size_t needed = snprintf(NULL, 0, "%s/%s%s", socketPath, socketPrefix, ".socket");
0475             needed += 1;
0476             fullSocket = malloc(needed);
0477             snprintf(fullSocket, needed, "%s/%s%s", socketPath, socketPrefix, ".socket");
0478         } else {
0479             size_t needed = snprintf(NULL, 0, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
0480             needed += 1;
0481             fullSocket = malloc(needed);
0482             snprintf(fullSocket, needed, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
0483         }
0484     }
0485 
0486     int result = set_env(pamh, envVar, fullSocket);
0487     if (result != PAM_SUCCESS) {
0488         pam_syslog(pamh, LOG_ERR, "%s: Impossible to set %s env, %s",
0489                    logPrefix, envVar, pam_strerror(pamh, result));
0490         free(fullSocket);
0491         return;
0492     }
0493 
0494     pid_t pid;
0495     int status;
0496     switch (pid = fork ()) {
0497     case -1:
0498         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
0499         free(fullSocket);
0500         return;
0501 
0502     //Child fork, will contain kwalletd
0503     case 0:
0504         execute_kwallet(pamh, userInfo, toWalletPipe, fullSocket);
0505         /* Should never be reached */
0506         break;
0507 
0508     //Parent
0509     default:
0510         waitpid(pid, &status, 0);
0511         if (status != 0) {
0512             pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
0513             return;
0514         }
0515         break;
0516     };
0517 
0518     free(fullSocket);
0519 
0520     close(toWalletPipe[0]);//Read end of the pipe, we will only use the write
0521     if (better_write(toWalletPipe[1], kwalletKey, KWALLET_PAM_KEYSIZE) < 0) {
0522         pam_syslog(pamh, LOG_ERR, "%s: Impossible to write walletKey to walletPipe", logPrefix);
0523         return;
0524     }
0525 
0526     close(toWalletPipe[1]);
0527 }
0528 
0529 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
0530 {
0531     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_open_session\n", logPrefix);
0532 
0533     if (get_env(pamh, envVar) != NULL) {
0534         pam_syslog(pamh, LOG_INFO, "%s: we were already executed", logPrefix);
0535         return PAM_SUCCESS;
0536     }
0537 
0538     parseArguments(argc, argv);
0539 
0540     if (!force_run && !is_graphical_session(pamh)) {
0541         pam_syslog(pamh, LOG_INFO, "%s: not a graphical session, skipping. Use force_run parameter to ignore this.", logPrefix);
0542         return PAM_IGNORE;
0543     }
0544 
0545     //Fetch the user, needed to get user information
0546     const char *username;
0547     int result = pam_get_user(pamh, &username, NULL);
0548     if (result != PAM_SUCCESS) {
0549         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get username %s",
0550                    logPrefix, pam_strerror(pamh, result));
0551         return PAM_IGNORE;//Since we are not an essential module, just make pam ignore us
0552     }
0553 
0554     struct passwd *userInfo;
0555     userInfo = getpwnam(username);
0556     if (!userInfo) {
0557         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get user info (passwd) info", logPrefix);
0558         return PAM_IGNORE;
0559     }
0560 
0561     if (userInfo->pw_uid == 0) {
0562         pam_syslog(pamh, LOG_DEBUG, "%s: Refusing to do anything for the root user", logPrefix);
0563         return PAM_IGNORE;
0564     }
0565 
0566     char *password;
0567     result = pam_get_data(pamh, kwalletPamDataKey, (const void **)&password);
0568 
0569     if (result != PAM_SUCCESS) {
0570         pam_syslog(pamh, LOG_INFO, "%s: open_session called without %s", logPrefix, kwalletPamDataKey);
0571         return PAM_IGNORE;
0572     }
0573 
0574     char *key = malloc(KWALLET_PAM_KEYSIZE);
0575     if (!key || kwallet_hash(pamh, password, userInfo, key) != 0) {
0576         free(key);
0577         pam_syslog(pamh, LOG_ERR, "%s: Fail into creating the hash", logPrefix);
0578         return PAM_IGNORE;
0579     }
0580 
0581     start_kwallet(pamh, userInfo, key);
0582 
0583     return PAM_SUCCESS;
0584 }
0585 
0586 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
0587 {
0588     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_close_session", logPrefix);
0589     return PAM_SUCCESS;
0590 }
0591 
0592 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
0593 {
0594     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_setcred", logPrefix);
0595     return PAM_SUCCESS;
0596 }
0597 
0598 
0599 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
0600 {
0601     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_chauthtok", logPrefix);
0602     return PAM_SUCCESS;
0603 }
0604 
0605 static int mkpath(char *path)
0606 {
0607     struct stat sb;
0608     char *slash;
0609     int done = 0;
0610 
0611     slash = path;
0612 
0613     while (!done) {
0614         slash += strspn(slash, "/");
0615         slash += strcspn(slash, "/");
0616 
0617         done = (*slash == '\0');
0618         *slash = '\0';
0619 
0620         if (stat(path, &sb)) {
0621             if (errno != ENOENT || (mkdir(path, 0777) &&
0622                 errno != EEXIST)) {
0623                 syslog(LOG_ERR, "%s: Couldn't create directory: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0624                 return (-1);
0625             }
0626         } else if (!S_ISDIR(sb.st_mode)) {
0627             return (-1);
0628         }
0629 
0630         *slash = '/';
0631     }
0632 
0633     return (0);
0634 }
0635 
0636 static void createNewSalt(pam_handle_t *pamh, const char *path, struct passwd *userInfo)
0637 {
0638     const pid_t pid = fork();
0639     if (pid == -1) {
0640         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to create salt file", logPrefix);
0641     } else if (pid == 0) {
0642         // Child process
0643         if (drop_privileges(userInfo) < 0) {
0644             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file creation", logPrefix);
0645             exit(-1);
0646         }
0647 
0648         // Don't re-create it if it already exists
0649         struct stat info;
0650         if (stat(path, &info) == 0 &&
0651             info.st_size != 0 &&
0652             S_ISREG(info.st_mode)) {
0653             exit(0);
0654         }
0655 
0656         unlink(path);//in case the file already exists
0657 
0658         char *dir = strdup(path);
0659         dir[strlen(dir) - 14] = '\0';//remove kdewallet.salt
0660         mkpath(dir); //create the path in case it does not exists
0661         free(dir);
0662 
0663         char *salt = gcry_random_bytes(KWALLET_PAM_SALTSIZE, GCRY_STRONG_RANDOM);
0664         const int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0600);
0665 
0666         //If the file can't be created
0667         if (fd == -1) {
0668             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0669             exit(-2);
0670         }
0671 
0672         const ssize_t wlen = write(fd, salt, KWALLET_PAM_SALTSIZE);
0673         close(fd);
0674         if (wlen != KWALLET_PAM_SALTSIZE) {
0675             syslog(LOG_ERR, "%s: Short write to file: %s", logPrefix, path);
0676             unlink(path);
0677             exit(-2);
0678         }
0679 
0680         exit(0); // success
0681     } else {
0682         // pam process, just wait for child to finish
0683         int status;
0684         waitpid(pid, &status, 0);
0685         if (status != 0) {
0686             pam_syslog(pamh, LOG_ERR, "%s: Couldn't create salt file", logPrefix);
0687         }
0688     }
0689 }
0690 
0691 static int readSaltFile(pam_handle_t *pamh, char *path, struct passwd *userInfo, char *saltOut)
0692 {
0693     int readSaltPipe[2];
0694     if (pipe(readSaltPipe) < 0) {
0695         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create read salt pipes", logPrefix);
0696         return 0;
0697     }
0698 
0699     const pid_t pid = fork();
0700     if (pid == -1) {
0701         syslog(LOG_ERR, "%s: Couldn't fork to read salt file", logPrefix);
0702         close(readSaltPipe[0]);
0703         close(readSaltPipe[1]);
0704         return 0;
0705     } else if (pid == 0) {
0706         // Child process
0707         close(readSaltPipe[0]); // we won't be reading from the pipe
0708         if (drop_privileges(userInfo) < 0) {
0709             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file reading", logPrefix);
0710             free(path);
0711             close(readSaltPipe[1]);
0712             exit(-1);
0713         }
0714 
0715         struct stat info;
0716         if (stat(path, &info) != 0 || info.st_size == 0 || !S_ISREG(info.st_mode)) {
0717             syslog(LOG_ERR, "%s: Failed to ensure %s looks like a salt file", logPrefix, path);
0718             free(path);
0719             close(readSaltPipe[1]);
0720             exit(-1);
0721         }
0722 
0723         const int fd = open(path, O_RDONLY | O_CLOEXEC);
0724         if (fd == -1) {
0725             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
0726             free(path);
0727             close(readSaltPipe[1]);
0728             exit(-1);
0729         }
0730         free(path);
0731         char salt[KWALLET_PAM_SALTSIZE] = {};
0732         const ssize_t bytesRead = read(fd, salt, KWALLET_PAM_SALTSIZE);
0733         close(fd);
0734         if (bytesRead != KWALLET_PAM_SALTSIZE) {
0735             syslog(LOG_ERR, "%s: Couldn't read the full salt file contents from file. %d:%d", logPrefix, bytesRead, KWALLET_PAM_SALTSIZE);
0736             exit(-1);
0737         }
0738 
0739         const ssize_t written = better_write(readSaltPipe[1], salt, KWALLET_PAM_SALTSIZE);
0740 
0741         close(readSaltPipe[1]);
0742         if (written != KWALLET_PAM_SALTSIZE) {
0743             syslog(LOG_ERR, "%s: Couldn't write the full salt file contents to pipe", logPrefix);
0744             exit(-1);
0745         }
0746 
0747         exit(0);
0748     }
0749 
0750     close(readSaltPipe[1]); // we won't be writing from the pipe
0751 
0752     // pam process, just wait for child to finish
0753     int status;
0754     waitpid(pid, &status, 0);
0755     int success = 1;
0756     if (status == 0) {
0757         const ssize_t readBytes = read(readSaltPipe[0], saltOut, KWALLET_PAM_SALTSIZE);
0758         if (readBytes != KWALLET_PAM_SALTSIZE) {
0759             pam_syslog(pamh, LOG_ERR, "%s: Couldn't read the full salt file contents from pipe", logPrefix);
0760             success = 0;
0761         }
0762     } else {
0763         pam_syslog(pamh, LOG_ERR, "%s: Couldn't read salt file", logPrefix);
0764         success = 0;
0765     }
0766 
0767     close(readSaltPipe[0]);
0768 
0769     return success;
0770 }
0771 
0772 int kwallet_hash(pam_handle_t *pamh, const char *passphrase, struct passwd *userInfo, char *key)
0773 {
0774     if (!gcry_check_version("1.5.0")) {
0775         syslog(LOG_ERR, "%s-kwalletd: libcrypt version is too old", logPrefix);
0776         return 1;
0777     }
0778 
0779     struct stat info;
0780     if (stat(userInfo->pw_dir, &info) != 0 || !S_ISDIR(info.st_mode)) {
0781         syslog(LOG_ERR, "%s-kwalletd: user home folder does not exist", logPrefix);
0782         return 1;
0783     }
0784 
0785     const char *fixpath = "kwalletd/kdewallet.salt";
0786     size_t pathSize = strlen(userInfo->pw_dir) + strlen(kdehome) + strlen(fixpath) + 3;//3 == /, / and \0
0787     char *path = (char*) malloc(pathSize);
0788     sprintf(path, "%s/%s/%s", userInfo->pw_dir, kdehome, fixpath);
0789 
0790     createNewSalt(pamh, path, userInfo);
0791 
0792     char salt[KWALLET_PAM_SALTSIZE] = {};
0793     const int readSaltSuccess = readSaltFile(pamh, path, userInfo, salt);
0794     free(path);
0795     if (!readSaltSuccess) {
0796         syslog(LOG_ERR, "%s-kwalletd: Couldn't create or read the salt file", logPrefix);
0797         return 1;
0798     }
0799 
0800     gcry_error_t error;
0801 
0802     /* We cannot call GCRYCTL_INIT_SECMEM as it drops privileges if getuid() != geteuid().
0803      * PAM modules are in many cases executed through setuid binaries, which this call
0804      * would break.
0805      * It was never effective anyway as neither key nor passphrase are in secure memory,
0806      * which is a prerequisite for secure operation...
0807     error = gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0);
0808     if (error != 0) {
0809         free(salt);
0810         syslog(LOG_ERR, "%s-kwalletd: Can't get secure memory: %d", logPrefix, error);
0811         return 1;
0812     }
0813     */
0814 
0815     gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
0816 
0817     error = gcry_kdf_derive(passphrase, strlen(passphrase),
0818                             GCRY_KDF_PBKDF2, GCRY_MD_SHA512,
0819                             salt, KWALLET_PAM_SALTSIZE,
0820                             KWALLET_PAM_ITERATIONS,KWALLET_PAM_KEYSIZE, key);
0821 
0822     return (int) error; // gcry_kdf_derive returns 0 on success
0823 }