Kerberos 5 and SU

"Jacques A. Vidrine" <[email protected]>
Newsgroups gmane.os.freebsd.devel.audit
Message-ID <[email protected]>
Hello,

Attached are two similar implementations of Kerberos 5 support for
su(1).  Why two?

 1. pam_ksu --- This is the long-term solution, but is only 
                appropriate for -CURRENT.  -STABLE does not have
                OpenPAM, and more importantly does not have a PAMified
                SU [1].

 2. su ---      This is for -STABLE only.  Support was added in
                parallel to the existing Kerberos 4 support.  This has
                made the code even more #ifdef grotty, but it will not
                be maintained after 4.x anyway.  The code path is
                still straightforward.

FYI, the `get_target_principal' functions in each correspond, and the
`auth_krb5' function in pam_ksu corresponds with the `kerberos5'
function in su(1).

Please tear it up :-)  I'm gone for the weekend.

Cheers,
-- 
Jacques A. Vidrine <[email protected]>                 http://www.nectar.cc/
NTT/Verio SME          .     FreeBSD UNIX     .       Heimdal Kerberos
[email protected]     .  [email protected]  .          [email protected]

[1] DES reports that he currently has no plans to backport the SU
    PAMificiation.
su.patch (text/plain, 11 KB)
Index: Makefile
===================================================================
RCS file: /home/ncvs/src/usr.bin/su/Makefile,v
retrieving revision 1.29
diff -u -r1.29 Makefile
--- Makefile	2000/02/24 21:06:21	1.29
+++ Makefile	2002/05/16 20:42:46
@@ -14,10 +14,19 @@
 CFLAGS+= -Wall
 
 .if exists(${DESTDIR}${LIBDIR}/libkrb.a) && defined(MAKE_KERBEROS4)
-CFLAGS+=-DKERBEROS
+CFLAGS+=-DKERBEROS4
 DPADD+=	${LIBKRB} ${LIBCRYPTO} ${LIBCOM_ERR}
 LDADD+=	-lkrb -lcrypto -lcom_err
 DISTRIBUTION=	krb4
+.endif
+
+.if exists(${DESTDIR}${LIBDIR}/libkrb5.a) && defined(MAKE_KERBEROS5)
+CFLAGS+=-DKERBEROS5
+DPADD+=	${LIBKRB5} ${LIBASN1} ${LIBCRYPTO} ${LIBCRYPT} ${LIBCOM_ERR} \
+	${LIBROKEN}
+LDADD+=	-lkrb5 -lasn1 -lcrypto -lcrypt -lcom_err \
+	-L${.OBJDIR}/../../../../kerberos5/lib/libroken -lroken
+DISTRIBUTION=	krb5
 .endif
 
 BINMODE=4555
Index: su.c
===================================================================
RCS file: /home/ncvs/src/usr.bin/su/su.c,v
retrieving revision 1.34.2.3
diff -u -r1.34.2.3 su.c
--- su.c	2001/08/17 15:44:42	1.34.2.3
+++ su.c	2002/05/16 20:42:46
@@ -69,28 +69,42 @@
 #include <skey.h>
 #endif
 
-#ifdef KERBEROS
+#ifdef KERBEROS5
+#include <com_err.h>
+#include <krb5.h>
+
+static long get_target_principal(krb5_context context, const char *user,
+    const char *ruser, char **su, krb5_principal *suprinc);
+static long kerberos5(krb5_context context, krb5_principal suprinc,
+    const char *pass);
+
+int use_kerberos5 = 1;
+#endif
+
+#ifdef KERBEROS4
 #include <openssl/des.h>
 #include <krb.h>
 #include <netdb.h>
 
-#ifdef LOGIN_CAP
-#define	ARGSTR	"-Kflmc:"
-#else
-#define	ARGSTR	"-Kflm"
-#endif
 
-static int kerberos(char *username, char *user, int uid, char *pword);
+static int kerberos4(char *username, char *user, int uid, char *pword);
 static int koktologin(char *name, char *toname);
+
+int use_kerberos4 = 1;
+#endif /* KERBEROS4 */
 
-int use_kerberos = 1;
-#else /* !KERBEROS */
 #ifdef LOGIN_CAP
-#define	ARGSTR	"-flmc:"
+#define LOGIN_CAP_ARG(x) x
 #else
