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