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