/***************************************************************************
 *
 * Copyright (c) 1999 Balázs Scheidler
 * Copyright (c) 1999-2001 BalaBit IT Ltd.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * Inspired by nsyslog, originally written by Darren Reed.
 *
 * $Id: log.c,v 1.32.4.1 2004/01/13 18:08:02 bazsi Exp $
 *
 ***************************************************************************/

#include "log.h"
#include "xalloc.h"
#include "format.h"

#include <sys/time.h>
#include <syslog.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#define CLASS_DEFINE
#include "log.h.x"
#undef CLASS_DEFINE

static char aix_fwd_string[] = "Message forwarded from ";
static char repeat_msg_string[] = "last message repeated";

static void parse_log_msg(struct log_info *lm, UINT32 length, UINT8 *data, UINT8 *prefix, regex_t *hostname_re)
{
	unsigned char *src;
	unsigned int left, pri, oldleft;
	time_t now = time(NULL);
	unsigned char *oldsrc;
	
	src = data;
	left = length;
	
	if (left && src[0] == '<') {
		src++;
		left--;
		pri = 0;
		while (left && *src != '>') {
			if (isdigit(*src)) {
				pri = pri * 10 + ((*src) - '0');
			}
			else {
				lm->msg = c_format_cstring("unparseable log message: \"%s\"", length, data);
				lm->pri = LOG_SYSLOG | LOG_ERR;
				return;
			}
			src++;
			left--;
		}
		lm->pri = pri;
		if (left) {
			src++;
			left--;
		}
	}
	/* No priority info in the buffer? Just assign a default. */
	else {
	        lm->pri = LOG_USER | LOG_NOTICE;
	}


	while (left && *src == ' ') {  /* Move past whitespace */
		src++;
		left--;
	}

	/* If the next chars look like a date, then read them as a date. */
	if (left >= 15) {
                struct tm tm, *nowtm;

		if (left >= 21 &&
		    src[3] == ' ' && src[6] == ' ' && src[11] == ' ' &&
		    src[14] == ':' && src[17] == ':' && src[20] ==':') {
			/* PIX time stamp, format: MMM DD YYYY HH:MM:SS: */
			
			src[20] = 0;
			memset(&tm, 0, sizeof(tm));
			strptime((char *) src, "%b %e %Y %H:%M:%S", &tm);
			tm.tm_isdst = -1;
			lm->date = ol_string_alloc(16);
			strftime((char *) lm->date->data, 16, "%b %e %H:%M:%S", &tm);
			lm->date->length--;
			src[20] = ':';
			src += 21;
			left -= 21;
			lm->stamp = mktime(&tm);
		}
	        else if (src[3] == ' ' && src[6] == ' ' &&
		         src[9] == ':' && src[12] == ':') {
	  	        /* Expected buffer format: MMM DD HH:MM:SS ... */

			/* Just read the buffer data into a textual
			   datestamp. */
			lm->date = c_format_cstring("%s", 15, src);
			src += 15;
			left -= 15;

			/* And also make struct time timestamp for the msg */
			nowtm = localtime(&now);
			memset(&tm, 0, sizeof(tm));
			strptime((char *) lm->date->data, "%b %e %H:%M:%S", &tm);
			tm.tm_isdst = -1;
			tm.tm_year = nowtm->tm_year;
			if (tm.tm_mon > nowtm->tm_mon + 1)
				tm.tm_year--;
			lm->stamp = mktime(&tm);
		}
	}
 
        if (lm->date) {
	        /* Expected format: hostname program[pid]: */
	        /* Possibly: Message forwarded from hostname: ... */
	        unsigned char *hostname_start = NULL;
		int hostname_len = 0;
		
		while (left && *src == ' ') {
			src++; /* skip whitespace */
			left--;
		}

		/* Detect funny AIX syslogd forwarded message. */
		if (left >= (sizeof(aix_fwd_string) - 1) &&
		    !memcmp(src, aix_fwd_string, sizeof(aix_fwd_string) - 1)) {

		        oldsrc = src;
			oldleft = left;
			src += sizeof(aix_fwd_string) - 1;
			left -= sizeof(aix_fwd_string) - 1;
			hostname_start = src;
			hostname_len   = 0;
			while (left && *src != ':') {
				src++;
				left--;
				hostname_len++;
			}
			while (left && (*src == ' ' || *src == ':')) {
				src++; /* skip whitespace */
				left--;
			}
		}

		/* Now, try to tell if it's a "last message repeated" line */
		if (left >= sizeof(repeat_msg_string) &&
		    !memcmp(src, repeat_msg_string, 
			    sizeof(repeat_msg_string) - 1)) {
			; /* It is. Do nothing since there's no hostname or
			     program name coming. */
		}
		/* It's a regular ol' message. */
		else {
			/* If we haven't already found the original hostname,
			   look for it now. */
			char hostname_buf[256];
			int dst;

		        oldsrc = src;	
			oldleft = left;

			dst = 0;
			while (left && *src != ' ' && *src != ':' 
			       && *src != '[' && dst < sizeof(hostname_buf) - 1) {
				if (lm->flags & LF_CHECK_HOSTNAME &&
				    !((*src >= 'A' && *src <= 'Z') ||
				      (*src >= 'a' && *src <= 'z') ||
				      (*src >= '0' && *src <= '9') ||
				      *src == '-' || *src == '_' ||
				      *src == '.' || *src == ':' ||
				      *src == '@' || *src == '/')) {
					break;
				}
				hostname_buf[dst++] = *src;
		 		src++;
				left--;
			}
			hostname_buf[dst] = 0;
			if (left && *src == ' ' &&
			    (!hostname_re || regexec(hostname_re, hostname_buf, 0, NULL, 0))) {
		  		/* This was a hostname. It came from a
				   syslog-ng, since syslogd doesn't send
				   hostnames. It's even better then the one
				   we got from the AIX fwd message, if we
				   did. */
				hostname_start = oldsrc;
				hostname_len   = oldleft - left;
			} else {
		  		src = oldsrc;
		  		left = oldleft;
			}

			/* Skip whitespace. */
			while (left && *src == ' ') {
				src++;
				left--;
			}

			/* Try to extract a program name */
			oldsrc = src;
			oldleft = left;
			while (left && *src != ':' && *src != '[') {
				src++;
				left--;
			}
			if (left) {
				lm->program = c_format_cstring("%s",
						oldleft - left, oldsrc);
			} 
			
			src = oldsrc;
			left = oldleft;
		}

		/* If we did manage to find a hostname, store it. */
		if (hostname_start)
			lm->host = c_format_cstring("%s", hostname_len,
						    hostname_start);
	}
	else {
	        /* Different format */

		oldsrc = src;
		oldleft = left;
		/* A kernel message? Use 'kernel' as the program name. */
		if ((lm->pri & LOG_FACMASK) == LOG_KERN) {
			lm->program = c_format_cstring("kernel");
		}
		/* No, not a kernel message. */
		else {
		        /* Capture the program name */ 
		        while (left && *src != ' ' && *src != '['
			       && *src != ':' && *src != '/' &&
			       *src != ',' && *src != '<') {
				src++;
				left--;
			}
			if (left) {
				lm->program = c_format_cstring("%s", oldleft - left, oldsrc);
			}
			left = oldleft;
			src = oldsrc;
		}
		lm->stamp = now;
	}

	for (oldsrc = src, oldleft = left; oldleft > 0; oldleft--, oldsrc++) {
		if (*oldsrc == '\n' || *oldsrc == '\r') *oldsrc = ' ';
	}
	lm->msg = c_format_cstring("%z%s", prefix ? prefix : (UINT8 *) "", left, src);
}

