/*
%%% copyright-cmetz
This software is Copyright 1996 by Craig Metz, All Rights Reserved.
The Inner Net License Version 2 applies to this software.
You should have received a copy of the license with this software. If
you didn't get a copy, you may request one from <license@inner.net>.

v0.04.
*/

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet6/in6.h>
#include <syslog.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <netdb.h>

#include "support.h"

int debug = 0;
char *myname;

char buffer[1024];
char hostname[128];
char servicename[32];

void promote_sockaddr(struct sockaddr_in *sin, struct sockaddr_in6 *sin6)
{
#ifdef SIN6_LEN
  sin6->sin6_len = sizeof(struct sockaddr_in6);
#endif /* SIN6_LEN */
  sin6->sin6_family = AF_INET6;
  sin6->sin6_port = sin->sin_port;
  ((u_int32_t *)&sin6->sin6_addr)[0] = 0;
  ((u_int32_t *)&sin6->sin6_addr)[1] = 0;
  ((u_int32_t *)&sin6->sin6_addr)[2] = htonl(0xffff);
  ((u_int32_t *)&sin6->sin6_addr)[3] = sin->sin_addr.s_addr;
}

int compare_sockaddr(struct sockaddr *sa1, struct sockaddr *sa2, int masklen)
{
  u_int8_t *a1, *a2, *a1m, *a2m, *m;
  union {
    struct sockaddr sa;
    struct sockaddr_in sin;
    struct sockaddr_in6 sin6;
  } sa1m, sa2m, mask;
  int i;

  if ((i = NRL_SA_LEN(sa1)) != NRL_SA_LEN(sa2))
    return -1;

  if (sa1->sa_family != sa2->sa_family)
    return -1;

  if (nrl_build_samask((struct sockaddr *)&mask, sa1->sa_family, masklen)) {
    syslog(LOG_ERR, "can't build mask (len=%d, family=%d)", masklen, sa1->sa_family);
    return -1;
  }

  a1 = (u_int8_t *)sa1;
  a1m = (u_int8_t *)&sa1m;
  a2 = (u_int8_t *)sa2;
  a2m = (u_int8_t *)&sa2m;
  m = (u_int8_t *)&mask;

  while(i--) {
    *(a1m++) = *(a1++) & *m;
    *(a2m++) = *(a2++) & *(m++);
  };

  if (debug) {
    syslog(LOG_DEBUG, "sa1:");
    dump_sockaddr(sa1);
    syslog(LOG_DEBUG, "sa2:");
    dump_sockaddr(sa2);
    mask.sa.sa_family = sa1->sa_family;
    syslog(LOG_DEBUG, "mask:");
    dump_sockaddr((struct sockaddr *)&mask);
    syslog(LOG_DEBUG, "sa1m:");
    dump_sockaddr((struct sockaddr *)&sa1m);
    syslog(LOG_DEBUG, "sa2m:");
    dump_sockaddr((struct sockaddr *)&sa2m);
  }
  return memcmp(&sa1m, &sa2m, NRL_SA_LEN((struct sockaddr *)sa1));
};

