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