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