1 /*
2     SPDX-FileCopyrightText: 2014 Alejandro Fiestas Olivares <afiestas@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6 
7 #include <fcntl.h>
8 #include <gcrypt.h>
9 #include <stdio.h>
10 #include <signal.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <grp.h>
17 
18 #define PAM_SM_PASSWORD
19 #define PAM_SM_SESSION
20 #define PAM_SM_AUTH
21 #include <pwd.h>
22 #include <sys/stat.h>
23 #include <sys/syslog.h>
24 #include <sys/wait.h>
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <sys/un.h>
28 
29 /* PAM headers.
30  *
31  * There are three styles in play:
32  *  - Apple, which has no pam_ext.h, does have pam_appl.h, does have pam_syslog
33  *  - Linux, which has pam_ext.h, does have pam_appl.h, does have pam_syslog
34  *  - BSD, which has no pam_ext.h, does have pam_appl.h, but no pam_syslog
35  * In the latter case, #define pam_syslog away.
36  */
37 #ifdef __APPLE__
38 #include "pam_darwin.h"
39 #include <security/pam_appl.h>
40 #else
41 #include <security/pam_modules.h>
42 #ifdef HAVE_PAM_EXT
43 /* "Linux style" */
44 #include <security/pam_ext.h>
45 #include <security/_pam_types.h>
46 #endif
47 #ifdef HAVE_PAM_APPL
48 /* "BSD style" .. see also __APPLE__, above */
49 #include <security/pam_appl.h>
50 #ifndef HAVE_PAM_EXT
51 /* FreeBSD has no pam_syslog(), va-macro it away */
52 #define pam_syslog(...)
53 #endif
54 #endif
55 #endif
56 
57 #define KWALLET_PAM_KEYSIZE 56
58 #define KWALLET_PAM_SALTSIZE 56
59 #define KWALLET_PAM_ITERATIONS 50000
60 
61 // Parameters
62 const static char *kdehome = NULL;
63 const static char *kwalletd = NULL;
64 const static char *socketPath = NULL;
65 static int force_run = 0;
66 
67 const static char * const kwalletPamDataKey = "kwallet5_key";
68 const static char * const logPrefix = "pam_kwallet5";
69 const static char * const envVar = "PAM_KWALLET5_LOGIN";
70 
71 static int argumentsParsed = -1;
72 
73 int kwallet_hash(pam_handle_t *pamh, const char *passphrase, struct passwd *userInfo, char *key);
74 
parseArguments(int argc,const char ** argv)75 static void parseArguments(int argc, const char **argv)
76 {
77     //If already parsed
78     if (argumentsParsed != -1) {
79         return;
80     }
81 
82     int x = 0;
83     for (;x < argc; ++x) {
84         if (strstr(argv[x], "kdehome=") != NULL) {
85             kdehome = argv[x] + 8;
86         } else if (strstr(argv[x], "kwalletd=") != NULL) {
87             kwalletd = argv[x] + 9;
88         } else if (strstr(argv[x], "socketPath=") != NULL) {
89             socketPath= argv[x] + 11;
90         } else if (strcmp(argv[x], "force_run") == 0) {
91             force_run = 1;
92         }
93     }
94     if (kdehome == NULL) {
95         kdehome = ".local/share";
96     }
97     if (kwalletd == NULL) {
98         kwalletd = KWALLETD_BIN_PATH;
99     }
100 }
101 
get_env(pam_handle_t * ph,const char * name)102 static const char* get_env(pam_handle_t *ph, const char *name)
103 {
104     const char *env = pam_getenv (ph, name);
105     if (env && env[0]) {
106         return env;
107     }
108 
109     env = getenv (name);
110     if (env && env[0]) {
111         return env;
112     }
113 
114     return NULL;
115 }
116 
set_env(pam_handle_t * pamh,const char * name,const char * value)117 static int set_env(pam_handle_t *pamh, const char *name, const char *value)
118 {
119     if (setenv(name, value, 1) < 0) {
120         pam_syslog(pamh, LOG_WARNING, "%s: Couldn't setenv %s = %s", logPrefix, name, value);
121         //We do not return because pam_putenv might work
122     }
123 
124     size_t pamEnvSize = strlen(name) + strlen(value) + 2; //2 is for = and \0
125     char *pamEnv = malloc(pamEnvSize);
126     if (!pamEnv) {
127         pam_syslog(pamh, LOG_WARNING, "%s: Impossible to allocate memory for pamEnv", logPrefix);
128         return -1;
129     }
130 
131     snprintf (pamEnv, pamEnvSize, "%s=%s", name, value);
132     int ret = pam_putenv(pamh, pamEnv);
133     free(pamEnv);
134 
135     return ret;
136 }
137 
138 /**
139  * Code copied from gkr-pam-module.c, GPL2+
140  */
wipeString(char * str)141 static void wipeString(char *str)
142 {
143     if (!str) {
144         return;
145     }
146 
147     const size_t len = strlen (str);
148 #if HAVE_EXPLICIT_BZERO
149     explicit_bzero(str, len);
150 #else
151     volatile char *vp;
152 
153     /* Defeats some optimizations */
154     memset (str, 0xAA, len);
155     memset (str, 0xBB, len);
156 
157     /* Defeats others */
158     vp = (volatile char*)str;
159     while (*vp) {
160         *(vp++) = 0xAA;
161     }
162 #endif
163 
164     free (str);
165 }
166 
prompt_for_password(pam_handle_t * pamh)167 static int prompt_for_password(pam_handle_t *pamh)
168 {
169     int result;
170 
171     //Get the function we have to call
172     const struct pam_conv *conv;
173     result = pam_get_item(pamh, PAM_CONV, (const void**)&conv);
174     if (result != PAM_SUCCESS) {
175         return result;
176     }
177 
178     //prepare the message
179     struct pam_message message;
180     memset (&message, 0, sizeof(message));
181     message.msg_style = PAM_PROMPT_ECHO_OFF;
182     message.msg = "Password: ";
183 
184     //We only need one message, but we still have to send it in an array
185     const struct pam_message *msgs[1];
186     msgs[0] = &message;
187 
188 
189     //Sending the message, asking for password
190     struct pam_response *response = NULL;
191     memset (&response, 0, sizeof(response));
192     result = (conv->conv) (1, msgs, &response, conv->appdata_ptr);
193     if (result != PAM_SUCCESS) {
194         goto cleanup;
195     }
196 
197     //If we got no password, just return;
198     if (response[0].resp == NULL) {
199         result = PAM_CONV_ERR;
200         goto cleanup;
201     }
202 
203     //Set the password in PAM memory
204     char *password = response[0].resp;
205     result = pam_set_item(pamh, PAM_AUTHTOK, password);
206     wipeString(password);
207 
208     if (result != PAM_SUCCESS) {
209         goto cleanup;
210     }
211 
212 cleanup:
213     free(response);
214     return result;
215 }
216 
cleanup_free(pam_handle_t * pamh,void * ptr,int error_status)217 static void cleanup_free(pam_handle_t *pamh, void *ptr, int error_status)
218 {
219     free(ptr);
220 }
221 
is_graphical_session(pam_handle_t * pamh)222 static int is_graphical_session(pam_handle_t *pamh)
223 {
224     //Detect a graphical session
225     const char *pam_tty = NULL, *pam_xdisplay = NULL,
226                *xdg_session_type = NULL;
227 
228     pam_get_item(pamh, PAM_TTY, (const void**) &pam_tty);
229 #ifdef PAM_XDISPLAY
230     pam_get_item(pamh, PAM_XDISPLAY, (const void**) &pam_xdisplay);
231 #endif
232     xdg_session_type = get_env(pamh, "XDG_SESSION_TYPE");
233 
234     return (pam_xdisplay && strlen(pam_xdisplay) != 0)
235            || (pam_tty && pam_tty[0] == ':')
236            || (xdg_session_type && strcmp(xdg_session_type, "x11") == 0)
237            || (xdg_session_type && strcmp(xdg_session_type, "wayland") == 0);
238 }
239 
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)240 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
241 {
242     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_authenticate\n", logPrefix);
243     if (get_env(pamh, envVar) != NULL) {
244         pam_syslog(pamh, LOG_INFO, "%s: we were already executed", logPrefix);
245         return PAM_IGNORE;
246     }
247 
248     parseArguments(argc, argv);
249 
250     int result;
251 
252     //Fetch the user, needed to get user information
253     const char *username;
254     result = pam_get_user(pamh, &username, NULL);
255     if (result != PAM_SUCCESS) {
256         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get username %s",
257                    logPrefix, pam_strerror(pamh, result));
258         return PAM_IGNORE;//Since we are not an essential module, just make pam ignore us
259     }
260 
261     struct passwd *userInfo;
262     userInfo = getpwnam(username);
263     if (!userInfo) {
264         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get user info (passwd) info", logPrefix);
265         return PAM_IGNORE;
266     }
267 
268     const char *password;
269     result = pam_get_item(pamh, PAM_AUTHTOK, (const void**)&password);
270 
271     if (result != PAM_SUCCESS) {
272         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get password %s", logPrefix,
273                    pam_strerror(pamh, result));
274         return PAM_IGNORE;
275     }
276 
277     if (!password) {
278         pam_syslog(pamh, LOG_NOTICE, "%s: Couldn't get password (it is empty)", logPrefix);
279         //Asking for the password ourselves
280         result = prompt_for_password(pamh);
281         if (result != PAM_SUCCESS) {
282             pam_syslog(pamh, LOG_ERR, "%s: Prompt for password failed %s",
283                        logPrefix, pam_strerror(pamh, result)
284             );
285             return PAM_IGNORE;
286         }
287     }
288 
289     //even though we just set it, better check to be 100% sure
290     result = pam_get_item(pamh, PAM_AUTHTOK, (const void**)&password);
291     if (result != PAM_SUCCESS || !password) {
292         pam_syslog(pamh, LOG_ERR, "%s: Password is not there even though we set it %s", logPrefix,
293                    pam_strerror(pamh, result));
294         return PAM_IGNORE;
295     }
296 
297     char *key = strdup(password);
298     result = pam_set_data(pamh, kwalletPamDataKey, key, cleanup_free);
299 
300     if (result != PAM_SUCCESS) {
301         free(key);
302         pam_syslog(pamh, LOG_ERR, "%s: Impossible to store the password: %s", logPrefix
303             , pam_strerror(pamh, result));
304         return PAM_IGNORE;
305     }
306 
307     //if sm_open_session has already been called (but we did not have password), call it now
308     const char *session_bit;
309     result = pam_get_data(pamh, "sm_open_session", (const void **)&session_bit);
310     if (result == PAM_SUCCESS) {
311         pam_syslog(pamh, LOG_ERR, "%s: open_session was called before us, calling it now", logPrefix);
312         return pam_sm_open_session(pamh, flags, argc, argv);
313     }
314 
315     //TODO unlock kwallet that is already executed
316     return PAM_IGNORE;
317 }
318 
drop_privileges(struct passwd * userInfo)319 static int drop_privileges(struct passwd *userInfo)
320 {
321     /* When dropping privileges from root, the `setgroups` call will
322     * remove any extraneous groups. If we don't call this, then
323     * even though our uid has dropped, we may still have groups
324     * that enable us to do super-user things. This will fail if we
325     * aren't root, so don't bother checking the return value, this
326     * is just done as an optimistic privilege dropping function.
327     */
328     setgroups(0, NULL);
329 
330     //Change to the user in case we are not it yet
331     if (setgid (userInfo->pw_gid) < 0 || setuid (userInfo->pw_uid) < 0 ||
332         setegid (userInfo->pw_gid) < 0 || seteuid (userInfo->pw_uid) < 0) {
333         return -1;
334     }
335 
336     return 0;
337 }
338 
execute_kwallet(pam_handle_t * pamh,struct passwd * userInfo,int toWalletPipe[2],char * fullSocket)339 static void execute_kwallet(pam_handle_t *pamh, struct passwd *userInfo, int toWalletPipe[2], char *fullSocket)
340 {
341     //In the child pam_syslog does not work, using syslog directly
342 
343     //keep stderr open so socket doesn't returns us that fd
344     int x = 3;
345     //Set FD_CLOEXEC on fd that are not of interest of kwallet
346     for (; x < 64; ++x) {
347         if (x != toWalletPipe[0]) {
348             fcntl(x, F_SETFD, FD_CLOEXEC);
349         }
350     }
351 
352     //This is the side of the pipe PAM will send the hash to
353     close (toWalletPipe[1]);
354 
355     //Change to the user in case we are not it yet
356     if (drop_privileges(userInfo) < 0) {
357         syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for kwalletd", logPrefix);
358         goto cleanup;
359     }
360 
361     int envSocket;
362     if ((envSocket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
363         syslog(LOG_ERR, "%s: couldn't create socket", logPrefix);
364         goto cleanup;
365     }
366 
367     struct sockaddr_un local = {};
368     local.sun_family = AF_UNIX;
369 
370     if (strlen(fullSocket) >= sizeof(local.sun_path)) {
371         syslog(LOG_ERR, "%s: socket path %s too long to open",
372                    logPrefix, fullSocket);
373         free(fullSocket);
374         goto cleanup;
375     }
376     strcpy(local.sun_path, fullSocket);
377     unlink(local.sun_path);//Just in case it exists from a previous login
378 
379     syslog(LOG_DEBUG, "%s: final socket path: %s", logPrefix, local.sun_path);
380 
381     if (bind(envSocket, (struct sockaddr *)&local, sizeof(local)) == -1) {
382         syslog(LOG_INFO, "%s-kwalletd: Couldn't bind to local file\n", logPrefix);
383         goto cleanup;
384     }
385 
386     if (listen(envSocket, 5) == -1) {
387         syslog(LOG_INFO, "%s-kwalletd: Couldn't listen in socket: %d-%s\n", logPrefix, errno, strerror(errno));
388         goto cleanup;
389     }
390     //finally close stderr
391     close(2);
392 
393     // Fork twice to daemonize kwallet
394     setsid();
395     pid_t pid = fork();
396     if (pid != 0) {
397         if (pid == -1) {
398             exit(EXIT_FAILURE);
399         } else {
400             exit(0);
401         }
402     }
403 
404     //TODO use a pam argument for full path kwalletd
405     char pipeInt[4];
406     sprintf(pipeInt, "%d", toWalletPipe[0]);
407     char sockIn[4];
408     sprintf(sockIn, "%d", envSocket);
409 
410     char *args[] = {strdup(kwalletd), "--pam-login", pipeInt, sockIn, NULL, NULL};
411     execve(args[0], args, pam_getenvlist(pamh));
412     syslog(LOG_ERR, "%s: could not execute kwalletd from %s", logPrefix, kwalletd);
413 
414 cleanup:
415     exit(EXIT_FAILURE);
416 }
417 
better_write(int fd,const char * buffer,int len)418 static int better_write(int fd, const char *buffer, int len)
419 {
420     size_t writtenBytes = 0;
421     while(writtenBytes < len) {
422         ssize_t result = write(fd, buffer + writtenBytes, len - writtenBytes);
423         if (result < 0) {
424             if (errno != EAGAIN && errno != EINTR) {
425                 return -1;
426             }
427         }
428         writtenBytes += result;
429     }
430 
431     return writtenBytes;
432 }
433 
start_kwallet(pam_handle_t * pamh,struct passwd * userInfo,const char * kwalletKey)434 static void start_kwallet(pam_handle_t *pamh, struct passwd *userInfo, const char *kwalletKey)
435 {
436     //Just in case we get broken pipe, do not break the pam process..
437     struct sigaction sigPipe, oldSigPipe;
438     memset (&sigPipe, 0, sizeof (sigPipe));
439     memset (&oldSigPipe, 0, sizeof (oldSigPipe));
440     sigPipe.sa_handler = SIG_IGN;
441     sigaction (SIGPIPE, &sigPipe, &oldSigPipe);
442 
443     int toWalletPipe[2] = { -1, -1};
444     if (pipe(toWalletPipe) < 0) {
445         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create pipes", logPrefix);
446     }
447 
448     const char *socketPrefix = "kwallet5";
449 
450     char *fullSocket = NULL;
451     if (socketPath) {
452         size_t needed = snprintf(NULL, 0, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
453         needed += 1;
454         fullSocket = malloc(needed);
455         snprintf(fullSocket, needed, "%s/%s_%s%s", socketPath, socketPrefix, userInfo->pw_name, ".socket");
456     } else {
457         socketPath = get_env(pamh, "XDG_RUNTIME_DIR");
458         if (socketPath) {
459             size_t needed = snprintf(NULL, 0, "%s/%s%s", socketPath, socketPrefix, ".socket");
460             needed += 1;
461             fullSocket = malloc(needed);
462             snprintf(fullSocket, needed, "%s/%s%s", socketPath, socketPrefix, ".socket");
463         } else {
464             size_t needed = snprintf(NULL, 0, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
465             needed += 1;
466             fullSocket = malloc(needed);
467             snprintf(fullSocket, needed, "/tmp/%s_%s%s", socketPrefix, userInfo->pw_name, ".socket");
468         }
469     }
470 
471     int result = set_env(pamh, envVar, fullSocket);
472     if (result != PAM_SUCCESS) {
473         pam_syslog(pamh, LOG_ERR, "%s: Impossible to set %s env, %s",
474                    logPrefix, envVar, pam_strerror(pamh, result));
475         free(fullSocket);
476         return;
477     }
478 
479     pid_t pid;
480     int status;
481     switch (pid = fork ()) {
482     case -1:
483         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
484         free(fullSocket);
485         return;
486 
487     //Child fork, will contain kwalletd
488     case 0:
489         execute_kwallet(pamh, userInfo, toWalletPipe, fullSocket);
490         /* Should never be reached */
491         break;
492 
493     //Parent
494     default:
495         waitpid(pid, &status, 0);
496         if (status != 0) {
497             pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to execv kwalletd", logPrefix);
498             return;
499         }
500         break;
501     };
502 
503     free(fullSocket);
504 
505     close(toWalletPipe[0]);//Read end of the pipe, we will only use the write
506     if (better_write(toWalletPipe[1], kwalletKey, KWALLET_PAM_KEYSIZE) < 0) {
507         pam_syslog(pamh, LOG_ERR, "%s: Impossible to write walletKey to walletPipe", logPrefix);
508         return;
509     }
510 
511     close(toWalletPipe[1]);
512 }
513 
pam_sm_open_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)514 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
515 {
516     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_open_session\n", logPrefix);
517 
518     if (get_env(pamh, envVar) != NULL) {
519         pam_syslog(pamh, LOG_INFO, "%s: we were already executed", logPrefix);
520         return PAM_SUCCESS;
521     }
522 
523     parseArguments(argc, argv);
524 
525     if (!force_run && !is_graphical_session(pamh)) {
526         pam_syslog(pamh, LOG_INFO, "%s: not a graphical session, skipping. Use force_run parameter to ignore this.", logPrefix);
527         return PAM_IGNORE;
528     }
529 
530     int result;
531     result = pam_set_data(pamh, "sm_open_session", "1", NULL);
532     if (result != PAM_SUCCESS) {
533         pam_syslog(pamh, LOG_ERR, "%s: Impossible to store sm_open_session: %s",
534                    logPrefix, pam_strerror(pamh, result));
535         return PAM_IGNORE;
536     }
537 
538      //Fetch the user, needed to get user information
539     const char *username;
540     result = pam_get_user(pamh, &username, NULL);
541     if (result != PAM_SUCCESS) {
542         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get username %s",
543                    logPrefix, pam_strerror(pamh, result));
544         return PAM_IGNORE;//Since we are not an essential module, just make pam ignore us
545     }
546 
547     struct passwd *userInfo;
548     userInfo = getpwnam(username);
549     if (!userInfo) {
550         pam_syslog(pamh, LOG_ERR, "%s: Couldn't get user info (passwd) info", logPrefix);
551         return PAM_IGNORE;
552     }
553 
554     char *password;
555     result = pam_get_data(pamh, kwalletPamDataKey, (const void **)&password);
556 
557     if (result != PAM_SUCCESS) {
558         pam_syslog(pamh, LOG_INFO, "%s: open_session called without %s", logPrefix, kwalletPamDataKey);
559         return PAM_SUCCESS;//We will wait for pam_sm_authenticate
560     }
561 
562     char *key = malloc(KWALLET_PAM_KEYSIZE);
563     if (!key || kwallet_hash(pamh, password, userInfo, key) != 0) {
564         free(key);
565         pam_syslog(pamh, LOG_ERR, "%s: Fail into creating the hash", logPrefix);
566         return PAM_IGNORE;
567     }
568 
569     start_kwallet(pamh, userInfo, key);
570 
571     return PAM_SUCCESS;
572 }
573 
pam_sm_close_session(pam_handle_t * pamh,int flags,int argc,const char ** argv)574 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
575 {
576     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_close_session", logPrefix);
577     return PAM_SUCCESS;
578 }
579 
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)580 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
581 {
582     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_setcred", logPrefix);
583     return PAM_SUCCESS;
584 }
585 
586 
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)587 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
588 {
589     pam_syslog(pamh, LOG_DEBUG, "%s: pam_sm_chauthtok", logPrefix);
590     return PAM_SUCCESS;
591 }
592 
mkpath(char * path)593 static int mkpath(char *path)
594 {
595     struct stat sb;
596     char *slash;
597     int done = 0;
598 
599     slash = path;
600 
601     while (!done) {
602         slash += strspn(slash, "/");
603         slash += strcspn(slash, "/");
604 
605         done = (*slash == '\0');
606         *slash = '\0';
607 
608         if (stat(path, &sb)) {
609             if (errno != ENOENT || (mkdir(path, 0777) &&
610                 errno != EEXIST)) {
611                 syslog(LOG_ERR, "%s: Couldn't create directory: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
612                 return (-1);
613             }
614         } else if (!S_ISDIR(sb.st_mode)) {
615             return (-1);
616         }
617 
618         *slash = '/';
619     }
620 
621     return (0);
622 }
623 
createNewSalt(pam_handle_t * pamh,const char * path,struct passwd * userInfo)624 static void createNewSalt(pam_handle_t *pamh, const char *path, struct passwd *userInfo)
625 {
626     const pid_t pid = fork();
627     if (pid == -1) {
628         pam_syslog(pamh, LOG_ERR, "%s: Couldn't fork to create salt file", logPrefix);
629     } else if (pid == 0) {
630         // Child process
631         if (drop_privileges(userInfo) < 0) {
632             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file creation", logPrefix);
633             exit(-1);
634         }
635 
636         // Don't re-create it if it already exists
637         struct stat info;
638         if (stat(path, &info) == 0 &&
639             info.st_size != 0 &&
640             S_ISREG(info.st_mode)) {
641             exit(0);
642         }
643 
644         unlink(path);//in case the file already exists
645 
646         char *dir = strdup(path);
647         dir[strlen(dir) - 14] = '\0';//remove kdewallet.salt
648         mkpath(dir); //create the path in case it does not exists
649         free(dir);
650 
651         char *salt = gcry_random_bytes(KWALLET_PAM_SALTSIZE, GCRY_STRONG_RANDOM);
652         const int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0600);
653 
654         //If the file can't be created
655         if (fd == -1) {
656             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
657             exit(-2);
658         }
659 
660         const ssize_t wlen = write(fd, salt, KWALLET_PAM_SALTSIZE);
661         close(fd);
662         if (wlen != KWALLET_PAM_SALTSIZE) {
663             syslog(LOG_ERR, "%s: Short write to file: %s", logPrefix, path);
664             unlink(path);
665             exit(-2);
666         }
667 
668         exit(0); // success
669     } else {
670         // pam process, just wait for child to finish
671         int status;
672         waitpid(pid, &status, 0);
673         if (status != 0) {
674             pam_syslog(pamh, LOG_ERR, "%s: Couldn't create salt file", logPrefix);
675         }
676     }
677 }
678 
readSaltFile(pam_handle_t * pamh,char * path,struct passwd * userInfo,char * saltOut)679 static int readSaltFile(pam_handle_t *pamh, char *path, struct passwd *userInfo, char *saltOut)
680 {
681     int readSaltPipe[2];
682     if (pipe(readSaltPipe) < 0) {
683         pam_syslog(pamh, LOG_ERR, "%s: Couldn't create read salt pipes", logPrefix);
684         return 0;
685     }
686 
687     const pid_t pid = fork();
688     if (pid == -1) {
689         syslog(LOG_ERR, "%s: Couldn't fork to read salt file", logPrefix);
690         close(readSaltPipe[0]);
691         close(readSaltPipe[1]);
692         return 0;
693     } else if (pid == 0) {
694         // Child process
695         close(readSaltPipe[0]); // we won't be reading from the pipe
696         if (drop_privileges(userInfo) < 0) {
697             syslog(LOG_ERR, "%s: could not set gid/uid/euid/egit for salt file reading", logPrefix);
698             free(path);
699             close(readSaltPipe[1]);
700             exit(-1);
701         }
702 
703         struct stat info;
704         if (stat(path, &info) != 0 || info.st_size == 0 || !S_ISREG(info.st_mode)) {
705             syslog(LOG_ERR, "%s: Failed to ensure %s looks like a salt file", logPrefix, path);
706             free(path);
707             close(readSaltPipe[1]);
708             exit(-1);
709         }
710 
711         const int fd = open(path, O_RDONLY | O_CLOEXEC);
712         if (fd == -1) {
713             syslog(LOG_ERR, "%s: Couldn't open file: %s because: %d-%s", logPrefix, path, errno, strerror(errno));
714             free(path);
715             close(readSaltPipe[1]);
716             exit(-1);
717         }
718         free(path);
719         char salt[KWALLET_PAM_SALTSIZE] = {};
720         const ssize_t bytesRead = read(fd, salt, KWALLET_PAM_SALTSIZE);
721         close(fd);
722         if (bytesRead != KWALLET_PAM_SALTSIZE) {
723             syslog(LOG_ERR, "%s: Couldn't read the full salt file contents from file. %d:%d", logPrefix, bytesRead, KWALLET_PAM_SALTSIZE);
724             exit(-1);
725         }
726 
727         const ssize_t written = better_write(readSaltPipe[1], salt, KWALLET_PAM_SALTSIZE);
728 
729         close(readSaltPipe[1]);
730         if (written != KWALLET_PAM_SALTSIZE) {
731             syslog(LOG_ERR, "%s: Couldn't write the full salt file contents to pipe", logPrefix);
732             exit(-1);
733         }
734 
735         exit(0);
736     }
737 
738     close(readSaltPipe[1]); // we won't be writing from the pipe
739 
740     // pam process, just wait for child to finish
741     int status;
742     waitpid(pid, &status, 0);
743     int success = 1;
744     if (status == 0) {
745         const ssize_t readBytes = read(readSaltPipe[0], saltOut, KWALLET_PAM_SALTSIZE);
746         if (readBytes != KWALLET_PAM_SALTSIZE) {
747             pam_syslog(pamh, LOG_ERR, "%s: Couldn't read the full salt file contents from pipe", logPrefix);
748             success = 0;
749         }
750     } else {
751         pam_syslog(pamh, LOG_ERR, "%s: Couldn't read salt file", logPrefix);
752         success = 0;
753     }
754 
755     close(readSaltPipe[0]);
756 
757     return success;
758 }
759 
kwallet_hash(pam_handle_t * pamh,const char * passphrase,struct passwd * userInfo,char * key)760 int kwallet_hash(pam_handle_t *pamh, const char *passphrase, struct passwd *userInfo, char *key)
761 {
762     if (!gcry_check_version("1.5.0")) {
763         syslog(LOG_ERR, "%s-kwalletd: libcrypt version is too old", logPrefix);
764         return 1;
765     }
766 
767     struct stat info;
768     if (stat(userInfo->pw_dir, &info) != 0 || !S_ISDIR(info.st_mode)) {
769         syslog(LOG_ERR, "%s-kwalletd: user home folder does not exist", logPrefix);
770         return 1;
771     }
772 
773     const char *fixpath = "kwalletd/kdewallet.salt";
774     size_t pathSize = strlen(userInfo->pw_dir) + strlen(kdehome) + strlen(fixpath) + 3;//3 == /, / and \0
775     char *path = (char*) malloc(pathSize);
776     sprintf(path, "%s/%s/%s", userInfo->pw_dir, kdehome, fixpath);
777 
778     createNewSalt(pamh, path, userInfo);
779 
780     char salt[KWALLET_PAM_SALTSIZE] = {};
781     const int readSaltSuccess = readSaltFile(pamh, path, userInfo, salt);
782     free(path);
783     if (!readSaltSuccess) {
784         syslog(LOG_ERR, "%s-kwalletd: Couldn't create or read the salt file", logPrefix);
785         return 1;
786     }
787 
788     gcry_error_t error;
789 
790     /* We cannot call GCRYCTL_INIT_SECMEM as it drops privileges if getuid() != geteuid().
791      * PAM modules are in many cases executed through setuid binaries, which this call
792      * would break.
793      * It was never effective anyway as neither key nor passphrase are in secure memory,
794      * which is a prerequisite for secure operation...
795     error = gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0);
796     if (error != 0) {
797         free(salt);
798         syslog(LOG_ERR, "%s-kwalletd: Can't get secure memory: %d", logPrefix, error);
799         return 1;
800     }
801     */
802 
803     gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
804 
805     error = gcry_kdf_derive(passphrase, strlen(passphrase),
806                             GCRY_KDF_PBKDF2, GCRY_MD_SHA512,
807                             salt, KWALLET_PAM_SALTSIZE,
808                             KWALLET_PAM_ITERATIONS,KWALLET_PAM_KEYSIZE, key);
809 
810     return (int) error; // gcry_kdf_derive returns 0 on success
811 }
812