/*
 * Copyright (c) 1997-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/* nntp-gw.c */

/* Status reporting and minor workarounds by ArkanoiD, 2001    */

/* Copyright 1997-2000 by Eberhard Mattes <em-gw@windhager.de>
   Copyright 1999-2000 by Giles Heron <giles@clear.net.nz>
   Donated to the public domain.  No warranty.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include "firewall.h"
#include "libemfw.h"
#include "emio.h"
#include "firewall2.h"
#include "ci_milter.h"

/* Reference: RFC 977 */

#define SERVER_PORT	119  /* Default port for NNTP */

#define LF	10
#define CR	13

#define WHITE(c) ((c) == 32 || (c) == 9)

/* Flags in command table. */

#define CMD_SIZE	1	/* Log list size (with "log list-size") */
#define CMD_NOAUTH	2	/* No authorization required */

#if defined(linux) || defined (SOLARIS)
extern void initsetproctitle(int,char**);
#endif

#define QTEMPLATE	"/tmp/nntp-gw.XXXXXXXXXX"

#define MAXLINE 1024

extern int milter_inspect_mail(char*,char*,char*,char*,char*,char*,char***);

static Cfg *confp;
static const char *user;
static char input[512];
static char cmd[32];
static size_t input_length;
static size_t cmd_length;
static int server_fd;
static int server_contacted;
static EMI_FILE *c_in, *s_in;
static EMO_FILE *c_out, *s_out;
static char group_name[512];
static unsigned long group_bytes;
static unsigned long group_articles;
static int log_auth;
static int log_command;
static int log_response;
static int log_list_size;
static int log_post;
static int log_article_summary;
static int cdscp = 0;
static int sdscp = 0;
static int transparent = 0;
static int extendperm = 0;
static char **validests = (char **)0;
static char **validusers = (char **)0;
static char separator[2] = "@";

static	fwparm	options[] = {
	{	FWPARM_BOOL,    "-tranparent",          (char*) &transparent },
	{	FWPARM_BOOL,    "-extnd",               (char*) &extendperm  },
	{	FWPARM_CHAR,    "-separator",           separator  	     },
	{	FWPARM_LIST,    "-dest",         	(char*) &validests   },
	{	FWPARM_LIST,    "-user",         	(char*) &validusers  },
	{	FWPARM_LIST,    "-plug-to",         	proxy_stats.dst	     },
	{	FWPARM_PORT,    "-port",             (char*)&proxy_stats.port },
	{	FWPARM_DSCP,    "-client-dscp",         (char*) &cdscp       },
	{	FWPARM_DSCP,    "-server-dscp",         (char*) &sdscp       },
	{	0,		0,			0		     }};

/* firewall.h lacks some declarations. */

int conn_server (const char *, int, int, char *);
int str_to_port (char *);
int do_daemon (int);
int peername (int, char *, char *, int);
int hostmatch (const char *, const char *);

/* Prototypes. */

static void quit (int) ATTR_NORETURN;
static void read_error (EMI_FILE *) ATTR_NORETURN;
static void write_error (EMO_FILE *) ATTR_NORETURN;

/* Keep byte count in sync */
void em_update_status()
{
        proxy_stats.inbytes = emi_amount(s_in,0);
        proxy_stats.outbytes = emo_amount(s_out,0);
        proxy_update_status();
}

static void log_group (void)
{
  if (log_article_summary && group_name[0] != 0 && group_articles != 0)
    syslog (LLEV, "group %.256s: articles=%lu bytes=%lu",
            group_name, group_articles, group_bytes);
}

static void quit (int rc)
{
  log_group ();
  em_update_status();
  proxy_exit();
}

static void
puts_qf(fd,line)
int     fd;
char    *line;
{
        if (sosay(fd,line)) {
                close(fd);
                syslog(LLEV,"fwtksyserr: error writing quarantine file, %s", strerror(errno));
                quit(1);
        }
}

