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