struct log_info *log_info_use(struct log_info *msg)
{
	msg->use_cnt++;
	return msg;
}

void log_info_free(struct log_info *msg)
{
	if (--msg->use_cnt == 0) {
		ol_string_free(msg->host);
		ol_string_free(msg->program);
		ol_string_free(msg->date);
		ol_string_free(msg->msg);
		ol_string_free(msg->host_from);
		ol_space_free(msg);
	}
}

struct log_info *make_log_info(UINT32 length, UINT8 *msg, UINT8 *prefix, UINT32 flags, regex_t *hostname_re)
{
	struct log_info *self;

	NEW_SPACE(self);
	self->flags = flags & LF_USER_FLAGS;
  	parse_log_msg(self, length, msg, prefix, hostname_re);
	self->use_cnt = 1;
  	self->recvd = time(NULL);
	return self;
}

struct log_info *make_internal_message(UINT32 pri, UINT32 length, UINT8 *data)
{
	struct log_info *self;

	NEW_SPACE(self);
	self->msg = c_format_cstring("syslog-ng[%i]: %s", getpid(), length, data);
	self->program = c_format_cstring("syslog-ng");
	self->stamp = self->recvd = time(NULL);
	self->pri = pri;
	self->flags = LF_INTERNAL;
	self->use_cnt = 1;
	self->host_from = NULL;
	return self;
}

struct log_info *make_mark_message(void)
{
	struct log_info *self = make_internal_message(LOG_SYSLOG | LOG_NOTICE, 12, (UINT8 *) "--- MARK ---");
	self->flags |= LF_MARK;
	return self;
}


syntax highlighted by Code2HTML, v. 0.9.1