/*  $Id: rc.c,v 1.83.2.1 2003/05/11 19:40:28 rra Exp $
**
**  Routines for the remote connect channel.  Create an Internet stream
**  socket that processes connect to.  If the incoming site is not one of
**  our feeds, then we optionally pass the connection off to the standard
**  NNTP daemon.
*/
#include "config.h"
#include "clibrary.h"
#include "portable/socket.h"
#include <errno.h>
#include <netdb.h>

#include "inn/innconf.h"
#include "innd.h"

#define TEST_CONFIG(a, b) \
    { \
	b = ((peer_params.Keysetbit & (1 << a)) != 0) ? true : false; \
    }

#define SET_CONFIG(a) \
    { \
	peer_params.Keysetbit |= (1 << a); \
    }

/*
**  A remote host has an address and a password.
*/
typedef struct _REMOTEHOST {
    char	*Label;         /* Peer label */
    char	*Name;          /* Hostname */
    struct sockaddr_storage Address;     /* List of ip adresses */
    char	*Password;      /* Optional password */
    char 	*Identd;	/* Optional identd */
    bool	Streaming;      /* Streaming allowed ? */
    bool	Skip;	        /* Skip this peer ? */
    bool	NoResendId;	/* Don't send RESEND responses ? */
    bool	Nolist;		/* no list command allowed */
    int		MaxCnx;		/* Max connections (per peer) */
    char	**Patterns;	/* List of groups allowed */
    char	*Pattern;       /* List of groups allowed (string) */
    char        *Email;         /* Email(s) of contact */
    char	*Comment;	/* Commentary [max size = MAXBUFF] */
    int		HoldTime;	/* Hold time before disconnect over MaxCnx */
    int		Keysetbit;	/* Bit to check duplicated key */
} REMOTEHOST;

typedef struct _REMOTEHOST_DATA {
    int         key;            /* Key (as defined in the _Keywords enum) */
    int         type;           /* Type of the value (see _Type enum) */
    char        *value;         /* Value */
} REMOTEHOST_DATA;

typedef struct _REMOTETABLE {
    struct sockaddr_storage Address;
    time_t         Expires;
} REMOTETABLE;

static char		*RCslaveflag;
static char		*RCnnrpd = NULL;
static char		*RCnntpd = NULL;
static CHANNEL		**RCchan;
static int		chanlimit;
static REMOTEHOST_DATA	*RCpeerlistfile;
static REMOTEHOST	*RCpeerlist;
static int		RCnpeerlist;
static char		RCbuff[BIG_BUFFER];

#define PEER	        "peer"
#define GROUP	        "group"
#define HOSTNAME        "hostname:"
#define STREAMING       "streaming:"
#define MAX_CONN        "max-connections:"
#define PASSWORD        "password:"
#define IDENTD	        "identd:"
#define PATTERNS        "patterns:"
#define EMAIL	        "email:"
#define COMMENT	        "comment:"
#define SKIP		"skip:"
#define NORESENDID	"noresendid:"
#define HOLD_TIME	"hold-time:"
#define NOLIST		"nolist:"

typedef enum {K_END, K_BEGIN_PEER, K_BEGIN_GROUP, K_END_PEER, K_END_GROUP,
	      K_STREAM, K_HOSTNAME, K_MAX_CONN, K_PASSWORD, K_IDENTD,
	      K_EMAIL, K_PATTERNS, K_COMMENT, K_SKIP, K_NORESENDID,
	      K_HOLD_TIME, K_NOLIST
	     } _Keywords;

typedef enum {T_STRING, T_BOOLEAN, T_INTEGER} _Types;

#define GROUP_NAME	"%s can't get group name in %s line %d"
#define PEER_IN_PEER	"%s peer can't contain peer in %s line %d"
#define PEER_NAME	"%s can't get peer name in %s line %d"
#define LEFT_BRACE	"%s '{' expected in %s line %d"
#define RIGHT_BRACE	"%s '}' unexpected line %d in %s"
#define INCOMPLETE_PEER "%s incomplete peer (%s) in %s line %d"
#define INCOMPLETE_GROUP "%s incomplete group (%s) in %s line %d"
#define MUST_BE_BOOL    "%s Must be 'true' or 'false' in %s line %d"
#define MUST_BE_INT	"%s Must be an integer value in %s line %d"
#define HOST_NEEDED     "%s 'hostname' needed in %s line %d"
#define DUPLICATE_KEY   "%s duplicate key in %s line %d"

/*
** Stuff needed for limiting incoming connects.
*/
static char		RCterm[] = "\r\n";
static REMOTETABLE	remotetable[REMOTETABLESIZE];
static int		remotecount;
static int		remotefirst;

/*
 * Check that the client has the right identd. Return true if is the
 * case, false, if not.
 */
static bool
GoodIdent(int fd, char *identd)
{
#define PORT_IDENTD 113
    char IDENTuser[80];
    struct sockaddr_storage ss_local;
    struct sockaddr_storage ss_distant;
    struct sockaddr *s_local = (struct sockaddr *)&ss_local;
    struct sockaddr *s_distant = (struct sockaddr *)&ss_distant;
    int ident_fd;
    socklen_t len;
    int port1,port2;
    ssize_t lu;
    char buf[80], *buf2;

    if(identd[0] == '\0') {
         return true;
    }
    
    len = sizeof( ss_local );
    if ((getsockname(fd,s_local,&len)) < 0) {
	syslog(L_ERROR, "can't do getsockname for identd");
	return false;
    }
    len = sizeof( ss_distant );
    if ((getpeername(fd,s_distant,&len)) < 0) {
	syslog(L_ERROR, "can't do getsockname for identd");
	return false;
    }
#ifdef HAVE_INET6
    if( s_local->sa_family == AF_INET6 )
    {
	struct sockaddr_in6 *s_l6 = (struct sockaddr_in6 *)s_local;
	struct sockaddr_in6 *s_d6 = (struct sockaddr_in6 *)s_distant;

	port1=ntohs(s_l6->sin6_port);
	port2=ntohs(s_d6->sin6_port);
	s_l6->sin6_port = 0;
	s_d6->sin6_port = htons( PORT_IDENTD );
	ident_fd=socket(PF_INET6, SOCK_STREAM, 0);
    } else
#endif
    if( s_local->sa_family == AF_INET )
    {
	struct sockaddr_in *s_l = (struct sockaddr_in *)s_local;
	struct sockaddr_in *s_d = (struct sockaddr_in *)s_distant;

	port1=ntohs(s_l->sin_port);
	port2=ntohs(s_d->sin_port);
	s_l->sin_port = 0;
	s_d->sin_port = htons( PORT_IDENTD );
	ident_fd=socket(PF_INET, SOCK_STREAM, 0);
    } else
    {
	syslog(L_ERROR, "Bad address family: %d\n", s_local->sa_family );
	return false;
    }
    if (ident_fd < 0) {
	syslog(L_ERROR, "can't open socket for identd (%m)");
	return false;
    }
    if (bind(ident_fd,s_local,SA_LEN(s_local)) < 0) {
	syslog(L_ERROR, "can't bind socket for identd (%m)");
	return false;
    }
    if (connect(ident_fd,s_distant,SA_LEN(s_distant)) < 0) {
	syslog(L_ERROR, "can't connect to identd (%m)");
	return false;
    }

    snprintf(buf,sizeof(buf),"%d,%d\r\n",port2, port1);
    write(ident_fd,buf, strlen(buf));
    memset( buf, 0, 80 );
    lu=read(ident_fd, buf, 79); /* pas encore parfait ("not yet perfect"?) */
    if (lu<0)
    {
	syslog(L_ERROR, "error reading from ident server: %m" );
	close( ident_fd );
	return false;
    }
    buf[lu]='\0';
    if ((lu>0) && (strstr(buf,"ERROR")==NULL)
		    && ((buf2=strrchr(buf,':'))!=NULL)) 
    {
	buf2++;
	while(*buf2 == ' ') buf2++;
	strlcpy(IDENTuser, buf2, sizeof(IDENTuser));
	buf2=strchr(IDENTuser,'\r');
	if (!buf2) buf2=strchr(IDENTuser,'\n');
	if (buf2) *buf2='\0';
    } else
        strlcpy(IDENTuser, "UNKNOWN", sizeof(IDENTuser));
    close(ident_fd);

    return strcmp(identd, IDENTuser) == 0;
}

