/***************************************************************************
 *
 * 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: macros.c,v 1.4.4.9 2006/05/26 19:21:13 bazsi Exp $
 *
 ***************************************************************************/

/*
 * Several patches to the macro expansion function were contributed
 * by Gert Menke.
 *
 * Portions Copyright (c) 2002 by Gert Menke
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * The patch to add the "template" and "template_escape" options to all
 * destination drivers was contributed by Achim Gsell.
 */

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>

#include "format.h"
#include "cfgfile.h"
#include "macros.h"
#include "syslog-names.h"


#define MAX_MACRO_ARGS        32
#define MAX_EXPANDED_MACRO    2048


static int
append_string(char **dest, unsigned int *left, char *str, int length, int escape)
{
	int l;
  
	if (!escape) {
		l = LIBOL_MIN(length, *left - 1);
		strncpy(*dest, str, l);
	}
	else {
		char *p;
      
		l = 0;
		for (p = str; length && ((*left - l) > 1); p++, length--, l++) {
			if (*p == '\'' || *p == '\"' || *p == '\\') {
				if ((*left - l) < 3)
					break;
				*(*dest + l) = '\\';
				*(*dest + l + 1) = *p;
				l++;
			}
			else {
				*(*dest + l) = *p;
			}
		}
	}
	return l;
}

static size_t
format_tzofs(char *dest, size_t left, time_t unixtime, struct tm *tm)
{
	size_t length;
	
#if HAVE_STRFTIME_PERCENT_Z
	length = strftime(dest, left -1, "%z", tm);
#else
        struct tm ltm, *gtm;
	long tzoff;

	/* NOTE: we need to copy the results away, gmtime might use the same
	 * buffer as localtime */
	ltm = *localtime(&unixtime);
	gtm = gmtime(&unixtime);

	tzoff = (ltm.tm_hour - gtm->tm_hour) * 3600;
	tzoff += (ltm.tm_min - gtm->tm_min) * 60;
	tzoff += ltm.tm_sec - gtm->tm_sec;

       /*
        * NOTE! We are not allowed to change or normalize or otherwise modify
        * the timezone offset. It is what it is, and there are places in the
        * world where the offset exceeds 12 hours (e.g. New Zealand DST).
        *
        * The code below, simply accounts for the fact that a mere subtraction
        * of hours may be off by 24 hours when either of the two times is on
        * a different calendar day than the other. We are assuming that the
        * GMT offset is never >= +2400 or <= -2400. This is safe.
        */

       if (tzoff > 0
           && (ltm.tm_year < gtm->tm_year || ltm.tm_mon < gtm->tm_mon || ltm.tm_mday < gtm->tm_mday))
               tzoff -= 86400;
       else if (tzoff < 0
                && (ltm.tm_year > gtm->tm_year || ltm.tm_mon > gtm->tm_mon || ltm.tm_mday > gtm->tm_mday))
               tzoff += 86400;

	length = snprintf(dest, left - 1, "%c%02ld%02ld", 
			  tzoff < 0 ? '-' : '+', 
			  (tzoff < 0 ? -tzoff : tzoff) / 3600, 
			  (tzoff % 3600) / 60);
#endif
	return length;
}