/*
 *      Parse rfc2822 address simple way and get address itself.
 *      NOTE: this function works for reference information only and
 *      may provide incaccurate data, keeps inline comments, ingores
 *      quoting etc etc. It is not important for nntp gateway but
 *      use of this function should be restricted for such purpose only.
 *      That's why we are static.
 */

static char*
parse_address(address)
char*   address;
{
        char            *p,*p1;
        static char     mybuf[MAXLINE];
        
        memset(mybuf,0,sizeof(mybuf));

        if (!address || !(*address))
                return(NULL);

        strncpy(mybuf,address,sizeof(mybuf)-1);
        if (!(p=strchr(mybuf,'<'))) 
                return(mybuf);
        if (!(*(++p)) || !(p1=strrchr(p,'>')))
                return(NULL);
        *(p1) = '\0';
        return(p);
}

/* Return "client" or "server", depending on whether the file F is
   used for the client or server, respectively. */

static const char *cs (void *f)
{
  return (f == c_in || f == c_out) ? "client" : "server";
}

static void read_error (EMI_FILE *f)
{
  syslog (LLEV, "%s: read(): %s", cs (f), strerror(errno));
  quit (1);
}

static void write_error (EMO_FILE *f)
{
  syslog (LLEV, "%s: write(): %s", cs (f), strerror(errno));
  quit (1);
}

static size_t read_line (EMI_FILE *f, char *buf, size_t size)
{
  size_t i = 0;
  int c;

  while ((c = emi_getc (f)) != EMI_EOF && c != LF)
    {
      if (i + 1 >= size)
        {
          syslog (LLEV, "securityalert: %s: line too long", cs (f));
          quit (1);
        }
      if (c == 0)
        {
          syslog (LLEV, "securityalert: %s: line contains invalid character", cs (f));
          quit (1);
        }
      buf[i++] = (char)c;
    }
  if (c == LF)
    {
      if (i != 0 && buf[i-1] == CR)
        --i;
      buf[i] = 0;
      return i;
    }
  if (emi_error (f))
    read_error (f);
  else
    {
      syslog (LLEV, "%s: unterminated line", cs (f));
      quit (1);
    }
  em_update_status();
  return 0; /* unreached */
}

static void command (void)
{
  if (log_command)
    syslog (LLEV, "command: %.512s", input);
  emo_write (s_out, input, input_length);
  emo_write (s_out, "\r\n", 2);
  if (emo_flush (s_out) != 0)
    write_error (s_out);
  em_update_status();
}

static void respond (const char *s)
{
  emo_puts (c_out, s);
  emo_write (c_out, "\r\n", 2);
  if (emo_flush (c_out) != 0)
    write_error (c_out);
}

static int has_body (int code)
{
  return (code == 100 || code == 215 || code == 220 || code == 221
          || code == 222 || code == 224 || code == 230 || code == 231);
}

static int get_response (void)
{
  int code, i;

  input_length = read_line (s_in, input, sizeof (input));
  if (log_response)
    syslog (LLEV, "response: %.512s", input);
  code = 0;
  for (i = 0; i < 3; ++i)
    {
      if (input[i] >= '0' && input[i] <= '9')
        code = code * 10 + input[i] - '0';
      else
        {
          code = -1;
          break;
        }
    }
  return code;
}

static void put_response (void)
{
  emo_write (c_out, input, input_length);
  emo_write (c_out, "\r\n", 2);
  if (emo_flush (c_out) != 0)
    write_error (c_out);
  em_update_status();
}

static int copy_response (void)
{
  int code = get_response ();
  put_response ();
  return code;
}

static void reject_article(EMO_FILE *dst)
{
      emo_puts (dst, "Path: localhost\r\n");
      emo_puts (dst, "From: \"Firewall content inspection service\" <quarantine@localhost>\r\n");
      emo_puts (dst, "Subject: Article was rejected by content inspection service\r\n");
      emo_puts (dst, "Distribution: local\r\n");
      emo_puts (dst, "Lines: 1\r\n");
      emo_puts (dst, "\r\n");
      emo_puts (dst, "The article does not meet filtering criteria and cannot be displayed\r\n");
}

