From owner-svn-src-stable-9@FreeBSD.ORG Sun Jan 6 01:41:15 2013 Return-Path: Delivered-To: svn-src-stable-9@freebsd.org Received: from mx1.freebsd.org (mx1.FreeBSD.org [8.8.178.115]) by hub.freebsd.org (Postfix) with ESMTP id 903201B9; Sun, 6 Jan 2013 01:41:15 +0000 (UTC) (envelope-from rmacklem@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) by mx1.freebsd.org (Postfix) with ESMTP id 65C8CD9B; Sun, 6 Jan 2013 01:41:15 +0000 (UTC) Received: from svn.freebsd.org (svn.FreeBSD.org [8.8.178.70]) by svn.freebsd.org (8.14.5/8.14.5) with ESMTP id r061fFbZ046039; Sun, 6 Jan 2013 01:41:15 GMT (envelope-from rmacklem@svn.freebsd.org) Received: (from rmacklem@localhost) by svn.freebsd.org (8.14.5/8.14.5/Submit) id r061fFCB046037; Sun, 6 Jan 2013 01:41:15 GMT (envelope-from rmacklem@svn.freebsd.org) Message-Id: <201301060141.r061fFCB046037@svn.freebsd.org> From: Rick Macklem Date: Sun, 6 Jan 2013 01:41:15 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-9@freebsd.org Subject: svn commit: r245089 - stable/9/usr.sbin/gssd X-SVN-Group: stable-9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-stable-9@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: SVN commit messages for only the 9-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 06 Jan 2013 01:41:15 -0000 Author: rmacklem Date: Sun Jan 6 01:41:14 2013 New Revision: 245089 URL: http://svnweb.freebsd.org/changeset/base/245089 Log: MFC: r244604, r244638, r245014 It was reported via email that some sshds create kerberos credential cache files with names other than /tmp/krb5cc_. The gssd daemon does not know how to find these credential caches. This patch implements a new option "-s" that does a search for credential cache files, using roughly the same algorithm as the gssd daemon for Linux uses. The gssd behaviour is only changed if the new "-s" option is specified. Modified: stable/9/usr.sbin/gssd/Makefile stable/9/usr.sbin/gssd/gssd.c Directory Properties: stable/9/usr.sbin/gssd/ (props changed) Modified: stable/9/usr.sbin/gssd/Makefile ============================================================================== --- stable/9/usr.sbin/gssd/Makefile Sun Jan 6 01:17:58 2013 (r245088) +++ stable/9/usr.sbin/gssd/Makefile Sun Jan 6 01:41:14 2013 (r245089) @@ -1,5 +1,7 @@ # $FreeBSD$ +.include + 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 Modified: stable/9/usr.sbin/gssd/gssd.c ============================================================================== --- stable/9/usr.sbin/gssd/gssd.c Sun Jan 6 01:17:58 2013 (r245088) +++ stable/9/usr.sbin/gssd/gssd.c Sun Jan 6 01:41:14 2013 (r245089) @@ -35,7 +35,11 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include +#ifndef WITHOUT_KERBEROS +#include +#endif #include #include #include @@ -64,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); @@ -82,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; } @@ -267,13 +311,52 @@ gssd_init_sec_context_1_svc(init_sec_con 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) { @@ -296,7 +379,6 @@ gssd_init_sec_context_1_svc(init_sec_con } } - 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, @@ -516,13 +598,53 @@ gssd_acquire_cred_1_svc(acquire_cred_arg { 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) { @@ -631,3 +753,176 @@ gssd_1_freeresult(SVCXPRT *transp, xdrpr 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 */ +} +