static void
expand_macro(struct syslog_config *cfg, int id, int escape, char **dest, unsigned int *left, struct log_info *msg)
{
	int length = 0;
	
	switch (id) {
	case M_FACILITY: {
		/* facility */
		char *n = syslog_lookup_value(msg->pri & LOG_FACMASK, sl_facilities);
		if (n) {
			length = append_string(dest, left, n, strlen(n), 0);
		}
		else {
			length = snprintf(*dest, *left, "%x", (msg->pri & LOG_FACMASK) >> 3);
		}
		break;
	}
        case M_FACILITY_NUM: {
                length = snprintf(*dest, *left, "%d", (msg->pri & LOG_FACMASK) >> 3);
                break;
        }
	case M_LEVEL: {
		/* level */
		char *n = syslog_lookup_value(msg->pri & LOG_PRIMASK, sl_levels);
		if (n) {
			length = append_string(dest, left, n, strlen(n), 0);
		}
		else {
			/* should never happen */
			length = snprintf(*dest, *left, "%d", msg->pri & LOG_PRIMASK);
		}

		break;
	}
        case M_LEVEL_NUM: {
                length = snprintf(*dest, *left, "%d", (msg->pri & LOG_PRIMASK));
                break;
        }
	case M_TAG: {
		length = snprintf(*dest, *left, "%02x", msg->pri);
		break;
	}
	case M_PRI: {
		length = snprintf(*dest, *left, "%d", msg->pri);
		break;
	}
	case M_SOURCE_IP: {
 		char *ip;
 		
		if (msg->saddr) {
	 		CAST(inet_address_info, addr, msg->saddr);
 		
	 		ip = inet_ntoa(addr->sa.sin_addr);
		}
		else {
			ip = "127.0.0.1";
		}
		length = append_string(dest, left, ip, strlen(ip), escape);
		break;
	}
	case M_FULLHOST_FROM:
	case M_FULLHOST: {
		struct ol_string *host = (id == M_FULLHOST ? msg->host : msg->host_from);
		/* full hostname */
		length = append_string(dest, left, (char *) host->data, host->length, escape);
		break;
	}
	case M_HOST_FROM:
	case M_HOST: {
		/* host */
		struct ol_string *host = (id == M_HOST ? msg->host : msg->host_from);
		UINT8 *p1;
		UINT8 *p2;
		int remaining;
		
		p1 = memchr(host->data, '@', host->length);
		if (p1) 
			p1++; 
		else 
			p1 = host->data;
                remaining = host->length - (p1 - host->data);
		p2 = memchr(p1, '/', remaining);
		if (p2) {
			length = LIBOL_MIN((unsigned int) (p2 - p1), *left);
		}
		else {
			length = LIBOL_MIN(*left, (unsigned int) (host->length - (p1 - host->data)));
		}
		length = append_string(dest, left, (char *) p1, length, escape);
		break;
	}
	case M_PROGRAM: {
		/* program */
		if (msg->program) {
			length = append_string(dest, left, (char *) msg->program->data, msg->program->length, escape);
		}
		break;
	}
	case M_FULLDATE_RECVD:
	case M_ISODATE_RECVD:
	case M_WEEKDAY_RECVD:
	case M_DATE_RECVD:
	case M_YEAR_RECVD:
	case M_MONTH_RECVD:
	case M_DAY_RECVD:
	case M_HOUR_RECVD:
	case M_MIN_RECVD:
	case M_SEC_RECVD:
	case M_TZOFFSET_RECVD:
	case M_TZ_RECVD:
	case M_UNIXTIME_RECVD:

	case M_FULLDATE_STAMP:
	case M_ISODATE_STAMP:
	case M_WEEKDAY_STAMP:
	case M_DATE_STAMP:
	case M_YEAR_STAMP:
	case M_MONTH_STAMP:
	case M_DAY_STAMP:
	case M_HOUR_STAMP:
	case M_MIN_STAMP:
	case M_SEC_STAMP:
	case M_TZOFFSET_STAMP:
	case M_TZ_STAMP:
	case M_UNIXTIME_STAMP:

	case M_FULLDATE:
	case M_ISODATE:
	case M_WEEKDAY:
	case M_DATE:
	case M_YEAR:
	case M_MONTH:
	case M_DAY: 
	case M_HOUR:
	case M_MIN:
	case M_SEC:
	case M_TZOFFSET:
	case M_TZ:
	case M_UNIXTIME: {
		/* year, month, day */
		struct tm *tm;
		time_t unixtime;

	       	switch(id) {
	       	case M_FULLDATE_RECVD:
	       	case M_ISODATE_RECVD: 
	       	case M_WEEKDAY_RECVD: 
	       	case M_DATE_RECVD:    
	       	case M_YEAR_RECVD:    
	       	case M_MONTH_RECVD:   
	       	case M_DAY_RECVD:     
	       	case M_HOUR_RECVD:    
	       	case M_MIN_RECVD:     
	       	case M_SEC_RECVD:     
		case M_TZOFFSET_RECVD:
		case M_TZ_RECVD:
	       	case M_UNIXTIME_RECVD:     
			unixtime = msg->recvd;
			break;
		case M_FULLDATE_STAMP:
		case M_ISODATE_STAMP: 
		case M_WEEKDAY_STAMP: 
		case M_DATE_STAMP:    
		case M_YEAR_STAMP:    
		case M_MONTH_STAMP:   
		case M_DAY_STAMP:     
		case M_HOUR_STAMP:    
		case M_MIN_STAMP:     
		case M_SEC_STAMP:     
		case M_TZOFFSET_STAMP:
		case M_TZ_STAMP:
		case M_UNIXTIME_STAMP:     
			unixtime = msg->stamp;
	                break;
		default:
 		        if (cfg->use_time_recvd)
				unixtime = msg->recvd;
 		        else
				unixtime = msg->stamp;
 		        break;
		}
		
		tm = localtime(&unixtime);

		switch (id) {
		case M_WEEKDAY:
 	        case M_WEEKDAY_RECVD:
 	        case M_WEEKDAY_STAMP:
	                length = strftime(*dest, *left - 1, "%a", tm);			
			break;
		case M_YEAR:
		case M_YEAR_RECVD:
 	        case M_YEAR_STAMP:
			length = snprintf(*dest, *left, "%04d", tm->tm_year + 1900);
			break;
		case M_MONTH:
 	        case M_MONTH_RECVD:
 	        case M_MONTH_STAMP:
			length = snprintf(*dest, *left, "%02d", tm->tm_mon + 1);
			break;
		case M_DAY:
 		case M_DAY_RECVD:
 	        case M_DAY_STAMP:  
			length = snprintf(*dest, *left, "%02d", tm->tm_mday);
			break;
		case M_HOUR:
		case M_HOUR_RECVD:
 	        case M_HOUR_STAMP:
			length = snprintf(*dest, *left, "%02d", tm->tm_hour);
			break;
		case M_MIN:
 		case M_MIN_RECVD:
		case M_MIN_STAMP:
			length = snprintf(*dest, *left, "%02d", tm->tm_min);
			break;
		case M_SEC:
		case M_SEC_RECVD:
		case M_SEC_STAMP:
			length = snprintf(*dest, *left, "%02d", tm->tm_sec);
			break;
		case M_ISODATE:
 		case M_ISODATE_RECVD:
		case M_ISODATE_STAMP:
	                length = strftime(*dest, *left - 1, "%Y-%m-%dT%H:%M:%S", tm);
	                length = length + format_tzofs((*dest) + length, *left - length - 1, unixtime, tm);
	                break;
	        case M_FULLDATE:
 	        case M_FULLDATE_RECVD:
 	        case M_FULLDATE_STAMP:
	                length = strftime(*dest, *left - 1, "%Y %h %e %H:%M:%S", tm);
	        	break;
	        case M_DATE:
 	        case M_DATE_RECVD:
 	        case M_DATE_STAMP:
	                length = strftime(*dest, *left - 1, "%h %e %H:%M:%S", tm);
	                break;
	        case M_TZOFFSET:
 	        case M_TZOFFSET_RECVD:
 	        case M_TZOFFSET_STAMP:
 	        	length = format_tzofs(*dest, *left - 1, unixtime, tm);
			break;
	        case M_TZ:
 	        case M_TZ_RECVD:
 	        case M_TZ_STAMP:
			length = strftime(*dest, *left -1, "%Z", tm);
			break;
	        case M_UNIXTIME:
 	        case M_UNIXTIME_RECVD:
 	        case M_UNIXTIME_STAMP:
			length = snprintf(*dest, *left, "%ld", (long) unixtime);
	                break;
		}
		break;
	}
	case M_MESSAGE: {
		/* message */
		length = append_string(dest, left, (char *) msg->msg->data, msg->msg->length, escape);
		break;
	}
	case M_MSGONLY: {
		char *colon;
		int ofs;
		
		colon = memchr(msg->msg->data, ':', msg->msg->length);
		
		if (!colon) {
			ofs = 0;
		}
		else {
			ofs = (colon - (char *) msg->msg->data) + 2;
			if (ofs > msg->msg->length)
				ofs = msg->msg->length;
		}
		length = append_string(dest, left, (char *) msg->msg->data + ofs, msg->msg->length - ofs, escape);
		break;
	}
	default:
		break;
	}
	if (length < 0 || (unsigned int) length > *left) 
		length = *left;

	*left -= length;
	*dest += length;
}