static unsigned long copy_body (EMO_FILE *dst, EMI_FILE *src)
{
  int c;
  enum
  {
    STATE_CR,                   /* CR */
    STATE_CR_LF,                /* CR LF */
    STATE_DOT,                  /* CR LF . */
    STATE_DOT_CR,               /* CR LF . CR */
    STATE_DOT_CR_LF,            /* CR LF . CR LF */
    STATE_OTHER                 /* everything else */
  } state;
  unsigned long n0;

  n0 = emi_amount (src, 0);
  state = STATE_CR_LF;
  for (;;)
    {
      /* We're in state STATE_CR or STATE_CR_LF now. */

      do
        {
          c = emi_getc (src);
          if (c == EMI_EOF)
            break;
          emo_putc (dst, c);
          switch (state)
            {
            case STATE_CR:
              if (c == LF)
                state = STATE_CR_LF;
              else if (c == CR)
                state = STATE_CR;
              else
                state = STATE_OTHER;
              break;
            case STATE_CR_LF:
              if (c == '.')
                state = STATE_DOT;
              else if (c == LF)
                state = STATE_CR_LF;
              else if (c == CR)
                state = STATE_CR;
              else
                state = STATE_OTHER;
              break;
            case STATE_DOT:
              if (c == LF)
                state = STATE_DOT_CR_LF;
              else if (c == CR)
                state = STATE_DOT_CR;
              else
                state = STATE_OTHER;
              break;
            case STATE_DOT_CR:
              if (c == LF)
                state = STATE_DOT_CR_LF;
              else if (c == CR)
                state = STATE_CR;
              else
                state = STATE_OTHER;
              break;
            default:
              syslog (LLEV, "fwtksyserr: internal error");
              quit (1);
            }
        } while (state != STATE_DOT_CR_LF && state != STATE_OTHER);

      if (c == EMI_EOF || state == STATE_DOT_CR_LF)
        break;

      do
        {
          c = emi_getc (src);
          if (c == EMI_EOF)
            break;
          emo_putc (dst, c);
        } while (c != LF && c != CR);
      if (c == LF)
        state = STATE_CR_LF;
      else
        state = STATE_CR;
    }

  if (c == EMI_EOF)
    {
      syslog (LLEV, "%s: unterminated line", cs (src));
      quit (1);
    }
  if (emo_flush (dst) != 0)
    write_error (dst);
  em_update_status();
  return emi_amount (src, 0) - n0;
}

