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++) { 415263970Sdes free(reply[i].resp); 416124211Sdes } 417263970Sdes 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 441263970Sdes sshpam_err = pam_get_item(sshpam_handle, PAM_USER, 442149753Sdes (sshpam_const void **)ptr_pam_user); 443263970Sdes if (sshpam_err != PAM_SUCCESS) 444263970Sdes goto auth_fail; 445162856Sdes 446126277Sdes environ[0] = NULL; 447162856Sdes if (tz != NULL) 448162856Sdes if (setenv("TZ", tz, 1) == -1) 449162856Sdes error("PAM: could not set TZ environment: %s", 450162856Sdes strerror(errno)); 451137019Sdes 452137019Sdes if (sshpam_authctxt != NULL) { 453137019Sdes setproctitle("%s [pam]", 454137019Sdes sshpam_authctxt->valid ? pam_user : "unknown"); 455137019Sdes } 456124211Sdes#endif 45769591Sgreen 458124211Sdes sshpam_conv.conv = sshpam_thread_conv; 459124211Sdes sshpam_conv.appdata_ptr = ctxt; 46069591Sgreen 461128460Sdes if (sshpam_authctxt == NULL) 462128460Sdes fatal("%s: PAM authctxt not initialized", __func__); 463128460Sdes 464124211Sdes buffer_init(&buffer); 465124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 466124211Sdes (const void *)&sshpam_conv); 467124211Sdes if (sshpam_err != PAM_SUCCESS) 468124211Sdes goto auth_fail; 469137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 470124211Sdes if (sshpam_err != PAM_SUCCESS) 471124211Sdes goto auth_fail; 472126277Sdes 473126277Sdes if (compat20) { 474162856Sdes if (!do_pam_account()) { 475162856Sdes sshpam_err = PAM_ACCT_EXPIRED; 476126277Sdes goto auth_fail; 477162856Sdes } 478128460Sdes if (sshpam_authctxt->force_pwchange) { 479126277Sdes sshpam_err = pam_chauthtok(sshpam_handle, 480126277Sdes PAM_CHANGE_EXPIRED_AUTHTOK); 481126277Sdes if (sshpam_err != PAM_SUCCESS) 482126277Sdes goto auth_fail; 483137019Sdes sshpam_password_change_required(0); 484126277Sdes } 485126277Sdes } 486126277Sdes 487124211Sdes buffer_put_cstring(&buffer, "OK"); 488126277Sdes 489147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK 490126277Sdes /* Export variables set by do_pam_account */ 491126277Sdes buffer_put_int(&buffer, sshpam_account_status); 492128460Sdes buffer_put_int(&buffer, sshpam_authctxt->force_pwchange); 493126277Sdes 494126277Sdes /* Export any environment strings set in child */ 495126277Sdes for(i = 0; environ[i] != NULL; i++) 496126277Sdes ; /* Count */ 497126277Sdes buffer_put_int(&buffer, i); 498126277Sdes for(i = 0; environ[i] != NULL; i++) 499126277Sdes buffer_put_cstring(&buffer, environ[i]); 500126277Sdes 501126277Sdes /* Export any environment strings set by PAM in child */ 502126277Sdes env_from_pam = pam_getenvlist(sshpam_handle); 503126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 504126277Sdes ; /* Count */ 505126277Sdes buffer_put_int(&buffer, i); 506126277Sdes for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) 507126277Sdes buffer_put_cstring(&buffer, env_from_pam[i]); 508147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */ 509126277Sdes 510126277Sdes /* XXX - can't do much about an error here */ 511124211Sdes ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer); 512124211Sdes buffer_free(&buffer); 513124211Sdes pthread_exit(NULL); 514124211Sdes 515124211Sdes auth_fail: 516124211Sdes buffer_put_cstring(&buffer, 517124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 518126277Sdes /* XXX - can't do much about an error here */ 519162856Sdes if (sshpam_err == PAM_ACCT_EXPIRED) 520162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer); 521162856Sdes else 522162856Sdes ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer); 523124211Sdes buffer_free(&buffer); 524124211Sdes pthread_exit(NULL); 525126277Sdes 526124211Sdes return (NULL); /* Avoid warning for non-pthread case */ 52769591Sgreen} 52869591Sgreen 529126277Sdesvoid 530126277Sdessshpam_thread_cleanup(void) 53169591Sgreen{ 532126277Sdes struct pam_ctxt *ctxt = cleanup_ctxt; 53369591Sgreen 534126277Sdes debug3("PAM: %s entering", __func__); 535126277Sdes if (ctxt != NULL && ctxt->pam_thread != 0) { 536126277Sdes pthread_cancel(ctxt->pam_thread); 537126277Sdes pthread_join(ctxt->pam_thread, NULL); 538126277Sdes close(ctxt->pam_psock); 539126277Sdes close(ctxt->pam_csock); 540126277Sdes memset(ctxt, 0, sizeof(*ctxt)); 541126277Sdes cleanup_ctxt = NULL; 542126277Sdes } 543124211Sdes} 54476394Salfred 545124211Sdesstatic int 546149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg, 547124211Sdes struct pam_response **resp, void *data) 548124211Sdes{ 549126277Sdes debug3("PAM: %s entering, %d messages", __func__, n); 550124211Sdes return (PAM_CONV_ERR); 551124211Sdes} 55298941Sdes 553124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL }; 554124211Sdes 555147005Sdesstatic int 556149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg, 557147005Sdes struct pam_response **resp, void *data) 558147005Sdes{ 559147005Sdes struct pam_response *reply; 560147005Sdes int i; 561147005Sdes size_t len; 562147005Sdes 563147005Sdes debug3("PAM: %s called with %d messages", __func__, n); 564147005Sdes *resp = NULL; 565147005Sdes 566147005Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 567147005Sdes return (PAM_CONV_ERR); 568147005Sdes 569162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 570147005Sdes return (PAM_CONV_ERR); 571147005Sdes 572147005Sdes for (i = 0; i < n; ++i) { 573147005Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 574147005Sdes case PAM_ERROR_MSG: 575147005Sdes case PAM_TEXT_INFO: 576147005Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 577147005Sdes buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len); 578147005Sdes buffer_append(&loginmsg, "\n", 1 ); 579147005Sdes reply[i].resp_retcode = PAM_SUCCESS; 580147005Sdes break; 581147005Sdes default: 582147005Sdes goto fail; 583147005Sdes } 584147005Sdes } 585147005Sdes *resp = reply; 586147005Sdes return (PAM_SUCCESS); 587147005Sdes 588147005Sdes fail: 589147005Sdes for(i = 0; i < n; i++) { 590263970Sdes free(reply[i].resp); 591147005Sdes } 592263970Sdes free(reply); 593147005Sdes return (PAM_CONV_ERR); 594147005Sdes} 595147005Sdes 596147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL }; 597147005Sdes 598126277Sdesvoid 599126277Sdessshpam_cleanup(void) 600124211Sdes{ 601181111Sdes if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor())) 602181111Sdes return; 603124211Sdes debug("PAM: cleanup"); 604124211Sdes pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv); 605197679Sdes if (sshpam_session_open) { 606197679Sdes debug("PAM: closing session"); 607197679Sdes pam_close_session(sshpam_handle, PAM_SILENT); 608197679Sdes sshpam_session_open = 0; 609197679Sdes } 610124211Sdes if (sshpam_cred_established) { 611181111Sdes debug("PAM: deleting credentials"); 612124211Sdes pam_setcred(sshpam_handle, PAM_DELETE_CRED); 613124211Sdes sshpam_cred_established = 0; 61469591Sgreen } 615126277Sdes sshpam_authenticated = 0; 616124211Sdes pam_end(sshpam_handle, sshpam_err); 617124211Sdes sshpam_handle = NULL; 61869591Sgreen} 61969591Sgreen 620124211Sdesstatic int 621128460Sdessshpam_init(Authctxt *authctxt) 62269591Sgreen{ 623124211Sdes extern char *__progname; 624128460Sdes const char *pam_rhost, *pam_user, *user = authctxt->user; 625149753Sdes const char **ptr_pam_user = &pam_user; 62676394Salfred 627124211Sdes if (sshpam_handle != NULL) { 628124211Sdes /* We already have a PAM context; check if the user matches */ 629124211Sdes sshpam_err = pam_get_item(sshpam_handle, 630149753Sdes PAM_USER, (sshpam_const void **)ptr_pam_user); 631124211Sdes if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0) 632124211Sdes return (0); 633124211Sdes pam_end(sshpam_handle, sshpam_err); 634124211Sdes sshpam_handle = NULL; 63569591Sgreen } 636124211Sdes debug("PAM: initializing for \"%s\"", user); 637124211Sdes sshpam_err = 638147005Sdes pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle); 639128460Sdes sshpam_authctxt = authctxt; 640128460Sdes 641124211Sdes if (sshpam_err != PAM_SUCCESS) { 642124211Sdes pam_end(sshpam_handle, sshpam_err); 643124211Sdes sshpam_handle = NULL; 644124211Sdes return (-1); 645124211Sdes } 646124211Sdes pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns); 647124211Sdes debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost); 648124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost); 649124211Sdes if (sshpam_err != PAM_SUCCESS) { 650124211Sdes pam_end(sshpam_handle, sshpam_err); 651124211Sdes sshpam_handle = NULL; 652124211Sdes return (-1); 653124211Sdes } 654124211Sdes#ifdef PAM_TTY_KLUDGE 655126277Sdes /* 656126277Sdes * Some silly PAM modules (e.g. pam_time) require a TTY to operate. 657126277Sdes * sshd doesn't set the tty until too late in the auth process and 658124211Sdes * may not even set one (for tty-less connections) 659126277Sdes */ 660124211Sdes debug("PAM: setting PAM_TTY to \"ssh\""); 661124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh"); 662124211Sdes if (sshpam_err != PAM_SUCCESS) { 663124211Sdes pam_end(sshpam_handle, sshpam_err); 664124211Sdes sshpam_handle = NULL; 665124211Sdes return (-1); 666124211Sdes } 66798941Sdes#endif 668124211Sdes return (0); 66969591Sgreen} 67069591Sgreen 671124211Sdesstatic void * 672124211Sdessshpam_init_ctx(Authctxt *authctxt) 67369591Sgreen{ 674124211Sdes struct pam_ctxt *ctxt; 675124211Sdes int socks[2]; 67669591Sgreen 677126277Sdes debug3("PAM: %s entering", __func__); 678162856Sdes /* 679162856Sdes * Refuse to start if we don't have PAM enabled or do_pam_account 680162856Sdes * has previously failed. 681162856Sdes */ 682162856Sdes if (!options.use_pam || sshpam_account_status == 0) 683124211Sdes return NULL; 68476394Salfred 685124211Sdes /* Initialize PAM */ 686128460Sdes if (sshpam_init(authctxt) == -1) { 687124211Sdes error("PAM: initialization failed"); 688124211Sdes return (NULL); 68969591Sgreen } 69069591Sgreen 691181111Sdes ctxt = xcalloc(1, sizeof *ctxt); 69276394Salfred 693124211Sdes /* Start the authentication thread */ 694124211Sdes if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) { 695124211Sdes error("PAM: failed create sockets: %s", strerror(errno)); 696263970Sdes free(ctxt); 697124211Sdes return (NULL); 698124211Sdes } 699124211Sdes ctxt->pam_psock = socks[0]; 700124211Sdes ctxt->pam_csock = socks[1]; 701124211Sdes if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) { 702124211Sdes error("PAM: failed to start authentication thread: %s", 703124211Sdes strerror(errno)); 704124211Sdes close(socks[0]); 705124211Sdes close(socks[1]); 706263970Sdes free(ctxt); 707124211Sdes return (NULL); 708124211Sdes } 709126277Sdes cleanup_ctxt = ctxt; 710124211Sdes return (ctxt); 71169591Sgreen} 71269591Sgreen 713124211Sdesstatic int 714124211Sdessshpam_query(void *ctx, char **name, char **info, 715124211Sdes u_int *num, char ***prompts, u_int **echo_on) 71669591Sgreen{ 717124211Sdes Buffer buffer; 718124211Sdes struct pam_ctxt *ctxt = ctx; 719124211Sdes size_t plen; 720124211Sdes u_char type; 721124211Sdes char *msg; 722147005Sdes size_t len, mlen; 72376394Salfred 724126277Sdes debug3("PAM: %s entering", __func__); 725124211Sdes buffer_init(&buffer); 726124211Sdes *name = xstrdup(""); 727124211Sdes *info = xstrdup(""); 728124211Sdes *prompts = xmalloc(sizeof(char *)); 729124211Sdes **prompts = NULL; 730124211Sdes plen = 0; 731124211Sdes *echo_on = xmalloc(sizeof(u_int)); 732124211Sdes while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) { 733124211Sdes type = buffer_get_char(&buffer); 734124211Sdes msg = buffer_get_string(&buffer, NULL); 735147005Sdes mlen = strlen(msg); 736124211Sdes switch (type) { 737124211Sdes case PAM_PROMPT_ECHO_ON: 738124211Sdes case PAM_PROMPT_ECHO_OFF: 739124211Sdes *num = 1; 740147005Sdes len = plen + mlen + 1; 741162856Sdes **prompts = xrealloc(**prompts, 1, len); 742147005Sdes strlcpy(**prompts + plen, msg, len - plen); 743147005Sdes plen += mlen; 744124211Sdes **echo_on = (type == PAM_PROMPT_ECHO_ON); 745263970Sdes free(msg); 746124211Sdes return (0); 747124211Sdes case PAM_ERROR_MSG: 748124211Sdes case PAM_TEXT_INFO: 749124211Sdes /* accumulate messages */ 750147005Sdes len = plen + mlen + 2; 751162856Sdes **prompts = xrealloc(**prompts, 1, len); 752147005Sdes strlcpy(**prompts + plen, msg, len - plen); 753147005Sdes plen += mlen; 754147005Sdes strlcat(**prompts + plen, "\n", len - plen); 755147005Sdes plen++; 756263970Sdes free(msg); 757124211Sdes break; 758162856Sdes case PAM_ACCT_EXPIRED: 759162856Sdes sshpam_account_status = 0; 760162856Sdes /* FALLTHROUGH */ 761157019Sdes case PAM_AUTH_ERR: 762162856Sdes debug3("PAM: %s", pam_strerror(sshpam_handle, type)); 763157019Sdes if (**prompts != NULL && strlen(**prompts) != 0) { 764157019Sdes *info = **prompts; 765157019Sdes **prompts = NULL; 766157019Sdes *num = 0; 767157019Sdes **echo_on = 0; 768157019Sdes ctxt->pam_done = -1; 769263970Sdes free(msg); 770157019Sdes return 0; 771157019Sdes } 772157019Sdes /* FALLTHROUGH */ 773124211Sdes case PAM_SUCCESS: 774124211Sdes if (**prompts != NULL) { 775124211Sdes /* drain any accumulated messages */ 776126277Sdes debug("PAM: %s", **prompts); 777126277Sdes buffer_append(&loginmsg, **prompts, 778126277Sdes strlen(**prompts)); 779263970Sdes free(**prompts); 780124211Sdes **prompts = NULL; 781124211Sdes } 782124211Sdes if (type == PAM_SUCCESS) { 783147005Sdes if (!sshpam_authctxt->valid || 784147005Sdes (sshpam_authctxt->pw->pw_uid == 0 && 785147005Sdes options.permit_root_login != PERMIT_YES)) 786147005Sdes fatal("Internal error: PAM auth " 787147005Sdes "succeeded when it should have " 788147005Sdes "failed"); 789126277Sdes import_environments(&buffer); 790124211Sdes *num = 0; 791124211Sdes **echo_on = 0; 792124211Sdes ctxt->pam_done = 1; 793263970Sdes free(msg); 794124211Sdes return (0); 795124211Sdes } 796128460Sdes error("PAM: %s for %s%.100s from %.100s", msg, 797128460Sdes sshpam_authctxt->valid ? "" : "illegal user ", 798128460Sdes sshpam_authctxt->user, 799128460Sdes get_remote_name_or_ip(utmp_len, options.use_dns)); 800126277Sdes /* FALLTHROUGH */ 801124211Sdes default: 802124211Sdes *num = 0; 803124211Sdes **echo_on = 0; 804263970Sdes free(msg); 805124211Sdes ctxt->pam_done = -1; 806124211Sdes return (-1); 807124211Sdes } 808124211Sdes } 809124211Sdes return (-1); 810124211Sdes} 81198941Sdes 812124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */ 813124211Sdesstatic int 814124211Sdessshpam_respond(void *ctx, u_int num, char **resp) 815124211Sdes{ 816124211Sdes Buffer buffer; 817124211Sdes struct pam_ctxt *ctxt = ctx; 81898941Sdes 819157019Sdes debug2("PAM: %s entering, %u responses", __func__, num); 820124211Sdes switch (ctxt->pam_done) { 821124211Sdes case 1: 822124211Sdes sshpam_authenticated = 1; 823124211Sdes return (0); 824124211Sdes case 0: 825124211Sdes break; 826124211Sdes default: 827124211Sdes return (-1); 828124211Sdes } 829124211Sdes if (num != 1) { 830124211Sdes error("PAM: expected one response, got %u", num); 831124211Sdes return (-1); 832124211Sdes } 833124211Sdes buffer_init(&buffer); 834147005Sdes if (sshpam_authctxt->valid && 835147005Sdes (sshpam_authctxt->pw->pw_uid != 0 || 836149753Sdes options.permit_root_login == PERMIT_YES)) 837147005Sdes buffer_put_cstring(&buffer, *resp); 838147005Sdes else 839147005Sdes buffer_put_cstring(&buffer, badpw); 840126277Sdes if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) { 841126277Sdes buffer_free(&buffer); 842126277Sdes return (-1); 843126277Sdes } 844124211Sdes buffer_free(&buffer); 845124211Sdes return (1); 84669591Sgreen} 84769591Sgreen 848124211Sdesstatic void 849124211Sdessshpam_free_ctx(void *ctxtp) 85069591Sgreen{ 851124211Sdes struct pam_ctxt *ctxt = ctxtp; 852124211Sdes 853126277Sdes debug3("PAM: %s entering", __func__); 854126277Sdes sshpam_thread_cleanup(); 855263970Sdes free(ctxt); 856124211Sdes /* 857124211Sdes * We don't call sshpam_cleanup() here because we may need the PAM 858124211Sdes * handle at a later stage, e.g. when setting up a session. It's 859124211Sdes * still on the cleanup list, so pam_end() *will* be called before 860124211Sdes * the server process terminates. 861124211Sdes */ 86269591Sgreen} 86369591Sgreen 864124211SdesKbdintDevice sshpam_device = { 865124211Sdes "pam", 866124211Sdes sshpam_init_ctx, 867124211Sdes sshpam_query, 868124211Sdes sshpam_respond, 869124211Sdes sshpam_free_ctx 870124211Sdes}; 871124211Sdes 872124211SdesKbdintDevice mm_sshpam_device = { 873124211Sdes "pam", 874124211Sdes mm_sshpam_init_ctx, 875124211Sdes mm_sshpam_query, 876124211Sdes mm_sshpam_respond, 877124211Sdes mm_sshpam_free_ctx 878124211Sdes}; 879124211Sdes 88098941Sdes/* 881124211Sdes * This replaces auth-pam.c 88269591Sgreen */ 883124211Sdesvoid 884128460Sdesstart_pam(Authctxt *authctxt) 88569591Sgreen{ 886124211Sdes if (!options.use_pam) 887124211Sdes fatal("PAM: initialisation requested when UsePAM=no"); 88869591Sgreen 889128460Sdes if (sshpam_init(authctxt) == -1) 890124211Sdes fatal("PAM: initialisation failed"); 89169591Sgreen} 89269591Sgreen 893124211Sdesvoid 894124211Sdesfinish_pam(void) 89569591Sgreen{ 896126277Sdes sshpam_cleanup(); 89769591Sgreen} 89869591Sgreen 899124211Sdesu_int 900124211Sdesdo_pam_account(void) 90169591Sgreen{ 902147005Sdes debug("%s: called", __func__); 903126277Sdes if (sshpam_account_status != -1) 904126277Sdes return (sshpam_account_status); 905126277Sdes 906124211Sdes sshpam_err = pam_acct_mgmt(sshpam_handle, 0); 907147005Sdes debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, 908147005Sdes pam_strerror(sshpam_handle, sshpam_err)); 909149753Sdes 910126277Sdes if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) { 911126277Sdes sshpam_account_status = 0; 912126277Sdes return (sshpam_account_status); 913124211Sdes } 91469591Sgreen 915126277Sdes if (sshpam_err == PAM_NEW_AUTHTOK_REQD) 916137019Sdes sshpam_password_change_required(1); 91769591Sgreen 918126277Sdes sshpam_account_status = 1; 919126277Sdes return (sshpam_account_status); 920124211Sdes} 92198941Sdes 922124211Sdesvoid 923124211Sdesdo_pam_set_tty(const char *tty) 924124211Sdes{ 925124211Sdes if (tty != NULL) { 926124211Sdes debug("PAM: setting PAM_TTY to \"%s\"", tty); 927124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty); 928124211Sdes if (sshpam_err != PAM_SUCCESS) 929124211Sdes fatal("PAM: failed to set PAM_TTY: %s", 930124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 931124211Sdes } 932124211Sdes} 93369591Sgreen 934124211Sdesvoid 935124211Sdesdo_pam_setcred(int init) 936124211Sdes{ 937124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 938147005Sdes (const void *)&store_conv); 939124211Sdes if (sshpam_err != PAM_SUCCESS) 940124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 941124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 942124211Sdes if (init) { 943124211Sdes debug("PAM: establishing credentials"); 944124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED); 945124211Sdes } else { 946124211Sdes debug("PAM: reinitializing credentials"); 947124211Sdes sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED); 948124211Sdes } 949124211Sdes if (sshpam_err == PAM_SUCCESS) { 950124211Sdes sshpam_cred_established = 1; 951124211Sdes return; 952124211Sdes } 953124211Sdes if (sshpam_authenticated) 954124211Sdes fatal("PAM: pam_setcred(): %s", 955124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 956124211Sdes else 957124211Sdes debug("PAM: pam_setcred(): %s", 958124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 95969591Sgreen} 96069591Sgreen 961124211Sdesstatic int 962149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg, 963124211Sdes struct pam_response **resp, void *data) 964106130Sdes{ 965124211Sdes char input[PAM_MAX_MSG_SIZE]; 966124211Sdes struct pam_response *reply; 967106130Sdes int i; 968106130Sdes 969126277Sdes debug3("PAM: %s called with %d messages", __func__, n); 970126277Sdes 971124211Sdes *resp = NULL; 972124211Sdes 973126277Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO)) 974124211Sdes return (PAM_CONV_ERR); 975124211Sdes 976162856Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 977124211Sdes return (PAM_CONV_ERR); 978124211Sdes 979124211Sdes for (i = 0; i < n; ++i) { 980124211Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 981124211Sdes case PAM_PROMPT_ECHO_OFF: 982124211Sdes reply[i].resp = 983126277Sdes read_passphrase(PAM_MSG_MEMBER(msg, i, msg), 984124211Sdes RP_ALLOW_STDIN); 985124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 986124211Sdes break; 987124211Sdes case PAM_PROMPT_ECHO_ON: 988126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 989181111Sdes if (fgets(input, sizeof input, stdin) == NULL) 990181111Sdes input[0] = '\0'; 991137019Sdes if ((reply[i].resp = strdup(input)) == NULL) 992137019Sdes goto fail; 993124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 994124211Sdes break; 995124211Sdes case PAM_ERROR_MSG: 996124211Sdes case PAM_TEXT_INFO: 997126277Sdes fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg)); 998124211Sdes reply[i].resp_retcode = PAM_SUCCESS; 999124211Sdes break; 1000124211Sdes default: 1001124211Sdes goto fail; 1002124211Sdes } 1003106130Sdes } 1004124211Sdes *resp = reply; 1005124211Sdes return (PAM_SUCCESS); 1006124211Sdes 1007124211Sdes fail: 1008124211Sdes for(i = 0; i < n; i++) { 1009263970Sdes free(reply[i].resp); 1010124211Sdes } 1011263970Sdes free(reply); 1012124211Sdes return (PAM_CONV_ERR); 1013106130Sdes} 1014106130Sdes 1015137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL }; 1016126277Sdes 1017124211Sdes/* 1018124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't 1019124211Sdes * support that 1020124211Sdes */ 1021124211Sdesvoid 1022124211Sdesdo_pam_chauthtok(void) 102369591Sgreen{ 1024124211Sdes if (use_privsep) 1025124211Sdes fatal("Password expired (unable to change with privsep)"); 1026124211Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1027126277Sdes (const void *)&tty_conv); 1028124211Sdes if (sshpam_err != PAM_SUCCESS) 1029124211Sdes fatal("PAM: failed to set PAM_CONV: %s", 1030124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 1031124211Sdes debug("PAM: changing password"); 1032124211Sdes sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK); 1033124211Sdes if (sshpam_err != PAM_SUCCESS) 1034124211Sdes fatal("PAM: pam_chauthtok(): %s", 1035124211Sdes pam_strerror(sshpam_handle, sshpam_err)); 103669591Sgreen} 103769591Sgreen 1038126277Sdesvoid 1039126277Sdesdo_pam_session(void) 1040126277Sdes{ 1041126277Sdes debug3("PAM: opening session"); 1042126277Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1043126277Sdes (const void *)&store_conv); 1044126277Sdes if (sshpam_err != PAM_SUCCESS) 1045126277Sdes fatal("PAM: failed to set PAM_CONV: %s", 1046126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1047126277Sdes sshpam_err = pam_open_session(sshpam_handle, 0); 1048147005Sdes if (sshpam_err == PAM_SUCCESS) 1049147005Sdes sshpam_session_open = 1; 1050147005Sdes else { 1051147005Sdes sshpam_session_open = 0; 1052147005Sdes disable_forwarding(); 1053147005Sdes error("PAM: pam_open_session(): %s", 1054126277Sdes pam_strerror(sshpam_handle, sshpam_err)); 1055147005Sdes } 1056147005Sdes 1057126277Sdes} 1058126277Sdes 1059147005Sdesint 1060147005Sdesis_pam_session_open(void) 1061147005Sdes{ 1062147005Sdes return sshpam_session_open; 1063147005Sdes} 1064147005Sdes 1065126277Sdes/* 1066124211Sdes * Set a PAM environment string. We need to do this so that the session 1067124211Sdes * modules can handle things like Kerberos/GSI credentials that appear 1068124211Sdes * during the ssh authentication process. 1069124211Sdes */ 1070124211Sdesint 1071126277Sdesdo_pam_putenv(char *name, char *value) 107269591Sgreen{ 1073124211Sdes int ret = 1; 1074126277Sdes#ifdef HAVE_PAM_PUTENV 1075124211Sdes char *compound; 1076124211Sdes size_t len; 107769591Sgreen 1078124211Sdes len = strlen(name) + strlen(value) + 2; 1079124211Sdes compound = xmalloc(len); 108069591Sgreen 1081124211Sdes snprintf(compound, len, "%s=%s", name, value); 1082124211Sdes ret = pam_putenv(sshpam_handle, compound); 1083263970Sdes free(compound); 1084124211Sdes#endif 108569591Sgreen 1086124211Sdes return (ret); 1087124211Sdes} 108869591Sgreen 1089126277Sdeschar ** 1090126277Sdesfetch_pam_child_environment(void) 1091124211Sdes{ 1092126277Sdes return sshpam_env; 109369591Sgreen} 109469591Sgreen 1095124211Sdeschar ** 1096124211Sdesfetch_pam_environment(void) 1097124211Sdes{ 1098124211Sdes return (pam_getenvlist(sshpam_handle)); 1099124211Sdes} 1100124211Sdes 1101124211Sdesvoid 1102124211Sdesfree_pam_environment(char **env) 1103124211Sdes{ 1104124211Sdes char **envp; 1105124211Sdes 1106124211Sdes if (env == NULL) 1107124211Sdes return; 1108124211Sdes 1109124211Sdes for (envp = env; *envp; envp++) 1110263970Sdes free(*envp); 1111263970Sdes free(env); 1112124211Sdes} 1113124211Sdes 1114137019Sdes/* 1115137019Sdes * "Blind" conversation function for password authentication. Assumes that 1116137019Sdes * echo-off prompts are for the password and stores messages for later 1117137019Sdes * display. 1118137019Sdes */ 1119137019Sdesstatic int 1120149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg, 1121137019Sdes struct pam_response **resp, void *data) 1122137019Sdes{ 1123137019Sdes struct pam_response *reply; 1124137019Sdes int i; 1125137019Sdes size_t len; 1126137019Sdes 1127137019Sdes debug3("PAM: %s called with %d messages", __func__, n); 1128137019Sdes 1129137019Sdes *resp = NULL; 1130137019Sdes 1131137019Sdes if (n <= 0 || n > PAM_MAX_NUM_MSG) 1132137019Sdes return (PAM_CONV_ERR); 1133137019Sdes 1134181111Sdes if ((reply = calloc(n, sizeof(*reply))) == NULL) 1135137019Sdes return (PAM_CONV_ERR); 1136137019Sdes 1137137019Sdes for (i = 0; i < n; ++i) { 1138137019Sdes switch (PAM_MSG_MEMBER(msg, i, msg_style)) { 1139137019Sdes case PAM_PROMPT_ECHO_OFF: 1140137019Sdes if (sshpam_password == NULL) 1141137019Sdes goto fail; 1142137019Sdes if ((reply[i].resp = strdup(sshpam_password)) == NULL) 1143137019Sdes goto fail; 1144137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1145137019Sdes break; 1146137019Sdes case PAM_ERROR_MSG: 1147137019Sdes case PAM_TEXT_INFO: 1148137019Sdes len = strlen(PAM_MSG_MEMBER(msg, i, msg)); 1149137019Sdes if (len > 0) { 1150137019Sdes buffer_append(&loginmsg, 1151137019Sdes PAM_MSG_MEMBER(msg, i, msg), len); 1152137019Sdes buffer_append(&loginmsg, "\n", 1); 1153137019Sdes } 1154137019Sdes if ((reply[i].resp = strdup("")) == NULL) 1155137019Sdes goto fail; 1156137019Sdes reply[i].resp_retcode = PAM_SUCCESS; 1157137019Sdes break; 1158137019Sdes default: 1159137019Sdes goto fail; 1160137019Sdes } 1161137019Sdes } 1162137019Sdes *resp = reply; 1163137019Sdes return (PAM_SUCCESS); 1164137019Sdes 1165149753Sdes fail: 1166137019Sdes for(i = 0; i < n; i++) { 1167263970Sdes free(reply[i].resp); 1168137019Sdes } 1169263970Sdes free(reply); 1170137019Sdes return (PAM_CONV_ERR); 1171137019Sdes} 1172137019Sdes 1173137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL }; 1174137019Sdes 1175137019Sdes/* 1176137019Sdes * Attempt password authentication via PAM 1177137019Sdes */ 1178137019Sdesint 1179137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password) 1180137019Sdes{ 1181137019Sdes int flags = (options.permit_empty_passwd == 0 ? 1182137019Sdes PAM_DISALLOW_NULL_AUTHTOK : 0); 1183137019Sdes 1184137019Sdes if (!options.use_pam || sshpam_handle == NULL) 1185137019Sdes fatal("PAM: %s called when PAM disabled or failed to " 1186137019Sdes "initialise.", __func__); 1187137019Sdes 1188137019Sdes sshpam_password = password; 1189137019Sdes sshpam_authctxt = authctxt; 1190137019Sdes 1191137019Sdes /* 1192137019Sdes * If the user logging in is invalid, or is root but is not permitted 1193137019Sdes * by PermitRootLogin, use an invalid password to prevent leaking 1194137019Sdes * information via timing (eg if the PAM config has a delay on fail). 1195137019Sdes */ 1196137019Sdes if (!authctxt->valid || (authctxt->pw->pw_uid == 0 && 1197149753Sdes options.permit_root_login != PERMIT_YES)) 1198137019Sdes sshpam_password = badpw; 1199137019Sdes 1200137019Sdes sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, 1201137019Sdes (const void *)&passwd_conv); 1202137019Sdes if (sshpam_err != PAM_SUCCESS) 1203137019Sdes fatal("PAM: %s: failed to set PAM_CONV: %s", __func__, 1204137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1205137019Sdes 1206137019Sdes sshpam_err = pam_authenticate(sshpam_handle, flags); 1207137019Sdes sshpam_password = NULL; 1208137019Sdes if (sshpam_err == PAM_SUCCESS && authctxt->valid) { 1209137019Sdes debug("PAM: password authentication accepted for %.100s", 1210137019Sdes authctxt->user); 1211149753Sdes return 1; 1212137019Sdes } else { 1213137019Sdes debug("PAM: password authentication failed for %.100s: %s", 1214137019Sdes authctxt->valid ? authctxt->user : "an illegal user", 1215137019Sdes pam_strerror(sshpam_handle, sshpam_err)); 1216137019Sdes return 0; 1217137019Sdes } 1218137019Sdes} 121969591Sgreen#endif /* USE_PAM */ 1220