/***************************************************************************
 *
 * 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: sources.c,v 1.37.4.11 2006/06/12 07:50:47 bazsi Exp $
 *
 ***************************************************************************/

#include "sources.h"
#include "xalloc.h"
#include "format.h"
#include "utils.h"
#include "cfgfile.h"
#include "objtypes.h"

#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

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

#include "sources.c.x"
#include "nscache.h"

/* CLASS:
     (class
       (name log_reader)
       (super read_handler)
       (vars
         (dgram simple UINT32)
         (prefix pointer UINT8)
	 (pos simple UINT32)
	 (buffer space UINT8)
	 (max_log_line simple UINT32)
	 (pad_size simple UINT32)
	 (msg_flags simple UINT32)
	 (bad_hostname special-struct regex_t #f regfree)
         (next object log_handler)))
*/

static int 
do_handle_line(struct log_reader *self, 
	       UINT32 length, UINT8 *data, 
	       abstract_addr *addr, 
	       size_t addrlen)
{
	struct log_info *logmsg;

	logmsg = make_log_info(length, data, self->prefix, self->msg_flags, &self->bad_hostname);
	if (addrlen) {
		logmsg->saddr = sockaddr2address_info(addrlen, addr);
	}
	HANDLE_LOG(self->next, logmsg);
	return ST_OK | ST_GOON;
}

static int 
do_read_line(struct read_handler **h, 
	     struct abstract_read *read)
{
	CAST(log_reader, closure, *h);
	UINT8 *eol, *start;
	UINT32 length;
	int n;
	char sabuf[256];
	socklen_t salen = sizeof(sabuf);
	int fetch_count = 0;
	int fetch_max = (closure->msg_flags & LF_NO_MULTI_READ) ? 1 : 30;
	
	while (fetch_count < fetch_max) {

		if (!closure->dgram) {
			if (closure->pad_size)
				n = A_READ(read, LIBOL_MIN(closure->max_log_line, closure->pad_size), closure->buffer + closure->pos);
			else
				n = A_READ(read, closure->max_log_line - closure->pos, closure->buffer + closure->pos);
			salen = 0;
		}
		else
			n = A_RECV(read, closure->max_log_line - closure->pos, closure->buffer + closure->pos, (abstract_addr *) &sabuf, &salen);

		switch(n) {
		case 0:
			return ST_OK | ST_GOON;
		case A_FAIL:
			/* Fall through */
			return ST_FAIL | ST_CLOSE;
		case A_EOF:
			return ST_OK | ST_CLOSE;
		}
		closure->pos += n;

		eol = memchr(closure->buffer, '\0', closure->pos);
		if (eol == NULL)
			eol = memchr(closure->buffer, '\n', closure->pos);
		if (closure->pad_size && eol) {
			do_handle_line(closure, eol - closure->buffer, closure->buffer, salen ? (abstract_addr *) &sabuf : NULL, salen);
			closure->pos = 0;
			goto next_fetch;
		}
		if (closure->dgram || (!eol && closure->pos == closure->max_log_line)) {
			/* we don't have a terminating nl nor \0, and our buffer is
			   full or we are a datagram receiver, when the message is in
			   its own packet.
			 */
			if (!eol && closure->pos == closure->max_log_line) {
				werror("Message length overflow, line is split, log_msg_size=%i\n", closure->max_log_line);
			}
			if (closure->dgram) {
				/* strip one trailing LF or NUL character */
				if (closure->pos > 0 && 
				    (closure->buffer[closure->pos - 1] == '\n' || 
				     closure->buffer[closure->pos - 1] == '\0'))
					closure->pos--;
			}
			do_handle_line(closure, closure->pos, closure->buffer, salen ? (abstract_addr *) &sabuf : NULL, salen);
			closure->pos = 0;
			goto next_fetch;
		}
		start = closure->buffer;
		while (eol) {
			/* eol points at the newline character. end points at the
			 * character terminating the line, which may be a carriage
			 * return preceeding the newline. */
			UINT8 *end = eol;

			while ((end > start) && (end[-1] == '\r' || end[-1] == '\n' || end[-1] == 0))
				end--;

			length = end - start;
			
			if (length)
				do_handle_line(closure, length, start, salen ? (abstract_addr *) &sabuf : NULL , salen);

			start = eol + 1;

			eol = memchr(start, '\0', &closure->buffer[closure->pos] - start);
			if (eol == NULL)
				eol = memchr(start, '\n', &closure->buffer[closure->pos] - start);

		}
		memmove(closure->buffer, start, &closure->buffer[closure->pos] - start);
		closure->pos = &closure->buffer[closure->pos] - start;
		
	next_fetch:
		fetch_count++;
	}
	return ST_OK | ST_GOON;
}

struct read_handler *
make_log_reader(UINT32 dgram, 
		UINT8 *prefix,
		UINT32 max_log_line,
		UINT32 pad_size,
		UINT32 msg_flags,
		UINT8 *hostname_re,
		struct log_handler *next)
{
	NEW(log_reader, self);