static long copy_inspect_body (EMO_FILE *dst, EMI_FILE *src)
{
  unsigned long n0;
  char buf[1024];
  char *qf;
  char **rcpt;
  int  tfd;
  FILE *tf;
  Cfg* cf;
  char *from = NULL;

  n0 = emi_amount (src, 0);
  qf = xstrdup(QTEMPLATE);
  if ((tfd = mkstemp(qf)) == -1) {
    syslog(LLEV,"fwtksyserr: cannot create temporary file");
    quit(1);
  }

/*
 * Parse header
 */

  while(1) {
    read_line (src,buf,sizeof(buf));
    if (!strcmp(buf,"."))
      goto skipbody;
    if (!(*buf))
      break;
    if (!from) {
      if (!strncmp(buf,"From: ",6)) {
        if (!(from = parse_address(buf+6)))
          syslog(LLEV,"Malformed From: %s", buf+6);
       }
    }
    puts_qf(tfd,buf);
  }

/*
 * Continue with body
 */

  while(1) {
    read_line (src,buf,sizeof(buf));
    if (!strcmp(buf,"."))
	break;
    puts_qf(tfd,buf);
  }

skipbody:

  close(tfd);

  if (!from)
    from = "unknown";

  snprintf(buf,sizeof(buf)-1,"nntp@%.80s",proxy_stats.rladdr);
  rcpt = xmalloc(sizeof(char*)*2);
  rcpt[0] = xstrdup(buf);
  rcpt[1] = NULL;

  cf=cfg_get("milter", confp);
  while (cf != (Cfg *)0) {
    int mfstate;
    if((cf->argc < 1) || (cf->argv[0][0] == '-')) {
      syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
    }
    if (((mfstate = milter_inspect_mail("nntp-gw",
					proxy_stats.rladdr,proxy_stats.riaddr,
					cf->argv[0],qf,from,&rcpt)) & MS_ER)) {
      syslog(LLEV,"fwtksyserr: milter %s failed",cf->argv[0]);
      quit(1);
    }
    if (mfstate & MS_RJ) {
      if (src == s_in) {
        reject_article(dst);
        goto done;
      } else 
        goto postcleanup;
    }
    cf=cfg_get("milter",(Cfg *)0);
  }

  if (src == c_in) 
    cf=cfg_get("post-milter", confp);
  else if (src == s_in)
    cf=cfg_get("read-milter", confp);
  else {
    syslog(LLEV,"fwtksyserr: internal logic error: milter called for operation which is not post nor read");
    quit(1);
  }
  while (cf != (Cfg *)0) {
    int mfstate;
    if((cf->argc < 1) || (cf->argv[0][0] == '-')) {
      syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
    }
    if (((mfstate = milter_inspect_mail("nntp-gw",
					proxy_stats.rladdr,proxy_stats.riaddr,
					cf->argv[0],qf,from,&rcpt)) & MS_ER)) {
      syslog(LLEV,"fwtksyserr: milter %s failed",cf->argv[0]);
      quit(1);
    }
    if (mfstate & MS_RJ) {
      if (src == s_in) {
        reject_article(dst);
        goto done;
      } else 
        goto postcleanup;
    }
    cf=cfg_get("milter",(Cfg *)0);
    if (src == c_in) 
      cf=cfg_get("post-milter", (Cfg *)0);
    else 
      cf=cfg_get("read-milter", (Cfg *)0);
  }
  
  if (!(tf = fopen(qf,"r"))) {
    syslog(LLEV,"fwtksyserr: cannot open quarantine file");
    quit(1);
  }
  while(fgets(buf,sizeof(buf),tf)) {
    emo_puts (dst, buf);
  }
  fclose(tf);
  unlink(qf);
done:
  emo_write (dst, ".\r\n", 3);
  if (emo_flush (dst) != 0)
    write_error (dst);
  em_update_status();
  free(rcpt[0]);
  free(rcpt);
  free(qf);
  return emi_amount (src, 0) - n0;

postcleanup:

/*
 * Posting failed, release data structures
 */

  unlink(qf);
  free(rcpt[0]);
  free(rcpt);
  free(qf);
  return -1;
}
  


static int check_group (const char *group, const char **msg, int *delay)
{
  Cfg *cf = cfg_get ("newsgroups", confp);
  int i;
  size_t len;

  len = strlen (group);
  while (cf != NULL)
    {
      for (i = 0; i < cf->argc && cf->argv[i][0] != '-'; ++i)
	{
	  if (lower_match (cf->argv[i], strlen (cf->argv[i]), group, len))
            {
	      if (cf->flags & PERM_DENY)
		goto deny;
	      return 0;
	    }
	}
      cf = cfg_get ("newsgroups", (Cfg *)0);
    }
  /* permit by default */
  return 0;

deny:
  syslog (LLEV, "deny host=%.100s/%.64s newsgroup=%.512s", 
          proxy_stats.rladdr, proxy_stats.riaddr, group);
  *msg = "411 access denied";
  *delay = 0;
  while (++i < cf->argc)
    {
      if (i + 1 < cf->argc && strcmp (cf->argv[i], "-message") == 0)
	*msg = cf->argv[++i];
      else if (i + 1 < cf->argc && strcmp (cf->argv[i], "-delay") == 0)
	*delay = atoi (cf->argv[++i]);
      else if (strcmp (cf->argv[i], "-quit") == 0)
	*delay = -1;
    }
  return -1;
}


/* Get banner from remote server and pass back. */

static void display_banner (void)
{
  int code;

  code = copy_response ();
  if (code != 200 && code != 201)
    quit (1);
}

