/*
 * General logging functions.
 *
 * Copyright 2000-2006 Willy Tarreau <w@1wt.eu>
 *
 * 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.
 *
 */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <sys/time.h>

#include <common/config.h>
#include <common/standard.h>

#include <types/backend.h>
#include <types/global.h>
#include <types/httperr.h>
#include <types/log.h>
#include <types/proxy.h>
#include <types/session.h>


const char *log_facilities[NB_LOG_FACILITIES] = {
	"kern", "user", "mail", "daemon",
	"auth", "syslog", "lpr", "news",
	"uucp", "cron", "auth2", "ftp",
	"ntp", "audit", "alert", "cron2",
	"local0", "local1", "local2", "local3",
	"local4", "local5", "local6", "local7"
};


const char *log_levels[NB_LOG_LEVELS] = {
	"emerg", "alert", "crit", "err",
	"warning", "notice", "info", "debug"
};

const char *monthname[12] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

const char sess_term_cond[8]  = "-cCsSPRI";	/* normal, CliTo, CliErr, SrvTo, SrvErr, PxErr, Resource, Internal */
const char sess_fin_state[8]  = "-RCHDLQ7";	/* cliRequest, srvConnect, srvHeader, Data, Last, Queue, unknown */
const char sess_cookie[4]     = "NIDV";		/* No cookie, Invalid cookie, cookie for a Down server, Valid cookie */
const char sess_set_cookie[8] = "N1I3PD5R";	/* No set-cookie, unknown, Set-Cookie Inserted, unknown,
					    	   Set-cookie seen and left unchanged (passive), Set-cookie Deleted,
						   unknown, Set-cookie Rewritten */
void **pool_requri = NULL;


/*
 * Displays the message on stderr with the date and pid. Overrides the quiet
 * mode during startup.
 */
void Alert(char *fmt, ...)
{
	va_list argp;
	struct timeval tv;
	struct tm *tm;

	if (!(global.mode & MODE_QUIET) || (global.mode & (MODE_VERBOSE | MODE_STARTING))) {
		va_start(argp, fmt);

		gettimeofday(&tv, NULL);
		tm = localtime(&tv.tv_sec);
		fprintf(stderr, "[ALERT] %03d/%02d%02d%02d (%d) : ",
			tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid());
		vfprintf(stderr, fmt, argp);
		fflush(stderr);
		va_end(argp);
	}
}


/*
 * Displays the message on stderr with the date and pid.
 */
void Warning(char *fmt, ...)
{
	va_list argp;
	struct timeval tv;
	struct tm *tm;

	if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
		va_start(argp, fmt);

		gettimeofday(&tv, NULL);
		tm = localtime(&tv.tv_sec);
		fprintf(stderr, "[WARNING] %03d/%02d%02d%02d (%d) : ",
			tm->tm_yday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)getpid());
		vfprintf(stderr, fmt, argp);
		fflush(stderr);
		va_end(argp);
	}
}

/*
 * Displays the message on <out> only if quiet mode is not set.
 */
void qfprintf(FILE *out, char *fmt, ...)
{
	va_list argp;

	if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) {
		va_start(argp, fmt);
		vfprintf(out, fmt, argp);
		fflush(out);
		va_end(argp);
	}
}

/*
 * returns log level for <lev> or -1 if not found.
 */
int get_log_level(const char *lev)
{
	int level;

	level = NB_LOG_LEVELS - 1;
	while (level >= 0 && strcmp(log_levels[level], lev))
		level--;

	return level;
}


/*
 * returns log facility for <fac> or -1 if not found.
 */
int get_log_facility(const char *fac)
{
	int facility;

	facility = NB_LOG_FACILITIES - 1;
	while (facility >= 0 && strcmp(log_facilities[facility], fac))
		facility--;
	
	return facility;
}


