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