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