#define FD_SETS_ARE_BITFIELDS
#ifdef FD_SETS_ARE_BITFIELDS
/*
 * This map is used with all the FD_* macros to check whether a particular bit
 * is set or not. Each bit represents an ACSII code. FD_SET() sets those bytes
 * which should be encoded. When FD_ISSET() returns non-zero, it means that the
 * byte should be encoded. Be careful to always pass bytes from 0 to 255
 * exclusively to the macros.
 */
fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];

#else
#error "Check if your OS uses bitfields for fd_sets"
#endif

/*
 * This function sends a syslog message to both log servers of a proxy,
 * or to global log servers if the proxy is NULL.
 * It also tries not to waste too much time computing the message header.
 * It doesn't care about errors nor does it report them.
 */
void send_log(struct proxy *p, int level, char *message, ...)
{
	static int logfd = -1;	/* syslog UDP socket */
	static long tvsec = -1;	/* to force the string to be initialized */
	struct timeval tv;
	va_list argp;
	static char logmsg[MAX_SYSLOG_LEN];
	static char *dataptr = NULL;
	int fac_level;
	int hdr_len, data_len;
	struct sockaddr_in *sa[2];
	int facilities[2], loglevel[2];
	int nbloggers = 0;
	char *log_ptr;

	if (logfd < 0) {
		if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
			return;
	}
    
	if (level < 0 || progname == NULL || message == NULL)
		return;

	gettimeofday(&tv, NULL);
	if (tv.tv_sec != tvsec || dataptr == NULL) {
		/* this string is rebuild only once a second */
		struct tm *tm = localtime(&tv.tv_sec);
		tvsec = tv.tv_sec;

		hdr_len = snprintf(logmsg, sizeof(logmsg),
				   "<<<<>%s %2d %02d:%02d:%02d %s[%d]: ",
				   monthname[tm->tm_mon],
				   tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
				   progname, pid);
		/* WARNING: depending upon implementations, snprintf may return
		 * either -1 or the number of bytes that would be needed to store
		 * the total message. In both cases, we must adjust it.
		 */
		if (hdr_len < 0 || hdr_len > sizeof(logmsg))
			hdr_len = sizeof(logmsg);

		dataptr = logmsg + hdr_len;
	}

	va_start(argp, message);
	data_len = vsnprintf(dataptr, logmsg + sizeof(logmsg) - dataptr, message, argp);
	if (data_len < 0 || data_len > (logmsg + sizeof(logmsg) - dataptr))
		data_len = logmsg + sizeof(logmsg) - dataptr;
	va_end(argp);
	dataptr[data_len - 1] = '\n'; /* force a break on ultra-long lines */

	if (p == NULL) {
		if (global.logfac1 >= 0) {
			sa[nbloggers] = &global.logsrv1;
			facilities[nbloggers] = global.logfac1;
			loglevel[nbloggers] = global.loglev1;
			nbloggers++;
		}
		if (global.logfac2 >= 0) {
			sa[nbloggers] = &global.logsrv2;
			facilities[nbloggers] = global.logfac2;
			loglevel[nbloggers] = global.loglev2;
			nbloggers++;
		}
	} else {
		if (p->logfac1 >= 0) {
			sa[nbloggers] = &p->logsrv1;
			facilities[nbloggers] = p->logfac1;
			loglevel[nbloggers] = p->loglev1;
			nbloggers++;
		}
		if (p->logfac2 >= 0) {
			sa[nbloggers] = &p->logsrv2;
			facilities[nbloggers] = p->logfac2;
			loglevel[nbloggers] = p->loglev2;
			nbloggers++;
		}
	}

	while (nbloggers-- > 0) {
		/* we can filter the level of the messages that are sent to each logger */
		if (level > loglevel[nbloggers])
			continue;
	
		/* For each target, we may have a different facility.
		 * We can also have a different log level for each message.
		 * This induces variations in the message header length.
		 * Since we don't want to recompute it each time, nor copy it every
		 * time, we only change the facility in the pre-computed header,
		 * and we change the pointer to the header accordingly.
		 */
		fac_level = (facilities[nbloggers] << 3) + level;
		log_ptr = logmsg + 3; /* last digit of the log level */
		do {
			*log_ptr = '0' + fac_level % 10;
			fac_level /= 10;
			log_ptr--;
		} while (fac_level && log_ptr > logmsg);
		*log_ptr = '<';
	
		/* the total syslog message now starts at logptr, for dataptr+data_len-logptr */

#ifndef MSG_NOSIGNAL
		sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT,
		       (struct sockaddr *)sa[nbloggers], sizeof(**sa));
#else
		sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL,
		       (struct sockaddr *)sa[nbloggers], sizeof(**sa));