	self->super.handler = do_read_line;
	self->dgram = dgram;
	self->next = next;
	self->prefix = prefix;
	self->max_log_line = LIBOL_MAX(max_log_line, pad_size);
	self->pad_size = pad_size;
	self->msg_flags = msg_flags;
	self->buffer = ol_space_alloc(self->max_log_line);
	if (hostname_re == NULL)
		regcomp(&self->bad_hostname, "^$", REG_NOSUB | REG_EXTENDED);
	else
		regcomp(&self->bad_hostname, (char *) hostname_re, REG_NOSUB | REG_EXTENDED);
	return &self->super;
}

/* source_group */

static struct ol_string *get_source_hostname(struct address_info *a, int usedns, int usefqdn, struct nscache *cache)
{
	struct ol_string *name;
	static struct ol_string *hostname = NULL;
	
	if (a && a->super.isa == &inet_address_info_class) {
		CAST(inet_address_info, inet_addr, a);
		char *hname, *p;

		if (usedns && cache) {
			hname = nscache_lookup(cache, inet_addr->sa.sin_addr);
		}
		else if (usedns) {
			struct hostent *hp;

			hp = gethostbyaddr((char *) &(inet_addr->sa.sin_addr),
					   sizeof(struct in_addr), AF_INET);
			hname = (hp && hp->h_name) ? hp->h_name : NULL;
		} 
		else
			hname = NULL;

		if (!hname) {
			return ol_string_use(inet_addr->ip);
		}
		else {
			if (!usefqdn) {
				p = strchr(hname, '.');
				if (p) *p = 0;
			}
		}
		name = c_format_cstring("%z", hname);
	}
	else {
		if (!hostname) {
			char buf[256];
			if (usefqdn) {
				gethostname(buf, sizeof(buf) - 1);
				buf[sizeof(buf) - 1] = 0;
				// Check if hostname includes a . else do an fqdn lookup
				if (strchr(buf, '.') == NULL) {
					struct hostent *result = gethostbyname(buf);
					if (result) {
						strncpy(buf, result->h_name, sizeof(buf) - 1);
					}
				}
			}
			else {
				getshorthostname(buf, sizeof(buf));
			}
			hostname = c_format_cstring("%z", buf);
		}

		ol_string_use(hostname);
		name = hostname;
	}
	return name;
}

static void do_add_source_name(struct log_handler *c, struct log_info *logmsg)
{
	CAST(log_source_group, self, c);
	struct ol_string *name;


	if (!self->super.next) {
		log_info_free(logmsg);
		return;
	}
	logmsg->source = c;
	name = get_source_hostname(logmsg->saddr, 
				   self->use_dns, self->use_fqdn,
				   self->cache);
	logmsg->host_from = name;
	if (!self->keep_hostname || !logmsg->host) {
		ol_string_use(name);
		if (self->chain_hostnames) {
			if (logmsg->flags & LF_LOCAL) {
				/* local */
				ol_string_free(logmsg->host);
				logmsg->host = c_format("%S@%fS", 
							self->name, name);
			}
			else if (!logmsg->host) {
				/* remote && no hostname */
				logmsg->host = c_format("%S/%fS", name, name);
			} 
			else {
				/* everything else, append source hostname */
				if (logmsg->host)
					logmsg->host = 
						c_format("%fS/%fS", 
							 logmsg->host, name);
				else
					logmsg->host = 
						c_format("%fS", name);
			}
		}
		else {
			ol_string_free(logmsg->host);
			logmsg->host = c_format("%fS", name);
		}
	}
	HANDLE_LOG(self->super.next, logmsg);
}

static int do_init_group(struct log_handler *c, struct syslog_config *cfg, struct persistent_config *persistent)
{
	CAST(log_source_group, self, c);
	int res;
	
	struct log_source_driver *drv;
	
	self->chain_hostnames = cfg->chain_hostnames;
	self->use_dns = cfg->use_dns;
	self->use_fqdn = cfg->use_fqdn;
	self->cache = cfg->cache;
	self->keep_hostname = cfg->keep_hostname;
	res = 0;
	for (drv = self->drivers; drv; drv = drv->next_driver) {
		res = LOG_HANDLER_INIT(drv, cfg, persistent);
		if (res & ST_QUIT) 
			break;
	}
	return res;
}

static void do_destroy_group(struct log_handler *c, struct syslog_config *cfg, struct persistent_config *persistent)
{
	CAST(log_source_group, self, c);
	
	struct log_source_driver *drv;
	
	for (drv = self->drivers; drv; drv = drv->next_driver) {
		if (drv->super.super.destroy)
			LOG_HANDLER_DESTROY(drv, cfg, persistent);
	}
}

void set_source_drivers(struct log_source_group *src, struct log_source_driver *drvs)
{
	struct log_source_driver *drv;
	
	src->drivers = drvs;
	for (drv = src->drivers; drv; drv = drv->next_driver) {
		append_log_handler(drv, src);
	}
}

struct log_source_group *make_source_group(const char *name, struct log_source_driver *drvs)
{
	NEW(log_source_group, self);
  
	self->super.super.init = do_init_group;  
	self->super.super.destroy = do_destroy_group;
	self->super.super.handler = do_add_source_name;
	self->name = c_format("%z", name);
	set_source_drivers(self, drvs);
	
	return self;
}


syntax highlighted by Code2HTML, v. 0.9.1