/*****************************************************************************/
/*
                                  SysLog.c

Syslog is a standard for sending and receiving notification messages in a
particular format from various network devices.  The messages include time
stamps, event messages, severity, host IP addresses, diagnostics and more.

   https://en.wikipedia.org/wiki/Syslog

   https://www.ietf.org/rfc/rfc5424.html

   https://www.ietf.org/rfc/rfc3164.html

There is not much to be gained (apart from complexity) for doing this via
system services so it's implemented using BSD sockets.

A maximum of four syslogd servers may be specified via the logical name
WASD_SYSLOG, using a string "host[:port][/pri][,host[:port][/pri]]".  The code
checks the logical name every minute for changes and reinitialises the syslogd
services if modified (so no need for a server restart for changes).

   $ DEFINE /SYSTEM /EXEC WASD_SYSLOG "klaatu.lan"

Where |host| is the IP address or host name of the syslogd server.

A non-default (514) port may be specified.

   $ DEFINE /SYSTEM /EXEC WASD_SYSLOG "klaatu.lan:6514"

The default is an RFC5424 message.  This comprises a place holder PRI, VERSION,
TIMESTAMP, HOSTNAME, APP-NAME, PROCID, MSGID and then free-form data.

The optional |pri| value, introduced by a slash sign, allows the format of the
message to be specified.  If a positive integer that value should correspond
to a valid PRI and is used in the RFC5424 message.

   $ DEFINE /SYSTEM /EXEC WASD_SYSLOG "klaatu.lan/123"

If specified as a negative zero the message is formatted as RFC3164.

   $ DEFINE /SYSTEM /EXEC WASD_SYSLOG "klaatu.lan/-0"

If specified as a negative integer that value should correspond to a valid PRI.
This example sets the PRI to local use 0 plus informational.

   $ DEFINE /SYSTEM /EXEC WASD_SYSLOG "klaatu.lan/-123"


VERSION HISTORY
---------------
01-FEB-2022  MGD  initial
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <unixio.h>

#include <in.h>
#include <inet.h>
#include <netdb.h>
#include <socket.h>

#include "wasd.h"
#include "tcpip.h"

#define WASD_MODULE "SYSLOG"

#define SYSLOG_MAX 4
#define SYSLOG_PORT 514
/* system daemons plus warning */
#define SYSLOG_PRI ((3 * 8) + 3)

/******************/
/* global storage */
/******************/

static int  sockfd [SYSLOG_MAX],
            syspri [SYSLOG_MAX];
static char  syslogd [SYSLOG_MAX][64];
static char  wasdSysLog [256];
static struct sockaddr_in  serv_addr [SYSLOG_MAX];

/********************/
/* external storage */
/********************/

extern int64  HttpdTime64;
extern ushort  HttpdTime7[];
extern char  ServerHostName[],
             TimeGmtString[];
extern struct dsc$descriptor TcpIpDeviceDsc;
extern SYS_INFO  SysInfo;
extern HTTPD_PROCESS  HttpdProcess;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Called at server startup and thereafter every minute to allow changes.
*/

void SysLogInit ()

