1 /*- 2 * Copyright (c) 2003 Networks Associates Technology, Inc. 3 * All rights reserved. 4 * 5 * This software was developed for the FreeBSD Project by ThinkSec AS and 6 * NAI Labs, the Security Research Division of Network Associates, Inc. 7 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 8 * DARPA CHATS research program. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. The name of the author may not be used to endorse or promote 19 * products derived from this software without specific prior written 20 * permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <sys/param.h> 39 #include <sys/wait.h> 40 41 #include <errno.h> 42 #include <fcntl.h> 43 #include <paths.h> 44 #include <pwd.h> 45 #include <signal.h> 46 #include <stdio.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #define PAM_SM_AUTH 51 #define PAM_SM_SESSION 52 53 #include <security/pam_appl.h> 54 #include <security/pam_modules.h> 55 #include <security/openpam.h> 56 57 #include <openssl/evp.h> 58 59 #include "key.h" 60 #include "buffer.h" 61 #include "authfd.h" 62 #include "authfile.h" 63 64 extern char **environ; 65 66 struct pam_ssh_key { 67 Key *key; 68 char *comment; 69 }; 70 71 static const char *pam_ssh_prompt = "SSH passphrase: "; 72 static const char *pam_ssh_have_keys = "pam_ssh_have_keys"; 73 74 static const char *pam_ssh_keyfiles[] = { 75 ".ssh/identity", /* SSH1 RSA key */ 76 ".ssh/id_rsa", /* SSH2 RSA key */ 77 ".ssh/id_dsa", /* SSH2 DSA key */ 78 NULL 79 }; 80 81 static const char *pam_ssh_agent = "/usr/bin/ssh-agent"; 82 static char *const pam_ssh_agent_argv[] = { "ssh_agent", "-s", NULL }; 83 static char *const pam_ssh_agent_envp[] = { NULL }; 84 85 /* 86 * Attempts to load a private key from the specified file in the specified 87 * directory, using the specified passphrase. If successful, returns a 88 * struct pam_ssh_key containing the key and its comment. 89 */ 90 static struct pam_ssh_key * 91 pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase) 92 { 93 struct pam_ssh_key *psk; 94 char fn[PATH_MAX]; 95 char *comment; 96 Key *key; 97 98 if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn)) 99 return (NULL); 100 comment = NULL; 101 key = key_load_private(fn, passphrase, &comment); 102 if (key == NULL) { 103 openpam_log(PAM_LOG_DEBUG, "failed to load key from %s\n", fn); 104 return (NULL); 105 } 106 107 openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s\n", comment, fn); 108 if ((psk = malloc(sizeof(*psk))) == NULL) { 109 key_free(key); 110 free(comment); 111 return (NULL); 112 } 113 psk->key = key; 114 psk->comment = comment; 115 return (psk); 116 } 117 118 /* 119 * Wipes a private key and frees the associated resources. 120 */ 121 static void 122 pam_ssh_free_key(pam_handle_t *pamh __unused, 123 void *data, int pam_err __unused) 124 { 125 struct pam_ssh_key *psk; 126 127 psk = data; 128 key_free(psk->key); 129 free(psk->comment); 130 free(psk); 131 } 132 133 PAM_EXTERN int 134 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 135 int argc __unused, const char *argv[] __unused) 136 { 137 const char **kfn, *passphrase, *user; 138 const void *item; 139 struct passwd *pwd; 140 struct pam_ssh_key *psk; 141 int nkeys, nullok, pam_err, pass; 142 143 nullok = (openpam_get_option(pamh, "nullok") != NULL); 144 145 /* PEM is not loaded by default */ 146 OpenSSL_add_all_algorithms(); 147 148 /* get user name and home directory */ 149 pam_err = pam_get_user(pamh, &user, NULL); 150 if (pam_err != PAM_SUCCESS) 151 return (pam_err); 152 pwd = getpwnam(user); 153 if (pwd == NULL) 154 return (PAM_USER_UNKNOWN); 155 if (pwd->pw_dir == NULL) 156 return (PAM_AUTH_ERR); 157 158 nkeys = 0; 159 pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS && 160 item != NULL); 161 load_keys: 162 /* get passphrase */ 163 pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, 164 &passphrase, pam_ssh_prompt); 165 if (pam_err != PAM_SUCCESS) 166 return (pam_err); 167 168 if (*passphrase == '\0' && !nullok) 169 goto skip_keys; 170 171 /* switch to user credentials */ 172 pam_err = openpam_borrow_cred(pamh, pwd); 173 if (pam_err != PAM_SUCCESS) 174 return (pam_err); 175 176 /* try to load keys from all keyfiles we know of */ 177 for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 178 psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase); 179 if (psk != NULL) { 180 pam_set_data(pamh, *kfn, psk, pam_ssh_free_key); 181 ++nkeys; 182 } 183 } 184 185 /* switch back to arbitrator credentials */ 186 openpam_restore_cred(pamh); 187 188 skip_keys: 189 /* 190 * If we tried an old token and didn't get anything, and 191 * try_first_pass was specified, try again after prompting the 192 * user for a new passphrase. 193 */ 194 if (nkeys == 0 && pass == 1 && 195 openpam_get_option(pamh, "try_first_pass") != NULL) { 196 pam_set_item(pamh, PAM_AUTHTOK, NULL); 197 pass = 0; 198 goto load_keys; 199 } 200 201 /* no keys? */ 202 if (nkeys == 0) 203 return (PAM_AUTH_ERR); 204 205 pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL); 206 return (PAM_SUCCESS); 207 } 208 209 PAM_EXTERN int 210 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 211 int argc __unused, const char *argv[] __unused) 212 { 213 214 return (PAM_SUCCESS); 215 } 216 217 /* 218 * Parses a line from ssh-agent's output. 219 */ 220 static void 221 pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f) 222 { 223 char *line, *p, *key, *val; 224 size_t len; 225 226 while ((line = fgetln(f, &len)) != NULL) { 227 if (len < 4 || strncmp(line, "SSH_", 4) != 0) 228 continue; 229 230 /* find equal sign at end of key */ 231 for (p = key = line; p < line + len; ++p) 232 if (*p == '=') 233 break; 234 if (p == line + len || *p != '=') 235 continue; 236 *p = '\0'; 237 238 /* find semicolon at end of value */ 239 for (val = ++p; p < line + len; ++p) 240 if (*p == ';') 241 break; 242 if (p == line + len || *p != ';') 243 continue; 244 *p = '\0'; 245 246 /* store key-value pair in environment */ 247 openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val); 248 pam_setenv(pamh, key, val, 1); 249 } 250 } 251 252 /* 253 * Starts an ssh agent and stores the environment variables derived from 254 * its output. 255 */ 256 static int 257 pam_ssh_start_agent(pam_handle_t *pamh) 258 { 259 int agent_pipe[2]; 260 pid_t pid; 261 FILE *f; 262 263 /* get a pipe which we will use to read the agent's output */ 264 if (pipe(agent_pipe) == -1) 265 return (PAM_SYSTEM_ERR); 266 267 /* start the agent */ 268 openpam_log(PAM_LOG_DEBUG, "starting an ssh agent"); 269 pid = fork(); 270 if (pid == (pid_t)-1) { 271 /* failed */ 272 close(agent_pipe[0]); 273 close(agent_pipe[1]); 274 return (PAM_SYSTEM_ERR); 275 } 276 if (pid == 0) { 277 int fd; 278 279 /* child: drop privs, close fds and start agent */ 280 setgid(getegid()); 281 setuid(geteuid()); 282 close(STDIN_FILENO); 283 open(_PATH_DEVNULL, O_RDONLY); 284 dup2(agent_pipe[1], STDOUT_FILENO); 285 dup2(agent_pipe[1], STDERR_FILENO); 286 for (fd = 3; fd < getdtablesize(); ++fd) 287 close(fd); 288 execve(pam_ssh_agent, pam_ssh_agent_argv, pam_ssh_agent_envp); 289 _exit(127); 290 } 291 292 /* parent */ 293 close(agent_pipe[1]); 294 if ((f = fdopen(agent_pipe[0], "r")) == NULL) 295 return (PAM_SYSTEM_ERR); 296 pam_ssh_process_agent_output(pamh, f); 297 fclose(f); 298 299 return (PAM_SUCCESS); 300 } 301 302 /* 303 * Adds previously stored keys to a running agent. 304 */ 305 static int 306 pam_ssh_add_keys_to_agent(pam_handle_t *pamh) 307 { 308 AuthenticationConnection *ac; 309 const struct pam_ssh_key *psk; 310 const char **kfn; 311 const void *item; 312 char **envlist, **env; 313 int pam_err; 314 315 /* switch to PAM environment */ 316 envlist = environ; 317 if ((environ = pam_getenvlist(pamh)) == NULL) { 318 environ = envlist; 319 return (PAM_SYSTEM_ERR); 320 } 321 322 /* get a connection to the agent */ 323 if ((ac = ssh_get_authentication_connection()) == NULL) { 324 pam_err = PAM_SYSTEM_ERR; 325 goto end; 326 } 327 328 /* look for keys to add to it */ 329 for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 330 pam_err = pam_get_data(pamh, *kfn, &item); 331 if (pam_err == PAM_SUCCESS && item != NULL) { 332 psk = item; 333 if (ssh_add_identity(ac, psk->key, psk->comment)) 334 openpam_log(PAM_LOG_DEBUG, 335 "added %s to ssh agent", psk->comment); 336 else 337 openpam_log(PAM_LOG_DEBUG, "failed " 338 "to add %s to ssh agent", psk->comment); 339 /* we won't need the key again, so wipe it */ 340 pam_set_data(pamh, *kfn, NULL, NULL); 341 } 342 } 343 pam_err = PAM_SUCCESS; 344 end: 345 /* disconnect from agent */ 346 if (ac != NULL) 347 ssh_close_authentication_connection(ac); 348 349 /* switch back to original environment */ 350 for (env = environ; *env != NULL; ++env) 351 free(*env); 352 free(environ); 353 environ = envlist; 354 355 return (pam_err); 356 } 357 358 PAM_EXTERN int 359 pam_sm_open_session(pam_handle_t *pamh, int flags __unused, 360 int argc __unused, const char *argv[] __unused) 361 { 362 struct passwd *pwd; 363 const char *user; 364 const void *data; 365 int pam_err; 366 367 /* no keys, no work */ 368 if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS && 369 openpam_get_option(pamh, "want_agent") == NULL) 370 return (PAM_SUCCESS); 371 372 /* switch to user credentials */ 373 pam_err = pam_get_user(pamh, &user, NULL); 374 if (pam_err != PAM_SUCCESS) 375 return (pam_err); 376 pwd = getpwnam(user); 377 if (pwd == NULL) 378 return (PAM_USER_UNKNOWN); 379 pam_err = openpam_borrow_cred(pamh, pwd); 380 if (pam_err != PAM_SUCCESS) 381 return (pam_err); 382 383 /* start the agent */ 384 pam_err = pam_ssh_start_agent(pamh); 385 if (pam_err != PAM_SUCCESS) { 386 openpam_restore_cred(pamh); 387 return (pam_err); 388 } 389 390 /* we have an agent, see if we can add any keys to it */ 391 pam_err = pam_ssh_add_keys_to_agent(pamh); 392 if (pam_err != PAM_SUCCESS) { 393 /* XXX ignore failures */ 394 } 395 396 openpam_restore_cred(pamh); 397 return (PAM_SUCCESS); 398 } 399 400 PAM_EXTERN int 401 pam_sm_close_session(pam_handle_t *pamh, int flags __unused, 402 int argc __unused, const char *argv[] __unused) 403 { 404 const char *ssh_agent_pid; 405 char *end; 406 int status; 407 pid_t pid; 408 409 if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) { 410 openpam_log(PAM_LOG_DEBUG, "no ssh agent"); 411 return (PAM_SUCCESS); 412 } 413 pid = (pid_t)strtol(ssh_agent_pid, &end, 10); 414 if (*ssh_agent_pid == '\0' || *end != '\0') { 415 openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid"); 416 return (PAM_SESSION_ERR); 417 } 418 openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid); 419 if (kill(pid, SIGTERM) == -1 || 420 (waitpid(pid, &status, 0) == -1 && errno != ECHILD)) 421 return (PAM_SYSTEM_ERR); 422 return (PAM_SUCCESS); 423 } 424 425 PAM_MODULE_ENTRY("pam_ssh"); 426