Date: Wed, 17 Jan 2001 16:47:35 -0500 (EST) From: "David J. MacKenzie" <djm@web.us.uu.net> To: freebsd-security@freebsd.org Cc: djm@web.us.uu.net Subject: full PAM support for login, rshd, and su Message-ID: <20010117214735.E7DAD46BC@dagger.web.us.uu.net>
next in thread | raw e-mail | index | archive | help
The FreeBSD (4.2-STABLE) login has only partial PAM support; it supports PAM authentication, but not account management or sessions. I want to use a locally written PAM module that restricts logins based on a DB file lookup, but the account management function is necessary for that. The FreeBSD rshd and su don't have any PAM support. Below are patches to add full PAM support to those programs. I haven't tackled adding PAM to the FreeBSD ftpd so far, because I use proftpd which already has it. I haven't looked at Heimdal or krb4, as the relevant utilities from them don't seem to be installed on FreeBSD, and my company has standardized on MIT krb5. The PAM_FAIL_CHECK and PAM_END macros in su.c came from the util-linux package's PAM patches to the BSD login.c, which are covered by the BSD copyright (no GPL). If you don't like that for some reason, it would be straightforward for someone to rewrite that error checking. I simplified the "cleanenv" code in su.c based on BSDI's version, which is covered by the same copyright as the FreeBSD version. I've also added PAM support (for account management and sessions) to the MIT krb5 ksu, login.krb5, and kshd, which I'll be submitting to krbdev@mit.edu soon. I think if you're going to ship PAM, you should actually use it. The OpenSSH shipped with FreeBSD (as of 4.2-STABLE) is also missing the USE_PAM support that's in the portable OpenSSH release. I highly recommend importing that code into your source tree. I'm going to have to do so in my tree. --- ./usr.bin/login/login.c 2000/08/08 03:12:59 1.1 +++ ./usr.bin/login/login.c 2001/01/16 23:38:50 @@ -81,6 +81,7 @@ #ifndef NO_PAM #include <security/pam_appl.h> #include <security/pam_misc.h> +#include <sys/wait.h> #endif #include "pathnames.h" @@ -106,6 +107,7 @@ #ifndef NO_PAM static int auth_pam __P((void)); +pam_handle_t *pamh = NULL; #endif static int auth_traditional __P((void)); extern void login __P((struct utmp *)); @@ -150,6 +152,10 @@ char tname[sizeof(_PATH_TTY) + 10]; char *shell = NULL; login_cap_t *lc = NULL; +#ifndef NO_PAM + pid_t pid; + int e; +#endif /* NO_PAM */ (void)signal(SIGQUIT, SIG_IGN); (void)signal(SIGINT, SIG_IGN); @@ -548,6 +554,35 @@ if (!pflag) environ = envinit; +#ifndef NO_PAM + if (pamh) { + /* + * We must fork() before setuid() because we need to call + * pam_close_session() as root. + */ + pid = fork(); + if (pid < 0) { + err(1, "fork"); + if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + exit(0); + } else if (pid) { + /* parent - wait for child to finish, then cleanup session */ + wait(NULL); + if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + exit(0); + } else { + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + } +#endif /* NO_PAM */ + /* * We don't need to be root anymore, so * set the user and session context @@ -562,6 +597,17 @@ exit(1); } +#ifndef NO_PAM + if (pamh) { + const char * const *env = (const char * const *)pam_getenvlist(pamh); + int i; + if (env != NULL) { + for (i=0; env[i]; i++) + putenv(env[i]); + } + } +#endif /* NO_PAM */ + (void)setenv("SHELL", pwd->pw_shell, 1); (void)setenv("HOME", pwd->pw_dir, 1); if (term != NULL && *term != '\0') @@ -663,7 +709,6 @@ static int auth_pam() { - pam_handle_t *pamh = NULL; const char *tmpl_user; const void *item; int rval; @@ -724,13 +769,36 @@ break; default: - syslog(LOG_ERR, "auth_pam: %s", pam_strerror(pamh, e)); + syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); rval = -1; break; } - if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { - syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); - rval = -1; + + if (rval != -1) { + e = pam_acct_mgmt(pamh, 0); + if (e == PAM_NEW_AUTHTOK_REQD) { + e = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, e)); + rval = -1; + } + } else if (e != PAM_SUCCESS) { + rval = 1; + } else if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e)); + rval = -1; + } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); + rval = -1; + pam_close_session(pamh, 0); + } + } + + if (rval == -1) { + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; } return rval; } @@ -745,7 +813,7 @@ /* * Allow for authentication style and/or kerberos instance - * */ + */ #define NBUFSIZ UT_NAMESIZE + 64 --- ./usr.bin/su/Makefile 2001/01/16 21:33:47 1.1 +++ ./usr.bin/su/Makefile 2001/01/16 21:41:43 @@ -4,9 +4,9 @@ PROG= su SRCS= su.c -COPTS+= -DLOGIN_CAP -DSKEY +COPTS+= -DLOGIN_CAP -DSKEY -DUSE_PAM DPADD= ${LIBUTIL} ${LIBSKEY} ${LIBMD} ${LIBCRYPT} -LDADD= -lutil -lskey -lmd -lcrypt +LDADD= -lutil -lskey -lmd -lcrypt -lpam .if defined(WHEELSU) COPTS+= -DWHEELSU --- ./usr.bin/su/su.c 2000/02/24 21:06:21 1.1 +++ ./usr.bin/su/su.c 2001/01/16 23:29:48 @@ -65,6 +65,20 @@ #include <login_cap.h> #endif +#ifdef USE_PAM +#include <security/pam_appl.h> +#include <security/pam_misc.h> +#include <signal.h> +#include <sys/wait.h> +#define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \ + fprintf(stderr,"su: PAM error: %s\n",pam_strerror(pamh, retcode)); \ + syslog(LOG_ERR,"PAM error: %s",pam_strerror(pamh, retcode)); \ + pam_end(pamh, retcode); exit(1); \ + } +#define PAM_END { retcode = pam_close_session(pamh,0); \ + pam_end(pamh,retcode); } +#endif /* USE_PAM */ + #ifdef SKEY #include <skey.h> #endif @@ -107,7 +121,7 @@ char *targetpass; int iswheelsu; #endif /* WHEELSU */ - char *p, **g, *user, *shell=NULL, *username, **cleanenv, **nargv, **np; + char *p, **g, *user, *shell=NULL, *username, *cleanenv = NULL, **nargv, **np; struct group *gr; uid_t ruid; gid_t gid; @@ -118,6 +132,15 @@ char *class=NULL; int setwhat; #endif +#ifdef USE_PAM + int retcode; + pam_handle_t *pamh = NULL; + struct pam_conv conv = { misc_conv, NULL }; + char myhost[MAXHOSTNAMELEN + 1], *mytty; + int statusp=0; + int child_pid, child_pgrp, ret_pid; + const char * const *env; +#endif /* USE_PAM */ #ifdef KERBEROS char *k; #endif @@ -230,11 +253,24 @@ } #endif +#ifdef USE_PAM + retcode = pam_start("su", user, &conv, &pamh); + PAM_FAIL_CHECK; +#else /* !USE_PAM */ #ifdef WHEELSU targetpass = strdup(pwd->pw_passwd); #endif /* WHEELSU */ +#endif /* USE_PAM */ if (ruid) { +#ifdef USE_PAM + retcode = pam_authenticate(pamh, 0); + PAM_FAIL_CHECK; + retcode = pam_acct_mgmt(pamh, 0); + if (retcode == PAM_NEW_AUTHTOK_REQD) + retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + PAM_FAIL_CHECK; +#else /* !USE_PAM */ #ifdef KERBEROS if (use_kerberos && koktologin(username, user) && !pwd->pw_uid) { @@ -280,11 +316,12 @@ #ifdef WHEELSU || (iswheelsu && !strcmp(targetpass, crypt(p,targetpass))) #endif /* WHEELSU */ - )) { -#else + )) +#else /* !SKEY */ p = getpass("Password:"); - if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) { -#endif + if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) +#endif /* SKEY */ + { #ifdef KERBEROS if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p))) #endif @@ -307,6 +344,7 @@ user, ontty()); exit(1); } +#endif /* USE_PAM */ } if (asme) { @@ -334,6 +372,60 @@ (void)setpriority(PRIO_PROCESS, 0, prio); +#ifdef USE_PAM + gethostname(myhost, sizeof(myhost)); + retcode = pam_set_item(pamh, PAM_RHOST, myhost); + PAM_FAIL_CHECK; + + mytty = ttyname(STDERR_FILENO); + if (!mytty) + mytty = "tty"; + retcode = pam_set_item(pamh, PAM_TTY, mytty); + PAM_FAIL_CHECK; + + retcode = pam_open_session(pamh, 0); + PAM_FAIL_CHECK; + + retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); + PAM_FAIL_CHECK; + + env = (const char * const *)pam_getenvlist(pamh); + if (env != NULL) { + for (i=0; env[i]; i++) + putenv(env[i]); + } + + /* + * We must fork() before setuid() because we need to call + * pam_close_session() as root. + */ + + statusp = 1; + switch ((child_pid = fork())) { + default: + while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { + if (WIFSTOPPED(statusp)) { + child_pgrp = tcgetpgrp(1); + kill(getpid(), SIGSTOP); + tcsetpgrp(1, child_pgrp); + kill(child_pid, SIGCONT); + statusp = 1; + continue; + } + break; + } + if (ret_pid == -1) + err(1, "waitpid"); + PAM_END; + exit(statusp); + case -1: + err(1, "fork"); + PAM_END; + exit (1); + case 0: + pam_end(pamh, retcode); +#endif /* USE_PAM */ + #ifdef LOGIN_CAP /* Set everything now except the environment & umask */ setwhat = LOGIN_SETUSER|LOGIN_SETGROUP|LOGIN_SETRESOURCES|LOGIN_SETPRIORITY; @@ -361,10 +453,7 @@ #ifdef KERBEROS k = getenv("KRBTKFILE"); #endif - if ((cleanenv = calloc(20, sizeof(char*))) == NULL) - errx(1, "calloc"); - cleanenv[0] = NULL; - environ = cleanenv; + environ = &cleanenv; #ifdef LOGIN_CAP /* set the su'd user's environment & umask */ setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH|LOGIN_SETUMASK|LOGIN_SETENV); @@ -403,6 +492,9 @@ execv(shell, np); err(1, "%s", shell); +#ifdef USE_PAM + } +#endif /* USE_PAM */ } static void --- ./libexec/rshd/Makefile 2001/01/17 00:04:57 1.1 +++ ./libexec/rshd/Makefile 2001/01/17 00:05:11 @@ -8,9 +8,9 @@ #CFLAGS+= -DCRYPT # For login_cap handling -CFLAGS+=-DLOGIN_CAP -Wall +CFLAGS+=-DLOGIN_CAP -DUSE_PAM -Wall DPADD+= ${LIBUTIL} -LDADD+= -lutil +LDADD+= -lutil -lpam # IPv6 support CFLAGS+= -DINET6 --- ./libexec/rshd/rshd.c 2000/11/12 07:00:38 1.1 +++ ./libexec/rshd/rshd.c 2001/01/17 00:40:07 @@ -80,6 +80,12 @@ #include <login_cap.h> #endif +#ifdef USE_PAM +#include <security/pam_appl.h> +#include <security/pam_misc.h> +static pam_handle_t *pamh; +#endif /* USE_PAM */ + /* wrapper for KAME-special getnameinfo() */ #ifndef NI_WITHSCOPEID #define NI_WITHSCOPEID 0 @@ -219,6 +225,10 @@ #ifdef LOGIN_CAP login_cap_t *lc; #endif +#ifdef USE_PAM + static struct pam_conv conv = { misc_conv, NULL }; + int retcode; +#endif /* USE_PAM */ (void) signal(SIGINT, SIG_DFL); (void) signal(SIGQUIT, SIG_DFL); @@ -349,7 +359,8 @@ remuser, fromhost, locuser, cmdbuf); if (errorstr == NULL) errorstr = "Login incorrect.\n"; - goto fail; + error(errorstr, fromhost); + exit(1); } #ifdef LOGIN_CAP lc = login_getpwclass(pwd); @@ -377,6 +388,36 @@ pwd->pw_dir = "/"; } +#ifdef USE_PAM + retcode = pam_start("rsh", locuser, &conv, &pamh); + if (retcode != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_start: %s\n", pam_strerror(pamh, retcode)); + exit(1); + } + pam_set_item (pamh, PAM_RUSER, remuser); + pam_set_item (pamh, PAM_RHOST, fromhost); + pam_set_item (pamh, PAM_TTY, "tty"); + + retcode = pam_authenticate(pamh, 0); + if (retcode == PAM_SUCCESS) { + retcode = pam_acct_mgmt(pamh, 0); + } + if (retcode == PAM_SUCCESS) { + retcode = pam_open_session(pamh,0); + } + if (retcode == PAM_SUCCESS) { + retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); + if (retcode != PAM_SUCCESS) + pam_close_session(pamh, 0); + } + if (retcode != PAM_SUCCESS) { + pam_end(pamh, retcode); + syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: permission denied (%s). cmd='%.80s'", + remuser, fromhost, locuser, pam_strerror(pamh, retcode), cmdbuf); + error("Login incorrect.\n"); + exit(1); + } +#else /* !USE_PAM */ if (errorstr || (pwd->pw_expire && time(NULL) >= pwd->pw_expire) || iruserok_sa(fromp, fromp->su_len, pwd->pw_uid == 0, @@ -390,7 +431,6 @@ syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: permission denied. cmd='%.80s'", remuser, fromhost, locuser, cmdbuf); -fail: if (errorstr == NULL) errorstr = "Login incorrect.\n"; error(errorstr, fromhost); @@ -401,6 +441,8 @@ error("Logins currently disabled.\n"); exit(1); } +#endif /* USE_PAM */ + #ifdef LOGIN_CAP if (lc != NULL && fromp->su_family == AF_INET) { /*XXX*/ char remote_ip[MAXHOSTNAMELEN]; @@ -569,6 +611,10 @@ (doencrypt && FD_ISSET(pv1[0], &readfrom)) || #endif FD_ISSET(pv[0], &readfrom)); +#ifdef USE_PAM + pam_close_session(pamh, 0); + pam_end(pamh, PAM_SUCCESS); +#endif /* USE_PAM */ exit(0); } setpgrp(0, getpid()); To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-security" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20010117214735.E7DAD46BC>