/* Get banner from remote server and discard. */

static void discard_banner (void)
{
  char banner[512];

  read_line (s_in, banner, sizeof (banner));
  if (log_response)
    syslog (LLEV, "discarded banner: %.512s", input);
}

/* Open a connection to the server. */

static void open_server (void)
{
  proxy_update_operation("CONNECTING");
  if ((server_fd = conn_server (proxy_stats.dst, proxy_stats.port, 0, (char *)0)) < 0)
    {
      syslog (LLEV, "cannot connect to server %.512s/%d: %s",
	      proxy_stats.dst, proxy_stats.port, strerror(errno));
      respond ("400 cannot connect to server");
      exit (1);
    }

  if (sdscp)
    proxy_set_dscp(server_fd,sdscp);

  s_in = emi_fdopen (server_fd, 4096);
  s_out = emo_fdopen (server_fd, 4096);
  if (s_in == NULL || s_out == NULL)
    {
      syslog (LLEV, "fdopen() failed: %s", strerror(errno));
      exit (1);
    }
  emi_timeout (s_in, PROXY_TIMEOUT);
  emo_timeout (s_out, PROXY_TIMEOUT);

  /* Scary side-effect time! */
  server_contacted = 1;
}

static int do_default (int flags)
{
  unsigned long n;

  command ();
  if (has_body (copy_response ()))
    {
      n = copy_body (c_out, s_in);
      if (log_list_size && (flags & CMD_SIZE))
        syslog (LLEV, "%s: bytes=%lu", cmd, n);
    }
  return 0;
}

/* AUTHINFO USER - called from do_auth. */

static int do_auth_user (int flags)
{
  static char server_buf[512];
  static char user_buf[512];
  int code;
  size_t sep, start, i, n;

  /* Check if connected to server already. */
  if (server_contacted)
    {
      do_default (flags);
      return 0;
    }

  /* Move onto the start of the user name. */
  i = cmd_length;
  while (input[i] != 0 && WHITE (input[i]))
    ++i;

  start = i;
  sep = i - 1;

  while (input[i] != 0 && !WHITE (input[i]))
    {
      if (input[i] == *separator)
	sep = i;
      ++i;
    }

  /* Get the server name. */
  n = i - sep - 1;
  if (n >= sizeof (server_buf))
    {
      respond ("501 server name too long");
      return 0;
    }
  memcpy (server_buf, input + sep + 1, n);
  server_buf[n] = 0;
  strncpy(proxy_stats.dst,server_buf,sizeof(proxy_stats.dst) - 1);
  proxy_update_status();

  /* Get the user name. */
  if (sep > start)
    {
      n = sep - start;
      if (n >= sizeof (user_buf))
	{
	  respond ("501 user name too long");
	  return 0;
	}
      memcpy (user_buf, input + start, n);
      user_buf[n] = 0;
      user = user_buf;
    }

  /* Check allowed to connect to server. */
  if (proxy_check_dest(validests,extendperm) || searchlist((char*) user, validusers))
    {
      /* Not allowed to connect to server. */
      /* Ask for more auth - will fail on USER (as no server length). */
      respond ("381 More authentication information required");
      return 0;
    }

  /* Do the initial connection to the server. */
  open_server ();

  if (user)
    {
      /* Auth - discard the banner from the far end. */
      discard_banner ();

      /* Want to ignore everything from the @ sign. */
      input[sep] = 0;
      input_length = sep;

      /* Now pass the AUTH command through. */
      command ();

      /* And get the response */
      code = copy_response ();

      if (log_auth)
	syslog (LLEV, "AUTHINFO USER: code=%d", code);
    }
  else
    {
      /* No auth - pass the returned banner to the user. */
      display_banner ();
    }
  return 0;
}

/* AUTHINFO PASS - called from do_auth. */

static int do_auth_pass (int flags)
{
  int code;

  if (!server_contacted)	/* First check we're connected to the server */
    respond ("482 Authentication rejected");
  else if (user == NULL)	/* Check if need to pass this through */
    respond ("281 Authentication accepted");
  else
    {
      /* Pass the auth command through to the server. */
      command ();
      code = copy_response ();
      if (log_auth)
        syslog (LLEV, "AUTHINFO PASS: code=%d", code);
    }
  return 0;
}

