pam_ssh.c revision 81036
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 81036 2001-08-02 10:35:41Z 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_RETURN(PAM_AUTH_ERR); 246 247 PAM_LOG("Done authenticating; got %d", authenticated); 248 249 /* 250 * Copy the passwd entry (in case successive calls are made) 251 * and save it for the session phase. 252 */ 253 pwd_keep = malloc(sizeof *pwd); 254 if (pwd_keep == NULL) { 255 syslog(LOG_CRIT, "%m"); 256 PAM_RETURN(PAM_SERVICE_ERR); 257 } 258 memcpy(pwd_keep, pwd, sizeof *pwd_keep); 259 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup); 260 if (retval != PAM_SUCCESS) { 261 free(pwd_keep); 262 PAM_RETURN(retval); 263 } 264 265 PAM_LOG("Saved ssh_passwd_entry"); 266 267 PAM_RETURN(PAM_SUCCESS); 268} 269 270 271PAM_EXTERN int 272pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 273{ 274 struct options options; /* module options */ 275 276 pam_std_option(&options, NULL, argc, argv); 277 278 PAM_LOG("Options processed"); 279 280 PAM_RETURN(PAM_SUCCESS); 281} 282 283 284typedef AuthenticationConnection AC; 285 286PAM_EXTERN int 287pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 288{ 289 struct options options; /* module options */ 290 AC *ac; /* to ssh-agent */ 291 char *agent_socket; /* agent socket */ 292 char *comment; /* on private key */ 293 char *env_end; /* end of env */ 294 char *env_file; /* to store env */ 295 FILE *env_fp; /* env_file handle */ 296 char *env_value; /* envariable value */ 297 char *data_name; /* PAM state */ 298 int final; /* final return value */ 299 int index; /* for saved keys */ 300 Key *key; /* user's private key */ 301 FILE *pipe; /* ssh-agent handle */ 302 struct passwd *pwd; /* user's passwd entry */ 303 int retval; /* from calls */ 304 uid_t saved_uid; /* caller's uid */ 305 const char *tty; /* tty or display name */ 306 char hname[MAXHOSTNAMELEN]; /* local hostname */ 307 char env_string[BUFSIZ]; /* environment string */ 308 309 pam_std_option(&options, NULL, argc, argv); 310 311 PAM_LOG("Options processed"); 312 313 /* dump output of ssh-agent in ~/.ssh */ 314 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd); 315 if (retval != PAM_SUCCESS) 316 PAM_RETURN(retval); 317 318 PAM_LOG("Got ssh_passwd_entry"); 319 320 /* use the tty or X display name in the filename */ 321 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 322 if (retval != PAM_SUCCESS) 323 PAM_RETURN(retval); 324 325 PAM_LOG("Got TTY"); 326 327 if (gethostname(hname, sizeof hname) == 0) { 328 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s", 329 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty) 330 == -1) { 331 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 332 PAM_RETURN(PAM_SERVICE_ERR); 333 } 334 } 335 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir, 336 tty) == -1) { 337 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 338 PAM_RETURN(PAM_SERVICE_ERR); 339 } 340 341 PAM_LOG("Got env_file: %s", env_file); 342 343 /* save the filename so we can delete the file on session close */ 344 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup); 345 if (retval != PAM_SUCCESS) { 346 free(env_file); 347 PAM_RETURN(retval); 348 } 349 350 PAM_LOG("Saved env_file"); 351 352 /* start the agent as the user */ 353 saved_uid = geteuid(); 354 seteuid(pwd->pw_uid); 355 env_fp = fopen(env_file, "w"); 356 if (env_fp != NULL) 357 chmod(env_file, S_IRUSR); 358 pipe = popen(SSH_AGENT, "r"); 359 seteuid(saved_uid); 360 if (!pipe) { 361 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 362 if (env_fp) 363 fclose(env_fp); 364 PAM_RETURN(PAM_SESSION_ERR); 365 } 366 367 PAM_LOG("Agent started as user"); 368 369 /* 370 * Save environment for application with pam_putenv(). 371 */ 372 agent_socket = NULL; 373 while (fgets(env_string, sizeof env_string, pipe)) { 374 if (env_fp) 375 fputs(env_string, env_fp); 376 env_value = strchr(env_string, '='); 377 if (env_value == NULL) { 378 env_end = strchr(env_value, ';'); 379 if (env_end != NULL) 380 continue; 381 } 382 *env_end = '\0'; 383 /* pass to the application ... */ 384 retval = pam_putenv(pamh, env_string); 385 if (retval != PAM_SUCCESS) { 386 pclose(pipe); 387 if (env_fp) 388 fclose(env_fp); 389 PAM_RETURN(PAM_SERVICE_ERR); 390 } 391 *env_value++ = '\0'; 392 if (strcmp(&env_string[strlen(env_string) - 393 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) { 394 agent_socket = strdup(env_value); 395 if (agent_socket == NULL) { 396 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 397 PAM_RETURN(PAM_SERVICE_ERR); 398 } 399 } 400 else if (strcmp(&env_string[strlen(env_string) - 401 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) { 402 retval = pam_set_data(pamh, "ssh_agent_pid", 403 env_value, ssh_cleanup); 404 if (retval != PAM_SUCCESS) 405 PAM_RETURN(retval); 406 } 407 } 408 if (env_fp) 409 fclose(env_fp); 410 retval = pclose(pipe); 411 switch (retval) { 412 case -1: 413 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT); 414 PAM_RETURN(PAM_SESSION_ERR); 415 case 0: 416 break; 417 case 127: 418 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, 419 SSH_AGENT); 420 PAM_RETURN(PAM_SESSION_ERR); 421 default: 422 syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME, 423 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" : 424 "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) : 425 WEXITSTATUS(retval)); 426 PAM_RETURN(PAM_SESSION_ERR); 427 } 428 if (agent_socket == NULL) 429 PAM_RETURN(PAM_SESSION_ERR); 430 431 PAM_LOG("Environment saved"); 432 433 /* connect to the agent */ 434 ac = ssh_get_authentication_connection(); 435 if (!ac) { 436 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket); 437 PAM_RETURN(PAM_SESSION_ERR); 438 } 439 440 PAM_LOG("Connected to agent"); 441 442 /* hand off each private key to the agent */ 443 final = 0; 444 for (index = 0; ; index++) { 445 if (!asprintf(&data_name, "ssh_private_key_%d", index)) { 446 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 447 ssh_close_authentication_connection(ac); 448 PAM_RETURN(PAM_SERVICE_ERR); 449 } 450 retval = pam_get_data(pamh, data_name, (const void **)&key); 451 free(data_name); 452 if (retval != PAM_SUCCESS) 453 break; 454 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) { 455 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 456 ssh_close_authentication_connection(ac); 457 PAM_RETURN(PAM_SERVICE_ERR); 458 } 459 retval = pam_get_data(pamh, data_name, (const void **)&comment); 460 free(data_name); 461 if (retval != PAM_SUCCESS) 462 break; 463 retval = ssh_add_identity(ac, key, comment); 464 if (!final) 465 final = retval; 466 } 467 ssh_close_authentication_connection(ac); 468 469 PAM_LOG("Keys handed off"); 470 471 PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR); 472} 473 474 475PAM_EXTERN int 476pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 477{ 478 struct options options; /* module options */ 479 const char *env_file; /* ssh-agent environment */ 480 pid_t pid; /* ssh-agent process id */ 481 int retval; /* from calls */ 482 const char *ssh_agent_pid; /* ssh-agent pid string */ 483 484 pam_std_option(&options, NULL, argc, argv); 485 486 PAM_LOG("Options processed"); 487 488 /* retrieve environment filename, then remove the file */ 489 retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file); 490 if (retval != PAM_SUCCESS) 491 PAM_RETURN(retval); 492 unlink(env_file); 493 494 PAM_LOG("Got ssh_agent_env"); 495 496 /* retrieve the agent's process id */ 497 retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid); 498 if (retval != PAM_SUCCESS) 499 PAM_RETURN(retval); 500 501 PAM_LOG("Got ssh_agent_pid"); 502 503 /* 504 * Kill the agent. SSH2 from SSH Communications Security does 505 * not have a -k option, so we just call kill(). 506 */ 507 pid = atoi(ssh_agent_pid); 508 if (pid <= 0) 509 PAM_RETURN(PAM_SESSION_ERR); 510 if (kill(pid, SIGTERM) != 0) { 511 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid); 512 PAM_RETURN(PAM_SESSION_ERR); 513 } 514 515 PAM_LOG("Agent killed"); 516 517 PAM_RETURN(PAM_SUCCESS); 518} 519 520 521PAM_MODULE_ENTRY(MODULE_NAME); 522