/*
 * Split text into comma-separated fields.  Return an allocated
 * NULL-terminated array of the fields within the modified argument that
 * the caller is expected to save or free.  We don't use strchr() since
 * the text is expected to be either relatively short or "comma-dense."
 * (This function is different from CommaSplit because spaces are allowed
 * and removed here)
 */

static char **
RCCommaSplit(char *text)
{
    int		i;
    char	*p;
    char	*q;
    char	*r;
    char	**av;
    char	**save;
 
    /* How much space do we need? */
    for (i = 2, p = text, q = r = xstrdup(text); *p; p++) {
        if (*p != ' ' && *p != '\t' && *p != '\n')
	    *q++ = *p;
        if (*p == ',')
            i++;
    }
    *q = '\0';
    free (text);
    for (text = r, av = save = xmalloc(i * sizeof(char *)), *av++ = p = text; *p; )
        if (*p == ',') {
            *p++ = '\0';
	    *av++ = p;
        }
        else
            p++;
    *av = NULL;
    return save;
}

 /*
  * Routine to disable IP-level socket options. This code was taken from 4.4BSD
  * rlogind source, but all mistakes in it are my fault.
  *
  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  *
  * 21-Jan-1997 smd
  *     Code copied again, and modified for INN, all new mistakes are mine.
  * 
  */

/* fix_options - get rid of IP-level socket options */
#ifndef IP_OPTIONS
#define IP_OPTIONS 1
#endif

static int
RCfix_options(int fd, struct sockaddr_storage *remote)
{
#if IP_OPTIONS
    unsigned char optbuf[BUFSIZ / 3], *cp;
    char    lbuf[BUFSIZ], *lp;
    socklen_t optsize = sizeof(optbuf);
    int     ipproto;
    struct protoent *ip;

    switch (remote->ss_family) {
    case AF_INET:
	if ((ip = getprotobyname("ip")) != 0)
	    ipproto = ip->p_proto;
	else
	    ipproto = IPPROTO_IP;
	break;
#ifdef HAVE_INET6
    case AF_INET6:
	if ((ip = getprotobyname("ipv6")) != 0)
	    ipproto = ip->p_proto;
	else
	    ipproto = IPPROTO_IPV6;
	break;
#endif
    default:
	syslog(LOG_ERR, "unknown address family: %d", remote->ss_family);
	return -1;
    }

    if (getsockopt(fd, ipproto, IP_OPTIONS, (char *) optbuf, &optsize) == 0
	&& optsize != 0) {
	lp = lbuf;
	for (cp = optbuf; optsize > 0; cp++, optsize--, lp += 3)
	    sprintf(lp, " %2.2x", *cp);
	syslog(LOG_NOTICE,
	       "connect from %s with IP options (ignored):%s",
	       sprint_sockaddr((struct sockaddr *)remote), lbuf);
	if (setsockopt(fd, ipproto, IP_OPTIONS, (char *) 0, optsize) != 0) {
	    syslog(LOG_ERR, "setsockopt IP_OPTIONS NULL: %m");
	    return -1;
	}
    }
#endif
    return 0;
}

static bool
RCaddressmatch(const struct sockaddr_storage *cp, const struct sockaddr_storage *rp)
{
#ifdef HAVE_INET6
    struct sockaddr_in	*sin_cp, *sin_rp;
    struct sockaddr_in6	*sin6_cp, *sin6_rp;

    if (cp->ss_family == AF_INET6 && rp->ss_family == AF_INET) {
	sin6_cp = (struct sockaddr_in6 *)cp;
	sin_rp = (struct sockaddr_in *)rp;
	if (IN6_IS_ADDR_V4MAPPED(&sin6_cp->sin6_addr) &&
		memcmp(&sin6_cp->sin6_addr.s6_addr[12],
		    &sin_rp->sin_addr.s_addr, sizeof(struct in_addr)) == 0)
	    return true;
    } else if (cp->ss_family == AF_INET && rp->ss_family == AF_INET6) {
	sin_cp = (struct sockaddr_in *)cp;
	sin6_rp = (struct sockaddr_in6 *)rp;
	if (IN6_IS_ADDR_V4MAPPED(&sin6_rp->sin6_addr) &&
		memcmp(&sin6_rp->sin6_addr.s6_addr[12],
		    &sin_cp->sin_addr.s_addr, sizeof(struct in_addr)) == 0)
	    return true;
    } else if (cp->ss_family == AF_INET6 && rp->ss_family == AF_INET6) {
#ifdef HAVE_BROKEN_IN6_ARE_ADDR_EQUAL
	if (!memcmp(&((struct sockaddr_in6 *)cp)->sin6_addr,
		    &((struct sockaddr_in6 *)rp)->sin6_addr,
		    sizeof(struct in6_addr)))
#else
	if (IN6_ARE_ADDR_EQUAL( &((struct sockaddr_in6 *)cp)->sin6_addr,
				&((struct sockaddr_in6 *)rp)->sin6_addr))
#endif
	    return true;
    } else
#endif /* INET6 */
	if (((struct sockaddr_in *)cp)->sin_addr.s_addr ==
	     ((struct sockaddr_in *)rp)->sin_addr.s_addr)
	    return true;

    return false;
}

/*
**  See if the site properly entered the password.
*/
bool
RCauthorized(CHANNEL *cp, char *pass)
{
    REMOTEHOST	*rp;
    int		i;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
	if (RCaddressmatch(&cp->Address, &rp->Address)) {
	    if (rp->Password[0] == '\0' || strcmp(pass, rp->Password) == 0)
		return true;
	    syslog(L_ERROR, "%s (%s) bad_auth", rp->Label,
		   sprint_sockaddr((struct sockaddr *)&cp->Address));
	    return false;
	}

    if (!AnyIncoming)
	/* Not found in our table; this can't happen. */
	syslog(L_ERROR, "%s not_found", sprint_sockaddr((struct sockaddr *)&cp->Address));

    /* Anonymous hosts should not authenticate. */
    return false;
}

/*
**  See if a host is limited or not.
*/
bool
RCnolimit(CHANNEL *cp)
{
    REMOTEHOST	*rp;
    int		i;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
	if (RCaddressmatch(&cp->Address, &rp->Address))
            return !rp->MaxCnx;

    /* Not found in our table; this can't happen. */
    return false;
}

/*
**  Return the limit (max number of connections) for a host.
*/
int
RClimit(CHANNEL *cp)
{
    REMOTEHOST	*rp;
    int		i;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
	if (RCaddressmatch(&cp->Address, &rp->Address))
	    return rp->MaxCnx;
    /* Not found in our table; this can't happen. */
    return RemoteLimit;
}


/*
**  Called when input is ready to read.  Shouldn't happen.
*/
static void
RCrejectreader(CHANNEL *cp)
{
    syslog(L_ERROR, "%s internal RCrejectreader (%s)", LogName,
	   sprint_sockaddr((struct sockaddr *)&cp->Address));
}


