pam_ssh.c revision 84218
1/*- 2 * Copyright (c) 1999, 2000 Andrew J. Korty 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 84218 2001-09-30 22:11:06Z dillon $"); 29 30#include <sys/param.h> 31#include <sys/stat.h> 32#include <sys/wait.h> 33 34#include <dirent.h> 35#include <pwd.h> 36#include <signal.h> 37#include <ssh.h> 38#include <stdio.h> 39#include <stdlib.h> 40#include <string.h> 41#include <unistd.h> 42 43#define PAM_SM_AUTH 44#define PAM_SM_SESSION 45#include <security/pam_modules.h> 46#include <security/pam_mod_misc.h> 47 48#include <openssl/dsa.h> 49#include <openssl/evp.h> 50 51#include "key.h" 52#include "authfd.h" 53#include "authfile.h" 54#include "log.h" 55#include "pam_ssh.h" 56 57/* 58 * Generic cleanup function for SSH "Key" type. 59 */ 60 61void 62key_cleanup(pam_handle_t *pamh, void *data, int error_status) 63{ 64 if (data) 65 key_free(data); 66} 67 68 69/* 70 * Generic PAM cleanup function for this module. 71 */ 72 73void 74ssh_cleanup(pam_handle_t *pamh, void *data, int error_status) 75{ 76 if (data) 77 free(data); 78} 79 80 81/* 82 * Authenticate a user's key by trying to decrypt it with the password 83 * provided. The key and its comment are then stored for later 84 * retrieval by the session phase. An increasing index is embedded in 85 * the PAM variable names so this function may be called multiple times 86 * for multiple keys. 87 */ 88 89int 90auth_via_key(pam_handle_t *pamh, int type, const char *file, 91 const char *dir, const struct passwd *user, const char *pass) 92{ 93 char *comment; /* private key comment */ 94 char *data_name; /* PAM state */ 95 static int index = 0; /* for saved keys */ 96 Key *key; /* user's key */ 97 char *path; /* to key files */ 98 int retval; /* from calls */ 99 uid_t saved_uid; /* caller's uid */ 100 101 /* locate the user's private key file */ 102 if (!asprintf(&path, "%s/%s", dir, file)) { 103 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 104 return PAM_SERVICE_ERR; 105 } 106 saved_uid = geteuid(); 107 /* 108 * Try to decrypt the private key with the passphrase provided. 109 * If success, the user is authenticated. 110 */ 111 seteuid(user->pw_uid); 112 key = key_load_private_type(type, path, pass, &comment); 113 free(path); 114 seteuid(saved_uid); 115 if (key == NULL) 116 return PAM_AUTH_ERR; 117 /* 118 * Save the key and comment to pass to ssh-agent in the session 119 * phase. 120 */ 121 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 122 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 123 free(comment); 124 return PAM_SERVICE_ERR; 125 } 126 retval = pam_set_data(pamh, data_name, key, key_cleanup); 127 free(data_name); 128 if (retval != PAM_SUCCESS) { 129 key_free(key); 130 free(comment); 131 return retval; 132 } 133 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 134 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 135 free(comment); 136 return PAM_SERVICE_ERR; 137 } 138 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup); 139 free(data_name); 140 if (retval != PAM_SUCCESS) { 141 free(comment); 142 return retval; 143 } 144 ++index; 145 return PAM_SUCCESS; 146} 147 148 149PAM_EXTERN int 150pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 151{ 152 struct options options; /* module options */ 153 int authenticated; /* user authenticated? */ 154 char *dotdir; /* .ssh2 dir name */ 155 struct dirent *dotdir_ent; /* .ssh2 dir entry */ 156 DIR *dotdir_p; /* .ssh2 dir pointer */ 157 const char *pass; /* passphrase */ 158 struct passwd *pwd; /* user's passwd entry */ 159 struct passwd *pwd_keep; /* our own copy */ 160 int retval; /* from calls */ 161 int pam_auth_dsa; /* Authorised via DSA */ 162 int pam_auth_rsa; /* Authorised via RSA */ 163 const char *user; /* username */ 164 165 pam_std_option(&options, NULL, argc, argv); 166 167 PAM_LOG("Options processed"); 168 169 retval = pam_get_user(pamh, &user, NULL); 170 if (retval != PAM_SUCCESS) 171 PAM_RETURN(retval); 172 pwd = getpwnam(user); 173 if (pwd == NULL || pwd->pw_dir == NULL) 174 /* delay? */ 175 PAM_RETURN(PAM_AUTH_ERR); 176 177 PAM_LOG("Got user: %s", user); 178 179 /* 180 * Pass prompt message to application and receive 181 * passphrase. 182 */ 183 retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options); 184 if (retval != PAM_SUCCESS) 185 PAM_RETURN(retval); 186 OpenSSL_add_all_algorithms(); /* required for DSA */ 187 188 PAM_LOG("Got passphrase"); 189 190 /* 191 * Either the DSA or the RSA key will authenticate us, but if 192 * we can decrypt both, we'll do so here so we can cache them in 193 * the session phase. 194 */ 195 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) { 196 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 197 PAM_RETURN(PAM_SERVICE_ERR); 198 } 199 pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir, 200 pwd, pass); 201 pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir, 202 pwd, pass); 203 authenticated = 0; 204 if (pam_auth_dsa == PAM_SUCCESS) 205 authenticated++; 206 if (pam_auth_rsa == PAM_SUCCESS) 207 authenticated++; 208 209 PAM_LOG("Done pre-authenticating; got %d", authenticated); 210 211 /* 212 * Compatibility with SSH2 from SSH Communications Security. 213 */ 214 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) { 215 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 216 PAM_RETURN(PAM_SERVICE_ERR); 217 } 218 /* 219 * Try to load anything that looks like a private key. For 220 * now, we only support DSA and RSA keys. 221 */ 222 dotdir_p = opendir(dotdir); 223 while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) { 224 /* skip public keys */ 225 if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen - 226 strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0) 227 continue; 228 /* DSA keys */ 229 if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX, 230 strlen(SSH2_DSA_PREFIX)) == 0) 231 retval = auth_via_key(pamh, KEY_DSA, 232 dotdir_ent->d_name, dotdir, pwd, pass); 233 /* RSA keys */ 234 else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX, 235 strlen(SSH2_RSA_PREFIX)) == 0) 236 retval = auth_via_key(pamh, KEY_RSA, 237 dotdir_ent->d_name, dotdir, pwd, pass); 238 /* skip other files */ 239 else 240 continue; 241 authenticated += (retval == PAM_SUCCESS); 242 } 243 if (!authenticated) { 244 PAM_VERBOSE_ERROR("SSH authentication refused"); 245 PAM_RETURN(PAM_AUTH_ERR); 246 } 247 248 PAM_LOG("Done authenticating; got %d", authenticated); 249 250 /* 251 * Copy the passwd entry (in case successive calls are made) 252 * and save it for the session phase. 253 */ 254 pwd_keep = malloc(sizeof *pwd); 255 if (pwd_keep == NULL) { 256 syslog(LOG_CRIT, "%m"); 257 PAM_RETURN(PAM_SERVICE_ERR); 258 } 259 memcpy(pwd_keep, pwd, sizeof *pwd_keep); 260 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup); 261 if (retval != PAM_SUCCESS) { 262 free(pwd_keep); 263 PAM_RETURN(retval); 264 } 265 266 PAM_LOG("Saved ssh_passwd_entry"); 267 268 PAM_RETURN(PAM_SUCCESS); 269} 270 271 272PAM_EXTERN int 273pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 274{ 275 struct options options; /* module options */ 276 277 pam_std_option(&options, NULL, argc, argv); 278 279 PAM_LOG("Options processed"); 280 281 PAM_RETURN(PAM_SUCCESS); 282} 283 284 285typedef AuthenticationConnection AC; 286 287PAM_EXTERN int 288pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 289{ 290 struct options options; /* module options */ 291 AC *ac; /* to ssh-agent */ 292 char *agent_socket; /* agent socket */ 293 char *comment; /* on private key */ 294 char *env_end; /* end of env */ 295 char *env_file; /* to store env */ 296 FILE *env_fp; /* env_file handle */ 297 char *env_value; /* envariable value */ 298 char *data_name; /* PAM state */ 299 int final; /* final return value */ 300 int index; /* for saved keys */ 301 Key *key; /* user's private key */ 302 FILE *pipe; /* ssh-agent handle */ 303 struct passwd *pwd; /* user's passwd entry */ 304 int retval; /* from calls */ 305 uid_t saved_uid; /* caller's uid */ 306 const char *tty; /* tty or display name */ 307 char hname[MAXHOSTNAMELEN]; /* local hostname */ 308 char env_string[BUFSIZ]; /* environment string */ 309 310 pam_std_option(&options, NULL, argc, argv); 311 312 PAM_LOG("Options processed"); 313 314 /* dump output of ssh-agent in ~/.ssh */ 315 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd); 316 if (retval != PAM_SUCCESS) 317 PAM_RETURN(retval); 318 319 PAM_LOG("Got ssh_passwd_entry"); 320 321 /* use the tty or X display name in the filename */ 322 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 323 if (retval != PAM_SUCCESS) 324 PAM_RETURN(retval); 325 326 PAM_LOG("Got TTY"); 327 328 if (gethostname(hname, sizeof hname) == 0) { 329 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s", 330 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty) 331 == -1) { 332 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 333 PAM_RETURN(PAM_SERVICE_ERR); 334 } 335 } 336 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir, 337 tty) == -1) { 338 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 339 PAM_RETURN(PAM_SERVICE_ERR); 340 } 341 342 PAM_LOG("Got env_file: %s", env_file); 343 344 /* save the filename so we can delete the file on session close */ 345 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup); 346 if (retval != PAM_SUCCESS) { 347 free(env_file); 348 PAM_RETURN(retval); 349 } 350 351 PAM_LOG("Saved env_file"); 352 353 /* start the agent as the user */ 354 saved_uid = geteuid(); 355 seteuid(pwd->pw_uid); 356 env_fp = fopen(env_file, "w"); 357 if (env_fp != NULL) 358 chmod(env_file, S_IRUSR); 359 pipe = popen(SSH_AGENT, "r"); 360 seteuid(saved_uid); 361 if (!pipe) { 362 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 363 if (env_fp) 364 fclose(env_fp); 365 PAM_RETURN(PAM_SESSION_ERR); 366 } 367 368 PAM_LOG("Agent started as user"); 369 370 /* 371 * Save environment for application with pam_putenv(). 372 */ 373 agent_socket = NULL; 374 while (fgets(env_string, sizeof env_string, pipe)) { 375 if (env_fp) 376 fputs(env_string, env_fp); 377 env_value = strchr(env_string, '='); 378 if (env_value == NULL) 379 continue; 380 env_end = strchr(env_value, ';'); 381 if (env_end == NULL) 382 continue; 383 *env_end = '\0'; 384 /* pass to the application ... */ 385 retval = pam_putenv(pamh, env_string); 386 if (retval != PAM_SUCCESS) { 387 pclose(pipe); 388 if (env_fp) 389 fclose(env_fp); 390 PAM_RETURN(PAM_SERVICE_ERR); 391 } 392 putenv(env_string); 393 394 PAM_LOG("Put to environment: %s", env_string); 395 396 *env_value++ = '\0'; 397 if (strcmp(&env_string[strlen(env_string) - 398 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) { 399 agent_socket = strdup(env_value); 400 if (agent_socket == NULL) { 401 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 402 PAM_RETURN(PAM_SERVICE_ERR); 403 } 404 } 405 else if (strcmp(&env_string[strlen(env_string) - 406 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) { 407 retval = pam_set_data(pamh, "ssh_agent_pid", 408 env_value, ssh_cleanup); 409 if (retval != PAM_SUCCESS) 410 PAM_RETURN(retval); 411 PAM_LOG("Environment write successful"); 412 } 413 } 414 if (env_fp) 415 fclose(env_fp); 416 retval = pclose(pipe); 417 switch (retval) { 418 case -1: 419 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 420 PAM_RETURN(PAM_SESSION_ERR); 421 case 0: 422 break; 423 case 127: 424 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, 425 SSH_AGENT); 426 PAM_RETURN(PAM_SESSION_ERR); 427 default: 428 syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME, 429 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" : 430 "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) : 431 WEXITSTATUS(retval)); 432 PAM_RETURN(PAM_SESSION_ERR); 433 } 434 if (agent_socket == NULL) 435 PAM_RETURN(PAM_SESSION_ERR); 436 437 PAM_LOG("Environment saved"); 438 439 /* connect to the agent */ 440 ac = ssh_get_authentication_connection(); 441 if (!ac) { 442 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket); 443 PAM_RETURN(PAM_SESSION_ERR); 444 } 445 446 PAM_LOG("Connected to agent"); 447 448 /* hand off each private key to the agent */ 449 final = 0; 450 for (index = 0; ; index++) { 451 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 452 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 453 ssh_close_authentication_connection(ac); 454 PAM_RETURN(PAM_SERVICE_ERR); 455 } 456 retval = pam_get_data(pamh, data_name, (const void **)&key); 457 free(data_name); 458 if (retval != PAM_SUCCESS) 459 break; 460 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 461 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 462 ssh_close_authentication_connection(ac); 463 PAM_RETURN(PAM_SERVICE_ERR); 464 } 465 retval = pam_get_data(pamh, data_name, (const void **)&comment); 466 free(data_name); 467 if (retval != PAM_SUCCESS) 468 break; 469 retval = ssh_add_identity(ac, key, comment); 470 if (!final) 471 final = retval; 472 } 473 ssh_close_authentication_connection(ac); 474 475 PAM_LOG("Keys handed off"); 476 477 PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR); 478} 479 480 481PAM_EXTERN int 482pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 483{ 484 struct options options; /* module options */ 485 const char *env_file; /* ssh-agent environment */ 486 pid_t pid; /* ssh-agent process id */ 487 int retval; /* from calls */ 488 const char *ssh_agent_pid; /* ssh-agent pid string */ 489 490 pam_std_option(&options, NULL, argc, argv); 491 492 PAM_LOG("Options processed"); 493 494 /* retrieve environment filename, then remove the file */ 495 retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file); 496 if (retval != PAM_SUCCESS) 497 PAM_RETURN(retval); 498 unlink(env_file); 499 500 PAM_LOG("Got ssh_agent_env"); 501 502 /* retrieve the agent's process id */ 503 retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid); 504 if (retval != PAM_SUCCESS) 505 PAM_RETURN(retval); 506 507 PAM_LOG("Got ssh_agent_pid"); 508 509 /* 510 * Kill the agent. SSH2 from SSH Communications Security does 511 * not have a -k option, so we just call kill(). 512 */ 513 pid = atoi(ssh_agent_pid); 514 if (pid <= 0) 515 PAM_RETURN(PAM_SESSION_ERR); 516 if (kill(pid, SIGTERM) != 0) { 517 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid); 518 PAM_RETURN(PAM_SESSION_ERR); 519 } 520 521 PAM_LOG("Agent killed"); 522 523 PAM_RETURN(PAM_SUCCESS); 524} 525 526PAM_MODULE_ENTRY("pam_ssh"); 527