Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: binary MIME-Version: 1.0 X-Mailer: MIME-tools 5.420 (Entity 5.420) X-RT-Original-Encoding: iso-8859-1 Content-Length: 37332 From krb5-bugs-incoming-bounces@PCH.MIT.EDU Tue Apr 15 13:57:42 2008 Received: from pch.mit.edu (PCH.MIT.EDU [18.7.21.90]) by krbdev.mit.edu (8.12.9) with ESMTP id m3FHvgHW020493; Tue, 15 Apr 2008 13:57:42 -0400 (EDT) Received: from pch.mit.edu (pch.mit.edu [127.0.0.1]) by pch.mit.edu (8.13.6/8.12.8) with ESMTP id m3FHvb1Q021875; Tue, 15 Apr 2008 13:57:37 -0400 Received: from pacific-carrier-annex.mit.edu (PACIFIC-CARRIER-ANNEX.MIT.EDU [18.7.21.83]) by pch.mit.edu (8.13.6/8.12.8) with ESMTP id m3FF2Vt4006051 for ; Tue, 15 Apr 2008 11:02:31 -0400 Received: from mit.edu (M24-004-BARRACUDA-2.MIT.EDU [18.7.7.112]) by pacific-carrier-annex.mit.edu (8.13.6/8.9.2) with ESMTP id m3FF2I1k008282 for ; Tue, 15 Apr 2008 11:02:19 -0400 (EDT) Received: from mx1.redhat.com (mx1.redhat.com [66.187.233.31]) by mit.edu (Spam Firewall) with ESMTP id 4E75A108F2D8 for ; Tue, 15 Apr 2008 11:01:53 -0400 (EDT) Received: from int-mx1.corp.redhat.com (int-mx1.corp.redhat.com [172.16.52.254]) by mx1.redhat.com (8.13.8/8.13.8) with ESMTP id m3FF1k9X018346 for ; Tue, 15 Apr 2008 11:01:46 -0400 Received: from blade.boston.redhat.com (blade.boston.redhat.com [172.16.80.50]) by int-mx1.corp.redhat.com (8.13.1/8.13.1) with ESMTP id m3FF1jVo000802 for ; Tue, 15 Apr 2008 11:01:45 -0400 Received: from blade.boston.redhat.com (localhost.localdomain [127.0.0.1]) by blade.boston.redhat.com (8.14.2/8.14.2) with ESMTP id m3FF1jsU002642 for ; Tue, 15 Apr 2008 11:01:45 -0400 Received: (from nalin@localhost) by blade.boston.redhat.com (8.14.2/8.14.2/Submit) id m3FF1jSs002641; Tue, 15 Apr 2008 11:01:45 -0400 Date: Tue, 15 Apr 2008 11:01:45 -0400 Message-Id: <200804151501.m3FF1jSs002641@blade.boston.redhat.com> To: krb5-bugs@mit.edu From: nalin@redhat.com X-send-pr-version: 3.99 X-Scanned-By: MIMEDefang 2.42 X-Scanned-By: MIMEDefang 2.58 on 172.16.52.254 X-Spam-Score: 2.779 X-Spam-Level: ** (2.779) X-Spam-Flag: NO X-Mailman-Approved-At: Tue, 15 Apr 2008 13:57:35 -0400 X-BeenThere: krb5-bugs-incoming@mailman.mit.edu X-Mailman-Version: 2.1.6 Precedence: list Reply-To: nalin@redhat.com Sender: krb5-bugs-incoming-bounces@PCH.MIT.EDU Errors-To: krb5-bugs-incoming-bounces@PCH.MIT.EDU >Submitter-Id: net >Originator: nalin@redhat.com >Organization: >Confidential: no >Synopsis: PAM support for rshd, login, and ftpd >Severity: non-critical >Priority: low >Category: krb5-appl >Class: change-request >Release: 1.6.3 >Environment: System: Linux blade.boston.redhat.com 2.6.23-6.fc8 #1 SMP Thu Oct 11 13:36:39 EDT 2007 x86_64 x86_64 x86_64 GNU/Linux Architecture: x86_64 >Description: Users who log in using the various Kerberos-aware applications (rshd, rlogin and telnet (login.krb5)) don't have PAM account and session management performed for them. On systems where process limits are set by PAM, their sessions are exempt from these limits. On systems where session management is used to hook in AFS token setting, users who FTP in don't have access to their home directories. >How-To-Repeat: Log in via rlogin or telnet, execute the "limit" or "ulimit -a" command via rsh, or access the system using ftpd. >Fix: I'm including a patch which makes changes to krshd, login, and ftpd. I'm not really sure about what the right thing to do for "ksu" is, so for now at least, this patch doesn't touch it. It modifies krshd so that it performs PAM account and session management. It must now always fork so that it can always clean up the session. The PAM session is opened and credentials initialized after any forwarded credentials are stored to disk and before access to the user's home directory is attempted (this required moving the "chdir" call). The default service name is "kshell" or "ekshell", depending on whether or not encryption is in use, to avoid a dependency or conflict on the plain rsh server's configuration file. At run-time, krshd's behavior can be reset to the earlier, non-PAM behavior by setting "use_pam" to false in the [rshd] section of /etc/krb5.conf. It modifies ftpd so that authentication with a plaintext password goes through PAM, and it performs PAM account and session management for all clients. The PAM session is opened and credentials initialized after any forwarded credentials are stored to disk. The default service name is "gssftp", mainly to avoid conflicts with other FTP servers' configuration files. At run-time, ftpd's behavior can be reset to the earlier, non-PAM behavior by setting "use_pam" to false in the [ftpd] section of /etc/krb5.conf. It modifies login so that instead of directly obtaining v5 or v4 credentials or running aklog, it calls PAM for authentication if strong authentication hasn't already been performed, so that it performs account management for all users (prompting for a password change if need be), and that it performs session management for all users. The PAM session is opened and credentials initialized after any forwarded credentials are stored to disk. The default service name is "login", because its configuration is pretty much always going to be there. At run-time, login's behavior can be reset to the earlier, non-PAM behavior by setting "use_pam" to false in the [login] section of /etc/krb5.conf. When enabled, ftpd, krshd, and login.krb5 gain dependence on libpam. The default at build-time is to enable PAM support if it's found to be available, but it can be forced on or off with the "--with-pam" or "--without-pam" option. The individual PAM service names can be set with these arguments: login.krb5: --with-pam-login-service (default is "login") rshd without encryption: --with-pam-kshell-service (default is "kshell") rshd with encryption: --with-pam-ekshell-service (default is "ekshell") gssftpd: --with-pam-ftp-service (default is "gssftp) Index: src/configure.in =================================================================== --- src/configure.in (revision 20303) +++ src/configure.in (working copy) @@ -1073,6 +1073,8 @@ dnl AC_CONFIG_SUBDIRS(appl/libpty appl/bsd appl/gssftp appl/telnet) +KRB5_WITH_PAM + AC_CONFIG_FILES(krb5-config, [chmod +x krb5-config]) V5_AC_OUTPUT_MAKEFILE(. Index: src/appl/gssftp/configure.in =================================================================== --- src/appl/gssftp/configure.in (revision 20303) +++ src/appl/gssftp/configure.in (working copy) @@ -17,6 +17,7 @@ AC_REPLACE_FUNCS(getdtablesize) AC_CHECK_FUNCS(getcwd getdtablesize getusershell seteuid setreuid setresuid strerror getenv) AC_CHECK_LIB(crypt,crypt) dnl +KRB5_WITH_PAM KRB5_AC_LIBUTIL dnl dnl copied from appl/bsd/configure.in Index: src/appl/gssftp/ftpd/Makefile.in =================================================================== --- src/appl/gssftp/ftpd/Makefile.in (revision 20303) +++ src/appl/gssftp/ftpd/Makefile.in (working copy) @@ -14,23 +14,25 @@ LIBOBJS=@LIBOBJS@ COMERRLIB=$(BUILDTOP)/util/et/libcom_err.a FTPD_LIBS=@FTPD_LIBS@ +PAM_LIBS=@PAM_LIBS@ SRCS = $(srcdir)/ftpd.c ftpcmd.c $(srcdir)/popen.c \ $(srcdir)/vers.c \ $(srcdir)/../ftp/glob.c \ $(srcdir)/../ftp/radix.c \ $(srcdir)/../ftp/secure.c \ + $(srcdir)/../../bsd/pam.c \ $(srcdir)/../../bsd/getdtablesize.c $(SETENVSRC) OBJS = ftpd.o ftpcmd.o glob.o popen.o vers.o radix.o \ - secure.o $(LIBOBJS) $(SETENVOBJ) + secure.o pam.o getdtablesize.o $(LIBOBJS) $(SETENVOBJ) LOCALINCLUDES = -I$(srcdir)/.. -I$(srcdir) @KRB4_INCLUDES@ all:: ftpd ftpd: $(OBJS) $(PTY_DEPLIB) $(GSS_DEPLIBS) $(KRB4COMPAT_DEPLIBS) - $(CC_LINK) -o $@ $(OBJS) $(FTPD_LIBS) $(PTY_LIB) $(UTIL_LIB) $(GSS_LIBS) $(KRB4COMPAT_LIBS) + $(CC_LINK) -o $@ $(OBJS) $(FTPD_LIBS) $(PTY_LIB) $(UTIL_LIB) $(GSS_LIBS) $(PAM_LIBS) $(KRB4COMPAT_LIBS) generate-files-mac: ftpcmd.c @@ -62,6 +64,8 @@ getdtablesize.o: $(srcdir)/../../bsd/getdtablesize.c $(CC) -c $(ALL_CFLAGS) $(srcdir)/../../bsd/getdtablesize.c +pam.o: $(srcdir)/../../bsd/pam.c + $(CC) -c $(ALL_CFLAGS) $(srcdir)/../../bsd/pam.c setenv.o: $(srcdir)/../../bsd/setenv.c $(CC) -c $(ALL_CFLAGS) $(srcdir)/../../bsd/setenv.c Index: src/appl/gssftp/ftpd/ftpd.c =================================================================== --- src/appl/gssftp/ftpd/ftpd.c (revision 20303) +++ src/appl/gssftp/ftpd/ftpd.c (working copy) @@ -70,6 +70,9 @@ #ifdef HAVE_SHADOW #include #endif +#ifdef USE_PAM +#include "../../bsd/pam.h" +#endif #include #include #ifndef POSIX_SETJMP @@ -803,6 +806,17 @@ } #endif /* KRB5_KRB4_COMPAT */ +#ifdef USE_PAM + if (appl_pam_enabled(kcontext, "ftpd")) { + if (appl_pam_acct_mgmt(FTP_PAM_SERVICE, + appl_pam_noninteractive, + pw->pw_name, "", + FTP_PAM_SERVICE) != 0) { + reply(530, "Login incorrect."); + return; + } + } +#endif if (!authorized && authlevel == AUTHLEVEL_AUTHORIZE) { strncat(buf, "; Access denied.", sizeof(buf) - strlen(buf) - 1); @@ -903,6 +917,10 @@ (void) krb5_seteuid((uid_t)0); if (logged_in) pty_logwtmp(ttyline, "", ""); +#ifdef USE_PAM + if (appl_pam_enabled(kcontext, "ftpd")) + appl_pam_cleanup(); +#endif if (have_creds) { #ifdef GSSAPI krb5_cc_destroy(kcontext, ccache); @@ -1073,9 +1091,18 @@ * kpass fails and the user has no local password * kpass fails and the provided password doesn't match pw */ - if (pw == NULL || (!kpass(pw->pw_name, passwd) && - (want_creds || !*pw->pw_passwd || - strcmp(xpasswd, pw->pw_passwd)))) { + if ((pw == NULL) || +#ifdef USE_PAM + appl_pam_enabled(kcontext, "ftpd") ? + (appl_pam_authenticate(FTP_PAM_SERVICE, + appl_pam_noninteractive, + pw->pw_name, passwd, + FTP_PAM_SERVICE) != 0) : +#endif + (!kpass(pw->pw_name, passwd) && + (want_creds || + !*pw->pw_passwd || + strcmp(xpasswd, pw->pw_passwd)))) { pw = NULL; sleep(5); if (++login_attempts >= 3) { @@ -1092,6 +1119,17 @@ } login_attempts = 0; /* this time successful */ +#ifdef USE_PAM + if (appl_pam_enabled(kcontext, "ftpd")) { + if (appl_pam_acct_mgmt(FTP_PAM_SERVICE, appl_pam_noninteractive, + pw->pw_name, passwd, + FTP_PAM_SERVICE) != 0) { + reply(530, "Login incorrect."); + return; + } + } +#endif + login(passwd, 0); return; } @@ -1110,6 +1148,18 @@ chown(tkt_string(), pw->pw_uid, pw->pw_gid); #endif } +#ifdef USE_PAM + if (appl_pam_enabled(kcontext, "ftpd")) { + if (appl_pam_session_open() != 0) { + reply(550, "Can't open PAM session."); + goto bad; + } + if (appl_pam_cred_init() != 0) { + reply(550, "Can't establish PAM credentials."); + goto bad; + } + } +#endif (void) krb5_setegid((gid_t)pw->pw_gid); (void) initgroups(pw->pw_name, pw->pw_gid); @@ -2114,6 +2164,10 @@ dest_tkt(); #endif } +#ifdef USE_PAM + if (appl_pam_enabled(kcontext, "ftpd")) + appl_pam_cleanup(); +#endif /* beware of flushing buffers after a SIGPIPE */ _exit(status); } Index: src/appl/bsd/Makefile.in =================================================================== --- src/appl/bsd/Makefile.in (revision 20303) +++ src/appl/bsd/Makefile.in (working copy) @@ -14,13 +14,14 @@ V4RCP=@V4RCP@ V4RCPO=@V4RCPO@ KRSHDLIBS=@KRSHDLIBS@ +PAMOBJS=pam.o SRCS= $(srcdir)/krcp.c $(srcdir)/krlogin.c $(srcdir)/krsh.c $(srcdir)/kcmd.c \ $(srcdir)/forward.c $(srcdir)/compat_recv.c \ $(srcdir)/login.c $(srcdir)/krshd.c $(srcdir)/krlogind.c \ $(srcdir)/v4rcp.c OBJS= krcp.o krlogin.o krsh.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) \ - login.o krshd.o krlogind.o $(V4RCPO) $(LIBOBJS) + login.o krshd.o krlogind.o $(V4RCPO) $(LIBOBJS) $(PAMOBJS) UCB_RLOGIN = @UCB_RLOGIN@ UCB_RSH = @UCB_RSH@ @@ -66,8 +67,8 @@ ${DESTDIR}$(CLIENT_MANDIR)/`echo $$f|sed '$(transform)'`.1; \ fi -kshd: krshd.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) $(LIBOBJS) $(PTY_DEPLIB) $(KRB4COMPAT_DEPLIBS) $(APPUTILS_DEPLIB) - $(CC_LINK) -o kshd krshd.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) $(LIBOBJS) $(KRSHDLIBS) $(PTY_LIB) $(UTIL_LIB) $(KRB4COMPAT_LIBS) $(APPUTILS_LIB) +kshd: krshd.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) $(PAMOBJS) $(LIBOBJS) $(PTY_DEPLIB) $(KRB4COMPAT_DEPLIBS) $(APPUTILS_DEPLIB) + $(CC_LINK) -o kshd krshd.o kcmd.o forward.o compat_recv.o $(PAMOBJS) $(SETENVOBJ) $(LIBOBJS) $(KRSHDLIBS) $(PTY_LIB) $(UTIL_LIB) $(KRB4COMPAT_LIBS) $(PAM_LIBS) $(APPUTILS_LIB) klogind: krlogind.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) $(LIBOBJS) $(PTY_DEPLIB) $(KRB4COMPAT_DEPLIBS) $(APPUTILS_DEPLIB) $(CC_LINK) -o klogind krlogind.o kcmd.o forward.o compat_recv.o $(SETENVOBJ) $(LIBOBJS) $(PTY_LIB) $(UTIL_LIB) $(KRB4COMPAT_LIBS) $(APPUTILS_LIB) @@ -84,8 +85,8 @@ # No program name transformation is done with login.krb5 since it is directly # referenced by klogind. # -login.krb5: login.o $(SETENVOBJ) $(LIBOBJS) $(PTY_DEPLIB) $(KRB4COMPAT_DEPLIBS) - $(CC_LINK) -o login.krb5 login.o $(SETENVOBJ) $(LIBOBJS) $(LOGINLIBS) $(PTY_LIB) $(KRB4COMPAT_LIBS) +login.krb5: login.o $(SETENVOBJ) $(PAMOBJS) $(LIBOBJS) $(PTY_DEPLIB) $(KRB4COMPAT_DEPLIBS) + $(CC_LINK) -o login.krb5 login.o $(SETENVOBJ) $(PAMOBJS) $(LIBOBJS) $(LOGINLIBS) $(PTY_LIB) $(KRB4COMPAT_LIBS) $(PAM_LIBS) install:: $(INSTALL_PROGRAM) login.krb5 $(DESTDIR)$(SERVER_BINDIR)/login.krb5 Index: src/appl/bsd/configure.in =================================================================== --- src/appl/bsd/configure.in (revision 20303) +++ src/appl/bsd/configure.in (working copy) @@ -24,6 +24,7 @@ AC_CHECK_LIB(cfg,main, LOGINLIBS="$LOGINLIBS -lodm -ls -lcfg" ))) +KRB5_WITH_PAM dnl dnl Make our operating system-specific security checks and definitions for dnl login. Index: src/appl/bsd/login.c =================================================================== --- src/appl/bsd/login.c (revision 20303) +++ src/appl/bsd/login.c (working copy) @@ -159,6 +159,11 @@ #include "osconf.h" #endif /* KRB5_GET_TICKETS */ +#ifdef USE_PAM +#include "pam.h" +int login_use_pam = 1; +#endif + #ifdef KRB4_KLOGIN /* support for running under v4 klogind, -k -K flags */ #define KRB4 @@ -351,6 +356,9 @@ char *flagname; int *flag; } login_conf_set[] = { +#if defined(USE_PAM) && defined(USE_PAM_CONFIGURATION_KEYWORD) + {USE_PAM_CONFIGURATION_KEYWORD, &login_use_pam}, +#endif #ifdef KRB5_GET_TICKETS {"krb5_get_tickets", &login_krb5_get_tickets}, #endif @@ -1292,6 +1300,19 @@ if (!unix_needs_passwd()) break; +#ifdef USE_PAM + if (login_use_pam) { + if (appl_pam_authenticate(LOGIN_PAM_SERVICE, appl_pam_interactive, + username, "", + ttyname(STDIN_FILENO)) == PAM_SUCCESS) { + break; + } else { + /* the goto target label is in a different nesting scope, but + * it's roughly where we want to land */ + goto bad_login; + } + } +#endif /* we have several sets of code: 1) get v5 tickets alone -DKRB5_GET_TICKETS 2) get v4 tickets alone [** don't! only get them *with* v5 **] @@ -1406,6 +1427,25 @@ /* committed to login -- turn off timeout */ (void) alarm((u_int) 0); +#ifdef USE_PAM + if (login_use_pam) { + if (appl_pam_acct_mgmt(LOGIN_PAM_SERVICE, appl_pam_interactive, + username, "", + ttyname(STDIN_FILENO)) != 0) { + printf("Login incorrect\n"); + sleepexit(1); + } + if (appl_pam_requires_chauthtok()) { + if (appl_pam_chauthtok() != 0) { + printf("Failed to change password.\n"); + sleepexit(1); + } + } + } else { + /* the "else" here is the non-PAM behavior which continues until the + * next ifdef USE_PAM block, as of this writing more or less + * duplicating the work of pam_securetty and an OQUOTA check */ +#endif /* * If valid so far and root is logging in, see if root logins on * this terminal are permitted. @@ -1446,7 +1486,22 @@ sleepexit(0); } #endif +#ifdef USE_PAM + } +#endif /* USE_PAM */ +#ifdef USE_PAM + if (login_use_pam) { + appl_pam_set_forwarded_ccname(getenv("KRB5CCNAME")); + if (appl_pam_session_open() != 0) { + sleepexit(1); + } + if (appl_pam_cred_init() != 0) { + sleepexit(1); + } + } +#endif /* USE_PAM */ + if (chdir(pwd->pw_dir) < 0) { printf("No directory %s!\n", pwd->pw_dir); if (chdir("/")) @@ -1792,6 +1847,11 @@ } #endif /* KRB5_GET_TICKETS */ +#ifdef USE_PAM + if (login_use_pam) + appl_pam_setenv(); +#endif + if (tty[sizeof("tty")-1] == 'd') syslog(LOG_INFO, "DIALUP %s, %s", tty, pwd->pw_name); if (pwd->pw_uid == 0) Index: src/appl/bsd/krshd.c =================================================================== --- src/appl/bsd/krshd.c (revision 20303) +++ src/appl/bsd/krshd.c (working copy) @@ -181,6 +181,10 @@ #include #endif +#ifdef USE_PAM +#include "pam.h" +#endif + #ifndef MAXDNAME #define MAXDNAME 256 /*per the rfc*/ #endif @@ -201,6 +205,7 @@ int require_encrypt = 0; int do_encrypt = 0; +int force_fork = 0; int anyport = 0; char *kprogdir = KPROGDIR; int netf; @@ -1081,14 +1086,6 @@ } #endif /*CRAY*/ - if (chdir(pwd->pw_dir) < 0) { - if(chdir("/") < 0) { - error("No remote directory.\n"); - goto signout_please; - } - pwd->pw_dir = "/"; - } - #ifdef KERBEROS #if defined(KRB5_KRB4_COMPAT) && !defined(ALWAYS_V5_KUSEROK) @@ -1147,11 +1144,49 @@ goto signout_please; } +#ifdef USE_PAM + if (appl_pam_enabled(bsd_context, "rshd")) { + if (appl_pam_acct_mgmt(do_encrypt ? + EKSHELL_PAM_SERVICE : + KSHELL_PAM_SERVICE, + appl_pam_noninteractive, + locuser, + "", + do_encrypt ? + EKSHELL_PAM_SERVICE : + KSHELL_PAM_SERVICE) != 0) { + error("Login denied.\n"); + goto signout_please; + } + if (appl_pam_requires_chauthtok()) { + error("Password change required, but not possible over rsh.\n"); + goto signout_please; + } + force_fork = 1; + appl_pam_set_forwarded_ccname(getenv("KRB5CCNAME")); + if (appl_pam_session_open() != 0) { + error("Login failure.\n"); + goto signout_please; + } + if (appl_pam_cred_init()) { + error("Login failure.\n"); + goto signout_please; + } + } else +#endif if (pwd->pw_uid && !access(NOLOGIN, F_OK)) { error("Logins currently disabled.\n"); goto signout_please; } + if (chdir(pwd->pw_dir) < 0) { + if (chdir("/") < 0) { + error("No remote directory.\n"); + goto signout_please; + } + pwd->pw_dir = "/"; + } + /* Log access to account */ pwd = (struct passwd *) getpwnam(locuser); if (pwd && (pwd->pw_uid == 0)) { @@ -1191,7 +1226,7 @@ (void) write(2, "", 1); - if (port||do_encrypt) { + if (port||do_encrypt||force_fork) { if (port&&(pipe(pv) < 0)) { error("Can't make pipe.\n"); goto signout_please; @@ -1503,6 +1538,15 @@ environ = envinit; +#ifdef USE_PAM + if (appl_pam_enabled(bsd_context, "rshd")) { + if (appl_pam_setenv() != 0) { + error("Login failure.\n"); + goto signout_please; + } + } +#endif + #ifdef KERBEROS /* To make Kerberos rcp work correctly, we must ensure that we invoke Kerberos rcp on this end, not normal rcp, even if the Index: src/appl/bsd/pam.c =================================================================== --- src/appl/bsd/pam.c (revision 0) +++ src/appl/bsd/pam.c (revision 0) @@ -0,0 +1,443 @@ +/* + * src/appl/bsd/pam.c + * + * Copyright 2007,2008 Red Hat, Inc. + * + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Red Hat, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Convenience wrappers for using PAM. + */ + +#ifdef USE_PAM +#include +#include +#include +#include +#include +#include "k5-int.h" +#include "pam.h" + +#ifndef MAXPWSIZE +#define MAXPWSIZE 256 +#endif + +static int appl_pam_started; +static pid_t appl_pam_starter = -1; +static int appl_pam_session_is_open; +static int appl_pam_creds_initialized; +static int appl_pam_pwchange_required; +static pam_handle_t *appl_pamh; +static struct pam_conv appl_pam_conv; +static char *appl_pam_user; +struct appl_pam_non_interactive_args { + const char *user; + const char *password; +}; + +int +appl_pam_enabled(krb5_context context, const char *section) +{ + int enabled = 1; +#ifdef USE_PAM_CONFIGURATION_KEYWORD + if ((context != NULL) && (context->profile != NULL)) { + if (profile_get_boolean(context->profile, + section, + USE_PAM_CONFIGURATION_KEYWORD, + NULL, + enabled, &enabled) != 0) { + enabled = 1; + } + } +#endif + return enabled; +} + +void +appl_pam_cleanup(void) +{ + if (getpid() != appl_pam_starter) { + return; + } +#ifdef DEBUG + printf("Called to clean up PAM.\n"); +#endif + if (appl_pam_creds_initialized) { +#ifdef DEBUG + printf("Deleting PAM credentials.\n"); +#endif + pam_setcred(appl_pamh, PAM_DELETE_CRED); + appl_pam_creds_initialized = 0; + } + if (appl_pam_session_is_open) { +#ifdef DEBUG + printf("Closing PAM session.\n"); +#endif + pam_close_session(appl_pamh, 0); + appl_pam_session_is_open = 0; + } + appl_pam_pwchange_required = 0; + if (appl_pam_started) { +#ifdef DEBUG + printf("Shutting down PAM.\n"); +#endif + pam_end(appl_pamh, 0); + appl_pam_started = 0; + appl_pam_starter = -1; + free(appl_pam_user); + appl_pam_user = NULL; + } +} + +static int +appl_pam_interactive_converse(int num_msg, const struct pam_message **msg, + struct pam_response **presp, void *appdata_ptr) +{ + const struct pam_message *message; + struct pam_response *resp; + int i, code; + char *pwstring, pwbuf[MAXPWSIZE]; + unsigned int pwsize; + resp = malloc(sizeof(struct pam_response) * num_msg); + if (resp == NULL) { + return PAM_BUF_ERR; + } + memset(resp, 0, sizeof(struct pam_response) * num_msg); + code = PAM_SUCCESS; + for (i = 0; i < num_msg; i++) { +#if defined(__LINUX_PAM__) || defined(_OPENPAM) + /* Linux-PAM and OpenPAM treat messages as an array of + * pointers. */ + message = msg[i]; +#else + /* Other PAM implementations treat messages as a pointer to an + * array. */ + message = &((*msg)[i]); +#endif + pwstring = NULL; + switch (message->msg_style) { + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + printf("[%s]\n", message->msg ? message->msg : ""); + fflush(stdout); + resp[i].resp = NULL; + resp[i].resp_retcode = PAM_SUCCESS; + break; + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + if (message->msg_style == PAM_PROMPT_ECHO_ON) { + if (fgets(pwbuf, sizeof(pwbuf), + stdin) != NULL) { + pwbuf[strcspn(pwbuf, "\r\n")] = '\0'; + pwstring = pwbuf; + } + } else { + pwstring = getpass(message->msg ? + message->msg : + ""); + } + if ((pwstring != NULL) && (pwstring[0] != '\0')) { + pwsize = strlen(pwstring); + resp[i].resp = malloc(pwsize + 1); + if (resp[i].resp == NULL) { + resp[i].resp_retcode = PAM_BUF_ERR; + } else { + memcpy(resp[i].resp, pwstring, pwsize); + resp[i].resp[pwsize] = '\0'; + resp[i].resp_retcode = PAM_SUCCESS; + } + } else { + resp[i].resp_retcode = PAM_CONV_ERR; + code = PAM_CONV_ERR; + } + break; + default: + break; + } + } + *presp = resp; + return code; +} + +static int +appl_pam_non_interactive_converse(int num_msg, + const struct pam_message **msg, + struct pam_response **presp, + void *appdata_ptr) +{ + const struct pam_message *message; + struct pam_response *resp; + int i, code; + unsigned int pwsize; + struct appl_pam_non_interactive_args *args; + const char *pwstring; + resp = malloc(sizeof(struct pam_response) * num_msg); + if (resp == NULL) { + return PAM_BUF_ERR; + } + args = appdata_ptr; + memset(resp, 0, sizeof(struct pam_response) * num_msg); + code = PAM_SUCCESS; + for (i = 0; i < num_msg; i++) { +#if defined(__LINUX_PAM__) || defined(_OPENPAM) + /* Linux-PAM and OpenPAM treat messages as an array of + * pointers. */ + message = msg[i]; +#else + /* Other PAM implementations treat messages as a pointer to an + * array. */ + message = &((*msg)[i]); +#endif + pwstring = NULL; + switch (message->msg_style) { + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + break; + case PAM_PROMPT_ECHO_ON: + case PAM_PROMPT_ECHO_OFF: + if (message->msg_style == PAM_PROMPT_ECHO_ON) { + /* assume "user" */ + pwstring = args->user; + } else { + /* assume "password" */ + pwstring = args->password; + } + if ((pwstring != NULL) && (pwstring[0] != '\0')) { + pwsize = strlen(pwstring); + resp[i].resp = malloc(pwsize + 1); + if (resp[i].resp == NULL) { + resp[i].resp_retcode = PAM_BUF_ERR; + } else { + memcpy(resp[i].resp, pwstring, pwsize); + resp[i].resp[pwsize] = '\0'; + resp[i].resp_retcode = PAM_SUCCESS; + } + } else { + resp[i].resp_retcode = PAM_CONV_ERR; + code = PAM_CONV_ERR; + } + break; + default: + break; + } + } + *presp = resp; + return code; +} + +void +appl_pam_set_forwarded_ccname(const char *ccname) +{ + char *ccname2; + if (appl_pam_started && (ccname != NULL) && (strlen(ccname) > 0)) { + ccname2 = malloc(strlen(KRB5_ENV_CCNAME) + strlen(ccname) + 2); + if (ccname2 != NULL) { +#ifdef DEBUG + printf("Setting %s to \"%s\" in PAM environment.\n", + KRB5_ENV_CCNAME, ccname); +#endif + sprintf(ccname2, "%s=%s", KRB5_ENV_CCNAME, ccname); + pam_putenv(appl_pamh, ccname2); + } + } +} + +static int +appl_pam_start(const char *service, enum appl_pam_interactive_flag interactive, + const char *login_username, + const char *password_if_not_interactive, + const char *tty) +{ + static int exit_handler_registered; + static struct appl_pam_non_interactive_args args; + int ret = 0; + if (appl_pam_started && + (strcmp(login_username, appl_pam_user) != 0)) { + appl_pam_cleanup(); + appl_pam_user = NULL; + } + if (!appl_pam_started) { +#ifdef DEBUG + printf("Starting PAM up (service=\"%s\",user=\"%s\").\n", + service, login_username); +#endif + memset(&appl_pam_conv, 0, sizeof(appl_pam_conv)); + appl_pam_conv.conv = (interactive == appl_pam_interactive) ? + &appl_pam_interactive_converse : + &appl_pam_non_interactive_converse; + memset(&args, 0, sizeof(args)); + args.user = strdup(login_username); + args.password = password_if_not_interactive ? + strdup(password_if_not_interactive) : + NULL; + appl_pam_conv.appdata_ptr = &args; + ret = pam_start(service, login_username, + &appl_pam_conv, &appl_pamh); + if (ret == 0) { + if (tty != NULL) { +#ifdef DEBUG + printf("Setting PAM_TTY to \"%s\".\n", tty); +#endif + pam_set_item(appl_pamh, PAM_TTY, tty); + } + if (!exit_handler_registered && + (atexit(appl_pam_cleanup) != 0)) { + pam_end(appl_pamh, 0); + appl_pamh = NULL; + ret = -1; + } else { + appl_pam_started = 1; + appl_pam_starter = getpid(); + appl_pam_user = strdup(login_username); + exit_handler_registered = 1; + } + } + } + return ret; +} + +int +appl_pam_authenticate(const char *service, + enum appl_pam_interactive_flag interactive, + const char *login_username, + const char *password_if_not_interactive, + const char *tty) +{ + int ret; + ret = appl_pam_start(service, interactive, login_username, + password_if_not_interactive, tty); + if (ret == 0) { + ret = pam_authenticate(appl_pamh, 0); + } + return ret; +} + +int +appl_pam_acct_mgmt(const char *service, + enum appl_pam_interactive_flag interactive, + const char *login_username, + const char *password_if_not_interactive, + const char *tty) +{ + int ret; + appl_pam_pwchange_required = 0; + ret = appl_pam_start(service, interactive, login_username, + password_if_not_interactive, tty); + if (ret == 0) { +#ifdef DEBUG + printf("Calling pam_acct_mgmt().\n"); +#endif + ret = pam_acct_mgmt(appl_pamh, 0); + switch (ret) { + case PAM_IGNORE: + ret = 0; + break; + case PAM_NEW_AUTHTOK_REQD: + appl_pam_pwchange_required = 1; + ret = 0; + break; + default: + break; + } + } + return ret; +} + +int +appl_pam_requires_chauthtok(void) +{ + return appl_pam_pwchange_required; +} + +int +appl_pam_chauthtok(void) +{ + int ret = 0; + if (appl_pam_started) { +#ifdef DEBUG + printf("Changing PAM expired authentication token.\n"); +#endif + ret = pam_chauthtok(appl_pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + } + return ret; +} + +int +appl_pam_session_open(void) +{ + int ret = 0; + if (appl_pam_started) { +#ifdef DEBUG + printf("Opening PAM session.\n"); +#endif + ret = pam_open_session(appl_pamh, 0); + if (ret == 0) { + appl_pam_session_is_open = 1; + } + } + return ret; +} + +int +appl_pam_setenv(void) +{ + int ret = 0; +#ifdef HAVE_PAM_GETENVLIST +#ifdef HAVE_PUTENV + int i; + char **list; + if (appl_pam_started) { + list = pam_getenvlist(appl_pamh); + for (i = 0; ((list != NULL) && (list[i] != NULL)); i++) { +#ifdef DEBUG + printf("Setting \"%s\" in environment.\n", list[i]); +#endif + putenv(list[i]); + } + } +#endif +#endif + return ret; +} + +int +appl_pam_cred_init(void) +{ + int ret = 0; + if (appl_pam_started) { +#ifdef DEBUG + printf("Initializing PAM credentials.\n"); +#endif + ret = pam_setcred(appl_pamh, PAM_ESTABLISH_CRED); + if (ret == 0) { + appl_pam_creds_initialized = 1; + } + } + return ret; +} +#endif Index: src/appl/bsd/pam.h =================================================================== --- src/appl/bsd/pam.h (revision 0) +++ src/appl/bsd/pam.h (revision 0) @@ -0,0 +1,65 @@ +/* + * src/appl/bsd/pam.h + * + * Copyright 2007 Red Hat, Inc. + * + * All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Red Hat, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Convenience wrappers for using PAM. + */ + +#include +#ifdef HAVE_SECURITY_PAM_APPL_H +#include +#endif + +#define USE_PAM_CONFIGURATION_KEYWORD "use_pam" + +#ifdef USE_PAM +enum appl_pam_interactive_flag { + appl_pam_interactive, + appl_pam_noninteractive +}; +int appl_pam_enabled(krb5_context context, const char *section); +int appl_pam_authenticate(const char *service, enum appl_pam_interactive_flag, + const char *local_username, + const char *password_if_not_interactive, + const char *tty); +int appl_pam_acct_mgmt(const char *service, enum appl_pam_interactive_flag, + const char *local_username, + const char *password_if_not_interactive, + const char *tty); +int appl_pam_requires_chauthtok(void); +int appl_pam_chauthtok(void); +void appl_pam_set_forwarded_ccname(const char *ccname); +int appl_pam_session_open(void); +int appl_pam_setenv(void); +int appl_pam_cred_init(void); +void appl_pam_cleanup(void); +#endif Index: src/config/pre.in =================================================================== --- src/config/pre.in (revision 20303) +++ src/config/pre.in (working copy) @@ -185,6 +185,7 @@ SRVDEPLIBS = @SRVDEPLIBS@ CLNTLIBS = @CLNTLIBS@ CLNTDEPLIBS = @CLNTDEPLIBS@ +PAM_LIBS = @PAM_LIBS@ INSTALL=@INSTALL@ INSTALL_STRIP= Index: src/aclocal.m4 =================================================================== --- src/aclocal.m4 (revision 20303) +++ src/aclocal.m4 (working copy) @@ -1785,3 +1785,75 @@ ])) ])dnl dnl +dnl +dnl Use PAM instead of local crypt() compare for checking local passwords, +dnl and perform PAM account, session management, and password-changing where +dnl appropriate. +dnl +AC_DEFUN(KRB5_WITH_PAM,[ +AC_ARG_WITH(pam,[AC_HELP_STRING(--with-pam,[compile with PAM support])], + withpam="$withval",withpam=auto) +AC_ARG_WITH(pam-login-service,[AC_HELP_STRING(--with-login-service,[PAM service name for login ["login"]])], + withloginpamservice="$withval",withloginpamservice=login) +AC_ARG_WITH(pam-kshell-service,[AC_HELP_STRING(--with-kshell-service,[PAM service name for unencrypted rsh ["kshell"]])], + withkshellpamservice="$withval",withkshellpamservice=kshell) +AC_ARG_WITH(pam-ekshell-service,[AC_HELP_STRING(--with-ekshell-service,[PAM service name for encrypted rsh ["ekshell"]])], + withekshellpamservice="$withval",withekshellpamservice=ekshell) +AC_ARG_WITH(pam-ftp-service,[AC_HELP_STRING(--with-ftp-service,[PAM service name for ftpd ["gssftp"]])], + withftppamservice="$withval",withftppamservice=gssftp) +old_LIBS="$LIBS" +if test "$withpam" != no ; then + AC_MSG_RESULT([checking for PAM...]) + PAM_LIBS= + + AC_CHECK_HEADERS(security/pam_appl.h) + if test "x$ac_cv_header_security_pam_appl_h" != xyes ; then + if test "$withpam" = auto ; then + AC_MSG_RESULT([Unable to locate security/pam_appl.h, building without PAM support.]) + withpam=no + else + AC_MSG_ERROR([Unable to locate security/pam_appl.h.]) + fi + fi + + LIBS= + unset ac_cv_func_pam_start + AC_CHECK_FUNCS(putenv pam_start) + if test "x$ac_cv_func_pam_start" = xno ; then + unset ac_cv_func_pam_start + AC_CHECK_LIB(dl,dlopen) + AC_CHECK_FUNCS(pam_start) + if test "x$ac_cv_func_pam_start" = xno ; then + AC_CHECK_LIB(pam,pam_start) + unset ac_cv_func_pam_start + unset ac_cv_func_pam_getenvlist + AC_CHECK_FUNCS(pam_start pam_getenvlist) + if test "x$ac_cv_func_pam_start" = xyes ; then + PAM_LIBS="$LIBS" + else + if test "$withpam" = auto ; then + AC_MSG_RESULT([Unable to locate libpam, building without PAM support]) + withpam=no + else + AC_MSG_ERROR([Unable to locate libpam.]) + fi + fi + fi + fi + if test "$withpam" != no ; then + AC_MSG_RESULT([Building with PAM support]) + AC_DEFINE(USE_PAM,1,[Define if Kerberos-aware applications should support PAM.]) + AC_DEFINE_UNQUOTED(LOGIN_PAM_SERVICE,"$withloginpamservice", + [Define to the name of the PAM service name to be used by login.krb5.]) + AC_DEFINE_UNQUOTED(KSHELL_PAM_SERVICE,"$withkshellpamservice", + [Define to the name of the PAM service name to be used by rshd for unencrypted sessions.]) + AC_DEFINE_UNQUOTED(EKSHELL_PAM_SERVICE,"$withekshellpamservice", + [Define to the name of the PAM service name to be used by rshd for encrypted sessions.]) + AC_DEFINE_UNQUOTED(FTP_PAM_SERVICE,"$withftppamservice", + [Define to the name of the PAM service name to be used by ftpd.]) + PAM_LIBS="$LIBS" + fi +fi +LIBS="$old_LIBS" +AC_SUBST(PAM_LIBS) +])dnl