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 }