pam_ssh.c revision 81476
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 * $FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 81476 2001-08-10 19:21:45Z markm $ 27 * 28 */ 29 30 31#include <sys/param.h> 32#include <sys/stat.h> 33#include <sys/wait.h> 34 35#include <dirent.h> 36#include <pwd.h> 37#include <signal.h> 38#include <ssh.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <unistd.h> 43 44#define PAM_SM_AUTH 45#define PAM_SM_SESSION 46#include <security/pam_modules.h> 47#include <security/pam_mod_misc.h> 48 49#include <openssl/dsa.h> 50#include <openssl/evp.h> 51 52#include "key.h" 53#include "authfd.h" 54#include "authfile.h" 55#include "log.h" 56#include "pam_ssh.h" 57 58/* 59 * Generic cleanup function for SSH "Key" type. 60 */ 61 62void 63key_cleanup(pam_handle_t *pamh, void *data, int error_status) 64{ 65 if (data) 66 key_free(data); 67} 68 69 70/* 71 * Generic PAM cleanup function for this module. 72 */ 73 74void 75ssh_cleanup(pam_handle_t *pamh, void *data, int error_status) 76{ 77 if (data) 78 free(data); 79} 80 81 82/* 83 * Authenticate a user's key by trying to decrypt it with the password 84 * provided. The key and its comment are then stored for later 85 * retrieval by the session phase. An increasing index is embedded in 86 * the PAM variable names so this function may be called multiple times 87 * for multiple keys. 88 */ 89 90int 91auth_via_key(pam_handle_t *pamh, int type, const char *file, 92 const char *dir, const struct passwd *user, const char *pass) 93{ 94 char *comment; /* private key comment */ 95 char *data_name; /* PAM state */ 96 static int index = 0; /* for saved keys */ 97 Key *key; /* user's key */ 98 char *path; /* to key files */ 99 int retval; /* from calls */ 100 uid_t saved_uid; /* caller's uid */ 101 102 /* locate the user's private key file */ 103 if (!asprintf(&path, "%s/%s", dir, file)) { 104 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 105 return PAM_SERVICE_ERR; 106 } 107 saved_uid = geteuid(); 108 /* 109 * Try to decrypt the private key with the passphrase provided. 110 * If success, the user is authenticated. 111 */ 112 seteuid(user->pw_uid); 113 key = key_load_private_type(type, path, pass, &comment); 114 free(path); 115 seteuid(saved_uid); 116 if (key == NULL) 117 return PAM_AUTH_ERR; 118 /* 119 * Save the key and comment to pass to ssh-agent in the session 120 * phase. 121 */ 122 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 123 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 124 free(comment); 125 return PAM_SERVICE_ERR; 126 } 127 retval = pam_set_data(pamh, data_name, key, key_cleanup); 128 free(data_name); 129 if (retval != PAM_SUCCESS) { 130 key_free(key); 131 free(comment); 132 return retval; 133 } 134 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 135 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 136 free(comment); 137 return PAM_SERVICE_ERR; 138 } 139 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup); 140 free(data_name); 141 if (retval != PAM_SUCCESS) { 142 free(comment); 143 return retval; 144 } 145 ++index; 146 return PAM_SUCCESS; 147} 148 149 150PAM_EXTERN int 151pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 152{ 153 struct options options; /* module options */ 154 int authenticated; /* user authenticated? */ 155 char *dotdir; /* .ssh2 dir name */ 156 struct dirent *dotdir_ent; /* .ssh2 dir entry */ 157 DIR *dotdir_p; /* .ssh2 dir pointer */ 158 const char *pass; /* passphrase */ 159 struct passwd *pwd; /* user's passwd entry */ 160 struct passwd *pwd_keep; /* our own copy */ 161 int retval; /* from calls */ 162 int pam_auth_dsa; /* Authorised via DSA */ 163 int pam_auth_rsa; /* Authorised via RSA */ 164 const char *user; /* username */ 165 166 pam_std_option(&options, NULL, argc, argv); 167 168 PAM_LOG("Options processed"); 169 170 retval = pam_get_user(pamh, &user, NULL); 171 if (retval != PAM_SUCCESS) 172 PAM_RETURN(retval); 173 pwd = getpwnam(user); 174 if (pwd == NULL || pwd->pw_dir == NULL) 175 /* delay? */ 176 PAM_RETURN(PAM_AUTH_ERR); 177 178 PAM_LOG("Got user: %s", user); 179 180 /* 181 * Pass prompt message to application and receive 182 * passphrase. 183 */ 184 retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options); 185 if (retval != PAM_SUCCESS) 186 PAM_RETURN(retval); 187 OpenSSL_add_all_algorithms(); /* required for DSA */ 188 189 PAM_LOG("Got passphrase"); 190 191 /* 192 * Either the DSA or the RSA key will authenticate us, but if 193 * we can decrypt both, we'll do so here so we can cache them in 194 * the session phase. 195 */ 196 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) { 197 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 198 PAM_RETURN(PAM_SERVICE_ERR); 199 } 200 pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir, 201 pwd, pass); 202 pam_auth_rsa = auth_via_key(pamh, KEY_RSA, SSH_CLIENT_IDENTITY, dotdir, 203 pwd, pass); 204 authenticated = 0; 205 if (pam_auth_dsa == PAM_SUCCESS) 206 authenticated++; 207 if (pam_auth_rsa == PAM_SUCCESS) 208 authenticated++; 209 210 PAM_LOG("Done pre-authenticating; got %d", authenticated); 211 212 /* 213 * Compatibility with SSH2 from SSH Communications Security. 214 */ 215 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) { 216 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 217 PAM_RETURN(PAM_SERVICE_ERR); 218 } 219 /* 220 * Try to load anything that looks like a private key. For 221 * now, we only support DSA and RSA keys. 222 */ 223 dotdir_p = opendir(dotdir); 224 while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) { 225 /* skip public keys */ 226 if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen - 227 strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0) 228 continue; 229 /* DSA keys */ 230 if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX, 231 strlen(SSH2_DSA_PREFIX)) == 0) 232 retval = auth_via_key(pamh, KEY_DSA, 233 dotdir_ent->d_name, dotdir, pwd, pass); 234 /* RSA keys */ 235 else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX, 236 strlen(SSH2_RSA_PREFIX)) == 0) 237 retval = auth_via_key(pamh, KEY_DSA, 238 dotdir_ent->d_name, dotdir, pwd, pass); 239 /* skip other files */ 240 else 241 continue; 242 authenticated += (retval == PAM_SUCCESS); 243 } 244 if (!authenticated) { 245 PAM_VERBOSE_ERROR("SSH authentication refused"); 246 PAM_RETURN(PAM_AUTH_ERR); 247 } 248 249 PAM_LOG("Done authenticating; got %d", authenticated); 250 251 /* 252 * Copy the passwd entry (in case successive calls are made) 253 * and save it for the session phase. 254 */ 255 pwd_keep = malloc(sizeof *pwd); 256 if (pwd_keep == NULL) { 257 syslog(LOG_CRIT, "%m"); 258 PAM_RETURN(PAM_SERVICE_ERR); 259 } 260 memcpy(pwd_keep, pwd, sizeof *pwd_keep); 261 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup); 262 if (retval != PAM_SUCCESS) { 263 free(pwd_keep); 264 PAM_RETURN(retval); 265 } 266 267 PAM_LOG("Saved ssh_passwd_entry"); 268 269 PAM_RETURN(PAM_SUCCESS); 270} 271 272 273PAM_EXTERN int 274pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 275{ 276 struct options options; /* module options */ 277 278 pam_std_option(&options, NULL, argc, argv); 279 280 PAM_LOG("Options processed"); 281 282 PAM_RETURN(PAM_SUCCESS); 283} 284 285 286typedef AuthenticationConnection AC; 287 288PAM_EXTERN int 289pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 290{ 291 struct options options; /* module options */ 292 AC *ac; /* to ssh-agent */ 293 char *agent_socket; /* agent socket */ 294 char *comment; /* on private key */ 295 char *env_end; /* end of env */ 296 char *env_file; /* to store env */ 297 FILE *env_fp; /* env_file handle */ 298 char *env_value; /* envariable value */ 299 char *data_name; /* PAM state */ 300 int final; /* final return value */ 301 int index; /* for saved keys */ 302 Key *key; /* user's private key */ 303 FILE *pipe; /* ssh-agent handle */ 304 struct passwd *pwd; /* user's passwd entry */ 305 int retval; /* from calls */ 306 uid_t saved_uid; /* caller's uid */ 307 const char *tty; /* tty or display name */ 308 char hname[MAXHOSTNAMELEN]; /* local hostname */ 309 char env_string[BUFSIZ]; /* environment string */ 310 311 pam_std_option(&options, NULL, argc, argv); 312 313 PAM_LOG("Options processed"); 314 315 /* dump output of ssh-agent in ~/.ssh */ 316 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd); 317 if (retval != PAM_SUCCESS) 318 PAM_RETURN(retval); 319 320 PAM_LOG("Got ssh_passwd_entry"); 321 322 /* use the tty or X display name in the filename */ 323 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 324 if (retval != PAM_SUCCESS) 325 PAM_RETURN(retval); 326 327 PAM_LOG("Got TTY"); 328 329 if (gethostname(hname, sizeof hname) == 0) { 330 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s", 331 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty) 332 == -1) { 333 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 334 PAM_RETURN(PAM_SERVICE_ERR); 335 } 336 } 337 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir, 338 tty) == -1) { 339 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 340 PAM_RETURN(PAM_SERVICE_ERR); 341 } 342 343 PAM_LOG("Got env_file: %s", env_file); 344 345 /* save the filename so we can delete the file on session close */ 346 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup); 347 if (retval != PAM_SUCCESS) { 348 free(env_file); 349 PAM_RETURN(retval); 350 } 351 352 PAM_LOG("Saved env_file"); 353 354 /* start the agent as the user */ 355 saved_uid = geteuid(); 356 seteuid(pwd->pw_uid); 357 env_fp = fopen(env_file, "w"); 358 if (env_fp != NULL) 359 chmod(env_file, S_IRUSR); 360 pipe = popen(SSH_AGENT, "r"); 361 seteuid(saved_uid); 362 if (!pipe) { 363 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 364 if (env_fp) 365 fclose(env_fp); 366 PAM_RETURN(PAM_SESSION_ERR); 367 } 368 369 PAM_LOG("Agent started as user"); 370 371 /* 372 * Save environment for application with pam_putenv(). 373 */ 374 agent_socket = NULL; 375 while (fgets(env_string, sizeof env_string, pipe)) { 376 if (env_fp) 377 fputs(env_string, env_fp); 378 env_value = strchr(env_string, '='); 379 if (env_value == NULL) { 380 env_end = strchr(env_value, ';'); 381 if (env_end == NULL) 382 continue; 383 *env_end = '\0'; 384 } 385 /* pass to the application ... */ 386 retval = pam_putenv(pamh, env_string); 387 if (retval != PAM_SUCCESS) { 388 pclose(pipe); 389 if (env_fp) 390 fclose(env_fp); 391 PAM_RETURN(PAM_SERVICE_ERR); 392 } 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