153874Sgreen/*- 2110598Sdes * Copyright (c) 2003 Networks Associates Technology, Inc. 3226101Sdes * Copyright (c) 2004-2011 Dag-Erling Sm��rgrav 453874Sgreen * All rights reserved. 553874Sgreen * 6110598Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and 7110598Sdes * NAI Labs, the Security Research Division of Network Associates, Inc. 8110598Sdes * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 9110598Sdes * DARPA CHATS research program. 1087398Sdes * 1153874Sgreen * Redistribution and use in source and binary forms, with or without 1253874Sgreen * modification, are permitted provided that the following conditions 1353874Sgreen * are met: 1453874Sgreen * 1. Redistributions of source code must retain the above copyright 1553874Sgreen * notice, this list of conditions and the following disclaimer. 1653874Sgreen * 2. Redistributions in binary form must reproduce the above copyright 1753874Sgreen * notice, this list of conditions and the following disclaimer in the 1853874Sgreen * documentation and/or other materials provided with the distribution. 19110598Sdes * 3. The name of the author may not be used to endorse or promote 20110598Sdes * products derived from this software without specific prior written 21110598Sdes * permission. 2253874Sgreen * 2353874Sgreen * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2453874Sgreen * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2553874Sgreen * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2653874Sgreen * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2753874Sgreen * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2853874Sgreen * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2953874Sgreen * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3053874Sgreen * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3153874Sgreen * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3253874Sgreen * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3353874Sgreen * SUCH DAMAGE. 3453874Sgreen */ 3553874Sgreen 3684218Sdillon#include <sys/cdefs.h> 3784218Sdillon__FBSDID("$FreeBSD: stable/11/lib/libpam/modules/pam_ssh/pam_ssh.c 306308 2016-09-25 09:36:52Z roberto $"); 3853874Sgreen 3953874Sgreen#include <sys/param.h> 4080542Smarkm#include <sys/wait.h> 4153874Sgreen 42120231Sdes#include <errno.h> 4393804Sdes#include <fcntl.h> 44110598Sdes#include <paths.h> 4553874Sgreen#include <pwd.h> 4680542Smarkm#include <signal.h> 4753874Sgreen#include <stdio.h> 4853874Sgreen#include <string.h> 4953874Sgreen#include <unistd.h> 5053874Sgreen 5193804Sdes#define PAM_SM_AUTH 5287398Sdes#define PAM_SM_SESSION 5387398Sdes 5490229Sdes#include <security/pam_appl.h> 5553874Sgreen#include <security/pam_modules.h> 5694216Sdes#include <security/openpam.h> 5753874Sgreen 5880542Smarkm#include <openssl/evp.h> 5961087Skris 60296651Sdes#define __bounded__(x, y, z) 6161087Skris#include "key.h" 62162900Sru#include "buffer.h" 6353874Sgreen#include "authfd.h" 6461087Skris#include "authfile.h" 6553874Sgreen 66204917Sdes#define ssh_add_identity(auth, key, comment) \ 67204917Sdes ssh_add_identity_constrained(auth, key, comment, 0, 0) 68204917Sdes 69110598Sdesextern char **environ; 7093984Sdes 71110598Sdesstruct pam_ssh_key { 72110598Sdes Key *key; 73110598Sdes char *comment; 74110598Sdes}; 7553874Sgreen 76110598Sdesstatic const char *pam_ssh_prompt = "SSH passphrase: "; 77110598Sdesstatic const char *pam_ssh_have_keys = "pam_ssh_have_keys"; 7853874Sgreen 79110598Sdesstatic const char *pam_ssh_keyfiles[] = { 80110598Sdes ".ssh/id_rsa", /* SSH2 RSA key */ 81110598Sdes ".ssh/id_dsa", /* SSH2 DSA key */ 82226101Sdes ".ssh/id_ecdsa", /* SSH2 ECDSA key */ 83306308Sroberto ".ssh/id_ed25519", /* SSH2 Ed25519 key */ 84110598Sdes NULL 85110598Sdes}; 8653874Sgreen 87110598Sdesstatic const char *pam_ssh_agent = "/usr/bin/ssh-agent"; 88296651Sdesstatic char str_ssh_agent[] = "ssh-agent"; 89296651Sdesstatic char str_dash_s[] = "-s"; 90296651Sdesstatic char *const pam_ssh_agent_argv[] = { str_ssh_agent, str_dash_s, NULL }; 91125650Sdesstatic char *const pam_ssh_agent_envp[] = { NULL }; 9280542Smarkm 9355166Sgreen/* 94110598Sdes * Attempts to load a private key from the specified file in the specified 95110598Sdes * directory, using the specified passphrase. If successful, returns a 96110598Sdes * struct pam_ssh_key containing the key and its comment. 9755166Sgreen */ 98110598Sdesstatic struct pam_ssh_key * 99227757Sdespam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase, 100227757Sdes int nullok) 10155166Sgreen{ 102110598Sdes struct pam_ssh_key *psk; 103110598Sdes char fn[PATH_MAX]; 104110598Sdes char *comment; 105110598Sdes Key *key; 10655166Sgreen 107110598Sdes if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn)) 108110598Sdes return (NULL); 10993804Sdes comment = NULL; 110227757Sdes /* 111227757Sdes * If the key is unencrypted, OpenSSL ignores the passphrase, so 112227757Sdes * it will seem like the user typed in the right one. This allows 113227757Sdes * a user to circumvent nullok by providing a dummy passphrase. 114227757Sdes * Verify that the key really *is* encrypted by trying to load it 115227757Sdes * with an empty passphrase, and if the key is not encrypted, 116227757Sdes * accept only an empty passphrase. 117227757Sdes */ 118236106Sdes key = key_load_private(fn, "", &comment); 119227757Sdes if (key != NULL && !(*passphrase == '\0' && nullok)) { 120227757Sdes key_free(key); 121227757Sdes return (NULL); 122227757Sdes } 123227757Sdes if (key == NULL) 124227757Sdes key = key_load_private(fn, passphrase, &comment); 125110598Sdes if (key == NULL) { 126219426Sdes openpam_log(PAM_LOG_DEBUG, "failed to load key from %s", fn); 127110598Sdes return (NULL); 12893804Sdes } 12993804Sdes 130219426Sdes openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s", comment, fn); 131110598Sdes if ((psk = malloc(sizeof(*psk))) == NULL) { 13280542Smarkm key_free(key); 13380542Smarkm free(comment); 134110598Sdes return (NULL); 13580542Smarkm } 136110598Sdes psk->key = key; 137110598Sdes psk->comment = comment; 138110598Sdes return (psk); 13955166Sgreen} 14055166Sgreen 14193804Sdes/* 142110598Sdes * Wipes a private key and frees the associated resources. 14393804Sdes */ 144110598Sdesstatic void 145110598Sdespam_ssh_free_key(pam_handle_t *pamh __unused, 146110598Sdes void *data, int pam_err __unused) 14755166Sgreen{ 148110598Sdes struct pam_ssh_key *psk; 14955166Sgreen 150110598Sdes psk = data; 151110598Sdes key_free(psk->key); 152110598Sdes free(psk->comment); 153110598Sdes free(psk); 15453874Sgreen} 15553874Sgreen 15653874SgreenPAM_EXTERN int 15793984Sdespam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 15894564Sdes int argc __unused, const char *argv[] __unused) 15953874Sgreen{ 160110598Sdes const char **kfn, *passphrase, *user; 161150596Sdes const void *item; 162110598Sdes struct passwd *pwd; 163110598Sdes struct pam_ssh_key *psk; 164150455Sdes int nkeys, nullok, pam_err, pass; 16580542Smarkm 166150455Sdes nullok = (openpam_get_option(pamh, "nullok") != NULL); 167150455Sdes 168110598Sdes /* PEM is not loaded by default */ 169110598Sdes OpenSSL_add_all_algorithms(); 170107934Sdes 171110598Sdes /* get user name and home directory */ 172110653Sdes pam_err = pam_get_user(pamh, &user, NULL); 173110598Sdes if (pam_err != PAM_SUCCESS) 174110598Sdes return (pam_err); 175110598Sdes pwd = getpwnam(user); 176110598Sdes if (pwd == NULL) 177110598Sdes return (PAM_USER_UNKNOWN); 178110598Sdes if (pwd->pw_dir == NULL) 17994564Sdes return (PAM_AUTH_ERR); 18053874Sgreen 181150455Sdes nkeys = 0; 182150596Sdes pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS && 183150596Sdes item != NULL); 184110598Sdes load_keys: 185110598Sdes /* get passphrase */ 186110598Sdes pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, 187110598Sdes &passphrase, pam_ssh_prompt); 188150426Sdes if (pam_err != PAM_SUCCESS) 189110598Sdes return (pam_err); 19087398Sdes 191150426Sdes /* switch to user credentials */ 192150426Sdes pam_err = openpam_borrow_cred(pamh, pwd); 193150426Sdes if (pam_err != PAM_SUCCESS) 194150426Sdes return (pam_err); 195150426Sdes 196110598Sdes /* try to load keys from all keyfiles we know of */ 197110598Sdes for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 198227757Sdes psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase, nullok); 199110598Sdes if (psk != NULL) { 200110598Sdes pam_set_data(pamh, *kfn, psk, pam_ssh_free_key); 201110598Sdes ++nkeys; 202110598Sdes } 203110598Sdes } 20487398Sdes 205150426Sdes /* switch back to arbitrator credentials */ 206150426Sdes openpam_restore_cred(pamh); 207150426Sdes 208110598Sdes /* 209110598Sdes * If we tried an old token and didn't get anything, and 210110598Sdes * try_first_pass was specified, try again after prompting the 211110598Sdes * user for a new passphrase. 212110598Sdes */ 213110598Sdes if (nkeys == 0 && pass == 1 && 214110598Sdes openpam_get_option(pamh, "try_first_pass") != NULL) { 215110598Sdes pam_set_item(pamh, PAM_AUTHTOK, NULL); 216110598Sdes pass = 0; 217110598Sdes goto load_keys; 21893804Sdes } 21987398Sdes 220110598Sdes /* no keys? */ 221110598Sdes if (nkeys == 0) 222110598Sdes return (PAM_AUTH_ERR); 22387398Sdes 224110598Sdes pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL); 22594564Sdes return (PAM_SUCCESS); 22687398Sdes} 22787398Sdes 22853874SgreenPAM_EXTERN int 22993984Sdespam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 23094564Sdes int argc __unused, const char *argv[] __unused) 23153874Sgreen{ 232110598Sdes 23394564Sdes return (PAM_SUCCESS); 23493804Sdes} 23553874Sgreen 236110598Sdes/* 237110598Sdes * Parses a line from ssh-agent's output. 238110598Sdes */ 239110598Sdesstatic void 240110598Sdespam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f) 24193804Sdes{ 242110598Sdes char *line, *p, *key, *val; 243110598Sdes size_t len; 24480542Smarkm 245110598Sdes while ((line = fgetln(f, &len)) != NULL) { 246110598Sdes if (len < 4 || strncmp(line, "SSH_", 4) != 0) 247110598Sdes continue; 248107934Sdes 249110598Sdes /* find equal sign at end of key */ 250110598Sdes for (p = key = line; p < line + len; ++p) 251110598Sdes if (*p == '=') 252110598Sdes break; 253110598Sdes if (p == line + len || *p != '=') 254110598Sdes continue; 255110598Sdes *p = '\0'; 25680542Smarkm 257110598Sdes /* find semicolon at end of value */ 258110598Sdes for (val = ++p; p < line + len; ++p) 259110598Sdes if (*p == ';') 260110598Sdes break; 261110598Sdes if (p == line + len || *p != ';') 262110598Sdes continue; 263110598Sdes *p = '\0'; 26480542Smarkm 265110598Sdes /* store key-value pair in environment */ 266110598Sdes openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val); 267110598Sdes pam_setenv(pamh, key, val, 1); 26880542Smarkm } 269110598Sdes} 27080542Smarkm 271110598Sdes/* 272110598Sdes * Starts an ssh agent and stores the environment variables derived from 273110598Sdes * its output. 274110598Sdes */ 275110598Sdesstatic int 276110598Sdespam_ssh_start_agent(pam_handle_t *pamh) 277110598Sdes{ 278110598Sdes int agent_pipe[2]; 279110598Sdes pid_t pid; 280110598Sdes FILE *f; 28180542Smarkm 282110598Sdes /* get a pipe which we will use to read the agent's output */ 283150426Sdes if (pipe(agent_pipe) == -1) 284110598Sdes return (PAM_SYSTEM_ERR); 28580542Smarkm 286110598Sdes /* start the agent */ 287110598Sdes openpam_log(PAM_LOG_DEBUG, "starting an ssh agent"); 288110598Sdes pid = fork(); 289110598Sdes if (pid == (pid_t)-1) { 290110598Sdes /* failed */ 291110598Sdes close(agent_pipe[0]); 292110598Sdes close(agent_pipe[1]); 293110598Sdes return (PAM_SYSTEM_ERR); 294110598Sdes } 295110598Sdes if (pid == 0) { 296110598Sdes int fd; 29780542Smarkm 298110598Sdes /* child: drop privs, close fds and start agent */ 299110598Sdes setgid(getegid()); 300110598Sdes setuid(geteuid()); 301110598Sdes close(STDIN_FILENO); 302110598Sdes open(_PATH_DEVNULL, O_RDONLY); 303110598Sdes dup2(agent_pipe[1], STDOUT_FILENO); 304110598Sdes dup2(agent_pipe[1], STDERR_FILENO); 305110598Sdes for (fd = 3; fd < getdtablesize(); ++fd) 306110598Sdes close(fd); 307110598Sdes execve(pam_ssh_agent, pam_ssh_agent_argv, pam_ssh_agent_envp); 308110598Sdes _exit(127); 30953874Sgreen } 31080542Smarkm 311110598Sdes /* parent */ 312110598Sdes close(agent_pipe[1]); 313110598Sdes if ((f = fdopen(agent_pipe[0], "r")) == NULL) 314110598Sdes return (PAM_SYSTEM_ERR); 315110598Sdes pam_ssh_process_agent_output(pamh, f); 316110598Sdes fclose(f); 31780542Smarkm 318110598Sdes return (PAM_SUCCESS); 319110598Sdes} 32093804Sdes 321110598Sdes/* 322110598Sdes * Adds previously stored keys to a running agent. 323110598Sdes */ 324110598Sdesstatic int 325110598Sdespam_ssh_add_keys_to_agent(pam_handle_t *pamh) 326110598Sdes{ 327174837Sdes const struct pam_ssh_key *psk; 328110598Sdes const char **kfn; 329174837Sdes const void *item; 330110598Sdes char **envlist, **env; 331294367Sjhb int fd, pam_err; 33293804Sdes 333110598Sdes /* switch to PAM environment */ 334110598Sdes envlist = environ; 335110598Sdes if ((environ = pam_getenvlist(pamh)) == NULL) { 336110598Sdes environ = envlist; 337110598Sdes return (PAM_SYSTEM_ERR); 338110598Sdes } 33993804Sdes 340110598Sdes /* get a connection to the agent */ 341294367Sjhb if (ssh_get_authentication_socket(&fd) != 0) { 342226101Sdes openpam_log(PAM_LOG_DEBUG, "failed to connect to the agent"); 343110598Sdes pam_err = PAM_SYSTEM_ERR; 344110598Sdes goto end; 345110598Sdes } 34693804Sdes 347110598Sdes /* look for keys to add to it */ 348110598Sdes for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) { 349150596Sdes pam_err = pam_get_data(pamh, *kfn, &item); 350150596Sdes if (pam_err == PAM_SUCCESS && item != NULL) { 351150596Sdes psk = item; 352294367Sjhb if (ssh_add_identity(fd, psk->key, psk->comment) == 0) 353110598Sdes openpam_log(PAM_LOG_DEBUG, 354110598Sdes "added %s to ssh agent", psk->comment); 35593804Sdes else 356110598Sdes openpam_log(PAM_LOG_DEBUG, "failed " 357110598Sdes "to add %s to ssh agent", psk->comment); 358110598Sdes /* we won't need the key again, so wipe it */ 359110598Sdes pam_set_data(pamh, *kfn, NULL, NULL); 36080542Smarkm } 36193804Sdes } 362110598Sdes pam_err = PAM_SUCCESS; 363294367Sjhb 364110598Sdes /* disconnect from agent */ 365294367Sjhb ssh_close_authentication_socket(fd); 36693804Sdes 367294367Sjhb end: 368110598Sdes /* switch back to original environment */ 369110598Sdes for (env = environ; *env != NULL; ++env) 370110598Sdes free(*env); 371110598Sdes free(environ); 372110598Sdes environ = envlist; 37393804Sdes 374110598Sdes return (pam_err); 375110598Sdes} 37693804Sdes 377110598SdesPAM_EXTERN int 378110598Sdespam_sm_open_session(pam_handle_t *pamh, int flags __unused, 379110598Sdes int argc __unused, const char *argv[] __unused) 380110598Sdes{ 381110598Sdes struct passwd *pwd; 382110598Sdes const char *user; 383174837Sdes const void *data; 384110598Sdes int pam_err; 38580542Smarkm 386110598Sdes /* no keys, no work */ 387110598Sdes if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS && 388110598Sdes openpam_get_option(pamh, "want_agent") == NULL) 38994564Sdes return (PAM_SUCCESS); 39093804Sdes 391110598Sdes /* switch to user credentials */ 392110653Sdes pam_err = pam_get_user(pamh, &user, NULL); 393110598Sdes if (pam_err != PAM_SUCCESS) 394110598Sdes return (pam_err); 395110598Sdes pwd = getpwnam(user); 396110598Sdes if (pwd == NULL) 397110598Sdes return (PAM_USER_UNKNOWN); 398110598Sdes pam_err = openpam_borrow_cred(pamh, pwd); 399110598Sdes if (pam_err != PAM_SUCCESS) 400110598Sdes return (pam_err); 40193804Sdes 402110598Sdes /* start the agent */ 403110598Sdes pam_err = pam_ssh_start_agent(pamh); 404110598Sdes if (pam_err != PAM_SUCCESS) { 405110598Sdes openpam_restore_cred(pamh); 406110598Sdes return (pam_err); 40755166Sgreen } 40880542Smarkm 409110598Sdes /* we have an agent, see if we can add any keys to it */ 410110598Sdes pam_err = pam_ssh_add_keys_to_agent(pamh); 411110598Sdes if (pam_err != PAM_SUCCESS) { 412110598Sdes /* XXX ignore failures */ 41353874Sgreen } 41480542Smarkm 415110598Sdes openpam_restore_cred(pamh); 41694564Sdes return (PAM_SUCCESS); 41753874Sgreen} 41853874Sgreen 41953874SgreenPAM_EXTERN int 42093984Sdespam_sm_close_session(pam_handle_t *pamh, int flags __unused, 42194564Sdes int argc __unused, const char *argv[] __unused) 42253874Sgreen{ 423110598Sdes const char *ssh_agent_pid; 424110598Sdes char *end; 425110598Sdes int status; 426110598Sdes pid_t pid; 42753874Sgreen 428110598Sdes if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) { 429110598Sdes openpam_log(PAM_LOG_DEBUG, "no ssh agent"); 430110598Sdes return (PAM_SUCCESS); 43193804Sdes } 432110598Sdes pid = (pid_t)strtol(ssh_agent_pid, &end, 10); 433110598Sdes if (*ssh_agent_pid == '\0' || *end != '\0') { 434110598Sdes openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid"); 43594564Sdes return (PAM_SESSION_ERR); 43653874Sgreen } 437110598Sdes openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid); 438110598Sdes if (kill(pid, SIGTERM) == -1 || 439120231Sdes (waitpid(pid, &status, 0) == -1 && errno != ECHILD)) 440110598Sdes return (PAM_SYSTEM_ERR); 44194564Sdes return (PAM_SUCCESS); 44293804Sdes} 44380542Smarkm 444110598SdesPAM_MODULE_ENTRY("pam_ssh"); 445