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