{
   int  idx;
   ushort  port;
   char  ch;
   char  *cptr, *sptr, *zptr;
   struct in_addr  addr;
   struct hostent  *heptr;
   void  *saptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "SysLogInit()");

   if (!(cptr = SysTrnLnm ("WASD_SYSLOG")))
   {
      /*******************/
      /* no logical name */
      /*******************/

      if (!sockfd[0]) return;
      for (idx = 0; idx < SYSLOG_MAX && sockfd[idx]; idx++)
      {
         if (sockfd[idx]) close (sockfd[idx]);
         sockfd[idx] = syspri[idx] = 0;
         syslogd[idx][0] = '\0';
      }
      return;
   }

   /* if no change in the logical name value */
   if (!strcmp (cptr, wasdSysLog)) return;

   /************************/
   /* changed logical name */
   /************************/

   /* clear any previous syslogd data */
   for (idx = 0; idx < SYSLOG_MAX && sockfd[idx]; idx++)
   {
      if (sockfd[idx]) close (sockfd[idx]);
      sockfd[idx] = syspri[idx] = 0;
      syslogd[idx][0] = '\0';
   }

   /* store the current configuration */
   zptr = (sptr = wasdSysLog) + sizeof(wasdSysLog)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   /*************************************/
   /* set up syslogd datagram server(s) */
   /*************************************/

   /* host[:port][/pri][,host[:port][/pri]] */
   idx = -1;
   cptr = wasdSysLog;
   while (*cptr)
   {
      while (*cptr == ',' || *cptr == ' ') cptr++;
      if (!*cptr) break;

      if (++idx >= SYSLOG_MAX) break;

      memset (&serv_addr[idx], 0, sizeof(serv_addr[0]));

      zptr = (sptr = syslogd[idx]) + sizeof(syslogd[0])-1;
      while (*cptr && *cptr != ',' && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';

      for (sptr = syslogd[idx]; *sptr && *sptr != ':' && *sptr != '/'; sptr++);
      ch = *sptr;
      *sptr = '\0';

      saptr = &serv_addr[idx].sin_addr;
      addr.s_addr = inet_addr (syslogd[idx]);
      if (addr.s_addr != INADDR_NONE)
         memcpy (saptr, &addr, sizeof(struct in_addr));
      else
      {
         if (heptr = gethostbyname(syslogd[idx]))
            memcpy (saptr, heptr->h_addr, sizeof(struct in_addr));
         else
         {
            *sptr = ch;
            FaoToStdout ("%HTTPD-W-SYSLOG, !AZ !AZ\n",
                         syslogd[idx], strerror(errno));
            continue;
         }
      }

      *sptr = ch;

      while (*sptr && *sptr != ':' && *sptr != '/') sptr++;
      if (*sptr == ':')
      {
         port = atoi (++sptr);
         while (isdigit(*sptr)) sptr++;
      }
      else
         port = SYSLOG_PORT;

      if (*sptr == '/')
      {
         syspri[idx] = atoi (++sptr);
         if (!syspri[idx])
            if (*sptr == '-')
               syspri[idx] = -SYSLOG_PRI;
            else
               syspri[idx] = SYSLOG_PRI;
      }
      else
         syspri[idx] = SYSLOG_PRI;

      serv_addr[idx].sin_family = AF_INET;
      serv_addr[idx].sin_port = htons(port);

      if ((sockfd[idx] = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
      {
         if (WATCH_MODULE(WATCH_MOD_NET))
            WatchThis (WATCHALL, WATCH_MOD_NET, "!AZ", sptr);
         FaoToStdout ("%HTTPD-W-SYSLOG, !AZ !AZ\n",
                      syslogd[idx], strerror(errno));
         continue;
      }

      FaoToStdout ("%HTTPD-I-SYSLOG, !AZ\n", syslogd[idx]);
   }
}

/*****************************************************************************/
/*
*/

void SysLogOpcom (char* OpcomMsg)

{
   int  idx, len, status;
   ushort  slen;
   char  *cptr, *sptr, *zptr;
   char  buf [986+256],
         dtime [32];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "SysLogOpcom()");

   if (!sockfd[0]) return;

   FaoToBuffer (dtime, sizeof(dtime), NULL,
                "!UL-!2ZL-!2ZLT!2ZL:!2ZL:!2ZL.!2ZL!AZ",
                HttpdTime7[0], HttpdTime7[1], HttpdTime7[2],
                HttpdTime7[3], HttpdTime7[4], HttpdTime7[5],
                HttpdTime7[6], TimeGmtString);

   for (idx = 0; idx < SYSLOG_MAX && sockfd[idx]; idx++)
   {
      zptr = (sptr = buf) + sizeof(buf)-5;

      if (syspri[idx] > 0)
         FaoToBuffer (buf, sizeof(buf), &slen,
"<!UL>1 !AZ !AZ !AZ !8XL !AZ !%T Message from user !AZ on !AZ | ",
                      syspri[idx], dtime, ServerHostName, "wasd",
                      HttpdProcess.Pid, "OPCOM", &HttpdTime64,
                      HttpdProcess.UserName, SysInfo.NodeName);
      else
         FaoToBuffer (buf, sizeof(buf), &slen,
                      "<!UL>OPCOM message from user !AZ on !AZ | ",
                      -syspri[idx], HttpdProcess.UserName, SysInfo.NodeName);
      sptr += slen;

      for (cptr = OpcomMsg; *cptr; cptr++)
      {
         if (sptr >= zptr) break;
         if (*cptr == '\n')
         {
            *sptr++ = ' ';
            *sptr++ = '|';
            *sptr++ = ' ';
         }
         else
         if (*cptr == '\r')
            continue;
         else
         if (*cptr < 32 || *cptr > 127)
            sptr += sprintf (buf, "\\x%02.02x", *cptr);
         else
            *sptr++ = *cptr;
      }
      *sptr = '\0';
      len = sptr - buf;

      if (WATCH_MODULE(WATCH_MOD_NET))
         WatchThis (WATCHALL, WATCH_MOD_NET, "{!UL}!AZ", len, buf);

      if (sendto (sockfd[idx], buf, len, 0,
                  (struct sockaddr *)&(serv_addr[idx]),
                  sizeof(serv_addr[0])) < 0)
         FaoToStdout ("%HTTPD-W-SYSLOG, !AZ !AZ\n",
                      syslogd[idx], strerror(errno));
   }
}

/*****************************************************************************/