/* Hash table of AUTH subcommands.  This include file is generated
   from auth.tab by maketable. */

#include "authinfo.h"

/* AUTHINFO */

static int do_auth (int flags)
{
  const char *s = input + cmd_length;
  const char *e;
  size_t len;
  const struct hash_entry *he;
  int (*handler)(int);

  while (*s != 0 && WHITE (*s))
    ++s;
  e = s;
  while (*e != 0 && !WHITE (*e))
    ++e;
  len = e - s;

  if (len == 0 || len >= sizeof (cmd))
    respond ("482 Authentication rejected");
  else
    {
      upper_copy (cmd, s, len);
      cmd[len] = 0;
      cmd_length = e - input;
      he = find_hash_entry2 (&authinfo_descr, cmd, len);
      if (he == NULL)
        respond ("482 Authentication rejected");
      else
        {
          handler = he->handler;
          ALWAYS_ASSERT (AUTHINFO_HANDLER (handler));
          handler (he->flags);
	}
    }
  return 0;
}

static void do_null (void)
{
  syslog (LLEV, "empty command");
}

static void do_unknown (void)
{
  syslog (LLEV, "unknown command: %.512s", input);
  respond ("500 command not recognized");
}

/* ARTICLE, BODY, and HEAD. */

/* XXX - we need special handling for BODY and HEAD */

static int do_article (int flags)
{
  command ();
  if (has_body (copy_response ()))
    {
      long n;
      if ((n = copy_inspect_body (c_out, s_in)) == -1L) 
	{
          syslog(LLEV,"fwtksyserr: internal logic error: invalid exit code on copy_inspect_body()");
          quit(1);
	}
      group_bytes += n;
      ++group_articles;
    }
  return 0;
}

/* GROUP */

static int do_group (int flags)
{
  const char *s = input + cmd_length;
  const char *e, *msg;
  size_t len;
  int delay;

  while (*s != 0 && WHITE (*s))
    ++s;
  e = s;
  while (*e != 0 && !WHITE (*e))
    ++e;
  len = e - s;
  if (*e != 0 || len >= sizeof (group_name))
    respond ("501 command syntax error");
  else
    {
      log_group ();
      memcpy (group_name, s, len);
      group_name[len] = 0;
      group_bytes = 0; group_articles = 0;
      if (check_group (group_name, &msg, &delay) != 0)
	{
	  respond (msg);
	  if (delay > 0)
	    sleep (delay);
	  else if (delay < 0)
	    quit (1);
	}
      else
        do_default (flags);
    }
  return 0;
}

/* Hash table of NNTP extensions (for LIST EXTENSIONS).  This include
   file is generated from ext.tab by maketable. */

#include "ext.h"

/* LIST EXTENSIONS */

static int do_list_extensions (int flags)
{
  const struct hash_entry *he;
  const char *s;
  size_t len;
  int code;

  command ();
  code = copy_response ();
  if (code == 202 || code == 215)
    {
      for (;;)
	{
	  input_length = read_line (s_in, input, sizeof (input));
	  if (strcmp (input, ".") == 0)
	    break;
	  s = input; len = input_length;
	  if (*s == ' ')
	    {
	      ++s; --len;
	    }
	  if (len >= sizeof (cmd))
	    he = NULL;
	  else
	    {
	      upper_copy (cmd, s, len);
	      cmd[len] = 0;
	      he = find_hash_entry2 (&ext_descr, cmd, len);
	    }
	  if (he == NULL)
	    syslog (LLEV, "unknown extension: %.512s", input);
	  else if (he->flags != 0)
	    {
	      emo_write (c_out, input, input_length);
	      emo_write (c_out, "\r\n", 2);
	    }
	}
      respond (".");
    }
  return 0;
}

/* Hash table of LIST subcommands.  This include file is generated
   from list.tab by maketable. */