#endif
	}
}


/*
 * send a log for the session when we have enough info about it
 */
void sess_log(struct session *s)
{
	char pn[INET6_ADDRSTRLEN + strlen(":65535")];
	struct proxy *p = s->proxy;
	int log;
	char *uri;
	char *pxid;
	char *srv;
	struct tm *tm;

	/* This is a first attempt at a better logging system.
	 * For now, we rely on send_log() to provide the date, although it obviously
	 * is the date of the log and not of the request, and most fields are not
	 * computed.
	 */

	log = p->to_log & ~s->logs.logwait;

	if (s->cli_addr.ss_family == AF_INET)
		inet_ntop(AF_INET,
			  (const void *)&((struct sockaddr_in *)&s->cli_addr)->sin_addr,
			  pn, sizeof(pn));
	else
		inet_ntop(AF_INET6,
			  (const void *)&((struct sockaddr_in6 *)(&s->cli_addr))->sin6_addr,
			  pn, sizeof(pn));

	uri = (log & LW_REQ) ? s->logs.uri ? s->logs.uri : "<BADREQ>" : "";
	pxid = p->id;
	srv = (p->to_log & LW_SVID) ?
		(s->data_source != DATA_SRC_STATS) ?
		(s->srv != NULL) ? s->srv->id : "<NOSRV>" : "<STATS>" : "-";

	tm = localtime(&s->logs.tv_accept.tv_sec);
	if (p->to_log & LW_REQ) {
		char tmpline[MAX_SYSLOG_LEN], *h;
		int hdr;
	
		h = tmpline;
		if (p->to_log & LW_REQHDR && (h < tmpline + sizeof(tmpline) - 10)) {
			*(h++) = ' ';
			*(h++) = '{';
			for (hdr = 0; hdr < p->nb_req_cap; hdr++) {
				if (hdr)
					*(h++) = '|';
				if (s->req_cap[hdr] != NULL)
					h = encode_string(h, tmpline + sizeof(tmpline) - 7,
							  '#', hdr_encode_map, s->req_cap[hdr]);
			}
			*(h++) = '}';
		}

		if (p->to_log & LW_RSPHDR && (h < tmpline + sizeof(tmpline) - 7)) {
			*(h++) = ' ';
			*(h++) = '{';
			for (hdr = 0; hdr < p->nb_rsp_cap; hdr++) {
				if (hdr)
					*(h++) = '|';
				if (s->rsp_cap[hdr] != NULL)
					h = encode_string(h, tmpline + sizeof(tmpline) - 4,
							  '#', hdr_encode_map, s->rsp_cap[hdr]);
			}
			*(h++) = '}';
		}

		if (h < tmpline + sizeof(tmpline) - 4) {
			*(h++) = ' ';
			*(h++) = '"';
			h = encode_string(h, tmpline + sizeof(tmpline) - 1,
					  '#', url_encode_map, uri);
			*(h++) = '"';
		}
		*h = '\0';

		send_log(p, LOG_INFO,
			 "%s:%d [%02d/%s/%04d:%02d:%02d:%02d]"
			 " %s %s %d/%d/%d/%d/%s%d %d %s%lld"
			 " %s %s %c%c%c%c %d/%d/%d %d/%d%s\n",
			 pn,
			 (s->cli_addr.ss_family == AF_INET) ?
			 ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) :
			 ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port),
			 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
			 tm->tm_hour, tm->tm_min, tm->tm_sec,
			 pxid, srv,
			 s->logs.t_request,
			 (s->logs.t_queue >= 0) ? s->logs.t_queue - s->logs.t_request : -1,
			 (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1,
			 (s->logs.t_data >= 0) ? s->logs.t_data - s->logs.t_connect : -1,
			 (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close,
			 s->logs.status,
			 (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes,
			 s->logs.cli_cookie ? s->logs.cli_cookie : "-",
			 s->logs.srv_cookie ? s->logs.srv_cookie : "-",
			 sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT],
			 sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT],
			 (p->options & PR_O_COOK_ANY) ? sess_cookie[(s->flags & SN_CK_MASK) >> SN_CK_SHIFT] : '-',
			 (p->options & PR_O_COOK_ANY) ? sess_set_cookie[(s->flags & SN_SCK_MASK) >> SN_SCK_SHIFT] : '-',
			 s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn,
			 s->logs.srv_queue_size, s->logs.prx_queue_size, tmpline);
	}
	else {
		send_log(p, LOG_INFO, "%s:%d [%02d/%s/%04d:%02d:%02d:%02d]"
			 " %s %s %d/%d/%s%d %s%lld"
			 " %c%c %d/%d/%d %d/%d\n",
			 pn,
			 (s->cli_addr.ss_family == AF_INET) ?
			 ntohs(((struct sockaddr_in *)&s->cli_addr)->sin_port) :
			 ntohs(((struct sockaddr_in6 *)&s->cli_addr)->sin6_port),
			 tm->tm_mday, monthname[tm->tm_mon], tm->tm_year+1900,
			 tm->tm_hour, tm->tm_min, tm->tm_sec,
			 pxid, srv,
			 (s->logs.t_queue >= 0) ? s->logs.t_queue : -1,
			 (s->logs.t_connect >= 0) ? s->logs.t_connect - s->logs.t_queue : -1,
			 (p->to_log & LW_BYTES) ? "" : "+", s->logs.t_close,
			 (p->to_log & LW_BYTES) ? "" : "+", s->logs.bytes,
			 sess_term_cond[(s->flags & SN_ERR_MASK) >> SN_ERR_SHIFT],
			 sess_fin_state[(s->flags & SN_FINST_MASK) >> SN_FINST_SHIFT],
			 s->srv ? s->srv->cur_sess : 0, p->nbconn, actconn,
			 s->logs.srv_queue_size, s->logs.prx_queue_size);
	}

	s->logs.logwait = 0;
}


/*
 * Initializes some data needed later.
 */
void init_log()
{
	int i;
	char *tmp;

	/* initialize the log header encoding map : '{|}"#' should be encoded with
	 * '#' as prefix, as well as non-printable characters ( <32 or >= 127 ).
	 * URL encoding only requires '"', '#' to be encoded as well as non-
	 * printable characters above.
	 */
	memset(hdr_encode_map, 0, sizeof(hdr_encode_map));
	memset(url_encode_map, 0, sizeof(url_encode_map));
	for (i = 0; i < 32; i++) {
		FD_SET(i, hdr_encode_map);
		FD_SET(i, url_encode_map);
	}
	for (i = 127; i < 256; i++) {
		FD_SET(i, hdr_encode_map);
		FD_SET(i, url_encode_map);
	}

	tmp = "\"#{|}";
	while (*tmp) {
		FD_SET(*tmp, hdr_encode_map);
		tmp++;
	}

	tmp = "\"#";
	while (*tmp) {
		FD_SET(*tmp, url_encode_map);
		tmp++;
	}
}

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 */
