/*****************************************************************************
 *
 * LOGGING.C - Log file functions for use with Nagios
 *
 *
 * 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/statusdata.h"
#include "../include/macros.h"
#include "../include/nagios.h"
#include "../include/broker.h"
#include <fcntl.h>


static FILE *debug_file_fp;
static FILE *log_fp;

/******************************************************************/
/************************ LOGGING FUNCTIONS ***********************/
/******************************************************************/

/* write something to the console */
static void write_to_console(char *buffer) {
	/* should we print to the console? */
	if(daemon_mode == FALSE)
		printf("%s\n", buffer);
	}


/* write something to the log file, syslog, and possibly the console */
static void write_to_logs_and_console(char *buffer, unsigned long data_type, int display) {
	register int len = 0;
	register int x = 0;

	/* strip unnecessary newlines */
	len = strlen(buffer);
	for(x = len - 1; x >= 0; x--) {
		if(buffer[x] == '\n')
			buffer[x] = '\x0';
		else
			break;
		}

	/* write messages to the logs */
	write_to_all_logs(buffer, data_type);

	/* write message to the console */
	if(display == TRUE) {

		/* don't display warnings if we're just testing scheduling */
		if(test_scheduling == TRUE && data_type == NSLOG_VERIFICATION_WARNING)
			return;

		write_to_console(buffer);
		}
	}


/* The main logging function */
void logit(int data_type, int display, const char *fmt, ...) {
	va_list ap;
	char *buffer = NULL;

	va_start(ap, fmt);
	if(vasprintf(&buffer, fmt, ap) > 0) {
		write_to_logs_and_console(buffer, data_type, display);
		free(buffer);
		}
	va_end(ap);
	}


/* write something to the log file and syslog facility */
int write_to_all_logs(char *buffer, unsigned long data_type) {

	/* write to syslog */
	write_to_syslog(buffer, data_type);

	/* write to main log */
	write_to_log(buffer, data_type, NULL);

	return OK;
	}


/* write something to the log file and syslog facility */
static void write_to_all_logs_with_timestamp(char *buffer, unsigned long data_type, time_t *timestamp) {
	/* write to syslog */
	write_to_syslog(buffer, data_type);

	/* write to main log */
	write_to_log(buffer, data_type, timestamp);
	}