#define GPERF_MACROS

#ifndef GPERF_MACROS

struct macro_def {
	char *name;
	int id;
	int len;
};

static struct macro_def macros[] = {
	{ "FACILITY", M_FACILITY },
	{ "PRIORITY", M_LEVEL },
	{ "LEVEL", M_LEVEL },
	{ "TAG", M_TAG },
	{ "PRI", M_PRI },

	{ "DATE", M_DATE },
	{ "FULLDATE", M_FULLDATE },
	{ "ISODATE", M_ISODATE },
	{ "YEAR", M_YEAR },
	{ "MONTH", M_MONTH },
	{ "DAY", M_DAY },
	{ "HOUR", M_HOUR },
	{ "MIN", M_MIN },
	{ "SEC", M_SEC },
	{ "WEEKDAY", M_WEEKDAY },
	{ "UNIXTIME", M_UNIXTIME },
	{ "TZOFFSET", M_TZOFFSET },
	{ "TZ", M_TZ },

	{ "R_DATE", M_DATE_RECVD },
	{ "R_FULLDATE", M_FULLDATE_RECVD },
	{ "R_ISODATE", M_ISODATE_RECVD },
	{ "R_YEAR", M_YEAR_RECVD },
	{ "R_MONTH", M_MONTH_RECVD },
	{ "R_DAY", M_DAY_RECVD },
	{ "R_HOUR", M_HOUR_RECVD },
	{ "R_MIN", M_MIN_RECVD },
	{ "R_SEC", M_SEC_RECVD },
	{ "R_WEEKDAY", M_WEEKDAY_RECVD },
	{ "R_UNIXTIME", M_UNIXTIME_RECVD },
	{ "R_TZOFFSET", M_TZOFFSET_RECVD },
	{ "R_TZ", M_TZ_RECVD },

