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