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