#include <libkern/OSAtomic.h>
#include <sys/param.h>
#include <sys/time.h>
#include <mach/mach.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <netdb.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <vproc.h>
#ifdef VDEBUG
#include <time.h>
#include "/usr/local/include/vproc_priv.h"
#endif
#include <Kerberos/Kerberos.h>
#include <Kerberos/KerberosLoginPrivate.h>
#include "gssd.h"
#include "gssd_mach.h"
#include "gssd_machServer.h"
mach_port_t gssd_receive_right;
union MaxMsgSize {
union __RequestUnion__gssd_mach_subsystem req;
union __ReplyUnion__gssd_mach_subsystem rep;
};
#define MAX_GSSD_MSG_SIZE (sizeof (union MaxMsgSize) + MAX_TRAILER_SIZE)
#define DISPLAY_ERRS(name, oid, major, minor) \
CGSSDisplay_errs((name), (oid), (major), (minor))
#define str_to_buf(s, b) do { \
(b)->value = (s); (b)->length = strlen(s) + 1; \
} while (0)
#ifndef max
#define max(a, b) (((a)>(b))?(a):(b))
#endif
#define CAST(T,x) (T)(uintptr_t)(x)
#define ERR(...) syslog(LOG_ERR, __VA_ARGS__)
#define INFO(...) syslog(LOG_INFO, __VA_ARGS__)
#define DEBUG(...) syslog(LOG_DEBUG, __VA_ARGS__)
#define APPLE_PREFIX "com.apple."
#define MAXLABEL 256
#define MAXTHREADS 64
#define NOBODY (uint32_t)-2
#define TIMEOUT 30
#define SHUTDOWN_TIMEOUT 2
#define NFS_SERVICE "nfs"
#define NFS_SERVICE_LEN 3
#define IS_NFS_SERVICE(s) ((strncmp((s), NFS_SERVICE, NFS_SERVICE_LEN) == 0) && \
((s)[NFS_SERVICE_LEN] == '/' || (s)[NFS_SERVICE_LEN] == '@'))
krb5_enctype NFS_ENCTYPES[] = {
ENCTYPE_DES_CBC_CRC,
ENCTYPE_DES_CBC_MD5,
ENCTYPE_DES_CBC_MD4,
ENCTYPE_DES3_CBC_SHA1
};
#define NUM_NFS_ENCTYPES (sizeof(NFS_ENCTYPES)/sizeof(krb5_enctype))
static uint32_t uid_to_gss_name(uint32_t *, uid_t, gss_OID, gss_name_t *);
static char * get_next_kerb_component(char *);
static uint32_t gss_name_to_ucred(uint32_t *, gss_name_t,
char **, uid_t *, gid_t *, uint32_t *);
static char * lowercase(char *);
static char * canonicalize_host(const char *, char **);
static uint32_t str_to_svc_names(uint32_t *, const char *, gss_name_t *, uint32_t *);
static void kerberos_init(void);
static void * receive_message(void *);
static void new_worker_thread(void);
static void end_worker_thread(void);
static void compute_new_timeout(struct timespec *);
static void * shutdown_thread(void *);
static void * timeout_thread(void *);
static void vm_alloc_buffer(gss_buffer_t, uint8_t **, uint32_t *);
static uint32_t GetSessionKey(uint32_t *, gss_ctx_id_t *, byte_buffer *,
mach_msg_type_number_t *, void **);
static uint32_t badcall(char *, uint32_t *, gss_ctx *, gss_cred *,
byte_buffer *, mach_msg_type_number_t *,
byte_buffer *, mach_msg_type_number_t *);
static void CGSSDisplay_errs(char*, gss_OID, OM_uint32, OM_uint32);
static void HexLine(const char *, uint32_t *, char [80]);
static void HexDump(const char *, uint32_t);
static time_t timeout = TIMEOUT;
static int debug = 0;
static int die = 0;
static int bye = 0;
static int no_canon = 0;
static int maxthreads = MAXTHREADS;
static int numthreads = 0;
static pthread_mutex_t numthreads_lock[1];
static pthread_cond_t numthreads_cv[1];
static pthread_attr_t attr[1];
static volatile int32_t initCnt = 0;
static volatile int32_t initErr = 0;
static volatile int32_t acceptCnt = 0;
static volatile int32_t acceptErr = 0;
bool kerb_init_failed = false;
krb5_context local_context;
#define NODEFAULTREALM ":NO_DEFAULT_REALM:"
char *nodefrealm[] = {
NODEFAULTREALM,
NULL
};
#define MAXDEFREALMLEN (strlen(NODEFAULTREALM) + 1)
char **realms = nodefrealm;
char *default_realm;
#ifdef NO_PER_USER_LAUNCHD
pthread_mutex_t acquire_cred_lock[1];
#endif
uid_t NobodyUid = NOBODY;
gid_t NobodyGid = NOBODY;
char *local_host;
long GetPWMaxRSz;
sigset_t waitset[1];
sigset_t contset[1];
#define GSS_cmp_oid(o1, o2) ((o1)->length == (o2)->length ? \
memcmp((o1)->elements, (o2)->elements, (o1)->length) : 0)
static gss_OID_desc spnego_mech_desc = {
6, "\053\006\001\005\005\002"
};
static gss_OID_desc krb5_mech_desc = {
9, "\052\206\110\206\367\022\001\002\002"
};
static gss_OID mechtab[] = {
&krb5_mech_desc,
&spnego_mech_desc,
NULL
};
static size_t
derlen(uint8_t **dptr, uint8_t *eptr)
{
int i;
const uint8_t *p = *dptr;
size_t len = 0;
if (*p & 0x80) {
for (i = *p & 0x7f; i > 0 && (eptr == NULL || (p < eptr)); i--)
len = (len << 8) + *++p;
} else
len = *p;
*dptr = p + 1;
return (len);
}
#define ADVANCE(p, l, e) do { \
(p) += (l); \
if (debug) \
DEBUG("%s:%d Advancing %d bytes\n", __func__, __LINE__, (l)); \
if ((p) > (e)) { \
if (debug) \
DEBUG("Defective at %d p = %p e = %p\n", __LINE__, (p), (e)); \
return (GSS_S_DEFECTIVE_TOKEN); \
} \
} while (0)
#define CHK(p, v, e) (((p) >= (e) || *(p) != (v)) ? 0 : 1)
static size_t
encode_derlen(size_t len, size_t max, uint8_t *value)
{
int i;
size_t count, len_save = len;
if (len < 0x80) {
if (max > 0 && value)
value[0] = len;
return 1;
}
for (count = 0; len; count++)
len >>= 8;
len = len_save;
if (value && max > count) {
for (i = count; i > 0; i--, len >>= 8) {
value[i] = (len & 0xff );
}
value[0] = (0x80 | count);
}
return (count + 1);
}
#define SEQUENCE 0x30
#define CONTEXT 0xA0
#define ENUM 0x0A
#define OCTETSTRING 0x04
static uint32_t
spnego_win2k_hack(gss_buffer_t token)
{
uint8_t *ptr, *eptr, *response, *start, *end;
size_t len, rlen, seqlen, seqlenbytes, negresplen, negresplenbytes, tlen;
ptr = token->value;
eptr = ptr + token->length;
if (debug) {
DEBUG("%s:%d token value\n", __func__, __LINE__);
HexDump(token->value, token->length);
}
if (!CHK(ptr, (CONTEXT | 1), eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (!CHK(ptr, SEQUENCE, eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
start = ptr;
if (!CHK(ptr, (CONTEXT | 0), eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (len != 3)
return (GSS_S_DEFECTIVE_TOKEN);
if (!CHK(ptr, ENUM, eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (len != 1)
return (GSS_S_DEFECTIVE_TOKEN);
if (!CHK(ptr, 0x0, eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
if (!CHK(ptr, (CONTEXT | 1), eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
ADVANCE(ptr, len, eptr);
if (!CHK(ptr, (CONTEXT | 2), eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (!CHK(ptr, OCTETSTRING, eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
rlen = derlen(&ptr, eptr);
response = ptr;
ADVANCE(ptr, rlen, eptr);
if (ptr == eptr)
return (GSS_S_COMPLETE);
end = ptr;
if (!CHK(ptr, (CONTEXT | 3), eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (!CHK(ptr, OCTETSTRING, eptr))
return (GSS_S_DEFECTIVE_TOKEN);
ADVANCE(ptr, 1, eptr);
len = derlen(&ptr, eptr);
if (len != rlen || ptr + rlen != eptr || memcmp(response, ptr, rlen) != 0) {
if (debug)
DEBUG("Mic does not equal response %p %p %p len = %d rlen = %d\n",
ptr, ptr + rlen, eptr, len, rlen);
return (GSS_S_DEFECTIVE_TOKEN);
}
seqlen = end - start;
seqlenbytes = encode_derlen(seqlen, 0, 0);
negresplen = seqlen + seqlenbytes + 1;
negresplenbytes = encode_derlen(negresplen, 0, 0);
tlen = negresplen + negresplenbytes + 1;
ptr = token->value;
*ptr++ = CONTEXT | 1;
encode_derlen(negresplen, negresplenbytes, ptr);
ptr += negresplenbytes;
*ptr++ = SEQUENCE;
encode_derlen(seqlen, seqlenbytes, ptr);
ptr += seqlenbytes;
memmove(ptr, start, seqlen);
token->length = tlen;
if (debug) {
DEBUG("Returning token from %s\n", __func__);
HexDump(token->value, token->length);
}
return (GSS_S_COMPLETE);
}
int main(int argc, char *argv[])
{
char label_buf[MAXLABEL];
char *bname = label_buf;
char *myname;
kern_return_t kr;
pthread_t timeout_thr, shutdown_thr;
int error;
int ch;
setlinebuf(stdout);
setlinebuf(stderr);
myname = strrchr(argv[0], '/');
myname = myname ? myname + 1 : argv[0];
strlcpy(label_buf, APPLE_PREFIX, sizeof(label_buf));
strlcat(label_buf, myname, sizeof(label_buf));
openlog(myname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
while ((ch = getopt(argc, argv, "Cdhm:n:t:")) != -1) {
switch (ch) {
case 'C':
no_canon = 1;
break;
case 'd':
debug++;
break;
case 'm':
maxthreads = atoi(optarg);
if (maxthreads < 1)
maxthreads = MAXTHREADS;
break;
case 'n':
bname = optarg;
break;
case 't':
timeout = atoi(optarg);
if (timeout < 1)
timeout = TIMEOUT;
break;
case 'h':
default:
ERR("usage: %s [-Cdht] [-m threads] "
"[-n bootstrap name]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
sigemptyset(waitset);
sigaddset(waitset, SIGABRT);
sigaddset(waitset, SIGINT);
sigaddset(waitset, SIGHUP);
sigaddset(waitset, SIGUSR1);
sigaddset(waitset, SIGUSR2);
*contset = *waitset;
sigaddset(waitset, SIGTERM);
pthread_sigmask(SIG_BLOCK, waitset, NULL);
(void) pthread_mutex_init(numthreads_lock, NULL);
(void) pthread_cond_init(numthreads_cv, NULL);
(void) pthread_attr_init(attr);
(void) pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED);
setlogmask(LOG_UPTO(debug ? LOG_DEBUG : LOG_ERR));
kerberos_init();
kr = bootstrap_check_in(bootstrap_port, bname, &gssd_receive_right);
if (kr != BOOTSTRAP_SUCCESS) {
ERR("Could not checkin for receive right: %s\n", bootstrap_strerror(kr));
exit(EXIT_FAILURE);
}
error = pthread_create(&shutdown_thr, attr, shutdown_thread, NULL);
if (error) {
ERR("unable to create shutdown thread: %s", strerror(error));
exit(EXIT_FAILURE);
}
error = pthread_create(&timeout_thr, NULL, timeout_thread, NULL);
if (error) {
ERR("unable to create time out thread: %s", strerror(error));
exit(EXIT_FAILURE);
}
#ifdef VDEBUG
{
time_t now;
if (debug == 2)
vproc_transaction_begin(NULL);
now = time(NULL);
fprintf(stderr, "starting %s with transaction count = %lu, "
"standby count = %lu\n", ctime(&now),
(unsigned long)_vproc_transaction_count(),
(unsigned long)_vproc_standby_count());
fflush(stderr);
}
#endif
new_worker_thread();
pthread_join(timeout_thr, NULL);
if (debug)
DEBUG("Time out exiting. Number of threads is %d\n", numthreads);
pthread_attr_destroy(attr);
if (debug) {
DEBUG("Total %d init_sec_context errors out of %d calls\n",
initErr, initCnt);
DEBUG("Total %d accept_sec_context errors out of %d calls\n",
acceptErr, acceptCnt);
}
#ifdef VDEBUG
fprintf(stderr, "exiting with transaction count = %lu, "
"standby count = %lu\n",
(unsigned long) _vproc_transaction_count(),
(unsigned long) _vproc_standby_count());
fflush(stderr);
sleep(30);
fprintf(stderr, "Bye Bye\n");
#endif
exit(kerb_init_failed);
}
static krb5_error_code
krb5_get_default_realms(krb5_context ctx, char ***lrealms)
{
krb5_error_code kec;
*lrealms = (char **)calloc(2, sizeof (char *));
if (*lrealms == NULL)
return (ENOMEM);
kec = krb5_get_default_realm(ctx, *lrealms);
if (kec) {
ERR("%s: could not get default realm: %s\n",
__func__, error_message(kec));
*lrealms = nodefrealm;
kec = 0;
}
return (kec);
}
static uint32_t
uid_to_gss_name(uint32_t *minor, uid_t uid, gss_OID oid, gss_name_t *name)
{
char pwbuf[GetPWMaxRSz];
struct passwd *pwd, pwent;
char *princ_str;
gss_buffer_desc buf_name;
uint32_t major, len;
int rc;
*minor = 0;
rc = getpwuid_r(uid, &pwent, pwbuf, sizeof(pwbuf), &pwd);
if (rc != 0 || pwd == NULL)
return (GSS_S_UNAUTHORIZED);
len = strlen(pwd->pw_name) + 1 + strlen(default_realm) + 1;
len = max(len, 10);
len = max(len, 5 + strlen(local_host) + 1 + strlen(default_realm) + 1);
if ((princ_str = malloc(len)) == NULL)
return (GSS_S_FAILURE);
if (oid == GSS_KRB5_NT_PRINCIPAL_NAME) {
if (pwd->pw_uid == 0)
snprintf(princ_str, len,
"host/%s@%s", local_host, default_realm);
else
snprintf(princ_str, len,
"%s@%s", pwd->pw_name, default_realm);
}
else if (oid == GSS_C_NT_USER_NAME)
snprintf(princ_str, len, "%s", pwd->pw_name);
else if (oid == GSS_C_NT_STRING_UID_NAME)
snprintf(princ_str, len, "%d", pwd->pw_uid);
else if (oid == GSS_C_NT_MACHINE_UID_NAME)
memcpy(princ_str, &pwd->pw_uid, sizeof(pwd->pw_uid));
else if (oid == GSS_C_NT_HOSTBASED_SERVICE && pwd->pw_uid == 0)
snprintf(princ_str, len, "host@%s", local_host);
else {
free(princ_str);
return (GSS_S_FAILURE);
}
str_to_buf(princ_str, &buf_name);
if (debug)
DEBUG("importing name %s\n", princ_str);
major = gss_import_name(minor, &buf_name, oid, name);
free(princ_str);
return (major);
}
static char *
get_next_kerb_component(char *str)
{
char *s, *p;
s = p = str;
do {
p = strpbrk(s, "/@\\");
s = (p && *p == '\\' && *(p+1)) ? p + 2 : NULL;
} while (s);
return (p);
}
static bool
getucred(const char *uname, uid_t *uid, gid_t *gids, uint32_t *ngroups)
{
struct passwd *pwd, pwent;
char pwdbuf[GetPWMaxRSz];
*uid = NobodyUid;
*gids = NobodyGid;
*ngroups = 1;
(void) getpwnam_r(uname, &pwent, pwdbuf, sizeof(pwdbuf), &pwd);
if (pwd) {
*uid = pwd->pw_uid;
*ngroups = NGROUPS_MAX;
if (getgrouplist(uname, pwd->pw_gid,
(int *)gids, (int *)ngroups) == -1) {
*gids = pwd->pw_gid;
*ngroups = 1;
}
return (true);
}
return (false);
}
static uint32_t
gss_name_to_ucred(uint32_t *minor, gss_name_t name,
char **lrealms, uid_t *uid, gid_t *gids, uint32_t *ngroups)
{
uint32_t major;
char *name_str = NULL;
gss_buffer_desc buf;
gss_OID oid;
char **rlm, *this_realm, *uname;
bool gotname;
*minor = 0;
major = gss_display_name(minor, name, &buf, &oid);
if (major != GSS_S_COMPLETE)
return (major);
name_str = malloc(buf.length + 1);
if (name_str == NULL) {
gss_release_buffer(minor, &buf);
return (GSS_S_FAILURE);
}
memcpy(name_str, buf.value, buf.length);
name_str[buf.length] = '\0';
uname = name_str;
if ((gotname = getucred(uname, uid, gids, ngroups)))
goto out;
if (oid == GSS_KRB5_NT_PRINCIPAL_NAME) {
this_realm = strrchr(name_str, '@');
if (this_realm == NULL)
goto out;
this_realm++;
for(rlm = lrealms; rlm && *rlm; rlm++) {
if (strncmp(this_realm, *rlm, buf.length) == 0) {
char *p;
p = get_next_kerb_component(name_str);
if (p)
*p = '\0';
gotname = getucred(uname, uid, gids, ngroups);
goto out;
}
}
}
out:
if (!gotname)
ERR("%s: Could not map %s to unix credentials\n", __func__, uname);
free(uname);
return (gotname ? GSS_S_COMPLETE : GSS_S_FAILURE);
}
static char *
lowercase(char *s)
{
char *t;
for (t = s; t && *t; t++)
*t = tolower(*t);
return (s);
}
static char *
canonicalize_host(const char *host, char **rfqdn)
{
struct hostent *hp, *rhp;
int h_err;
char *fqdn;
if (rfqdn)
*rfqdn = NULL;
hp = getipnodebyname(host, AF_INET6, AI_DEFAULT, &h_err);
if (hp == NULL) {
if (debug)
DEBUG("host look up for %s returned %d\n", host, h_err);
return (NULL);
}
fqdn = strdup(lowercase(hp->h_name));
if (fqdn == NULL) {
ERR("Could not allocat hostname in canonicalize_host\n");
return (NULL);
}
if (rfqdn) {
if (debug)
DEBUG("%s: Trying reverse lookup\n", __func__);
rhp = getipnodebyaddr(hp->h_addr_list[0], hp->h_length, AF_INET6, &h_err);
if (rhp) {
if (strncmp(fqdn, lowercase(rhp->h_name), MAXHOSTNAMELEN) != 0) {
*rfqdn = strdup(rhp->h_name);
if (*rfqdn == NULL)
ERR("Could not allocat hostname in canonicalize_host\n");
}
freehostent(rhp);
}
else {
if (debug)
DEBUG("reversed host look up for %s returned %d\n", host, h_err);
}
}
freehostent(hp);
return (fqdn);
}
static uint32_t
construct_service_name(uint32_t *minor, const char *service, char *host,
const char *realm, bool lcase, gss_name_t *svcname)
{
int len;
char *s;
gss_buffer_desc name_buf;
uint32_t major;
if (lcase)
lowercase(host);
len = strlen(service) + strlen(host) + strlen(realm) + 3;
s = malloc(len);
if (s == NULL) {
ERR("Out of memory in %s\n", __func__);
return (GSS_S_FAILURE);
}
strlcpy(s, service, len);
strlcat(s, "/", len);
strlcat(s, host, len);
strlcat(s, "@", len);
strlcat(s, realm, len);
str_to_buf(s, &name_buf);
if (debug)
DEBUG("Importing kerberos principal service name %s\n", s);
major = gss_import_name(minor, &name_buf,
GSS_KRB5_NT_PRINCIPAL_NAME, svcname);
free(s);
return (major);
}
static uint32_t
construct_hostbased_service_name(uint32_t *minor, const char *service, const char *host, gss_name_t *svcname)
{
int len;
char *s;
gss_buffer_desc name_buf;
uint32_t major;
len = strlen(service) + strlen(host) + 2;
s = malloc(len);
if (s == NULL) {
ERR("Out of memory in %s\n", __func__);
return (GSS_S_FAILURE);
}
strlcpy(s, service, len);
strlcat(s, "@", len);
strlcat(s, host, len);
str_to_buf(s, &name_buf);
if (debug)
DEBUG("Importing host based service name %s\n", s);
major = gss_import_name(minor, &name_buf, GSS_C_NT_HOSTBASED_SERVICE, svcname);
free(s);
return (major);
}
#define LKDCPREFIX "LKDC:"
static uint32_t
str_to_svc_names(uint32_t *minor, const char *svcstr,
gss_name_t *svcname, uint32_t *name_count)
{
uint32_t major, first_major;
char *realm = NULL , *host;
char *s, *p, *service;
char *fqdn = NULL, *rfqdn = NULL;
uint32_t count = *name_count;
int is_lkdc;
*minor = 0;
major = GSS_S_FAILURE;
*name_count = 0;
if (debug)
DEBUG("%s: %s count = %d\n", __func__, svcstr, count);
service = strdup(svcstr);
if (service == NULL) {
ERR("%s: Out of memory\n", __func__);
return (GSS_S_FAILURE);
}
p = get_next_kerb_component(service);
host = p + 1;
if (p == NULL || *p == '\0') {
host = local_host;
} else if (*p == '@') {
*p = '\0';
s = get_next_kerb_component(host);
if (s != NULL) {
ERR("%s: Invalid host name part %s\n", __func__, host);
free(service);
return (GSS_S_BAD_NAME);
}
major = construct_hostbased_service_name(minor, service, host, svcname);
if (major == GSS_S_COMPLETE)
*name_count = 1;
return (major);
} else if (*p == '/') {
*p = '\0';
s = host;
do {
s = get_next_kerb_component(s+1);
if (s && (*s == '@')) {
realm = s + 1;
*s = '\0';
break;
}
} while (s);
} else {
free(service);
return (GSS_S_BAD_NAME);
}
if (realm == NULL) {
major = construct_hostbased_service_name(minor, service, host, svcname);
if (major == GSS_S_COMPLETE) {
*name_count = 1;
return (major);
}
realm = default_realm;
}
if (strncmp(realm, NODEFAULTREALM, MAXDEFREALMLEN) == 0) {
free(service);
kill(getpid(), SIGTERM);
return (GSS_S_BAD_NAME);
}
is_lkdc = (strncmp(realm, LKDCPREFIX, strlen(LKDCPREFIX)) == 0);
major = construct_service_name(minor, service, host, realm, !is_lkdc, &svcname[*name_count]);
if (major == GSS_S_COMPLETE)
*name_count += 1;
first_major = major;
if (count == 1 || is_lkdc)
goto done;
fqdn = canonicalize_host(host, (count == 3) ? &rfqdn : NULL);
if (fqdn && strncmp(fqdn, host, MAXHOSTNAMELEN) != 0) {
major = construct_service_name(minor, service, fqdn, realm, true, &svcname[*name_count]);
if (major == GSS_S_COMPLETE)
*name_count += 1;
}
if (rfqdn && *name_count < count) {
major = construct_service_name(minor, service, rfqdn, realm, true, &svcname[*name_count]);
if (major == GSS_S_COMPLETE)
*name_count += 1;
free(rfqdn);
}
done:
free(service);
return (*name_count ? GSS_S_COMPLETE : first_major);
}
static void
kerberos_init(void)
{
int error;
struct passwd *pwent;
struct group *grent;
char hostbuf[MAXHOSTNAMELEN];
#ifndef NO_PER_USER_LAUNCHD
__KLSetHomeDirectoryAccess(false);
#endif
kerb_init_failed = false;
error = krb5_init_context(&local_context);
if (error) {
ERR("gssd: Kerberos: %s\n", error_message(error));
kerb_init_failed = true;
}
if (kerb_init_failed == false &&
krb5_get_default_realms(local_context, &realms)) {
ERR("gssd: no Kerberos realm\n");
kerb_init_failed = true;
}
default_realm = *realms;
#ifdef NO_PER_USER_LAUNCHD
pthread_mutex_init(acquire_cred_lock, NULL);
#endif
pwent = getpwnam("nobody");
NobodyUid = pwent ? pwent->pw_uid : NOBODY;
grent = getgrnam("nobody");
NobodyGid = grent ? grent->gr_gid : NOBODY;
gethostname(hostbuf, MAXHOSTNAMELEN);
local_host = canonicalize_host(hostbuf, NULL);
if ( local_host == NULL) {
ERR("Could not canonicalize our host name in kerberos_init\n");
local_host = lowercase(hostbuf);
}
GetPWMaxRSz = sysconf(_SC_GETPW_R_SIZE_MAX);
GetPWMaxRSz = (GetPWMaxRSz == -1) ? 512 : GetPWMaxRSz;
if (debug)
DEBUG("\tKerberos default realm is %s for %s\n",
default_realm, local_host);
if (geteuid() == 0)
__KLSetPromptMechanism(klPromptMechanism_None);
}
static void *
receive_message(void *arg __attribute__((unused)))
{
kern_return_t kr;
if (debug) {
DEBUG("Enter receive_message %p\n", pthread_self());
#ifdef VDEBUG
fprintf(stderr, "Enter receive_message %p with transaction count = %lu, "
"standby count = %lu\n", pthread_self(),
_vproc_transaction_count(), _vproc_standby_count());
fflush(stderr);
#endif
}
kr = mach_msg_server_once(gssd_mach_server, MAX_GSSD_MSG_SIZE,
gssd_receive_right,
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
if (debug) {
DEBUG("Leaving receive_message %p\n", pthread_self());
#ifdef VDEBUG
fprintf(stderr, "Leaving receive_message %p with transaction count = %lu, "
"standby count = %lu\n", pthread_self(),
_vproc_transaction_count(), _vproc_standby_count());
fflush(stderr);
#endif
}
if (kr != KERN_SUCCESS) {
ERR("mach_msg_server(mp): %s\n", mach_error_string(kr));
exit(1);
}
return (NULL);
}
static void
new_worker_thread(void)
{
pthread_t thread;
int error;
(void) pthread_mutex_lock(numthreads_lock);
while (bye == 0 && numthreads >= maxthreads) {
(void) pthread_cond_wait(numthreads_cv, numthreads_lock);
}
if (bye)
goto out;
numthreads++;
error = pthread_create(&thread, attr, receive_message, NULL);
if (error) {
ERR("unable to create worker thread: %s", strerror(error));
numthreads--;
}
out:
(void) pthread_mutex_unlock(numthreads_lock);
}
static void
end_worker_thread(void)
{
(void) pthread_mutex_lock(numthreads_lock);
numthreads--;
if (numthreads < maxthreads)
pthread_cond_signal(numthreads_cv);
if (debug)
DEBUG("Thread %p ending numthreads = %d\n",
pthread_self(), numthreads);
(void) pthread_mutex_unlock(numthreads_lock);
}
static void*
shutdown_thread(void *arg __attribute__((unused)))
{
int sig;
do {
sigwait(waitset, &sig);
switch (sig) {
case SIGABRT:
die = 1;
break;
case SIGUSR1:
debug++;
if (debug == 1)
setlogmask(LOG_UPTO(LOG_DEBUG));
DEBUG("Debug level is now %d\n", debug);
break;
case SIGUSR2:
if (debug > 0)
debug--;
if (debug == 0)
setlogmask(LOG_UPTO(LOG_ERR));
break;
case SIGHUP:
debug = !debug;
setlogmask(LOG_UPTO(debug ? LOG_DEBUG : LOG_ERR));
break;
}
} while (sigismember(contset, sig));
pthread_mutex_lock(numthreads_lock);
bye = 1;
timeout = SHUTDOWN_TIMEOUT;
pthread_cond_broadcast(numthreads_cv);
pthread_mutex_unlock(numthreads_lock);
return (NULL);
}
static void
compute_new_timeout(struct timespec *new)
{
struct timeval current;
gettimeofday(¤t, NULL);
new->tv_sec = current.tv_sec + timeout;
new->tv_nsec = 1000 * current.tv_usec;
}
static void*
timeout_thread(void *arg __attribute__((unused)))
{
int rv = 0;
struct timespec exittime;
(void) pthread_mutex_lock(numthreads_lock);
while (bye ? (rv == 0 && numthreads > 1) : (rv == 0 || numthreads > 1)) {
if (bye < 2)
compute_new_timeout(&exittime);
if (bye)
bye++;
rv = pthread_cond_timedwait(numthreads_cv,
numthreads_lock, &exittime);
if (debug)
DEBUG("timeout_thread: rv = %s %d\n",
rv ? strerror(rv) : "signaled", numthreads);
}
(void) pthread_mutex_unlock(numthreads_lock);
return (NULL);
}
static void
vm_alloc_buffer(gss_buffer_t buf, uint8_t **value, uint32_t *len)
{
kern_return_t kr;
*value = NULL;
*len = 0;
if (buf->length == 0)
return;
kr = vm_allocate(mach_task_self(),
(vm_address_t *)value, buf->length, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) {
ERR("Could not allocate vm in vm_alloc_buffer\n");
return;
}
*len = buf->length;
memcpy(*value, buf->value, *len);
}
static uint32_t
GetSessionKey(uint32_t *minor, gss_ctx_id_t *ctx,
byte_buffer *skey, mach_msg_type_number_t *skeyCnt, void **lctx)
{
gss_krb5_lucid_context_v1_t *lucid_ctx;
gss_krb5_lucid_key_t *key;
void *some_lucid_ctx;
uint32_t major;
uint32_t vers;
gss_buffer_desc buf;
*lctx = NULL;
*skey = NULL;
*skeyCnt = 0;
*minor = 0;
if (debug)
DEBUG("Calling gss_krb5_export_lucid_sec_context\n");
major = gss_krb5_export_lucid_sec_context(minor, ctx,
1, &some_lucid_ctx);
if (major != GSS_S_COMPLETE)
return (major);
*lctx = some_lucid_ctx;
vers = ((gss_krb5_lucid_context_version_t *)some_lucid_ctx)->version;
switch (vers) {
case 1:
lucid_ctx = (gss_krb5_lucid_context_v1_t *)some_lucid_ctx;
break;
default:
ERR("Lucid version %d is unsupported\n", vers);
return (GSS_S_UNAVAILABLE);
}
if (debug)
DEBUG("vers = %d, protocol = %d\n", vers, lucid_ctx->protocol);
switch (lucid_ctx->protocol) {
case 0:
key = &lucid_ctx->rfc1964_kd.ctx_key;
break;
case 1:
key = lucid_ctx->cfx_kd.have_acceptor_subkey ?
&lucid_ctx->cfx_kd.acceptor_subkey :
&lucid_ctx->cfx_kd.ctx_key;
break;
default:
return (GSS_S_CALL_BAD_STRUCTURE);
}
buf.length = key->length;
buf.value = key->data;
vm_alloc_buffer(&buf, skey, skeyCnt);
if (skey == NULL) {
ERR("Out of memory in GetSessionKey\n");
return (GSS_S_FAILURE);
}
return (GSS_S_COMPLETE);
}
static uint32_t
badcall(char *rtn, uint32_t *minor_stat,
gss_ctx *gss_context, gss_cred *cred_handle,
byte_buffer *skey, mach_msg_type_number_t *skeyCnt,
byte_buffer *otoken, mach_msg_type_number_t *otokenCnt)
{
ERR("%s request not addressed to us\n", rtn);
*minor_stat = 0;
*gss_context = CAST(gss_ctx, GSS_C_NO_CONTEXT);
*cred_handle = CAST(gss_cred, GSS_C_NO_CREDENTIAL);
*skey = NULL;
*skeyCnt = 0;
*otoken = NULL;
*otokenCnt = 0;
return (GSS_S_CALL_BAD_STRUCTURE);
}
static uint32_t
gss_name_to_kprinc(uint32_t *minor, gss_name_t name, krb5_principal *princ)
{
uint32_t major, m;
gss_name_t kname = GSS_C_NO_NAME;
gss_buffer_desc dname;
char *strname = NULL;
*minor = 0;
major = gss_canonicalize_name(minor, name, &krb5_mech_desc, &kname);
if (major != GSS_S_COMPLETE)
return (major);
major = gss_display_name(minor, kname, &dname, NULL);
if (major != GSS_S_COMPLETE) {
(void) gss_release_name(&m, &kname);
return (major);
}
strname = malloc(dname.length + 1);
if (strname == NULL) {
(void) gss_release_name(&m, &kname);
(void) gss_release_buffer(&m, &dname);
return (GSS_S_FAILURE);
}
strlcpy(strname, dname.value, dname.length+1);
(void) gss_release_buffer(&m, &dname);
(void) gss_release_name(&m, &kname);
if (debug)
DEBUG("%s: parsing %s\n", __func__, strname);
*minor = krb5_parse_name(local_context, strname, princ);
major = *minor ? GSS_S_FAILURE : GSS_S_COMPLETE;
free(strname);
return (major);
}
#define CacheDone(e) (((e) == ccIteratorEnd) || ((e) == KRB5_CC_END))
static char*
krb5_find_cache_name(krb5_principal sprinc)
{
krb5_error_code error;
cc_context_t cc_context;
cc_ccache_iterator_t cc_iterator;
cc_ccache_t cc_cache;
cc_string_t cc_name;
krb5_ccache ccache;
krb5_principal ccache_princ;
char *cname = NULL;
const char *cc_type = NULL;
error = cc_initialize(&cc_context, ccapi_version_4, NULL, NULL);
if (error) {
INFO("Could not initialize the credentials cache\n");
return (NULL);
}
error = cc_context_new_ccache_iterator(cc_context, &cc_iterator);
if (error) {
INFO("Could not iterate of credentials cache\n");
cc_context_release(cc_context);
return (NULL);
}
while (!(error = cc_ccache_iterator_next(cc_iterator, &cc_cache))) {
krb5_error_code err;
if (cc_ccache_get_name(cc_cache, &cc_name)) {
INFO("%s: Could not get cache name, skipping\n", __func__);
cc_ccache_release(cc_cache);
continue;
}
if (debug)
DEBUG("%s: Found ccache %s\n", __func__, cc_name->data);
err = krb5_cc_resolve(local_context, cc_name->data, &ccache);
cc_ccache_release(cc_cache);
if (err) {
INFO("%s: krb5_cc_resolve error: %s\n", __func__, error_message(err));
cc_string_release(cc_name);
continue;
}
cc_type = krb5_cc_get_type(local_context, ccache);
err = krb5_cc_get_principal(local_context, ccache, &ccache_princ);
krb5_cc_close(local_context, ccache);
if (err) {
INFO("%s: krb5_cc_get_principal error: %s\n", __func__, error_message(err));
cc_string_release(cc_name);
continue;
}
if (krb5_realm_compare(local_context, sprinc, ccache_princ)) {
size_t len = strlen(cc_name->data) + 1;
len += cc_type ? strlen(cc_type) + 1 : 0;
cname = malloc(len);
if (cname == NULL)
ERR("%s: Could not duplicate cache name\n", __func__);
else {
if (cc_type && *cc_type)
snprintf(cname, len, "%s:%s", cc_type, cc_name->data);
else
snprintf(cname, len, "%s", cc_name->data);
}
cc_string_release(cc_name);
krb5_free_principal(local_context, ccache_princ);
break;
}
krb5_free_principal(local_context, ccache_princ);
cc_string_release(cc_name);
}
if (error && !CacheDone(error))
ERR("%s: Could not iterate through cache collections: %s\n", __func__,
error_message(error));
cc_ccache_iterator_release(cc_iterator);
cc_context_release(cc_context);
return (cname);
}
static void
set_principal_identity(gss_name_t sname)
{
krb5_principal sprinc;
uint32_t major, minor;
char *cname;
major = gss_name_to_kprinc(&minor, sname, &sprinc);
if (major != GSS_S_COMPLETE) {
DEBUG("%s: Could not convert gss name to kerberos principal\n", __func__);
return;
}
cname = krb5_find_cache_name(sprinc);
krb5_free_principal(local_context, sprinc);
if (debug)
DEBUG("%s: Using ccache <%s>\n", __func__, cname ? cname : "Default");
if (cname) {
major = gss_krb5_ccache_name(&minor, cname, NULL);
if (debug)
DEBUG("gss_krb5_ccache_name returned %d:%d\n", major, minor);
free(cname);
}
}
static uint32_t
do_acquire_cred(uint32_t *minor, char *principal, mechtype mech, gss_name_t sname, uint32_t uid,
gss_cred *cred_handle, uint32_t flags)
{
uint32_t major = GSS_S_FAILURE, mstat;
gss_buffer_desc buf_name;
gss_name_t clnt_gss_name;
gss_OID_set mechset = GSS_C_NULL_OID_SET;
gss_OID name_type = GSS_KRB5_NT_PRINCIPAL_NAME;
#ifdef NO_PER_USER_LAUNCHD
int allow_home = FALSE;
pthread_mutex_lock(acquire_cred_lock);
if ((flags & GSSD_HOME_ACCESS_OK) == 0) {
allow_home = __KLAllowHomeDirectoryAccess();
if (allow_home)
__KLSetHomeDirectoryAccess(!allow_home);
}
#endif
if ((flags & GSSD_UI_OK) == 0)
__KLSetPromptMechanism(klPromptMechanism_None);
set_principal_identity(sname);
major = gss_create_empty_oid_set(minor, &mechset);
if (major != GSS_S_COMPLETE)
goto done;
major = gss_add_oid_set_member(minor, mechtab[mech], &mechset);
if (major != GSS_S_COMPLETE)
goto done;
if (principal && *principal) {
str_to_buf(principal, &buf_name);
if (debug)
DEBUG("importing name %s with Keberos\n", principal);
retry:
major = gss_import_name(minor, &buf_name, name_type, &clnt_gss_name);
if (major == GSS_S_COMPLETE) {
major = gss_acquire_cred(
minor,
clnt_gss_name,
GSS_C_INDEFINITE,
mechset,
GSS_C_INITIATE,
(gss_cred_id_t *) cred_handle,
NULL, NULL);
if (major == GSS_S_COMPLETE) {
if (debug)
DEBUG("Using credentials for %s\n", principal);
(void) gss_release_name(&mstat, &clnt_gss_name);
goto done;
}
}
if (name_type == GSS_KRB5_NT_PRINCIPAL_NAME) {
name_type = GSS_C_NT_USER_NAME;
DEBUG("importing name %s as a user name\n", principal);
goto retry;
}
}
if (!(flags & GSSD_NO_DEFAULT)) {
major = gss_acquire_cred(
minor,
GSS_C_NO_NAME,
GSS_C_INDEFINITE,
mechset,
GSS_C_INITIATE,
(gss_cred_id_t *) cred_handle,
NULL, NULL);
if (major == GSS_S_COMPLETE) {
if (debug)
DEBUG("Using default credential %p\n", *(gss_cred_id_t *)cred_handle);
goto done;
}
}
major = uid_to_gss_name(minor, (uid_t) uid,
GSS_C_NT_USER_NAME, &clnt_gss_name);
if (major != GSS_S_COMPLETE)
return (major);
major = gss_acquire_cred(
minor,
clnt_gss_name,
GSS_C_INDEFINITE,
mechset,
GSS_C_INITIATE,
(gss_cred_id_t *) cred_handle,
NULL, NULL);
(void) gss_release_name(&mstat, &clnt_gss_name);
done:
if (mechset != GSS_C_NULL_OID_SET)
gss_release_oid_set(&mstat, &mechset);
#ifdef NO_PER_USER_LAUNCHD
if ((flags & GSSD_HOME_ACCESS) == 0 && allow_home)
__KLSetHomeDirectoryAccess(allow_home);
if ((flags & GSSD_UI_OK) == 0 && getuid() != 0)
__KLSetPromptMechanism(klPromptMechanism_Autodetect);
pthread_mutex_unlock(acquire_cred_lock);
#endif
return (major);
}
typedef struct {
gss_ctx_id_t gss_cntx;
gss_name_t svc_name;
vproc_transaction_t trans_handle;
} gssd_context, *gssd_context_t;
static gss_ctx
gssd_set_context(gss_ctx_id_t ctx, gss_name_t svc_name)
{
gssd_context_t g;
g = malloc(sizeof (gssd_context));
if (g == NULL)
return (CAST(gss_ctx, GSS_C_NO_CONTEXT));
gssd_enter(g);
g->gss_cntx = ctx;
g->svc_name = svc_name;
g->trans_handle = vproc_transaction_begin(NULL);
return (CAST(gss_ctx, g));
}
static gss_ctx_id_t
gssd_get_context(gss_ctx ctx, gss_name_t *svc_name)
{
gssd_context_t g;
gss_ctx_id_t gss_context;
if (!ctx) {
if (svc_name)
*svc_name = GSS_C_NO_NAME;
return (GSS_C_NO_CONTEXT);
}
g = CAST(gssd_context_t, ctx);
if (svc_name)
*svc_name = g->svc_name;
gss_context = g->gss_cntx;
vproc_transaction_end(NULL, g->trans_handle);
gssd_remove(g);
free(g);
return (gss_context);
}
#define MAX_SVC_NAMES 3
kern_return_t
svc_mach_gss_init_sec_context(
mach_port_t test_port __attribute__((unused)),
mechtype mech,
byte_buffer itoken, mach_msg_type_number_t itokenCnt,
uint32_t uid,
string_t princ_namestr,
string_t svc_namestr,
uint32_t flags,
uint32_t gssd_flags,
gss_ctx *gss_context,
gss_cred *cred_handle,
uint32_t *ret_flags,
byte_buffer *skey, mach_msg_type_number_t *skeyCnt,
byte_buffer *otoken, mach_msg_type_number_t *otokenCnt,
uint32_t *major_stat,
uint32_t *minor_stat)
{
gss_name_t svc_gss_name[MAX_SVC_NAMES];
gss_ctx_id_t g_cntx = GSS_C_NO_CONTEXT;
uint32_t i, gnames = MAX_SVC_NAMES, name_index = MAX_SVC_NAMES;
void *lucid_ctx = NULL;
uint32_t mstat;
vproc_transaction_t gssd_vproc_handle;
uint32_t only_1des = ((gssd_flags & GSSD_NFS_1DES) != 0);
gssd_vproc_handle = vproc_transaction_begin(NULL);
new_worker_thread();
if (kerb_init_failed) {
if (debug)
DEBUG("%s: is exiting since kerbos init failed\n", __func__);
end_worker_thread();
vproc_transaction_end(NULL, gssd_vproc_handle);
kill(getpid(), SIGTERM);
return (KERN_NOT_SUPPORTED);
}
if (debug) {
DEBUG("svc_mach_gss_init_sec_context:\n");
DEBUG(" cred_handle = %p\n", CAST(gss_cred_id_t, *cred_handle));
DEBUG(" uid = %d\n", (int) uid);
DEBUG(" flags = %0x\n", flags);
DEBUG(" gssd flags = %0x\n", gssd_flags);
if (die) {
DEBUG("Forced server death\n");
_exit(0);
}
}
if (!gssd_check(CAST(void *, *gss_context)) || !gssd_check(CAST(void *, *cred_handle))) {
*major_stat = badcall("svc_mach_gss_init_context",
minor_stat, gss_context, cred_handle,
skey, skeyCnt,
otoken, otokenCnt);
end_worker_thread();
vproc_transaction_end(NULL, gssd_vproc_handle);
return KERN_SUCCESS;
}
if (*gss_context == CAST(gss_ctx, GSS_C_NO_CONTEXT)) {
if (no_canon || (gssd_flags & GSSD_NO_CANON))
gnames = 1;
*major_stat = str_to_svc_names(minor_stat,
svc_namestr, svc_gss_name, &gnames);
if (*major_stat != GSS_S_COMPLETE)
goto done;
}
else {
gnames = 1;
g_cntx = gssd_get_context(*gss_context, svc_gss_name);
if ((gssd_flags & GSSD_RESTART) && g_cntx != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&mstat, &g_cntx, GSS_C_NO_BUFFER);
}
if (*cred_handle && (gssd_flags & GSSD_RESTART)) {
gssd_remove(CAST(void *, *cred_handle));
(void) gss_release_cred(&mstat, (gss_cred_id_t *) cred_handle);
}
if (CAST(gss_cred_id_t, *cred_handle) == GSS_C_NO_CREDENTIAL || (gssd_flags & GSSD_RESTART)) {
*major_stat = do_acquire_cred(minor_stat, princ_namestr, mech,
*svc_gss_name, uid, cred_handle, gssd_flags);
if (*major_stat != GSS_S_COMPLETE)
goto done;
if (IS_NFS_SERVICE(svc_namestr)) {
*major_stat = gss_krb5_set_allowable_enctypes
(minor_stat, *(gss_cred_id_t *)cred_handle,
NUM_NFS_ENCTYPES - only_1des, NFS_ENCTYPES);
if (*major_stat != GSS_S_COMPLETE) {
ERR("Could not set enctypes for NFS\n");
goto done;
}
}
gssd_enter(CAST(void *, *cred_handle));
}
gss_buffer_desc intoken = {itokenCnt, itoken};
gss_buffer_desc outtoken = {0, NULL};
if ((gssd_flags & GSSD_WIN2K_HACK) && itokenCnt > 0)
spnego_win2k_hack(&intoken);
if (debug) {
DEBUG("Calling gss_init_sec_context\n");
DEBUG("Using mech = %d\n", mech);
DEBUG("\tminor_stat = %d\n", (int) *minor_stat);
DEBUG("\tcred = %p\n", *cred_handle);
DEBUG("\tgss_context = %p\n", *gss_context);
DEBUG("Flags = %0x gss flags = %0x\n", flags, gssd_flags);
DEBUG("\titokenCnt = %d\n", itokenCnt);
HexDump((char *)itoken, (itokenCnt > 80) ? 80 : itokenCnt);
}
*major_stat = GSS_S_BAD_NAME;
for (i = 0; i < gnames; i++) {
*major_stat = gss_init_sec_context(
minor_stat,
CAST(gss_cred_id_t, *cred_handle),
&g_cntx,
svc_gss_name[i],
mechtab[mech],
flags,
0,
NULL,
&intoken,
NULL,
&outtoken,
ret_flags,
NULL);
if (*major_stat == GSS_S_COMPLETE ||
*major_stat == GSS_S_CONTINUE_NEEDED)
break;
}
name_index = i;
for (i = 0; i < gnames; i++)
if (i != name_index)
(void)gss_release_name(&mstat, &svc_gss_name[i]);
vm_alloc_buffer(&outtoken, otoken, otokenCnt);
gss_release_buffer(&mstat, &outtoken);
if (*major_stat == GSS_S_COMPLETE) {
*major_stat = GetSessionKey(minor_stat, &g_cntx,
skey, skeyCnt, &lucid_ctx);
if (debug) {
DEBUG("Client key length = %d\n", *skeyCnt);
HexDump((char *) *skey, *skeyCnt);
}
} else if (*major_stat == GSS_S_CONTINUE_NEEDED) {
*gss_context = gssd_set_context(g_cntx, svc_gss_name[name_index]);
if (*gss_context == 0)
*major_stat = GSS_S_FAILURE;
}
done:
if (*major_stat != GSS_S_CONTINUE_NEEDED) {
gssd_remove(CAST(void *, *cred_handle));
(void) gss_release_cred(&mstat, (gss_cred_id_t *) cred_handle);
if (lucid_ctx)
(void) gss_krb5_free_lucid_sec_context(&mstat, lucid_ctx);
else if (g_cntx != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&mstat, &g_cntx, GSS_C_NO_BUFFER);
if (*gss_context)
gssd_remove(CAST(void *, *gss_context));
if (name_index < gnames)
(void)gss_release_name(&mstat, &svc_gss_name[name_index]);
}
if (debug) {
OSAtomicIncrement32(&initCnt);
if (*major_stat != GSS_S_CONTINUE_NEEDED &&
*major_stat != GSS_S_COMPLETE)
OSAtomicIncrement32(&initErr);
DEBUG("Returning from init (%d/%d)\n", initErr, initCnt);
}
if (debug || (*major_stat != GSS_S_CONTINUE_NEEDED &&
*major_stat != GSS_S_COMPLETE))
DISPLAY_ERRS("svc_mach_gss_init_sec_context", mechtab[mech],
*major_stat, *minor_stat);
if (debug) {
DEBUG("Returning from svc_mach_gss_init_sec_context");
DEBUG("\tcred = %p\n", *cred_handle);
DEBUG("\tgss_context = %p\n", *gss_context);
DEBUG("\totokenCnt = %d\n", *otokenCnt);
HexDump((char *)*otoken, (*otokenCnt > 80) ? 80 : *otokenCnt);
}
end_worker_thread();
vproc_transaction_end(NULL, gssd_vproc_handle);
return (KERN_SUCCESS);
}
kern_return_t
svc_mach_gss_accept_sec_context(
mach_port_t test_port __attribute__((unused)),
byte_buffer itoken, mach_msg_type_number_t itokenCnt,
string_t svc_namestr,
uint32_t gssd_flags __attribute__((unused)),
gss_ctx *gss_context,
gss_cred *cred_handle,
uint32_t *ret_flags,
uint32_t *uid,
gid_list gids, mach_msg_type_number_t *gidsCnt,
byte_buffer *skey, mach_msg_type_number_t *skeyCnt,
byte_buffer *otoken, mach_msg_type_number_t *otokenCnt,
uint32_t *major_stat,
uint32_t *minor_stat)
{
gss_ctx_id_t g_cntx = GSS_C_NO_CONTEXT;
gss_name_t princ;
gss_OID oid;
void *lucid_ctx = NULL;
uint32_t mstat;
kern_return_t kr = KERN_SUCCESS;
vproc_transaction_t gssd_vproc_handle;
gssd_vproc_handle = vproc_transaction_begin(NULL);
new_worker_thread();
if (kerb_init_failed) {
if (debug)
DEBUG("%s: is exiting since kerbos init failed\n", __func__);
end_worker_thread();
kill(getpid(), SIGTERM);
return (KERN_NOT_SUPPORTED);
}
*uid = NobodyUid;
if (debug) {
DEBUG("svc_mach_gss_accept_sec_context:\n");
if (die) {
DEBUG("Forced server death\n");
_exit(0);
}
}
DEBUG("Checking context and cred %llx %llX\n", *gss_context, *cred_handle);
if (!gssd_check(CAST(void *, *gss_context)) || !gssd_check(CAST(void *, *cred_handle))) {
*major_stat = badcall("svc_mach_gss_accept_sec_context",
minor_stat, gss_context, cred_handle,
skey, skeyCnt, otoken, otokenCnt);
end_worker_thread();
vproc_transaction_end(NULL, gssd_vproc_handle);
return (KERN_SUCCESS);
}
DEBUG("Getting context from %llx\n", *gss_context);
g_cntx = gssd_get_context(*gss_context, NULL);
DEBUG("Context is %p\n", g_cntx);
if (*cred_handle == CAST(gss_cred, GSS_C_NO_CREDENTIAL)) {
uint32_t i, gnames;
gss_name_t svc_gss_name[2];
if (debug)
DEBUG("\tgss_acquire_cred for service: %s\n",
svc_namestr);
gnames = (no_canon || (gssd_flags & GSSD_NO_CANON)) ? 1 : 2;
*major_stat = str_to_svc_names(minor_stat, svc_namestr,
svc_gss_name, &gnames);
if (*major_stat != GSS_S_COMPLETE)
goto done;
for (i = 0; i < gnames; i++) {
*major_stat = gss_acquire_cred(minor_stat,
svc_gss_name[i],
GSS_C_INDEFINITE,
GSS_C_NULL_OID_SET,
GSS_C_ACCEPT,
(gss_cred_id_t *) cred_handle,
NULL, NULL);
if (*major_stat == GSS_S_COMPLETE)
break;
}
for (i = 0; i < gnames; i++)
gss_release_name(&mstat, &svc_gss_name[i]);
if (*major_stat != GSS_S_COMPLETE)
goto done;
gssd_enter(CAST(void *, *cred_handle));
}
gss_buffer_desc intoken = {itokenCnt, itoken};
gss_buffer_desc outtoken = {0, NULL};;
*major_stat = 0;
*minor_stat = 0;
if (debug) {
DEBUG("Calling gss_accept_sec_context\n");
DEBUG("\tminor_stat = %d\n", (int) *minor_stat);
DEBUG("\tcred = %p\n", *cred_handle);
DEBUG("\tgss_context = %p\n", g_cntx);
DEBUG("\titokenCnt = %d\n", itokenCnt);
}
*major_stat = gss_accept_sec_context(
minor_stat,
&g_cntx, CAST(gss_cred_id_t, *cred_handle), &intoken, GSS_C_NO_CHANNEL_BINDINGS, &princ, &oid, &outtoken, ret_flags, NULL, NULL);
vm_alloc_buffer(&outtoken, otoken, otokenCnt);
gss_release_buffer(&mstat, &outtoken);
if (*major_stat == GSS_S_COMPLETE) {
*major_stat = gss_name_to_ucred(minor_stat, princ, realms,
uid, gids, gidsCnt);
if (*major_stat != GSS_S_COMPLETE) {
kr = KERN_FAILURE;
goto done;
}
*major_stat = GetSessionKey(minor_stat, &g_cntx,
skey, skeyCnt, &lucid_ctx);
if (debug) {
DEBUG("Server key length = %d\n", *skeyCnt);
HexDump((char *) *skey, *skeyCnt);
DEBUG("Returning uid = %d\n", *uid);
}
} else if (*major_stat == GSS_S_CONTINUE_NEEDED) {
*gss_context = gssd_set_context(g_cntx, NULL);
if (*gss_context == 0)
*major_stat = GSS_S_FAILURE;
gssd_enter(CAST(void *, *gss_context));
}
done:
gss_release_name(&mstat, &princ);
if (*major_stat != GSS_S_CONTINUE_NEEDED) {
gssd_remove(CAST(void *, *cred_handle));
(void)gss_release_cred(&mstat, (gss_cred_id_t *) cred_handle);
if (lucid_ctx)
(void) gss_krb5_free_lucid_sec_context(&mstat, lucid_ctx);
else if (g_cntx != GSS_C_NO_CONTEXT)
(void) gss_delete_sec_context(&mstat, &g_cntx, GSS_C_NO_BUFFER);
if (*gss_context)
gssd_remove(CAST(void *, *gss_context));
}
if (debug) {
OSAtomicIncrement32(&acceptCnt);
if (*major_stat != GSS_S_CONTINUE_NEEDED &&
*major_stat != GSS_S_COMPLETE)
OSAtomicIncrement32(&acceptErr);
DEBUG("Returning from accept (%d/%d)\n", acceptErr, acceptCnt);
}
if (*major_stat != GSS_S_CONTINUE_NEEDED &&
*major_stat != GSS_S_COMPLETE)
DISPLAY_ERRS("svc_mach_gss_accept_sec_context", oid,
*major_stat, *minor_stat);
end_worker_thread();
vproc_transaction_end(NULL, gssd_vproc_handle);
return (kr);
}
kern_return_t
svc_mach_gss_log_error(
mach_port_t test_port __attribute__((unused)),
string_t mnt,
uint32_t uid,
string_t source,
uint32_t major,
uint32_t minor)
{
OM_uint32 msg_context = 0;
OM_uint32 min_stat = 0;
OM_uint32 maj_stat = 0;
gss_buffer_desc errBuf;
char msgbuf[1024];
int full = 0;
new_worker_thread();
(void) snprintf(msgbuf, sizeof(msgbuf), "nfs %s Kerberos: %s, uid=%d",
source, mnt, uid);
do {
if (major == GSS_S_FAILURE) break;
maj_stat = gss_display_status(&min_stat, major, GSS_C_GSS_CODE,
GSS_C_NULL_OID, &msg_context, &errBuf);
if (maj_stat != GSS_S_COMPLETE)
goto done;
full = strlcat(msgbuf, " - ", sizeof(msgbuf)) >= sizeof(msgbuf) ||
strlcat(msgbuf, (char *) errBuf.value, sizeof(msgbuf)) >= sizeof(msgbuf);
(void) gss_release_buffer(&min_stat, &errBuf);
if (full)
goto done;
} while (msg_context != 0);
msg_context = 0;
do {
maj_stat = gss_display_status (&min_stat, minor, GSS_C_MECH_CODE,
GSS_C_NULL_OID, &msg_context, &errBuf);
if (maj_stat != GSS_S_COMPLETE)
goto done;
full = strlcat(msgbuf, " - ", sizeof(msgbuf)) >= sizeof(msgbuf) ||
strlcat(msgbuf, (char *) errBuf.value, sizeof(msgbuf)) >= sizeof(msgbuf);
(void) gss_release_buffer(&min_stat, &errBuf);
if (full)
goto done;
} while (msg_context != 0);
done:
syslog(LOG_ERR, "%s", msgbuf);
end_worker_thread();
return (KERN_SUCCESS);
}
static void
CGSSDisplay_errs(char* rtnName, gss_OID mech, OM_uint32 maj, OM_uint32 min)
{
OM_uint32 msg_context = 0;
OM_uint32 min_stat = 0;
OM_uint32 maj_stat = 0;
gss_buffer_desc errBuf;
int count = 1;
ERR("Error returned by %s:\n", rtnName);
do {
maj_stat = gss_display_status(&min_stat, maj, GSS_C_GSS_CODE,
mech, &msg_context, &errBuf);
if (count == 1)
ERR("\tMajor error = %d: %s\n", maj, (char *)errBuf.value);
else
ERR("\t\t%s\n", (char *)errBuf.value);
(void)gss_release_buffer(&min_stat, &errBuf);
++count;
} while (msg_context != 0);
count = 1;
msg_context = 0;
do {
maj_stat = gss_display_status (&min_stat, min, GSS_C_MECH_CODE,
mech, &msg_context, &errBuf);
if (count == 1)
ERR("\tMinor error = %d: %s\n", min, (char *)errBuf.value);
else
ERR("\t\t%s\n", (char *)errBuf.value);
(void)gss_release_buffer(&min_stat, &errBuf);
++count;
} while (msg_context != 0);
}
static const char HexChars[16] = {
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
};
static void
HexLine(const char *buf, uint32_t *bufSize, char linebuf[80])
{
char *bptr = buf;
int limit;
int i;
char *cptr = linebuf;
memset(linebuf,0,sizeof(linebuf));
limit = (*bufSize > 16) ? 16 : *bufSize;
*bufSize -= limit;
for(i = 0; i < 16; i++)
{
if(i < limit)
{
*cptr++ = HexChars[(*bptr >> 4) & 0x0f];
*cptr++ = HexChars[*bptr & 0x0f];
*cptr++ = ' ';
bptr++;
} else {
*cptr++ = ' ';
*cptr++ = ' ';
*cptr++ = ' ';
}
}
bptr = buf;
*cptr++ = ' ';
*cptr++ = ' ';
*cptr++ = ' ';
for(i = 0; i < limit; i++)
{
*cptr++ = (char) (((*bptr > 0x1f) && (*bptr < 0x7f)) ? *bptr : '.');
bptr++;
}
*cptr++ = '\n';
*cptr = '\0';
}
static void
HexDump(const char *inBuffer, uint32_t inLength)
{
uint32_t currentSize = inLength;
char linebuf[80];
while(currentSize > 0)
{
HexLine(inBuffer, ¤tSize, linebuf);
DEBUG("%s", linebuf);
inBuffer += 16;
}
}