/*
 *  Simple syslog handler for Win32.
 *
 *  Loosely based on BSD-version.
 *    by G. Vanem <giva@bgnett.no>  Jun-2003
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include "syslog.h"
#define DMID 17
#include "debug.h"

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock.h>
//#include <iphlpapi.h>

#include "printk.h"
const char *time_now_ftp (void);

#define SYSLOG_BUF_SIZE 2048

static SOCKET      logSock     = INVALID_SOCKET;  /* UDP socket for log-daemon */
static FILE       *logFile     = NULL;            /* FILE* for log-file */
static const char *logFileName = NULL;            /* name of log-file */
static DWORD       logHostAddr = 0UL;             /* IP-address of log-daemon (network order) */
static WORD        logHostPort = 514;               /* destination port for syslog daemon */
static BOOL        logOpened   = FALSE;           /* have done openlog() */
static int         logStat     = 0;               /* status bits, set by openlog() */
static char       logTag[200];
static int         logFacil    = LOG_USER;        /* default facility code */
static int         logMask     = 0xFF;            /* mask of priorities to be logged */
static int default_port = 514;
static DWORD my_ip_addr  = INADDR_ANY;     /* IP of local interface (network order) */
static DWORD net_mask    = INADDR_NONE;    /* netmask for IP (network order) */
static char *logHostName;
static char  err_buf [256];

static const char *sock_error (void);
static BOOL get_netmask (DWORD *mask);

/*
 * syslog, vsyslog --
 *    print message on log file or to loghost.
 */
int syslog (int pri, const char *fmt, ...)
{
  int rc;
  va_list args;
  va_start (args, fmt);
  rc = vsyslog (pri, fmt, args);
  va_end (args);
  return (rc);
}

int vsyslog (int pri, const char *fmt, va_list ap)
{
  char  tbuffer [SYSLOG_BUF_SIZE+2];
  char *p = tbuffer;
  int   left = sizeof(tbuffer);
  int   saved_errno = errno;
  int   rc = 0;
  char *s;
  struct sockaddr_in addr;
  static char hostname[2000];

  if ((int)strlen(hostname)==0) {
	gethostname(hostname,1000);
  }

  err_buf[0] = '\0';

  /* Build the message
   */
  p    += _snprintf (p, left, "<%d>%s %s %s ", pri + logFacil, time_now_ftp(),hostname,logTag);
  
  left -= (int)(p - tbuffer);

  errno = saved_errno;
  p += vsnprintk (p, left, fmt, ap);
  s = strchr(tbuffer,'\n'); if (s!=NULL) *s = 0;
  s = strchr(tbuffer,'\r'); if (s!=NULL) *s = 0;
  strcat(tbuffer,"\n");
  if (!logOpened) return FALSE;
  if (logSock == INVALID_SOCKET) return FALSE;

  memset (&addr, 0, sizeof(addr));    
	addr.sin_family      = AF_INET;
	addr.sin_addr.s_addr = logHostAddr;
	addr.sin_port        = htons(default_port);    

//printf("sending (%s)\n",tbuffer);
    if (sendto(logSock, tbuffer, (int)strlen(tbuffer), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0 &&
        WSAGetLastError() != WSAEMSGSIZE) 
    {
      const char *err = sock_error();
      char  tmp[256];

      closesocket (logSock);
      logSock = INVALID_SOCKET;
      _snprintf (tmp, sizeof(tmp), "UDP-write failed; %s\n", err);
      tmp[255] = 0;
      strcpy (err_buf, tmp);
      rc = -1;
    }

  return (rc);
}

static const char *getlogname (void)
{
  char  path [MAX_PATH] = { 0 };
  char *name, *exe;

  if (!GetModuleFileName(NULL,path,sizeof(path)) || !path[0])
     goto unknown;

  name = strdup (path);
  if (!name)
     goto unknown;

  exe = strrchr (name, '.');            /* ".exe" or ".dll" */
  if (exe && exe <= strchr(name,'\0')-4)
  {
    strcpy (exe, ".log");
    return (name);
  }
unknown:
  return strdup ("$unknown.log");
}

static char *rip (char *s)
{
  char *p;

  if ((p = strrchr(s,'\n')) != NULL) *p = '\0';
  if ((p = strrchr(s,'\r')) != NULL) *p = '\0';
  return (s);
}

static const char *sock_error (void)
{
  static char buf[256];
  DWORD  err  = WSAGetLastError();
  DWORD  lang = LANG_NEUTRAL;

  if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, lang,
                      buf, sizeof(buf)-1, NULL))
    sprintf (buf, "Unknown error 0x%08lX", err);
  return rip (rip(buf));
}