	{ "S_DATE", M_DATE_STAMP },
	{ "S_FULLDATE", M_FULLDATE_STAMP },
	{ "S_ISODATE", M_ISODATE_STAMP },
	{ "S_YEAR", M_YEAR_STAMP },
	{ "S_MONTH", M_MONTH_STAMP },
	{ "S_DAY", M_DAY_STAMP },
	{ "S_HOUR", M_HOUR_STAMP },
	{ "S_MIN", M_MIN_STAMP },
	{ "S_SEC", M_SEC_STAMP },
	{ "S_WEEKDAY", M_WEEKDAY_STAMP },
	{ "S_UNIXTIME", M_UNIXTIME_STAMP },
	{ "S_TZOFFSET", M_TZOFFSET_STAMP },
	{ "S_TZ", M_TZ_STAMP },
	
	{ "HOST_FROM", M_HOST_FROM },
	{ "FULLHOST_FROM", M_FULLHOST_FROM },
	{ "HOST", M_HOST },
	{ "FULLHOST", M_FULLHOST },

	{ "PROGRAM", M_PROGRAM },
	{ "MSG", M_MESSAGE },
	{ "MSGONLY", M_MSGONLY },
	{ "MESSAGE", M_MESSAGE },
	{ "SOURCEIP", M_SOURCE_IP }
};

static int macro_cache[sizeof(macros) / sizeof(struct macro_def)];
static int macro_cache_inited = 0;

int 
cmpmacro(const void *k1, const void *k2)
{
	return strcmp(macros[*((int *) k1)].name, macros[*((int *) k2)].name);
}

void
cache_macros(void)
{
	int i;
	int num;
	
	num = sizeof(macros) / sizeof(struct macro_def);	
	for (i = 0; i < num; i++) {
		macro_cache[i] = i;
		macros[i].len = strlen(macros[i].name);
	}
	qsort(macro_cache, num, sizeof(int), cmpmacro);
}

struct macro_def *
find_macro(char *template, int template_left)
{
	int l, h, m, len, cmp;
	
	if (!macro_cache_inited) {
		cache_macros();
		macro_cache_inited = 1;
	}
	
	l = 0; 
	h = sizeof(macros) / sizeof(struct macro_def);
	while (l <= h) {
		m = (l + h) >> 1;
		len = LIBOL_MIN(template_left, macros[macro_cache[m]].len);
		
		cmp = strncmp(template, macros[macro_cache[m]].name, len);
		if (cmp == 0) {
			return &macros[macro_cache[m]];
		}
		else if (cmp < 0) {
			h = m - 1;
		}
		else if (cmp > 0) {
			l = m + 1;
		}
	}
	return NULL;
}

#else

#include "macros-gperf.c"

#endif

struct ol_string *
expand_macros(struct syslog_config *cfg, struct ol_string *template, int template_escape, struct log_info *msg)
{
	char format[cfg->log_msg_size + 1], *format_ptr = format;
	unsigned int left = sizeof(format) - 1;
	unsigned int i;

	i = 0;
	while (left && (i < template->length)) {

		if (template->data[i] == '$') {
			/* beginning of a macro */
			struct macro_def *m;
			int start;
			
			start = ++i;
			while ((template->data[i] >= 'A' && 
				template->data[i] <= 'Z') ||
			       template->data[i] == '_') 
				i++;
			m = find_macro((char *) &template->data[start], i - start);
			if (m) {
				expand_macro(cfg, m->id, template_escape, &format_ptr, &left, msg);
			}
		}
		else {
			*format_ptr = template->data[i];
			format_ptr++;
			i++;
			left--;
		}
	}
	*format_ptr = 0;
	return c_format_cstring("%z", format);
}


syntax highlighted by Code2HTML, v. 0.9.1