1124211Sdes/*- 2124211Sdes * Copyright (c) 2002 Networks Associates Technology, Inc. 3124211Sdes * All rights reserved. 469591Sgreen * 5124211Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and 6124211Sdes * NAI Labs, the Security Research Division of Network Associates, Inc. 7124211Sdes * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the 8124211Sdes * DARPA CHATS research program. 9124211Sdes * 1069591Sgreen * Redistribution and use in source and binary forms, with or without 1169591Sgreen * modification, are permitted provided that the following conditions 1269591Sgreen * are met: 1369591Sgreen * 1. Redistributions of source code must retain the above copyright 1469591Sgreen * notice, this list of conditions and the following disclaimer. 1569591Sgreen * 2. Redistributions in binary form must reproduce the above copyright 1669591Sgreen * notice, this list of conditions and the following disclaimer in the 1769591Sgreen * documentation and/or other materials provided with the distribution. 1869591Sgreen * 19124211Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20124211Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21124211Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22124211Sdes * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23124211Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24124211Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25124211Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26124211Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27124211Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28124211Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29124211Sdes * SUCH DAMAGE. 3069591Sgreen */ 31137019Sdes/* 32137019Sdes * Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org> 33137019Sdes * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au> 34137019Sdes * 35137019Sdes * Permission to use, copy, modify, and distribute this software for any 36137019Sdes * purpose with or without fee is hereby granted, provided that the above 37137019Sdes * copyright notice and this permission notice appear in all copies. 38137019Sdes * 39137019Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 40137019Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 41137019Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 42137019Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 43137019Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 44137019Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 45137019Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 46137019Sdes */ 4769591Sgreen 48296633Sdes/* Based on FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des */ 49296633Sdes 5069591Sgreen#include "includes.h" 5169591Sgreen 52162856Sdes#include <sys/types.h> 53162856Sdes#include <sys/stat.h> 54162856Sdes#include <sys/wait.h> 55162856Sdes 56162856Sdes#include <errno.h> 57162856Sdes#include <signal.h> 58162856Sdes#include <stdarg.h> 59162856Sdes#include <string.h> 60162856Sdes#include <unistd.h> 61162856Sdes 6269591Sgreen#ifdef USE_PAM 63126277Sdes#if defined(HAVE_SECURITY_PAM_APPL_H) 64124211Sdes#include <security/pam_appl.h> 65126277Sdes#elif defined (HAVE_PAM_PAM_APPL_H) 66126277Sdes#include <pam/pam_appl.h> 67126277Sdes#endif 68124211Sdes 69323134Sdes#if !defined(SSHD_PAM_SERVICE) 70323134Sdesextern char *__progname; 71323134Sdes# define SSHD_PAM_SERVICE __progname 72323134Sdes#endif 73323134Sdes 74149753Sdes/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */ 75149753Sdes#ifdef PAM_SUN_CODEBASE 76323129Sdes# define sshpam_const /* Solaris, HP-UX, SunOS */ 77149753Sdes#else 78323129Sdes# define sshpam_const const /* LinuxPAM, OpenPAM, AIX */ 79149753Sdes#endif 80149753Sdes 81162856Sdes/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */ 82162856Sdes#ifdef PAM_SUN_CODEBASE 83162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member) 84162856Sdes#else 85162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member) 86162856Sdes#endif 87162856Sdes 88162856Sdes#include "xmalloc.h" 89162856Sdes#include "buffer.h" 90162856Sdes#include "key.h" 91162856Sdes#include "hostfile.h" 9298941Sdes#include "auth.h" 9398941Sdes#include "auth-pam.h" 9498941Sdes#include "canohost.h" 95124211Sdes#include "log.h" 96124211Sdes#include "msg.h" 97124211Sdes#include "packet.h" 98137019Sdes#include "misc.h" 99124211Sdes#include "servconf.h" 100124211Sdes#include "ssh2.h" 101124211Sdes#include "auth-options.h" 102162856Sdes#ifdef GSSAPI 103162856Sdes#include "ssh-gss.h" 104162856Sdes#endif 105162856Sdes#include "monitor_wrap.h" 106305476Slidl#include "blacklist_client.h" 10769591Sgreen 108124211Sdesextern ServerOptions options; 109126277Sdesextern Buffer loginmsg; 110126277Sdesextern int compat20; 111128460Sdesextern u_int utmp_len; 11269591Sgreen 113147005Sdes/* so we don't silently change behaviour */ 114124211Sdes#ifdef USE_POSIX_THREADS 115147005Sdes# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK" 116147005Sdes#endif 117147005Sdes 118147005Sdes/* 119147005Sdes * Formerly known as USE_POSIX_THREADS, using this is completely unsupported 120147005Sdes * and generally a bad idea. Use at own risk and do not expect support if 121147005Sdes * this breaks. 122147005Sdes */ 123147005Sdes#ifdef UNSUPPORTED_POSIX_THREADS_HACK 124124211Sdes#include <pthread.h> 125124211Sdes/* 126126277Sdes * Avoid namespace clash when *not* using pthreads for systems *with* 127126277Sdes * pthreads, which unconditionally define pthread_t via sys/types.h 128124211Sdes * (e.g. Linux) 129124211Sdes */ 130126277Sdestypedef pthread_t sp_pthread_t; 131124211Sdes#else 132126277Sdestypedef pid_t sp_pthread_t; 133126277Sdes#endif 134126277Sdes 135126277Sdesstruct pam_ctxt { 136126277Sdes sp_pthread_t pam_thread; 137126277Sdes int pam_psock; 138126277Sdes int pam_csock; 139126277Sdes int pam_done; 140126277Sdes}; 141126277Sdes 142126277Sdesstatic void sshpam_free_ctx(void *); 143126277Sdesstatic struct pam_ctxt *cleanup_ctxt; 144126277Sdes 145147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 146124211Sdes/* 147124211Sdes * Simulate threads with processes. 148124211Sdes */ 149106130Sdes 150126277Sdesstatic int sshpam_thread_status = -1; 151126277Sdesstatic mysig_t sshpam_oldsig; 152126277Sdes 153149753Sdesstatic void 154126277Sdessshpam_sigchld_handler(int sig) 155126277Sdes{ 156137019Sdes signal(SIGCHLD, SIG_DFL); 157126277Sdes if (cleanup_ctxt == NULL) 158126277Sdes return; /* handler called after PAM cleanup, shouldn't happen */ 159137019Sdes if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG) 160149753Sdes <= 0) { 161137019Sdes /* PAM thread has not exitted, privsep slave must have */ 162137019Sdes kill(cleanup_ctxt->pam_thread, SIGTERM); 163323129Sdes while (waitpid(cleanup_ctxt->pam_thread, 164323129Sdes &sshpam_thread_status, 0) == -1) { 165323129Sdes if (errno == EINTR) 166323129Sdes continue; 167323129Sdes return; 168323129Sdes } 169137019Sdes } 170126277Sdes if (WIFSIGNALED(sshpam_thread_status) && 171126277Sdes WTERMSIG(sshpam_thread_status) == SIGTERM) 172126277Sdes return; /* terminated by pthread_cancel */ 173126277Sdes if (!WIFEXITED(sshpam_thread_status)) 174181111Sdes sigdie("PAM: authentication thread exited unexpectedly"); 175126277Sdes if (WEXITSTATUS(sshpam_thread_status) != 0) 176181111Sdes sigdie("PAM: authentication thread exited uncleanly"); 177126277Sdes} 178126277Sdes 179162856Sdes/* ARGSUSED */ 180124211Sdesstatic void 181162856Sdespthread_exit(void *value) 182124211Sdes{ 183124211Sdes _exit(0); 184124211Sdes} 18569591Sgreen 186162856Sdes/* ARGSUSED */ 187124211Sdesstatic int 188162856Sdespthread_create(sp_pthread_t *thread, const void *attr, 189124211Sdes void *(*thread_start)(void *), void *arg) 190124211Sdes{ 191124211Sdes pid_t pid; 192149753Sdes struct pam_ctxt *ctx = arg; 19369591Sgreen 194128460Sdes sshpam_thread_status = -1; 195124211Sdes switch ((pid = fork())) { 196124211Sdes case -1: 197124211Sdes error("fork(): %s", strerror(errno)); 198124211Sdes return (-1); 199124211Sdes case 0: 200149753Sdes close(ctx->pam_psock); 201149753Sdes ctx->pam_psock = -1; 202124211Sdes thread_start(arg); 203124211Sdes _exit(1); 204124211Sdes default: 205124211Sdes *thread = pid; 206149753Sdes close(ctx->pam_csock); 207149753Sdes ctx->pam_csock = -1; 208126277Sdes sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler); 209124211Sdes return (0); 210124211Sdes } 211124211Sdes} 21269591Sgreen 213124211Sdesstatic int 214124211Sdespthread_cancel(sp_pthread_t thread) 21576394Salfred{ 216126277Sdes signal(SIGCHLD, sshpam_oldsig); 217124211Sdes return (kill(thread, SIGTERM)); 21876394Salfred} 21976394Salfred 220162856Sdes/* ARGSUSED */ 221124211Sdesstatic int 222162856Sdespthread_join(sp_pthread_t thread, void **value) 22398941Sdes{ 224124211Sdes int status; 225124211Sdes 226126277Sdes if (sshpam_thread_status != -1) 227126277Sdes return (sshpam_thread_status); 228126277Sdes signal(SIGCHLD, sshpam_oldsig); 229323129Sdes while (waitpid(thread, &status, 0) == -1) { 230323129Sdes if (errno == EINTR) 231323129Sdes continue; 232323129Sdes fatal("%s: waitpid: %s", __func__, strerror(errno)); 233323129Sdes } 234124211Sdes return (status); 23598941Sdes} 236124211Sdes#endif 23798941Sdes 238124211Sdes 239124211Sdesstatic pam_handle_t *sshpam_handle = NULL; 240124211Sdesstatic int sshpam_err = 0; 241124211Sdesstatic int sshpam_authenticated = 0; 242124211Sdesstatic int sshpam_session_open = 0; 243124211Sdesstatic int sshpam_cred_established = 0; 244126277Sdesstatic int sshpam_account_status = -1; 245323129Sdesstatic int sshpam_maxtries_reached = 0; 246126277Sdesstatic char **sshpam_env = NULL; 247128460Sdesstatic Authctxt *sshpam_authctxt = NULL; 248137019Sdesstatic const char *sshpam_password = NULL; 249124211Sdes 250126277Sdes/* Some PAM implementations don't implement this */ 251126277Sdes#ifndef HAVE_PAM_GETENVLIST 252126277Sdesstatic char ** 253126277Sdespam_getenvlist(pam_handle_t *pamh) 254126277Sdes{ 255126277Sdes /* 256126277Sdes * XXX - If necessary, we can still support envrionment passing 257126277Sdes * for platforms without pam_getenvlist by searching for known 258126277Sdes * env vars (e.g. KRB5CCNAME) from the PAM environment. 259126277Sdes */ 260126277Sdes return NULL; 261126277Sdes} 262126277Sdes#endif 263124211Sdes 264137019Sdes/* 265137019Sdes * Some platforms, notably Solaris, do not enforce password complexity 266137019Sdes * rules during pam_chauthtok() if the real uid of the calling process 267137019Sdes * is 0, on the assumption that it's being called by "passwd" run by root. 268137019Sdes * This wraps pam_chauthtok and sets/restore the real uid so PAM will do 269137019Sdes * the right thing. 270137019Sdes */ 271137019Sdes#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID 272137019Sdesstatic int 273137019Sdessshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) 274137019Sdes{ 275137019Sdes int result; 276137019Sdes 277137019Sdes if (sshpam_authctxt == NULL) 278137019Sdes fatal("PAM: sshpam_authctxt not initialized"); 279137019Sdes if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1) 280137019Sdes fatal("%s: setreuid failed: %s", __func__, strerror(errno)); 281137019Sdes result = pam_chauthtok(pamh, flags); 282137019Sdes if (setreuid(0, -1) == -1) 283137019Sdes fatal("%s: setreuid failed: %s", __func__, strerror(errno)); 284137019Sdes return result; 285137019Sdes} 286137019Sdes# define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) 287137019Sdes#endif 288137019Sdes 289126277Sdesvoid 290137019Sdessshpam_password_change_required(int reqd) 291126277Sdes{ 292126277Sdes debug3("%s %d", __func__, reqd); 293128460Sdes if (sshpam_authctxt == NULL) 294128460Sdes fatal("%s: PAM authctxt not initialized", __func__); 295128460Sdes sshpam_authctxt->force_pwchange = reqd; 296126277Sdes if (reqd) { 297126277Sdes no_port_forwarding_flag |= 2; 298126277Sdes no_agent_forwarding_flag |= 2; 299126277Sdes no_x11_forwarding_flag |= 2; 300126277Sdes } else { 301126277Sdes no_port_forwarding_flag &= ~2; 302126277Sdes no_agent_forwarding_flag &= ~2; 303126277Sdes no_x11_forwarding_flag &= ~2; 304126277Sdes } 305126277Sdes} 306124211Sdes 307126277Sdes/* Import regular and PAM environment from subprocess */ 308126277Sdesstatic void 309126277Sdesimport_environments(Buffer *b) 310126277Sdes{ 311126277Sdes char *env; 312126277Sdes u_int i, num_env; 313126277Sdes int err; 314126277Sdes 315126277Sdes debug3("PAM: %s entering", __func__); 316126277Sdes 317147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 318126277Sdes /* Import variables set by do_pam_account */ 319126277Sdes sshpam_account_status = buffer_get_int(b); 320137019Sdes sshpam_password_change_required(buffer_get_int(b)); 321126277Sdes 322126277Sdes /* Import environment from subprocess */ 323126277Sdes num_env = buffer_get_int(b); 324162856Sdes if (num_env > 1024) 325162856Sdes fatal("%s: received %u environment variables, expected <= 1024", 326162856Sdes __func__, num_env); 327162856Sdes sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env)); 328126277Sdes debug3("PAM: num env strings %d", num_env); 329126277Sdes for(i = 0; i < num_env; i++) 330126277Sdes sshpam_env[i] = buffer_get_string(b, NULL); 331126277Sdes 332126277Sdes sshpam_env[num_env] = NULL; 333126277Sdes 334126277Sdes /* Import PAM environment from subprocess */ 335126277Sdes num_env = buffer_get_int(b); 336126277Sdes debug("PAM: num PAM env strings %d", num_env); 337126277Sdes for(i = 0; i < num_env; i++) { 338126277Sdes env = buffer_get_string(b, NULL); 339126277Sdes 340126277Sdes#ifdef HAVE_PAM_PUTENV 341126277Sdes /* Errors are not fatal here */ 342126277Sdes if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) { 343126277Sdes error("PAM: pam_putenv: %s", 344126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 345126277Sdes } 346126277Sdes#endif 347126277Sdes } 348128460Sdes#endif 349126277Sdes} 350126277Sdes 35176394Salfred/* 352124211Sdes * Conversation function for authentication thread. 35369591Sgreen */ 354124211Sdesstatic int 355149753Sdessshpam_thread_conv(int n, sshpam_const struct pam_message **msg, 356124211Sdes struct pam_response **resp, void *data) 35769591Sgreen{ 358124211Sdes Buffer buffer; 359124211Sdes struct pam_ctxt *ctxt; 36069591Sgreen struct pam_response *reply; 361124211Sdes int i; 36269591Sgreen 363126277Sdes debug3("PAM: %s entering, %d messages", __func__, n); 364124211Sdes *resp = NULL; 36569591Sgreen 366137019Sdes if (data == NULL) { 367137019Sdes error("PAM: conversation function passed a null context"); 368137019Sdes return (PAM_CONV_ERR); 369137019Sdes } 370124211Sdes ctxt = data; 371124211Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 372124211Sdes return (PAM_CONV_ERR); 373124211Sdes 374162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 375124211Sdes return (PAM_CONV_ERR); 376124211Sdes 377124211Sdes buffer_init(&buffer); 378124211Sdes for (i = 0; i < n; ++i) { 379124211Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 380124211Sdes case PAM_PROMPT_ECHO_OFF: 381124211Sdes case PAM_PROMPT_ECHO_ON: 382126277Sdes buffer_put_cstring(&buffer, 383124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 384126277Sdes if (ssh_msg_send(ctxt->pam_csock, 385126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 386126277Sdes goto fail; 387126277Sdes if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1) 388126277Sdes goto fail; 389124211Sdes if (buffer_get_char(&buffer) != PAM_AUTHTOK) 390124211Sdes goto fail; 391124211Sdes reply[i].resp = buffer_get_string(&buffer, NULL); 392124211Sdes break; 393124211Sdes case PAM_ERROR_MSG: 394124211Sdes case PAM_TEXT_INFO: 395126277Sdes buffer_put_cstring(&buffer, 396124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 397126277Sdes if (ssh_msg_send(ctxt->pam_csock, 398126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 399126277Sdes goto fail; 400124211Sdes break; 401124211Sdes default: 402124211Sdes goto fail; 40369591Sgreen } 404124211Sdes buffer_clear(&buffer); 40569591Sgreen } 406124211Sdes buffer_free(&buffer); 40769591Sgreen *resp = reply; 408124211Sdes return (PAM_SUCCESS); 40969591Sgreen 410124211Sdes fail: 411124211Sdes for(i = 0; i < n; i++) { 412255767Sdes free(reply[i].resp); 413124211Sdes } 414255767Sdes free(reply); 415124211Sdes buffer_free(&buffer); 416124211Sdes return (PAM_CONV_ERR); 41769591Sgreen} 41869591Sgreen 419124211Sdes/* 420124211Sdes * Authentication thread. 421124211Sdes */ 422124211Sdesstatic void * 423124211Sdessshpam_thread(void *ctxtp) 42469591Sgreen{ 425124211Sdes struct pam_ctxt *ctxt = ctxtp; 426124211Sdes Buffer buffer; 427124211Sdes struct pam_conv sshpam_conv; 428137019Sdes int flags = (options.permit_empty_passwd == 0 ? 429137019Sdes PAM_DISALLOW_NULL_AUTHTOK : 0); 430147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 431126277Sdes extern char **environ; 432126277Sdes char **env_from_pam; 433126277Sdes u_int i; 434124211Sdes const char *pam_user; 435149753Sdes const char **ptr_pam_user = &pam_user; 436162856Sdes char *tz = getenv("TZ"); 43769591Sgreen 438261320Sdes sshpam_err = pam_get_item(sshpam_handle, PAM_USER, 439149753Sdes (sshpam_const void **)ptr_pam_user); 440261320Sdes if (sshpam_err != PAM_SUCCESS) 441261320Sdes goto auth_fail; 442162856Sdes 443126277Sdes environ[0] = NULL; 444162856Sdes if (tz != NULL) 445162856Sdes if (setenv("TZ", tz, 1) == -1) 446162856Sdes error("PAM: could not set TZ environment: %s", 447162856Sdes strerror(errno)); 448137019Sdes 449137019Sdes if (sshpam_authctxt != NULL) { 450137019Sdes setproctitle("%s [pam]", 451137019Sdes sshpam_authctxt->valid ? pam_user : "unknown"); 452137019Sdes } 453124211Sdes#endif 45469591Sgreen 455124211Sdes sshpam_conv.conv = sshpam_thread_conv; 456124211Sdes sshpam_conv.appdata_ptr = ctxt; 45769591Sgreen 458128460Sdes if (sshpam_authctxt == NULL) 459128460Sdes fatal("%s: PAM authctxt not initialized", __func__); 460128460Sdes 461124211Sdes buffer_init(&buffer); 462124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 463124211Sdes (const void *)&sshpam_conv); 464124211Sdes if (sshpam_err != PAM_SUCCESS) 465124211Sdes goto auth_fail; 466137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 467323129Sdes if (sshpam_err == PAM_MAXTRIES) 468323129Sdes sshpam_set_maxtries_reached(1); 469124211Sdes if (sshpam_err != PAM_SUCCESS) 470124211Sdes goto auth_fail; 471126277Sdes 472126277Sdes if (compat20) { 473162856Sdes if (!do_pam_account()) { 474162856Sdes sshpam_err = PAM_ACCT_EXPIRED; 475126277Sdes goto auth_fail; 476162856Sdes } 477128460Sdes if (sshpam_authctxt->force_pwchange) { 478126277Sdes sshpam_err = pam_chauthtok(sshpam_handle, 479126277Sdes PAM_CHANGE_EXPIRED_AUTHTOK); 480126277Sdes if (sshpam_err != PAM_SUCCESS) 481126277Sdes goto auth_fail; 482137019Sdes sshpam_password_change_required(0); 483126277Sdes } 484126277Sdes } 485126277Sdes 486124211Sdes buffer_put_cstring(&buffer, "OK"); 487126277Sdes 488147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 489126277Sdes /* Export variables set by do_pam_account */ 490126277Sdes buffer_put_int(&buffer, sshpam_account_status); 491128460Sdes buffer_put_int(&buffer, sshpam_authctxt->force_pwchange); 492126277Sdes 493126277Sdes /* Export any environment strings set in child */ 494126277Sdes for(i = 0; environ[i] != NULL; i++) 495126277Sdes ; /* Count */ 496126277Sdes buffer_put_int(&buffer, i); 497126277Sdes for(i = 0; environ[i] != NULL; i++) 498126277Sdes buffer_put_cstring(&buffer, environ[i]); 499126277Sdes 500126277Sdes /* Export any environment strings set by PAM in child */ 501126277Sdes env_from_pam = pam_getenvlist(sshpam_handle); 502126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 503126277Sdes ; /* Count */ 504126277Sdes buffer_put_int(&buffer, i); 505126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 506126277Sdes buffer_put_cstring(&buffer, env_from_pam[i]); 507147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */ 508126277Sdes 509126277Sdes /* XXX - can't do much about an error here */ 510124211Sdes ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer); 511124211Sdes buffer_free(&buffer); 512124211Sdes pthread_exit(NULL); 513124211Sdes 514124211Sdes auth_fail: 515124211Sdes buffer_put_cstring(&buffer, 516124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 517126277Sdes /* XXX - can't do much about an error here */ 518162856Sdes if (sshpam_err == PAM_ACCT_EXPIRED) 519162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer); 520323129Sdes else if (sshpam_maxtries_reached) 521323129Sdes ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, &buffer); 522162856Sdes else 523162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer); 524124211Sdes buffer_free(&buffer); 525124211Sdes pthread_exit(NULL); 526126277Sdes 527124211Sdes return (NULL); /* Avoid warning for non-pthread case */ 52869591Sgreen} 52969591Sgreen 530126277Sdesvoid 531126277Sdessshpam_thread_cleanup(void) 53269591Sgreen{ 533126277Sdes struct pam_ctxt *ctxt = cleanup_ctxt; 53469591Sgreen 535126277Sdes debug3("PAM: %s entering", __func__); 536126277Sdes if (ctxt != NULL && ctxt->pam_thread != 0) { 537126277Sdes pthread_cancel(ctxt->pam_thread); 538126277Sdes pthread_join(ctxt->pam_thread, NULL); 539126277Sdes close(ctxt->pam_psock); 540126277Sdes close(ctxt->pam_csock); 541126277Sdes memset(ctxt, 0, sizeof(*ctxt)); 542126277Sdes cleanup_ctxt = NULL; 543126277Sdes } 544124211Sdes} 54576394Salfred 546124211Sdesstatic int 547149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg, 548124211Sdes struct pam_response **resp, void *data) 549124211Sdes{ 550126277Sdes debug3("PAM: %s entering, %d messages", __func__, n); 551124211Sdes return (PAM_CONV_ERR); 552124211Sdes} 55398941Sdes 554124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL }; 555124211Sdes 556147005Sdesstatic int 557149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg, 558147005Sdes struct pam_response **resp, void *data) 559147005Sdes{ 560147005Sdes struct pam_response *reply; 561147005Sdes int i; 562147005Sdes size_t len; 563147005Sdes 564147005Sdes debug3("PAM: %s called with %d messages", __func__, n); 565147005Sdes *resp = NULL; 566147005Sdes 567147005Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 568147005Sdes return (PAM_CONV_ERR); 569147005Sdes 570162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 571147005Sdes return (PAM_CONV_ERR); 572147005Sdes 573147005Sdes for (i = 0; i < n; ++i) { 574147005Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 575147005Sdes case PAM_ERROR_MSG: 576147005Sdes case PAM_TEXT_INFO: 577147005Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 578147005Sdes buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len); 579147005Sdes buffer_append(&loginmsg, "\n", 1 ); 580147005Sdes reply[i].resp_retcode = PAM_SUCCESS; 581147005Sdes break; 582147005Sdes default: 583147005Sdes goto fail; 584147005Sdes } 585147005Sdes } 586147005Sdes *resp = reply; 587147005Sdes return (PAM_SUCCESS); 588147005Sdes 589147005Sdes fail: 590147005Sdes for(i = 0; i < n; i++) { 591255767Sdes free(reply[i].resp); 592147005Sdes } 593255767Sdes free(reply); 594147005Sdes return (PAM_CONV_ERR); 595147005Sdes} 596147005Sdes 597147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL }; 598147005Sdes 599126277Sdesvoid 600126277Sdessshpam_cleanup(void) 601124211Sdes{ 602181111Sdes if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor())) 603181111Sdes return; 604124211Sdes debug("PAM: cleanup"); 605124211Sdes pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv); 606197679Sdes if (sshpam_session_open) { 607197679Sdes debug("PAM: closing session"); 608197679Sdes pam_close_session(sshpam_handle, PAM_SILENT); 609197679Sdes sshpam_session_open = 0; 610197679Sdes } 611124211Sdes if (sshpam_cred_established) { 612181111Sdes debug("PAM: deleting credentials"); 613124211Sdes pam_setcred(sshpam_handle, PAM_DELETE_CRED); 614124211Sdes sshpam_cred_established = 0; 61569591Sgreen } 616126277Sdes sshpam_authenticated = 0; 617124211Sdes pam_end(sshpam_handle, sshpam_err); 618124211Sdes sshpam_handle = NULL; 61969591Sgreen} 62069591Sgreen 621124211Sdesstatic int 622128460Sdessshpam_init(Authctxt *authctxt) 62369591Sgreen{ 624128460Sdes const char *pam_rhost, *pam_user, *user = authctxt->user; 625149753Sdes const char **ptr_pam_user = &pam_user; 626323129Sdes struct ssh *ssh = active_state; /* XXX */ 62776394Salfred 628124211Sdes if (sshpam_handle != NULL) { 629124211Sdes /* We already have a PAM context; check if the user matches */ 630124211Sdes sshpam_err = pam_get_item(sshpam_handle, 631149753Sdes PAM_USER, (sshpam_const void **)ptr_pam_user); 632124211Sdes if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) 633124211Sdes return (0); 634124211Sdes pam_end(sshpam_handle, sshpam_err); 635124211Sdes sshpam_handle = NULL; 63669591Sgreen } 637124211Sdes debug("PAM: initializing for \"%s\"", user); 638124211Sdes sshpam_err = 639147005Sdes pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle); 640128460Sdes sshpam_authctxt = authctxt; 641128460Sdes 642124211Sdes if (sshpam_err != PAM_SUCCESS) { 643124211Sdes pam_end(sshpam_handle, sshpam_err); 644124211Sdes sshpam_handle = NULL; 645124211Sdes return (-1); 646124211Sdes } 647323129Sdes pam_rhost = auth_get_canonical_hostname(ssh, options.use_dns); 648124211Sdes debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost); 649124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost); 650124211Sdes if (sshpam_err != PAM_SUCCESS) { 651124211Sdes pam_end(sshpam_handle, sshpam_err); 652124211Sdes sshpam_handle = NULL; 653124211Sdes return (-1); 654124211Sdes } 655124211Sdes#ifdef PAM_TTY_KLUDGE 656126277Sdes /* 657126277Sdes * Some silly PAM modules (e.g. pam_time) require a TTY to operate. 658126277Sdes * sshd doesn't set the tty until too late in the auth process and 659124211Sdes * may not even set one (for tty-less connections) 660126277Sdes */ 661124211Sdes debug("PAM: setting PAM_TTY to \"ssh\""); 662124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh"); 663124211Sdes if (sshpam_err != PAM_SUCCESS) { 664124211Sdes pam_end(sshpam_handle, sshpam_err); 665124211Sdes sshpam_handle = NULL; 666124211Sdes return (-1); 667124211Sdes } 66898941Sdes#endif 669124211Sdes return (0); 67069591Sgreen} 67169591Sgreen 672124211Sdesstatic void * 673124211Sdessshpam_init_ctx(Authctxt *authctxt) 67469591Sgreen{ 675124211Sdes struct pam_ctxt *ctxt; 676124211Sdes int socks[2]; 67769591Sgreen 678126277Sdes debug3("PAM: %s entering", __func__); 679162856Sdes /* 680162856Sdes * Refuse to start if we don't have PAM enabled or do_pam_account 681162856Sdes * has previously failed. 682162856Sdes */ 683162856Sdes if (!options.use_pam || sshpam_account_status == 0) 684124211Sdes return NULL; 68576394Salfred 686124211Sdes /* Initialize PAM */ 687128460Sdes if (sshpam_init(authctxt) == -1) { 688124211Sdes error("PAM: initialization failed"); 689124211Sdes return (NULL); 69069591Sgreen } 69169591Sgreen 692181111Sdes ctxt = xcalloc(1, sizeof *ctxt); 69376394Salfred 694124211Sdes /* Start the authentication thread */ 695124211Sdes if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) { 696124211Sdes error("PAM: failed create sockets: %s", strerror(errno)); 697255767Sdes free(ctxt); 698124211Sdes return (NULL); 699124211Sdes } 700124211Sdes ctxt->pam_psock = socks[0]; 701124211Sdes ctxt->pam_csock = socks[1]; 702124211Sdes if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) { 703124211Sdes error("PAM: failed to start authentication thread: %s", 704124211Sdes strerror(errno)); 705124211Sdes close(socks[0]); 706124211Sdes close(socks[1]); 707255767Sdes free(ctxt); 708124211Sdes return (NULL); 709124211Sdes } 710126277Sdes cleanup_ctxt = ctxt; 711124211Sdes return (ctxt); 71269591Sgreen} 71369591Sgreen 714124211Sdesstatic int 715124211Sdessshpam_query(void *ctx, char **name, char **info, 716124211Sdes u_int *num, char ***prompts, u_int **echo_on) 71769591Sgreen{ 718323129Sdes struct ssh *ssh = active_state; /* XXX */ 719124211Sdes Buffer buffer; 720124211Sdes struct pam_ctxt *ctxt = ctx; 721124211Sdes size_t plen; 722124211Sdes u_char type; 723124211Sdes char *msg; 724147005Sdes size_t len, mlen; 72576394Salfred 726126277Sdes debug3("PAM: %s entering", __func__); 727124211Sdes buffer_init(&buffer); 728124211Sdes *name = xstrdup(""); 729124211Sdes *info = xstrdup(""); 730124211Sdes *prompts = xmalloc(sizeof(char *)); 731124211Sdes **prompts = NULL; 732124211Sdes plen = 0; 733124211Sdes *echo_on = xmalloc(sizeof(u_int)); 734124211Sdes while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) { 735124211Sdes type = buffer_get_char(&buffer); 736124211Sdes msg = buffer_get_string(&buffer, NULL); 737147005Sdes mlen = strlen(msg); 738124211Sdes switch (type) { 739124211Sdes case PAM_PROMPT_ECHO_ON: 740124211Sdes case PAM_PROMPT_ECHO_OFF: 741124211Sdes *num = 1; 742147005Sdes len = plen + mlen + 1; 743294336Sdes **prompts = xreallocarray(**prompts, 1, len); 744147005Sdes strlcpy(**prompts + plen, msg, len - plen); 745147005Sdes plen += mlen; 746124211Sdes **echo_on = (type == PAM_PROMPT_ECHO_ON); 747255767Sdes free(msg); 748124211Sdes return (0); 749124211Sdes case PAM_ERROR_MSG: 750124211Sdes case PAM_TEXT_INFO: 751124211Sdes /* accumulate messages */ 752147005Sdes len = plen + mlen + 2; 753294336Sdes **prompts = xreallocarray(**prompts, 1, len); 754147005Sdes strlcpy(**prompts + plen, msg, len - plen); 755147005Sdes plen += mlen; 756147005Sdes strlcat(**prompts + plen, "\n", len - plen); 757147005Sdes plen++; 758255767Sdes free(msg); 759124211Sdes break; 760162856Sdes case PAM_ACCT_EXPIRED: 761323129Sdes case PAM_MAXTRIES: 762323129Sdes if (type == PAM_ACCT_EXPIRED) 763323129Sdes sshpam_account_status = 0; 764323129Sdes if (type == PAM_MAXTRIES) 765323129Sdes sshpam_set_maxtries_reached(1); 766162856Sdes /* FALLTHROUGH */ 767157019Sdes case PAM_AUTH_ERR: 768162856Sdes debug3("PAM: %s", pam_strerror(sshpam_handle, type)); 769157019Sdes if (**prompts != NULL && strlen(**prompts) != 0) { 770157019Sdes *info = **prompts; 771157019Sdes **prompts = NULL; 772157019Sdes *num = 0; 773157019Sdes **echo_on = 0; 774157019Sdes ctxt->pam_done = -1; 775255767Sdes free(msg); 776157019Sdes return 0; 777157019Sdes } 778157019Sdes /* FALLTHROUGH */ 779124211Sdes case PAM_SUCCESS: 780124211Sdes if (**prompts != NULL) { 781124211Sdes /* drain any accumulated messages */ 782126277Sdes debug("PAM: %s", **prompts); 783126277Sdes buffer_append(&loginmsg, **prompts, 784126277Sdes strlen(**prompts)); 785255767Sdes free(**prompts); 786124211Sdes **prompts = NULL; 787124211Sdes } 788124211Sdes if (type == PAM_SUCCESS) { 789147005Sdes if (!sshpam_authctxt->valid || 790147005Sdes (sshpam_authctxt->pw->pw_uid == 0 && 791147005Sdes options.permit_root_login != PERMIT_YES)) 792147005Sdes fatal("Internal error: PAM auth " 793147005Sdes "succeeded when it should have " 794147005Sdes "failed"); 795126277Sdes import_environments(&buffer); 796124211Sdes *num = 0; 797124211Sdes **echo_on = 0; 798124211Sdes ctxt->pam_done = 1; 799255767Sdes free(msg); 800124211Sdes return (0); 801124211Sdes } 802318402Slidl BLACKLIST_NOTIFY(BLACKLIST_BAD_USER, 803318402Slidl sshpam_authctxt->user); 804128460Sdes error("PAM: %s for %s%.100s from %.100s", msg, 805128460Sdes sshpam_authctxt->valid ? "" : "illegal user ", 806128460Sdes sshpam_authctxt->user, 807323129Sdes auth_get_canonical_hostname(ssh, options.use_dns)); 808126277Sdes /* FALLTHROUGH */ 809124211Sdes default: 810124211Sdes *num = 0; 811124211Sdes **echo_on = 0; 812255767Sdes free(msg); 813124211Sdes ctxt->pam_done = -1; 814124211Sdes return (-1); 815124211Sdes } 816124211Sdes } 817124211Sdes return (-1); 818124211Sdes} 81998941Sdes 820323129Sdes/* 821323129Sdes * Returns a junk password of identical length to that the user supplied. 822323129Sdes * Used to mitigate timing attacks against crypt(3)/PAM stacks that 823323129Sdes * vary processing time in proportion to password length. 824323129Sdes */ 825323129Sdesstatic char * 826323129Sdesfake_password(const char *wire_password) 827323129Sdes{ 828323129Sdes const char junk[] = "\b\n\r\177INCORRECT"; 829323129Sdes char *ret = NULL; 830323129Sdes size_t i, l = wire_password != NULL ? strlen(wire_password) : 0; 831323129Sdes 832323129Sdes if (l >= INT_MAX) 833323129Sdes fatal("%s: password length too long: %zu", __func__, l); 834323129Sdes 835323129Sdes ret = malloc(l + 1); 836323136Sdes if (ret == NULL) 837323136Sdes return NULL; 838323129Sdes for (i = 0; i < l; i++) 839323129Sdes ret[i] = junk[i % (sizeof(junk) - 1)]; 840323129Sdes ret[i] = '\0'; 841323129Sdes return ret; 842323129Sdes} 843323129Sdes 844124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */ 845124211Sdesstatic int 846124211Sdessshpam_respond(void *ctx, u_int num, char **resp) 847124211Sdes{ 848124211Sdes Buffer buffer; 849124211Sdes struct pam_ctxt *ctxt = ctx; 850323129Sdes char *fake; 85198941Sdes 852157019Sdes debug2("PAM: %s entering, %u responses", __func__, num); 853124211Sdes switch (ctxt->pam_done) { 854124211Sdes case 1: 855124211Sdes sshpam_authenticated = 1; 856124211Sdes return (0); 857124211Sdes case 0: 858124211Sdes break; 859124211Sdes default: 860124211Sdes return (-1); 861124211Sdes } 862124211Sdes if (num != 1) { 863124211Sdes error("PAM: expected one response, got %u", num); 864124211Sdes return (-1); 865124211Sdes } 866124211Sdes buffer_init(&buffer); 867147005Sdes if (sshpam_authctxt->valid && 868147005Sdes (sshpam_authctxt->pw->pw_uid != 0 || 869149753Sdes options.permit_root_login == PERMIT_YES)) 870147005Sdes buffer_put_cstring(&buffer, *resp); 871323129Sdes else { 872323129Sdes fake = fake_password(*resp); 873323129Sdes buffer_put_cstring(&buffer, fake); 874323129Sdes free(fake); 875323129Sdes } 876126277Sdes if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) { 877126277Sdes buffer_free(&buffer); 878126277Sdes return (-1); 879126277Sdes } 880124211Sdes buffer_free(&buffer); 881124211Sdes return (1); 88269591Sgreen} 88369591Sgreen 884124211Sdesstatic void 885124211Sdessshpam_free_ctx(void *ctxtp) 88669591Sgreen{ 887124211Sdes struct pam_ctxt *ctxt = ctxtp; 888124211Sdes 889126277Sdes debug3("PAM: %s entering", __func__); 890126277Sdes sshpam_thread_cleanup(); 891255767Sdes free(ctxt); 892124211Sdes /* 893124211Sdes * We don't call sshpam_cleanup() here because we may need the PAM 894124211Sdes * handle at a later stage, e.g. when setting up a session. It's 895124211Sdes * still on the cleanup list, so pam_end() *will* be called before 896124211Sdes * the server process terminates. 897124211Sdes */ 89869591Sgreen} 89969591Sgreen 900124211SdesKbdintDevice sshpam_device = { 901124211Sdes "pam", 902124211Sdes sshpam_init_ctx, 903124211Sdes sshpam_query, 904124211Sdes sshpam_respond, 905124211Sdes sshpam_free_ctx 906124211Sdes}; 907124211Sdes 908124211SdesKbdintDevice mm_sshpam_device = { 909124211Sdes "pam", 910124211Sdes mm_sshpam_init_ctx, 911124211Sdes mm_sshpam_query, 912124211Sdes mm_sshpam_respond, 913124211Sdes mm_sshpam_free_ctx 914124211Sdes}; 915124211Sdes 91698941Sdes/* 917124211Sdes * This replaces auth-pam.c 91869591Sgreen */ 919124211Sdesvoid 920128460Sdesstart_pam(Authctxt *authctxt) 92169591Sgreen{ 922124211Sdes if (!options.use_pam) 923124211Sdes fatal("PAM: initialisation requested when UsePAM=no"); 92469591Sgreen 925128460Sdes if (sshpam_init(authctxt) == -1) 926124211Sdes fatal("PAM: initialisation failed"); 92769591Sgreen} 92869591Sgreen 929124211Sdesvoid 930124211Sdesfinish_pam(void) 93169591Sgreen{ 932126277Sdes sshpam_cleanup(); 93369591Sgreen} 93469591Sgreen 935124211Sdesu_int 936124211Sdesdo_pam_account(void) 93769591Sgreen{ 938147005Sdes debug("%s: called", __func__); 939126277Sdes if (sshpam_account_status != -1) 940126277Sdes return (sshpam_account_status); 941126277Sdes 942124211Sdes sshpam_err = pam_acct_mgmt(sshpam_handle, 0); 943147005Sdes debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, 944147005Sdes pam_strerror(sshpam_handle, sshpam_err)); 945149753Sdes 946126277Sdes if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) { 947126277Sdes sshpam_account_status = 0; 948126277Sdes return (sshpam_account_status); 949124211Sdes } 95069591Sgreen 951126277Sdes if (sshpam_err == PAM_NEW_AUTHTOK_REQD) 952137019Sdes sshpam_password_change_required(1); 95369591Sgreen 954126277Sdes sshpam_account_status = 1; 955126277Sdes return (sshpam_account_status); 956124211Sdes} 95798941Sdes 958124211Sdesvoid 959124211Sdesdo_pam_setcred(int init) 960124211Sdes{ 961124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 962147005Sdes (const void *)&store_conv); 963124211Sdes if (sshpam_err != PAM_SUCCESS) 964124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 965124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 966124211Sdes if (init) { 967124211Sdes debug("PAM: establishing credentials"); 968124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED); 969124211Sdes } else { 970124211Sdes debug("PAM: reinitializing credentials"); 971124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED); 972124211Sdes } 973124211Sdes if (sshpam_err == PAM_SUCCESS) { 974124211Sdes sshpam_cred_established = 1; 975124211Sdes return; 976124211Sdes } 977124211Sdes if (sshpam_authenticated) 978124211Sdes fatal("PAM: pam_setcred(): %s", 979124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 980124211Sdes else 981124211Sdes debug("PAM: pam_setcred(): %s", 982124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 98369591Sgreen} 98469591Sgreen 985124211Sdesstatic int 986149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg, 987124211Sdes struct pam_response **resp, void *data) 988106130Sdes{ 989124211Sdes char input[PAM_MAX_MSG_SIZE]; 990124211Sdes struct pam_response *reply; 991106130Sdes int i; 992106130Sdes 993126277Sdes debug3("PAM: %s called with %d messages", __func__, n); 994126277Sdes 995124211Sdes *resp = NULL; 996124211Sdes 997126277Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO)) 998124211Sdes return (PAM_CONV_ERR); 999124211Sdes 1000162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 1001124211Sdes return (PAM_CONV_ERR); 1002124211Sdes 1003124211Sdes for (i = 0; i < n; ++i) { 1004124211Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 1005124211Sdes case PAM_PROMPT_ECHO_OFF: 1006124211Sdes reply[i].resp = 1007126277Sdes read_passphrase(PAM_MSG_MEMBER(msg, i, msg), 1008124211Sdes RP_ALLOW_STDIN); 1009124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 1010124211Sdes break; 1011124211Sdes case PAM_PROMPT_ECHO_ON: 1012126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 1013181111Sdes if (fgets(input, sizeof input, stdin) == NULL) 1014181111Sdes input[0] = '\0'; 1015137019Sdes if ((reply[i].resp = strdup(input)) == NULL) 1016137019Sdes goto fail; 1017124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 1018124211Sdes break; 1019124211Sdes case PAM_ERROR_MSG: 1020124211Sdes case PAM_TEXT_INFO: 1021126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 1022124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 1023124211Sdes break; 1024124211Sdes default: 1025124211Sdes goto fail; 1026124211Sdes } 1027106130Sdes } 1028124211Sdes *resp = reply; 1029124211Sdes return (PAM_SUCCESS); 1030124211Sdes 1031124211Sdes fail: 1032124211Sdes for(i = 0; i < n; i++) { 1033255767Sdes free(reply[i].resp); 1034124211Sdes } 1035255767Sdes free(reply); 1036124211Sdes return (PAM_CONV_ERR); 1037106130Sdes} 1038106130Sdes 1039137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; 1040126277Sdes 1041124211Sdes/* 1042124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't 1043124211Sdes * support that 1044124211Sdes */ 1045124211Sdesvoid 1046124211Sdesdo_pam_chauthtok(void) 104769591Sgreen{ 1048124211Sdes if (use_privsep) 1049124211Sdes fatal("Password expired (unable to change with privsep)"); 1050124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1051126277Sdes (const void *)&tty_conv); 1052124211Sdes if (sshpam_err != PAM_SUCCESS) 1053124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 1054124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 1055124211Sdes debug("PAM: changing password"); 1056124211Sdes sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); 1057124211Sdes if (sshpam_err != PAM_SUCCESS) 1058124211Sdes fatal("PAM: pam_chauthtok(): %s", 1059124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 106069591Sgreen} 106169591Sgreen 1062126277Sdesvoid 1063126277Sdesdo_pam_session(void) 1064126277Sdes{ 1065126277Sdes debug3("PAM: opening session"); 1066126277Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1067126277Sdes (const void *)&store_conv); 1068126277Sdes if (sshpam_err != PAM_SUCCESS) 1069126277Sdes fatal("PAM: failed to set PAM_CONV: %s", 1070126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1071126277Sdes sshpam_err = pam_open_session(sshpam_handle, 0); 1072147005Sdes if (sshpam_err == PAM_SUCCESS) 1073147005Sdes sshpam_session_open = 1; 1074147005Sdes else { 1075147005Sdes sshpam_session_open = 0; 1076147005Sdes disable_forwarding(); 1077147005Sdes error("PAM: pam_open_session(): %s", 1078126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1079147005Sdes } 1080147005Sdes 1081126277Sdes} 1082126277Sdes 1083147005Sdesint 1084147005Sdesis_pam_session_open(void) 1085147005Sdes{ 1086147005Sdes return sshpam_session_open; 1087147005Sdes} 1088147005Sdes 1089126277Sdes/* 1090124211Sdes * Set a PAM environment string. We need to do this so that the session 1091124211Sdes * modules can handle things like Kerberos/GSI credentials that appear 1092124211Sdes * during the ssh authentication process. 1093124211Sdes */ 1094124211Sdesint 1095126277Sdesdo_pam_putenv(char *name, char *value) 109669591Sgreen{ 1097124211Sdes int ret = 1; 1098126277Sdes#ifdef HAVE_PAM_PUTENV 1099124211Sdes char *compound; 1100124211Sdes size_t len; 110169591Sgreen 1102124211Sdes len = strlen(name) + strlen(value) + 2; 1103124211Sdes compound = xmalloc(len); 110469591Sgreen 1105124211Sdes snprintf(compound, len, "%s=%s", name, value); 1106124211Sdes ret = pam_putenv(sshpam_handle, compound); 1107255767Sdes free(compound); 1108124211Sdes#endif 110969591Sgreen 1110124211Sdes return (ret); 1111124211Sdes} 111269591Sgreen 1113126277Sdeschar ** 1114126277Sdesfetch_pam_child_environment(void) 1115124211Sdes{ 1116126277Sdes return sshpam_env; 111769591Sgreen} 111869591Sgreen 1119124211Sdeschar ** 1120124211Sdesfetch_pam_environment(void) 1121124211Sdes{ 1122124211Sdes return (pam_getenvlist(sshpam_handle)); 1123124211Sdes} 1124124211Sdes 1125124211Sdesvoid 1126124211Sdesfree_pam_environment(char **env) 1127124211Sdes{ 1128124211Sdes char **envp; 1129124211Sdes 1130124211Sdes if (env == NULL) 1131124211Sdes return; 1132124211Sdes 1133124211Sdes for (envp = env; *envp; envp++) 1134255767Sdes free(*envp); 1135255767Sdes free(env); 1136124211Sdes} 1137124211Sdes 1138137019Sdes/* 1139137019Sdes * "Blind" conversation function for password authentication. Assumes that 1140137019Sdes * echo-off prompts are for the password and stores messages for later 1141137019Sdes * display. 1142137019Sdes */ 1143137019Sdesstatic int 1144149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, 1145137019Sdes struct pam_response **resp, void *data) 1146137019Sdes{ 1147137019Sdes struct pam_response *reply; 1148137019Sdes int i; 1149137019Sdes size_t len; 1150137019Sdes 1151137019Sdes debug3("PAM: %s called with %d messages", __func__, n); 1152137019Sdes 1153137019Sdes *resp = NULL; 1154137019Sdes 1155137019Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 1156137019Sdes return (PAM_CONV_ERR); 1157137019Sdes 1158181111Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 1159137019Sdes return (PAM_CONV_ERR); 1160137019Sdes 1161137019Sdes for (i = 0; i < n; ++i) { 1162137019Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 1163137019Sdes case PAM_PROMPT_ECHO_OFF: 1164137019Sdes if (sshpam_password == NULL) 1165137019Sdes goto fail; 1166137019Sdes if ((reply[i].resp = strdup(sshpam_password)) == NULL) 1167137019Sdes goto fail; 1168137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1169137019Sdes break; 1170137019Sdes case PAM_ERROR_MSG: 1171137019Sdes case PAM_TEXT_INFO: 1172137019Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 1173137019Sdes if (len > 0) { 1174137019Sdes buffer_append(&loginmsg, 1175137019Sdes PAM_MSG_MEMBER(msg, i, msg), len); 1176137019Sdes buffer_append(&loginmsg, "\n", 1); 1177137019Sdes } 1178137019Sdes if ((reply[i].resp = strdup("")) == NULL) 1179137019Sdes goto fail; 1180137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1181137019Sdes break; 1182137019Sdes default: 1183137019Sdes goto fail; 1184137019Sdes } 1185137019Sdes } 1186137019Sdes *resp = reply; 1187137019Sdes return (PAM_SUCCESS); 1188137019Sdes 1189149753Sdes fail: 1190137019Sdes for(i = 0; i < n; i++) { 1191255767Sdes free(reply[i].resp); 1192137019Sdes } 1193255767Sdes free(reply); 1194137019Sdes return (PAM_CONV_ERR); 1195137019Sdes} 1196137019Sdes 1197137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; 1198137019Sdes 1199137019Sdes/* 1200137019Sdes * Attempt password authentication via PAM 1201137019Sdes */ 1202137019Sdesint 1203137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password) 1204137019Sdes{ 1205137019Sdes int flags = (options.permit_empty_passwd == 0 ? 1206137019Sdes PAM_DISALLOW_NULL_AUTHTOK : 0); 1207323129Sdes char *fake = NULL; 1208137019Sdes 1209137019Sdes if (!options.use_pam || sshpam_handle == NULL) 1210137019Sdes fatal("PAM: %s called when PAM disabled or failed to " 1211137019Sdes "initialise.", __func__); 1212137019Sdes 1213137019Sdes sshpam_password = password; 1214137019Sdes sshpam_authctxt = authctxt; 1215137019Sdes 1216137019Sdes /* 1217137019Sdes * If the user logging in is invalid, or is root but is not permitted 1218137019Sdes * by PermitRootLogin, use an invalid password to prevent leaking 1219137019Sdes * information via timing (eg if the PAM config has a delay on fail). 1220137019Sdes */ 1221137019Sdes if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && 1222149753Sdes options.permit_root_login != PERMIT_YES)) 1223323129Sdes sshpam_password = fake = fake_password(password); 1224137019Sdes 1225137019Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1226137019Sdes (const void *)&passwd_conv); 1227137019Sdes if (sshpam_err != PAM_SUCCESS) 1228137019Sdes fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, 1229137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1230137019Sdes 1231137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 1232137019Sdes sshpam_password = NULL; 1233323129Sdes free(fake); 1234323129Sdes if (sshpam_err == PAM_MAXTRIES) 1235323129Sdes sshpam_set_maxtries_reached(1); 1236137019Sdes if (sshpam_err == PAM_SUCCESS && authctxt->valid) { 1237137019Sdes debug("PAM: password authentication accepted for %.100s", 1238137019Sdes authctxt->user); 1239149753Sdes return 1; 1240137019Sdes } else { 1241137019Sdes debug("PAM: password authentication failed for %.100s: %s", 1242137019Sdes authctxt->valid ? authctxt->user : "an illegal user", 1243137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1244137019Sdes return 0; 1245137019Sdes } 1246137019Sdes} 1247323129Sdes 1248323129Sdesint 1249323129Sdessshpam_get_maxtries_reached(void) 1250323129Sdes{ 1251323129Sdes return sshpam_maxtries_reached; 1252323129Sdes} 1253323129Sdes 1254323129Sdesvoid 1255323129Sdessshpam_set_maxtries_reached(int reached) 1256323129Sdes{ 1257323129Sdes if (reached == 0 || sshpam_maxtries_reached) 1258323129Sdes return; 1259323129Sdes sshpam_maxtries_reached = 1; 1260323129Sdes options.password_authentication = 0; 1261323129Sdes options.kbd_interactive_authentication = 0; 1262323129Sdes options.challenge_response_authentication = 0; 1263323129Sdes} 126469591Sgreen#endif /* USE_PAM */ 1265