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