#include <Kerberos/com_err.h>
#include <Kerberos/KerberosDebug.h>
#include <Kerberos/KerberosLoginPrivate.h> // for __KLApplicationGetTextEncoding
#include <CoreFoundation/CoreFoundation.h>
#include "com_err_threads.h"
#define UNKNOWN_ERROR_FORMAT "Unknown Error Code: %ld"
#define UNKNOWN_ERROR_MESSAGE "Unknown error"
#define UNKNOWN_ERROR_MANAGER "Unknown API"
#define UNKNOWN_ERROR_MAX_SIZE 256
#define MESSAGE_KEY_FORMAT "KEMessage %ld"
#define MANAGER_KEY_FORMAT "KEManager %ld"
#define MAX_KEY_LENGTH 256
#define MAX_ERROR_MESSAGE_SIZE BUFSIZ
#define ERROR_TABLE_SIZE_INCREMENT 10
#define ERRCODE_RANGE 8
#define BITS_PER_CHAR 6
#pragma mark -
errcode_t add_error_table (const struct error_table *et)
{
error_table_array_t error_tables = NULL;
errcode_t lock_err = com_err_thread_lock_error_tables (&error_tables);
errcode_t err = lock_err;
if (!err) {
if (et == NULL) { err = EINVAL; }
}
if (!err) {
if ((error_tables->count + 1) > error_tables->size) {
const struct error_table **new_ets = NULL;
size_t new_size = error_tables->size + ERROR_TABLE_SIZE_INCREMENT;
new_ets = (const struct error_table **) realloc (error_tables->ets, (sizeof (char *)) * new_size);
if (new_ets == NULL) {
err = ENOMEM;
} else {
error_tables->ets = new_ets;
error_tables->size = new_size;
}
}
}
if (!err) {
error_tables->ets[error_tables->count] = et; error_tables->count++;
}
if (!lock_err) { com_err_thread_unlock_error_tables (&error_tables); }
return err;
}
errcode_t remove_error_table (const struct error_table *et)
{
error_table_array_t error_tables = NULL;
errcode_t lock_err = com_err_thread_lock_error_tables (&error_tables);
errcode_t err = lock_err;
size_t etindex = 0;
if (!err) {
if (et == NULL) { err = EINVAL; }
}
if (!err) {
int found = false;
size_t i;
for (i = 0; i < error_tables->count; i++) {
if (error_tables->ets[i] == et) {
etindex = i;
found = true;
}
}
if (!found) { err = EINVAL; }
}
if (!err) {
memmove (&error_tables->ets[etindex], &error_tables->ets[etindex + 1],
((error_tables->count - 1) - etindex) * sizeof (struct error_table **));
error_tables->count--;
}
if (!lock_err) { com_err_thread_unlock_error_tables (&error_tables); }
return err;
}
#pragma mark -
static errcode_t create_cstring (const char *in_string, char **out_string)
{
errcode_t err = 0;
if (in_string == NULL) { err = EINVAL; }
if (out_string == NULL) { err = EINVAL; }
if (!err) {
*out_string = (char *) malloc (strlen (in_string) + 1);
if (*out_string == NULL) { err = ENOMEM; }
}
if (!err) {
strcpy (*out_string, in_string);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning string '%s' for string '%s' (err = %d)",
__FUNCTION__, err ? "(null)" : *out_string, in_string, err);
#endif
return err;
}
static errcode_t create_cstring_from_cfstring (CFStringRef in_cfstring, char **out_string)
{
errcode_t err = klNoErr;
char *string = NULL;
CFIndex length = 0;
CFStringEncoding encoding = __KLApplicationGetTextEncoding ();
if (in_cfstring == NULL) { err = EINVAL; }
if (out_string == NULL) { err = EINVAL; }
if (!err) {
length = CFStringGetMaximumSizeForEncoding (CFStringGetLength (in_cfstring), encoding) + 1;
}
if (!err) {
string = (char *) calloc (length, sizeof (char));
if (string == NULL) { err = ENOMEM; }
}
if (!err) {
if (CFStringGetCString (in_cfstring, string, length, encoding) != true) {
err = ENOMEM;
}
}
if (!err) {
*out_string = string;
} else {
free (string);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning string '%s' (err = %d)", __FUNCTION__, err ? "(null)" : *out_string, err);
#endif
return err;
}
static errcode_t create_bundle_error_string_for_format (errcode_t in_code, const char *in_format, char **out_string)
{
errcode_t lock_err = com_err_thread_lock_for_bundle_lookup ();
errcode_t err = lock_err;
char keyString [MAX_KEY_LENGTH];
CFStringRef cfstring = NULL;
if (!err) {
snprintf (keyString, sizeof (keyString), in_format, in_code);
keyString [sizeof (keyString) - 1] = '\0';
}
if (!err) {
cfstring = __KLGetCFStringForInfoDictionaryKey (keyString);
if (cfstring == NULL) {
*out_string = NULL;
} else {
err = create_cstring_from_cfstring (cfstring, out_string);
}
}
if (!lock_err) { com_err_thread_unlock_for_bundle_lookup (); }
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning string '%s' for code %ld (err = %d)",
__FUNCTION__, err ? "(null)" : *out_string, in_code, err);
#endif
return err;
}
static errcode_t create_unknown_error_message (errcode_t in_code, char **out_string)
{
errcode_t err = 0;
if (!err) {
char unknownError[UNKNOWN_ERROR_MAX_SIZE];
snprintf (unknownError, UNKNOWN_ERROR_MAX_SIZE, UNKNOWN_ERROR_FORMAT, in_code);
unknownError[UNKNOWN_ERROR_MAX_SIZE - 1] = '\0';
err = create_cstring (unknownError, out_string);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning string '%s' for code %ld (err = %d)",
__FUNCTION__, err ? "(null)" : *out_string, in_code, err);
#endif
return err;
}
static errcode_t create_error_table_name (int32_t in_base, char **out_string)
{
errcode_t err = 0;
if (!err) {
char table_name[6];
int ch;
int i;
char *p;
const char char_set[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
p = table_name;
in_base >>= ERRCODE_RANGE;
in_base &= 077777777;
for (i = 4; i >= 0; i--) {
ch = (in_base >> BITS_PER_CHAR * i) & ((1 << BITS_PER_CHAR) - 1);
if (ch != 0)
*p++ = char_set[ch-1];
}
*p = '\0';
err = create_cstring (table_name, out_string);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning string '%s' (err = %d)", __FUNCTION__, err ? "(null)" : *out_string, err);
#endif
return err;
}
const char *error_message (errcode_t code)
{
errcode_t err = 0;
char *message = NULL;
if (!err) {
err = create_bundle_error_string_for_format (code, MESSAGE_KEY_FORMAT, &message);
}
if (!err) {
if (message == NULL) {
error_table_array_t error_tables = NULL;
errcode_t lock_err = com_err_thread_lock_error_tables (&error_tables);
if (!lock_err) {
size_t i;
for (i = 0; i < error_tables->count; i++) {
const struct error_table *et = error_tables->ets[i];
int32_t offset = code - et->base;
int32_t max = et->base + et->count - 1;
if (((code >= et->base) && (code <= max)) && (et->messages[offset] != NULL) && (strlen (et->messages[offset]) > 0)) {
err = create_cstring (et->messages[offset], &message);
break;
}
}
}
if (!lock_err) { com_err_thread_unlock_error_tables (&error_tables); }
}
}
if (!err) {
if ((message == NULL) && (code > 0) && (code < ELAST)) {
err = create_cstring (strerror (code), &message);
}
}
if (!err) {
if (message == NULL) {
err = create_unknown_error_message (code, &message);
}
}
if (!err) {
err = com_err_thread_set_error_message (message);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning message '%s'", __FUNCTION__, err ? UNKNOWN_ERROR_MESSAGE : message);
#endif
return err ? UNKNOWN_ERROR_MESSAGE : message ;
}
const char *error_manager (errcode_t code)
{
errcode_t err = 0;
char *manager = NULL;
if (!err) {
err = create_bundle_error_string_for_format (code, MANAGER_KEY_FORMAT, &manager);
}
if (!err) {
if (manager == NULL) {
error_table_array_t error_tables = NULL;
errcode_t lock_err = com_err_thread_lock_error_tables (&error_tables);
if (!lock_err) {
size_t i;
for (i = 0; i < error_tables->count; i++) {
const struct error_table *et = error_tables->ets[i];
int32_t offset = code - et->base;
int32_t max = et->base + et->count - 1;
if (((code >= et->base) && (code < max)) && (et->messages[offset] != NULL) && (strlen (et->messages[offset]) > 0)) {
if (et->messages[et->count] != 0) {
err = create_cstring (et->messages[et->count], &manager);
} else {
int32_t tableBase = code - (code & ((1 << ERRCODE_RANGE) - 1));
err = create_error_table_name (tableBase, &manager);
}
break;
}
}
}
if (!lock_err) { com_err_thread_unlock_error_tables (&error_tables); }
}
}
if (!err) {
if (manager == NULL) {
err = create_cstring (UNKNOWN_ERROR_MANAGER, &manager);
}
}
if (!err) {
err = com_err_thread_set_error_manager (manager);
}
#ifdef KERBEROSCOMERR_DEBUG
dprintf ("%s: returning manager '%s'", __FUNCTION__, err ? UNKNOWN_ERROR_MANAGER : manager);
#endif
return err ? UNKNOWN_ERROR_MANAGER : manager;
}
#pragma mark -
static void default_com_err_proc (const char *progname, errcode_t code, const char *format, va_list args)
{
if (progname) {
fputs (progname, stderr);
fputs (": ", stderr);
}
if (code) {
fputs (error_message (code), stderr);
fputs (" ", stderr);
}
if (format) {
vfprintf(stderr, format, args);
}
putc ('\r', stderr);
putc ('\n', stderr);
fflush (stderr);
}
void com_err_va (const char *progname, errcode_t code, const char *format, va_list args)
{
errcode_t err = 0;
com_err_handler_t handler = NULL;
if (!err) {
err = com_err_thread_get_com_err_hook (&handler);
}
if (!err) {
if (handler == NULL) { handler = default_com_err_proc; }
(*handler) (progname, code, format, args);
}
}
void com_err (const char *progname, errcode_t code, const char *format, ...)
{
va_list pvar;
va_start (pvar, format);
com_err_va (progname, code, format, pvar);
va_end (pvar);
}
com_err_handler_t set_com_err_hook (com_err_handler_t new_proc)
{
errcode_t err = 0;
com_err_handler_t handler = NULL;
if (!err) {
err = com_err_thread_get_com_err_hook (&handler);
}
if (!err) {
err = com_err_thread_set_com_err_hook (new_proc);
}
return err ? NULL : handler;
}
com_err_handler_t reset_com_err_hook (void)
{
return set_com_err_hook (NULL);
}