summaryrefslogtreecommitdiffstats
path: root/usr.sbin/gssd
diff options
context:
space:
mode:
authorobrien <obrien@FreeBSD.org>2013-02-08 16:10:16 +0000
committerobrien <obrien@FreeBSD.org>2013-02-08 16:10:16 +0000
commit3028e3f8aba938dfd0bf9fda987b8a72140b8027 (patch)
treeb2f038222ff8a70f687652441df00d2b564c8abe /usr.sbin/gssd
parent952a6d5a7cd3d3f9007acfa06805262fc04a105f (diff)
parent1d08d5f677c1dfa810e381073590adbae19cc69f (diff)
downloadFreeBSD-src-3028e3f8aba938dfd0bf9fda987b8a72140b8027.zip
FreeBSD-src-3028e3f8aba938dfd0bf9fda987b8a72140b8027.tar.gz
Sync with HEAD.
Diffstat (limited to 'usr.sbin/gssd')
-rw-r--r--usr.sbin/gssd/Makefile8
-rw-r--r--usr.sbin/gssd/gssd.825
-rw-r--r--usr.sbin/gssd/gssd.c340
3 files changed, 361 insertions, 12 deletions
diff --git a/usr.sbin/gssd/Makefile b/usr.sbin/gssd/Makefile
index 3fab810..3663bf9 100644
--- a/usr.sbin/gssd/Makefile
+++ b/usr.sbin/gssd/Makefile
@@ -1,5 +1,7 @@
# $FreeBSD$
+.include <bsd.own.mk>
+
PROG= gssd
MAN= gssd.8
SRCS= gssd.c gssd.h gssd_svc.c gssd_xdr.c gssd_prot.c
@@ -9,6 +11,12 @@ WARNS?= 1
DPADD= ${LIBGSSAPI}
LDADD= -lgssapi
+.if ${MK_KERBEROS_SUPPORT} != "no"
+DPADD+= ${LIBKRB5} ${LIBHX509} ${LIBASN1} ${LIBROKEN} ${LIBCOM_ERR} ${LIBCRYPT} ${LIBCRYPTO}
+LDADD+= -lkrb5 -lhx509 -lasn1 -lroken -lcom_err -lcrypt -lcrypto
+.else
+CFLAGS+= -DWITHOUT_KERBEROS
+.endif
CLEANFILES= gssd_svc.c gssd.h
diff --git a/usr.sbin/gssd/gssd.8 b/usr.sbin/gssd/gssd.8
index 273a9ff..73eb6e3 100644
--- a/usr.sbin/gssd/gssd.8
+++ b/usr.sbin/gssd/gssd.8
@@ -25,7 +25,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd November 5, 2008
+.Dd December 30, 2012
.Dt GSSD 8
.Os
.Sh NAME
@@ -34,6 +34,9 @@
.Sh SYNOPSIS
.Nm
.Op Fl d
+.Op Fl s Ar dir-list
+.Op Fl c Ar file-substring
+.Op Fl r Ar preferred-realm
.Sh DESCRIPTION
The
.Nm
@@ -46,6 +49,26 @@ Run in debug mode.
In this mode,
.Nm
will not fork when it starts.
+.It Fl s Ar dir-list
+Look for an appropriate credential cache file in this list of directories.
+The list should be full pathnames from root, separated by ':' characters.
+Usually this list will simply be "/tmp".
+Without this option,
+.Nm
+assumes that the credential cache file is called /tmp/krb5cc_<uid>,
+where <uid> is the effective uid for the RPC caller.
+.It Fl c Ar file-substring
+Set a file-substring for the credential cache file names.
+Only files with this substring embedded in their names will be
+selected as candidates when
+.Fl s
+has been specified.
+If not specified, it defaults to "krb5cc_".
+.It Fl r Ar preferred-realm
+Use Kerberos credentials for this realm when searching for
+credentials in directories specified with
+.Fl s .
+If not specified, the default Kerberos realm will be used.
.El
.Sh FILES
.Bl -tag -width ".Pa /etc/krb5.keytab" -compact
diff --git a/usr.sbin/gssd/gssd.c b/usr.sbin/gssd/gssd.c
index ba2805b..9777943 100644
--- a/usr.sbin/gssd/gssd.c
+++ b/usr.sbin/gssd/gssd.c
@@ -33,8 +33,13 @@ __FBSDID("$FreeBSD$");
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/queue.h>
+#include <sys/syslog.h>
#include <ctype.h>
+#include <dirent.h>
#include <err.h>
+#ifndef WITHOUT_KERBEROS
+#include <krb5.h>
+#endif
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -63,8 +68,12 @@ int gss_resource_count;
uint32_t gss_next_id;
uint32_t gss_start_time;
int debug_level;
+static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1];
+static char pref_realm[1024];
static void gssd_load_mech(void);
+static int find_ccache_file(const char *, uid_t, char *);
+static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *);
extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp);
extern int gssd_syscall(char *path);
@@ -81,14 +90,50 @@ main(int argc, char **argv)
int fd, oldmask, ch, debug;
SVCXPRT *xprt;
+ /*
+ * Initialize the credential cache file name substring and the
+ * search directory list.
+ */
+ strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring));
+ ccfile_dirlist[0] = '\0';
+ pref_realm[0] = '\0';
debug = 0;
- while ((ch = getopt(argc, argv, "d")) != -1) {
+ while ((ch = getopt(argc, argv, "ds:c:r:")) != -1) {
switch (ch) {
case 'd':
debug_level++;
break;
+ case 's':
+#ifndef WITHOUT_KERBEROS
+ /*
+ * Set the directory search list. This enables use of
+ * find_ccache_file() to search the directories for a
+ * suitable credentials cache file.
+ */
+ strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist));
+#else
+ errx(1, "This option not available when built"
+ " without MK_KERBEROS\n");
+#endif
+ break;
+ case 'c':
+ /*
+ * Specify a non-default credential cache file
+ * substring.
+ */
+ strlcpy(ccfile_substring, optarg,
+ sizeof(ccfile_substring));
+ break;
+ case 'r':
+ /*
+ * Set the preferred realm for the credential cache tgt.
+ */
+ strlcpy(pref_realm, optarg, sizeof(pref_realm));
+ break;
default:
- fprintf(stderr, "usage: %s [-d]\n", argv[0]);
+ fprintf(stderr,
+ "usage: %s [-d] [-s dir-list] [-c file-substring]"
+ " [-r preferred-realm]\n", argv[0]);
exit(1);
break;
}
@@ -106,21 +151,43 @@ main(int argc, char **argv)
sun.sun_len = SUN_LEN(&sun);
fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (!fd) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't create local gssd socket");
+ exit(1);
+ }
err(1, "Can't create local gssd socket");
}
oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't bind local gssd socket");
+ exit(1);
+ }
err(1, "Can't bind local gssd socket");
}
umask(oldmask);
if (listen(fd, SOMAXCONN) < 0) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR, "Can't listen on local gssd socket");
+ exit(1);
+ }
err(1, "Can't listen on local gssd socket");
}
xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE);
if (!xprt) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't create transport for local gssd socket");
+ exit(1);
+ }
err(1, "Can't create transport for local gssd socket");
}
if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) {
+ if (debug_level == 0) {
+ syslog(LOG_ERR,
+ "Can't register service for local gssd socket");
+ exit(1);
+ }
err(1, "Can't register service for local gssd socket");
}
@@ -244,13 +311,52 @@ gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *r
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
gss_name_t name = GSS_C_NO_NAME;
- char ccname[strlen("FILE:/tmp/krb5cc_") + 6 + 1];
+ char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
+ int gotone;
- snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
- (int) argp->uid);
+ memset(result, 0, sizeof(*result));
+ if (ccfile_dirlist[0] != '\0' && argp->cred == 0) {
+ /*
+ * For the "-s" case and no credentials provided as an
+ * argument, search the directory list for an appropriate
+ * credential cache file. If the search fails, return failure.
+ */
+ gotone = 0;
+ cp = ccfile_dirlist;
+ do {
+ cp2 = strchr(cp, ':');
+ if (cp2 != NULL)
+ *cp2 = '\0';
+ gotone = find_ccache_file(cp, argp->uid, ccname);
+ if (gotone != 0)
+ break;
+ if (cp2 != NULL)
+ *cp2++ = ':';
+ cp = cp2;
+ } while (cp != NULL && *cp != '\0');
+ if (gotone == 0) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ return (TRUE);
+ }
+ } else {
+ /*
+ * If there wasn't a "-s" option or the credentials have
+ * been provided as an argument, do it the old way.
+ * When credentials are provided, the uid should be root.
+ */
+ if (argp->cred != 0 && argp->uid != 0) {
+ if (debug_level == 0)
+ syslog(LOG_ERR, "gss_init_sec_context:"
+ " cred for non-root");
+ else
+ fprintf(stderr, "gss_init_sec_context:"
+ " cred for non-root\n");
+ }
+ snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
+ (int) argp->uid);
+ }
setenv("KRB5CCNAME", ccname, TRUE);
- memset(result, 0, sizeof(*result));
if (argp->cred) {
cred = gssd_find_resource(argp->cred);
if (!cred) {
@@ -273,7 +379,6 @@ gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *r
}
}
- memset(result, 0, sizeof(*result));
result->major_status = gss_init_sec_context(&result->minor_status,
cred, &ctx, name, argp->mech_type,
argp->req_flags, argp->time_req, argp->input_chan_bindings,
@@ -493,13 +598,53 @@ gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struc
{
gss_name_t desired_name = GSS_C_NO_NAME;
gss_cred_id_t cred;
- char ccname[strlen("FILE:/tmp/krb5cc_") + 6 + 1];
+ char ccname[PATH_MAX + 5 + 1], *cp, *cp2;
+ int gotone;
- snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
- (int) argp->uid);
+ memset(result, 0, sizeof(*result));
+ if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) {
+ /*
+ * For the "-s" case and no name provided as an
+ * argument, search the directory list for an appropriate
+ * credential cache file. If the search fails, return failure.
+ */
+ gotone = 0;
+ cp = ccfile_dirlist;
+ do {
+ cp2 = strchr(cp, ':');
+ if (cp2 != NULL)
+ *cp2 = '\0';
+ gotone = find_ccache_file(cp, argp->uid, ccname);
+ if (gotone != 0)
+ break;
+ if (cp2 != NULL)
+ *cp2++ = ':';
+ cp = cp2;
+ } while (cp != NULL && *cp != '\0');
+ if (gotone == 0) {
+ result->major_status = GSS_S_CREDENTIALS_EXPIRED;
+ return (TRUE);
+ }
+ } else {
+ /*
+ * If there wasn't a "-s" option or the name has
+ * been provided as an argument, do it the old way.
+ * When a name is provided, it will normally exist in the
+ * default keytab file and the uid will be root.
+ */
+ if (argp->desired_name != 0 && argp->uid != 0) {
+ if (debug_level == 0)
+ syslog(LOG_ERR, "gss_acquire_cred:"
+ " principal_name for non-root");
+ else
+ fprintf(stderr, "gss_acquire_cred:"
+ " principal_name for non-root\n");
+ }
+ snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d",
+ (int) argp->uid);
+ }
setenv("KRB5CCNAME", ccname, TRUE);
- memset(result, 0, sizeof(*result));
if (argp->desired_name) {
desired_name = gssd_find_resource(argp->desired_name);
if (!desired_name) {
@@ -608,3 +753,176 @@ gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
return (TRUE);
}
+
+/*
+ * Search a directory for the most likely candidate to be used as the
+ * credential cache for a uid. If successful, return 1 and fill the
+ * file's path id into "rpath". Otherwise, return 0.
+ */
+static int
+find_ccache_file(const char *dirpath, uid_t uid, char *rpath)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct stat sb;
+ time_t exptime, oexptime;
+ int gotone, len, rating, orating;
+ char namepath[PATH_MAX + 5 + 1];
+ char retpath[PATH_MAX + 5 + 1];
+
+ dirp = opendir(dirpath);
+ if (dirp == NULL)
+ return (0);
+ gotone = 0;
+ orating = 0;
+ oexptime = 0;
+ while ((dp = readdir(dirp)) != NULL) {
+ len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath,
+ dp->d_name);
+ if (len < sizeof(namepath) &&
+ strstr(dp->d_name, ccfile_substring) != NULL &&
+ lstat(namepath, &sb) >= 0 &&
+ sb.st_uid == uid &&
+ S_ISREG(sb.st_mode)) {
+ len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s",
+ dirpath, dp->d_name);
+ if (len < sizeof(namepath) &&
+ is_a_valid_tgt_cache(namepath, uid, &rating,
+ &exptime) != 0) {
+ if (gotone == 0 || rating > orating ||
+ (rating == orating && exptime > oexptime)) {
+ orating = rating;
+ oexptime = exptime;
+ strcpy(retpath, namepath);
+ gotone = 1;
+ }
+ }
+ }
+ }
+ closedir(dirp);
+ if (gotone != 0) {
+ strcpy(rpath, retpath);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Try to determine if the file is a valid tgt cache file.
+ * Check that the file has a valid tgt for a principal.
+ * If it does, return 1, otherwise return 0.
+ * It also returns a "rating" and the expiry time for the TGT, when found.
+ * This "rating" is higher based on heuristics that make it more
+ * likely to be the correct credential cache file to use. It can
+ * be used by the caller, along with expiry time, to select from
+ * multiple credential cache files.
+ */
+static int
+is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating,
+ time_t *retexptime)
+{
+#ifndef WITHOUT_KERBEROS
+ krb5_context context;
+ krb5_principal princ;
+ krb5_ccache ccache;
+ krb5_error_code retval;
+ krb5_cc_cursor curse;
+ krb5_creds krbcred;
+ int gotone, orating, rating, ret;
+ struct passwd *pw;
+ char *cp, *cp2, *pname;
+ time_t exptime;
+
+ /* Find a likely name for the uid principal. */
+ pw = getpwuid(uid);
+
+ /*
+ * Do a bunch of krb5 library stuff to try and determine if
+ * this file is a credentials cache with an appropriate TGT
+ * in it.
+ */
+ retval = krb5_init_context(&context);
+ if (retval != 0)
+ return (0);
+ retval = krb5_cc_resolve(context, filepath, &ccache);
+ if (retval != 0) {
+ krb5_free_context(context);
+ return (0);
+ }
+ ret = 0;
+ orating = 0;
+ exptime = 0;
+ retval = krb5_cc_start_seq_get(context, ccache, &curse);
+ if (retval == 0) {
+ while ((retval = krb5_cc_next_cred(context, ccache, &curse,
+ &krbcred)) == 0) {
+ gotone = 0;
+ rating = 0;
+ retval = krb5_unparse_name(context, krbcred.server,
+ &pname);
+ if (retval == 0) {
+ cp = strchr(pname, '/');
+ if (cp != NULL) {
+ *cp++ = '\0';
+ if (strcmp(pname, "krbtgt") == 0 &&
+ krbcred.times.endtime > time(NULL)
+ ) {
+ gotone = 1;
+ /*
+ * Test to see if this is a
+ * tgt for cross-realm auth.
+ * Rate it higher, if it is not.
+ */
+ cp2 = strchr(cp, '@');
+ if (cp2 != NULL) {
+ *cp2++ = '\0';
+ if (strcmp(cp, cp2) ==
+ 0)
+ rating++;
+ }
+ }
+ }
+ free(pname);
+ }
+ if (gotone != 0) {
+ retval = krb5_unparse_name(context,
+ krbcred.client, &pname);
+ if (retval == 0) {
+ cp = strchr(pname, '@');
+ if (cp != NULL) {
+ *cp++ = '\0';
+ if (pw != NULL && strcmp(pname,
+ pw->pw_name) == 0)
+ rating++;
+ if (strchr(pname, '/') == NULL)
+ rating++;
+ if (pref_realm[0] != '\0' &&
+ strcmp(cp, pref_realm) == 0)
+ rating++;
+ }
+ }
+ free(pname);
+ if (rating > orating) {
+ orating = rating;
+ exptime = krbcred.times.endtime;
+ } else if (rating == orating &&
+ krbcred.times.endtime > exptime)
+ exptime = krbcred.times.endtime;
+ ret = 1;
+ }
+ krb5_free_cred_contents(context, &krbcred);
+ }
+ krb5_cc_end_seq_get(context, ccache, &curse);
+ }
+ krb5_cc_close(context, ccache);
+ krb5_free_context(context);
+ if (ret != 0) {
+ *retrating = orating;
+ *retexptime = exptime;
+ }
+ return (ret);
+#else /* WITHOUT_KERBEROS */
+ return (0);
+#endif /* !WITHOUT_KERBEROS */
+}
+
OpenPOWER on IntegriCloud