/* This file is part of GNU Pies.
   Copyright (C) 2009-2022 Sergey Poznyakoff

   GNU Pies 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 3, or (at your option)
   any later version.

   GNU Pies 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 GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#include "pies.h"
#include "prog.h"
#include <netdb.h>

#define INTBUFSIZE	8192

/* Echo protocol, RFC 862 */
static void
echo_stream (int fd, struct component const *comp)
{
  int rc;
  char buffer[INTBUFSIZE];

  while ((rc = read (fd, buffer, sizeof buffer)) > 0
	 && write (fd, buffer, rc) > 0)
    ;
}

static void
echo_dg (int fd, struct component const *comp)
{
  int rc;
  char buffer[INTBUFSIZE];
  struct sockaddr sa;
  socklen_t size = sizeof sa;
  
  rc = recvfrom (fd, buffer, sizeof buffer, 0, &sa, &size);
  if (rc < 0)
    return;
  sendto (fd, buffer, rc, 0, &sa, sizeof sa);
}

/* Discard protocol, RFC 863 */
static void
discard_stream (int fd, struct component const *comp)
{
  int rc;
  char buffer[INTBUFSIZE];

  while (1)
    {
      while ((rc = read (fd, buffer, sizeof buffer)) > 0)
	;
      if (rc == 0 || errno != EINTR)
	break;
    }
}

static void
discard_dg (int fd, struct component const *comp)
{
  char buffer[INTBUFSIZE];
  read (fd, buffer, sizeof buffer);
}


/* Time Protocol, RFC 868 */

/* Return a machine readable date and time as seconds since
   midnight, Jan 1, 1900. */

#define SEVENTY_YEARS ((unsigned long)25567 * 24 * 60 * 60)

static unsigned long
time_since_1900 (void)
{
  struct timeval tv;

  if (gettimeofday (&tv, NULL) < 0)
    {
      logmsg (LOG_ERR, "gettimeofday: %s", strerror (errno));
      return 0;
    }
  return htonl ((long) (tv.tv_sec + SEVENTY_YEARS));
}

static void
time_stream (int fd, struct component const *comp)
{
  unsigned long result = time_since_1900 ();
  if (write (fd, (char *) &result, sizeof result) == -1)
    logmsg (LOG_ERR, "write: %s", strerror (errno));
}

static void
time_dg (int fd, struct component const *comp)
{
  unsigned long result;
  struct sockaddr sa;
  socklen_t size = sizeof sa;
  
  if (recvfrom (fd, (char *) &result, sizeof result, 0, &sa, &size) < 0)
    return;
  result = time_since_1900 ();
  sendto (fd, (char *) &result, sizeof result, 0, &sa, sizeof sa);
}

/* Daytime Protocol, RFC 867 */
static void
daytime_stream (int fd, struct component const *comp)
{
  char buffer[27];
  time_t now;

  time (&now);
  sprintf (buffer, "%.24s\r\n", ctime (&now));
  if (write (fd, buffer, strlen (buffer)) == -1)
    logmsg (LOG_ERR, "write: %s", strerror (errno));
}

static void
daytime_dg (int fd, struct component const *comp)
{
  char buffer[27];
  time_t now;
  struct sockaddr sa;
  socklen_t size = sizeof sa;

  time (&now);

  if (recvfrom (fd, buffer, sizeof buffer, 0, &sa, &size) < 0)
    return;
  sprintf (buffer, "%.24s\r\n", ctime (&now));
  sendto (fd, buffer, strlen (buffer), 0, &sa, sizeof sa);
}

/* Character Generator Protocol, RFC 864 */

#define LINESIZ 72

static void
chargen_next_line (char *text)
{
  static int ch = 0;
  int i, c;
  
  do
    ch = (ch + 1) % 128;
  while (!c_isprint (ch));
  
  for (i = 0, c = ch; i < LINESIZ; )
    {
      if (c_isprint (c))
	text[i++] = c;
      c = (c + 1) % 128;
    }
}

static void
chargen_stream (int fd, struct component const *comp)
{
  char text[LINESIZ + 2];

  text[LINESIZ] = '\r';
  text[LINESIZ + 1] = '\n';
  
  while (1)
    {
      chargen_next_line (text);
      if (write (fd, text, sizeof text) != sizeof text)
	break;
    }
}

static void
chargen_dg (int fd, struct component const *comp)
{
  struct sockaddr sa;
  socklen_t size = sizeof sa;
  char text[LINESIZ + 2];

  if (recvfrom (fd, text, sizeof text, 0, &sa, &size) < 0)
    return;
  text[LINESIZ] = '\r';
  text[LINESIZ + 1] = '\n';
  chargen_next_line (text);
  sendto (fd, text, sizeof text, 0, &sa, sizeof sa);
}  


/* Quote of the Day Protocol, RFC 865 */

#define QOTD_MAX 512
#define QOTD_DEF "Quote of the Day\r\n"

static size_t
trnl (char *text, size_t size)
{
  size_t off = size;
  
  for (; off > 0; off--)
    if (text[off] == '\n')
      {
	if (text[off-1] == '\r')
	  off--;
	else
	  {
	    if (size == QOTD_MAX)
	      size--;
	    memmove (text + off + 1, text + off, size - off);
	    text[off] = '\r';
	    size++;
	  }
      }
  return size;
}

