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 env_swap(self, 0); 182 SLIST_FOREACH(p, &self->e_head, ee_entries) { 183 free(p->ee_env); 184 free(p); 185 } 186 if (self->e_committed) 187 free(self->e_environ_new); 188 free(self); 189 } 190 191 192 void 193 env_cleanup(pam_handle_t *pamh, void *data, int error_status) 194 { 195 if (data) 196 env_destroy(data); 197 } 198 199 200 typedef struct passwd PASSWD; 201 202 PAM_EXTERN int 203 pam_sm_authenticate( 204 pam_handle_t *pamh, 205 int flags, 206 int argc, 207 const char **argv) 208 { 209 char *comment_priv; /* on private key */ 210 char *comment_pub; /* on public key */ 211 char *identity; /* user's identity file */ 212 Key key; /* user's private key */ 213 int options; /* module options */ 214 const char *pass; /* passphrase */ 215 char *prompt; /* passphrase prompt */ 216 Key public_key; /* user's public key */ 217 const PASSWD *pwent; /* user's passwd entry */ 218 PASSWD *pwent_keep; /* our own copy */ 219 int retval; /* from calls */ 220 uid_t saved_uid; /* caller's uid */ 221 const char *user; /* username */ 222 223 options = 0; 224 while (argc--) 225 pam_std_option(&options, *argv++); 226 if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) 227 return retval; 228 if (!((pwent = getpwnam(user)) && pwent->pw_dir)) { 229 /* delay? */ 230 return PAM_AUTH_ERR; 231 } 232 /* locate the user's private key file */ 233 if (!asprintf(&identity, "%s/%s", pwent->pw_dir, 234 SSH_CLIENT_IDENTITY)) { 235 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 236 return PAM_SERVICE_ERR; 237 } 238 /* 239 * Fail unless we can load the public key. Change to the 240 * owner's UID to appease load_public_key(). 241 */ 242 key.type = KEY_RSA; 243 key.rsa = RSA_new(); 244 public_key.type = KEY_RSA; 245 public_key.rsa = RSA_new(); 246 saved_uid = getuid(); 247 (void)setreuid(pwent->pw_uid, saved_uid); 248 retval = load_public_key(identity, &public_key, &comment_pub); 249 (void)setuid(saved_uid); 250 if (!retval) { 251 free(identity); 252 return PAM_AUTH_ERR; 253 } 254 RSA_free(public_key.rsa); 255 /* build the passphrase prompt */ 256 retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub); 257 free(comment_pub); 258 if (!retval) { 259 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 260 free(identity); 261 return PAM_SERVICE_ERR; 262 } 263 /* pass prompt message to application and receive passphrase */ 264 retval = pam_get_pass(pamh, &pass, prompt, options); 265 free(prompt); 266 if (retval != PAM_SUCCESS) { 267 free(identity); 268 return retval; 269 } 270 /* 271 * Try to decrypt the private key with the passphrase provided. 272 * If success, the user is authenticated. 273 */ 274 (void)setreuid(pwent->pw_uid, saved_uid); 275 retval = load_private_key(identity, pass, &key, &comment_priv); 276 free(identity); 277 (void)setuid(saved_uid); 278 if (!retval) 279 return PAM_AUTH_ERR; 280 /* 281 * Save the key and comment to pass to ssh-agent in the session 282 * phase. 283 */ 284 if ((retval = pam_set_data(pamh, "ssh_private_key", key.rsa, 285 rsa_cleanup)) != PAM_SUCCESS) { 286 RSA_free(key.rsa); 287 free(comment_priv); 288 return retval; 289 } 290 if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv, 291 ssh_cleanup)) != PAM_SUCCESS) { 292 free(comment_priv); 293 return retval; 294 } 295 /* 296 * Copy the passwd entry (in case successive calls are made) 297 * and save it for the session phase. 298 */ 299 if (!(pwent_keep = malloc(sizeof *pwent))) { 300 syslog(LOG_CRIT, "%m"); 301 return PAM_SERVICE_ERR; 302 } 303 (void)memcpy(pwent_keep, pwent, sizeof *pwent_keep); 304 if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep, 305 ssh_cleanup)) != PAM_SUCCESS) { 306 free(pwent_keep); 307 return retval; 308 } 309 return PAM_SUCCESS; 310 } 311 312 313 PAM_EXTERN int 314 pam_sm_setcred( 315 pam_handle_t *pamh, 316 int flags, 317 int argc, 318 const char **argv) 319 { 320 return PAM_SUCCESS; 321 } 322 323 324 typedef AuthenticationConnection AC; 325 326 PAM_EXTERN int 327 pam_sm_open_session( 328 pam_handle_t *pamh, 329 int flags, 330 int argc, 331 const char **argv) 332 { 333 AC *ac; /* to ssh-agent */ 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 Key key; /* user's private key */ 339 FILE *pipe; /* ssh-agent handle */ 340 const PASSWD *pwent; /* user's passwd entry */ 341 int retval; /* from calls */ 342 uid_t saved_uid; /* caller's uid */ 343 ENV *ssh_env; /* env handle */ 344 const char *tty; /* tty or display name */ 345 char hname[MAXHOSTNAMELEN]; /* local hostname */ 346 char parse[BUFSIZ]; /* commands output */ 347 348 /* dump output of ssh-agent in ~/.ssh */ 349 if ((retval = pam_get_data(pamh, "ssh_passwd_entry", 350 (const void **)&pwent)) != PAM_SUCCESS) 351 return retval; 352 /* use the tty or X display name in the filename */ 353 if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty)) 354 != PAM_SUCCESS) 355 return retval; 356 if (*tty == ':' && gethostname(hname, sizeof hname) == 0) { 357 if (asprintf(&env_file, "%s/.ssh/agent-%s%s", 358 pwent->pw_dir, hname, tty) == -1) { 359 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 360 return PAM_SERVICE_ERR; 361 } 362 } else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir, 363 tty) == -1) { 364 syslog(LOG_CRIT, "%s: %m", MODULE_NAME); 365 return PAM_SERVICE_ERR; 366 } 367 /* save the filename so we can delete the file on session close */ 368 if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file, 369 ssh_cleanup)) != PAM_SUCCESS) { 370 free(env_file); 371 return retval; 372 } 373 /* start the agent as the user */ 374 saved_uid = geteuid(); 375 (void)seteuid(pwent->pw_uid); 376 env_fp = fopen(env_file, "w"); 377 pipe = popen(PATH_SSH_AGENT, "r"); 378 (void)seteuid(saved_uid); 379 if (!pipe) { 380 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); 381 if (env_fp) 382 (void)fclose(env_fp); 383 return PAM_SESSION_ERR; 384 } 385 if (!(ssh_env = env_new())) 386 return PAM_SESSION_ERR; 387 if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env, 388 env_cleanup)) != PAM_SUCCESS) 389 return retval; 390 while (fgets(parse, sizeof parse, pipe)) { 391 if (env_fp) 392 (void)fputs(parse, env_fp); 393 /* 394 * Save environment for application with pam_putenv() 395 * but also with env_* functions for our own call to 396 * ssh_get_authentication_connection(). 397 */ 398 if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) { 399 *env_end = '\0'; 400 /* pass to the application ... */ 401 if (!((retval = pam_putenv(pamh, parse)) == 402 PAM_SUCCESS)) { 403 (void)pclose(pipe); 404 if (env_fp) 405 (void)fclose(env_fp); 406 env_destroy(ssh_env); 407 return PAM_SERVICE_ERR; 408 } 409 env_put(ssh_env, parse); 410 } 411 } 412 if (env_fp) 413 (void)fclose(env_fp); 414 switch (retval = pclose(pipe)) { 415 case -1: 416 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); 417 env_destroy(ssh_env); 418 return PAM_SESSION_ERR; 419 case 0: 420 break; 421 case 127: 422 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, 423 PATH_SSH_AGENT); 424 env_destroy(ssh_env); 425 return PAM_SESSION_ERR; 426 default: 427 syslog(LOG_ERR, "%s: %s exited with status %d", 428 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); 429 env_destroy(ssh_env); 430 return PAM_SESSION_ERR; 431 } 432 key.type = KEY_RSA; 433 /* connect to the agent and hand off the private key */ 434 if ((retval = pam_get_data(pamh, "ssh_private_key", 435 (const void **)&key.rsa)) != PAM_SUCCESS || 436 (retval = pam_get_data(pamh, "ssh_key_comment", 437 (const void **)&comment)) != PAM_SUCCESS || 438 (retval = env_commit(ssh_env)) != PAM_SUCCESS) { 439 env_destroy(ssh_env); 440 return retval; 441 } 442 if (!(ac = ssh_get_authentication_connection())) { 443 syslog(LOG_ERR, "%s: could not connect to agent", 444 MODULE_NAME); 445 env_destroy(ssh_env); 446 return PAM_SESSION_ERR; 447 } 448 retval = ssh_add_identity(ac, &key, comment); 449 ssh_close_authentication_connection(ac); 450 env_swap(ssh_env, 0); 451 return retval ? PAM_SUCCESS : PAM_SESSION_ERR; 452 } 453 454 455 PAM_EXTERN int 456 pam_sm_close_session( 457 pam_handle_t *pamh, 458 int flags, 459 int argc, 460 const char **argv) 461 { 462 const char *env_file; /* ssh-agent environment */ 463 int retval; /* from calls */ 464 ENV *ssh_env; /* env handle */ 465 466 if ((retval = pam_get_data(pamh, "ssh_env_handle", 467 (const void **)&ssh_env)) != PAM_SUCCESS) 468 return retval; 469 env_swap(ssh_env, 1); 470 /* kill the agent */ 471 retval = system(PATH_SSH_AGENT " -k"); 472 env_destroy(ssh_env); 473 switch (retval) { 474 case -1: 475 syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME, 476 PATH_SSH_AGENT); 477 return PAM_SESSION_ERR; 478 case 0: 479 break; 480 case 127: 481 syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME, 482 PATH_SSH_AGENT); 483 return PAM_SESSION_ERR; 484 default: 485 syslog(LOG_ERR, "%s: %s -k exited with status %d", 486 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); 487 return PAM_SESSION_ERR; 488 } 489 /* retrieve environment filename, then remove the file */ 490 if ((retval = pam_get_data(pamh, "ssh_agent_env", 491 (const void **)&env_file)) != PAM_SUCCESS) 492 return retval; 493 (void)unlink(env_file); 494 return PAM_SUCCESS; 495 } 496 497 498 PAM_MODULE_ENTRY(MODULE_NAME); 499