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 "authfd.h" 61 #include "authfile.h" 62 63 extern char **environ; 64 65 struct pam_ssh_key { 66 Key *key; 67 char *comment; 68 }; 69 70 static const char *pam_ssh_prompt = "SSH passphrase: "; 71 static const char *pam_ssh_have_keys = "pam_ssh_have_keys"; 72 73 static const char *pam_ssh_keyfiles[] = { 74 ".ssh/identity", /* SSH1 RSA key */ 75 ".ssh/id_rsa", /* SSH2 RSA key */ 76 ".ssh/id_dsa", /* SSH2 DSA key */ 77 NULL 78 }; 79 80 static const char *pam_ssh_agent = "/usr/bin/ssh-agent"; 81 static char *const pam_ssh_agent_argv[] = { "ssh_agent", "-s", NULL }; 82 static char *const pam_ssh_agent_envp[] = { NULL }; 83 84 /* 85 * Attempts to load a private key from the specified file in the specified 86 * directory, using the specified passphrase. If successful, returns a 87 * struct pam_ssh_key containing the key and its comment. 88 */ 89 static struct pam_ssh_key * 90 pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase) 91 { 92 struct pam_ssh_key *psk; 93 char fn[PATH_MAX]; 94 char *comment; 95 Key *key; 96 97 if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn)) 98 return (NULL); 99 comment = NULL; 100 key = key_load_private(fn, passphrase, &comment); 101 if (key == NULL) { 102 openpam_log(PAM_LOG_DEBUG, "failed to load key from %s\n", fn); 103 return (NULL); 104 } 105 106 openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s\n", comment, fn); 107 if ((psk = malloc(sizeof(*psk))) == NULL) { 108 key_free(key); 109 free(comment); 110 return (NULL); 111 } 112 psk->key = key; 113 psk->comment = comment; 114 return (psk); 115 } 116 117 /* 118 * Wipes a private key and frees the associated resources. 119 */ 120 static void 121 pam_ssh_free_key(pam_handle_t *pamh __unused, 122 void *data, int pam_err __unused) 123 { 124 struct pam_ssh_key *psk; 125 126 psk = data; 127 key_free(psk->key); 128 free(psk->comment); 129 free(psk); 130 } 131 132 PAM_EXTERN int 133 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 134 int argc __unused, const char *argv[] __unused) 135 { 136 const char **kfn, *passphrase, *user; 137 const void *item; 138 struct passwd *pwd; 139 struct pam_ssh_key *psk; 140 int nkeys, nullok, pam_err, pass; 141 142 nullok = (openpam_get_option(pamh, "nullok") != NULL); 143 144 /* PEM is not loaded by default */ 145 OpenSSL_add_all_algorithms(); 146 147 /* get user name and home directory */ 148 pam_err = pam_get_user(pamh, &user, NULL); 149 if (pam_err != PAM_SUCCESS) 150 return (pam_err); 151 pwd = getpwnam(user); 152 if (pwd == NULL) 153 return (PAM_USER_UNKNOWN); 154 if (pwd->pw_dir == NULL) 155 return (PAM_AUTH_ERR); 156 157 nkeys = 0; 158 pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS && 159 item != NULL); 160 load_keys: 161 /* get passphrase */ 162 pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, 163 &passphrase, pam_ssh_prompt); 164 if (pam_err != PAM_SUCCESS) 165 return (pam_err); 166 167 if (*passphrase == '\0' && !nullok) 168 goto skip_keys; 169 170 /* switch to user credentials */ 171 pam_err = openpam_borrow_cred(pamh, pwd); 172 if (pam_err != PAM_SUCCESS) 173 return (pam_err); 174 175 /* try to load keys from all keyfiles we know of */ 176 for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 177 psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase); 178 if (psk != NULL) { 179 pam_set_data(pamh, *kfn, psk, pam_ssh_free_key); 180 ++nkeys; 181 } 182 } 183 184 /* switch back to arbitrator credentials */ 185 openpam_restore_cred(pamh); 186 187 skip_keys: 188 /* 189 * If we tried an old token and didn't get anything, and 190 * try_first_pass was specified, try again after prompting the 191 * user for a new passphrase. 192 */ 193 if (nkeys == 0 && pass == 1 && 194 openpam_get_option(pamh, "try_first_pass") != NULL) { 195 pam_set_item(pamh, PAM_AUTHTOK, NULL); 196 pass = 0; 197 goto load_keys; 198 } 199 200 /* no keys? */ 201 if (nkeys == 0) 202 return (PAM_AUTH_ERR); 203 204 pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL); 205 return (PAM_SUCCESS); 206 } 207 208 PAM_EXTERN int 209 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 210 int argc __unused, const char *argv[] __unused) 211 { 212 213 return (PAM_SUCCESS); 214 } 215 216 /* 217 * Parses a line from ssh-agent's output. 218 */ 219 static void 220 pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f) 221 { 222 char *line, *p, *key, *val; 223 size_t len; 224 225 while ((line = fgetln(f, &len)) != NULL) { 226 if (len < 4 || strncmp(line, "SSH_", 4) != 0) 227 continue; 228 229 /* find equal sign at end of key */ 230 for (p = key = line; p < line + len; ++p) 231 if (*p == '=') 232 break; 233 if (p == line + len || *p != '=') 234 continue; 235 *p = '\0'; 236 237 /* find semicolon at end of value */ 238 for (val = ++p; p < line + len; ++p) 239 if (*p == ';') 240 break; 241 if (p == line + len || *p != ';') 242 continue; 243 *p = '\0'; 244 245 /* store key-value pair in environment */ 246 openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val); 247 pam_setenv(pamh, key, val, 1); 248 } 249 } 250 251 /* 252 * Starts an ssh agent and stores the environment variables derived from 253 * its output. 254 */ 255 static int 256 pam_ssh_start_agent(pam_handle_t *pamh) 257 { 258 int agent_pipe[2]; 259 pid_t pid; 260 FILE *f; 261 262 /* get a pipe which we will use to read the agent's output */ 263 if (pipe(agent_pipe) == -1) 264 return (PAM_SYSTEM_ERR); 265 266 /* start the agent */ 267 openpam_log(PAM_LOG_DEBUG, "starting an ssh agent"); 268 pid = fork(); 269 if (pid == (pid_t)-1) { 270 /* failed */ 271 close(agent_pipe[0]); 272 close(agent_pipe[1]); 273 return (PAM_SYSTEM_ERR); 274 } 275 if (pid == 0) { 276 int fd; 277 278 /* child: drop privs, close fds and start agent */ 279 setgid(getegid()); 280 setuid(geteuid()); 281 close(STDIN_FILENO); 282 open(_PATH_DEVNULL, O_RDONLY); 283 dup2(agent_pipe[1], STDOUT_FILENO); 284 dup2(agent_pipe[1], STDERR_FILENO); 285 for (fd = 3; fd < getdtablesize(); ++fd) 286 close(fd); 287 execve(pam_ssh_agent, pam_ssh_agent_argv, pam_ssh_agent_envp); 288 _exit(127); 289 } 290 291 /* parent */ 292 close(agent_pipe[1]); 293 if ((f = fdopen(agent_pipe[0], "r")) == NULL) 294 return (PAM_SYSTEM_ERR); 295 pam_ssh_process_agent_output(pamh, f); 296 fclose(f); 297 298 return (PAM_SUCCESS); 299 } 300 301 /* 302 * Adds previously stored keys to a running agent. 303 */ 304 static int 305 pam_ssh_add_keys_to_agent(pam_handle_t *pamh) 306 { 307 AuthenticationConnection *ac; 308 struct pam_ssh_key *psk; 309 const char **kfn; 310 void *item; 311 char **envlist, **env; 312 int pam_err; 313 314 /* switch to PAM environment */ 315 envlist = environ; 316 if ((environ = pam_getenvlist(pamh)) == NULL) { 317 environ = envlist; 318 return (PAM_SYSTEM_ERR); 319 } 320 321 /* get a connection to the agent */ 322 if ((ac = ssh_get_authentication_connection()) == NULL) { 323 pam_err = PAM_SYSTEM_ERR; 324 goto end; 325 } 326 327 /* look for keys to add to it */ 328 for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 329 pam_err = pam_get_data(pamh, *kfn, &item); 330 if (pam_err == PAM_SUCCESS && item != NULL) { 331 psk = item; 332 if (ssh_add_identity(ac, psk->key, psk->comment)) 333 openpam_log(PAM_LOG_DEBUG, 334 "added %s to ssh agent", psk->comment); 335 else 336 openpam_log(PAM_LOG_DEBUG, "failed " 337 "to add %s to ssh agent", psk->comment); 338 /* we won't need the key again, so wipe it */ 339 pam_set_data(pamh, *kfn, NULL, NULL); 340 } 341 } 342 pam_err = PAM_SUCCESS; 343 end: 344 /* disconnect from agent */ 345 if (ac != NULL) 346 ssh_close_authentication_connection(ac); 347 348 /* switch back to original environment */ 349 for (env = environ; *env != NULL; ++env) 350 free(*env); 351 free(environ); 352 environ = envlist; 353 354 return (pam_err); 355 } 356 357 PAM_EXTERN int 358 pam_sm_open_session(pam_handle_t *pamh, int flags __unused, 359 int argc __unused, const char *argv[] __unused) 360 { 361 struct passwd *pwd; 362 const char *user; 363 void *data; 364 int pam_err; 365 366 /* no keys, no work */ 367 if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS && 368 openpam_get_option(pamh, "want_agent") == NULL) 369 return (PAM_SUCCESS); 370 371 /* switch to user credentials */ 372 pam_err = pam_get_user(pamh, &user, NULL); 373 if (pam_err != PAM_SUCCESS) 374 return (pam_err); 375 pwd = getpwnam(user); 376 if (pwd == NULL) 377 return (PAM_USER_UNKNOWN); 378 pam_err = openpam_borrow_cred(pamh, pwd); 379 if (pam_err != PAM_SUCCESS) 380 return (pam_err); 381 382 /* start the agent */ 383 pam_err = pam_ssh_start_agent(pamh); 384 if (pam_err != PAM_SUCCESS) { 385 openpam_restore_cred(pamh); 386 return (pam_err); 387 } 388 389 /* we have an agent, see if we can add any keys to it */ 390 pam_err = pam_ssh_add_keys_to_agent(pamh); 391 if (pam_err != PAM_SUCCESS) { 392 /* XXX ignore failures */ 393 } 394 395 openpam_restore_cred(pamh); 396 return (PAM_SUCCESS); 397 } 398 399 PAM_EXTERN int 400 pam_sm_close_session(pam_handle_t *pamh, int flags __unused, 401 int argc __unused, const char *argv[] __unused) 402 { 403 const char *ssh_agent_pid; 404 char *end; 405 int status; 406 pid_t pid; 407 408 if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) { 409 openpam_log(PAM_LOG_DEBUG, "no ssh agent"); 410 return (PAM_SUCCESS); 411 } 412 pid = (pid_t)strtol(ssh_agent_pid, &end, 10); 413 if (*ssh_agent_pid == '\0' || *end != '\0') { 414 openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid"); 415 return (PAM_SESSION_ERR); 416 } 417 openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid); 418 if (kill(pid, SIGTERM) == -1 || 419 (waitpid(pid, &status, 0) == -1 && errno != ECHILD)) 420 return (PAM_SYSTEM_ERR); 421 return (PAM_SUCCESS); 422 } 423 424 PAM_MODULE_ENTRY("pam_ssh"); 425