pam_ssh.c revision 93804
1/*- 2 * Copyright (c) 1999, 2000 Andrew J. Korty 3 * All rights reserved. 4 * Copyright (c) 2001 Networks Associates Technology, Inc. 5 * All rights reserved. 6 * 7 * Portions of this software were developed for the FreeBSD Project by 8 * ThinkSec AS and NAI Labs, the Security Research Division of Network 9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 10 * ("CBOSS"), as part of the DARPA CHATS research program. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. The name of the author may not be used to endorse or promote 21 * products derived from this software without specific prior written 22 * permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 * 36 * $Id: pam_ssh.c,v 1.23 2001/08/20 01:44:02 akorty Exp $ 37 */ 38 39#include <sys/cdefs.h> 40__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 93804 2002-04-04 18:45:21Z des $"); 41 42#include <sys/param.h> 43#include <sys/stat.h> 44#include <sys/wait.h> 45 46#include <fcntl.h> 47#include <pwd.h> 48#include <signal.h> 49#include <stdio.h> 50#include <stdlib.h> 51#include <string.h> 52#include <unistd.h> 53 54#define PAM_SM_AUTH 55#define PAM_SM_ACCOUNT 56#define PAM_SM_SESSION 57#define PAM_SM_PASSWORD 58 59#include <security/pam_appl.h> 60#include <security/pam_modules.h> 61#include <security/pam_mod_misc.h> 62 63#include <openssl/dsa.h> 64#include <openssl/evp.h> 65 66#include "key.h" 67#include "authfd.h" 68#include "authfile.h" 69#include "log.h" 70#include "pam_ssh.h" 71 72/* 73 * Generic cleanup function for OpenSSH "Key" type. 74 */ 75 76void 77key_cleanup(pam_handle_t *pamh, void *data, int error_status) 78{ 79 if (data) 80 key_free(data); 81} 82 83 84/* 85 * Generic PAM cleanup function for this module. 86 */ 87 88void 89ssh_cleanup(pam_handle_t *pamh, void *data, int error_status) 90{ 91 if (data) 92 free(data); 93} 94 95 96/* 97 * Authenticate a user's key by trying to decrypt it with the password 98 * provided. The key and its comment are then stored for later 99 * retrieval by the session phase. An increasing index is embedded in 100 * the PAM variable names so this function may be called multiple times 101 * for multiple keys. 102 */ 103 104static int 105auth_via_key(pam_handle_t *pamh, const char *file, const char *dir, 106 const struct passwd *user, const char *pass) 107{ 108 char *comment; /* private key comment */ 109 char *data_name; /* PAM state */ 110 static int index = 0; /* for saved keys */ 111 Key *key; /* user's key */ 112 char *path; /* to key files */ 113 int retval; /* from calls */ 114 uid_t saved_uid; /* caller's uid */ 115 116 /* locate the user's private key file */ 117 118 if (!asprintf(&path, "%s/%s", dir, file)) { 119 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 120 return PAM_SERVICE_ERR; 121 } 122 123 saved_uid = getuid(); 124 125 /* Try to decrypt the private key with the passphrase provided. If 126 success, the user is authenticated. */ 127 128 comment = NULL; 129 (void) setreuid(user->pw_uid, saved_uid); 130 key = key_load_private(path, pass, &comment); 131 (void) setuid(saved_uid); 132 free(path); 133 if (!comment) 134 comment = strdup(file); 135 if (!key) { 136 free(comment); 137 return PAM_AUTH_ERR; 138 } 139 140 /* save the key and comment to pass to ssh-agent in the session 141 phase */ 142 143 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 144 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 145 free(comment); 146 return PAM_SERVICE_ERR; 147 } 148 retval = pam_set_data(pamh, data_name, key, key_cleanup); 149 free(data_name); 150 if (retval != PAM_SUCCESS) { 151 key_free(key); 152 free(comment); 153 return retval; 154 } 155 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 156 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 157 free(comment); 158 return PAM_SERVICE_ERR; 159 } 160 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup); 161 free(data_name); 162 if (retval != PAM_SUCCESS) { 163 free(comment); 164 return retval; 165 } 166 167 ++index; 168 return PAM_SUCCESS; 169} 170 171 172/* 173 * Add the keys stored by auth_via_key() to the agent connected to the 174 * socket provided. 175 */ 176 177static int 178add_keys(pam_handle_t *pamh, char *socket) 179{ 180 AuthenticationConnection *ac; /* connection to ssh-agent */ 181 char *comment; /* private key comment */ 182 char *data_name; /* PAM state */ 183 int final; /* final return value */ 184 int index; /* for saved keys */ 185 Key *key; /* user's private key */ 186 int retval; /* from calls */ 187 188 /* 189 * Connect to the agent. 190 * 191 * XXX Because ssh_get_authentication_connection() gets the 192 * XXX agent parameters from the environment, we have to 193 * XXX temporarily replace the environment with the PAM 194 * XXX environment list. This is a hack. 195 */ 196 { 197 extern char **environ; 198 char **saved, **evp; 199 200 saved = environ; 201 if ((environ = pam_getenvlist(pamh)) == NULL) { 202 environ = saved; 203 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 204 return (PAM_BUF_ERR); 205 } 206 ac = ssh_get_authentication_connection(); 207 for (evp = environ; *evp; evp++) 208 free(*evp); 209 free(environ); 210 environ = saved; 211 } 212 if (!ac) { 213 openpam_log(PAM_LOG_ERROR, "%s: %s: %m", MODULE_NAME, socket); 214 return PAM_SESSION_ERR; 215 } 216 217 /* hand off each private key to the agent */ 218 219 final = 0; 220 for (index = 0; ; index++) { 221 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 222 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 223 ssh_close_authentication_connection(ac); 224 return PAM_SERVICE_ERR; 225 } 226 retval = pam_get_data(pamh, data_name, (const void **)&key); 227 free(data_name); 228 if (retval != PAM_SUCCESS) 229 break; 230 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 231 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 232 ssh_close_authentication_connection(ac); 233 return PAM_SERVICE_ERR; 234 } 235 retval = pam_get_data(pamh, data_name, 236 (const void **)&comment); 237 free(data_name); 238 if (retval != PAM_SUCCESS) 239 break; 240 retval = ssh_add_identity(ac, key, comment); 241 if (!final) 242 final = retval; 243 } 244 ssh_close_authentication_connection(ac); 245 246 return final ? PAM_SUCCESS : PAM_SESSION_ERR; 247} 248 249 250PAM_EXTERN int 251pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, 252 const char **argv) 253{ 254 int authenticated; /* user authenticated? */ 255 char *dotdir; /* .ssh dir name */ 256 char *file; /* current key file */ 257 char *keyfiles; /* list of key files to add */ 258 int options; /* options for pam_get_pass() */ 259 const char *pass; /* passphrase */ 260 const struct passwd *pwent; /* user's passwd entry */ 261 struct passwd *pwent_keep; /* our own copy */ 262 int retval; /* from calls */ 263 const char *user; /* username */ 264 265 keyfiles = DEF_KEYFILES; 266 options = 0; 267 for (; argc; argc--, argv++) 268 if (strncmp(*argv, OPT_KEYFILES "=", sizeof OPT_KEYFILES) 269 == 0) { 270 if (!(keyfiles = strchr(*argv, '=') + 1)) 271 return PAM_AUTH_ERR; 272 } else if (strcmp(*argv, OPT_TRY_FIRST_PASS) == 0) 273 options |= PAM_OPT_TRY_FIRST_PASS; 274 else if (strcmp(*argv, OPT_USE_FIRST_PASS) == 0) 275 options |= PAM_OPT_USE_FIRST_PASS; 276 277 278 if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) 279 return retval; 280 if (!((pwent = getpwnam(user)) && pwent->pw_dir)) 281 return PAM_AUTH_ERR; 282 283 /* pass prompt message to application and receive passphrase */ 284 285 if ((retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, options)) 286 != PAM_SUCCESS) 287 return retval; 288 289 OpenSSL_add_all_algorithms(); /* required for DSA */ 290 291 /* any key will authenticate us, but if we can decrypt all of the 292 specified keys, we'll do so here so we can cache them in the 293 session phase */ 294 295 if (!asprintf(&dotdir, "%s/%s", pwent->pw_dir, SSH_CLIENT_DIR)) { 296 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 297 return PAM_SERVICE_ERR; 298 } 299 authenticated = 0; 300 keyfiles = strdup(keyfiles); 301 for (file = strtok(keyfiles, SEP_KEYFILES); file; 302 file = strtok(NULL, SEP_KEYFILES)) 303 if (auth_via_key(pamh, file, dotdir, pwent, pass) == 304 PAM_SUCCESS) 305 authenticated++; 306 free(keyfiles); 307 if (!authenticated) 308 return PAM_AUTH_ERR; 309 310 /* copy the passwd entry (in case successive calls are made) and 311 save it for the session phase */ 312 313 if (!(pwent_keep = malloc(sizeof *pwent))) { 314 openpam_log(PAM_LOG_ERROR, "%m"); 315 return PAM_SERVICE_ERR; 316 } 317 (void) memcpy(pwent_keep, pwent, sizeof *pwent_keep); 318 if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep, 319 ssh_cleanup)) != PAM_SUCCESS) { 320 free(pwent_keep); 321 return retval; 322 } 323 324 return PAM_SUCCESS; 325} 326 327 328PAM_EXTERN int 329pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 330{ 331 return PAM_SUCCESS; 332} 333 334 335PAM_EXTERN int 336pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, 337 const char **argv) 338{ 339 char *agent_socket; /* agent socket */ 340 char *env_end; /* end of env */ 341 FILE *env_read; /* env data source */ 342 char env_string[BUFSIZ]; /* environment string */ 343 char *env_value; /* envariable value */ 344 int env_write; /* env file descriptor */ 345 char hname[MAXHOSTNAMELEN]; /* local hostname */ 346 int no_link; /* link per-agent file? */ 347 char *per_agent; /* to store env */ 348 char *per_session; /* per-session filename */ 349 const struct passwd *pwent; /* user's passwd entry */ 350 int retval; /* from calls */ 351 uid_t saved_uid; /* caller's uid */ 352 int start_agent; /* start agent? */ 353 const char *tty; /* tty or display name */ 354 355 /* dump output of ssh-agent in ~/.ssh */ 356 if ((retval = pam_get_data(pamh, "ssh_passwd_entry", 357 (const void **)&pwent)) != PAM_SUCCESS) 358 return retval; 359 360 /* 361 * Use reference counts to limit agents to one per user per host. 362 * 363 * Technique: Create an environment file containing 364 * information about the agent. Only one file is created, but 365 * it may be given many names. One name is given for the 366 * agent itself, agent-<host>. Another name is given for each 367 * session, agent-<host>-<display> or agent-<host>-<tty>. We 368 * delete the per-session filename on session close, and when 369 * the link count goes to unity on the per-agent file, we 370 * delete the file and kill the agent. 371 */ 372 373 /* the per-agent file contains just the hostname */ 374 375 (void) gethostname(hname, sizeof hname); 376 if (asprintf(&per_agent, "%s/.ssh/agent-%s", pwent->pw_dir, hname) 377 == -1) { 378 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 379 return PAM_SERVICE_ERR; 380 } 381 382 /* save the per-agent filename in case we want to delete it on 383 session close */ 384 385 if ((retval = pam_set_data(pamh, "ssh_agent_env_agent", per_agent, 386 ssh_cleanup)) != PAM_SUCCESS) { 387 free(per_agent); 388 return retval; 389 } 390 391 /* take on the user's privileges for writing files and starting the 392 agent */ 393 394 saved_uid = geteuid(); 395 (void) seteuid(pwent->pw_uid); 396 397 /* Try to create the per-agent file or open it for reading if it 398 exists. If we can't do either, we won't try to link a 399 per-session filename later. Start the agent if we can't open 400 the file for reading. */ 401 402 env_write = no_link = 0; 403 env_read = NULL; 404 if ((env_write = open(per_agent, O_CREAT | O_EXCL | O_WRONLY, 405 S_IRUSR)) < 0 && !(env_read = fopen(per_agent, "r"))) 406 no_link = 1; 407 if (env_read) { 408 start_agent = 0; 409 (void) seteuid(saved_uid); 410 } else { 411 start_agent = 1; 412 env_read = popen(SSH_AGENT, "r"); 413 (void) seteuid(saved_uid); 414 if (!env_read) { 415 openpam_log(PAM_LOG_ERROR, "%s: %s: %m", MODULE_NAME, 416 SSH_AGENT); 417 if (env_write >= 0) 418 (void) close(env_write); 419 free(per_agent); 420 return PAM_SESSION_ERR; 421 } 422 } 423 424 /* save environment for application with pam_putenv() */ 425 426 agent_socket = NULL; 427 while (fgets(env_string, sizeof env_string, env_read)) { 428 429 /* parse environment definitions */ 430 431 if (env_write >= 0) 432 (void) write(env_write, env_string, 433 strlen(env_string)); 434 if (!(env_value = strchr(env_string, '=')) || 435 !(env_end = strchr(env_value, ';'))) 436 continue; 437 *env_end = '\0'; 438 439 /* pass to the application */ 440 441 if (!((retval = pam_putenv(pamh, env_string)) == 442 PAM_SUCCESS)) { 443 if (start_agent) 444 (void) pclose(env_read); 445 else 446 (void) fclose(env_read); 447 if (env_write >= 0) 448 (void) close(env_write); 449 if (agent_socket) 450 free(agent_socket); 451 free(per_agent); 452 return PAM_SERVICE_ERR; 453 } 454 455 *env_value++ = '\0'; 456 457 /* save the agent socket so we can connect to it and add 458 the keys as well as the PID so we can kill the agent on 459 session close. */ 460 461 if (strcmp(&env_string[strlen(env_string) - 462 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0 && 463 !(agent_socket = strdup(env_value))) { 464 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 465 if (start_agent) 466 (void) pclose(env_read); 467 else 468 (void) fclose(env_read); 469 if (env_write >= 0) 470 (void) close(env_write); 471 if (agent_socket) 472 free(agent_socket); 473 free(per_agent); 474 return PAM_SERVICE_ERR; 475 } else if (strcmp(&env_string[strlen(env_string) - 476 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0 && 477 (retval = pam_set_data(pamh, "ssh_agent_pid", 478 env_value, ssh_cleanup)) != PAM_SUCCESS) { 479 if (start_agent) 480 (void) pclose(env_read); 481 else 482 (void) fclose(env_read); 483 if (env_write >= 0) 484 (void) close(env_write); 485 if (agent_socket) 486 free(agent_socket); 487 free(per_agent); 488 return retval; 489 } 490 491 } 492 if (env_write >= 0) 493 (void) close(env_write); 494 495 if (start_agent) { 496 switch (retval = pclose(env_read)) { 497 case -1: 498 openpam_log(PAM_LOG_ERROR, "%s: %s: %m", MODULE_NAME, 499 SSH_AGENT); 500 if (agent_socket) 501 free(agent_socket); 502 free(per_agent); 503 return PAM_SESSION_ERR; 504 case 0: 505 break; 506 case 127: 507 openpam_log(PAM_LOG_ERROR, "%s: cannot execute %s", 508 MODULE_NAME, SSH_AGENT); 509 if (agent_socket) 510 free(agent_socket); 511 free(per_agent); 512 return PAM_SESSION_ERR; 513 default: 514 openpam_log(PAM_LOG_ERROR, "%s: %s exited %s %d", 515 MODULE_NAME, 516 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" : 517 "with status", WIFSIGNALED(retval) ? 518 WTERMSIG(retval) : WEXITSTATUS(retval)); 519 if (agent_socket) 520 free(agent_socket); 521 free(per_agent); 522 return PAM_SESSION_ERR; 523 } 524 } else 525 (void) fclose(env_read); 526 527 if (!agent_socket) { 528 free(per_agent); 529 return PAM_SESSION_ERR; 530 } 531 532 if (start_agent && (retval = add_keys(pamh, agent_socket)) 533 != PAM_SUCCESS) { 534 free(per_agent); 535 return retval; 536 } 537 free(agent_socket); 538 539 /* if we couldn't access the per-agent file, don't link a 540 per-session filename to it */ 541 542 if (no_link) 543 return PAM_SUCCESS; 544 545 /* the per-session file contains the display name or tty name as 546 well as the hostname */ 547 548 if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty)) 549 != PAM_SUCCESS) { 550 free(per_agent); 551 return retval; 552 } 553 if (asprintf(&per_session, "%s/.ssh/agent-%s-%s", pwent->pw_dir, 554 hname, tty) == -1) { 555 openpam_log(PAM_LOG_ERROR, "%s: %m", MODULE_NAME); 556 free(per_agent); 557 return PAM_SERVICE_ERR; 558 } 559 560 /* save the per-session filename so we can delete it on session 561 close */ 562 563 if ((retval = pam_set_data(pamh, "ssh_agent_env_session", 564 per_session, ssh_cleanup)) != PAM_SUCCESS) { 565 free(per_session); 566 free(per_agent); 567 return retval; 568 } 569 570 (void) unlink(per_session); /* remove cruft */ 571 (void) link(per_agent, per_session); 572 free(per_agent); 573 free(per_session); 574 575 return PAM_SUCCESS; 576} 577 578 579PAM_EXTERN int 580pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, 581 const char **argv) 582{ 583 const char *env_file; /* ssh-agent environment */ 584 pid_t pid; /* ssh-agent process id */ 585 int retval; /* from calls */ 586 const char *ssh_agent_pid; /* ssh-agent pid string */ 587 struct stat sb; /* to check st_nlink */ 588 589 if ((retval = pam_get_data(pamh, "ssh_agent_env_session", 590 (const void **)&env_file)) == PAM_SUCCESS && env_file) 591 (void) unlink(env_file); 592 593 /* Retrieve per-agent filename and check link count. If it's 594 greater than unity, other sessions are still using this 595 agent. */ 596 597 if ((retval = pam_get_data(pamh, "ssh_agent_env_agent", 598 (const void **)&env_file)) == PAM_SUCCESS && env_file && 599 stat(env_file, &sb) == 0) { 600 if (sb.st_nlink > 1) 601 return PAM_SUCCESS; 602 (void) unlink(env_file); 603 } 604 605 /* retrieve the agent's process id */ 606 607 if ((retval = pam_get_data(pamh, "ssh_agent_pid", 608 (const void **)&ssh_agent_pid)) != PAM_SUCCESS) 609 return retval; 610 611 /* Kill the agent. SSH's ssh-agent does not have a -k option, so 612 just call kill(). */ 613 614 pid = atoi(ssh_agent_pid); 615 if (ssh_agent_pid <= 0) 616 return PAM_SESSION_ERR; 617 if (kill(pid, SIGTERM) != 0) { 618 openpam_log(PAM_LOG_ERROR, "%s: %s: %m", MODULE_NAME, 619 ssh_agent_pid); 620 return PAM_SESSION_ERR; 621 } 622 623 return PAM_SUCCESS; 624} 625 626PAM_EXTERN int 627pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, 628 const char **argv) 629{ 630 return (PAM_IGNORE); 631} 632 633PAM_EXTERN int 634pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, 635 const char **argv) 636{ 637 return (PAM_IGNORE); 638} 639 640PAM_MODULE_ENTRY(MODULE_NAME); 641