static FILE *open_log_file(void)
{
	int fh;
	struct stat st;

	if(log_fp) /* keep it open unless we rotate */
		return log_fp;

	if ((fh = open(log_file, O_RDWR|O_APPEND|O_CREAT|O_NOFOLLOW, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) {
		if (daemon_mode == FALSE)
			printf("Warning: Cannot open log file '%s' for writing\n", log_file);
		return NULL;
	}
	log_fp = fdopen(fh, "a+");
	if(log_fp == NULL) {
		if (daemon_mode == FALSE)
			printf("Warning: Cannot open log file '%s' for writing\n", log_file);
		return NULL;
		}

	if ((fstat(fh, &st)) == -1) {
		log_fp = NULL;
		close(fh);
		if (daemon_mode == FALSE)
			printf("Warning: Cannot fstat log file '%s'\n", log_file);
		return NULL;
	}
	if (st.st_nlink != 1 || (st.st_mode & S_IFMT) != S_IFREG) {
		log_fp = NULL;
		close(fh);
		if (daemon_mode == FALSE)
			printf("Warning: log file '%s' has an invalid mode\n", log_file);
		return NULL;
	}

	(void)fcntl(fileno(log_fp), F_SETFD, FD_CLOEXEC);
	return log_fp;
}

int fix_log_file_owner(uid_t uid, gid_t gid)
{
	int r1 = 0, r2 = 0;

	if (!(log_fp = open_log_file()))
		return -1;
	r1 = fchown(fileno(log_fp), uid, gid);

	if (open_debug_log() != OK)
		return -1;
	if (debug_file_fp)
		r2 = fchown(fileno(debug_file_fp), uid, gid);

	/* return 0 if both are 0 and otherwise < 0 */
	return r1 < r2 ? r1 : r2;
}

int close_log_file(void)
{
	if(!log_fp)
		return 0;

	fflush(log_fp);
	fclose(log_fp);
	log_fp = NULL;
	return 0;
}

/* write something to the nagios log file */
int write_to_log(char *buffer, unsigned long data_type, time_t *timestamp) {
	FILE *fp;
	time_t log_time = 0L;

	if(buffer == NULL)
		return ERROR;

	/* don't log anything if we're not actually running... */
	if(verify_config || test_scheduling == TRUE)
		return OK;

	/* make sure we can log this type of entry */
	if(!(data_type & logging_options))
		return OK;

	fp = open_log_file();
	if (fp == NULL)
		return ERROR;
	/* what timestamp should we use? */
	if(timestamp == NULL)
		time(&log_time);
	else
		log_time = *timestamp;

	/* strip any newlines from the end of the buffer */
	strip(buffer);

	/* write the buffer to the log file */
	fprintf(fp, "[%llu] %s\n", (unsigned long long)log_time, buffer);
	fflush(fp);

#ifdef USE_EVENT_BROKER
	/* send data to the event broker */
	broker_log_data(NEBTYPE_LOG_DATA, NEBFLAG_NONE, NEBATTR_NONE, buffer, data_type, log_time, NULL);
#endif

	return OK;
	}


/* write something to the syslog facility */
int write_to_syslog(char *buffer, unsigned long data_type) {

	if(buffer == NULL)
		return ERROR;

	/* don't log anything if we're not actually running... */
	if(verify_config || test_scheduling == TRUE)
		return OK;

	/* bail out if we shouldn't write to syslog */
	if(use_syslog == FALSE)
		return OK;

	/* make sure we should log this type of entry */
	if(!(data_type & syslog_options))
		return OK;

	/* write the buffer to the syslog facility */
	syslog(LOG_USER | LOG_INFO, "%s", buffer);

	return OK;
	}


/* write a service problem/recovery to the nagios log file */
int log_service_event(service *svc) {
	char *temp_buffer = NULL;
	unsigned long log_options = 0L;
	host *temp_host = NULL;

	/* don't log soft errors if the user doesn't want to */
	if(svc->state_type == SOFT_STATE && !log_service_retries)
		return OK;

	/* get the log options */
	if(svc->current_state == STATE_UNKNOWN)
		log_options = NSLOG_SERVICE_UNKNOWN;
	else if(svc->current_state == STATE_WARNING)
		log_options = NSLOG_SERVICE_WARNING;
	else if(svc->current_state == STATE_CRITICAL)
		log_options = NSLOG_SERVICE_CRITICAL;
	else
		log_options = NSLOG_SERVICE_OK;

	/* find the associated host */
	if((temp_host = svc->host_ptr) == NULL)
		return ERROR;

	asprintf(&temp_buffer, "SERVICE ALERT: %s;%s;%s;%s;%d;%s\n",
			 svc->host_name, svc->description,
			 service_state_name(svc->current_state),
			 state_type_name(svc->state_type),
			 svc->current_attempt,
			 (svc->plugin_output == NULL) ? "" : svc->plugin_output);

	write_to_all_logs(temp_buffer, log_options);
	free(temp_buffer);

	return OK;
	}


/* write a host problem/recovery to the log file */
int log_host_event(host *hst) {
	char *temp_buffer = NULL;
	unsigned long log_options = 0L;

	/* get the log options */
	if(hst->current_state == HOST_DOWN)
		log_options = NSLOG_HOST_DOWN;
	else if(hst->current_state == HOST_UNREACHABLE)
		log_options = NSLOG_HOST_UNREACHABLE;
	else
		log_options = NSLOG_HOST_UP;

	asprintf(&temp_buffer, "HOST ALERT: %s;%s;%s;%d;%s\n",
			 hst->name,
			 host_state_name(hst->current_state),
			 state_type_name(hst->state_type),
			 hst->current_attempt,
			 (hst->plugin_output == NULL) ? "" : hst->plugin_output);

	write_to_all_logs(temp_buffer, log_options);

	my_free(temp_buffer);

	return OK;
	}


/* logs host states */
int log_host_states(int type, time_t *timestamp) {
	char *temp_buffer = NULL;
	host *temp_host = NULL;;

	/* bail if we shouldn't be logging initial states */
	if(type == INITIAL_STATES && log_initial_states == FALSE)
		return OK;

	for(temp_host = host_list; temp_host != NULL; temp_host = temp_host->next) {

		asprintf(&temp_buffer, "%s HOST STATE: %s;%s;%s;%d;%s\n", (type == INITIAL_STATES) ? "INITIAL" : "CURRENT",
				 temp_host->name,
				 host_state_name(temp_host->current_state),
				 state_type_name(temp_host->state_type),
				 temp_host->current_attempt,
				 (temp_host->plugin_output == NULL) ? "" : temp_host->plugin_output);

		write_to_all_logs_with_timestamp(temp_buffer, NSLOG_INFO_MESSAGE, timestamp);

		my_free(temp_buffer);
		}

	return OK;
	}


/* logs service states */
int log_service_states(int type, time_t *timestamp) {
	char *temp_buffer = NULL;
	service *temp_service = NULL;
	host *temp_host = NULL;;

	/* bail if we shouldn't be logging initial states */
	if(type == INITIAL_STATES && log_initial_states == FALSE)
		return OK;

	for(temp_service = service_list; temp_service != NULL; temp_service = temp_service->next) {

		/* find the associated host */
		if((temp_host = temp_service->host_ptr) == NULL)
			continue;

		asprintf(&temp_buffer, "%s SERVICE STATE: %s;%s;%s;%s;%d;%s\n",
				 (type == INITIAL_STATES) ? "INITIAL" : "CURRENT",
				 temp_service->host_name, temp_service->description,
				 service_state_name(temp_service->current_state),
				 state_type_name(temp_service->state_type),
				 temp_service->current_attempt,
				 temp_service->plugin_output);

		write_to_all_logs_with_timestamp(temp_buffer, NSLOG_INFO_MESSAGE, timestamp);

		my_free(temp_buffer);
		}

	return OK;
	}


/* rotates the main log file */
int rotate_log_file(time_t rotation_time) {
	char *temp_buffer = NULL;
	char method_string[16] = "";
	char *log_archive = NULL;
	struct tm *t, tm_s;
	int rename_result = 0;
	int stat_result = -1;
	struct stat log_file_stat;
	struct stat archive_stat;
	int archive_stat_result;

	if(log_rotation_method == LOG_ROTATION_NONE) {
		return OK;
		}
	else if(log_rotation_method == LOG_ROTATION_HOURLY)
		strcpy(method_string, "HOURLY");
	else if(log_rotation_method == LOG_ROTATION_DAILY)
		strcpy(method_string, "DAILY");
	else if(log_rotation_method == LOG_ROTATION_WEEKLY)
		strcpy(method_string, "WEEKLY");
	else if(log_rotation_method == LOG_ROTATION_MONTHLY)
		strcpy(method_string, "MONTHLY");
	else
		return ERROR;

	/* update the last log rotation time and status log */
	last_log_rotation = time(NULL);
	update_program_status(FALSE);

	t = localtime_r(&rotation_time, &tm_s);

	stat_result = stat(log_file, &log_file_stat);

	close_log_file();

	/* get the archived filename to use */
	asprintf(&log_archive, "%s%snagios-%02d-%02d-%d-%02d.log", log_archive_path, (log_archive_path[strlen(log_archive_path) - 1] == '/') ? "" : "/", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900, t->tm_hour);

	/* HACK: If the archive exists, don't overwrite it. This is a hack
		because the real problem is that some log rotations are executed
		early and as a result the next log rotation is scheduled for 
		the same time as the one that ran early */
	archive_stat_result = stat(log_archive, &archive_stat);
	if((0 == archive_stat_result) || 
			((-1 == archive_stat_result) && (ENOENT != errno))) {
		return OK;
	}

	/* rotate the log file */
	rename_result = my_rename(log_file, log_archive);
	log_fp = open_log_file();
	if (log_fp == NULL)
		return ERROR;

	if(rename_result) {
		my_free(log_archive);
		return ERROR;
		}

	/* record the log rotation after it has been done... */
	asprintf(&temp_buffer, "LOG ROTATION: %s\n", method_string);
	write_to_all_logs_with_timestamp(temp_buffer, NSLOG_PROCESS_INFO, &rotation_time);
	my_free(temp_buffer);

	/* record log file version format */
	write_log_file_info(&rotation_time);

	if(stat_result == 0) {
		chmod(log_file, log_file_stat.st_mode);
		chown(log_file, log_file_stat.st_uid, log_file_stat.st_gid);
		}

	/* log current host and service state if activated */
	if(log_current_states==TRUE) {
		log_host_states(CURRENT_STATES, &rotation_time);
		log_service_states(CURRENT_STATES, &rotation_time);
	}

	/* free memory */
	my_free(log_archive);

	return OK;
	}


/* record log file version/info */
int write_log_file_info(time_t *timestamp) {
	char *temp_buffer = NULL;

	/* write log version */
	asprintf(&temp_buffer, "LOG VERSION: %s\n", LOG_VERSION_2);
	write_to_all_logs_with_timestamp(temp_buffer, NSLOG_PROCESS_INFO, timestamp);
	my_free(temp_buffer);

	return OK;
	}


/* opens the debug log for writing */
int open_debug_log(void)
{
	int fh;
	struct stat st;

	/* don't do anything if we're not actually running... */
	if(verify_config || test_scheduling == TRUE)
		return OK;

	/* don't do anything if we're not debugging */
	if(debug_level == DEBUGL_NONE)
		return OK;

	if ((fh = open(debug_file, O_RDWR|O_APPEND|O_CREAT|O_NOFOLLOW, S_IRUSR|S_IWUSR)) == -1)
		return ERROR;
	if((debug_file_fp = fdopen(fh, "a+")) == NULL)
		return ERROR;

	if ((fstat(fh, &st)) == -1) {
		debug_file_fp = NULL;
		close(fh);
		return ERROR;
	}
	if (st.st_nlink != 1 || (st.st_mode & S_IFMT) != S_IFREG) {
		debug_file_fp = NULL;
		close(fh);
		return ERROR;
	}

	(void)fcntl(fh, F_SETFD, FD_CLOEXEC);

	return OK;
	}


/* closes the debug log */
int close_debug_log(void) {

	if(debug_file_fp != NULL)
		fclose(debug_file_fp);

	debug_file_fp = NULL;

	return OK;
	}


/* write to the debug log */
int log_debug_info(int level, int verbosity, const char *fmt, ...) {
	va_list ap;
	char *tmppath = NULL;
	struct timeval current_time;

	if(!(debug_level == DEBUGL_ALL || (level & debug_level)))
		return OK;

	if(verbosity > debug_verbosity)
		return OK;

	if(debug_file_fp == NULL)
		return ERROR;

	/* write the timestamp */
	gettimeofday(&current_time, NULL);
	fprintf(debug_file_fp, "[%lu.%06lu] [%03d.%d] [pid=%lu] ", current_time.tv_sec, current_time.tv_usec, level, verbosity, (unsigned long)getpid());

	/* write the data */
	va_start(ap, fmt);
	vfprintf(debug_file_fp, fmt, ap);
	va_end(ap);

	/* flush, so we don't have problems tailing or when fork()ing */
	fflush(debug_file_fp);

	/* if file has grown beyond max, rotate it */
	if((unsigned long)ftell(debug_file_fp) > max_debug_file_size && max_debug_file_size > 0L) {

		/* close the file */
		close_debug_log();

		/* rotate the log file */
		asprintf(&tmppath, "%s.old", debug_file);
		if(tmppath) {

			/* unlink the old debug file */
			unlink(tmppath);

			/* rotate the debug file */
			my_rename(debug_file, tmppath);

			/* free memory */
			my_free(tmppath);
			}

		/* open a new file */
		open_debug_log();
		}

	return OK;
	}
