pam_ssh.c revision 60938
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 60938 2000-05-26 02:09:24Z jake $ 27 * 28 */ 29 30 31#include <sys/param.h> 32#include <sys/queue.h> 33#include <sys/stat.h> 34 35#include <fcntl.h> 36#include <paths.h> 37#include <pwd.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 "includes.h" 49#include "rsa.h" 50#include "ssh.h" 51#include "authfd.h" 52 53#define MODULE_NAME "pam_ssh" 54#define NEED_PASSPHRASE "Need passphrase for %s (%s).\nEnter passphrase: " 55#define PATH_SSH_AGENT "/usr/bin/ssh-agent" 56 57 58void 59rsa_cleanup(pam_handle_t *pamh, void *data, int error_status) 60{ 61 if (data) 62 RSA_free(data); 63} 64 65 66void 67ssh_cleanup(pam_handle_t *pamh, void *data, int error_status) 68{ 69 if (data) 70 free(data); 71} 72 73 74/* 75 * The following set of functions allow the module to manipulate the 76 * environment without calling the putenv() or setenv() stdlib functions. 77 * At least one version of these functions, on the first call, copies 78 * the environment into dynamically-allocated memory and then augments 79 * it. On subsequent calls, the realloc() call is used to grow the 80 * previously allocated buffer. Problems arise when the "environ" 81 * variable is changed to point to static memory after putenv()/setenv() 82 * have been called. 83 * 84 * We don't use putenv() or setenv() in case the application subsequently 85 * manipulates environ, (e.g., to clear the environment by pointing 86 * environ at an array of one element equal to NULL). 87 */ 88 89SLIST_HEAD(env_head, env_entry); 90 91struct env_entry { 92 char *ee_env; 93 SLIST_ENTRY(env_entry) ee_entries; 94}; 95 96typedef struct env { 97 char **e_environ_orig; 98 char **e_environ_new; 99 int e_count; 100 struct env_head e_head; 101 int e_committed; 102} ENV; 103 104extern char **environ; 105 106 107static ENV * 108env_new(void) 109{ 110 ENV *self; 111 112 if (!(self = malloc(sizeof (ENV)))) { 113 syslog(LOG_CRIT, "%m"); 114 return NULL; 115 } 116 SLIST_INIT(&self->e_head); 117 self->e_count = 0; 118 self->e_committed = 0; 119 return self; 120} 121 122 123static int 124env_put(ENV *self, const char *s) 125{ 126 struct env_entry *env; 127 128 if (!(env = malloc(sizeof (struct env_entry))) || 129 !(env->ee_env = strdup(s))) { 130 syslog(LOG_CRIT, "%m"); 131 return PAM_SERVICE_ERR; 132 } 133 SLIST_INSERT_HEAD(&self->e_head, env, ee_entries); 134 ++self->e_count; 135 return PAM_SUCCESS; 136} 137 138 139static void 140env_swap(const ENV *self, int which) 141{ 142 environ = which ? self->e_environ_new : self->e_environ_orig; 143} 144 145 146static int 147env_commit(ENV *self) 148{ 149 int n; 150 struct env_entry *p; 151 char **v; 152 153 for (v = environ, n = 0; v && *v; v++, n++) 154 ; 155 if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) { 156 syslog(LOG_CRIT, "%m"); 157 return PAM_SERVICE_ERR; 158 } 159 self->e_committed = 1; 160 (void)memcpy(v, environ, n * sizeof (char *)); 161 SLIST_FOREACH(p, &self->e_head, ee_entries) 162 v[n++] = p->ee_env; 163 v[n] = NULL; 164 self->e_environ_orig = environ; 165 self->e_environ_new = v; 166 env_swap(self, 1); 167 return PAM_SUCCESS; 168} 169 170 171static void 172env_destroy(ENV *self) 173{ 174 struct env_entry *p; 175 176 env_swap(self, 0); 177 while ((p = SLIST_FIRST(&self->e_head))) { 178 free(p->ee_env); 179 free(p); 180 SLIST_REMOVE_HEAD(&self->e_head, ee_entries); 181 } 182 if (self->e_committed) 183 free(self->e_environ_new); 184 free(self); 185} 186 187 188void 189env_cleanup(pam_handle_t *pamh, void *data, int error_status) 190{ 191 if (data) 192 env_destroy(data); 193} 194 195 196typedef struct passwd PASSWD; 197 198PAM_EXTERN int 199pam_sm_authenticate( 200 pam_handle_t *pamh, 201 int flags, 202 int argc, 203 const char **argv) 204{ 205 char *comment_priv; /* on private key */ 206 char *comment_pub; /* on public key */ 207 char *identity; /* user's identity file */ 208 RSA *key; /* user's private key */ 209 int options; /* module options */ 210 const char *pass; /* passphrase */ 211 char *prompt; /* passphrase prompt */ 212 RSA *public_key; /* user's public key */ 213 const PASSWD *pwent; /* user's passwd entry */ 214 PASSWD *pwent_keep; /* our own copy */ 215 int retval; /* from calls */ 216 uid_t saved_uid; /* caller's uid */ 217 const char *user; /* username */ 218 219 options = 0; 220 while (argc--) 221 pam_std_option(&options, *argv++); 222 if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) 223 return retval; 224 if (!((pwent = getpwnam(user)) && pwent->pw_dir)) { 225 /* delay? */ 226 return PAM_AUTH_ERR; 227 } 228 /* locate the user's private key file */ 229 if (!asprintf(&identity, "%s/%s", pwent->pw_dir, 230 SSH_CLIENT_IDENTITY)) { 231 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 232 return PAM_SERVICE_ERR; 233 } 234 /* 235 * Fail unless we can load the public key. Change to the 236 * owner's UID to appease load_public_key(). 237 */ 238 key = RSA_new(); 239 public_key = RSA_new(); 240 saved_uid = getuid(); 241 (void)setreuid(pwent->pw_uid, saved_uid); 242 retval = load_public_key(identity, public_key, &comment_pub); 243 (void)setuid(saved_uid); 244 if (!retval) { 245 free(identity); 246 return PAM_AUTH_ERR; 247 } 248 RSA_free(public_key); 249 /* build the passphrase prompt */ 250 retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub); 251 free(comment_pub); 252 if (!retval) { 253 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 254 free(identity); 255 return PAM_SERVICE_ERR; 256 } 257 /* pass prompt message to application and receive passphrase */ 258 retval = pam_get_pass(pamh, &pass, prompt, options); 259 free(prompt); 260 if (retval != PAM_SUCCESS) { 261 free(identity); 262 return retval; 263 } 264 /* 265 * Try to decrypt the private key with the passphrase provided. 266 * If success, the user is authenticated. 267 */ 268 (void)setreuid(pwent->pw_uid, saved_uid); 269 retval = load_private_key(identity, pass, key, &comment_priv); 270 free(identity); 271 (void)setuid(saved_uid); 272 if (!retval) 273 return PAM_AUTH_ERR; 274 /* 275 * Save the key and comment to pass to ssh-agent in the session 276 * phase. 277 */ 278 if ((retval = pam_set_data(pamh, "ssh_private_key", key, 279 rsa_cleanup)) != PAM_SUCCESS) { 280 RSA_free(key); 281 free(comment_priv); 282 return retval; 283 } 284 if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv, 285 ssh_cleanup)) != PAM_SUCCESS) { 286 free(comment_priv); 287 return retval; 288 } 289 /* 290 * Copy the passwd entry (in case successive calls are made) 291 * and save it for the session phase. 292 */ 293 if (!(pwent_keep = malloc(sizeof *pwent))) { 294 syslog(LOG_CRIT, "%m"); 295 return PAM_SERVICE_ERR; 296 } 297 (void)memcpy(pwent_keep, pwent, sizeof *pwent_keep); 298 if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep, 299 ssh_cleanup)) != PAM_SUCCESS) { 300 free(pwent_keep); 301 return retval; 302 } 303 return PAM_SUCCESS; 304} 305 306 307PAM_EXTERN int 308pam_sm_setcred( 309 pam_handle_t *pamh, 310 int flags, 311 int argc, 312 const char **argv) 313{ 314 return PAM_SUCCESS; 315} 316 317 318typedef AuthenticationConnection AC; 319 320PAM_EXTERN int 321pam_sm_open_session( 322 pam_handle_t *pamh, 323 int flags, 324 int argc, 325 const char **argv) 326{ 327 AC *ac; /* to ssh-agent */ 328 char *comment; /* on private key */ 329 char *env_end; /* end of env */ 330 char *env_file; /* to store env */ 331 FILE *env_fp; /* env_file handle */ 332 RSA *key; /* user's private key */ 333 FILE *pipe; /* ssh-agent handle */ 334 const PASSWD *pwent; /* user's passwd entry */ 335 int retval; /* from calls */ 336 uid_t saved_uid; /* caller's uid */ 337 ENV *ssh_env; /* env handle */ 338 const char *tty; /* tty or display name */ 339 char hname[MAXHOSTNAMELEN]; /* local hostname */ 340 char parse[BUFSIZ]; /* commands output */ 341 342 /* dump output of ssh-agent in ~/.ssh */ 343 if ((retval = pam_get_data(pamh, "ssh_passwd_entry", 344 (const void **)&pwent)) != PAM_SUCCESS) 345 return retval; 346 /* use the tty or X display name in the filename */ 347 if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty)) 348 != PAM_SUCCESS) 349 return retval; 350 if (*tty == ':' && gethostname(hname, sizeof hname) == 0) { 351 if (asprintf(&env_file, "%s/.ssh/agent-%s%s", 352 pwent->pw_dir, hname, tty) == -1) { 353 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 354 return PAM_SERVICE_ERR; 355 } 356 } else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir, 357 tty) == -1) { 358 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 359 return PAM_SERVICE_ERR; 360 } 361 /* save the filename so we can delete the file on session close */ 362 if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file, 363 ssh_cleanup)) != PAM_SUCCESS) { 364 free(env_file); 365 return retval; 366 } 367 /* start the agent as the user */ 368 saved_uid = geteuid(); 369 (void)seteuid(pwent->pw_uid); 370 if ((env_fp = fopen(env_file, "w"))) 371 (void)chmod(env_file, S_IRUSR); 372 pipe = popen(PATH_SSH_AGENT, "r"); 373 (void)seteuid(saved_uid); 374 if (!pipe) { 375 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); 376 if (env_fp) 377 (void)fclose(env_fp); 378 return PAM_SESSION_ERR; 379 } 380 if (!(ssh_env = env_new())) 381 return PAM_SESSION_ERR; 382 if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env, 383 env_cleanup)) != PAM_SUCCESS) 384 return retval; 385 while (fgets(parse, sizeof parse, pipe)) { 386 if (env_fp) 387 (void)fputs(parse, env_fp); 388 /* 389 * Save environment for application with pam_putenv() 390 * but also with env_* functions for our own call to 391 * ssh_get_authentication_connection(). 392 */ 393 if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) { 394 *env_end = '\0'; 395 /* pass to the application ... */ 396 if (!((retval = pam_putenv(pamh, parse)) == 397 PAM_SUCCESS)) { 398 (void)pclose(pipe); 399 if (env_fp) 400 (void)fclose(env_fp); 401 env_destroy(ssh_env); 402 return PAM_SERVICE_ERR; 403 } 404 env_put(ssh_env, parse); 405 } 406 } 407 if (env_fp) 408 (void)fclose(env_fp); 409 switch (retval = pclose(pipe)) { 410 case -1: 411 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); 412 env_destroy(ssh_env); 413 return PAM_SESSION_ERR; 414 case 0: 415 break; 416 case 127: 417 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, 418 PATH_SSH_AGENT); 419 env_destroy(ssh_env); 420 return PAM_SESSION_ERR; 421 default: 422 syslog(LOG_ERR, "%s: %s exited with status %d", 423 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); 424 env_destroy(ssh_env); 425 return PAM_SESSION_ERR; 426 } 427 /* connect to the agent and hand off the private key */ 428 if ((retval = pam_get_data(pamh, "ssh_private_key", 429 (const void **)&key)) != PAM_SUCCESS || 430 (retval = pam_get_data(pamh, "ssh_key_comment", 431 (const void **)&comment)) != PAM_SUCCESS || 432 (retval = env_commit(ssh_env)) != PAM_SUCCESS) { 433 env_destroy(ssh_env); 434 return retval; 435 } 436 if (!(ac = ssh_get_authentication_connection())) { 437 syslog(LOG_ERR, "%s: could not connect to agent", 438 MODULE_NAME); 439 env_destroy(ssh_env); 440 return PAM_SESSION_ERR; 441 } 442 retval = ssh_add_identity(ac, key, comment); 443 ssh_close_authentication_connection(ac); 444 env_swap(ssh_env, 0); 445 return retval ? PAM_SUCCESS : PAM_SESSION_ERR; 446} 447 448 449PAM_EXTERN int 450pam_sm_close_session( 451 pam_handle_t *pamh, 452 int flags, 453 int argc, 454 const char **argv) 455{ 456 const char *env_file; /* ssh-agent environment */ 457 int retval; /* from calls */ 458 ENV *ssh_env; /* env handle */ 459 460 if ((retval = pam_get_data(pamh, "ssh_env_handle", 461 (const void **)&ssh_env)) != PAM_SUCCESS) 462 return retval; 463 env_swap(ssh_env, 1); 464 /* kill the agent */ 465 retval = system(PATH_SSH_AGENT " -k"); 466 env_destroy(ssh_env); 467 switch (retval) { 468 case -1: 469 syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME, 470 PATH_SSH_AGENT); 471 return PAM_SESSION_ERR; 472 case 0: 473 break; 474 case 127: 475 syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME, 476 PATH_SSH_AGENT); 477 return PAM_SESSION_ERR; 478 default: 479 syslog(LOG_ERR, "%s: %s -k exited with status %d", 480 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); 481 return PAM_SESSION_ERR; 482 } 483 /* retrieve environment filename, then remove the file */ 484 if ((retval = pam_get_data(pamh, "ssh_agent_env", 485 (const void **)&env_file)) != PAM_SUCCESS) 486 return retval; 487 (void)unlink(env_file); 488 return PAM_SUCCESS; 489} 490 491 492PAM_MODULE_ENTRY(MODULE_NAME); 493