/**************************************************************************
 *
 * ARCHIVEUTILS.C -  Utilities for Nagios CGIs that read archive logs
 *
 * Copyright (c) 2013 Nagios Enterprises, LLC
 * Last Modified: 06-30-2013
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *************************************************************************/

#include "../include/config.h"
#include "../include/common.h"
#include "../include/objects.h"
#include "../include/statusdata.h"
#include "../include/comments.h"

#include "../include/cgiutils.h"
#include "../include/statusdata.h"
#include "../include/archiveutils.h"

#define AU_INITIAL_LIST_SIZE	16

const string_value_mapping svm_au_object_types[] = {
	{ "none", AU_OBJTYPE_NONE, "None" },
	{ "host", AU_OBJTYPE_HOST, "Host" },
	{ "service", AU_OBJTYPE_SERVICE, "Service" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_au_state_types[] = {
	{ "hard", AU_STATETYPE_HARD, "Hard" },
	{ "soft", AU_STATETYPE_SOFT, "Soft" },
	{ "nodata", AU_STATETYPE_NO_DATA, "No Data" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_au_states[] = {
	{ "nodata", AU_STATE_NO_DATA, "No Data" },
	{ "up", AU_STATE_HOST_UP, "Host Up" },
	{ "down", AU_STATE_HOST_DOWN, "How Down" },
	{ "unreachable", AU_STATE_HOST_UNREACHABLE, "Host Unreachable" },
	{ "ok", AU_STATE_SERVICE_OK, "Service OK" },
	{ "warning", AU_STATE_SERVICE_WARNING, "Service Warning" },
	{ "critical", AU_STATE_SERVICE_CRITICAL, "Service Critical" },
	{ "unknown", AU_STATE_SERVICE_UNKNOWN, "Service Unknown" },
	{ "programstart", AU_STATE_PROGRAM_START, "Program Start"},
	{ "programend", AU_STATE_PROGRAM_END, "Program End"},
	{ "downtimestart", AU_STATE_DOWNTIME_START, "Downtime Start"},
	{ "downtimeend", AU_STATE_DOWNTIME_END, "Downtime End"},
	{ "currentstate", AU_STATE_CURRENT_STATE, "Current State"},
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_au_log_types[] = {
	{ "alert", AU_LOGTYPE_ALERT, "Alert" },
	{ "initialstate", AU_LOGTYPE_STATE_INITIAL, "Initial State" },
	{ "currentstate", AU_LOGTYPE_STATE_CURRENT, "Current State" },
	{ "notification", AU_LOGTYPE_NOTIFICATION, "Notification" },
	{ "downtime", AU_LOGTYPE_DOWNTIME, "Downtime" },
	{ "nagios", AU_LOGTYPE_NAGIOS, "Nagios" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_au_notification_types[] = {
	{ "nodata", AU_NOTIFICATION_NO_DATA, 
			"No Data" },
	{ "down", AU_NOTIFICATION_HOST_DOWN, 
			"Host Down" },
	{ "unreachable", AU_NOTIFICATION_HOST_UNREACHABLE, 
			"Host Unreachable" },
	{ "recovery", AU_NOTIFICATION_HOST_RECOVERY, 
			"Host Recovery" },
	{ "hostcustom", AU_NOTIFICATION_HOST_CUSTOM, 
			"Host Custom" },
	{ "hostack", AU_NOTIFICATION_HOST_ACK, 
			"Host Acknowledgement" },
	{ "hostflapstart", AU_NOTIFICATION_HOST_FLAPPING_START, 
			"Host Flapping Start" },
	{ "hostflapstop", AU_NOTIFICATION_HOST_FLAPPING_STOP, 
			"Host Flapping Stop" },
	{ "critical", AU_NOTIFICATION_SERVICE_CRITICAL, 
			"Service Critical" },
	{ "warning", AU_NOTIFICATION_SERVICE_WARNING, 
			"Service Warning" },
	{ "recovery", AU_NOTIFICATION_SERVICE_RECOVERY, 
			"Service Recovery" },
	{ "custom", AU_NOTIFICATION_SERVICE_CUSTOM, 
			"Service Custom" },
	{ "serviceack", AU_NOTIFICATION_SERVICE_ACK, 
			"Service Acknowledgement" },
	{ "serviceflapstart", AU_NOTIFICATION_SERVICE_FLAPPING_START, 
			"Service Flapping Start" },
	{ "serviceflapstop", AU_NOTIFICATION_SERVICE_FLAPPING_STOP, 
			"Service Flapping Stop" },
	{ "unknown", AU_NOTIFICATION_SERVICE_UNKNOWN, 
			"Service Unknown" },
	{ NULL, -1, NULL },
	};

/* Function prototypes */
int read_log_file(char *, unsigned, unsigned, unsigned, au_log *);
int au_add_nagios_log(au_log *, time_t, int, char *);
void au_free_nagios_log(au_log_nagios *);
int parse_states_and_alerts(char *, time_t, int, unsigned, unsigned, au_log *);
int parse_downtime_alerts(char *, time_t, unsigned, au_log *);
int au_add_downtime_log(au_log *, time_t, int, void *, int);
void au_free_downtime_log(au_log_downtime *);
int parse_notification_log(char *, time_t, int, au_log *);
int au_add_notification_log(au_log *, time_t, int, void *, au_contact *, int, 
		char *, char *);
void au_free_notification_log(au_log_notification *);
au_log_entry *au_add_log_entry(au_log *, time_t, int, void *);
void au_free_log_entry_void(void *);
void au_free_log_entry(au_log_entry *);
au_host *au_add_host_and_sort(au_array *, char *);
void au_free_host_void(void *);
void au_free_host(au_host *);
int	au_cmp_hosts(const void *, const void *);
au_service *au_add_service_and_sort(au_array *, char *, char *);
int	au_cmp_services(const void *, const void *);
void au_free_service_void(void *);
void au_free_service(au_service *);
au_contact *au_add_contact_and_sort(au_array *, char *);
au_contact *au_add_contact(au_array *, char *);
void au_free_contact_void(void *);
void au_free_contact(au_contact *);
int	au_cmp_contacts(const void *, const void *);
au_contact *au_find_contact(au_array *, char *);
void au_sort_array(au_array *, int(*cmp)(const void *, const void *));
void *au_find_in_array(au_array *, void *, 
		int(*cmp)(const void *, const void *));
au_linked_list *au_init_list(char *);
void au_empty_list(au_linked_list *, void(*)(void *));
void au_free_list(au_linked_list *, void(*)(void *));

/* External variables */
extern int log_rotation_method;

/* Initialize log structure */
au_log *au_init_log(void) {
	au_log *log;

	/* Initialize the log structure itself */
	if((log = calloc(1, sizeof( au_log))) == NULL) {
		return NULL;
		}

	/* Initialize the host subjects */
	if((log->host_subjects = au_init_array("host_subjects")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	/* Initialize the service subjects */
	if((log->service_subjects = au_init_array("service_subjects")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	/* Initialize the log entry list */
	if((log->entry_list = au_init_list("global log entries")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	/* Initialize the host list */
	if((log->hosts = au_init_array("hosts")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	/* Initialize the service list */
	if((log->services = au_init_array("services")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	/* Initialize the contact list */
	if((log->contacts = au_init_array("contacts")) == NULL) {
		au_free_log(log);
		return NULL;
		}

	return log;
	}

void au_free_log(au_log *log) {
	if(NULL == log) return;
	if(NULL != log->host_subjects) {
		au_free_array(log->host_subjects, au_free_host_void);
		}
	if(NULL != log->service_subjects) {
		au_free_array(log->service_subjects, au_free_service_void);
		}
	if(NULL != log->hosts) {
		au_free_array(log->hosts, au_free_host_void);
		}
	if(NULL != log->services) {
		au_free_array(log->services, au_free_service_void);
		}
	if(NULL != log->contacts) {
		au_free_array(log->contacts, au_free_contact_void);
		}
	if(NULL != log->entry_list) {
		au_free_list(log->entry_list, au_free_log_entry_void);
		}
	free(log);
	}

/* reads log files for archived data */
int read_archived_data(time_t start_time, time_t end_time, 
		int backtrack_archives, unsigned obj_types, unsigned state_types, 
		unsigned log_types, au_log *log, time_t *last_archive_data_update) {

	char filename[MAX_FILENAME_LENGTH];
	int oldest_archive = 0;
	int newest_archive = 0;
	int current_archive = 0;
	int	x;
	int	retval = 1;
	au_host *global_host;
	au_host *temp_host;
	au_service *temp_service;
	au_node *temp_entry;
	struct stat adstat;

	/* Determine oldest archive to use when scanning for data 
		(include backtracked archives as well) */
	oldest_archive = determine_archive_to_use_from_time(start_time);
	if(log_rotation_method != LOG_ROTATION_NONE) {
		oldest_archive += backtrack_archives;
		}

	/* determine most recent archive to use when scanning for data */
	newest_archive = determine_archive_to_use_from_time(end_time);

	if(oldest_archive < newest_archive) {
		oldest_archive = newest_archive;
		}

	/* read in all the necessary archived logs (from most recent to earliest) */
	for(current_archive = newest_archive; current_archive <= oldest_archive; 
			current_archive++) {

#ifdef DEBUG
		printf("Reading archive #%d... ", current_archive);
#endif

		/* get the name of the log file that contains this archive */
		get_log_archive_to_use(current_archive, filename, sizeof(filename) - 1);

#ifdef DEBUG
		printf("Archive name: '%s'\n", filename);
#endif

		/* Record the last modification time of the archive file */
		if(stat(filename, &adstat) < 0) {
			/* ENOENT is OK because Nagios may have been down when the 
				logs were being rotated */
			if(ENOENT != errno) return -1;
			}
		else {
			if(*last_archive_data_update < adstat.st_mtime) {
				*last_archive_data_update = adstat.st_mtime;
				}

			/* scan the log file for archived state data */
			if(read_log_file(filename, obj_types, state_types, log_types, 
					log) == 0) {
				return 0;	/* Memory allocation error */
				}
			}
		}

	/* Add Nagios log events to all hosts and services found */
	global_host = au_find_host(log->hosts, "*");
	if(NULL != global_host) {
		for(temp_entry = global_host->log_entries->head; NULL != temp_entry; 
				temp_entry = temp_entry->next) {
			for(x = 0; x < log->hosts->count; x++) {
				temp_host = log->hosts->members[x];
				if(temp_host == global_host) continue;
				if(au_list_add_node(temp_host->log_entries, 
						temp_entry->data, au_cmp_log_entries) == 0) {
					retval = 0;
					break;
					}
				}
			if(0 == retval) break;
			for(x = 0; x < log->services->count; x++) {
				temp_service = log->services->members[x];
				if(au_list_add_node(temp_service->log_entries, 
							temp_entry->data, au_cmp_log_entries) == 0) {
					retval = 0;
					break;
					}
				}
			if(0 == retval) break;
			}
		}

	return 1;
	}

/* grabs archives state data from a log file */
int read_log_file(char *filename, unsigned obj_types, unsigned state_types, 
		unsigned log_types, au_log *log) {
	char *input = NULL;
	char *input2 = NULL;
	char *temp_buffer = NULL;
	time_t time_stamp;
	mmapfile *thefile = NULL;
	int	retval = 1;

	if((thefile = mmap_fopen(filename)) == NULL) {
		return 1;
		}

	while(1) {

		/* free memory */
		free(input);
		free(input2);
		input = NULL;
		input2 = NULL;

		/* read the next line */
		if((input = mmap_fgets(thefile)) == NULL) break;

		strip(input);

		if((input2 = strdup(input)) == NULL) continue;

		temp_buffer = my_strtok(input2, "]");
		time_stamp = (temp_buffer == NULL) ? (time_t)0 : 
				(time_t)strtoul(temp_buffer + 1, NULL, 10);

		/* program starts/restarts */
		if(strstr(input, " starting...")) {
			if(au_add_nagios_log(log, time_stamp, AU_STATE_PROGRAM_START, 
					"Program start") == 0) {
				retval = 0;
				break;
				}
			}
		if(strstr(input, " restarting...")) {
			if(au_add_nagios_log(log, time_stamp, AU_STATE_PROGRAM_START, 
					"Program restart") == 0) {
				retval = 0;
				break;
				}
			}

		/* program stops */
		if(strstr(input, " shutting down...")) {
			if(au_add_nagios_log(log, time_stamp, AU_STATE_PROGRAM_END, 
					"Normal program termination") == 0) {
				retval = 0;
				break;
				}
			}
		if(strstr(input, "Bailing out")) {
			if(au_add_nagios_log(log, time_stamp, AU_STATE_PROGRAM_END, 
					"Abnormal program termination") == 0) {
				retval = 0;
				break;
				}
			}

		if(obj_types & AU_OBJTYPE_HOST) {

			/* normal host alerts */
			if((log_types & AU_LOGTYPE_ALERT) && strstr(input, "HOST ALERT:")) {
				if(parse_states_and_alerts(input, time_stamp, AU_LOGTYPE_ALERT, 
						AU_OBJTYPE_HOST, state_types, log) == 0) {
					retval = 0;
					break;
					}
				}

			/* host initial states */
			else if((log_types & AU_LOGTYPE_STATE) && 
					strstr(input, "INITIAL HOST STATE:")) {
				if(parse_states_and_alerts(input, time_stamp, 
						AU_LOGTYPE_STATE_INITIAL, AU_OBJTYPE_HOST, state_types, 
						log) == 0) {
					retval = 0;
					break;
					}
				}

			/* host current states */
			else if((log_types & AU_LOGTYPE_STATE) && 
					strstr(input, "CURRENT HOST STATE:")) {
				if(parse_states_and_alerts(input, time_stamp, 
						AU_LOGTYPE_STATE_CURRENT, AU_OBJTYPE_HOST, state_types, 
						log) == 0) {
					retval = 0;
					break;
					}
				}

			/* scheduled downtime notices */
			else if((log_types & AU_LOGTYPE_DOWNTIME) && 
					strstr(input, "HOST DOWNTIME ALERT:")) {
				if(parse_downtime_alerts(input, time_stamp, AU_OBJTYPE_HOST, 
						log) == 0) {
					retval = 0;
					break;
					}
				}

			/* host notifications */
			else if((log_types & AU_LOGTYPE_NOTIFICATION) && 
					strstr(input, "HOST NOTIFICATION:")) {
				if(parse_notification_log(input, time_stamp, AU_OBJTYPE_HOST, 
						log) ==0) {
					retval = 0;
					break;
					}
				}
	
			}

		if(obj_types & AU_OBJTYPE_SERVICE) {

			/* normal service alerts */
			if((log_types & AU_LOGTYPE_ALERT) && 
					strstr(input, "SERVICE ALERT:")) {
				if(parse_states_and_alerts(input, time_stamp, AU_LOGTYPE_ALERT, 
						AU_OBJTYPE_SERVICE, state_types, log) == 0) {
					retval = 0;
					break;
					}
				}

			/* service initial states */
			else if((log_types & AU_LOGTYPE_STATE) && 
					strstr(input, "INITIAL SERVICE STATE:")) {
				if(parse_states_and_alerts(input, time_stamp, 
						AU_LOGTYPE_STATE_INITIAL, AU_OBJTYPE_SERVICE, 
						state_types, log) == 0) {
					retval = 0;
					break;
					}
				}

			/* service current states */
			else if((log_types & AU_LOGTYPE_STATE) && 
					strstr(input, "CURRENT SERVICE STATE:")) {
				if(parse_states_and_alerts(input, time_stamp, 
						AU_LOGTYPE_STATE_CURRENT, AU_OBJTYPE_SERVICE, 
						state_types, log) == 0) {
					retval = 0;
					break;
					}
				}

			/* scheduled service downtime notices */
			else if((log_types & AU_LOGTYPE_DOWNTIME) && 
					strstr(input, "SERVICE DOWNTIME ALERT:")) {
				if(parse_downtime_alerts(input, time_stamp, AU_OBJTYPE_SERVICE, 
						log) == 0) {
					retval = 0;
					break;
					}
				}

			/* service notifications */
			else if((log_types & AU_LOGTYPE_NOTIFICATION) && 
					strstr(input, "SERVICE NOTIFICATION:")) {
				if(parse_notification_log(input, time_stamp, 
						AU_OBJTYPE_SERVICE, log) ==0) {
					retval = 0;
					break;
					}
				}
			}
		}

	/* free memory and close the file */
	free(input);
	free(input2);
	mmap_fclose(thefile);
	return retval;
	}

int	au_cmp_log_entries(const void *a, const void *b) {

	au_log_entry *lea = *(au_log_entry **)a;
	au_log_entry *leb = *(au_log_entry **)b;

	if(lea->timestamp == leb->timestamp) return 0;
	else if(lea->timestamp < leb->timestamp) return -1;
	else return 1;
	}

int au_add_nagios_log(au_log *log, time_t timestamp, int type, 
		char *description) {

	au_log_nagios *nagios_log;
	au_log_entry *new_log_entry;
	au_host *global_host;

	/* Create the au_log_nagios */
	if((nagios_log = calloc(1, sizeof(au_log_nagios))) == NULL) {
		return 0;
		}
	nagios_log->type = type;
	if((nagios_log->description = strdup(description)) == NULL) {
		au_free_nagios_log(nagios_log);
		return 0;
		}

	/* Create the log entry */
	if((new_log_entry = au_add_log_entry(log, timestamp, AU_LOGTYPE_NAGIOS, 
			(void *)nagios_log)) == NULL) {
		au_free_nagios_log(nagios_log);
		return 0;
		}

	/* Find the au_host object associated with the global host */
	global_host = au_find_host(log->hosts, "*");
	if(NULL == global_host) {
		global_host = au_add_host_and_sort(log->hosts, "*");
		if(NULL == global_host) { /* Could not allocate memory */
			return 0;
			}
		}

	/* Add the entry to the global host */
	if(au_list_add_node(global_host->log_entries, new_log_entry, 
			au_cmp_log_entries) == 0) {
		return 0;
		}

	return 1;
	}

void au_free_nagios_log(au_log_nagios *nagios_log) {
	if(NULL == nagios_log) return;
	if(NULL != nagios_log->description) free(nagios_log->description);
	free(nagios_log);
	}

/* parse state and alert log entries */
int parse_states_and_alerts(char *input, time_t timestamp, int log_type, 
		unsigned obj_type, unsigned state_types, au_log *log) {

	char *temp_buffer = NULL;
	char entry_host_name[MAX_INPUT_BUFFER];
	char entry_svc_description[MAX_INPUT_BUFFER];
	char *plugin_output;
	au_host *temp_host_subject;
	au_host *temp_host = NULL;
	au_service *temp_service_subject;
	au_service *temp_service = NULL;
	int	state_type;
	int	state;

	/* get host name */
	temp_buffer = my_strtok(NULL, ":");
	temp_buffer = my_strtok(NULL, ";");
	strncpy(entry_host_name, (temp_buffer == NULL) ? "" : 
			temp_buffer + 1, sizeof(entry_host_name));
	entry_host_name[sizeof(entry_host_name) - 1] = '\x0';

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(log->host_subjects->count > 0) {
			/* see if there is a corresponding subject for this host */
			temp_host_subject = au_find_host(log->host_subjects, 
					entry_host_name);
			if(temp_host_subject == NULL) return 1;
			}

		/* Find the au_host object associated with the host name */
		temp_host = au_find_host(log->hosts, entry_host_name);
		if(NULL == temp_host) {
			temp_host = au_add_host_and_sort(log->hosts, entry_host_name);
			if(NULL == temp_host) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
	case AU_OBJTYPE_SERVICE:
		/* get service description */
		temp_buffer = my_strtok(NULL, ";");
		strncpy(entry_svc_description, (temp_buffer == NULL) ? "" : 
				temp_buffer, sizeof(entry_svc_description));
		entry_svc_description[sizeof(entry_svc_description) - 1] = '\x0';

		if(log->service_subjects->count > 0) {
			/* see if there is a corresponding subject for this service */
			temp_service_subject = au_find_service(log->service_subjects, 
					entry_host_name, entry_svc_description);
			if(temp_service_subject == NULL) return 1;
			}

		/* Find the au_service object associated with the service */
		temp_service = au_find_service(log->services, entry_host_name, 
				entry_svc_description);
		if(NULL == temp_service) {
			temp_service = au_add_service_and_sort(log->services, 
					entry_host_name, entry_svc_description);
			if(NULL == temp_service) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
		}

	/* state types */
	if(strstr(input, ";SOFT;")) {
		if(!(state_types & AU_STATETYPE_SOFT)) return 1;
		state_type = AU_STATETYPE_SOFT;
		}
	if(strstr(input, ";HARD;")) {
		if(!(state_types & AU_STATETYPE_HARD)) return 1;
		state_type = AU_STATETYPE_HARD;
		}

	/* get the plugin output */
	temp_buffer = my_strtok(NULL, ";");
	temp_buffer = my_strtok(NULL, ";");
	temp_buffer = my_strtok(NULL, ";");
	plugin_output = my_strtok(NULL, "\n");

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(strstr(input, ";DOWN;")) {
			state = AU_STATE_HOST_DOWN;
			}
		else if(strstr(input, ";UNREACHABLE;")) {
			state = AU_STATE_HOST_UNREACHABLE;
			}
		else if(strstr(input, ";RECOVERY") || strstr(input, ";UP;")) {
			state = AU_STATE_HOST_UP;
			}
		else {
			state = AU_STATE_NO_DATA;
			state_type = AU_STATETYPE_NO_DATA;
			}
		if(au_add_alert_or_state_log(log, timestamp, log_type, AU_OBJTYPE_HOST, 
				(void *)temp_host, state_type, state, plugin_output) == 0) {
			return 0;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(strstr(input, ";CRITICAL;")) {
			state = AU_STATE_SERVICE_CRITICAL;
			}
		else if(strstr(input, ";WARNING;")) {
			state = AU_STATE_SERVICE_WARNING;
			}
		else if(strstr(input, ";UNKNOWN;")) {
			state = AU_STATE_SERVICE_UNKNOWN;
			}
		else if(strstr(input, ";RECOVERY;") || strstr(input, ";OK;")) {
			state = AU_STATE_SERVICE_OK;
			}
		else {
			state = AU_STATE_NO_DATA;
			state_type = AU_STATETYPE_NO_DATA;
			}
		if(au_add_alert_or_state_log(log, timestamp, log_type, 
				AU_OBJTYPE_SERVICE, (void *)temp_service, state_type, state, 
				plugin_output) == 0) {
			return 0;
			}
		break;
		}

	return 1;
	}

int au_add_alert_or_state_log(au_log *log, time_t timestamp, int log_type,
		int obj_type, void *object, int state_type, int state, 
		char *plugin_output) {

	au_log_alert *alert_log;
	au_log_entry *new_log_entry;

	/* Create the au_log_alert */
	if((alert_log = au_create_alert_or_state_log(obj_type, object, state_type, 
			state, plugin_output)) == NULL) {
		return 0;
		}

	/* Create the log entry */
	if((new_log_entry = au_add_log_entry(log, timestamp, log_type, 
			(void *)alert_log)) == NULL) {
		au_free_alert_log(alert_log);
		return 0;
		}

	/* Add the log entry to the logs for the object supplied */
	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(au_list_add_node(((au_host *)object)->log_entries,
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(au_list_add_node(((au_service *)object)->log_entries, 
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
		}

	return 1;
	}

au_log_alert *au_create_alert_or_state_log(int obj_type, void *object, 
		int state_type, int state, char *plugin_output) {

	au_log_alert *alert_log;

	/* Create the au_log_alert */
	if((alert_log = calloc(1, sizeof(au_log_alert))) == NULL) {
		return NULL;
		}
	alert_log->obj_type = obj_type;
	alert_log->object = object;
	alert_log->state_type = state_type;
	alert_log->state = state;
	alert_log->plugin_output = NULL;
	if(plugin_output != NULL) {
		if((alert_log->plugin_output = strdup(plugin_output)) == NULL) {
			au_free_alert_log(alert_log);
			return NULL;
			}
		}

	return alert_log;
	}

void au_free_alert_log(au_log_alert *alert_log) {
	if(NULL == alert_log) return;
	if(NULL != alert_log->plugin_output) free(alert_log->plugin_output);
	free(alert_log);
	}

/* parse archive log downtime notifications */
int parse_downtime_alerts(char *input, time_t timestamp, unsigned obj_type, 
		au_log *log) {

	char *temp_buffer = NULL;
	char entry_host_name[MAX_INPUT_BUFFER];
	char entry_svc_description[MAX_INPUT_BUFFER];
	au_host *temp_host_subject;
	au_host *temp_host = NULL;
	au_service *temp_service_subject;
	au_service *temp_service = NULL;

	/* get host name */
	temp_buffer = my_strtok(NULL, ":");
	temp_buffer = my_strtok(NULL, ";");
	strncpy(entry_host_name, (temp_buffer == NULL) ? "" : 
			temp_buffer + 1, sizeof(entry_host_name));
	entry_host_name[sizeof(entry_host_name) - 1] = '\x0';

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(log->host_subjects->count > 0) {
			/* see if there is a corresponding subject for this host */
			temp_host_subject = au_find_host(log->host_subjects, 
					entry_host_name);
			if(temp_host_subject == NULL) return 1;
			}

		/* Find the au_host object associated with the host name */
		temp_host = au_find_host(log->hosts, entry_host_name);
		if(NULL == temp_host) {
			temp_host = au_add_host_and_sort(log->hosts, entry_host_name);
			if(NULL == temp_host) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
	case AU_OBJTYPE_SERVICE:
		/* get service description */
		temp_buffer = my_strtok(NULL, ";");
		strncpy(entry_svc_description, (temp_buffer == NULL) ? "" : 
				temp_buffer, sizeof(entry_svc_description));
		entry_svc_description[sizeof(entry_svc_description) - 1] = '\x0';

		if(log->service_subjects->count > 0) {
			/* see if there is a corresponding subject for this service */
			temp_service_subject = au_find_service(log->service_subjects, 
					entry_host_name, entry_svc_description);
			if(temp_service_subject == NULL) return 1;
			}

		/* Find the au_service object associated with the service */
		temp_service = au_find_service(log->services, entry_host_name, 
				entry_svc_description);
		if(NULL == temp_service) {
			temp_service = au_add_service_and_sort(log->services, 
					entry_host_name, entry_svc_description);
			if(NULL == temp_service) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
		}

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(strstr(input, ";STARTED;")) {
			if(au_add_downtime_log(log, timestamp, AU_OBJTYPE_HOST, 
					(void *)temp_host, AU_STATE_DOWNTIME_START) == 0) {
				return 0;
				}
			}
		else {
			if(au_add_downtime_log(log, timestamp, AU_OBJTYPE_HOST, 
					(void *)temp_host, AU_STATE_DOWNTIME_END) == 0) {
				return 0;
				}
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(strstr(input, ";STARTED;")) {
			if(au_add_downtime_log(log, timestamp, AU_OBJTYPE_SERVICE, 
					(void *)temp_service, AU_STATE_DOWNTIME_START) == 0) {
				return 0;
				}
			}
		else {
			if(au_add_downtime_log(log, timestamp, AU_OBJTYPE_SERVICE, 
					(void *)temp_service, AU_STATE_DOWNTIME_END) == 0) {
				return 0;
				}
			}
		break;
		}

	return 1;
	}

int au_add_downtime_log(au_log *log, time_t timestamp, int obj_type, 
		void *object, int downtime_type) {

	au_log_downtime *downtime_log;
	au_log_entry *new_log_entry;

	/* Create the au_log_downtime */
	if((downtime_log = calloc(1, sizeof(au_log_downtime))) == NULL) {
		return 0;
		}
	downtime_log->obj_type = obj_type;
	downtime_log->object = object;
	downtime_log->downtime_type = downtime_type;

	/* Create the log entry */
	if((new_log_entry = au_add_log_entry(log, timestamp, AU_LOGTYPE_DOWNTIME, 
			(void *)downtime_log)) == NULL) {
		au_free_downtime_log(downtime_log);
		return 0;
		}

	/* Add the log entry to the logs for the object supplied */
	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(au_list_add_node(((au_host *)object)->log_entries,
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(au_list_add_node(((au_service *)object)->log_entries, 
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
		}

	return 1;
	}

void au_free_downtime_log(au_log_downtime *downtime_log) {
	if(NULL == downtime_log) return;
	free(downtime_log);
	}

int parse_notification_log(char *input, time_t timestamp, int obj_type, 
		au_log *log) {

	char entry_contact_name[MAX_INPUT_BUFFER];
	char entry_host_name[MAX_INPUT_BUFFER];
	char entry_svc_description[MAX_INPUT_BUFFER];
	char alert_level[MAX_INPUT_BUFFER];
	char method_name[MAX_INPUT_BUFFER];
	int	notification_detail_type = AU_NOTIFICATION_NO_DATA;
	char *temp_buffer = NULL;
	au_host *temp_host_subject;
	au_service *temp_service_subject;
	au_contact *temp_contact;
	au_host *temp_host = NULL;
	au_service *temp_service = NULL;

	/* get the contact name */
	temp_buffer = my_strtok(NULL, ":");
	temp_buffer = my_strtok(NULL, ";");
	strncpy(entry_contact_name, (temp_buffer == NULL) ? "" : 
			temp_buffer + 1, sizeof(entry_contact_name));
	entry_contact_name[sizeof(entry_contact_name) - 1] = '\x0';

	/* Find the au_contact object associated with the contact name */
	temp_contact = au_find_contact(log->contacts, entry_contact_name);
	if(NULL == temp_contact) {
		temp_contact = au_add_contact_and_sort(log->contacts, 
				entry_contact_name);
		if(NULL == temp_contact) { /* Could not allocate memory */
			return 0;
			}
		}

	/* get the host name */
	temp_buffer = (char *)my_strtok(NULL, ";");
	snprintf(entry_host_name, sizeof(entry_host_name), "%s", 
			(temp_buffer == NULL) ? "" : temp_buffer);
	entry_host_name[sizeof(entry_host_name) - 1] = '\x0';

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(log->host_subjects->count > 0) {
			/* see if there is a corresponding subject for this host */
			temp_host_subject = au_find_host(log->host_subjects, 
					entry_host_name);
			if(temp_host_subject == NULL) return 1;
			}

		/* Find the au_host object associated with the host name */
		temp_host = au_find_host(log->hosts, entry_host_name);
		if(NULL == temp_host) {
			temp_host = au_add_host_and_sort(log->hosts, entry_host_name);
			if(NULL == temp_host) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
	case AU_OBJTYPE_SERVICE:
		/* get service description */
		temp_buffer = my_strtok(NULL, ";");
		strncpy(entry_svc_description, (temp_buffer == NULL) ? "" : 
				temp_buffer, sizeof(entry_svc_description));
		entry_svc_description[sizeof(entry_svc_description) - 1] = '\x0';

		if(log->service_subjects->count > 0) {
			/* see if there is a corresponding subject for this service */
			temp_service_subject = au_find_service(log->service_subjects, 
					entry_host_name, entry_svc_description);
			if(temp_service_subject == NULL) return 1;
			}

		/* Find the au_service object associated with the service */
		temp_service = au_find_service(log->services, entry_host_name, 
				entry_svc_description);
		if(NULL == temp_service) {
			temp_service = au_add_service_and_sort(log->services, 
					entry_host_name, entry_svc_description);
			if(NULL == temp_service) { /* Could not allocate memory */
				return 0;
				}
			}

		break;
		}

	/* get the alert level */
	temp_buffer = (char *)my_strtok(NULL, ";");
	snprintf(alert_level, sizeof(alert_level), "%s", 
			(temp_buffer == NULL) ? "" : temp_buffer);
	alert_level[sizeof(alert_level) - 1] = '\x0';

	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(!strcmp(alert_level, "DOWN")) {
			notification_detail_type = AU_NOTIFICATION_HOST_DOWN;
			}
		else if(!strcmp(alert_level, "UNREACHABLE")) {
			notification_detail_type = AU_NOTIFICATION_HOST_UNREACHABLE;
			}
		else if(!strcmp(alert_level, "RECOVERY") || 
				!strcmp(alert_level, "UP")) {
			notification_detail_type = AU_NOTIFICATION_HOST_RECOVERY;
			}
		else if(strstr(alert_level, "CUSTOM (")) {
			notification_detail_type = AU_NOTIFICATION_HOST_CUSTOM;
			}
		else if(strstr(alert_level, "ACKNOWLEDGEMENT (")) {
			notification_detail_type = AU_NOTIFICATION_HOST_ACK;
			}
		else if(strstr(alert_level, "FLAPPINGSTART (")) {
			notification_detail_type = AU_NOTIFICATION_HOST_FLAPPING_START;
			}
		else if(strstr(alert_level, "FLAPPINGSTOP (")) {
			notification_detail_type = AU_NOTIFICATION_HOST_FLAPPING_STOP;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(!strcmp(alert_level, "CRITICAL")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_CRITICAL;
			}
		else if(!strcmp(alert_level, "WARNING")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_WARNING;
			}
		else if(!strcmp(alert_level, "RECOVERY") || 
				!strcmp(alert_level, "OK")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_RECOVERY;
			}
		else if(strstr(alert_level, "CUSTOM (")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_CUSTOM;
			}
		else if(strstr(alert_level, "ACKNOWLEDGEMENT (")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_ACK;
			}
		else if(strstr(alert_level, "FLAPPINGSTART (")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_FLAPPING_START;
			}
		else if(strstr(alert_level, "FLAPPINGSTOP (")) {
			notification_detail_type = AU_NOTIFICATION_SERVICE_FLAPPING_STOP;
			}
		else {
			notification_detail_type = AU_NOTIFICATION_SERVICE_UNKNOWN;
			}
		break;
		}

	/* get the method name */
	temp_buffer = (char *)my_strtok(NULL, ";");
	snprintf(method_name, sizeof(method_name), "%s", 
			(temp_buffer == NULL) ? "" : temp_buffer);
	method_name[sizeof(method_name) - 1] = '\x0';

	/* move to the informational message */
	temp_buffer = my_strtok(NULL, ";");

	/* Create the log entry */
	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(au_add_notification_log(log, timestamp, AU_OBJTYPE_HOST, 
				(void *)temp_host, temp_contact, notification_detail_type, 
				method_name, temp_buffer) == 0) {
			return 0;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(au_add_notification_log(log, timestamp, AU_OBJTYPE_SERVICE, 
				(void *)temp_service, temp_contact, notification_detail_type, 
				method_name, temp_buffer) == 0) {
			return 0;
			}
		break;
		}

	return 1;
	}

int au_add_notification_log(au_log *log, time_t timestamp, int obj_type, 
		void *object, au_contact *contact, int notification_type, 
		char *method, char *message) {

	au_log_notification *notification_log;
	au_log_entry *new_log_entry;

	/* Create the au_log_downtime */
	if((notification_log = calloc(1, sizeof(au_log_notification))) == NULL) {
		return 0;
		}
	notification_log->obj_type = obj_type;
	notification_log->object = object;
	notification_log->contact = contact;
	notification_log->notification_type = notification_type;
	if((notification_log->method = strdup(method)) 
			== NULL) {
		au_free_notification_log(notification_log);
		return 0;
		}
	if((notification_log->message = strdup(message)) == NULL) {
		au_free_notification_log(notification_log);
		return 0;
		}

	/* Create the log entry */
	if((new_log_entry = au_add_log_entry(log, timestamp, 
			AU_LOGTYPE_NOTIFICATION, (void *)notification_log)) == NULL) {
		au_free_notification_log(notification_log);
		return 0;
		}

	/* Add the log entry to the logs for the object supplied */
	switch(obj_type) {
	case AU_OBJTYPE_HOST:
		if(au_list_add_node((void *)((au_host *)object)->log_entries,
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
	case AU_OBJTYPE_SERVICE:
		if(au_list_add_node((void *)((au_service *)object)->log_entries, 
				new_log_entry, au_cmp_log_entries) == 0) {
			return 0;
			}
		break;
		}

	return 1;
	}

void au_free_notification_log(au_log_notification *notification_log) {
	if(NULL == notification_log) return;
	if(NULL != notification_log->method) free(notification_log->method);
	if(NULL != notification_log->message) free(notification_log->message);
	free(notification_log);
	}

au_log_entry *au_add_log_entry(au_log *log, time_t timestamp, int entry_type, 
		void *entry) {

	au_log_entry *new_log_entry;

	/* Create the au_log_entry */
	if((new_log_entry = calloc(1, sizeof(au_log_entry))) == NULL) {
		return NULL;
		}
	new_log_entry->timestamp = timestamp;
	new_log_entry->entry_type = entry_type;
	new_log_entry->entry = entry;

	if(au_list_add_node(log->entry_list, (void *)new_log_entry, 
			au_cmp_log_entries) == NULL) {
		au_free_log_entry(new_log_entry);
		return NULL;
		}

	return new_log_entry;
	}

void au_free_log_entry_void(void *log_entry) {
	au_free_log_entry((au_log_entry *)log_entry);
	}

void au_free_log_entry(au_log_entry *log_entry) {
	if(NULL == log_entry) return;
	switch(log_entry->entry_type) {
	case AU_LOGTYPE_ALERT:
	case AU_LOGTYPE_STATE:
		au_free_alert_log((au_log_alert *)log_entry->entry);
		break;
	case AU_LOGTYPE_NOTIFICATION:
		au_free_notification_log((au_log_notification *)log_entry->entry);
		break;
	case AU_LOGTYPE_DOWNTIME:
		au_free_downtime_log((au_log_downtime *)log_entry->entry);
		break;
	case AU_LOGTYPE_NAGIOS:
		au_free_nagios_log((au_log_nagios *)log_entry->entry);
		break;
		}
	free(log_entry);
	}

/* Add a host to a host list and sort the list */
au_host *au_add_host_and_sort(au_array *host_list, char *name) {

	au_host *temp_host;

	temp_host = au_add_host(host_list, name);
	if(NULL != temp_host) {
		au_sort_array(host_list, au_cmp_hosts);
		}

	return temp_host;
	}

/* Add a host to a host list */
au_host *au_add_host(au_array *host_list, char *name) {

	au_host	*new_host;
	char	buf[8192];

	/* Should have been allocated during au_log_init() */
	if(NULL == host_list) {
		return NULL;
		}

	/* Create the host */
	if((new_host = calloc(1, sizeof(au_host))) == NULL) {
		return NULL;
		}
	if((new_host->name = strdup(name)) == NULL) {
		au_free_host(new_host);
		return NULL;
		}
	new_host->hostp = find_host(name);
	new_host->availability = NULL;
	snprintf(buf, sizeof(buf) - 1, "Host %s log entries", name);
	if((new_host->log_entries = au_init_list(buf)) == NULL) {
		au_free_host(new_host);
		return NULL;
		}

	/* Add it to the list of hosts */
	if(0 == au_array_append_member(host_list, (void *)new_host)) {
		au_free_host(new_host);
		return NULL;
		}

	return new_host;
	}

void au_free_host_void(void *host) {
	au_free_host((au_host *)host);
	}

void au_free_host(au_host *host) {
	if(NULL == host) return;
	if(NULL != host->name) free(host->name);
	/* Do not free the log entry data here because they are freed when the
		master list's log entries are freed */
	if(NULL != host->log_entries) au_free_list(host->log_entries, NULL);
	if(NULL != host->availability) free(host->availability);
	free(host);
	return;
	}

int	au_cmp_hosts(const void *a, const void *b) {
	au_host *hosta = *(au_host **)a;
	au_host *hostb = *(au_host **)b;

	return strcmp(hosta->name, hostb->name);
	}

au_host *au_find_host(au_array *host_list, char *name) {

	au_host	key;
	void *found;

	if(NULL == host_list) return NULL;
	key.name = name;
	found = au_find_in_array(host_list, (void *)&key, au_cmp_hosts);
	if(NULL == found) return NULL;
	return *(au_host **)found;
	}

au_service *au_add_service_and_sort(au_array *service_list, char *host_name, 
		char *description) {

	au_service *temp_service;

	temp_service = au_add_service(service_list, host_name, description);
	if(NULL != temp_service) {
		au_sort_array(service_list, au_cmp_services);
		}

	return temp_service;
	}

au_service *au_add_service(au_array *service_list, char *host_name, 
		char *description) {

	au_service	*new_service;
	char	buf[8192];

	/* Should have been allocated during au_log_init() */
	if(NULL == service_list) {
		return NULL;
		}

	/* Create the service */
	if((new_service = calloc(1, sizeof(au_service))) == NULL) {
		return NULL;
		}
	if((new_service->host_name = strdup(host_name)) == NULL) {
		au_free_service(new_service);
		return NULL;
		}
	if((new_service->description = strdup(description)) == NULL) {
		au_free_service(new_service);
		return NULL;
		}
	new_service->servicep = find_service(host_name, description);
	new_service->availability = NULL;
	snprintf(buf, sizeof(buf) - 1, "Service %s:%s log entries", host_name, 
			description);
	if((new_service->log_entries = au_init_list(buf)) == NULL) {
		au_free_service(new_service);
		return NULL;
		}

	/* Add it to the list of services */
	if(0 == au_array_append_member(service_list, (void *)new_service)) {
		au_free_service(new_service);
		return NULL;
		}
	
	return new_service;
	}

void au_free_service_void(void *service) {
	au_free_service((au_service *)service);
	}

void au_free_service(au_service *service) {
	if(NULL == service) return;
	if(NULL != service->host_name) free(service->host_name);
	if(NULL != service->description) free(service->description);
	/* Do not free the log entry data here because they are freed when the
		master list's log entries are freed */
	if(NULL != service->log_entries) au_free_list(service->log_entries, NULL);
	if(NULL != service->availability) free(service->availability);
	free(service);
	return;
	}

au_service *au_find_service(au_array *service_list, char *host_name,
		char *description) {

	au_service	key;
	void *found;

	if(NULL == service_list) return NULL;
	key.host_name = host_name;
	key.description = description;
	found = (au_service *)au_find_in_array(service_list, (void *)&key,
			au_cmp_services);
	if(NULL == found) return NULL;
	return *(au_service **)found;
	}

int	au_cmp_services(const void *a, const void *b) {

	au_service *servicea = *(au_service **)a;
	au_service *serviceb = *(au_service **)b;
	int	host_result;

	host_result = strcmp(servicea->host_name, serviceb->host_name);
	if(0 == host_result) {
		return strcmp(servicea->description, serviceb->description);
		}
	else {
		return host_result;
		}
	}

/* Add a contact to a contact list and sort the list */
au_contact *au_add_contact_and_sort(au_array *contact_list, char *name) {

	au_contact *temp_contact;

	temp_contact = au_add_contact(contact_list, name);
	if(NULL != temp_contact) {
		au_sort_array(contact_list, au_cmp_contacts);
		}
	return temp_contact;
	}

/* Add a contact to a contact list */
au_contact *au_add_contact(au_array *contact_list, char *name) {

	au_contact	*new_contact;

	/* Should have been allocated during au_log_init() */
	if(NULL == contact_list) {
		return NULL;
		}

	/* Create the contact */
	if((new_contact = calloc(1, sizeof(au_contact))) == NULL) {
		return NULL;
		}
	if((new_contact->name = strdup(name)) == NULL) {
		au_free_contact(new_contact);
		return NULL;
		}

	/* Add it to the list of contacts */
	if(0 == au_array_append_member(contact_list, (void *)new_contact)) {
		au_free_contact(new_contact);
		return NULL;
		}

	return new_contact;
	}

void au_free_contact_void(void *contact) {
	au_free_contact((au_contact *)contact);
	}

void au_free_contact(au_contact *contact) {
	if(NULL == contact) return;
	if(NULL != contact->name) free(contact->name);
	free(contact);
	return;
	}

int	au_cmp_contacts(const void *a, const void *b) {

	au_contact *contacta = (au_contact *)a;
	au_contact *contactb = (au_contact *)b;

	return strcmp(contacta->name, contactb->name);
	}

au_contact *au_find_contact(au_array *contact_list, char *name) {

	au_contact	key;
	void *found;

	if(NULL == contact_list) return NULL;
	key.name = name;
	found = (au_contact *)au_find_in_array(contact_list, (void *)&key,
			au_cmp_contacts);
	if(NULL == found) return NULL;
	return *(au_contact **)found;
	}

au_array *au_init_array(char *label) {
	au_array *array;

	if((array = calloc(1, sizeof(au_array))) == NULL) {
		return NULL;
		}
	array->label = NULL;
	if(NULL != label) {
		if((array->label = strdup(label)) == NULL) {
			free(array);
			return NULL;
			}
		}
	array->size = 0;
	array->count = 0;
	array->members = (void **)NULL;
	array->new = 0;

	return array;
	}

void au_free_array(au_array *array, void(*datafree)(void *)) {

	int x;

	if(NULL == array) return;
	if(NULL != array->label) free(array->label);
	for(x = 0; x < array->count; x++) {
		if((NULL != datafree) && (NULL != array->members[x])) {
			datafree(array->members[x]);
			}
		}
	if(NULL != array->members) free(array->members);
	free(array);
	return;
	}

int au_array_append_member(au_array *array, void *member) {

	/* Check whether the list needs to be grown before adding the member */
	if(array->count == array->size) { 
		/* Need to grow the list */
		if(0 == array->count) {
			/* Never had any members */
			if((array->members = (void **)calloc(AU_INITIAL_LIST_SIZE, 
					sizeof(void *))) == NULL) {
				return 0;
				}
			array->size = AU_INITIAL_LIST_SIZE;
			}
		else {
			/* Double the size of the list */
			if((array->members = (void **)realloc(array->members, 
					sizeof(void *) * array->size * 2)) == NULL) {
				return 0;
				}
			array->size *= 2;
			}
		}

	array->members[array->count] = member;
	array->new++;		/* Number of appends since last sort */
	array->count++;

	return 1;
	}

void au_sort_array(au_array *array, int(*cmp)(const void *, const void *)) {

	/* TODO: Use array->new to determine whether to do a quick sort or a 
		bubble sort */
	qsort(array->members, array->count, sizeof(void *), cmp);
	array->new = 0;
	}

void *au_find_in_array(au_array *array, void *key, 
		int(*cmp)(const void *, const void *)) {

	return bsearch(&key, array->members, array->count, sizeof(void *), cmp);
	}

au_linked_list *au_init_list(char *label) {

	au_linked_list *list;

	if((list = calloc(1, sizeof(au_linked_list))) == NULL) {
		return NULL;
		}
	list->label = NULL;
	if(NULL != label) {
		if((list->label = strdup(label)) == NULL) {
			free(list);
			return NULL;
			}
		}
	list->head = (au_node *)0;
	list->last_new = (au_node *)0;

	return list;
	}

au_node *au_list_add_node(au_linked_list *list, void *data, 
		int(*cmp)(const void *, const void *)) {

	au_node *new_node;
	au_node *temp_node;

	/* Create the new node */
	if((new_node = calloc(1, sizeof(au_node))) == NULL) {
		return NULL;
		}
	new_node->data = data;
	new_node->next = NULL;

	/* Add it to the list */
	if(NULL == list->head) {
		/* If the list is empty, add this node as the only item in the list */
		list->head = new_node;
		}
	else if(cmp(&(list->last_new->data), &(new_node->data)) <= 0) {
		/* The new node goes somewhere on the list downstream of the most
			recently added node */
		if(NULL == list->last_new->next) {
			/* If the most recently added node is the last one on the list, 
				append the list with this node */
			list->last_new->next = new_node;
			}
		else if(cmp(&(list->last_new->next->data), &(new_node->data)) > 0) {
			/* If the next node is "greater than" the new node, the new 
				node goes here */
			new_node->next = list->last_new->next;
			list->last_new->next = new_node;
			}
		else {
			/* Visit each node downstream in the list until we reach the 
				end of the list or until we find a node whose next
				node is "greater than" the new node */
			temp_node = list->last_new;
			while((NULL != temp_node->next) && (cmp(&(temp_node->next->data),
					&(new_node->data)) <= 0)) {
				temp_node = temp_node->next;
				}
			if(NULL == temp_node->next) {
				/* If we reach the end of the list, the new node gets
					appended */
				temp_node->next = new_node;
				}
			else {
				/* Otherwise, the new node gets inserted here */
				new_node->next = temp_node->next;
				temp_node->next = new_node;
				}
			}
		}
	else {
		/* The new node is "less than" the last new node.  Visit each node 
			starting at the beginning of the list until we reach the end of 
			the list (which shouldn't happen because that case was covered 
			earlier) or we find a node whose next node is "greater than"
			the new node. */
		temp_node = list->head;
		while((NULL != temp_node->next) && (cmp(&(temp_node->next->data), 
				&(new_node->data)) <= 0)) {
			temp_node = temp_node->next;
			}
		if(temp_node == list->head) {
			/* We insert at the beginning of the list */
			new_node->next = list->head;
			list->head = new_node;
			}
		else if(NULL == temp_node->next) {
			/* If we reach the end of the list, the new node gets appended */
			temp_node->next = new_node;
			}
		else {
			/* Otherwise, the new node gets inserted here */
			new_node->next = temp_node->next;
			temp_node->next = new_node;
			}
		}
	list->last_new = new_node;

	return new_node;
	}

void au_empty_list(au_linked_list *list, void(*datafree)(void *)) {

	au_node *temp_node1;
	au_node *temp_node2;

	temp_node1 = list->head;
	while(NULL != temp_node1) {
		temp_node2 = temp_node1->next;
		if((NULL != datafree) && (NULL != temp_node1->data)) {
			datafree(temp_node1->data);
			}
		free(temp_node1);
		temp_node1 = temp_node2;
		}
	list->head = NULL;
	list->last_new = NULL;
	}

void au_free_list(au_linked_list *list, void(*datafree)(void *)) {

	if(NULL == list) return;
	if(NULL != list->label) free(list->label);
	au_empty_list(list, datafree);
	free(list);
	}