#include "list.h"

/* LIST */

static int do_list (int flags)
{
  const char *s = input + cmd_length;
  const char *e;
  size_t len;
  const struct hash_entry *he;
  int (*handler)(int);

  while (*s != 0 && WHITE (*s))
    ++s;
  e = s;
  while (*e != 0 && !WHITE (*e))
    ++e;
  len = e - s;
  if (len == 0)
    do_default (flags);		/* LIST ACTIVE */
  else
    {
      if (len >= sizeof (cmd))
	do_unknown ();
      else
	{
	  upper_copy (cmd, s, len);
	  cmd[len] = 0; cmd_length = e - input;
	  he = find_hash_entry2 (&list_descr, cmd, len);
	  if (he == NULL)
	    do_unknown ();
	  else
	    {
	      handler = he->handler;
	      ALWAYS_ASSERT (LIST_HANDLER (handler));
	      handler (he->flags);
	    }
	}
    }
  return 0;
}

/* LISTGROUP */

static int do_listgroup (int flags)
{
  int code;
  unsigned long n;

  command ();
  code = copy_response ();
  if (code == 211 || has_body (code))
    {
      n = copy_body (c_out, s_in);
      if (log_list_size)
	syslog (LLEV, "LISTGROUP: bytes=%lu", n);
    }
  return 0;
}

/* POST and IHAVE */

static int do_post (int flags)
{
  long n;
  int code;
  int is_ihave;

  is_ihave = !strcasecmp(input,"ihave");
  command ();
  code = copy_response ();
  if (code == 335 || code == 340)
    {
      if ((n = copy_inspect_body (s_out, c_in)) == -1L) {
	if (is_ihave)
	  strlcpy(input,"437 article rejected",sizeof(input));
	else
	  strlcpy(input,"441 posting failed",sizeof(input));
	input_length = strlen(input);
	put_response();
        return 0;
      }
      code = copy_response ();
      if (log_post)
	{
	  if (code == 235 || code == 240)
	    syslog (LLEV, "%s: bytes=%lu", cmd, n);
	  else
	    syslog (LLEV, "%s: error=%d bytes=%lu", cmd, code, n);
	}
    }
  else if (log_post)
    syslog (LLEV, "%s: error=%d", cmd, code);
  return 0;
}

/* QUIT */

static int do_quit (int flags)
{
  if (server_contacted)
    do_default (flags);
  quit (0);
  return 0;
}

/* Specific configuration for nntp-gw. */

static void config_nntp_gw (Cfg *confp)
{
  Cfg *cf;
  int i;

  if ((cf = cfg_get ("server", confp)) != NULL)
    {
      if (cf->argc != 1 && cf->argc != 2)
	{
	  syslog (LLEV,
		  "fwtkcfgerr: server must have one or two parameters, "
		  "line %d", cf->ln);
	  exit (1);
	}
      strlcpy(proxy_stats.dst,cf->argv[0],sizeof(proxy_stats.dst) - 1);
      proxy_update_status();
      if (cf->argc == 2)
	proxy_stats.port = str_to_port (cf->argv[1]);
    }

  if ((cf = cfg_get ("log", confp)) != NULL)
    for (i = 0; i < cf->argc; ++i)
      {
	if (strcmp (cf->argv[i], "command") == 0)
	  log_command = 1;
	else if (strcmp (cf->argv[i], "response") == 0)
	  log_response = 1;
	else if (strcmp (cf->argv[i], "list-size") == 0)
	  log_list_size = 1;
	else if (strcmp (cf->argv[i], "article-summary") == 0)
	  log_article_summary = 1;
	else if (strcmp (cf->argv[i], "post") == 0)
	  log_post = 1;
        else if (strcmp (cf->argv[i], "auth") == 0)
          log_auth = 1;
	else
	  {
	    syslog (LLEV, "fwtkcfgerr: invalid parameter for log, line %d",
		    cf->ln);
	    exit (1);
	  }
      }
}

/* Catch SIGPIPE.  SIGPIPE is generated when writing to a closed pipe
   or socket. */

