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 48225614Sdes/* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */ 4969591Sgreen#include "includes.h" 5069591Sgreen 51162856Sdes#include <sys/types.h> 52162856Sdes#include <sys/stat.h> 53162856Sdes#include <sys/wait.h> 54162856Sdes 55162856Sdes#include <errno.h> 56162856Sdes#include <signal.h> 57162856Sdes#include <stdarg.h> 58162856Sdes#include <string.h> 59162856Sdes#include <unistd.h> 60162856Sdes 6169591Sgreen#ifdef USE_PAM 62126277Sdes#if defined(HAVE_SECURITY_PAM_APPL_H) 63124211Sdes#include <security/pam_appl.h> 64126277Sdes#elif defined (HAVE_PAM_PAM_APPL_H) 65126277Sdes#include <pam/pam_appl.h> 66126277Sdes#endif 67124211Sdes 68149753Sdes/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */ 69149753Sdes#ifdef PAM_SUN_CODEBASE 70149753Sdes# define sshpam_const /* Solaris, HP-UX, AIX */ 71149753Sdes#else 72149753Sdes# define sshpam_const const /* LinuxPAM, OpenPAM */ 73149753Sdes#endif 74149753Sdes 75162856Sdes/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */ 76162856Sdes#ifdef PAM_SUN_CODEBASE 77162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member) 78162856Sdes#else 79162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member) 80162856Sdes#endif 81162856Sdes 82162856Sdes#include "xmalloc.h" 83162856Sdes#include "buffer.h" 84162856Sdes#include "key.h" 85162856Sdes#include "hostfile.h" 8698941Sdes#include "auth.h" 8798941Sdes#include "auth-pam.h" 8898941Sdes#include "canohost.h" 89124211Sdes#include "log.h" 90124211Sdes#include "msg.h" 91124211Sdes#include "packet.h" 92137019Sdes#include "misc.h" 93124211Sdes#include "servconf.h" 94124211Sdes#include "ssh2.h" 95124211Sdes#include "auth-options.h" 96162856Sdes#ifdef GSSAPI 97162856Sdes#include "ssh-gss.h" 98162856Sdes#endif 99162856Sdes#include "monitor_wrap.h" 10069591Sgreen 101124211Sdesextern ServerOptions options; 102126277Sdesextern Buffer loginmsg; 103126277Sdesextern int compat20; 104128460Sdesextern u_int utmp_len; 10569591Sgreen 106147005Sdes/* so we don't silently change behaviour */ 107124211Sdes#ifdef USE_POSIX_THREADS 108147005Sdes# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK" 109147005Sdes#endif 110147005Sdes 111147005Sdes/* 112147005Sdes * Formerly known as USE_POSIX_THREADS, using this is completely unsupported 113147005Sdes * and generally a bad idea. Use at own risk and do not expect support if 114147005Sdes * this breaks. 115147005Sdes */ 116147005Sdes#ifdef UNSUPPORTED_POSIX_THREADS_HACK 117124211Sdes#include <pthread.h> 118124211Sdes/* 119126277Sdes * Avoid namespace clash when *not* using pthreads for systems *with* 120126277Sdes * pthreads, which unconditionally define pthread_t via sys/types.h 121124211Sdes * (e.g. Linux) 122124211Sdes */ 123126277Sdestypedef pthread_t sp_pthread_t; 124124211Sdes#else 125126277Sdestypedef pid_t sp_pthread_t; 126126277Sdes#endif 127126277Sdes 128126277Sdesstruct pam_ctxt { 129126277Sdes sp_pthread_t pam_thread; 130126277Sdes int pam_psock; 131126277Sdes int pam_csock; 132126277Sdes int pam_done; 133126277Sdes}; 134126277Sdes 135126277Sdesstatic void sshpam_free_ctx(void *); 136126277Sdesstatic struct pam_ctxt *cleanup_ctxt; 137126277Sdes 138147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 139124211Sdes/* 140124211Sdes * Simulate threads with processes. 141124211Sdes */ 142106130Sdes 143126277Sdesstatic int sshpam_thread_status = -1; 144126277Sdesstatic mysig_t sshpam_oldsig; 145126277Sdes 146149753Sdesstatic void 147126277Sdessshpam_sigchld_handler(int sig) 148126277Sdes{ 149137019Sdes signal(SIGCHLD, SIG_DFL); 150126277Sdes if (cleanup_ctxt == NULL) 151126277Sdes return; /* handler called after PAM cleanup, shouldn't happen */ 152137019Sdes if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG) 153149753Sdes <= 0) { 154137019Sdes /* PAM thread has not exitted, privsep slave must have */ 155137019Sdes kill(cleanup_ctxt->pam_thread, SIGTERM); 156137019Sdes if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0) 157137019Sdes <= 0) 158137019Sdes return; /* could not wait */ 159137019Sdes } 160126277Sdes if (WIFSIGNALED(sshpam_thread_status) && 161126277Sdes WTERMSIG(sshpam_thread_status) == SIGTERM) 162126277Sdes return; /* terminated by pthread_cancel */ 163126277Sdes if (!WIFEXITED(sshpam_thread_status)) 164181111Sdes sigdie("PAM: authentication thread exited unexpectedly"); 165126277Sdes if (WEXITSTATUS(sshpam_thread_status) != 0) 166181111Sdes sigdie("PAM: authentication thread exited uncleanly"); 167126277Sdes} 168126277Sdes 169162856Sdes/* ARGSUSED */ 170124211Sdesstatic void 171162856Sdespthread_exit(void *value) 172124211Sdes{ 173124211Sdes _exit(0); 174124211Sdes} 17569591Sgreen 176162856Sdes/* ARGSUSED */ 177124211Sdesstatic int 178162856Sdespthread_create(sp_pthread_t *thread, const void *attr, 179124211Sdes void *(*thread_start)(void *), void *arg) 180124211Sdes{ 181124211Sdes pid_t pid; 182149753Sdes struct pam_ctxt *ctx = arg; 18369591Sgreen 184128460Sdes sshpam_thread_status = -1; 185124211Sdes switch ((pid = fork())) { 186124211Sdes case -1: 187124211Sdes error("fork(): %s", strerror(errno)); 188124211Sdes return (-1); 189124211Sdes case 0: 190149753Sdes close(ctx->pam_psock); 191149753Sdes ctx->pam_psock = -1; 192124211Sdes thread_start(arg); 193124211Sdes _exit(1); 194124211Sdes default: 195124211Sdes *thread = pid; 196149753Sdes close(ctx->pam_csock); 197149753Sdes ctx->pam_csock = -1; 198126277Sdes sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler); 199124211Sdes return (0); 200124211Sdes } 201124211Sdes} 20269591Sgreen 203124211Sdesstatic int 204124211Sdespthread_cancel(sp_pthread_t thread) 20576394Salfred{ 206126277Sdes signal(SIGCHLD, sshpam_oldsig); 207124211Sdes return (kill(thread, SIGTERM)); 20876394Salfred} 20976394Salfred 210162856Sdes/* ARGSUSED */ 211124211Sdesstatic int 212162856Sdespthread_join(sp_pthread_t thread, void **value) 21398941Sdes{ 214124211Sdes int status; 215124211Sdes 216126277Sdes if (sshpam_thread_status != -1) 217126277Sdes return (sshpam_thread_status); 218126277Sdes signal(SIGCHLD, sshpam_oldsig); 219124211Sdes waitpid(thread, &status, 0); 220124211Sdes return (status); 22198941Sdes} 222124211Sdes#endif 22398941Sdes 224124211Sdes 225124211Sdesstatic pam_handle_t *sshpam_handle = NULL; 226124211Sdesstatic int sshpam_err = 0; 227124211Sdesstatic int sshpam_authenticated = 0; 228124211Sdesstatic int sshpam_session_open = 0; 229124211Sdesstatic int sshpam_cred_established = 0; 230126277Sdesstatic int sshpam_account_status = -1; 231126277Sdesstatic char **sshpam_env = NULL; 232128460Sdesstatic Authctxt *sshpam_authctxt = NULL; 233137019Sdesstatic const char *sshpam_password = NULL; 234147005Sdesstatic char badpw[] = "\b\n\r\177INCORRECT"; 235124211Sdes 236126277Sdes/* Some PAM implementations don't implement this */ 237126277Sdes#ifndef HAVE_PAM_GETENVLIST 238126277Sdesstatic char ** 239126277Sdespam_getenvlist(pam_handle_t *pamh) 240126277Sdes{ 241126277Sdes /* 242126277Sdes * XXX - If necessary, we can still support envrionment passing 243126277Sdes * for platforms without pam_getenvlist by searching for known 244126277Sdes * env vars (e.g. KRB5CCNAME) from the PAM environment. 245126277Sdes */ 246126277Sdes return NULL; 247126277Sdes} 248126277Sdes#endif 249124211Sdes 250137019Sdes/* 251137019Sdes * Some platforms, notably Solaris, do not enforce password complexity 252137019Sdes * rules during pam_chauthtok() if the real uid of the calling process 253137019Sdes * is 0, on the assumption that it's being called by "passwd" run by root. 254137019Sdes * This wraps pam_chauthtok and sets/restore the real uid so PAM will do 255137019Sdes * the right thing. 256137019Sdes */ 257137019Sdes#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID 258137019Sdesstatic int 259137019Sdessshpam_chauthtok_ruid(pam_handle_t *pamh, int flags) 260137019Sdes{ 261137019Sdes int result; 262137019Sdes 263137019Sdes if (sshpam_authctxt == NULL) 264137019Sdes fatal("PAM: sshpam_authctxt not initialized"); 265137019Sdes if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1) 266137019Sdes fatal("%s: setreuid failed: %s", __func__, strerror(errno)); 267137019Sdes result = pam_chauthtok(pamh, flags); 268137019Sdes if (setreuid(0, -1) == -1) 269137019Sdes fatal("%s: setreuid failed: %s", __func__, strerror(errno)); 270137019Sdes return result; 271137019Sdes} 272137019Sdes# define pam_chauthtok(a,b) (sshpam_chauthtok_ruid((a), (b))) 273137019Sdes#endif 274137019Sdes 275126277Sdesvoid 276137019Sdessshpam_password_change_required(int reqd) 277126277Sdes{ 278126277Sdes debug3("%s %d", __func__, reqd); 279128460Sdes if (sshpam_authctxt == NULL) 280128460Sdes fatal("%s: PAM authctxt not initialized", __func__); 281128460Sdes sshpam_authctxt->force_pwchange = reqd; 282126277Sdes if (reqd) { 283126277Sdes no_port_forwarding_flag |= 2; 284126277Sdes no_agent_forwarding_flag |= 2; 285126277Sdes no_x11_forwarding_flag |= 2; 286126277Sdes } else { 287126277Sdes no_port_forwarding_flag &= ~2; 288126277Sdes no_agent_forwarding_flag &= ~2; 289126277Sdes no_x11_forwarding_flag &= ~2; 290126277Sdes } 291126277Sdes} 292124211Sdes 293126277Sdes/* Import regular and PAM environment from subprocess */ 294126277Sdesstatic void 295126277Sdesimport_environments(Buffer *b) 296126277Sdes{ 297126277Sdes char *env; 298126277Sdes u_int i, num_env; 299126277Sdes int err; 300126277Sdes 301126277Sdes debug3("PAM: %s entering", __func__); 302126277Sdes 303147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 304126277Sdes /* Import variables set by do_pam_account */ 305126277Sdes sshpam_account_status = buffer_get_int(b); 306137019Sdes sshpam_password_change_required(buffer_get_int(b)); 307126277Sdes 308126277Sdes /* Import environment from subprocess */ 309126277Sdes num_env = buffer_get_int(b); 310162856Sdes if (num_env > 1024) 311162856Sdes fatal("%s: received %u environment variables, expected <= 1024", 312162856Sdes __func__, num_env); 313162856Sdes sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env)); 314126277Sdes debug3("PAM: num env strings %d", num_env); 315126277Sdes for(i = 0; i < num_env; i++) 316126277Sdes sshpam_env[i] = buffer_get_string(b, NULL); 317126277Sdes 318126277Sdes sshpam_env[num_env] = NULL; 319126277Sdes 320126277Sdes /* Import PAM environment from subprocess */ 321126277Sdes num_env = buffer_get_int(b); 322126277Sdes debug("PAM: num PAM env strings %d", num_env); 323126277Sdes for(i = 0; i < num_env; i++) { 324126277Sdes env = buffer_get_string(b, NULL); 325126277Sdes 326126277Sdes#ifdef HAVE_PAM_PUTENV 327126277Sdes /* Errors are not fatal here */ 328126277Sdes if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) { 329126277Sdes error("PAM: pam_putenv: %s", 330126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 331126277Sdes } 332126277Sdes#endif 333126277Sdes } 334128460Sdes#endif 335126277Sdes} 336126277Sdes 33776394Salfred/* 338124211Sdes * Conversation function for authentication thread. 33969591Sgreen */ 340124211Sdesstatic int 341149753Sdessshpam_thread_conv(int n, sshpam_const struct pam_message **msg, 342124211Sdes struct pam_response **resp, void *data) 34369591Sgreen{ 344124211Sdes Buffer buffer; 345124211Sdes struct pam_ctxt *ctxt; 34669591Sgreen struct pam_response *reply; 347124211Sdes int i; 34869591Sgreen 349126277Sdes debug3("PAM: %s entering, %d messages", __func__, n); 350124211Sdes *resp = NULL; 35169591Sgreen 352137019Sdes if (data == NULL) { 353137019Sdes error("PAM: conversation function passed a null context"); 354137019Sdes return (PAM_CONV_ERR); 355137019Sdes } 356124211Sdes ctxt = data; 357124211Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 358124211Sdes return (PAM_CONV_ERR); 359124211Sdes 360162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 361124211Sdes return (PAM_CONV_ERR); 362124211Sdes 363124211Sdes buffer_init(&buffer); 364124211Sdes for (i = 0; i < n; ++i) { 365124211Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 366124211Sdes case PAM_PROMPT_ECHO_OFF: 367126277Sdes buffer_put_cstring(&buffer, 368124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 369126277Sdes if (ssh_msg_send(ctxt->pam_csock, 370126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 371126277Sdes goto fail; 372126277Sdes if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1) 373126277Sdes goto fail; 374124211Sdes if (buffer_get_char(&buffer) != PAM_AUTHTOK) 375124211Sdes goto fail; 376124211Sdes reply[i].resp = buffer_get_string(&buffer, NULL); 377124211Sdes break; 378124211Sdes case PAM_PROMPT_ECHO_ON: 379126277Sdes buffer_put_cstring(&buffer, 380124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 381126277Sdes if (ssh_msg_send(ctxt->pam_csock, 382126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 383126277Sdes goto fail; 384126277Sdes if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1) 385126277Sdes goto fail; 386124211Sdes if (buffer_get_char(&buffer) != PAM_AUTHTOK) 387124211Sdes goto fail; 388124211Sdes reply[i].resp = buffer_get_string(&buffer, NULL); 389124211Sdes break; 390124211Sdes case PAM_ERROR_MSG: 391126277Sdes buffer_put_cstring(&buffer, 392124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 393126277Sdes if (ssh_msg_send(ctxt->pam_csock, 394126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 395126277Sdes goto fail; 396124211Sdes break; 397124211Sdes case PAM_TEXT_INFO: 398126277Sdes buffer_put_cstring(&buffer, 399124211Sdes PAM_MSG_MEMBER(msg, i, msg)); 400126277Sdes if (ssh_msg_send(ctxt->pam_csock, 401126277Sdes PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1) 402126277Sdes goto fail; 403124211Sdes break; 404124211Sdes default: 405124211Sdes goto fail; 40669591Sgreen } 407124211Sdes buffer_clear(&buffer); 40869591Sgreen } 409124211Sdes buffer_free(&buffer); 41069591Sgreen *resp = reply; 411124211Sdes return (PAM_SUCCESS); 41269591Sgreen 413124211Sdes fail: 414124211Sdes for(i = 0; i < n; i++) { 415255767Sdes free(reply[i].resp); 416124211Sdes } 417255767Sdes free(reply); 418124211Sdes buffer_free(&buffer); 419124211Sdes return (PAM_CONV_ERR); 42069591Sgreen} 42169591Sgreen 422124211Sdes/* 423124211Sdes * Authentication thread. 424124211Sdes */ 425124211Sdesstatic void * 426124211Sdessshpam_thread(void *ctxtp) 42769591Sgreen{ 428124211Sdes struct pam_ctxt *ctxt = ctxtp; 429124211Sdes Buffer buffer; 430124211Sdes struct pam_conv sshpam_conv; 431137019Sdes int flags = (options.permit_empty_passwd == 0 ? 432137019Sdes PAM_DISALLOW_NULL_AUTHTOK : 0); 433147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 434126277Sdes extern char **environ; 435126277Sdes char **env_from_pam; 436126277Sdes u_int i; 437124211Sdes const char *pam_user; 438149753Sdes const char **ptr_pam_user = &pam_user; 439162856Sdes char *tz = getenv("TZ"); 44069591Sgreen 441149753Sdes pam_get_item(sshpam_handle, PAM_USER, 442149753Sdes (sshpam_const void **)ptr_pam_user); 443162856Sdes 444126277Sdes environ[0] = NULL; 445162856Sdes if (tz != NULL) 446162856Sdes if (setenv("TZ", tz, 1) == -1) 447162856Sdes error("PAM: could not set TZ environment: %s", 448162856Sdes strerror(errno)); 449137019Sdes 450137019Sdes if (sshpam_authctxt != NULL) { 451137019Sdes setproctitle("%s [pam]", 452137019Sdes sshpam_authctxt->valid ? pam_user : "unknown"); 453137019Sdes } 454124211Sdes#endif 45569591Sgreen 456124211Sdes sshpam_conv.conv = sshpam_thread_conv; 457124211Sdes sshpam_conv.appdata_ptr = ctxt; 45869591Sgreen 459128460Sdes if (sshpam_authctxt == NULL) 460128460Sdes fatal("%s: PAM authctxt not initialized", __func__); 461128460Sdes 462124211Sdes buffer_init(&buffer); 463124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 464124211Sdes (const void *)&sshpam_conv); 465124211Sdes if (sshpam_err != PAM_SUCCESS) 466124211Sdes goto auth_fail; 467137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 468124211Sdes if (sshpam_err != PAM_SUCCESS) 469124211Sdes goto auth_fail; 470126277Sdes 471126277Sdes if (compat20) { 472162856Sdes if (!do_pam_account()) { 473162856Sdes sshpam_err = PAM_ACCT_EXPIRED; 474126277Sdes goto auth_fail; 475162856Sdes } 476128460Sdes if (sshpam_authctxt->force_pwchange) { 477126277Sdes sshpam_err = pam_chauthtok(sshpam_handle, 478126277Sdes PAM_CHANGE_EXPIRED_AUTHTOK); 479126277Sdes if (sshpam_err != PAM_SUCCESS) 480126277Sdes goto auth_fail; 481137019Sdes sshpam_password_change_required(0); 482126277Sdes } 483126277Sdes } 484126277Sdes 485124211Sdes buffer_put_cstring(&buffer, "OK"); 486126277Sdes 487147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 488126277Sdes /* Export variables set by do_pam_account */ 489126277Sdes buffer_put_int(&buffer, sshpam_account_status); 490128460Sdes buffer_put_int(&buffer, sshpam_authctxt->force_pwchange); 491126277Sdes 492126277Sdes /* Export any environment strings set in child */ 493126277Sdes for(i = 0; environ[i] != NULL; i++) 494126277Sdes ; /* Count */ 495126277Sdes buffer_put_int(&buffer, i); 496126277Sdes for(i = 0; environ[i] != NULL; i++) 497126277Sdes buffer_put_cstring(&buffer, environ[i]); 498126277Sdes 499126277Sdes /* Export any environment strings set by PAM in child */ 500126277Sdes env_from_pam = pam_getenvlist(sshpam_handle); 501126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 502126277Sdes ; /* Count */ 503126277Sdes buffer_put_int(&buffer, i); 504126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 505126277Sdes buffer_put_cstring(&buffer, env_from_pam[i]); 506147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */ 507126277Sdes 508126277Sdes /* XXX - can't do much about an error here */ 509124211Sdes ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer); 510124211Sdes buffer_free(&buffer); 511124211Sdes pthread_exit(NULL); 512124211Sdes 513124211Sdes auth_fail: 514124211Sdes buffer_put_cstring(&buffer, 515124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 516126277Sdes /* XXX - can't do much about an error here */ 517162856Sdes if (sshpam_err == PAM_ACCT_EXPIRED) 518162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer); 519162856Sdes else 520162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer); 521124211Sdes buffer_free(&buffer); 522124211Sdes pthread_exit(NULL); 523126277Sdes 524124211Sdes return (NULL); /* Avoid warning for non-pthread case */ 52569591Sgreen} 52669591Sgreen 527126277Sdesvoid 528126277Sdessshpam_thread_cleanup(void) 52969591Sgreen{ 530126277Sdes struct pam_ctxt *ctxt = cleanup_ctxt; 53169591Sgreen 532126277Sdes debug3("PAM: %s entering", __func__); 533126277Sdes if (ctxt != NULL && ctxt->pam_thread != 0) { 534126277Sdes pthread_cancel(ctxt->pam_thread); 535126277Sdes pthread_join(ctxt->pam_thread, NULL); 536126277Sdes close(ctxt->pam_psock); 537126277Sdes close(ctxt->pam_csock); 538126277Sdes memset(ctxt, 0, sizeof(*ctxt)); 539126277Sdes cleanup_ctxt = NULL; 540126277Sdes } 541124211Sdes} 54276394Salfred 543124211Sdesstatic int 544149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg, 545124211Sdes struct pam_response **resp, void *data) 546124211Sdes{ 547126277Sdes debug3("PAM: %s entering, %d messages", __func__, n); 548124211Sdes return (PAM_CONV_ERR); 549124211Sdes} 55098941Sdes 551124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL }; 552124211Sdes 553147005Sdesstatic int 554149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg, 555147005Sdes struct pam_response **resp, void *data) 556147005Sdes{ 557147005Sdes struct pam_response *reply; 558147005Sdes int i; 559147005Sdes size_t len; 560147005Sdes 561147005Sdes debug3("PAM: %s called with %d messages", __func__, n); 562147005Sdes *resp = NULL; 563147005Sdes 564147005Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 565147005Sdes return (PAM_CONV_ERR); 566147005Sdes 567162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 568147005Sdes return (PAM_CONV_ERR); 569147005Sdes 570147005Sdes for (i = 0; i < n; ++i) { 571147005Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 572147005Sdes case PAM_ERROR_MSG: 573147005Sdes case PAM_TEXT_INFO: 574147005Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 575147005Sdes buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len); 576147005Sdes buffer_append(&loginmsg, "\n", 1 ); 577147005Sdes reply[i].resp_retcode = PAM_SUCCESS; 578147005Sdes break; 579147005Sdes default: 580147005Sdes goto fail; 581147005Sdes } 582147005Sdes } 583147005Sdes *resp = reply; 584147005Sdes return (PAM_SUCCESS); 585147005Sdes 586147005Sdes fail: 587147005Sdes for(i = 0; i < n; i++) { 588255767Sdes free(reply[i].resp); 589147005Sdes } 590255767Sdes free(reply); 591147005Sdes return (PAM_CONV_ERR); 592147005Sdes} 593147005Sdes 594147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL }; 595147005Sdes 596126277Sdesvoid 597126277Sdessshpam_cleanup(void) 598124211Sdes{ 599181111Sdes if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor())) 600181111Sdes return; 601124211Sdes debug("PAM: cleanup"); 602124211Sdes pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv); 603197679Sdes if (sshpam_session_open) { 604197679Sdes debug("PAM: closing session"); 605197679Sdes pam_close_session(sshpam_handle, PAM_SILENT); 606197679Sdes sshpam_session_open = 0; 607197679Sdes } 608124211Sdes if (sshpam_cred_established) { 609181111Sdes debug("PAM: deleting credentials"); 610124211Sdes pam_setcred(sshpam_handle, PAM_DELETE_CRED); 611124211Sdes sshpam_cred_established = 0; 61269591Sgreen } 613126277Sdes sshpam_authenticated = 0; 614124211Sdes pam_end(sshpam_handle, sshpam_err); 615124211Sdes sshpam_handle = NULL; 61669591Sgreen} 61769591Sgreen 618124211Sdesstatic int 619128460Sdessshpam_init(Authctxt *authctxt) 62069591Sgreen{ 621124211Sdes extern char *__progname; 622128460Sdes const char *pam_rhost, *pam_user, *user = authctxt->user; 623149753Sdes const char **ptr_pam_user = &pam_user; 62476394Salfred 625124211Sdes if (sshpam_handle != NULL) { 626124211Sdes /* We already have a PAM context; check if the user matches */ 627124211Sdes sshpam_err = pam_get_item(sshpam_handle, 628149753Sdes PAM_USER, (sshpam_const void **)ptr_pam_user); 629124211Sdes if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) 630124211Sdes return (0); 631124211Sdes pam_end(sshpam_handle, sshpam_err); 632124211Sdes sshpam_handle = NULL; 63369591Sgreen } 634124211Sdes debug("PAM: initializing for \"%s\"", user); 635124211Sdes sshpam_err = 636147005Sdes pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle); 637128460Sdes sshpam_authctxt = authctxt; 638128460Sdes 639124211Sdes if (sshpam_err != PAM_SUCCESS) { 640124211Sdes pam_end(sshpam_handle, sshpam_err); 641124211Sdes sshpam_handle = NULL; 642124211Sdes return (-1); 643124211Sdes } 644124211Sdes pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns); 645124211Sdes debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost); 646124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost); 647124211Sdes if (sshpam_err != PAM_SUCCESS) { 648124211Sdes pam_end(sshpam_handle, sshpam_err); 649124211Sdes sshpam_handle = NULL; 650124211Sdes return (-1); 651124211Sdes } 652124211Sdes#ifdef PAM_TTY_KLUDGE 653126277Sdes /* 654126277Sdes * Some silly PAM modules (e.g. pam_time) require a TTY to operate. 655126277Sdes * sshd doesn't set the tty until too late in the auth process and 656124211Sdes * may not even set one (for tty-less connections) 657126277Sdes */ 658124211Sdes debug("PAM: setting PAM_TTY to \"ssh\""); 659124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh"); 660124211Sdes if (sshpam_err != PAM_SUCCESS) { 661124211Sdes pam_end(sshpam_handle, sshpam_err); 662124211Sdes sshpam_handle = NULL; 663124211Sdes return (-1); 664124211Sdes } 66598941Sdes#endif 666124211Sdes return (0); 66769591Sgreen} 66869591Sgreen 669124211Sdesstatic void * 670124211Sdessshpam_init_ctx(Authctxt *authctxt) 67169591Sgreen{ 672124211Sdes struct pam_ctxt *ctxt; 673124211Sdes int socks[2]; 67469591Sgreen 675126277Sdes debug3("PAM: %s entering", __func__); 676162856Sdes /* 677162856Sdes * Refuse to start if we don't have PAM enabled or do_pam_account 678162856Sdes * has previously failed. 679162856Sdes */ 680162856Sdes if (!options.use_pam || sshpam_account_status == 0) 681124211Sdes return NULL; 68276394Salfred 683124211Sdes /* Initialize PAM */ 684128460Sdes if (sshpam_init(authctxt) == -1) { 685124211Sdes error("PAM: initialization failed"); 686124211Sdes return (NULL); 68769591Sgreen } 68869591Sgreen 689181111Sdes ctxt = xcalloc(1, sizeof *ctxt); 69076394Salfred 691124211Sdes /* Start the authentication thread */ 692124211Sdes if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) { 693124211Sdes error("PAM: failed create sockets: %s", strerror(errno)); 694255767Sdes free(ctxt); 695124211Sdes return (NULL); 696124211Sdes } 697124211Sdes ctxt->pam_psock = socks[0]; 698124211Sdes ctxt->pam_csock = socks[1]; 699124211Sdes if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) { 700124211Sdes error("PAM: failed to start authentication thread: %s", 701124211Sdes strerror(errno)); 702124211Sdes close(socks[0]); 703124211Sdes close(socks[1]); 704255767Sdes free(ctxt); 705124211Sdes return (NULL); 706124211Sdes } 707126277Sdes cleanup_ctxt = ctxt; 708124211Sdes return (ctxt); 70969591Sgreen} 71069591Sgreen 711124211Sdesstatic int 712124211Sdessshpam_query(void *ctx, char **name, char **info, 713124211Sdes u_int *num, char ***prompts, u_int **echo_on) 71469591Sgreen{ 715124211Sdes Buffer buffer; 716124211Sdes struct pam_ctxt *ctxt = ctx; 717124211Sdes size_t plen; 718124211Sdes u_char type; 719124211Sdes char *msg; 720147005Sdes size_t len, mlen; 72176394Salfred 722126277Sdes debug3("PAM: %s entering", __func__); 723124211Sdes buffer_init(&buffer); 724124211Sdes *name = xstrdup(""); 725124211Sdes *info = xstrdup(""); 726124211Sdes *prompts = xmalloc(sizeof(char *)); 727124211Sdes **prompts = NULL; 728124211Sdes plen = 0; 729124211Sdes *echo_on = xmalloc(sizeof(u_int)); 730124211Sdes while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) { 731124211Sdes type = buffer_get_char(&buffer); 732124211Sdes msg = buffer_get_string(&buffer, NULL); 733147005Sdes mlen = strlen(msg); 734124211Sdes switch (type) { 735124211Sdes case PAM_PROMPT_ECHO_ON: 736124211Sdes case PAM_PROMPT_ECHO_OFF: 737124211Sdes *num = 1; 738147005Sdes len = plen + mlen + 1; 739162856Sdes **prompts = xrealloc(**prompts, 1, len); 740147005Sdes strlcpy(**prompts + plen, msg, len - plen); 741147005Sdes plen += mlen; 742124211Sdes **echo_on = (type == PAM_PROMPT_ECHO_ON); 743255767Sdes free(msg); 744124211Sdes return (0); 745124211Sdes case PAM_ERROR_MSG: 746124211Sdes case PAM_TEXT_INFO: 747124211Sdes /* accumulate messages */ 748147005Sdes len = plen + mlen + 2; 749162856Sdes **prompts = xrealloc(**prompts, 1, len); 750147005Sdes strlcpy(**prompts + plen, msg, len - plen); 751147005Sdes plen += mlen; 752147005Sdes strlcat(**prompts + plen, "\n", len - plen); 753147005Sdes plen++; 754255767Sdes free(msg); 755124211Sdes break; 756162856Sdes case PAM_ACCT_EXPIRED: 757162856Sdes sshpam_account_status = 0; 758162856Sdes /* FALLTHROUGH */ 759157019Sdes case PAM_AUTH_ERR: 760162856Sdes debug3("PAM: %s", pam_strerror(sshpam_handle, type)); 761157019Sdes if (**prompts != NULL && strlen(**prompts) != 0) { 762157019Sdes *info = **prompts; 763157019Sdes **prompts = NULL; 764157019Sdes *num = 0; 765157019Sdes **echo_on = 0; 766157019Sdes ctxt->pam_done = -1; 767255767Sdes free(msg); 768157019Sdes return 0; 769157019Sdes } 770157019Sdes /* FALLTHROUGH */ 771124211Sdes case PAM_SUCCESS: 772124211Sdes if (**prompts != NULL) { 773124211Sdes /* drain any accumulated messages */ 774126277Sdes debug("PAM: %s", **prompts); 775126277Sdes buffer_append(&loginmsg, **prompts, 776126277Sdes strlen(**prompts)); 777255767Sdes free(**prompts); 778124211Sdes **prompts = NULL; 779124211Sdes } 780124211Sdes if (type == PAM_SUCCESS) { 781147005Sdes if (!sshpam_authctxt->valid || 782147005Sdes (sshpam_authctxt->pw->pw_uid == 0 && 783147005Sdes options.permit_root_login != PERMIT_YES)) 784147005Sdes fatal("Internal error: PAM auth " 785147005Sdes "succeeded when it should have " 786147005Sdes "failed"); 787126277Sdes import_environments(&buffer); 788124211Sdes *num = 0; 789124211Sdes **echo_on = 0; 790124211Sdes ctxt->pam_done = 1; 791255767Sdes free(msg); 792124211Sdes return (0); 793124211Sdes } 794128460Sdes error("PAM: %s for %s%.100s from %.100s", msg, 795128460Sdes sshpam_authctxt->valid ? "" : "illegal user ", 796128460Sdes sshpam_authctxt->user, 797128460Sdes get_remote_name_or_ip(utmp_len, options.use_dns)); 798126277Sdes /* FALLTHROUGH */ 799124211Sdes default: 800124211Sdes *num = 0; 801124211Sdes **echo_on = 0; 802255767Sdes free(msg); 803124211Sdes ctxt->pam_done = -1; 804124211Sdes return (-1); 805124211Sdes } 806124211Sdes } 807124211Sdes return (-1); 808124211Sdes} 80998941Sdes 810124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */ 811124211Sdesstatic int 812124211Sdessshpam_respond(void *ctx, u_int num, char **resp) 813124211Sdes{ 814124211Sdes Buffer buffer; 815124211Sdes struct pam_ctxt *ctxt = ctx; 81698941Sdes 817157019Sdes debug2("PAM: %s entering, %u responses", __func__, num); 818124211Sdes switch (ctxt->pam_done) { 819124211Sdes case 1: 820124211Sdes sshpam_authenticated = 1; 821124211Sdes return (0); 822124211Sdes case 0: 823124211Sdes break; 824124211Sdes default: 825124211Sdes return (-1); 826124211Sdes } 827124211Sdes if (num != 1) { 828124211Sdes error("PAM: expected one response, got %u", num); 829124211Sdes return (-1); 830124211Sdes } 831124211Sdes buffer_init(&buffer); 832147005Sdes if (sshpam_authctxt->valid && 833147005Sdes (sshpam_authctxt->pw->pw_uid != 0 || 834149753Sdes options.permit_root_login == PERMIT_YES)) 835147005Sdes buffer_put_cstring(&buffer, *resp); 836147005Sdes else 837147005Sdes buffer_put_cstring(&buffer, badpw); 838126277Sdes if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) { 839126277Sdes buffer_free(&buffer); 840126277Sdes return (-1); 841126277Sdes } 842124211Sdes buffer_free(&buffer); 843124211Sdes return (1); 84469591Sgreen} 84569591Sgreen 846124211Sdesstatic void 847124211Sdessshpam_free_ctx(void *ctxtp) 84869591Sgreen{ 849124211Sdes struct pam_ctxt *ctxt = ctxtp; 850124211Sdes 851126277Sdes debug3("PAM: %s entering", __func__); 852126277Sdes sshpam_thread_cleanup(); 853255767Sdes free(ctxt); 854124211Sdes /* 855124211Sdes * We don't call sshpam_cleanup() here because we may need the PAM 856124211Sdes * handle at a later stage, e.g. when setting up a session. It's 857124211Sdes * still on the cleanup list, so pam_end() *will* be called before 858124211Sdes * the server process terminates. 859124211Sdes */ 86069591Sgreen} 86169591Sgreen 862124211SdesKbdintDevice sshpam_device = { 863124211Sdes "pam", 864124211Sdes sshpam_init_ctx, 865124211Sdes sshpam_query, 866124211Sdes sshpam_respond, 867124211Sdes sshpam_free_ctx 868124211Sdes}; 869124211Sdes 870124211SdesKbdintDevice mm_sshpam_device = { 871124211Sdes "pam", 872124211Sdes mm_sshpam_init_ctx, 873124211Sdes mm_sshpam_query, 874124211Sdes mm_sshpam_respond, 875124211Sdes mm_sshpam_free_ctx 876124211Sdes}; 877124211Sdes 87898941Sdes/* 879124211Sdes * This replaces auth-pam.c 88069591Sgreen */ 881124211Sdesvoid 882128460Sdesstart_pam(Authctxt *authctxt) 88369591Sgreen{ 884124211Sdes if (!options.use_pam) 885124211Sdes fatal("PAM: initialisation requested when UsePAM=no"); 88669591Sgreen 887128460Sdes if (sshpam_init(authctxt) == -1) 888124211Sdes fatal("PAM: initialisation failed"); 88969591Sgreen} 89069591Sgreen 891124211Sdesvoid 892124211Sdesfinish_pam(void) 89369591Sgreen{ 894126277Sdes sshpam_cleanup(); 89569591Sgreen} 89669591Sgreen 897124211Sdesu_int 898124211Sdesdo_pam_account(void) 89969591Sgreen{ 900147005Sdes debug("%s: called", __func__); 901126277Sdes if (sshpam_account_status != -1) 902126277Sdes return (sshpam_account_status); 903126277Sdes 904124211Sdes sshpam_err = pam_acct_mgmt(sshpam_handle, 0); 905147005Sdes debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, 906147005Sdes pam_strerror(sshpam_handle, sshpam_err)); 907149753Sdes 908126277Sdes if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) { 909126277Sdes sshpam_account_status = 0; 910126277Sdes return (sshpam_account_status); 911124211Sdes } 91269591Sgreen 913126277Sdes if (sshpam_err == PAM_NEW_AUTHTOK_REQD) 914137019Sdes sshpam_password_change_required(1); 91569591Sgreen 916126277Sdes sshpam_account_status = 1; 917126277Sdes return (sshpam_account_status); 918124211Sdes} 91998941Sdes 920124211Sdesvoid 921124211Sdesdo_pam_set_tty(const char *tty) 922124211Sdes{ 923124211Sdes if (tty != NULL) { 924124211Sdes debug("PAM: setting PAM_TTY to \"%s\"", tty); 925124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty); 926124211Sdes if (sshpam_err != PAM_SUCCESS) 927124211Sdes fatal("PAM: failed to set PAM_TTY: %s", 928124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 929124211Sdes } 930124211Sdes} 93169591Sgreen 932124211Sdesvoid 933124211Sdesdo_pam_setcred(int init) 934124211Sdes{ 935124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 936147005Sdes (const void *)&store_conv); 937124211Sdes if (sshpam_err != PAM_SUCCESS) 938124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 939124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 940124211Sdes if (init) { 941124211Sdes debug("PAM: establishing credentials"); 942124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED); 943124211Sdes } else { 944124211Sdes debug("PAM: reinitializing credentials"); 945124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED); 946124211Sdes } 947124211Sdes if (sshpam_err == PAM_SUCCESS) { 948124211Sdes sshpam_cred_established = 1; 949124211Sdes return; 950124211Sdes } 951124211Sdes if (sshpam_authenticated) 952124211Sdes fatal("PAM: pam_setcred(): %s", 953124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 954124211Sdes else 955124211Sdes debug("PAM: pam_setcred(): %s", 956124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 95769591Sgreen} 95869591Sgreen 959124211Sdesstatic int 960149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg, 961124211Sdes struct pam_response **resp, void *data) 962106130Sdes{ 963124211Sdes char input[PAM_MAX_MSG_SIZE]; 964124211Sdes struct pam_response *reply; 965106130Sdes int i; 966106130Sdes 967126277Sdes debug3("PAM: %s called with %d messages", __func__, n); 968126277Sdes 969124211Sdes *resp = NULL; 970124211Sdes 971126277Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO)) 972124211Sdes return (PAM_CONV_ERR); 973124211Sdes 974162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 975124211Sdes return (PAM_CONV_ERR); 976124211Sdes 977124211Sdes for (i = 0; i < n; ++i) { 978124211Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 979124211Sdes case PAM_PROMPT_ECHO_OFF: 980124211Sdes reply[i].resp = 981126277Sdes read_passphrase(PAM_MSG_MEMBER(msg, i, msg), 982124211Sdes RP_ALLOW_STDIN); 983124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 984124211Sdes break; 985124211Sdes case PAM_PROMPT_ECHO_ON: 986126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 987181111Sdes if (fgets(input, sizeof input, stdin) == NULL) 988181111Sdes input[0] = '\0'; 989137019Sdes if ((reply[i].resp = strdup(input)) == NULL) 990137019Sdes goto fail; 991124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 992124211Sdes break; 993124211Sdes case PAM_ERROR_MSG: 994124211Sdes case PAM_TEXT_INFO: 995126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 996124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 997124211Sdes break; 998124211Sdes default: 999124211Sdes goto fail; 1000124211Sdes } 1001106130Sdes } 1002124211Sdes *resp = reply; 1003124211Sdes return (PAM_SUCCESS); 1004124211Sdes 1005124211Sdes fail: 1006124211Sdes for(i = 0; i < n; i++) { 1007255767Sdes free(reply[i].resp); 1008124211Sdes } 1009255767Sdes free(reply); 1010124211Sdes return (PAM_CONV_ERR); 1011106130Sdes} 1012106130Sdes 1013137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; 1014126277Sdes 1015124211Sdes/* 1016124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't 1017124211Sdes * support that 1018124211Sdes */ 1019124211Sdesvoid 1020124211Sdesdo_pam_chauthtok(void) 102169591Sgreen{ 1022124211Sdes if (use_privsep) 1023124211Sdes fatal("Password expired (unable to change with privsep)"); 1024124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1025126277Sdes (const void *)&tty_conv); 1026124211Sdes if (sshpam_err != PAM_SUCCESS) 1027124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 1028124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 1029124211Sdes debug("PAM: changing password"); 1030124211Sdes sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); 1031124211Sdes if (sshpam_err != PAM_SUCCESS) 1032124211Sdes fatal("PAM: pam_chauthtok(): %s", 1033124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 103469591Sgreen} 103569591Sgreen 1036126277Sdesvoid 1037126277Sdesdo_pam_session(void) 1038126277Sdes{ 1039126277Sdes debug3("PAM: opening session"); 1040126277Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1041126277Sdes (const void *)&store_conv); 1042126277Sdes if (sshpam_err != PAM_SUCCESS) 1043126277Sdes fatal("PAM: failed to set PAM_CONV: %s", 1044126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1045126277Sdes sshpam_err = pam_open_session(sshpam_handle, 0); 1046147005Sdes if (sshpam_err == PAM_SUCCESS) 1047147005Sdes sshpam_session_open = 1; 1048147005Sdes else { 1049147005Sdes sshpam_session_open = 0; 1050147005Sdes disable_forwarding(); 1051147005Sdes error("PAM: pam_open_session(): %s", 1052126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1053147005Sdes } 1054147005Sdes 1055126277Sdes} 1056126277Sdes 1057147005Sdesint 1058147005Sdesis_pam_session_open(void) 1059147005Sdes{ 1060147005Sdes return sshpam_session_open; 1061147005Sdes} 1062147005Sdes 1063126277Sdes/* 1064124211Sdes * Set a PAM environment string. We need to do this so that the session 1065124211Sdes * modules can handle things like Kerberos/GSI credentials that appear 1066124211Sdes * during the ssh authentication process. 1067124211Sdes */ 1068124211Sdesint 1069126277Sdesdo_pam_putenv(char *name, char *value) 107069591Sgreen{ 1071124211Sdes int ret = 1; 1072126277Sdes#ifdef HAVE_PAM_PUTENV 1073124211Sdes char *compound; 1074124211Sdes size_t len; 107569591Sgreen 1076124211Sdes len = strlen(name) + strlen(value) + 2; 1077124211Sdes compound = xmalloc(len); 107869591Sgreen 1079124211Sdes snprintf(compound, len, "%s=%s", name, value); 1080124211Sdes ret = pam_putenv(sshpam_handle, compound); 1081255767Sdes free(compound); 1082124211Sdes#endif 108369591Sgreen 1084124211Sdes return (ret); 1085124211Sdes} 108669591Sgreen 1087126277Sdeschar ** 1088126277Sdesfetch_pam_child_environment(void) 1089124211Sdes{ 1090126277Sdes return sshpam_env; 109169591Sgreen} 109269591Sgreen 1093124211Sdeschar ** 1094124211Sdesfetch_pam_environment(void) 1095124211Sdes{ 1096124211Sdes return (pam_getenvlist(sshpam_handle)); 1097124211Sdes} 1098124211Sdes 1099124211Sdesvoid 1100124211Sdesfree_pam_environment(char **env) 1101124211Sdes{ 1102124211Sdes char **envp; 1103124211Sdes 1104124211Sdes if (env == NULL) 1105124211Sdes return; 1106124211Sdes 1107124211Sdes for (envp = env; *envp; envp++) 1108255767Sdes free(*envp); 1109255767Sdes free(env); 1110124211Sdes} 1111124211Sdes 1112137019Sdes/* 1113137019Sdes * "Blind" conversation function for password authentication. Assumes that 1114137019Sdes * echo-off prompts are for the password and stores messages for later 1115137019Sdes * display. 1116137019Sdes */ 1117137019Sdesstatic int 1118149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, 1119137019Sdes struct pam_response **resp, void *data) 1120137019Sdes{ 1121137019Sdes struct pam_response *reply; 1122137019Sdes int i; 1123137019Sdes size_t len; 1124137019Sdes 1125137019Sdes debug3("PAM: %s called with %d messages", __func__, n); 1126137019Sdes 1127137019Sdes *resp = NULL; 1128137019Sdes 1129137019Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 1130137019Sdes return (PAM_CONV_ERR); 1131137019Sdes 1132181111Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 1133137019Sdes return (PAM_CONV_ERR); 1134137019Sdes 1135137019Sdes for (i = 0; i < n; ++i) { 1136137019Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 1137137019Sdes case PAM_PROMPT_ECHO_OFF: 1138137019Sdes if (sshpam_password == NULL) 1139137019Sdes goto fail; 1140137019Sdes if ((reply[i].resp = strdup(sshpam_password)) == NULL) 1141137019Sdes goto fail; 1142137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1143137019Sdes break; 1144137019Sdes case PAM_ERROR_MSG: 1145137019Sdes case PAM_TEXT_INFO: 1146137019Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 1147137019Sdes if (len > 0) { 1148137019Sdes buffer_append(&loginmsg, 1149137019Sdes PAM_MSG_MEMBER(msg, i, msg), len); 1150137019Sdes buffer_append(&loginmsg, "\n", 1); 1151137019Sdes } 1152137019Sdes if ((reply[i].resp = strdup("")) == NULL) 1153137019Sdes goto fail; 1154137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1155137019Sdes break; 1156137019Sdes default: 1157137019Sdes goto fail; 1158137019Sdes } 1159137019Sdes } 1160137019Sdes *resp = reply; 1161137019Sdes return (PAM_SUCCESS); 1162137019Sdes 1163149753Sdes fail: 1164137019Sdes for(i = 0; i < n; i++) { 1165255767Sdes free(reply[i].resp); 1166137019Sdes } 1167255767Sdes free(reply); 1168137019Sdes return (PAM_CONV_ERR); 1169137019Sdes} 1170137019Sdes 1171137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; 1172137019Sdes 1173137019Sdes/* 1174137019Sdes * Attempt password authentication via PAM 1175137019Sdes */ 1176137019Sdesint 1177137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password) 1178137019Sdes{ 1179137019Sdes int flags = (options.permit_empty_passwd == 0 ? 1180137019Sdes PAM_DISALLOW_NULL_AUTHTOK : 0); 1181137019Sdes 1182137019Sdes if (!options.use_pam || sshpam_handle == NULL) 1183137019Sdes fatal("PAM: %s called when PAM disabled or failed to " 1184137019Sdes "initialise.", __func__); 1185137019Sdes 1186137019Sdes sshpam_password = password; 1187137019Sdes sshpam_authctxt = authctxt; 1188137019Sdes 1189137019Sdes /* 1190137019Sdes * If the user logging in is invalid, or is root but is not permitted 1191137019Sdes * by PermitRootLogin, use an invalid password to prevent leaking 1192137019Sdes * information via timing (eg if the PAM config has a delay on fail). 1193137019Sdes */ 1194137019Sdes if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && 1195149753Sdes options.permit_root_login != PERMIT_YES)) 1196137019Sdes sshpam_password = badpw; 1197137019Sdes 1198137019Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1199137019Sdes (const void *)&passwd_conv); 1200137019Sdes if (sshpam_err != PAM_SUCCESS) 1201137019Sdes fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, 1202137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1203137019Sdes 1204137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 1205137019Sdes sshpam_password = NULL; 1206137019Sdes if (sshpam_err == PAM_SUCCESS && authctxt->valid) { 1207137019Sdes debug("PAM: password authentication accepted for %.100s", 1208137019Sdes authctxt->user); 1209149753Sdes return 1; 1210137019Sdes } else { 1211137019Sdes debug("PAM: password authentication failed for %.100s: %s", 1212137019Sdes authctxt->valid ? authctxt->user : "an illegal user", 1213137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1214137019Sdes return 0; 1215137019Sdes } 1216137019Sdes} 121769591Sgreen#endif /* USE_PAM */ 1218