static size_t
qotd_read (char *text)
{
  ssize_t rc;
  int fd = open (qotdfile, O_RDONLY);
  if (fd == -1)
    {
      logmsg (LOG_ERR, _("cannot open file %s: %s"), qotdfile,
	      strerror (errno));
      strncpy (text, QOTD_DEF, QOTD_MAX);
      rc = QOTD_MAX;
    }
  else
    {    
      rc = read (fd, text, QOTD_MAX);
      if (rc >= 0)
	rc = trnl (text, rc);
      else
	{
	  logmsg (LOG_ERR, _("error reading %s: %s"),
		  qotdfile, strerror (errno));
	  strncpy (text, QOTD_DEF, QOTD_MAX);
	  rc = QOTD_MAX;
	}
    }
  close (fd);
  return rc;
}

static void
qotd_stream (int fd, struct component const *comp)
{
  char text[QOTD_MAX];
  size_t len = qotd_read (text);
  if (write (fd, text, len) == -1)
    logmsg (LOG_ERR, "write: %s", strerror (errno));
}

static void
qotd_dg (int fd, struct component const *comp)
{
  char text[QOTD_MAX];
  struct sockaddr sa;
  socklen_t size = sizeof sa;
  size_t len;
  if (recvfrom (fd, text, sizeof text, 0, &sa, &size) < 0)
    return;
  len = qotd_read (text);
  sendto (fd, text, len, 0, &sa, sizeof sa);
}  


/* TCPMUX service, RFC 1078 */
#define MAX_SERV_LEN    (256+2)

static int
fd_getline (int fd, char *buf, int len)
{
  int count = 0, n;

  do
    {
      n = read (fd, buf, len - count);
      if (n == 0)
	return count;
      if (n < 0)
	return -1;
      while (--n >= 0)
	{
	  if (*buf == '\r' || *buf == '\n' || *buf == '\0')
	    return count;
	  count++;
	  buf++;
	}
    }
  while (count < len);
  return count;
}

static int
tcpmux_help (struct component *comp, void *data)
{
  int *pfd = data;

  if (ISCF_TCPMUX (comp->flags) && comp->prog && comp->prog->active)
    {
      fd_report (*pfd, comp->service);
      fd_report (*pfd, "\r\n");
    }
  return 0;
}

static void
tcpmux (int fd, struct component const *comp)
{
  char service[MAX_SERV_LEN + 1];
  size_t len;
  struct component *srv_comp;
  union pies_sockaddr_storage sa;
  socklen_t salen = sizeof (sa);
  int rc;
  
  /* Read service name */
  if ((len = fd_getline (fd, service, MAX_SERV_LEN)) < 0)
    {
      fd_report (fd, "-Error reading service name\r\n");
      return;
    }
  service[len] = 0;

  debug (2, ("tcpmux: someone wants %s", service));

  if (!strcasecmp (service, "help"))
    {
      component_foreach (tcpmux_help, &fd);
      return;
    }

  srv_comp = progman_lookup_tcpmux (service, comp->tag);
  if (!srv_comp)
    {
      fd_report (fd, "-Service not available\r\n");
      return;
    }

  rc = getpeername (fd, (struct sockaddr *) &sa, &salen);
  if (rc)
    logmsg (LOG_ERR, _("%s: cannot get peer name: %s"),
	    comp->tag, strerror (errno));
  
  if (comp->acl)
    {
      if (rc)
	{
	  fd_report (fd, "-Service not available\r\n");
	  return;
	}
      
      if (check_acl (comp->acl, (struct sockaddr *) &sa, salen, NULL))
	{
	  fd_report (fd, "-Service not available\r\n");
	  return;
	}
    }
  /* FIXME: What about max-instances, etc.? */
  
  if (srv_comp->flags & CF_TCPMUXPLUS)
    fd_report (fd, "+Go\r\n");
  
  progman_run_comp (srv_comp, fd, &sa, salen);
}


struct inetd_builtin inetd_builtin_tab[] = {
    /* Echo received data */
    {"echo",    SOCK_STREAM, 0, 0, echo_stream},
    {"echo",    SOCK_DGRAM,  1, 0, echo_dg},
    /* Internet /dev/null */
    {"discard", SOCK_STREAM, 0, 0, discard_stream},
    {"discard", SOCK_DGRAM,  1, 0, discard_dg},
    /* Return 32 bit time since 1900 */
    {"time",    SOCK_STREAM, 1, 0, time_stream},
    {"time",    SOCK_DGRAM,  1, 0, time_dg},
    /* Return human-readable time */
    {"daytime", SOCK_STREAM, 1, 0, daytime_stream},
    {"daytime", SOCK_DGRAM,  1, 0, daytime_dg},
    /* Character generator */
    {"chargen", SOCK_STREAM, 0, 0, chargen_stream},
    {"chargen", SOCK_DGRAM,  1, 0, chargen_dg},
    /* Quote of the Day */
    {"qotd",    SOCK_STREAM, 0, 0, qotd_stream},
    {"qotd",    SOCK_DGRAM,  1, 0, qotd_dg},
    /* TCPMUX */
    {"tcpmux",  SOCK_STREAM, 0, 0, tcpmux},
    {NULL, 0, 0, 0, NULL}
};

struct inetd_builtin *
inetd_builtin_lookup (const char *service, int socktype)
{
  struct inetd_builtin *bp;

  for (bp = inetd_builtin_tab; bp->service; bp++)
    if (bp->socktype == socktype && strcmp (bp->service, service) == 0)
      return bp;
  return NULL;
}
