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#include "mech_locl.h"
37#include "heim_threads.h"
38#include "heimbase.h"
39
40#include <asl.h>
41#include <CoreFoundation/CoreFoundation.h>
42#include <krb5.h>
43#include <notify.h>
44
45#undef HEIMDAL_PRINTF_ATTRIBUTE
46#define HEIMDAL_PRINTF_ATTRIBUTE(x)
47
48struct mg_thread_ctx {
49    gss_OID mech;
50    OM_uint32 min_stat;
51    gss_buffer_desc min_error;
52    aslclient asl;
53    aslmsg msg;
54};
55
56static HEIMDAL_MUTEX context_mutex = HEIMDAL_MUTEX_INITIALIZER;
57static int created_key;
58static HEIMDAL_thread_key context_key;
59
60
61static void
62destroy_context(void *ptr)
63{
64    struct mg_thread_ctx *mg = ptr;
65    OM_uint32 junk;
66
67    if (mg == NULL)
68	return;
69
70    gss_release_buffer(&junk, &mg->min_error);
71
72    if (mg->msg)
73	asl_free(mg->msg);
74    if (mg->asl)
75	asl_close(mg->asl);
76
77    free(mg);
78}
79
80
81static struct mg_thread_ctx *
82_gss_mechglue_thread(void)
83{
84    struct mg_thread_ctx *ctx;
85    int ret = 0;
86
87    HEIMDAL_MUTEX_lock(&context_mutex);
88
89    if (!created_key) {
90	HEIMDAL_key_create(&context_key, destroy_context, ret);
91	if (ret) {
92	    HEIMDAL_MUTEX_unlock(&context_mutex);
93	    return NULL;
94	}
95	created_key = 1;
96    }
97    HEIMDAL_MUTEX_unlock(&context_mutex);
98
99    ctx = HEIMDAL_getspecific(context_key);
100    if (ctx == NULL) {
101
102	ctx = calloc(1, sizeof(*ctx));
103	if (ctx == NULL)
104	    return NULL;
105	HEIMDAL_setspecific(context_key, ctx, ret);
106	if (ret) {
107	    free(ctx);
108	    return NULL;
109	}
110
111	ctx->asl = asl_open(getprogname(), NULL, 0);
112	ctx->msg = asl_new(ASL_TYPE_MSG);
113	asl_set(ctx->msg, "org.h5l.asl", "gssapi");
114    }
115    return ctx;
116}
117
118OM_uint32
119_gss_mg_get_error(const gss_OID mech, OM_uint32 value, gss_buffer_t string)
120{
121    struct mg_thread_ctx *mg;
122
123    mg = _gss_mechglue_thread();
124    if (mg == NULL)
125	return GSS_S_BAD_STATUS;
126
127    if (value != mg->min_stat || mg->min_error.length == 0) {
128	_mg_buffer_zero(string);
129	return GSS_S_BAD_STATUS;
130    }
131    string->value = malloc(mg->min_error.length);
132    if (string->value == NULL) {
133	_mg_buffer_zero(string);
134	return GSS_S_FAILURE;
135    }
136    string->length = mg->min_error.length;
137    memcpy(string->value, mg->min_error.value, mg->min_error.length);
138    return GSS_S_COMPLETE;
139}
140
141void
142_gss_mg_error(struct gssapi_mech_interface_desc *m, OM_uint32 min)
143{
144    OM_uint32 major_status, minor_status;
145    OM_uint32 message_content = 0;
146    struct mg_thread_ctx *mg;
147
148    /*
149     * Mechs without gss_display_status() does
150     * gss_mg_collect_error() by themself.
151     */
152    if (m->gm_display_status == NULL)
153	return ;
154
155    mg = _gss_mechglue_thread();
156    if (mg == NULL)
157	return;
158
159    gss_release_buffer(&minor_status, &mg->min_error);
160
161    mg->mech = &m->gm_mech_oid;
162    mg->min_stat = min;
163
164    major_status = m->gm_display_status(&minor_status,
165					min,
166					GSS_C_MECH_CODE,
167					&m->gm_mech_oid,
168					&message_content,
169					&mg->min_error);
170    if (major_status != GSS_S_COMPLETE) {
171	_mg_buffer_zero(&mg->min_error);
172    } else {
173	_gss_mg_log(5, "_gss_mg_error: captured %.*s (%d) from underlaying mech %s",
174		    (int)mg->min_error.length, (const char *)mg->min_error.value,
175		    (int)min, m->gm_name);
176    }
177}
178
179void
180gss_mg_collect_error(gss_OID mech, OM_uint32 maj, OM_uint32 min)
181{
182    gssapi_mech_interface m = __gss_get_mechanism(mech);
183    if (m == NULL)
184	return;
185    _gss_mg_error(m, min);
186}
187
188OM_uint32
189gss_mg_set_error_string(gss_OID mech,
190			OM_uint32 maj, OM_uint32 min,
191			const char *fmt, ...)
192    HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5))
193{
194    struct mg_thread_ctx *mg;
195    char *str = NULL;
196    OM_uint32 junk;
197    va_list ap;
198
199    mg = _gss_mechglue_thread();
200    if (mg == NULL)
201	return maj;
202
203    va_start(ap, fmt);
204    vasprintf(&str, fmt, ap);
205    va_end(ap);
206
207    if (str) {
208	gss_release_buffer(&junk, &mg->min_error);
209
210	mg->mech = mech;
211	mg->min_stat = min;
212
213	mg->min_error.value = str;
214	mg->min_error.length = strlen(str);
215
216	_gss_mg_log(5, "gss_mg_set_error_string: %.*s (%d/%d)",
217		    (int)mg->min_error.length, (const char *)mg->min_error.value,
218		    (int)maj, (int)min);
219    }
220    return maj;
221}
222
223#ifdef __APPLE__
224
225#include <CoreFoundation/CoreFoundation.h>
226
227CFErrorRef
228_gss_mg_create_cferror(OM_uint32 major_status,
229		       OM_uint32 minor_status,
230		       gss_const_OID mech)
231{
232    struct mg_thread_ctx *mg;
233    CFErrorRef e;
234#define NUM_ERROR_DESC 5
235    void const *keys[NUM_ERROR_DESC] = {
236	CFSTR("kGSSMajorErrorCode"),
237	CFSTR("kGSSMinorErrorCode"),
238	CFSTR("kGSSMechanismOID"),
239	CFSTR("kGSSMechanism"),
240	kCFErrorDescriptionKey
241    };
242    void const *values[NUM_ERROR_DESC] = { 0 };
243    gss_buffer_desc oid;
244    const char *name;
245    OM_uint32 junk;
246    size_t n;
247
248    values[0] = CFNumberCreate(NULL, kCFNumberSInt32Type, &major_status);
249    values[1] = CFNumberCreate(NULL, kCFNumberSInt32Type, &minor_status);
250
251    if (mech && gss_oid_to_str(&junk, (gss_OID)mech, &oid) == GSS_S_COMPLETE) {
252	values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"), (int)oid.length, (char *)oid.value);
253	gss_release_buffer(&junk, &oid);
254    } else {
255	values[2] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no-mech"));
256    }
257
258    if (mech && (name = gss_oid_to_name(mech)) != NULL) {
259	values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), name);;
260    }  else {
261	values[3] = CFStringCreateWithFormat(NULL, NULL, CFSTR("no mech given"));
262    }
263
264    mg = _gss_mechglue_thread();
265    if (mg && minor_status == mg->min_stat && mg->min_error.length != 0) {
266	values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("%.*s"),
267					       (int)mg->min_error.length,
268					       mg->min_error.value);
269    } else {
270	values[4] = CFStringCreateWithFormat(NULL, NULL, CFSTR("Unknown minor status: %d"), (int)minor_status);
271    }
272
273    e = CFErrorCreateWithUserInfoKeysAndValues(NULL,
274					       CFSTR("org.h5l.GSS"),
275					       (CFIndex)major_status,
276					       keys,
277					       values,
278					       NUM_ERROR_DESC);
279    for (n = 0; n < sizeof(values) / sizeof(values[0]); n++)
280	CFRelease(values[n]);
281
282    return e;
283}
284
285
286static CFTypeRef
287CopyKeyFromFile(CFStringRef file, CFStringRef key)
288{
289    CFReadStreamRef s;
290    CFDictionaryRef d;
291    CFErrorRef e;
292    CFURLRef url;
293    CFTypeRef val;
294
295    url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, file, kCFURLPOSIXPathStyle, false);
296    if (url == NULL)
297	return NULL;
298
299    s = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
300    CFRelease(url);
301    if (s == NULL)
302	return NULL;
303
304    if (!CFReadStreamOpen(s)) {
305	CFRelease(s);
306	return NULL;
307    }
308
309    d = (CFDictionaryRef)CFPropertyListCreateWithStream (kCFAllocatorDefault, s, 0, kCFPropertyListImmutable, NULL, &e);
310    CFRelease(s);
311    if (d == NULL)
312	return NULL;
313
314    if (CFGetTypeID(d) != CFDictionaryGetTypeID()) {
315	CFRelease(d);
316	return NULL;
317    }
318
319    val = CFDictionaryGetValue(d, key);
320    if (val)
321	CFRetain(val);
322    CFRelease(d);
323    return val;
324}
325
326
327static CFTypeRef
328CopyKeyFromDomain(CFStringRef domain, CFStringRef key)
329{
330    CFStringRef file;
331    CFTypeRef val = NULL;
332
333    file = CFStringCreateWithFormat(NULL, 0, CFSTR("/Library/Preferences/%@.plist"), domain);
334    if (file) {
335	val = CopyKeyFromFile(file, key);
336	CFRelease(file);
337    }
338
339    return val;
340}
341
342static CFTypeRef
343CopyMangedPreference(CFStringRef key)
344{
345#if __APPLE_TARGET_EMBEDDED__
346#define GLOBAL_PREFERENCE_FILE CFSTR("/Library/Managed Preferences/mobile/.GlobalPreferences.plist")
347#else
348#define GLOBAL_PREFERENCE_FILE CFSTR("/Library/Managed Preferences/.GlobalPreferences.plist")
349#endif
350
351    return CopyKeyFromFile(GLOBAL_PREFERENCE_FILE, key);
352}
353
354CFTypeRef
355_gss_mg_copy_key(CFStringRef domain, CFStringRef key)
356{
357    CFTypeRef val;
358
359    /*
360     * First prefer system file, then user copy if we are allowed to
361     * touch user home directory.
362     */
363
364    val = CopyKeyFromDomain(domain, key);
365
366    if (val == NULL && krb5_homedir_access(NULL)) {
367	val = CFPreferencesCopyAppValue(key, domain);
368	if (val == NULL)
369	    val = CFPreferencesCopyValue(key, domain, kCFPreferencesAnyUser, kCFPreferencesAnyHost);
370    }
371    return val;
372}
373
374#endif
375
376static int log_level = 0;
377static int asl_level = ASL_LEVEL_ERR;
378static void *log_ctx = NULL;
379static HEIMDAL_MUTEX log_mutex = HEIMDAL_MUTEX_INITIALIZER;
380static int config_token = -1;
381static void (*log_func)(void *ctx, int level, const char *fmt, va_list) = NULL;
382
383void
384gss_set_log_function(void *ctx, void (*func)(void *ctx, int level, const char *fmt, va_list))
385{
386    if (log_func == NULL) {
387	log_func = func;
388	log_ctx = ctx;
389    }
390}
391
392static void
393init_log(void)
394{
395    CFTypeRef val;
396
397    val = _gss_mg_copy_key(CFSTR("com.apple.GSS"), CFSTR("DebugLevel"));
398    if (val == NULL) {
399	/*
400	 * Pick up global preferences that can be configured via a
401	 * profile.
402	 */
403	if (geteuid() == 0 || !krb5_homedir_access(NULL)) {
404	    val = CopyMangedPreference(CFSTR("GSSDebugLevel"));
405	} else {
406	    val = CFPreferencesCopyAppValue(CFSTR("GSSDebugLevel"),
407					    CFSTR(".GlobalPreferences"));
408	}
409    }
410
411    if (val == NULL)
412	return;
413
414    HEIMDAL_MUTEX_lock(&log_mutex);
415
416    if (CFGetTypeID(val) == CFBooleanGetTypeID())
417	log_level = CFBooleanGetValue(val) ? 1 : 0;
418    else if (CFGetTypeID(val) == CFNumberGetTypeID())
419	CFNumberGetValue(val, kCFNumberIntType, &log_level);
420    else
421	/* ignore other types */;
422
423    CFRelease(val);
424
425    if (log_level > 0)
426	asl_level = ASL_LEVEL_ERR;
427
428    HEIMDAL_MUTEX_unlock(&log_mutex);
429}
430
431static void
432setup_logging(void *ptr)
433{
434     init_log();
435     notify_register_check("com.apple.ManagedConfiguration.profileListChanged", &config_token);
436}
437
438int
439_gss_mg_log_level(int level)
440{
441    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
442
443    heim_base_once_f(&once, NULL, setup_logging);
444
445    if (config_token != -1) {
446	int ret, check = 0;
447	ret = notify_check(config_token, &check);
448	if (ret == NOTIFY_STATUS_OK && check)
449	    init_log();
450    }
451
452    return (level > log_level) ? 0 : 1;
453}
454
455void
456_gss_mg_log(int level, const char *fmt, ...)
457    HEIMDAL_PRINTF_ATTRIBUTE((printf, 2, 3))
458{
459    struct mg_thread_ctx *mg;
460    va_list ap;
461
462    if (!_gss_mg_log_level(level))
463	return;
464
465    mg = _gss_mechglue_thread();
466    if (mg == NULL)
467	return;
468
469    va_start(ap, fmt);
470    asl_vlog(mg->asl, mg->msg, asl_level, fmt, ap);
471    va_end(ap);
472
473    if (log_func) {
474	va_start(ap, fmt);
475	log_func(log_ctx, level, fmt, ap);
476	va_end(ap);
477    }
478}
479
480void
481_gss_mg_log_name(int level, struct _gss_name *name, gss_OID mech_type, const char *fmt, ...)
482    HEIMDAL_PRINTF_ATTRIBUTE((printf, 4, 5))
483{
484    struct _gss_mechanism_name *mn = NULL;
485    gssapi_mech_interface m;
486    OM_uint32 junk;
487
488    if (!_gss_mg_log_level(level))
489        return;
490
491    m = __gss_get_mechanism(mech_type);
492    if (m == NULL)
493        return;
494
495    if (_gss_find_mn(&junk, name, mech_type, &mn) == GSS_S_COMPLETE) {
496	OM_uint32 maj_stat = GSS_S_COMPLETE;
497	gss_buffer_desc namebuf;
498
499	if (mn == NULL) {
500	    namebuf.value = "no name";
501	    namebuf.length = strlen((char *)namebuf.value);
502	} else {
503	    maj_stat = m->gm_display_name(&junk, mn->gmn_name,
504					  &namebuf, NULL);
505	}
506	if (maj_stat == GSS_S_COMPLETE) {
507	    char *str = NULL;
508	    va_list ap;
509
510	    va_start(ap, fmt);
511	    vasprintf(&str, fmt, ap);
512	    va_end(ap);
513
514	    if (str)
515	        _gss_mg_log(level, "%s %.*s", str,
516			    (int)namebuf.length, (char *)namebuf.value);
517	    free(str);
518	    if (mn != NULL)
519		gss_release_buffer(&junk, &namebuf);
520	}
521    }
522
523}
524
525void
526_gss_mg_log_cred(int level, struct _gss_cred *cred, const char *fmt, ...)
527    HEIMDAL_PRINTF_ATTRIBUTE((printf, 3, 4))
528{
529    struct _gss_mechanism_cred *mc;
530    char *str;
531    va_list ap;
532
533    if (!_gss_mg_log_level(level))
534        return;
535
536    va_start(ap, fmt);
537    vasprintf(&str, fmt, ap);
538    va_end(ap);
539
540    if (cred) {
541	HEIM_SLIST_FOREACH(mc, &cred->gc_mc, gmc_link) {
542	    _gss_mg_log(1, "%s: %s", str, mc->gmc_mech->gm_name);
543	}
544    } else {
545	_gss_mg_log(1, "%s: GSS_C_NO_CREDENTIAL", str);
546    }
547    free(str);
548}
549
550#if TARGET_IPHONE_SIMULATOR
551#define PLUGIN_PREFIX "%{IPHONE_SIMULATOR_ROOT}"
552#else
553#define PLUGIN_PREFIX ""
554#endif
555
556
557static const char *paths[] = {
558    PLUGIN_PREFIX "/Library/KerberosPlugins/GSSAPI",
559    PLUGIN_PREFIX "/System/Library/KerberosPlugins/GSSAPI",
560    NULL
561};
562
563static void
564load_plugins(void *ptr)
565{
566    krb5_context context;
567    if (krb5_init_context(&context))
568	return;
569    krb5_load_plugins(context, "gss", paths);
570    krb5_free_context(context);
571}
572
573
574void
575_gss_load_plugins(void)
576{
577    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
578    heim_base_once_f(&once, NULL, load_plugins);
579}
580