static void catch_sigpipe (int signo)
{
  syslog (LLEV, "attempt to write to closed connection");
  quit (1);
}

/* Hash table of NNTP commands.  This include file is generated from
   command.tab by maketable. */

#include "command.h"

/* Read commands from the client and pass them to the server. */

static void cmd_loop (void)
{
  size_t len;
  const struct hash_entry *he;
  int (*handler)(int);

  for (;;)
    {
      proxy_update_operation("IDLE");
      input_length = read_line (c_in, input, sizeof (input));
      len = 0;
      while (input[len] != 0 && !WHITE (input[len]))
	++len;
      if (len >= sizeof (cmd))
	do_unknown ();
      else if (len == 0)
        do_null ();
      else
	{
	  upper_copy (cmd, input, len);
	  cmd[len] = 0; cmd_length = len;
	  proxy_update_operation(cmd);
	  he = find_hash_entry2 (&command_descr, cmd, len);
	  if (he == NULL)
	    do_unknown ();
	  else if (!server_contacted && !(he->flags & CMD_NOAUTH))
	    respond ("450 Authorization required for this command");
	  else
	    {
	      handler = he->handler;
	      ALWAYS_ASSERT (COMMAND_HANDLER (handler));
	      handler (he->flags);
	    }
	}
    }
}

/* This is where nntp-gw starts. */

int main (int argc, char *argv[])
{
  char *config_name = "nntp-gw";
  Cfg* cf;

  if (argc >= 2 && strcmp (argv[1], "-check") == 0)
    {
      check_snprintf (0);
      exit (0);
    }

  openlog ("nntp-gw", LOG_PID|LOG_NDELAY, LFAC);

#if defined(linux) || defined (SOLARIS)
  initsetproctitle(argc,argv);
#endif

  proxy_stats.port = SERVER_PORT; 

  if (argc >= 3 && strcmp (argv[1], "-daemon") == 0)
    {
      int port = str_to_port (argv[2]);
      argc -= 2; argv += 2;
      syslog (LLEV, "Starting daemon mode on port %d", port);
      do_daemon (port);
    }

  if (argc >= 2 && argv[1][0] != '-')
    {
      config_name = argv[1];
      argc -= 1; argv += 1;
    }

  if (argc != 1)
    {
      syslog (LLEV, "Bad command line");
      exit (1);
    }

  if ((confp = cfg_read (config_name)) == (Cfg *)-1)
    exit (1);
  config_groupid (confp, 1);
  config_userid (confp, 1);
  config_nntp_gw (confp);

  time(&proxy_stats.start_time);
  if (peername (0, proxy_stats.rladdr, proxy_stats.riaddr, sizeof (proxy_stats.riaddr)) != 0)
    {
      syslog (LLEV, "fwtksyserr: cannot get peer name: %s", strerror(errno));
      exit (1);
    }
  proxy_update_status();

  if ((cf = config_hosts (confp, proxy_stats.rladdr, proxy_stats.riaddr)) == NULL)
    exit (1);
  else
    proxy_parse_options(cf,options);

  if (cdscp)
    proxy_set_dscp(0,cdscp);

  if (transparent) 
    proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);

  c_in = emi_fdopen (0, 4096);
  c_out = emo_fdopen (0, 4096);
  if (c_in == NULL || c_out == NULL)
    {
      syslog (LLEV, "fdopen() failed: %s", strerror(errno));
      exit (1);
    }
  emi_timeout (c_in, PROXY_TIMEOUT);
  emo_timeout (c_out, PROXY_TIMEOUT);

  /* Install signal handler. */

  signal (SIGPIPE, catch_sigpipe);

  if (*proxy_stats.dst)
    {
      /* Config has "server" parameter - so we want to connect now. */
      open_server ();
      display_banner ();
    }
  else
    {
      /* Will connect when the user sends AUTHINFO. */
      respond ("200 Hello you can post or not");
    }

  /* Now sit there processing commands. */
  cmd_loop ();
  return 0;
}
