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