-#define	ARGSTR	"-flm"
+#define LOGIN_CAP_ARG(x)
 #endif
-#endif /* KERBEROS */
+#if defined(KERBEROS4) || defined(KERBEROS5)
+#define KERBEROS_ARG(x) x
+#else
+#define KERBEROS_ARG(x)
+#endif
+#define COMMON_ARG(x) x
+#define ARGSTR	"-" COMMON_ARG("flm") LOGIN_CAP_ARG("c:") KERBEROS_ARG("K")
 
 char   *ontty __P((void));
 int	chshell __P((char *));
@@ -118,9 +132,14 @@
 	char *class=NULL;
 	int setwhat;
 #endif
-#ifdef KERBEROS
+#if defined(KERBEROS4) || defined(KERBEROS5)
 	char *k;
 #endif
+#ifdef KERBEROS5
+	char *suname, *ccname;
+	krb5_context context;
+	krb5_principal suprinc;
+#endif
 	char shellbuf[MAXPATHLEN];
 
 #ifdef WHEELSU
@@ -130,9 +149,14 @@
 	user = "root";
 	while((ch = getopt(argc, argv, ARGSTR)) != -1) 
 		switch((char)ch) {
-#ifdef KERBEROS
+#if defined(KERBEROS4) || defined(KERBEROS5)
 		case 'K':
-			use_kerberos = 0;
+#ifdef KERBEROS4
+			use_kerberos4 = 0;
+#endif
+#ifdef KERBEROS5
+			use_kerberos5 = 0;
+#endif
 			break;
 #endif
 		case 'f':
@@ -179,10 +203,22 @@
 
 	argv += optind;
 
-#ifdef KERBEROS
+#if defined(KERBEROS4) || defined(KERBEROS5)
 	k = auth_getval("auth_list");
-	if (k && !strstr(k, "kerberos"))
-	    use_kerberos = 0;
+	if (k && !strstr(k, "kerberos")) {
+#ifdef KERBEROS4
+	    use_kerberos4 = 0;
+#endif
+#ifdef KERBEROS5
+	    use_kerberos5 = 0;
+#endif
+	}
+#endif
+#ifdef KERBEROS5
+	suname = NULL;
+	suprinc = NULL;
+	if (krb5_init_context(&context) != 0)
+		use_kerberos5 = 0;
 #endif
 	errno = 0;
 	prio = getpriority(PRIO_PROCESS, 0);
@@ -235,13 +271,23 @@
 #endif /* WHEELSU */
 
 	if (ruid) {
-#ifdef KERBEROS
-		if (use_kerberos && koktologin(username, user)
+#ifdef KERBEROS4
+		if (use_kerberos4 && koktologin(username, user)
 		    && !pwd->pw_uid) {
-			warnx("kerberos: not in %s's ACL.", user);
-			use_kerberos = 0;
+			warnx("kerberos4: not in %s's ACL.", user);
+			use_kerberos4 = 0;
 		}
 #endif
+#ifdef KERBEROS5
+		if (use_kerberos5) {
+			if (!(get_target_principal(context, user, username,
+			    &suname, &suprinc) == 0 &&
+			    krb5_kuserok(context, suprinc, user))) {
+				warnx("kerberos5: not in %s's ACL.", user);
+				use_kerberos5 = 0;
+			}
+		}
+#endif
 		{
 			/*
 			 * Only allow those with pw_gid==0 or those listed in
@@ -288,15 +334,23 @@
 			p = getpass("Password:");
 			if (strcmp(pwd->pw_passwd, crypt(p, pwd->pw_passwd))) {
 #endif
-#ifdef KERBEROS
-	    			if (!use_kerberos || (use_kerberos && kerberos(username, user, pwd->pw_uid, p)))
-#endif
-					{
-					fprintf(stderr, "Sorry\n");
-					syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s%s", username, user, ontty());
-					exit(1);
-				}
+#ifdef KERBEROS4
+				if (use_kerberos4 && kerberos4(username, user,
+				    pwd->pw_uid, p) == 0)
+					goto authok;
+#endif
+#ifdef KERBEROS5
+				if (use_kerberos5 && kerberos5(context,
+				    suprinc, p) == 0)
+					goto authok;
+#endif
+				fprintf(stderr, "Sorry\n");
+				syslog(LOG_AUTH|LOG_WARNING,
+				    "BAD SU %s to %s%s", username, user,
+				    ontty());
+				exit(1);
 			}
+		authok:
 #ifdef WHEELSU
 			if (iswheelsu) {
 				pwd = getpwnam(user);
@@ -361,9 +415,12 @@
 	if (!asme) {
 		if (asthem) {
 			p = getenv("TERM");
-#ifdef KERBEROS
+#ifdef KERBEROS4
 			k = getenv("KRBTKFILE");
 #endif
+#ifdef KERBEROS5
+			ccname = getenv("KRB5CCNAME");
+#endif
 			if ((cleanenv = calloc(20, sizeof(char*))) == NULL)
 				errx(1, "calloc");
 			cleanenv[0] = NULL;
@@ -376,10 +433,14 @@
 #endif
 			if (p)
 				(void)setenv("TERM", p, 1);
-#ifdef KERBEROS
+#ifdef KERBEROS4
 			if (k)
 				(void)setenv("KRBTKFILE", k, 1);
 #endif
+#ifdef KERBEROS5
+			if (ccname)
+				(void)setenv("KRB5CCNAME", ccname, 1);
+#endif
 			if (chdir(pwd->pw_dir) < 0)
 				errx(1, "no directory");
 		}
@@ -413,12 +474,8 @@
 static void
 usage()
 {
-	(void)fprintf(stderr, "usage: su [-] %s%s[login [args]]\n",
-#ifdef KERBEROS
-	    "[-Kflm] ",
-#else
-	    "[-flm] ",
-#endif
+	(void)fprintf(stderr, "usage: su [-] [-%s] %s[login [args]]\n",
+	    KERBEROS_ARG("K") COMMON_ARG("flm"),
 #ifdef LOGIN_CAP
 	    "[-c class] "
 #else
@@ -454,10 +511,146 @@
 		snprintf(buf, sizeof(buf), " on %s", p);
 	return (buf);
 }
+
+#ifdef KERBEROS5
+const char superuser[] = "root";
+
+/* Authenticate using Kerberos 5.
+ *   context -- An initialized krb5_context.
+ *   suprinc -- The target krb5_principal.
+ *   pass    -- The user's password.
+ * Note that a valid keytab in the default location with a host entry
+ * must be available.
+ * Returns 0 if authentication was successful, or a com_err error code if
+ * it was not.
+ */
+static long
+kerberos5(krb5_context context, krb5_principal suprinc, const char *pass)
+{
+	krb5_creds	 creds;
+	krb5_get_init_creds_opt gic_opt;
+	krb5_verify_init_creds_opt vic_opt;
+	long		 rv;
+
+	krb5_get_init_creds_opt_init(&gic_opt);
+	krb5_verify_init_creds_opt_init(&vic_opt);
+	rv = krb5_get_init_creds_password(context, &creds, suprinc,
+	    pass, NULL, NULL, 0, NULL, &gic_opt);
+	if (rv != 0) {
+		syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s",
+		    error_message(rv)); /* XXX */
+		return (rv);
+	}
+	krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
+	rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
+	    &vic_opt);
+	krb5_free_cred_contents(context, &creds);
+	if (rv != 0) {
+		syslog(LOG_NOTICE|LOG_AUTH, "BAD Kerberos5 SU: %s",
+		    error_message(rv)); /* XXX */
+		return (rv);
+	}
+	return (0);
+}
+
+/* Determine the target principal given the current user and the target user.
+ *   context -- An initialized krb5_context.
+ *   user    -- The target username.
+ *   ruser   -- The current username.
+ *   su      -- (out) The target principal name.
+ *   suprinc -- (out) The target krb5_principal.
+ * When the target user is `root', the target principal will be a `root
+ * instance', e.g. `luser/[email protected]'.  Otherwise, the target principal
+ * will simply be the current user's default principal name.  Note that
+ * in any case, if KRB5CCNAME is set and a credentials cache exists, the
+ * principal name found there will be the `starting point', rather than
+ * the ruser parameter.
+ *
+ * Returns 0 for success, or a com_err error code on failure.
+ */
+static long
+get_target_principal(krb5_context context, const char *user, const char *ruser,
+    char **su, krb5_principal *suprinc)
+{
+	krb5_principal	 princ;
+	krb5_ccache	 ccache;
+	char		*unparsed, *p;
+	long		 rv;
+	uid_t		 euid;
+
+	*suprinc = NULL;
+	princ = NULL;
+	/* Unless KRB5CCNAME was explicitly set, we won't really be able
+	 * to look at the credentials cache since krb5_cc_default will
+	 * look at getuid().
+	 */
+	if (getenv("KRB5CCNAME") != NULL) {
+		/* Lower privs while messing about with the credentials
+		 * cache.
+		 */
+		euid = geteuid();
+		rv = seteuid(getuid());
+		if (rv != 0)
+			return (errno);
+		rv = krb5_cc_default(context, &ccache);
+		if (rv == 0) {
+			rv = krb5_cc_get_principal(context, ccache, &princ);
+			krb5_cc_close(context, ccache);
+			if (rv != 0)
+				princ = NULL; /* just to be safe */
+		}
+		rv = seteuid(euid);
+		if (rv != 0)
+			return (errno);
+	}
+	if (princ == NULL) {
+		rv = krb5_make_principal(context, &princ, NULL, ruser, NULL);
+		if (rv != 0) {
+			warnx("Could not determine default principal name.");
+			return (rv);
+		}
+	}
+	/* Now that we have some principal, if the target account is
+	 * `root', then transform it into a `root' instance, e.g.
+	 * `[email protected]' -> `user/[email protected]'.
+	 */
+	rv = krb5_unparse_name(context, princ, &unparsed);
+	krb5_free_principal(context, princ);
+	if (rv != 0) {
+		warnx("krb5_unparse_name: %s", error_message(rv));
+		return (rv);
+	}
+	if (strcmp(user, superuser) == 0) {
+		p = strrchr(unparsed, '@');
+		if (p == NULL) {
+			warnx("malformed principal name `%s'", unparsed);
+			free(unparsed);
+			return (rv);
+		}
+		*p++ = '\0';
+		*su = NULL;
+		(void)asprintf(su, "%s/%s@%s", unparsed, superuser, p);
+		free(unparsed);
+	} else 
+		*su = unparsed;
+
+	if (*su == NULL)
+		return errno;
+	rv = krb5_parse_name(context, *su, &princ);
+	if (rv != 0) {
+		warnx("krb5_parse_name `%s': %s", *su, error_message(rv));
+		free(*su);
+		return (rv);
+	}
+	*suprinc = princ;
+	return 0;
+}
+
+#endif
 
-#ifdef KERBEROS
+#ifdef KERBEROS4
 int
-kerberos(username, user, uid, pword)
+kerberos4(username, user, uid, pword)
 	char *username, *user;
 	int uid;
 	char *pword;
@@ -503,14 +696,14 @@
 
 	if (kerno != KSUCCESS) {
 		if (kerno == KDC_PR_UNKNOWN) {
-			warnx("kerberos: principal unknown: %s.%s@%s",
+			warnx("kerberos4: principal unknown: %s.%s@%s",
 				(uid == 0 ? username : user),
 				(uid == 0 ? "root" : ""), lrealm);
 			return (1);
 		}
-		warnx("kerberos: unable to su: %s", krb_err_txt[kerno]);
+		warnx("kerberos4: unable to su: %s", krb_err_txt[kerno]);
 		syslog(LOG_NOTICE|LOG_AUTH,
-		    "BAD Kerberos SU: %s to %s%s: %s",
+		    "BAD Kerberos4 SU: %s to %s%s: %s",
 		    username, user, ontty(), krb_err_txt[kerno]);
 		return (1);
 	}
@@ -556,7 +749,7 @@
 
 		if ((kerno = krb_rd_req(&ticket, "rcmd", savehost, faddr,
 		    &authdata, "")) != KSUCCESS) {
-			warnx("kerberos: unable to verify rcmd ticket: %s\n",
+			warnx("kerberos4: unable to verify rcmd ticket: %s\n",
 			    krb_err_txt[kerno]);
 			syslog(LOG_NOTICE|LOG_AUTH,
 			    "failed su: %s to %s%s: %s", username,
pam_ksu.shar (application/x-shar, 14.7 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.