1/*
2 * Copyright (c) 2009 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 *    may be used to endorse or promote products derived from this software
21 *    without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36#define HEIMDAL_PRINTF_ATTRIBUTE(x)
37
38#include "mech_locl.h"
39#include "heim_threads.h"
40#include "heimbase.h"
41
42#include <asl.h>
43#include <CoreFoundation/CoreFoundation.h>
44#include <krb5.h>
45
46struct mg_thread_ctx {
47    gss_OID mech;
48    OM_uint32 min_stat;
49    gss_buffer_desc min_error;
50    aslclient asl;
51    aslmsg msg;
52};
53
54static HEIMDAL_MUTEX context_mutex = HEIMDAL_MUTEX_INITIALIZER;
55static int created_key;
56static HEIMDAL_thread_key context_key;
57
58
59static void
60destroy_context(void *ptr)
61{
62    struct mg_thread_ctx *mg = ptr;
63    OM_uint32 junk;
64
65    if (mg == NULL)
66	return;
67
68    gss_release_buffer(&junk, &mg->min_error);
69
70    if (mg->msg)
71	asl_free(mg->msg);
72    if (mg->asl)
73	asl_close(mg->asl);
74
75    free(mg);
76}
77
78
79static struct mg_thread_ctx *
80_gss_mechglue_thread(void)
81{
82    struct mg_thread_ctx *ctx;
83    int ret = 0;
84
85    HEIMDAL_MUTEX_lock(&context_mutex);
86
87    if (!created_key) {
88	HEIMDAL_key_create(&context_key, destroy_context, ret);
89	if (ret) {
90	    HEIMDAL_MUTEX_unlock(&context_mutex);
91	    return NULL;
92	}
93	created_key = 1;
94    }
95    HEIMDAL_MUTEX_unlock(&context_mutex);
96
97    ctx = HEIMDAL_getspecific(context_key);
98    if (ctx == NULL) {
99
100	ctx = calloc(1, sizeof(*ctx));
101	if (ctx == NULL)
102	    return NULL;
103	HEIMDAL_setspecific(context_key, ctx, ret);
104	if (ret) {
105	    free(ctx);
106	    return NULL;
107	}
108
109	ctx->asl = asl_open(getprogname(), NULL, 0);
110	ctx->msg = asl_new(ASL_TYPE_MSG);
111	asl_set(ctx->msg, "org.h5l.asl", "gssapi");
112    }
113    return ctx;
114}
115
116OM_uint32
117_gss_mg_get_error(const gss_OID mech, OM_uint32 value, gss_buffer_t string)
118{
119    struct mg_thread_ctx *mg;
120
121    mg = _gss_mechglue_thread();
122    if (mg == NULL)
123	return GSS_S_BAD_STATUS;
124
125    if (value != mg->min_stat || mg->min_error.length == 0) {
126	_mg_buffer_zero(string);
127	return GSS_S_BAD_STATUS;
128    }
129    string->value = malloc(mg->min_error.length);
130    if (string->value == NULL) {
131	_mg_buffer_zero(string);
132	return GSS_S_FAILURE;
133    }
134    string->length = mg->min_error.length;
135    memcpy(string->value, mg->min_error.value, mg->min_error.length);
136    return GSS_S_COMPLETE;
137}
138
139void
140_gss_mg_error(gssapi_mech_interface m, OM_uint32 min)
141{
142    OM_uint32 major_status, minor_status;
143    OM_uint32 message_content = 0;
144    struct mg_thread_ctx *mg;
145
146    /*
147     * Mechs without gss_display_status() does
148     * gss_mg_collect_error() by themself.
149     */
150    if (m->gm_display_status == NULL)
151	return ;
152
153    mg = _gss_mechglue_thread();
154    if (mg == NULL)
155	return;
156
157    gss_release_buffer(&minor_status, &mg->min_error);
158
159    mg->mech = &m->gm_mech_oid;
160    mg->min_stat = min;
161
162    major_status = m->gm_display_status(&minor_status,
163					min,
164					GSS_C_MECH_CODE,
165					&m->gm_mech_oid,
166					&message_content,
167					&mg->min_error);
168    if (major_status != GSS_S_COMPLETE) {
169	_mg_buffer_zero(&mg->min_error);
170    } else {
171	_gss_mg_log(5, "_gss_mg_error: captured %.*s (%d) from underlaying mech %s",
172		    (int)mg->min_error.length, (const char *)mg->min_error.value,
173		    (int)min, m->gm_name);
174    }
175}
176
177void
178gss_mg_collect_error(gss_OID mech, OM_uint32 maj, OM_uint32 min)
179{
180    gssapi_mech_interface m = __gss_get_mechanism(mech);
181    if (m == NULL)
182	return;
183    _gss_mg_error(m, min);
184}
185
186OM_uint32
187gss_mg_set_error_string(gss_OID mech,
188			OM_uint32 maj, OM_uint32 min,
189			const char *fmt, ...)
190    HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5))
191{
192    struct mg_thread_ctx *mg;
193    char *str = NULL;
194    OM_uint32 junk;
195    va_list ap;
196
197    mg = _gss_mechglue_thread();
198    if (mg == NULL)
199	return maj;
200
201    va_start(ap, fmt);
202    vasprintf(&str, fmt, ap);
203    va_end(ap);
204
205    if (str) {
206	gss_release_buffer(&junk, &mg->min_error);
207
208	mg->mech = mech;
209	mg->min_stat = min;
210
211	mg->min_error.value = str;
212	mg->min_error.length = strlen(str);
213
214	_gss_mg_log(5, "gss_mg_set_error_string: %.*s (%d/%d)",
215		    (int)mg->min_error.length, (const char *)mg->min_error.value,
216		    (int)maj, (int)min);
217    }
218    return maj;
219}
220
221#ifdef __APPLE__
222
223#include <CoreFoundation/CoreFoundation.h>
224
225CFErrorRef
226_gss_mg_cferror(OM_uint32 major_status,
227		OM_uint32 minor_status,
228		gss_const_OID mech)
229{
230    struct mg_thread_ctx *mg;
231    CFErrorRef e;
232#define NUM_ERROR_DESC 5
233    void const *keys[NUM_ERROR_DESC] = {
234	CFSTR("kGSSMajorErrorCode"),
235	CFSTR("kGSSMinorErrorCode"),
236	CFSTR("kGSSMechanismOID"),
237	CFSTR("kGSSMechanism"),
238	kCFErrorDescriptionKey
239    };
240    void const *values[NUM_ERROR_DESC] = { 0 };
241    gss_buffer_desc oid;
242    const char *name;
243    OM_uint32 junk;
244    size_t n;
245
246    values[0] = CFNumberCreate(NULL, kCFNumberSInt32Type, &major_status);
247    values[1] = CFNumberCreate(NULL, kCFNumberSInt32Type, &minor_status);
248
249    if (mech && gss_oid_to_str(&junk, (gss_OID)mech, &oid) == GSS_S_COMPLETE) {
250	values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"), (int)oid.length, (char *)oid.value);
251	gss_release_buffer(&junk, &oid);
252    } else {
253	values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no-mech"));
254    }
255
256    if (mech && (name = gss_oid_to_name(mech)) != NULL) {
257	values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), name);;
258    }  else {
259	values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no mech given"));
260    }
261
262    mg = _gss_mechglue_thread();
263    if (mg && minor_status == mg->min_stat && mg->min_error.length != 0) {
264	values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"),
265					       (int)mg->min_error.length,
266					       mg->min_error.value);
267    } else {
268	values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("Unknown minor status: %d"), (int)minor_status);
269    }
270
271    e = CFErrorCreateWithUserInfoKeysAndValues(NULL,
272					       CFSTR("org.h5l.GSS"),
273					       (CFIndex)major_status,
274					       keys,
275					       values,
276					       NUM_ERROR_DESC);
277    for (n = 0; n < sizeof(values) / sizeof(values[0]); n++)
278	CFRelease(values[n]);
279
280    return e;
281}
282
283
284
285static CFTypeRef
286CopyKeyFromFile(CFStringRef domain, CFStringRef key)
287{
288    CFReadStreamRef s;
289    CFDictionaryRef d;
290    CFStringRef file;
291    CFErrorRef e;
292    CFURLRef url;
293    CFTypeRef val;
294
295    file = CFStringCreateWithFormat(NULL, 0, CFSTR("/Library/Preferences/%@.plist"), domain);
296    if (file == NULL)
297	return NULL;
298
299    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, file, kCFURLPOSIXPathStyle, false);
300    CFRelease(file);
301    if (url == NULL)
302	return NULL;
303
304    s = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
305    CFRelease(url);
306    if (s == NULL)
307	return NULL;
308
309    if (!CFReadStreamOpen(s)) {
310	CFRelease(s);
311	return NULL;
312    }
313
314    d = (CFDictionaryRef)CFPropertyListCreateWithStream (kCFAllocatorDefault, s, 0, kCFPropertyListImmutable, NULL, &e);
315    CFRelease(s);
316    if (d == NULL)
317	return NULL;
318
319    if (CFGetTypeID(d) != CFDictionaryGetTypeID()) {
320	CFRelease(d);
321	return NULL;
322    }
323
324    val = CFDictionaryGetValue(d, key);
325    if (val)
326	CFRetain(val);
327    CFRelease(d);
328    return val;
329}
330
331
332CFTypeRef
333_gss_mg_copy_key(CFStringRef domain, CFStringRef key)
334{
335    CFTypeRef val;
336
337    /*
338     * First prefer system file, then user copy if we are allowed to
339     * touch user home directory.
340     */
341
342    val = CopyKeyFromFile(domain, key);
343
344    if (val == NULL && krb5_homedir_access(NULL)) {
345	val = CFPreferencesCopyAppValue(key, domain);
346	if (val == NULL)
347	    val = CFPreferencesCopyValue(key, domain, kCFPreferencesAnyUser, kCFPreferencesAnyHost);
348    }
349    return val;
350}
351
352#endif
353
354static int log_level = 1;
355static int asl_level = ASL_LEVEL_DEBUG;
356
357static void
358init_log(void *ptr)
359{
360    CFTypeRef val;
361
362    val = _gss_mg_copy_key(CFSTR("com.apple.GSS"), CFSTR("DebugLevel"));
363    if (val == NULL)
364	return;
365
366    if (CFGetTypeID(val) == CFBooleanGetTypeID())
367	log_level = CFBooleanGetValue(val) ? 1 : 0;
368    else if (CFGetTypeID(val) == CFNumberGetTypeID())
369	CFNumberGetValue(val, kCFNumberIntType, &log_level);
370    else
371	/* ignore other types */;
372
373    CFRelease(val);
374
375    if (log_level > 0)
376	asl_level = ASL_LEVEL_NOTICE;
377}
378
379int
380_gss_mg_log_level(int level)
381{
382    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
383
384    heim_base_once_f(&once, NULL, init_log);
385
386    return (level > log_level) ? 0 : 1;
387}
388
389void
390_gss_mg_log(int level, const char *fmt, ...)
391    HEIMDAL_PRINTF_ATTRIBUTE((printf, 2, 3))
392{
393    struct mg_thread_ctx *mg;
394    va_list ap;
395
396    if (!_gss_mg_log_level(level))
397	return;
398
399    mg = _gss_mechglue_thread();
400    if (mg == NULL)
401	return;
402
403    va_start(ap, fmt);
404    asl_vlog(mg->asl, mg->msg, asl_level, fmt, ap);
405    va_end(ap);
406}
407
408void
409_gss_mg_log_name(int level, struct _gss_name *name, gss_OID mech_type, const char *fmt, ...)
410    HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5))
411{
412    struct _gss_mechanism_name *mn = NULL;
413    gssapi_mech_interface m;
414    OM_uint32 junk;
415
416    if (!_gss_mg_log_level(level))
417        return;
418
419    m = __gss_get_mechanism(mech_type);
420    if (m == NULL)
421        return;
422
423    if (_gss_find_mn(&junk, name, mech_type, &mn) == GSS_S_COMPLETE) {
424	OM_uint32 maj_stat = GSS_S_COMPLETE;
425	gss_buffer_desc namebuf;
426
427	if (mn == NULL) {
428	    namebuf.value = "no name";
429	    namebuf.length = strlen((char *)namebuf.value);
430	} else {
431	    maj_stat = m->gm_display_name(&junk, mn->gmn_name,
432					  &namebuf, NULL);
433	}
434	if (maj_stat == GSS_S_COMPLETE) {
435	    char *str = NULL;
436	    va_list ap;
437
438	    va_start(ap, fmt);
439	    vasprintf(&str, fmt, ap);
440	    va_end(ap);
441
442	    if (str)
443	        _gss_mg_log(level, "%s %.*s", str,
444			    (int)namebuf.length, (char *)namebuf.value);
445	    free(str);
446	    if (mn != NULL)
447		gss_release_buffer(&junk, &namebuf);
448	}
449    }
450
451}
452
453void
454_gss_mg_log_cred(int level, struct _gss_cred *cred, const char *fmt, ...)
455    HEIMDAL_PRINTF_ATTRIBUTE((printf, 3, 4))
456{
457    struct _gss_mechanism_cred *mc;
458    char *str;
459    va_list ap;
460
461    if (!_gss_mg_log_level(level))
462        return;
463
464    va_start(ap, fmt);
465    vasprintf(&str, fmt, ap);
466    va_end(ap);
467
468    if (cred) {
469	HEIM_SLIST_FOREACH(mc, &cred->gc_mc, gmc_link) {
470	    _gss_mg_log(1, "%s: %s", str, mc->gmc_mech->gm_name);
471	}
472    } else {
473	_gss_mg_log(1, "%s: GSS_C_NO_CREDENTIAL", str);
474    }
475    free(str);
476}
477
478static const char *paths[] = {
479    "/Library/KerberosPlugins/GSSAPI",
480    "/System/Library/KerberosPlugins/GSSAPI",
481    NULL
482};
483
484static void
485load_plugins(void *ptr)
486{
487    krb5_context context;
488    if (krb5_init_context(&context))
489	return;
490    krb5_load_plugins(context, "gss", paths);
491    krb5_free_context(context);
492}
493
494
495void
496_gss_load_plugins(void)
497{
498    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
499    heim_base_once_f(&once, NULL, load_plugins);
500}
501