pam_ssh.c revision 91714
1/*- 2 * Copyright (c) 1999, 2000 Andrew J. Korty 3 * All rights reserved. 4 * Copyright (c) 2001 Networks Associates Technologies, 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 37#include <sys/cdefs.h> 38__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 91714 2002-03-05 21:56:25Z des $"); 39 40#include <sys/param.h> 41#include <sys/socket.h> 42#include <sys/stat.h> 43#include <sys/wait.h> 44 45#include <dirent.h> 46#include <pwd.h> 47#include <signal.h> 48#include <ssh.h> 49#include <stdio.h> 50#include <stdlib.h> 51#include <string.h> 52#include <syslog.h> 53#include <unistd.h> 54 55#define PAM_SM_AUTH 56#define PAM_SM_ACCOUNT 57#define PAM_SM_SESSION 58#define PAM_SM_PASSWORD 59 60#include <security/pam_appl.h> 61#include <security/pam_modules.h> 62#include <security/pam_mod_misc.h> 63 64#include <openssl/dsa.h> 65#include <openssl/evp.h> 66 67#include "key.h" 68#include "authfd.h" 69#include "authfile.h" 70#include "log.h" 71#include "pam_ssh.h" 72 73static int auth_via_key(pam_handle_t *, int, const char *, const char *, const struct passwd *, const char *); 74static void key_cleanup(pam_handle_t *, void *, int); 75static void ssh_cleanup(pam_handle_t *, void *, int); 76 77/* 78 * Generic cleanup function for SSH "Key" type. 79 */ 80 81static void 82key_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused) 83{ 84 if (data) 85 key_free(data); 86} 87 88 89/* 90 * Generic PAM cleanup function for this module. 91 */ 92 93static void 94ssh_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused) 95{ 96 if (data) 97 free(data); 98} 99 100 101/* 102 * Authenticate a user's key by trying to decrypt it with the password 103 * provided. The key and its comment are then stored for later 104 * retrieval by the session phase. An increasing index is embedded in 105 * the PAM variable names so this function may be called multiple times 106 * for multiple keys. 107 */ 108 109static int 110auth_via_key(pam_handle_t *pamh, int type, const char *file, 111 const char *dir, const struct passwd *user, const char *pass) 112{ 113 char *comment; /* private key comment */ 114 char *data_name; /* PAM state */ 115 static int indx = 0; /* for saved keys */ 116 Key *key; /* user's key */ 117 char *path; /* to key files */ 118 int retval; /* from calls */ 119 uid_t saved_uid; /* caller's uid */ 120 121 /* locate the user's private key file */ 122 if (!asprintf(&path, "%s/%s", dir, file)) { 123 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 124 return PAM_SERVICE_ERR; 125 } 126 saved_uid = geteuid(); 127 /* 128 * Try to decrypt the private key with the passphrase provided. 129 * If success, the user is authenticated. 130 */ 131 seteuid(user->pw_uid); 132 key = key_load_private_type(type, path, pass, &comment); 133 free(path); 134 seteuid(saved_uid); 135 if (key == NULL) 136 return PAM_AUTH_ERR; 137 /* 138 * Save the key and comment to pass to ssh-agent in the session 139 * phase. 140 */ 141 if (!asprintf(&data_name, "ssh_private_key_%d", indx)) { 142 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 143 free(comment); 144 return PAM_SERVICE_ERR; 145 } 146 retval = pam_set_data(pamh, data_name, key, key_cleanup); 147 free(data_name); 148 if (retval != PAM_SUCCESS) { 149 key_free(key); 150 free(comment); 151 return retval; 152 } 153 if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) { 154 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 155 free(comment); 156 return PAM_SERVICE_ERR; 157 } 158 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup); 159 free(data_name); 160 if (retval != PAM_SUCCESS) { 161 free(comment); 162 return retval; 163 } 164 ++indx; 165 return PAM_SUCCESS; 166} 167 168 169PAM_EXTERN int 170pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, int argc, const char **argv) 171{ 172 struct options options; /* module options */ 173 int authenticated; /* user authenticated? */ 174 char *dotdir; /* .ssh2 dir name */ 175 struct dirent *dotdir_ent; /* .ssh2 dir entry */ 176 DIR *dotdir_p; /* .ssh2 dir pointer */ 177 const char *pass; /* passphrase */ 178 struct passwd *pwd; /* user's passwd entry */ 179 struct passwd *pwd_keep; /* our own copy */ 180 int retval; /* from calls */ 181 int pam_auth_dsa; /* Authorised via DSA */ 182 int pam_auth_rsa; /* Authorised via RSA */ 183 const char *user; /* username */ 184 185 pam_std_option(&options, NULL, argc, argv); 186 187 PAM_LOG("Options processed"); 188 189 retval = pam_get_user(pamh, &user, NULL); 190 if (retval != PAM_SUCCESS) 191 PAM_RETURN(retval); 192 pwd = getpwnam(user); 193 if (pwd == NULL || pwd->pw_dir == NULL) 194 /* delay? */ 195 PAM_RETURN(PAM_AUTH_ERR); 196 197 PAM_LOG("Got user: %s", user); 198 199 /* 200 * Pass prompt message to application and receive 201 * passphrase. 202 */ 203 retval = pam_get_authtok(pamh, &pass, NEED_PASSPHRASE); 204 if (retval != PAM_SUCCESS) 205 PAM_RETURN(retval); 206 OpenSSL_add_all_algorithms(); /* required for DSA */ 207 208 PAM_LOG("Got passphrase"); 209 210 /* 211 * Either the DSA or the RSA key will authenticate us, but if 212 * we can decrypt both, we'll do so here so we can cache them in 213 * the session phase. 214 */ 215 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) { 216 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 217 PAM_RETURN(PAM_SERVICE_ERR); 218 } 219 pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir, 220 pwd, pass); 221 pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir, 222 pwd, pass); 223 authenticated = 0; 224 if (pam_auth_dsa == PAM_SUCCESS) 225 authenticated++; 226 if (pam_auth_rsa == PAM_SUCCESS) 227 authenticated++; 228 229 PAM_LOG("Done pre-authenticating; got %d", authenticated); 230 231 /* 232 * Compatibility with SSH2 from SSH Communications Security. 233 */ 234 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) { 235 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 236 PAM_RETURN(PAM_SERVICE_ERR); 237 } 238 /* 239 * Try to load anything that looks like a private key. For 240 * now, we only support DSA and RSA keys. 241 */ 242 dotdir_p = opendir(dotdir); 243 while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) { 244 /* skip public keys */ 245 if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen - 246 strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0) 247 continue; 248 /* DSA keys */ 249 if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX, 250 strlen(SSH2_DSA_PREFIX)) == 0) 251 retval = auth_via_key(pamh, KEY_DSA, 252 dotdir_ent->d_name, dotdir, pwd, pass); 253 /* RSA keys */ 254 else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX, 255 strlen(SSH2_RSA_PREFIX)) == 0) 256 retval = auth_via_key(pamh, KEY_RSA, 257 dotdir_ent->d_name, dotdir, pwd, pass); 258 /* skip other files */ 259 else 260 continue; 261 authenticated += (retval == PAM_SUCCESS); 262 } 263 if (!authenticated) { 264 PAM_VERBOSE_ERROR("SSH authentication refused"); 265 PAM_RETURN(PAM_AUTH_ERR); 266 } 267 268 PAM_LOG("Done authenticating; got %d", authenticated); 269 270 /* 271 * Copy the passwd entry (in case successive calls are made) 272 * and save it for the session phase. 273 */ 274 pwd_keep = malloc(sizeof *pwd); 275 if (pwd_keep == NULL) { 276 syslog(LOG_CRIT, "%m"); 277 PAM_RETURN(PAM_SERVICE_ERR); 278 } 279 memcpy(pwd_keep, pwd, sizeof *pwd_keep); 280 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup); 281 if (retval != PAM_SUCCESS) { 282 free(pwd_keep); 283 PAM_RETURN(retval); 284 } 285 286 PAM_LOG("Saved ssh_passwd_entry"); 287 288 PAM_RETURN(PAM_SUCCESS); 289} 290 291 292PAM_EXTERN int 293pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv) 294{ 295 struct options options; /* module options */ 296 297 pam_std_option(&options, NULL, argc, argv); 298 299 PAM_LOG("Options processed"); 300 301 PAM_RETURN(PAM_SUCCESS); 302} 303 304PAM_EXTERN int 305pam_sm_acct_mgmt(pam_handle_t *pamh __unused, int flags __unused, int argc ,const char **argv) 306{ 307 struct options options; 308 309 pam_std_option(&options, NULL, argc, argv); 310 311 PAM_LOG("Options processed"); 312 313 PAM_RETURN(PAM_IGNORE); 314} 315 316PAM_EXTERN int 317pam_sm_chauthtok(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv) 318{ 319 struct options options; 320 321 pam_std_option(&options, NULL, argc, argv); 322 323 PAM_LOG("Options processed"); 324 325 PAM_RETURN(PAM_IGNORE); 326} 327 328typedef AuthenticationConnection AC; 329 330PAM_EXTERN int 331pam_sm_open_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv) 332{ 333 struct options options; /* module options */ 334 AC *ac; /* to ssh-agent */ 335 char *agent_socket; /* agent socket */ 336 char *comment; /* on private key */ 337 char *env_end; /* end of env */ 338 char *env_file; /* to store env */ 339 FILE *env_fp; /* env_file handle */ 340 char *env_value; /* envariable value */ 341 char *data_name; /* PAM state */ 342 int final; /* final return value */ 343 int indx; /* for saved keys */ 344 Key *key; /* user's private key */ 345 FILE *lpipe; /* ssh-agent handle */ 346 struct passwd *pwd; /* user's passwd entry */ 347 int retval; /* from calls */ 348 uid_t saved_uid; /* caller's uid */ 349 const char *tty; /* tty or display name */ 350 char hname[MAXHOSTNAMELEN]; /* local hostname */ 351 char env_string[BUFSIZ]; /* environment string */ 352 353 pam_std_option(&options, NULL, argc, argv); 354 355 PAM_LOG("Options processed"); 356 357 /* dump output of ssh-agent in ~/.ssh */ 358 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd); 359 if (retval != PAM_SUCCESS) 360 PAM_RETURN(retval); 361 362 PAM_LOG("Got ssh_passwd_entry"); 363 364 /* use the tty or X display name in the filename */ 365 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 366 if (retval != PAM_SUCCESS) 367 PAM_RETURN(retval); 368 369 PAM_LOG("Got TTY"); 370 371 if (gethostname(hname, sizeof hname) == 0) { 372 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s", 373 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty) 374 == -1) { 375 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 376 PAM_RETURN(PAM_SERVICE_ERR); 377 } 378 } 379 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir, 380 tty) == -1) { 381 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 382 PAM_RETURN(PAM_SERVICE_ERR); 383 } 384 385 PAM_LOG("Got env_file: %s", env_file); 386 387 /* save the filename so we can delete the file on session close */ 388 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup); 389 if (retval != PAM_SUCCESS) { 390 free(env_file); 391 PAM_RETURN(retval); 392 } 393 394 PAM_LOG("Saved env_file"); 395 396 /* start the agent as the user */ 397 saved_uid = geteuid(); 398 seteuid(pwd->pw_uid); 399 env_fp = fopen(env_file, "w"); 400 if (env_fp != NULL) 401 chmod(env_file, S_IRUSR); 402 lpipe = popen(SSH_AGENT, "r"); 403 seteuid(saved_uid); 404 if (!lpipe) { 405 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 406 if (env_fp) 407 fclose(env_fp); 408 PAM_RETURN(PAM_SESSION_ERR); 409 } 410 411 PAM_LOG("Agent started as user"); 412 413 /* 414 * Save environment for application with pam_putenv(). 415 */ 416 agent_socket = NULL; 417 while (fgets(env_string, sizeof env_string, lpipe)) { 418 if (env_fp) 419 fputs(env_string, env_fp); 420 env_value = strchr(env_string, '='); 421 if (env_value == NULL) 422 continue; 423 env_end = strchr(env_value, ';'); 424 if (env_end == NULL) 425 continue; 426 *env_end = '\0'; 427 /* pass to the application ... */ 428 retval = pam_putenv(pamh, env_string); 429 if (retval != PAM_SUCCESS) { 430 pclose(lpipe); 431 if (env_fp) 432 fclose(env_fp); 433 PAM_RETURN(PAM_SERVICE_ERR); 434 } 435 436 PAM_LOG("Put to environment: %s", env_string); 437 438 *env_value++ = '\0'; 439 if (strcmp(&env_string[strlen(env_string) - 440 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) { 441 agent_socket = strdup(env_value); 442 if (agent_socket == NULL) { 443 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 444 PAM_RETURN(PAM_SERVICE_ERR); 445 } 446 } 447 else if (strcmp(&env_string[strlen(env_string) - 448 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) { 449 env_value = strdup(env_value); 450 if (env_value == NULL) { 451 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 452 PAM_RETURN(PAM_SERVICE_ERR); 453 } 454 retval = pam_set_data(pamh, "ssh_agent_pid", 455 env_value, ssh_cleanup); 456 if (retval != PAM_SUCCESS) 457 PAM_RETURN(retval); 458 PAM_LOG("Environment write successful"); 459 } 460 } 461 if (env_fp) 462 fclose(env_fp); 463 retval = pclose(lpipe); 464 switch (retval) { 465 case -1: 466 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 467 PAM_RETURN(PAM_SESSION_ERR); 468 case 0: 469 break; 470 case 127: 471 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, 472 SSH_AGENT); 473 PAM_RETURN(PAM_SESSION_ERR); 474 default: 475 syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME, 476 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" : 477 "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) : 478 WEXITSTATUS(retval)); 479 PAM_RETURN(PAM_SESSION_ERR); 480 } 481 if (agent_socket == NULL) 482 PAM_RETURN(PAM_SESSION_ERR); 483 484 PAM_LOG("Environment saved"); 485 486 /* 487 * Connect to the agent. 488 * 489 * XXX Because ssh_get_authentication_connection() gets the 490 * XXX agent parameters from the environment, we have to 491 * XXX temporarily replace the environment with the PAM 492 * XXX environment list. This is a hack. 493 */ 494 { 495 extern char **environ; 496 char **saved, **evp; 497 498 saved = environ; 499 if ((environ = pam_getenvlist(pamh)) == NULL) { 500 environ = saved; 501 syslog(LOG_ERR, "%s: %m", MODULE_NAME); 502 PAM_RETURN(PAM_BUF_ERR); 503 } 504 ac = ssh_get_authentication_connection(); 505 for (evp = environ; *evp; evp++) 506 free(*evp); 507 free(environ); 508 environ = saved; 509 } 510 if (!ac) { 511 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket); 512 PAM_RETURN(PAM_SESSION_ERR); 513 } 514 515 PAM_LOG("Connected to agent"); 516 517 /* hand off each private key to the agent */ 518 final = 0; 519 for (indx = 0; ; indx++) { 520 if (!asprintf(&data_name, "ssh_private_key_%d", indx)) { 521 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 522 ssh_close_authentication_connection(ac); 523 PAM_RETURN(PAM_SERVICE_ERR); 524 } 525 retval = pam_get_data(pamh, data_name, (const void **)&key); 526 free(data_name); 527 if (retval != PAM_SUCCESS) 528 break; 529 if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) { 530 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 531 ssh_close_authentication_connection(ac); 532 PAM_RETURN(PAM_SERVICE_ERR); 533 } 534 retval = pam_get_data(pamh, data_name, (const void **)&comment); 535 free(data_name); 536 if (retval != PAM_SUCCESS) 537 break; 538 retval = ssh_add_identity(ac, key, comment); 539 if (!final) 540 final = retval; 541 } 542 ssh_close_authentication_connection(ac); 543 544 PAM_LOG("Keys handed off"); 545 546 PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR); 547} 548 549 550PAM_EXTERN int 551pam_sm_close_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv) 552{ 553 struct options options; /* module options */ 554 const char *env_file; /* ssh-agent environment */ 555 pid_t pid; /* ssh-agent process id */ 556 int retval; /* from calls */ 557 const char *ssh_agent_pid; /* ssh-agent pid string */ 558 559 pam_std_option(&options, NULL, argc, argv); 560 561 PAM_LOG("Options processed"); 562 563 /* retrieve environment filename, then remove the file */ 564 retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file); 565 if (retval != PAM_SUCCESS) 566 PAM_RETURN(retval); 567 unlink(env_file); 568 569 PAM_LOG("Got ssh_agent_env"); 570 571 /* retrieve the agent's process id */ 572 retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid); 573 if (retval != PAM_SUCCESS) 574 PAM_RETURN(retval); 575 576 PAM_LOG("Got ssh_agent_pid"); 577 578 /* 579 * Kill the agent. SSH2 from SSH Communications Security does 580 * not have a -k option, so we just call kill(). 581 */ 582 pid = atoi(ssh_agent_pid); 583 if (pid <= 0) 584 PAM_RETURN(PAM_SESSION_ERR); 585 if (kill(pid, SIGTERM) != 0) { 586 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid); 587 PAM_RETURN(PAM_SESSION_ERR); 588 } 589 590 PAM_LOG("Agent killed"); 591 592 PAM_RETURN(PAM_SUCCESS); 593} 594 595PAM_MODULE_ENTRY("pam_ssh"); 596