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