/*-
 * Copyright (c) 2004 Free (Olivier Beyssac)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include "options.h"
#include "cmd.h"
#include "iptree.h"
#include "ipinfo.h"
#include "utils.h"
#include "netlist.h"
#include "client.h"


#define MSG_BL    (421)
#define MSG_NOBL  (200)
#define MSG_AUTHF (600)


#define exiterror(ident, svc, svl, buf) \
  do {                                                          \
    syslog(LOG_ERR, "invalid request from %s: %s", ident, buf); \
    restorebuf(svc, svl, buf);                                  \
    return 500;                                                 \
  } while(0)


extern struct options opt;


/* Send notifications */
static void send_notifies(const char *ip)
{
  int i;
  pid_t pid;
  struct sigaction sa;

  if (!opt.notify_hosts)
    return;

  if ((pid = fork()) == -1) {
    syslog(LOG_ERR, "fork() error: %s", strerror(errno));
    return;
  }

  if (pid != 0)
    return;

  /* Detach from previous sigactions */
  xsigaction(sa, SIGTERM, SIG_DFL);
  xsigaction(sa, SIGINT, SIG_DFL);
  xsigaction(sa, SIGUSR1, SIG_IGN);
  xsigaction(sa, SIGUSR2, SIG_IGN);
  xsigaction(sa, SIGCHLD, SIG_IGN);

  for (i = 0; opt.notify_hosts[i]; i += 2) {
    int sd, reply;
    char *host = opt.notify_hosts[i];
    char *port = opt.notify_hosts[i+1];
    
    opt.notifies++;

    if ((sd = client_connect(host, port)) == -1) {
      syslog(LOG_ERR, "error (connect) while notifying %s to %s:%s",
	     ip, host, port);
    } else {
      if ((reply = client_send_cmdi(sd, CMD_INSERT, ip)) != 200
	  && reply != 421)
	syslog(LOG_ERR, "error (reply=%d) while notifying %s to %s:%s",
	       reply, ip, host, port);
      else
	syslog(LOG_INFO, "notified %s:%s about %s", host, port, ip);
      
      close(sd);
    }
  }

  exit(EXIT_SUCCESS);
}


/* Do the real stuff after command has been interpreted by read_cmd */
static int cmd_commit(const iptree ipt, const int cmd, const char *val,
		      const char *ident)
{
  struct in_addr inp;
  unsigned long ip;
  time_t t;
  int options = 0;
  int bl = 0;
  int notify = 0;
  int code = 200;
  int wl = 0;
  int count = 1;
  
  t = time(NULL);

  if (!inet_aton(val, &inp)) {
    syslog(LOG_ERR, "invalid IP address submitted by %s: %s", ident, val);
    return 500;
  }
  
  ip = ntohl(inp.s_addr);

  if (iptree_get_bl(ipt, ip, t)) {
    bl = 1;
    code = MSG_BL;
  }

  /* Check against whitelist */
  wl = (netlist_getmode(opt.whitelist, ip) == 1);
  
  if (cmd == CMD_DECR) {
    opt.decrqueries++;
    if (wl) {
      syslog(LOG_INFO, "%s (wl) decremented by %s", val, ident);
      return code;
    }
    count = -1;
  } else if (cmd == CMD_INSERT) {
    opt.insertqueries++;
    options |= IPINFO_OPT_FORCED;
    if (wl) {
      syslog(LOG_INFO, "%s (wl) inserted by %s", val, ident);
      return code;
      
    }
  } else if (cmd == CMD_SUBMIT) {
    opt.submissions++;
    if (wl) {
      syslog(LOG_INFO, "%s (wl) submitted by %s", val, ident);
      return code;
    }
  }
  
  switch (cmd) {
  case CMD_DECR:
    /* Decrement IP count */
  case CMD_INSERT:
    /* IP insertion */
  case CMD_SUBMIT:
    /* IP submission */
    if (iptree_add(ipt, ip, t, t, count, options)) {
      if (cmd == CMD_SUBMIT) {
	syslog(LOG_INFO, "%s submitted by %s", val, ident);
	if (iptree_get_bl(ipt, ip, t)) {
	  notify = 1;
	}
      } else if (cmd == CMD_DECR) {
	syslog(LOG_INFO, "%s decremented by %s", val, ident);
      } else if (cmd == CMD_INSERT && !bl) {
	syslog(LOG_INFO, "%s inserted by %s", val, ident);
	notify = 1;
      }
      /* If the IP has just been put in the blacklist, send notifies */
      if (!bl && notify) {
	send_notifies(val);
	code = MSG_BL;
      }
    } else
      syslog(LOG_ERR, "error in iptree_add IP %s", val);
    
    break;

  case CMD_QUERY:
    /* Check if an IP is blacklisted or not */
    opt.blqueries++;

    if (bl) {
      opt.positive_blqueries++;
      return MSG_BL;
    } else
      return MSG_NOBL;
    break;
  }

  return code;
}


/* Read the command pointed to by cmd and update iptree
   Return the code to show to client */
extern int read_cmd(char *buf, const ssize_t len, const char *ident,
		    const iptree ipt, const int mode)
{
  char *p;
  char svc, svctmp;
  ssize_t svl;
  int cmd = CMD_NO;
  int code;
  
  if (buf[len-1] == '\n' && buf[len-2] == '\r')
    savebufpos(svc, svl, buf, len-2);
  else {
    savebufpos(svc, svl, buf, len);
    exiterror(ident, svc, svl, buf);
  }

  if ((p = strchr(buf, '=')) == NULL)
    exiterror(ident, svc, svl, buf);

  svctmp = *p;
  *p = '\0';
  if (strcmp(buf, "ip") == 0)
    cmd = CMD_SUBMIT;
  else if (strcmp(buf, "ip?") == 0)
    cmd = CMD_QUERY;
  else if (strcmp(buf, "ipbl") == 0)
    cmd = CMD_INSERT;
  else if (strcmp(buf, "ipdecr") == 0)
    cmd = CMD_DECR;
  else {
    opt.bad_requests++;
  }

  *p++ = svctmp;

  /* Check client request against ACL */
  if ((cmd == CMD_SUBMIT && !(mode & ACL_M_SUBMIT))
      || (cmd == CMD_QUERY && !(mode & ACL_M_QUERY))
      || (cmd == CMD_INSERT && !(mode & ACL_M_INSERT))
      || (cmd == CMD_DECR && !(mode & ACL_M_DECR))) {
    syslog(LOG_INFO, "denied request from %s", ident);
    opt.denied_requests++;
    restorebuf(svc, svl, buf);
    return MSG_AUTHF;
  }
    
  if (cmd == CMD_NO || (code = cmd_commit(ipt, cmd, p, ident)) == 0)
    exiterror(ident, svc, svl, buf);

  restorebuf(svc, svl, buf);

  return code;
}