int main(int argc, char **argv)
{
  FILE *f;
  char *c, *c2, *service;
  union {
    struct sockaddr sa;
    struct sockaddr_in sin;
    struct sockaddr_in6 sin6;
  } peer, peerdowngraded;
  int i, hostmasklen, masklen, peerlen = sizeof(peer), line = 0;
  struct addrinfo *ai, req;

  if (myname = strrchr(argv[0], '/'))
    myname++;
  else
    myname = argv[0];

  if (argc < 2) {
    fprintf(stderr, "usage: %s [-d] <service>\n", myname);
    exit(1);
  }

  openlog(myname, LOG_PID, LOG_DAEMON);

  while((i = getopt(argc, argv, "d")) != EOF) {
    switch(i) {
      case 'd':
        debug = 1;
        break;
    };
  };

  service = argv[optind];

  if (getpeername(0, (struct sockaddr *)&peer, &peerlen)) {
    syslog(LOG_ERR, "getpeername: %s(%d)", strerror(errno), errno);
    exit(1);
  };

  switch(peer.sa.sa_family) {
    case AF_INET6:
      hostmasklen = 128;
      break;
    case AF_INET:
      hostmasklen = 32;
      break;
    default:
      syslog(LOG_ERR, "don't know how to handle %s sockets!", nrl_afnumtoname(peer.sa.sa_family));
      exit(1);
  };

  memset(&req, 0, sizeof(struct addrinfo));
  req.ai_socktype = SOCK_STREAM;

  {
    char hbuf[64];

    if (nrl_getnameinfo((struct sockaddr *)&peer, NRL_SA_LEN((struct sockaddr *)&peer), hbuf, sizeof(hbuf), NULL, 0, 1)) {
      syslog(LOG_ERR, "getnameinfo failed");
      exit(1);
    };

    if (nrl_getnameinfo((struct sockaddr *)&peer, NRL_SA_LEN((struct sockaddr *)&peer), hostname, sizeof(hostname), servicename, sizeof(servicename), 2)) {
      syslog(LOG_ERR, "getnameinfo failed");
      exit(1);
    };

    syslog(LOG_INFO, "connection from %s.%s (%s)", hostname, servicename, hbuf);
  };

  if (i = getaddrinfo(hostname, servicename, &req, &ai)) {
    syslog(LOG_ERR, "reverse name is bogus: getaddrinfo %s.%s: %s(%d)", hostname, servicename, nrl_gai_strerror(i), i);
    exit(1);
  };

  {
    struct addrinfo *ai2 = ai;
    while(ai2) {
      if (!compare_sockaddr((struct sockaddr *)&peer, ai2->ai_addr, hostmasklen))
	goto dnsok;

      if ((peer.sa.sa_family == AF_INET6) && (ai2->ai_family == AF_INET)) {
	union {
	  struct sockaddr sa;
	  struct sockaddr_in sin;
	  struct sockaddr_in6 sin6;
	} sap;
	promote_sockaddr((struct sockaddr_in *)ai2->ai_addr, &sap.sin6);

	if (!compare_sockaddr((struct sockaddr *)&peer, (struct sockaddr *)&sap, 128))
	  goto dnsok;
      };

      ai2 = ai2->ai_next;
    };
  };
  freeaddrinfo(ai);

  syslog(LOG_ERR, "reverse name is bogus; no forward addresses match");
  exit(1);

dnsok:
  freeaddrinfo(ai);

  {
    sprintf(buffer, "/etc/%s.conf", myname);

    if (!(f = fopen(buffer, "r"))) {
      syslog(LOG_ERR, "can't open config file %s", buffer);
      exit(1);
    };
  }

  while(fgets(buffer, sizeof(buffer), f)) {
    if (c = strchr(buffer, '#'))
      *c = 0;
    line++;
    if (debug)
      syslog(LOG_DEBUG, "line %d: %s", line, buffer);
    c2 = buffer;
    while(*c2 && isspace(*c2)) c2++;
    if (!*c2) {
      if (debug)
        syslog(LOG_DEBUG, "(parse error 1)");
      continue;
    }
    c = c2;
    while(*c2 && !isspace(*c2)) c2++;
    if (!*c2) {
      if (debug)
        syslog(LOG_DEBUG, "(parse error 2)");
      continue;
    }
    *c2 = 0;
    if (strcmp(c, service) && strcmp(c, "*")) {
      if (debug)
        syslog(LOG_DEBUG, "(parse error 3 - +%s+ != +%s+", c, service);
      continue;
    }
    c2++;
    while(*c2 && isspace(*c2)) c2++;

    c = c2;
    while(*c2 && !isspace(*c2) && (*c2 != '/')) c2++;
    if (!*c2) {
      syslog(LOG_DEBUG, "line %d: out of data", line);
      continue;
    }
    if (*c2 == '/') {
      *c2 = 0;
      c2++;
      if (debug)
        syslog(LOG_DEBUG, "line %d: c2 = %s", line, c2);
      if ((masklen = strtoul(c2, &c2, 10)) > 128) {
	syslog(LOG_DEBUG, "line %d: error parsing mask length", line);
	continue;
      }
      if (debug)
        syslog(LOG_DEBUG, "line %d: masklen = %d", line, masklen);
    } else {
      *c2 = 0;
      c2++;
      masklen = hostmasklen;
    }

    if (i = getaddrinfo(c, NULL, &req, &ai)) {
      syslog(LOG_DEBUG, "line %d: getaddrinfo %s: %s(%d)", line, c, nrl_gai_strerror(i), i);
      continue;
    }

    {
      struct addrinfo *ai2 = ai;

      i = 0;
      while(ai2) {
	if (compare_sockaddr((struct sockaddr *)&peer, ai2->ai_addr, masklen)) {
	  i++;
	  break;
	}
	ai2 = ai2->ai_next;
      }
    }
    freeaddrinfo(ai);

    if (!i)
      goto matched;
  };
  syslog(LOG_ERR, "no rule found for service %s", service);
  exit(1);

matched:
  while(isspace(*c2)) c2++;

  {
  int argc;
  char *argv[10];

  for (argc = 0, c = c2; argc < 9; c = NULL)
    if (!(argv[argc++] = strtok(c, " \t\n")))
      break;

  if (debug) {
    int i;

    for (i = 0; i < argc; i++)
      syslog(LOG_DEBUG, "argv[%d] = %s", i, argv[i]);
  };
  execv(argv[0], &(argv[0]));

  syslog(LOG_ERR, "exec %s: %s(%d)", argv[0], strerror(errno), errno);
  };
};