static BOOL sock_init (void)
{
  static  BOOL done = FALSE;
  static  BOOL rc   = FALSE;
  char    myHostName[1024];
  struct  servent *se;
  WSADATA wsaData;

  if (done)
     return (rc);

  done = 1;
//printf("calling wsa satartup socket\n");
  if (WSAStartup (MAKEWORD(1,1), &wsaData) != 0)
  {
    _snprintf (err_buf, sizeof(err_buf), "Winsock init failed; %s", sock_error());
    return (rc);
  }

  if (gethostname(myHostName, sizeof(myHostName)) == 0)
  {
    struct  hostent *he = gethostbyname (myHostName);    
    if (!he)
    {
      _snprintf (err_buf, sizeof(err_buf), "Failed to get local IP-address");
      return (rc);
    }
    my_ip_addr = *(DWORD*)he->h_addr;
  }

  se = getservbyname ("syslog", "udp");
  logHostPort = se ? se->s_port : htons(514);
//printf("sending to port %d\n",logHostPort);

  net_mask = INADDR_NONE;   /* point-to-point */
  rc = TRUE;
  return (rc);
}

/*
 * Return IP-address of name on network order 
 */
static DWORD resolve (const char *name)
{
  struct hostent *he;
  DWORD  ip = inet_addr (name);

  if (ip != INADDR_NONE)
     return (ip);
  
  he = gethostbyname (name);
  if (!he)
  {
    _snprintf (err_buf, sizeof(err_buf), "Unknown host `%s'", name);
    return (0);
  }
  ip = *(DWORD*)he->h_addr;
  return (ip);
}

/*
 * Check if host address 'ip' replies on ARP.
 */
static BOOL check_arp (DWORD ip)
{
	return FALSE;
}

/* 
 * Return true if 'ip' is on this LAN.
 */
static BOOL is_on_LAN (DWORD ip)
{  
  return (((ntohl(ip) ^ ntohl(my_ip_addr)) & ntohl(net_mask)) == 0);
}

/* 
 * Return true if 'ip' is a (directed) ip-broadcast address.
 */
static BOOL is_broadcast (DWORD ip)
{
  return ((~ntohl(ip) & ~ntohl(net_mask)) == 0);
}

/*
 * Open a UDP "connection" to log-host
 */
static int openloghost (const char *hostName)
{
  if (!sock_init())
     return (-1);

  //printf("openloghost (%s)\n",hostName);
  if (logSock != INVALID_SOCKET)
  {
    closesocket (logSock);
    logSock = INVALID_SOCKET;
  }

  logHostAddr = resolve (hostName);
  if (!logHostAddr)
     return (-1);

  if (is_on_LAN(logHostAddr) && !is_broadcast(logHostAddr) && !check_arp(logHostAddr))
     return (-1);

//printf("creating socket\n");
  logSock = socket (AF_INET, SOCK_DGRAM, 0);
  if (logSock == INVALID_SOCKET)
  {
    _snprintf (err_buf, sizeof(err_buf), "socket() failed; %s\n", sock_error());
    return (-1);
  }
  if (is_broadcast(logHostAddr))
  {
    BOOL on = TRUE;
    setsockopt (logSock, SOL_SOCKET, SO_BROADCAST, (const char*)&on, sizeof(on));
  }
  return (0);
}

int openlog (const char *ident, int options, int logfac)
{
  int rc = -1;  /* pessimistic */

//	printf("openlog (%s)\n",ident);
  strcpy(logTag,(char*)ident);

  logStat = options;

  logFacil = logfac;

  if (!logFileName)        /* not already set */
     logFileName = getlogname();
  
  if (logMask)
     setlogmask (logMask);

 
  logOpened = TRUE;  /* prevent recursion below */
  

  rc = openloghost (logHostName);
  return (rc);
}

int closelog (void)
{
  if (logFile)
     fclose (logFile);
  if (logSock != INVALID_SOCKET)
     closesocket (logSock);
  if (logFileName)
     free ((void*)logFileName);
  if (logHostName)
     free ((void*)logHostName);

  logSock     = INVALID_SOCKET;
  logFile     = NULL;
  logFileName = NULL;
  logHostName = NULL;
  logOpened   = FALSE;
  err_buf[0]  = '\0';
  return (0);
}

int setlogmask (int pri_mask)
{
  int old = logMask;
  if (pri_mask)
     logMask = pri_mask;
  return (old);
}

char *setlogtag (char *newx)
{
  char *old = logTag;
  strcpy(logTag,newx);
  return (old);
}

const char *syslog_logfilename (void)
{
  return (logFileName);
}

const char *syslog_strerror (void)
{
  return (err_buf);
}

void syslog_port(int n)
{
	if (n==0) n = 514;
	default_port = n;
}
const char *syslog_loghost (const char *host)
{
  if (logHostName)
     free (logHostName);
  logHostName = NULL;
  if (!host)
     return (NULL);

  logHostName = strdup (host);
  if (!strcmp(host,"0.0.0.0"))
     return (logHostName);

  if (!sock_init() || !resolve(host))
     return (NULL);
  return (logHostName);
}