/*
**  Write-done function for rejects.
*/
static void
RCrejectwritedone(CHANNEL *cp)
{
    switch (cp->State) {
    default:
	syslog(L_ERROR, "%s internal RCrejectwritedone state %d",
	    CHANname(cp), cp->State);
	break;
    case CSwritegoodbye:
	CHANclose(cp, CHANname(cp));
	break;
    }
}


/*
**  Hand off a descriptor to NNRPD.
*/
void
RChandoff(int fd, HANDOFF h)
{
    const char *argv[6];
    char buff[SMBUF];
    int i;

    if (RCnnrpd == NULL)
	RCnnrpd = concatpath(innconf->pathbin, "nnrpd");
    if (RCnntpd == NULL)
	RCnntpd = concatpath(innconf->pathbin, "nnrpd");
#if	defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
    /* Set KEEPALIVE to catch broken socket connections. */
    i = 1;
    if (setsockopt(fd, SOL_SOCKET,  SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
        syslog(L_ERROR, "fd %d cant setsockopt(KEEPALIVE) %m", fd);
#endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */

    if (nonblocking(fd, false) < 0)
	syslog(L_ERROR, "%s cant nonblock %d in RChandoff %m", LogName, fd);
    switch (h) {
    default:
	syslog(L_ERROR, "%s internal RChandoff %d type %d", LogName, fd, h);
	/* FALLTHROUGH */
    case HOnnrpd:	argv[0] = RCnnrpd;	break;
    case HOnntpd:	argv[0] = RCnntpd;	break;
    }
    argv[1] = "-s                                                ";
    i = 2;
    if (NNRPReason) {
	snprintf(buff, sizeof(buff), "-r%s", NNRPReason);
	argv[i++] = buff;
    }
    if (NNRPTracing)
	argv[i++] = "-t";
    if (RCslaveflag)
	argv[i++] = RCslaveflag;
    argv[i] = NULL;

    /* Call NNRP; don't send back a QUIT message if Spawn fails since  
     * that's a major error we want to find out about quickly. */
    (void)Spawn(innconf->nicekids, fd, fd, fd, (char * const *)argv);
}


/*
**  Read function.  Accept the connection and either create an NNTP channel
**  or spawn an nnrpd to handle it.
*/
static void
RCreader(CHANNEL *cp)
{
    int			fd;
    struct sockaddr_storage	remote;
    socklen_t		size;
    int                 i;
    REMOTEHOST          *rp;
    CHANNEL		*new;
    char		*name;
    long		reject_val = 0;
    const char		*reject_message;
    int			count;
    int			found;
    time_t		now;
    CHANNEL		tempchan;
    char		buff[SMBUF];

    for (i = 0 ; i < chanlimit ; i++) {
	if (RCchan[i] == cp) {
	    break;
	}
    }
    if (i == chanlimit) {
	syslog(L_ERROR, "%s internal RCreader wrong channel 0x%p",
		LogName, (void *)cp);
	return;
    }

    /* Get the connection. */
    size = sizeof remote;
    if ((fd = accept(cp->fd, (struct sockaddr *)&remote, &size)) < 0) {
	if (errno != EWOULDBLOCK && errno != EAGAIN)
	    syslog(L_ERROR, "%s cant accept RCreader %m", LogName);
	return;
    }

    /*
    ** Clear any IP_OPTIONS, including source routing, on the socket
    */
    /* FIXME RCfix_options breaks IPv6 sockets, at least on Linux -lutchann */
#ifndef HAVE_INET6
    if (RCfix_options(fd, &remote) != 0) {
	/* shouldn't happen, but we're bit paranoid at this point */
	if (close(fd) < 0)
	    syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
	return;
    }
#endif

    /* If RemoteTimer is not zero, then check the limits on incoming
       connections on a total and per host basis.

       The incoming connection table is fixed at 128 entries to make
       calculating the index easy (i + 1) & 7, and to be pretty sure that you
       won't run out of space.  The table is used as a ring with new entries
       being added to the end (wrapping around) and expired entries being
       deleted from the front (again wrapping around).  It is doubtful that
       you will ever use even half of the table.

       There are three parameters controlling the use of the table not
       counting the starting index and count:

           H = per host incoming connects per X seconds allowed
           T = total incoming connects per X seconds allowed
           X = number of seconds to remember a successful connect

       First, one pass is made over the live entries deleting any that are
       over X seconds old.  If the entry hasn't expired, compare the incoming
       connection's host address with the entry's host address.  If equal,
       increment the "found" counter.

       Second, if the number of entries now in the table is equal to the T
       parameter, reject the connection with a message indicating that the
       server is overloaded.

       Third, if the number of entries now in the table which match the
       incoming connection's host address is equal to the H parameter, reject
       the connection.

       Finally, if neither rejection happened, add the entry to the table, and
       continue on as a normal connect. */
    memcpy(&tempchan.Address, &remote, SA_LEN((struct sockaddr *)&remote));
    reject_message = NULL;
    if (RemoteTimer != 0) {
	now = time(NULL);
	i = remotefirst;
	count = remotecount;
	found = 0;
	while (count--) {
	    if (remotetable[i].Expires < now) {
		remotecount--;
		remotefirst = (remotefirst + 1) & (REMOTETABLESIZE - 1);
		i = (i + 1) & (REMOTETABLESIZE - 1);
		continue;
	    }
	    if (RCaddressmatch(&remotetable[i].Address, &remote))
		found++;
	    i = (i + 1) & (REMOTETABLESIZE - 1);
	}
	if (remotecount == RemoteTotal) {
	    reject_val = NNTP_GOODBYE_VAL;
	    reject_message = "400 Server overloaded, try later";
	}
	else if (found >= RemoteLimit && !RCnolimit(&tempchan)) {
	    reject_val = NNTP_GOODBYE_VAL;
	    reject_message = "400 Connection rejected, you're making too"
                " many connects per minute";
	}
	else {
	    i = (remotefirst + remotecount) & (REMOTETABLESIZE - 1);
	    memcpy(&remotetable[i].Address, &remote, SA_LEN((struct sockaddr *)&remote));
	    remotetable[i].Expires = now + RemoteTimer;
	    remotecount++;
	}
    }

    /*
    ** Create a reject channel to reject the connection.  This is done
    ** to avoid a call to fork.  
    */
    if (reject_message) {
	new = CHANcreate(fd, CTreject, CSwritegoodbye, RCrejectreader,
	    RCrejectwritedone);
	memcpy(&remotetable[i].Address, &remote, SA_LEN((struct sockaddr *)&remote));
	new->Rejected = reject_val;
	RCHANremove(new);
	WCHANset(new, reject_message, (int)strlen(reject_message));
	WCHANappend(new, RCterm, strlen(RCterm));
	WCHANadd(new);
	return;
    }

    /* See if it's one of our servers. */
    for (name = NULL, rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
	if (RCaddressmatch(&rp->Address, &remote)) {
	    name = rp->Name;
	    break;
	}

    /* If not a server, and not allowing anyone, hand him off unless
       not spawning nnrpd in which case we return an error. */
    if ((i >= 0) && !rp->Skip) {

	/* We check now the identd if we have to */
	if(! GoodIdent(fd, rp->Identd))
	{
	    if (!innconf->noreader) {
		RChandoff(fd, HOnntpd);
		if (close(fd) < 0)
		    syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
		return;
	    }
	}
	
	if ((new = NCcreate(fd, rp->Password[0] != '\0', false)) != NULL) {
            new->Streaming = rp->Streaming;
            new->Skip = rp->Skip;
            new->NoResendId = rp->NoResendId;
            new->Nolist = rp->Nolist;
            new->MaxCnx = rp->MaxCnx;
            new->HoldTime = rp->HoldTime;
	    memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
	    if (new->MaxCnx > 0 && new->HoldTime == 0) {
		CHANsetActiveCnx(new);
		if((new->ActiveCnx > new->MaxCnx) && (new->fd > 0)) {
		    snprintf(buff, sizeof(buff),
                             "You are limited to %d connection%s",
                             new->MaxCnx, (new->MaxCnx != 1) ? "s" : "");
		    NCwriteshutdown(new, buff);
		    syslog(L_NOTICE, "too many connections from %s", rp->Label);
		} else {
		    NCwritereply(new, (char *)NCgreeting);
		}
	    } else {
		NCwritereply(new, (char *)NCgreeting);
	    }
	}
    } else if (AnyIncoming && !rp->Skip) {
	if ((new = NCcreate(fd, false, false)) != NULL) {
	    NCwritereply(new, (char *)NCgreeting);
	}
    } else if (!innconf->noreader) {
	RChandoff(fd, HOnntpd);
	if (close(fd) < 0)
	    syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
	return;
    } else {
	reject_val = NNTP_ACCESS_VAL;
	reject_message = NNTP_ACCESS;
        new = CHANcreate(fd, CTreject, CSwritegoodbye, RCrejectreader,
            RCrejectwritedone);
	memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
        new->Rejected = reject_val;
        RCHANremove(new);
        WCHANset(new, reject_message, (int)strlen(reject_message));
        WCHANappend(new, RCterm, strlen(RCterm));
        WCHANadd(new);
        return;
    }

    if (new != NULL) {
	memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
	syslog(L_NOTICE, "%s connected %d streaming %s",
           name ? name : sprint_sockaddr((struct sockaddr *)&new->Address),
	   new->fd, (!StreamingOff && new->Streaming) ? "allowed" : "not allowed");
    }
}


/*
**  Write-done function.  Shouldn't happen.
*/
static void
RCwritedone(CHANNEL *unused)
{
    unused = unused;		/* ARGSUSED */
    syslog(L_ERROR, "%s internal RCwritedone", LogName);
}

/*
 *  New config file style. Old hosts.nntp and hosts.nntp.nolimit are merged
 *  into one file called incoming.conf (to avoid confusion).
 *  See ../samples/incoming.conf for the new syntax.
 *
 *  Fabien Tassin <fta@sofaraway.org>, 21-Dec-1997.
 */


/*
 * Read something (a word or a double quoted string) from a file.
 */
static char *
RCreaddata(int *num, FILE *F, bool *toolong)
{
  char	*p;
  char	*s;
  char	*t;
  char	*word;
  bool	flag;

  *toolong = false;
  if (*RCbuff == '\0') {
    if (feof (F)) return (NULL);
    fgets(RCbuff, sizeof RCbuff, F);
    (*num)++;
    if (strlen (RCbuff) == sizeof RCbuff) {
      *toolong = true;
      return (NULL); /* Line too long */
    }
  }
  p = RCbuff;
  do {
     /* Ignore blank and comment lines. */
     if ((p = strchr(RCbuff, '\n')) != NULL)
       *p = '\0';
     if ((p = strchr(RCbuff, '#')) != NULL) {
       if (p == RCbuff || (p > RCbuff && *(p - 1) != '\\'))
	   *p = '\0';
     }
     for (p = RCbuff; *p == ' ' || *p == '\t' ; p++);
     flag = true;
     if (*p == '\0' && !feof (F)) {
       flag = false;
       fgets(RCbuff, sizeof RCbuff, F);
       (*num)++;
       if (strlen (RCbuff) == sizeof RCbuff) {
	 *toolong = true;
	 return (NULL); /* Line too long */
       }
       continue;
     }
     break;
  } while (!feof (F) || !flag);

  if (*p == '"') { /* double quoted string ? */
    p++;
    do {
      for (t = p; (*t != '"' || (*t == '"' && *(t - 1) == '\\')) &&
	     *t != '\0'; t++);
      if (*t == '\0') {
	*t++ = '\n';
	fgets(t, sizeof RCbuff - strlen (RCbuff), F);
	(*num)++;
	if (strlen (RCbuff) == sizeof RCbuff) {
	  *toolong = true;
	  return (NULL); /* Line too long */
	}
	if ((s = strchr(t, '\n')) != NULL)
	  *s = '\0';
      }
      else 
	break;
    } while (!feof (F));
    *t++ = '\0';
  }
  else {
    for (t = p; *t != ' ' && *t != '\t' && *t != '\0'; t++);
    if (*t != '\0')
      *t++ = '\0';
  }
  if (*p == '\0' && feof (F)) return (NULL);
  word = xstrdup (p);
  for (p = RCbuff; *t != '\0'; t++)
    *p++ = *t;
  *p = '\0';

  return (word);
}

/*
 *  Add all data into RCpeerlistfile.
 */
static void
RCadddata(REMOTEHOST_DATA **d, int *count, int Key, int Type, char* Value)
{
  (*d)[*count].key = Key;
  (*d)[*count].type = Type;
  (*d)[*count].value = Value;
  (*count)++;
  *d = xrealloc(*d, (*count + 1) * sizeof(REMOTEHOST_DATA));
}

/*
**  Read in the file listing the hosts we take news from, and fill in the
**  global list of their Internet addresses.  A host can have multiple
**  addresses, so we take care to add all of them to the list.
*/
static void
RCreadfile (REMOTEHOST_DATA **data, REMOTEHOST **list, int *count, 
	    char *filename)
{
    static char		NOPASS[] = "";
    static char		NOIDENTD[] = "";
    static char		NOEMAIL[] = "";
    static char		NOCOMMENT[] = "";
    FILE		*F;
    char 		*p;
    char 		**q;
    char 		**r;
#if     !defined( HAVE_INET6)
    struct hostent	*hp;
#endif
#if	!defined(HAVE_UNIX_DOMAIN_SOCKETS) || !defined(HAVE_INET6)
    struct in_addr      addr;
#endif
    int                 i;
    int                 j;
    int			linecount;
    int			infocount;
    int                 groupcount;
    int                 maxgroup;
    REMOTEHOST_DATA 	*dt;
    REMOTEHOST		*rp;
    char		*word;
    REMOTEHOST		*groups;
    REMOTEHOST		*group_params = NULL;
    REMOTEHOST		peer_params;
    REMOTEHOST		default_params;
    bool		flag, bit, toolong;
 
    *RCbuff = '\0';
    if (*list) {
	for (rp = *list, i = *count; --i >= 0; rp++) {
	    free(rp->Name);
	    free(rp->Label);
	    free(rp->Email);
	    free(rp->Comment);
	    free(rp->Password);
	    free(rp->Identd);
	    if (rp->Patterns) {
		free(rp->Patterns[0]);
		free(rp->Patterns);
	    }
	}
	free(*list);
	*list = NULL;
	*count = 0;
    }
    if (*data) {
        for (i = 0; (*data)[i].key != K_END; i++)
	    if ((*data)[i].value != NULL)
	        free((*data)[i].value);
        free(*data);
	*data = NULL;
    }

    *count = 0;
    maxgroup = 0;
    /* Open the server file. */
    if ((F = Fopen(filename, "r", TEMPORARYOPEN)) == NULL) {
	syslog(L_FATAL, "%s cant read %s: %m", LogName, filename);
	exit(1);
    }
    dt = *data = xmalloc(sizeof(REMOTEHOST_DATA));
    rp = *list = xmalloc(sizeof(REMOTEHOST));

#if	!defined(HAVE_UNIX_DOMAIN_SOCKETS)
    addr.s_addr = INADDR_LOOPBACK;
    make_sin( (struct sockaddr_in *)&rp->Address, &addr );
    rp->Name = xstrdup("localhost");
    rp->Label = xstrdup("localhost");
    rp->Email = xstrdup(NOEMAIL);
    rp->Comment = xstrdup(NOCOMMENT);
    rp->Password = xstrdup(NOPASS);
    rp->Identd = xstrdup(NOIDENTD);
    rp->Patterns = NULL;
    rp->MaxCnx = 0;
    rp->Streaming = true;
    rp->Skip = false;
    rp->NoResendId = false;
    rp->Nolist = false;
    rp->HoldTime = 0;
    rp++;
    (*count)++;
#endif	/* !defined(HAVE_UNIX_DOMAIN_SOCKETS) */

    linecount = 0;
    infocount = 0;
    groupcount = 0; /* no group defined yet */
    groups = 0;
    peer_params.Label = NULL;
    default_params.Streaming = true;
    default_params.Skip = false;
    default_params.NoResendId = false;
    default_params.Nolist = false;
    default_params.MaxCnx = 0;
    default_params.HoldTime = 0;
    default_params.Password = xstrdup(NOPASS);
    default_params.Identd = xstrdup(NOIDENTD);
    default_params.Email = xstrdup(NOEMAIL);
    default_params.Comment = xstrdup(NOCOMMENT);
    default_params.Pattern = NULL;
    peer_params.Keysetbit = 0;

    /* Read the file to add all the hosts. */
    while ((word = RCreaddata (&linecount, F, &toolong)) != NULL) {

      /* group */
      if (!strncmp (word, GROUP,  sizeof GROUP)) {
	free(word);
	/* name of the group */
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  syslog(L_ERROR, GROUP_NAME, LogName, filename, linecount);
	  break;
	}
	RCadddata(data, &infocount, K_BEGIN_GROUP, T_STRING, word);
	groupcount++;
	if (groupcount == 1) {
          group_params = groups = xmalloc(sizeof(REMOTEHOST));
	}
	else if (groupcount >= maxgroup) {
          /* alloc 5 groups */
          groups = xrealloc(groups, (groupcount + 4) * sizeof(REMOTEHOST));
	  maxgroup += 5;
	  group_params = groups + groupcount - 1;
	}
	group_params->Label = word;
	group_params->Skip = groupcount > 1 ?
	  groups[groupcount - 2].Skip : default_params.Skip;
	group_params->Streaming = groupcount > 1 ?
	  groups[groupcount - 2].Streaming : default_params.Streaming;
	group_params->NoResendId = groupcount > 1 ?
	  groups[groupcount - 2].NoResendId : default_params.NoResendId;
	group_params->Nolist = groupcount > 1 ?
	  groups[groupcount - 2].Nolist : default_params.Nolist;
	group_params->Email = groupcount > 1 ?
	  groups[groupcount - 2].Email : default_params.Email;
	group_params->Comment = groupcount > 1 ?
	  groups[groupcount - 2].Comment : default_params.Comment;
	group_params->Pattern = groupcount > 1 ?
	  groups[groupcount - 2].Pattern : default_params.Pattern;
	group_params->Password = groupcount > 1 ?
	  groups[groupcount - 2].Password : default_params.Password;
	group_params->Identd = groupcount > 1 ?
	  groups[groupcount - 2].Identd : default_params.Identd;
	group_params->MaxCnx = groupcount > 1 ?
	  groups[groupcount - 2].MaxCnx : default_params.MaxCnx;
	group_params->HoldTime = groupcount > 1 ?
	  groups[groupcount - 2].HoldTime : default_params.HoldTime;

	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
	  break;
	}
	/* left brace */
	if (strncmp (word, "{", 1)) {
	  free(word);
	  syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
	  break;
	}
	else
	  free(word);
	peer_params.Keysetbit = 0;
	continue;
      }

      /* peer */
      if (!strncmp (word, PEER, sizeof PEER)) {
	free(word);
	if (peer_params.Label != NULL) {
	  /* peer can't contain peer */
	  syslog(L_ERROR, PEER_IN_PEER, LogName, 
	      filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL)
	{
	  syslog(L_ERROR, PEER_NAME, LogName, filename, linecount);
	  break;
	}
	RCadddata(data, &infocount, K_BEGIN_PEER, T_STRING, word);
	/* name of the peer */
	peer_params.Label = word;
	peer_params.Name = NULL;
	peer_params.Skip = groupcount > 0 ?
	  group_params->Skip : default_params.Skip;
	peer_params.Streaming = groupcount > 0 ?
	  group_params->Streaming : default_params.Streaming;
	peer_params.NoResendId = groupcount > 0 ?
	  group_params->NoResendId : default_params.NoResendId;
	peer_params.Nolist = groupcount > 0 ?
	  group_params->Nolist : default_params.Nolist;
	peer_params.Email = groupcount > 0 ?
	  group_params->Email : default_params.Email;
	peer_params.Comment = groupcount > 0 ?
	  group_params->Comment : default_params.Comment;
	peer_params.Pattern = groupcount > 0 ?
	  group_params->Pattern : default_params.Pattern;
	peer_params.Password = groupcount > 0 ?
	  group_params->Password : default_params.Password;
	peer_params.Identd = groupcount > 0 ?
	  group_params->Identd : default_params.Identd;
	peer_params.MaxCnx = groupcount > 0 ?
	  group_params->MaxCnx : default_params.MaxCnx;
	peer_params.HoldTime = groupcount > 0 ?
	  group_params->HoldTime : default_params.HoldTime;

	peer_params.Keysetbit = 0;

	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL)
	{
	  syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
	  break;
	}
	/* left brace */
	if (strncmp (word, "{", 1)) {
	  syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
	  free(word);
	  break;
	}
	else
	  free(word);
	continue;
      }

      /* right brace */
      if (!strncmp (word, "}", 1)) {
	free(word);
	if (peer_params.Label != NULL) {
	  RCadddata(data, &infocount, K_END_PEER, T_STRING, NULL);

	  /* Hostname defaults to label if not given */
	  if (peer_params.Name == NULL)
            peer_params.Name = xstrdup(peer_params.Label);

	  for(r = q = RCCommaSplit(xstrdup(peer_params.Name)); *q != NULL; q++) {
#ifdef HAVE_INET6
	      struct addrinfo *res, *res0, hints;
	      int gai_ret;
#endif
	    (*count)++;

	    /* Grow the array */
	    j = rp - *list;
            *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
	    rp = *list + j;

#ifdef HAVE_INET6
	    memset( &hints, 0, sizeof( hints ) );
	    hints.ai_socktype = SOCK_STREAM;
	    hints.ai_family = PF_UNSPEC;
	    if ((gai_ret = getaddrinfo(*q, NULL, &hints, &res0)) != 0) {
		syslog(L_ERROR, "%s cant getaddrinfo %s %s", LogName, *q,
				gai_strerror( gai_ret ) );
		/* decrement *count, since we never got to add this record. */
		(*count)--;
		continue;
	    }
	    /* Count the addresses and see if we have to grow the list */
	    i = 0;
	    for (res = res0; res != NULL; res = res->ai_next)
		i++;
	    /* Grow the array */
	    j = rp - *list;
	    *count += i - 1;
            *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
	    rp = *list + j;

	    /* Add all hosts */
	    for (res = res0; res != NULL; res = res->ai_next) {
		(void)memcpy(&rp->Address, res->ai_addr, res->ai_addrlen);
		rp->Name = xstrdup (*q);
		rp->Label = xstrdup (peer_params.Label);
		rp->Email = xstrdup(peer_params.Email);
		rp->Comment = xstrdup(peer_params.Comment);
		rp->Streaming = peer_params.Streaming;
		rp->Skip = peer_params.Skip;
		rp->NoResendId = peer_params.NoResendId;
		rp->Nolist = peer_params.Nolist;
		rp->Password = xstrdup(peer_params.Password);
		rp->Identd = xstrdup(peer_params.Identd);
		rp->Patterns = peer_params.Pattern != NULL ?
		    RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
		rp->MaxCnx = peer_params.MaxCnx;
		rp->HoldTime = peer_params.HoldTime;
		rp++;
	    }
	    freeaddrinfo(res0);
#else /* HAVE_INET6 */
	    /* Was host specified as a dotted quad ? */
	    if (inet_aton(*q, &addr)) {
	      make_sin( (struct sockaddr_in *)&rp->Address, &addr );
	      rp->Name = xstrdup (*q);
	      rp->Label = xstrdup (peer_params.Label);
	      rp->Password = xstrdup(peer_params.Password);
	      rp->Identd = xstrdup(peer_params.Identd);
	      rp->Skip = peer_params.Skip;
	      rp->Streaming = peer_params.Streaming;
	      rp->NoResendId = peer_params.NoResendId;
	      rp->Nolist = peer_params.Nolist;
	      rp->Email = xstrdup(peer_params.Email);
	      rp->Comment = xstrdup(peer_params.Comment);
	      rp->Patterns = peer_params.Pattern != NULL ?
		    RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
	      rp->MaxCnx = peer_params.MaxCnx;
	      rp->HoldTime = peer_params.HoldTime;
	      rp++;
	      continue;
	    }
	    
	    /* Host specified as a text name ? */
	    if ((hp = gethostbyname(*q)) == NULL) {
	      syslog(L_ERROR, "%s cant gethostbyname %s %m", LogName, *q);
	      /* decrement *count, since we never got to add this record. */
	      (*count)--;
	      continue;
	    }

	    /* Count the adresses and see if we have to grow the list */
	    for (i = 0; hp->h_addr_list[i]; i++)
	      continue;
	    if (i == 0) {
	      syslog(L_ERROR, "%s no_address %s %m", LogName, *q);
	      continue;
	    }
	    if (i == 1) {
	      char **rr;
	      int    t = 0;
	      /* Strange DNS ? try this.. */
	      for (rr = hp->h_aliases; *rr != 0; rr++) {
                if (!inet_aton(*rr, &addr))
		  continue;
		(*count)++;
		/* Grow the array */
		j = rp - *list;
                *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
		rp = *list + j;

		make_sin( (struct sockaddr_in *)&rp->Address, &addr );
		rp->Name = xstrdup (*q);
		rp->Label = xstrdup (peer_params.Label);
		rp->Email = xstrdup(peer_params.Email);
		rp->Comment = xstrdup(peer_params.Comment);
		rp->Streaming = peer_params.Streaming;
		rp->Skip = peer_params.Skip;
		rp->NoResendId = peer_params.NoResendId;
		rp->Nolist = peer_params.Nolist;
		rp->Password = xstrdup(peer_params.Password);
		rp->Identd = xstrdup(peer_params.Identd);
		rp->Patterns = peer_params.Pattern != NULL ?
		  RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
		rp->MaxCnx = peer_params.MaxCnx;
		rp->HoldTime = peer_params.HoldTime;
		rp++;
		t++;
	      }
	      if (t == 0) {
		/* Just one, no need to grow. */
		make_sin( (struct sockaddr_in *)&rp->Address,
				(struct in_addr *)hp->h_addr_list[0] );
		rp->Name = xstrdup (*q);
		rp->Label = xstrdup (peer_params.Label);
		rp->Email = xstrdup(peer_params.Email);
		rp->Comment = xstrdup(peer_params.Comment);
		rp->Streaming = peer_params.Streaming;
		rp->Skip = peer_params.Skip;
		rp->NoResendId = peer_params.NoResendId;
		rp->Nolist = peer_params.Nolist;
		rp->Password = xstrdup(peer_params.Password);
		rp->Identd = xstrdup(peer_params.Identd);
		rp->Patterns = peer_params.Pattern != NULL ?
		  RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
		rp->MaxCnx = peer_params.MaxCnx;
		rp->HoldTime = peer_params.HoldTime;
		rp++;
		continue;
	      }
	    }
	    /* Grow the array */
	    j = rp - *list;
	    *count += i - 1;
            *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
	    rp = *list + j;

	    /* Add all the hosts. */
	    for (i = 0; hp->h_addr_list[i]; i++) {
	      make_sin( (struct sockaddr_in *)&rp->Address,
			      (struct in_addr *)hp->h_addr_list[i] );
	      rp->Name = xstrdup (*q);
	      rp->Label = xstrdup (peer_params.Label);
	      rp->Email = xstrdup(peer_params.Email);
	      rp->Comment = xstrdup(peer_params.Comment);
	      rp->Streaming = peer_params.Streaming;
	      rp->Skip = peer_params.Skip;
	      rp->NoResendId = peer_params.NoResendId;
	      rp->Nolist = peer_params.Nolist;
	      rp->Password = xstrdup(peer_params.Password);
	      rp->Identd = xstrdup(peer_params.Identd);
	      rp->Patterns = peer_params.Pattern != NULL ?
		RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
	      rp->MaxCnx = peer_params.MaxCnx;
	      rp->HoldTime = peer_params.HoldTime;
	      rp++;
	    }
#endif /* HAVE_INET6 */
	  }
	  free(r[0]);
	  free(r);
	  peer_params.Label = NULL;
	}
	else if (groupcount > 0 && group_params->Label != NULL) {
	  RCadddata(data, &infocount, K_END_GROUP, T_STRING, NULL);
	  group_params->Label = NULL;
	  groupcount--;
	  if (groupcount == 0)
	    free(groups);
	  else
	    group_params--;
	}
	else {
	  syslog(L_ERROR, RIGHT_BRACE, LogName, linecount, filename);
	}
	continue;
      }

      /* streaming */
      if (!strncmp (word, STREAMING, sizeof STREAMING)) {
	free(word);
	TEST_CONFIG(K_STREAM, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	if (!strcmp (word, "true"))
	  flag = true;
	else
	  if (!strcmp (word, "false"))
	    flag = false;
	  else {
	    syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
	    break;
	  }
	RCadddata(data, &infocount, K_STREAM, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Streaming = flag;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Streaming = flag;
	  else
	    default_params.Streaming = flag;
	SET_CONFIG(K_STREAM);
	continue;
      }

      /* skip */
      if (!strncmp (word, SKIP, sizeof SKIP)) {
	free(word);
	TEST_CONFIG(K_SKIP, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	if (!strcmp (word, "true"))
	  flag = true;
	else
	  if (!strcmp (word, "false"))
	    flag = false;
	  else {
	    syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
	    break;
	  }
	RCadddata(data, &infocount, K_SKIP, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Skip = flag;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Skip = flag;
	  else
	    default_params.Skip = flag;
	SET_CONFIG(K_SKIP);
	continue;
      }

      /* noresendid */
      if (!strncmp (word, NORESENDID, sizeof NORESENDID)) {
	free(word);
	TEST_CONFIG(K_NORESENDID, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	if (!strcmp (word, "true"))
	  flag = true;
	else
	  if (!strcmp (word, "false"))
	    flag = false;
	  else {
	    syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
	    break;
	  }
	RCadddata(data, &infocount, K_NORESENDID, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.NoResendId = flag;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->NoResendId = flag;
	  else
	    default_params.NoResendId = flag;
	SET_CONFIG(K_NORESENDID);
	continue;
      }

      /* nolist */
      if (!strncmp (word, NOLIST, sizeof NOLIST)) {
	free(word);
	TEST_CONFIG(K_NOLIST, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	if (!strcmp (word, "true"))
	  flag = true;
	else
	  if (!strcmp (word, "false"))
	    flag = false;
	  else {
	    syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
	    break;
	  }
	RCadddata(data, &infocount, K_NOLIST, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Nolist = flag;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Nolist = flag;
	  else
	    default_params.Nolist = flag;
	SET_CONFIG(K_NOLIST);
	continue;
      }

      /* max-connections */
      if (!strncmp (word, MAX_CONN, sizeof MAX_CONN)) {
	int max;
	free(word);
	TEST_CONFIG(K_MAX_CONN, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_MAX_CONN, T_STRING, word);
	for (p = word; CTYPE(isdigit, *p) && *p != '\0'; p++);
	if (!strcmp (word, "none") || !strcmp (word, "unlimited")) {
	  max = 0;
	} else {
	  if (*p != '\0') {
	    syslog(L_ERROR, MUST_BE_INT, LogName, filename, linecount);
	    break;
	  }
	  max = atoi(word);
	}
	if (peer_params.Label != NULL)
	  peer_params.MaxCnx = max;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->MaxCnx = max;
	  else
	    default_params.MaxCnx = max;
	SET_CONFIG(K_MAX_CONN);
	continue;
      }

      /* hold-time */
      if (!strncmp (word, HOLD_TIME, sizeof HOLD_TIME)) {
	free(word);
	TEST_CONFIG(K_HOLD_TIME, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_HOLD_TIME, T_STRING, word);
	for (p = word; CTYPE(isdigit, *p) && *p != '\0'; p++);
	if (*p != '\0') {
	  syslog(L_ERROR, MUST_BE_INT, LogName, filename, linecount);
	  break;
	}
	if (peer_params.Label != NULL)
	  peer_params.HoldTime = atoi(word);
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->HoldTime = atoi(word);
	  else
	    default_params.HoldTime = atoi(word);
	SET_CONFIG(K_HOLD_TIME);
	continue;
      }

      /* hostname */
      if (!strncmp (word, HOSTNAME, sizeof HOSTNAME)) {
	free(word);
	TEST_CONFIG(K_HOSTNAME, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_HOSTNAME, T_STRING, word);
	peer_params.Name = word;
	SET_CONFIG(K_HOSTNAME);
	continue;
      }

      /* password */
      if (!strncmp (word, PASSWORD, sizeof PASSWORD)) {
	free(word);
	TEST_CONFIG(K_PASSWORD, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_PASSWORD, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Password = word;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Password = word;
	  else
	    default_params.Password = word;
	SET_CONFIG(K_PASSWORD);
	continue;
      }

      /* identd */
      if (!strncmp (word, IDENTD, sizeof IDENTD)) {
	free(word);
	TEST_CONFIG(K_IDENTD, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_IDENTD, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Identd = word;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Identd = word;
	  else
	    default_params.Identd = word;
	SET_CONFIG(K_IDENTD);
	continue;
      }

      /* patterns */
      if (!strncmp (word, PATTERNS, sizeof PATTERNS)) {
	TEST_CONFIG(K_PATTERNS, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	free(word);
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_PATTERNS, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Pattern = word;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Pattern = word;
	  else
	    default_params.Pattern = word;
	SET_CONFIG(K_PATTERNS);
	continue;
      }

      /* email */
      if (!strncmp (word, EMAIL, sizeof EMAIL)) {
	free(word);
	TEST_CONFIG(K_EMAIL, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_EMAIL, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Email = word;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Email = word;
	  else
	    default_params.Email = word;
	SET_CONFIG(K_EMAIL);
	continue;
      }

      /* comment */
      if (!strncmp (word, COMMENT, sizeof COMMENT)) {
	free(word);
	TEST_CONFIG(K_COMMENT, bit);
        if (bit) {
	  syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
	  break;
	}
	if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
	  break;
	}
	RCadddata(data, &infocount, K_COMMENT, T_STRING, word);
	if (peer_params.Label != NULL)
	  peer_params.Comment = word;
	else
	  if (groupcount > 0 && group_params->Label != NULL)
	    group_params->Comment = word;
	  else
	    default_params.Comment = word;
	SET_CONFIG(K_COMMENT);
	continue;
      }

      if (toolong)
	syslog(L_ERROR, "%s line too long at %d: %s",
	     LogName, --linecount, filename);
      else
	syslog(L_ERROR, "%s Unknown value line %d: %s",
	     LogName, linecount, filename);
      free(word);
      break;
    }
    free(default_params.Email);
    free(default_params.Comment);
    RCadddata(data, &infocount, K_END, T_STRING, NULL);

    if (feof (F)) {
      if (peer_params.Label != NULL)
	syslog(L_ERROR, INCOMPLETE_PEER, LogName, peer_params.Label,
	       filename, linecount);
      if (groupcount > 0 && group_params->Label != NULL)
	syslog(L_ERROR, INCOMPLETE_GROUP, LogName, group_params->Label,
	       filename, linecount);
    }
    else
      syslog(L_ERROR, "%s Syntax error in %s at or before line %d", LogName, 
	     filename, linecount);

    if (Fclose(F) == EOF)
	syslog(L_ERROR, "%s cant fclose %s %m", LogName, filename);

    free(default_params.Password);
    free(default_params.Identd);
}


/*
**  Indent a line with 3 * c blanks.
**  Used by RCwritelist().
*/
static void
RCwritelistindent(FILE *F, int c)
{
    int		i;

    for (i = 0; i < c; i++)
        fprintf(F, "   ");
}

/*
**  Add double quotes around a string, if needed.
**  Used by RCwritelist().
*/
static void
RCwritelistvalue(FILE *F, char *value)
{
    if (*value == '\0' || strchr (value, '\n') ||
	strchr (value, ' ') || strchr (value, '\t'))
	fprintf(F, "\"%s\"", value);
    else
        fprintf(F, "%s", value);
}

/*
**  Write the incoming configuration (memory->disk)
*/
static void UNUSED
RCwritelist(char *filename)
{
    FILE	*F;
    int		i;
    int		inc;
    char	*p;
    char	*q;
    char	*r;

    if ((F = Fopen(filename, "w", TEMPORARYOPEN)) == NULL) {
        syslog(L_FATAL, "%s cant write %s: %m", LogName, filename);
        return;
    }

    /* Write a standard header.. */

    /* Find the filename */
    p = concatpath(innconf->pathetc, _PATH_INNDHOSTS);
    for (r = q = p; *p; p++)
        if (*p == '/')
	   q = p + 1;

    fprintf (F, "##  $Revision: 1.83.2.1 $\n");
    fprintf (F, "##  %s - names and addresses that feed us news\n", q);
    free(r);
    fprintf (F, "##\n\n");

    /* ... */

    inc = 0;
    for (i = 0; RCpeerlistfile[i].key != K_END; i++) {
        switch (RCpeerlistfile[i].key) {
	  case K_BEGIN_PEER:
	    fputc ('\n', F);
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s %s {\n", PEER, RCpeerlistfile[i].value);
	    inc++;
	    break;
	  case K_BEGIN_GROUP:
	    fputc ('\n', F);
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s %s {\n", GROUP, RCpeerlistfile[i].value);
	    inc++;
	    break;
	  case K_END_PEER:
	  case K_END_GROUP:
	    inc--;
	    RCwritelistindent (F, inc);
	    fprintf(F, "}\n");
	    break;
	  case K_STREAM:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", STREAMING);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_SKIP:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", SKIP);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_NORESENDID:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", NORESENDID);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_NOLIST:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", NOLIST);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_HOSTNAME:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", HOSTNAME);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_MAX_CONN:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", MAX_CONN);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_HOLD_TIME:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", HOLD_TIME);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_PASSWORD:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", PASSWORD);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_IDENTD:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", IDENTD);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_EMAIL:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", EMAIL);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_PATTERNS:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", PATTERNS);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  case K_COMMENT:
	    RCwritelistindent (F, inc);
	    fprintf(F, "%s\t", COMMENT);
	    RCwritelistvalue (F, RCpeerlistfile[i].value);
	    fputc ('\n', F);
	    break;
	  default:
	    fprintf(F, "# ***ERROR***\n");
	}
    }
    if (Fclose(F) == EOF)
        syslog(L_ERROR, "%s cant fclose %s %m", LogName, filename);

}

void
RCreadlist(void)
{
    static char	*INNDHOSTS = NULL;

    if (INNDHOSTS == NULL)
	INNDHOSTS = concatpath(innconf->pathetc, _PATH_INNDHOSTS);
    StreamingOff = false;
    RCreadfile(&RCpeerlistfile, &RCpeerlist, &RCnpeerlist, INNDHOSTS);
    /* RCwritelist("/tmp/incoming.conf.new"); */
}

/*
**  Find the name of a remote host we've connected to.
*/
char *
RChostname(const CHANNEL *cp)
{
    static char	buff[SMBUF];
    REMOTEHOST	*rp;
    int		i;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
	if (RCaddressmatch(&cp->Address, &rp->Address))
	    return rp->Name;
    strlcpy(buff, sprint_sockaddr((struct sockaddr *)&cp->Address),
            sizeof(buff));
    return buff;
}

/*
**  Find the label name of a remote host we've connected to.
*/
char *
RClabelname(CHANNEL *cp) {
    REMOTEHOST	*rp;
    int		i;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
	if (RCaddressmatch(&cp->Address, &rp->Address))
	    return rp->Label;
    }
    return NULL;
}

/*
**  Is the remote site allowed to post to this group?
*/
int
RCcanpost(CHANNEL *cp, char *group)
{
    REMOTEHOST	        *rp;
    char	        match;
    char	        subvalue;
    char	        **argv;
    char	        *pat;
    int	                i;

    /* Connections from lc.c are from local nnrpd and should always work */
    if (cp->Address.ss_family == 0)
	return 1;

    for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
	if (!RCaddressmatch(&cp->Address, &rp->Address))
	    continue;
	if (rp->Patterns == NULL)
	    break;
	for (match = 0, argv = rp->Patterns; (pat = *argv++) != NULL; ) {
	    subvalue = (*pat != SUB_NEGATE) && (*pat != SUB_POISON) ?
	      0 : *pat;
	    if (subvalue)
		pat++;
	    if ((match != subvalue) && uwildmat(group, pat)) {
		if (subvalue == SUB_POISON)
		    return -1;
		match = subvalue;
	    }
	}
	return !match;
    }
    return 1;
}


/*
**  Create the channel.
*/
void
RCsetup(int i)
{
#if	defined(SO_REUSEADDR)
    int		on;
#endif	/* defined(SO_REUSEADDR) */
    int		j;
    CHANNEL	*rcchan;

    /* This code is called only when inndstart is not being used */
    if (i < 0) {
#ifdef HAVE_INET6
	syslog(L_FATAL, "%s innd MUST be started with inndstart", LogName);
	exit(1);
#else
	/* Create a socket and name it. */
	struct sockaddr_in	server;

	if ((i = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	    syslog(L_FATAL, "%s cant socket RCreader %m", LogName);
	    exit(1);
	}
#if	defined(SO_REUSEADDR)
	on = 1;
	if (setsockopt(i, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0)
	    syslog(L_ERROR, "%s cant setsockopt RCreader %m", LogName);
#endif	/* defined(SO_REUSEADDR) */
	memset(&server, 0, sizeof server);
	server.sin_port = htons(innconf->port);
	server.sin_family = AF_INET;
#ifdef HAVE_SOCKADDR_LEN
	server.sin_len = sizeof( struct sockaddr_in );
#endif
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	if (innconf->bindaddress) {
            if (!inet_aton(innconf->bindaddress, &server.sin_addr)) {
                syslog(L_FATAL, "unable to determine bind ip (%s) %m",
                       innconf->bindaddress);
		exit(1);
	    }
	}
	if (bind(i, (struct sockaddr *)&server, sizeof server) < 0) {
	    syslog(L_FATAL, "%s cant bind RCreader %m", LogName);
	    exit(1);
	}
#endif /* HAVE_INET6 */
    }

    /* Set it up to wait for connections. */
    if (listen(i, MAXLISTEN) < 0) {
	j = errno;
	syslog(L_FATAL, "%s cant listen RCreader %m", LogName);
	/* some IPv6 systems already listening on any address will 
	   return EADDRINUSE when trying to listen on the IPv4 socket */
	if (j == EADDRINUSE)
	   return;
	exit(1);
    }

    rcchan = CHANcreate(i, CTremconn, CSwaiting, RCreader, RCwritedone);
    syslog(L_NOTICE, "%s rcsetup %s", LogName, CHANname(rcchan));
    RCHANadd(rcchan);

    for (j = 0 ; j < chanlimit ; j++ ) {
	if (RCchan[j] == NULL) {
	    break;
	}
    }
    if (j < chanlimit) {
	RCchan[j] = rcchan;
    } else if (chanlimit == 0) {
	/* assuming two file descriptors(AF_INET and AF_INET6) */
	chanlimit = 2;
        RCchan = xmalloc(chanlimit * sizeof(CHANNEL **));
	for (j = 0 ; j < chanlimit ; j++ ) {
	    RCchan[j] = NULL;
	}
	RCchan[0] = rcchan;
    } else {
	/* extend to double size */
        RCchan = xrealloc(RCchan, chanlimit * 2 * sizeof(CHANNEL **));
	for (j = chanlimit ; j < chanlimit * 2 ; j++ ) {
	    RCchan[j] = NULL;
	}
	RCchan[chanlimit] = rcchan;
	chanlimit *= 2;
    }

    /* Get the list of hosts we handle. */
    RCreadlist();
}


/*
**  Cleanly shut down the channel.
*/
void
RCclose(void)
{
    REMOTEHOST	*rp;
    int		i;

    for (i = 0 ; i < chanlimit ; i++) {
	if (RCchan[i] != NULL) {
	    CHANclose(RCchan[i], CHANname(RCchan[i]));
	} else {
	    break;
	}
    }
    if (chanlimit != 0)
	free(RCchan);
    RCchan = NULL;
    chanlimit = 0;
    if (RCpeerlist) {
	for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
	    free(rp->Name);
	    free(rp->Label);
	    free(rp->Email);
	    free(rp->Password);
	    free(rp->Identd);
	    free(rp->Comment);
	    if (rp->Patterns) {
		free(rp->Patterns[0]);
		free(rp->Patterns);
	    }
	}
	free(RCpeerlist);
	RCpeerlist = NULL;
	RCnpeerlist = 0;
    }

    if (RCpeerlistfile) {
        for (i = 0; RCpeerlistfile[i].key != K_END; i++)
        if (RCpeerlistfile[i].value != NULL)
	   free(RCpeerlistfile[i].value);
	free(RCpeerlistfile);
	RCpeerlistfile = NULL;
    }
}
