/*
 * Usage: bbsnnrp [options] nntpserver activefile -h|? (help) -v (verbose
 * protocol transactions) -c (reset active files only; don't receive
 * articles) -r remotehost(send articles to remotehost, default=local) -p
 * port|(send articles to remotehost at port, default=7777) path(send
 * articles to local at path, default=~bbs/innd/.innbbsd) -n (don't ask
 * innbbsd server and stat articles) -w seconds (wait for seconds and run
 * infinitely, default=once)  -a max_art (maximum number of articles received
 * for a group each time) -s max_stat(maximum number of articles stated for a
 * group each time) -t stdin|nntp (default=nntp)
 */

#include "innbbsconf.h"
#include <sys/mman.h>
#ifndef AIX
#include <sys/fcntl.h>
#endif
#include "bbslib.h"
#include "daemon.h"
#include "nntp.h"

#ifndef MAX_ARTS
#define MAX_ARTS 100
#endif
#ifndef MAX_STATS
#define MAX_STATS 1000
#endif

#if  defined(__linux)
#define NO_USE_MMAP
#else
#define USE_MMAP
#endif

int             Max_Arts = MAX_ARTS;
int             Max_Stats = MAX_STATS;

typedef struct NEWSRC_T {
    char           *nameptr, *lowptr, *highptr, *modeptr;
    int             namelen, lowlen, highlen;
    ULONG           low, high;
    int             mode, subscribe;
}               newsrc_t;

typedef struct NNRP_T {
    int             nnrpfd;
    int             innbbsfd;
    FILE           *nnrpin, *nnrpout;
    FILE           *innbbsin, *innbbsout;
    char            activefile[MAXPATHLEN];
    char            rcfile[MAXPATHLEN];
    newsrc_t       *newsrc;
    char           *actpointer, *actend;
    int             actsize, actfd, actdirty;
}               nnrp_t;

typedef struct XHDR_T {
    char           *header;
    ULONG           artno;
}               xhdr_t;

xhdr_t          XHDR[MAX_ARTS];
char            LockFile[1024];

#define NNRPGroupOK NNTP_GROUPOK_VAL
#define NNRPXhdrOK NNTP_HEAD_FOLLOWS_VAL
#define NNRParticleOK NNTP_ARTICLE_FOLLOWS_VAL
#define INNBBSstatOK NNTP_NOTHING_FOLLOWS_VAL
#define INNBBSihaveOK NNTP_SENDIT_VAL
#define NNRPconnectOK NNTP_POSTOK_VAL
#define NNRPstatOK NNTP_NOTHING_FOLLOWS_VAL
#define INNBBSconnectOK NNTP_POSTOK_VAL

nnrp_t          BBSNNRP;

void 
doterm(s)
    int             s;
{
    printf("bbsnnrp terminated.  Signal %d\n", s);
    writerc(&BBSNNRP);
    if (isfile(LockFile))
	unlink(LockFile);
    exit(1);
}

extern char    *optarg;
extern int      opterr, optind;

#ifndef MIN_WAIT
#define MIN_WAIT 60
#endif

int             ResetActive = 0;
int             StatHistory = 1;
int             AskLocal = 1;
int             RunOnce = 1;

int             DefaultWait = MIN_WAIT;

char           *DefaultPort = DefaultINNBBSPort;
char           *DefaultPath = LOCALDAEMON;
char           *DefaultRemoteHost;

#ifndef MAXBUFLEN
#define MAXBUFLEN 256
#endif
char            DefaultNewsgroups[MAXBUFLEN];
char            DefaultOrganization[MAXBUFLEN];
char            DefaultModerator[MAXBUFLEN];
char            DefaultTrustfrom[MAXBUFLEN];
char            DefaultTrustFrom[MAXBUFLEN];

usage(arg)
    char           *arg;
{
    fprintf(stderr, "Usage: %s [options] nntpserver activefile\n", arg);
    fprintf(stderr, "       -h|? (help) \n");
    fprintf(stderr, "       -v (verbose protocol transactions)\n");
    fprintf(stderr, "       -c (reset active files only; don't receive articles)\n");
    fprintf(stderr, "       -r [proto:]remotehost\n");
    fprintf(stderr, "          (send articles to remotehost, default=ihave:local)\n");
    fprintf(stderr, "       -p port|(send articles to remotehost at port, default=%s)\n", DefaultINNBBSPort);
    fprintf(stderr, "          path(send articles to local at path, default=~bbs/innd/.innbbsd)\n");
    fprintf(stderr, "       -w seconds ( > 1 wait for seconds and run infinitely, default=once)\n");
    fprintf(stderr, "       -n (don't ask innbbsd server and stat articles)\n");
    fprintf(stderr, "       -a max_art(maximum number of articles received for a group each time)\n");
    fprintf(stderr, "          default=%d\n", MAX_ARTS);
    fprintf(stderr, "       -s max_stat(maximum number of articles stated for a group each time)\n");
    fprintf(stderr, "          default=%d\n", MAX_STATS);
    fprintf(stderr, "       -t stdin|nntp (default=nntp)\n");
    fprintf(stderr, "       -g newsgroups\n");
    fprintf(stderr, "       -m moderator\n");
    fprintf(stderr, "       -o organization\n");
    fprintf(stderr, "       -f trust_user (From: trust_user)\n");
    fprintf(stderr, "       -F trust_user (From trust_user)\n");
    fprintf(stderr, " Please E-mail bug to skhuang@csie.nctu.edu.tw or\n");
    fprintf(stderr, " post to tw.bbs.admin.installbbs\n");
}

static char    *StdinInputType = "stdin";
static char    *NntpInputType = "nntp";
static char    *NntpIhaveProtocol = "ihave";
static char    *NntpPostProtocol = "post";
static char    *DefaultNntpProtocol;
main(argc, argv)
    int             argc;
    char          **argv;
{
    char           *ptr, *server, *active;
    int             c, errflag = 0;
    int             lockfd;
    char           *inputtype;

    DefaultNntpProtocol = NntpIhaveProtocol;
    *DefaultNewsgroups = '\0';
    *DefaultModerator = '\0';
    *DefaultOrganization = '\0';
    *DefaultTrustFrom = '\0';
    *DefaultTrustfrom = '\0';
    inputtype = NntpInputType;
    while ((c = getopt(argc, argv, "f:F:m:o:g:w:r:p:a:s:t:h?ncv")) != -1)
	switch (c) {
	case 'v':
	    verboseon("bbsnnrp.log");
	    break;
	case 'c':
	    ResetActive = 1;
	    break;
	case 'g':
	    strncpy(DefaultNewsgroups, optarg, sizeof DefaultNewsgroups);
	    break;
	case 'm':
	    strncpy(DefaultModerator, optarg, sizeof DefaultModerator);
	    break;
	case 'o':
	    strncpy(DefaultOrganization, optarg, sizeof DefaultOrganization);
	    break;
	case 'f':
	    strncpy(DefaultTrustfrom, optarg, sizeof DefaultTrustfrom);
	    break;
	case 'F':
	    strncpy(DefaultTrustFrom, optarg, sizeof DefaultTrustFrom);
	    break;
	case 'r':{
		char           *hostptr;
		AskLocal = 0;
		DefaultRemoteHost = optarg;
		if ((hostptr = strchr(optarg, ':')) != NULL) {
		    *hostptr = '\0';
		    DefaultRemoteHost = hostptr + 1;
		    if (strcasecmp(optarg, "post") == 0)
			DefaultNntpProtocol = NntpPostProtocol;
		    *hostptr = ':';
		}
		break;
	    }
	case 'w':
	    RunOnce = 0;
	    DefaultWait = atoi(optarg);
	    if (DefaultWait < MIN_WAIT)
		DefaultWait = MIN_WAIT;
	    break;
	case 'p':
	    if (AskLocal == 0) {
		DefaultPort = optarg;
	    } else {
		DefaultPath = optarg;
	    }
	    break;
	case 'n':
	    StatHistory = 0;
	    break;
	case 'a':
	    Max_Arts = atol(optarg);
	    if (Max_Arts < 0)
		Max_Arts = 0;
	    break;
	case 's':
	    Max_Stats = atol(optarg);
	    if (Max_Stats < 0)
		Max_Stats = 0;
	    break;
	case 't':
	    if (strcasecmp(optarg, StdinInputType) == 0) {
		inputtype = StdinInputType;
	    }
	    break;
	case 'h':
	case '?':
	default:
	    errflag++;
	}
    if (errflag > 0) {
	usage(argv[0]);
	return (1);
    }
    if (inputtype == NntpInputType && argc - optind < 2) {
	usage(argv[0]);
	exit(1);
    }
    if (inputtype == NntpInputType) {
	server = argv[optind];
	active = argv[optind + 1];
	if (isfile(active)) {
	    strncpy(BBSNNRP.activefile, active, sizeof BBSNNRP.activefile);
	} else if (strchr(active, '/') == NULL) {
	    sprintf(BBSNNRP.activefile, "%s/innd/%.*s", BBSHOME, sizeof BBSNNRP.activefile - 7 - strlen(BBSHOME), active);
	} else {
	    strncpy(BBSNNRP.activefile, active, sizeof BBSNNRP.activefile);
	}

	strncpy(LockFile, (char *)fileglue("%s.lock", active), sizeof LockFile);
	if ((lockfd = open(LockFile, O_RDONLY)) >= 0) {
	    char            buf[10];
	    int             pid;

	    if (read(lockfd, buf, sizeof buf) > 0 && (pid = atoi(buf)) > 0 && kill(pid, 0) == 0) {
		fprintf(stderr, "another process [%d] running\n", pid);
		exit(1);
	    } else {
		fprintf(stderr, "no process [%d] running, but lock file existed, unlinked\n", pid);
		unlink(LockFile);
	    }
	    close(lockfd);
	}
	if ((lockfd = open(LockFile, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0) {
	    fprintf(stderr, "maybe another %s process running\n", argv[0]);
	    exit(1);
	} else {
	    char            buf[10];
	    int             pid;
	    sprintf(buf, "%-.8d\n", getpid());
	    write(lockfd, buf, strlen(buf));
	    close(lockfd);
	}
	for (;;) {
	    if (!initial_bbs(NULL)) {
		fprintf(stderr, "Initial BBS failed\n");
		exit(1);
	    }
	    initsockets(server, &BBSNNRP, inputtype);
	    ptr = (char *)strrchr(active, '/');
	    if (ptr != NULL)
		ptr++;
	    else
		ptr = active;
	    sprintf(BBSNNRP.rcfile, "%s/.newsrc.%s.%s", INNDHOME, server, ptr);
	    initrcfiles(&BBSNNRP);

	    signal(SIGTERM, doterm);
	    signal(SIGKILL, doterm);
	    signal(SIGHUP, doterm);
	    signal(SIGPIPE, doterm);

	    readnews(server, &BBSNNRP);
	    writerc(&BBSNNRP);
	    closesockets();

	    if (RunOnce)
		break;
	    sleep(DefaultWait);
	}
	unlink(LockFile);
    }
     /* NntpInputType */ 
    else {
	if (!initial_bbs(NULL)) {
	    fprintf(stderr, "Initial BBS failed\n");
	    exit(1);
	}
	initsockets(server, &BBSNNRP, inputtype);
	signal(SIGTERM, doterm);
	signal(SIGKILL, doterm);
	signal(SIGHUP, doterm);
	signal(SIGPIPE, doterm);

	stdinreadnews(&BBSNNRP);
	closesockets();
    }				/* stdin input type */
    return 0;
}

headbegin(buffer)
    char           *buffer;
{
    if (strncmp(buffer, "Path: ", 6) == 0) {
	if (strchr(buffer + 6, '!') != NULL)
	    return 1;
    }
    if (strncmp(buffer, "From ", 5) == 0) {
	if (strchr(buffer + 5, ':') != NULL)
	    return 1;
    }
    return 0;
}

stdinreadnews(bbsnnrp)
    nnrp_t         *bbsnnrp;
{
    int             i;
    char            buffer[4096];
    ULONG           low, high;
    char            tmpfilename[MAXPATHLEN];
    FILE           *tmpfp = NULL;
    char            mid[1024];
    int             pathagain;
    int             ngmet, submet, midmet, pathmet, orgmet, approvedmet;
    int             discard;
    char            sending_path[MAXPATHLEN];
    int             sending_path_len = 0;

    strncpy(tmpfilename, (char *)fileglue("/tmp/bbsnnrp-stdin-%d-%d", getuid(), getpid()), sizeof tmpfilename);
    fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
    verboselog("innbbsGet: %s", buffer);
    if (atoi(buffer) != INNBBSconnectOK) {
	fprintf(stderr, "INNBBS server not OK\n");
	return;
    }
    if (DefaultNntpProtocol == NntpPostProtocol) {
	fputs("MODE READER\r\n", bbsnnrp->innbbsout);
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: MODE READER\n");
	fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
	verboselog("innbbsGet: %s", buffer);
    }
    if (StatHistory == 0) {
	fputs("MIDCHECK OFF\r\n", bbsnnrp->innbbsout);
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: MIDCHECK OFF\n");
	fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
	verboselog("innbbsGet: %s", buffer);
    }
    tmpfp = fopen(tmpfilename, "w");
    if (tmpfp == NULL)
	return;
    *mid = '\0';
    for (;;) {
	fprintf(stderr, "Try to read from stdin ...\n");
	ngmet = 0, submet = 0, midmet = 0, pathmet = 0, orgmet = 0, approvedmet = 0;
	discard = 0;
	while (fgets(buffer, sizeof buffer, stdin) != NULL) {
	    char           *tmpptr;
	    tmpptr = strchr(buffer, '\n');
	    if (tmpptr != NULL)
		*tmpptr = '\0';
	    if (strncasecmp(buffer, "Message-ID: ", 12) == 0) {
		strncpy(mid, buffer + 12, sizeof mid);
		midmet = 1;
	    } else if (strncmp(buffer, "Subject: ", 9) == 0) {
		submet = 1;
	    } else if (strncmp(buffer, "Path: ", 6) == 0) {
		pathmet = 1;
	    } else if (strncmp(buffer, "Organization: ", 14) == 0) {
		orgmet = 1;
	    } else if (strncmp(buffer, "Approved: ", 10) == 0) {
		approvedmet = 1;
	    } else if (strncmp(buffer, "From: ", 6) == 0 && *DefaultTrustfrom) {
		if (strstr(buffer + 6, DefaultTrustfrom) == NULL) {
		    discard = 1;
		    verboselog("Discard: %s for %s", buffer, DefaultTrustfrom);
		}
	    } else if (strncmp(buffer, "From ", 5) == 0 && *DefaultTrustFrom) {
		if (strstr(buffer + 5, DefaultTrustFrom) == NULL) {
		    discard = 1;
		    verboselog("Discard: %s for %s", buffer, DefaultTrustFrom);
		}
	    } else if (strncmp(buffer, "Received: ", 10) == 0) {
		char           *rptr = buffer + 10, *rrptr;
		int             savech, len;
		if (strncmp(buffer + 10, "from ", 5) == 0) {
		    rptr += 5;
		    rrptr = strchr(rptr, '(');
		    if (rrptr != NULL)
			rptr = rrptr + 1;
		    rrptr = strchr(rptr, ' ');
		    savech = *rrptr;
		    if (rrptr != NULL)
			*rrptr = '\0';
		} else if (strncmp(buffer + 10, "(from ", 6) == 0) {
		    rptr += 6;
		    rrptr = strchr(rptr, ')');
		    savech = *rrptr;
		    if (rrptr != NULL)
			*rrptr = '\0';
		}
		len = strlen(rptr) + 1;
		if (*rptr && sending_path_len + len < sizeof(sending_path)) {
		    if (*sending_path)
			strcat(sending_path, "!");
		    strcat(sending_path, rptr);
		    sending_path_len += len;
		}
		if (rrptr != NULL)
		    *rrptr = savech;
	    }
	    if (strncmp(buffer, "Newsgroups: ", 12) == 0) {
		if (*DefaultNewsgroups) {
		    fprintf(tmpfp, "Newsgroups: %s\r\n", DefaultNewsgroups);
		} else {
		    fprintf(tmpfp, "%s\r\n", buffer);
		}
		ngmet = 1;
	    } else {
		if (buffer[0] == '\0') {
		    if (!ngmet && *DefaultNewsgroups) {
			fprintf(tmpfp, "Newsgroups: %s\r\n", DefaultNewsgroups);
		    }
		    if (!submet) {
			fprintf(tmpfp, "Subject: (no subject)\r\n");
		    }
		    if (!pathmet) {
			fprintf(tmpfp, "Path: from-mail\r\n");
		    }
		    if (!midmet) {
			static int      seed;
			time_t          now;
			time(&now);
			fprintf(tmpfp, "Message-ID: <%d@%d.%d.%d>\r\n", now, getpid(), getuid(), seed);
			sprintf(mid, "<%d@%d.%d.%d>", now, getpid(), getuid(), seed);
			seed++;
		    }
		    if (!orgmet && *DefaultOrganization) {
			fprintf(tmpfp, "Organization: %s\r\n", DefaultOrganization);
		    }
		    if (!approvedmet && *DefaultModerator) {
			fprintf(tmpfp, "Approved: %s\r\n", DefaultModerator);
		    }
		}
		if (strncmp(buffer, "From ", 5) != 0 && strncmp(buffer, "To: ", 4) != 0) {
		    if (buffer[0] == '\0') {
			if (*sending_path) {
			    fprintf(tmpfp, "X-Sending-Path: %s\r\n", sending_path);
			}
		    }
		    fprintf(tmpfp, "%s\r\n", buffer);
		}
	    }
	    if (buffer[0] == '\0')
		break;
	}
	fprintf(stderr, "Article Body begin ...\n");
	pathagain = 0;
	while (fgets(buffer, sizeof buffer, stdin) != NULL) {
	    char           *tmpptr;
	    tmpptr = strchr(buffer, '\n');
	    if (tmpptr != NULL)
		*tmpptr = '\0';
	    if (headbegin(buffer)) {
		FILE           *oldfp = bbsnnrp->nnrpin;
		pathagain = 1;
		fputs(".\r\n", tmpfp);
		fclose(tmpfp);
		fprintf(stderr, "Try to post ...\n");
		tmpfp = fopen(tmpfilename, "r");
		bbsnnrp->nnrpin = tmpfp;
		if (!discard)
		    if (INNBBSihave(bbsnnrp, -1, mid) == -1) {
			fprintf(stderr, "post failed\n");
		    }
		bbsnnrp->nnrpin = oldfp;
		fclose(tmpfp);
		*mid = '\0';
		tmpfp = fopen(tmpfilename, "w");
		fprintf(tmpfp, "%s\r\n", buffer);
		break;
	    } else {
		fprintf(tmpfp, "%s\r\n", buffer);
	    }
	}
	if (!pathagain)
	    break;
    }
    if (!pathagain && tmpfp) {
	FILE           *oldfp = bbsnnrp->nnrpin;
	fputs(".\r\n", tmpfp);
	fclose(tmpfp);
	fprintf(stderr, "Try to post ...\n");
	tmpfp = fopen(tmpfilename, "r");
	bbsnnrp->nnrpin = tmpfp;
	if (!discard)
	    if (INNBBSihave(bbsnnrp, -1, mid) == -1) {
		fprintf(stderr, "post failed\n");
	    }
	bbsnnrp->nnrpin = oldfp;
	fclose(tmpfp);
    }
    if (isfile(tmpfilename)) {
	unlink(tmpfilename);
    }
}

static char    *ACT_BUF, *RC_BUF;
int             ACT_COUNT;

initrcfiles(bbsnnrp)
    nnrp_t         *bbsnnrp;
{
    FILE           *actfp, *rcfp;
    char            buff[1024];
    int             actfd, i, count, actcount = 0, rcount = 0, maxcount;
    struct stat     st;
    char           *actlistptr, *ptr;

    actfd = open(bbsnnrp->activefile, O_RDWR);
    if (actfd < 0) {
	fprintf(stderr, "can't read/write %s\n", bbsnnrp->activefile);
	exit(1);
    }
    if (fstat(actfd, &st) != 0) {
	fprintf(stderr, "can't stat %s\n", bbsnnrp->activefile);
	exit(1);
    }
    bbsnnrp->actfd = actfd;
    bbsnnrp->actsize = st.st_size;
#ifdef USE_MMAP
    bbsnnrp->actpointer = mmap(0, st.st_size, PROT_WRITE | PROT_READ,
			       MAP_SHARED, actfd, 0);
    if (bbsnnrp->actpointer == (char *)-1) {
	fprintf(stderr, "mmap error \n");
	exit(1);
    }
#else
    if (bbsnnrp->actpointer == NULL) {
	bbsnnrp->actpointer = (char *)mymalloc(st.st_size);
    } else {
	bbsnnrp->actpointer = (char *)myrealloc(bbsnnrp->actpointer, st.st_size);
    }
    if (bbsnnrp->actpointer == NULL || read(actfd, bbsnnrp->actpointer, st.st_size) <= 0) {
	fprintf(stderr, "read act error \n");
	exit(1);
    }
#endif
    bbsnnrp->actend = bbsnnrp->actpointer + st.st_size;
    i = 0, count = 0;
    for (ptr = bbsnnrp->actpointer; ptr < bbsnnrp->actend && (actlistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = actlistptr + 1, ACT_COUNT++) {
	if (*ptr == '\n')
	    continue;
	if (*ptr == '#')
	    continue;
	count++;
    }
    bbsnnrp->newsrc = (newsrc_t *) mymalloc(sizeof(newsrc_t) * count);
    ACT_COUNT = 0;
    for (ptr = bbsnnrp->actpointer; ptr < bbsnnrp->actend && (actlistptr = (char *)strchr(ptr, '\n')) != NULL; ptr = actlistptr + 1) {
	register newsrc_t *rcptr;
	char           *nptr;
	/**actlistptr = '\0';*/
	if (*ptr == '\n')
	    continue;
	if (*ptr == '#')
	    continue;
	rcptr = &bbsnnrp->newsrc[ACT_COUNT];
	rcptr->nameptr = NULL;
	rcptr->namelen = 0;
	rcptr->lowptr = NULL;
	rcptr->lowlen = 0;
	rcptr->highptr = NULL;
	rcptr->highlen = 0;
	rcptr->modeptr = NULL;
	rcptr->low = 0;
	rcptr->high = 0;
	rcptr->mode = 'y';
	for (nptr = ptr; *nptr && isspace(*nptr);)
	    nptr++;
	if (nptr == actlistptr)
	    continue;
	rcptr->nameptr = nptr;
	for (nptr++; *nptr && !isspace(*nptr);)
	    nptr++;
	rcptr->namelen = (int)(nptr - rcptr->nameptr);
	if (nptr == actlistptr)
	    continue;
	for (nptr++; *nptr && isspace(*nptr);)
	    nptr++;
	if (nptr == actlistptr)
	    continue;
	rcptr->highptr = nptr;
	rcptr->high = atol(nptr);
	for (nptr++; *nptr && !isspace(*nptr);)
	    nptr++;
	rcptr->highlen = (int)(nptr - rcptr->highptr);
	if (nptr == actlistptr)
	    continue;
	for (nptr++; *nptr && isspace(*nptr);)
	    nptr++;
	if (nptr == actlistptr)
	    continue;
	rcptr->lowptr = nptr;
	rcptr->low = atol(nptr);
	for (nptr++; *nptr && !isspace(*nptr);)
	    nptr++;
	rcptr->lowlen = (int)(nptr - rcptr->lowptr);
	if (nptr == actlistptr)
	    continue;
	for (nptr++; *nptr && isspace(*nptr);)
	    nptr++;
	if (nptr == actlistptr)
	    continue;
	rcptr->mode = *nptr;
	rcptr->modeptr = nptr;
	ACT_COUNT++;
    }
}

initsockets(server, bbsnnrp, type)
    char           *server;
    nnrp_t         *bbsnnrp;
    char           *type;
{
    int             nnrpfd;
    int             innbbsfd;
    if (AskLocal) {
	innbbsfd = unixclient(DefaultPath, "tcp");
	if (innbbsfd < 0) {
	    fprintf(stderr, "Connect to %s error. You may not run innbbsd\n", LOCALDAEMON);
	    /*
	     * unix connect fail, may run by inetd, try to connect to local
	     * once
	     */
	    innbbsfd = inetclient("localhost", DefaultPort, "tcp");
	    if (innbbsfd < 0) {
		exit(2);
	    }
	    close(innbbsfd);
	    /* try again */
	    innbbsfd = unixclient(DefaultPath, "tcp");
	    if (innbbsfd < 0) {
		exit(3);
	    }
	}
	verboselog("INNBBS connect to %s\n", DefaultPath);
    } else {
	innbbsfd = inetclient(DefaultRemoteHost, DefaultPort, "tcp");
	if (innbbsfd < 0) {
	    fprintf(stderr, "Connect to %s at %s error. Remote Server not Ready\n", DefaultRemoteHost, DefaultPort);
	    exit(2);
	}
	verboselog("INNBBS connect to %s\n", DefaultRemoteHost);
    }
    if (type == StdinInputType) {
	bbsnnrp->nnrpfd = 0;
	bbsnnrp->innbbsfd = innbbsfd;
	if ((bbsnnrp->nnrpin = fdopen(0, "r")) == NULL ||
	    (bbsnnrp->nnrpout = fdopen(1, "w")) == NULL ||
	    (bbsnnrp->innbbsin = fdopen(innbbsfd, "r")) == NULL ||
	    (bbsnnrp->innbbsout = fdopen(innbbsfd, "w")) == NULL) {
	    fprintf(stderr, "fdopen error\n");
	    exit(3);
	}
	return;
    }
    nnrpfd = inetclient(server, "nntp", "tcp");
    if (nnrpfd < 0) {
	fprintf(stderr, " connect to %s error \n", server);
	exit(2);
    }
    verboselog("NNRP connect to %s\n", server);
    bbsnnrp->nnrpfd = nnrpfd;
    bbsnnrp->innbbsfd = innbbsfd;
    if ((bbsnnrp->nnrpin = fdopen(nnrpfd, "r")) == NULL ||
	(bbsnnrp->nnrpout = fdopen(nnrpfd, "w")) == NULL ||
	(bbsnnrp->innbbsin = fdopen(innbbsfd, "r")) == NULL ||
	(bbsnnrp->innbbsout = fdopen(innbbsfd, "w")) == NULL) {
	fprintf(stderr, "fdopen error\n");
	exit(3);
    }
}

closesockets()
{
    fclose(BBSNNRP.nnrpin);
    fclose(BBSNNRP.nnrpout);
    fclose(BBSNNRP.innbbsin);
    fclose(BBSNNRP.innbbsout);
    close(BBSNNRP.nnrpfd);
    close(BBSNNRP.innbbsfd);
}

updaterc(actptr, len, value)
    char           *actptr;
    int             len;
    ULONG           value;
{
    for (actptr += len - 1; len-- > 0;) {
	*actptr-- = value % 10 + '0';
	value /= 10;
    }
}

/*
 * if old file is empty, don't need to update prevent from disk full
 */
int
myrename(old, new)
    char           *old, *new;
{
    struct stat     st;
    if (stat(old, &st) != 0)
	return -1;
    if (st.st_size <= 0)
	return -1;
    return rename(old, new);
}

flushrc(bbsnnrp)
    nnrp_t         *bbsnnrp;
{
    int             backfd;
    char           *bak1;
    if (bbsnnrp->actdirty == 0)
	return;
    bak1 = (char *)strdup((char *)fileglue("%s.BAK", bbsnnrp->activefile));
    if (isfile(bak1)) {
	myrename(bak1, (char *)fileglue("%s.BAK.OLD", bbsnnrp->activefile));
    }
#ifdef USE_MMAP
    if ((backfd = open((char *)fileglue("%s.BAK", bbsnnrp->activefile), O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize)
#else
    myrename(bbsnnrp->activefile, bak1);
    if ((backfd = open(bbsnnrp->activefile, O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize)
#endif
    {
	char            emergent[128];
	sprintf(emergent, "/tmp/bbsnnrp.%d.active", getpid());
	fprintf(stderr, "write to backup active fail. Maybe disk full\n");
	fprintf(stderr, "try to write in %s\n", emergent);
	if ((backfd = open(emergent, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0 || write(backfd, bbsnnrp->actpointer, bbsnnrp->actsize) < bbsnnrp->actsize) {
	    fprintf(stderr, "write to %sfail.\n", emergent);
	} else {
	    close(backfd);
	}
	/* if write fail, should leave */
	/* exit(1); */
    } else {
	close(backfd);
    }
    free(bak1);
    bbsnnrp->actdirty = 0;
}

writerc(bbsnnrp)
    nnrp_t         *bbsnnrp;
{
    if (bbsnnrp->actpointer) {
	flushrc(bbsnnrp);
#ifdef USE_MMAP
	if (munmap(bbsnnrp->actpointer, bbsnnrp->actsize) < 0)
	    fprintf(stderr, "can't unmap\n");
	/* free(bbsnnrp->actpointer); */
	bbsnnrp->actpointer = NULL;
#endif
	if (close(bbsnnrp->actfd) < 0)
	    fprintf(stderr, "can't close actfd\n");
    }
}

static FILE    *Xhdrfp;
static char     NNRPbuffer[4096];
static char     INNBBSbuffer[4096];

char           *
NNRPgets(string, len, fp)
    char           *string;
    int             len;
    FILE           *fp;
{
    char           *re = fgets(string, len, fp);
    char           *ptr;
    if (re != NULL) {
	if ((ptr = (char *)strchr(string, '\r')) != NULL)
	    *ptr = '\0';
	if ((ptr = (char *)strchr(string, '\n')) != NULL)
	    *ptr = '\0';
    }
    return re;
}

int 
NNRPstat(bbsnnrp, artno, mid)
    nnrp_t         *bbsnnrp;
    ULONG           artno;
    char          **mid;
{
    char           *ptr;
    int             code;

    *mid = NULL;
    fprintf(bbsnnrp->nnrpout, "STAT %d\r\n", artno);
    fflush(bbsnnrp->nnrpout);
    verboselog("nnrpPut: STAT %d\n", artno);
    NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
    verboselog("nnrpGet: %s\n", NNRPbuffer);

    ptr = (char *)strchr(NNRPbuffer, ' ');
    if (ptr != NULL)
	*ptr++ = '\0';
    code = atoi(NNRPbuffer);
    ptr = (char *)strchr(ptr, ' ');
    if (ptr != NULL)
	*ptr++ = '\0';
    *mid = ptr;
    ptr = (char *)strchr(ptr, ' ');
    if (ptr != NULL)
	*ptr++ = '\0';
    return code;
}

int
NNRPxhdr(pattern, bbsnnrp, i, low, high)
    char           *pattern;
    nnrp_t         *bbsnnrp;
    int             i;
    ULONG           low, high;
{
    newsrc_t       *rcptr = &bbsnnrp->newsrc[i];
    int             size, code;

    Xhdrfp = bbsnnrp->nnrpin;
    fprintf(bbsnnrp->nnrpout, "XHDR %s %d-%d\r\n", pattern, low, high);
#ifdef BBSNNRPDEBUG
    printf("XHDR %s %d-%d\r\n", pattern, low, high);
#endif
    fflush(bbsnnrp->nnrpout);
    verboselog("nnrpPut: XHDR %s %d-%d\n", pattern, low, high);
    NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
    verboselog("nnrpGet: %s\n", NNRPbuffer);
    code = atoi(NNRPbuffer);
    return code;
}

int 
NNRPxhdrget(artno, mid, iscontrol)
    int            *artno;
    char          **mid;
    int             iscontrol;
{
    *mid = NULL;
    *artno = 0;
    if (NNRPgets(NNRPbuffer, sizeof NNRPbuffer, Xhdrfp) == NULL)
	return 0;
    else {
	char           *ptr, *s;
	if (strcmp(NNRPbuffer, ".") == 0)
	    return 0;
	ptr = (char *)strchr(NNRPbuffer, ' ');
	if (!ptr)
	    return 1;
	*ptr++ = '\0';
	*artno = atol(NNRPbuffer);
	if (iscontrol) {
	    ptr = (char *)strchr(s = ptr, ' ');
	    if (!ptr)
		return 1;
	    *ptr++ = '\0';
	    if (strcmp(s, "cancel") != 0)
		return 1;
	}
	*mid = ptr;
	return 1;
    }
}

int 
INNBBSstat(bbsnnrp, i, mid)
    nnrp_t         *bbsnnrp;
    int             i;
    char           *mid;
{
    newsrc_t       *rcptr = &bbsnnrp->newsrc[i];
    int             size, code;

    fprintf(bbsnnrp->innbbsout, "STAT %s\r\n", mid);
    fflush(bbsnnrp->innbbsout);
    verboselog("innbbsPut: STAT %s\n", mid);
    NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);
    verboselog("innbbsGet: %s\n", INNBBSbuffer);
    return atol(INNBBSbuffer);
}

int 
INNBBSihave(bbsnnrp, artno, mid)
    nnrp_t         *bbsnnrp;
    ULONG           artno;
    char           *mid;
{
    int             size, code;
    int             header = 1;

    if (DefaultNntpProtocol == NntpPostProtocol) {
	fprintf(bbsnnrp->innbbsout, "POST\r\n");
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: POST %s\n", mid);
    } else {
	fprintf(bbsnnrp->innbbsout, "IHAVE %s\r\n", mid);
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: IHAVE %s\n", mid);
    }
    if (NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin) == NULL) {
	return -1;
    }
    verboselog("innbbsGet: %s\n", INNBBSbuffer);
#ifdef BBSNNRPDEBUG
    printf("ihave got %s\n", INNBBSbuffer);
#endif

    if (DefaultNntpProtocol == NntpPostProtocol) {
	if ((code = atol(INNBBSbuffer)) != NNTP_START_POST_VAL) {
	    if (code == NNTP_POSTFAIL_VAL)
		return 0;
	    else
		return -1;
	}
    } else {
	if ((code = atol(INNBBSbuffer)) != INNBBSihaveOK) {
	    if (code == 435 || code == 437)
		return 0;
	    else
		return -1;
	}
    }
    if (artno != -1) {
	fprintf(bbsnnrp->nnrpout, "ARTICLE %d\r\n", artno);
	verboselog("nnrpPut: ARTICLE %d\n", artno);
#ifdef BBSNNRPDEBUG
	printf("ARTICLE %d\r\n", artno);
#endif
	fflush(bbsnnrp->nnrpout);
	if (NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin) == NULL) {
	    return -1;
	}
	verboselog("nnrpGet: %s\n", NNRPbuffer);
#ifdef BBSNNRPDEBUG
	printf("article got %s\n", NNRPbuffer);
#endif
	if (atol(NNRPbuffer) != NNRParticleOK) {
	    fputs(".\r\n", bbsnnrp->innbbsout);
	    fflush(bbsnnrp->innbbsout);
	    NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);
	    verboselog("innbbsGet: %s\n", INNBBSbuffer);
	    return 0;
	}
    }
    header = 1;
    while (fgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin) != NULL) {
	if (strcmp(NNRPbuffer, "\r\n") == 0)
	    header = 0;
	if (strcmp(NNRPbuffer, ".\r\n") == 0) {
	    verboselog("nnrpGet: .\n");
	    fputs(NNRPbuffer, bbsnnrp->innbbsout);
	    fflush(bbsnnrp->innbbsout);
	    verboselog("innbbsPut: .\n");
	    if (NNRPgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin) == NULL)
		return -1;
	    verboselog("innbbsGet: %s\n", INNBBSbuffer);
#ifdef BBSNNRPDEBUG
	    printf("end ihave got %s\n", INNBBSbuffer);
#endif
	    code = atol(INNBBSbuffer);
	    if (DefaultNntpProtocol == NntpPostProtocol) {
		if (code == NNTP_POSTEDOK_VAL)
		    return 1;
		if (code == NNTP_POSTFAIL_VAL)
		    return 0;
	    } else {
		if (code == 235)
		    return 1;
		if (code == 437 || code == 435)
		    return 0;
	    }
	    break;
	}
	if (DefaultNntpProtocol == NntpPostProtocol &&
	header && strncasecmp(NNRPbuffer, "NNTP-Posting-Host: ", 19) == 0) {
	    fprintf(bbsnnrp->innbbsout, "X-%s", NNRPbuffer);
	} else {
	    fputs(NNRPbuffer, bbsnnrp->innbbsout);
	}
    }
    fflush(bbsnnrp->innbbsout);
    return -1;
}

int
NNRPgroup(bbsnnrp, i, low, high)
    nnrp_t         *bbsnnrp;
    int             i;
    ULONG          *low, *high;
{
    newsrc_t       *rcptr = &bbsnnrp->newsrc[i];
    int             size, code;
    ULONG           tmp;

    fprintf(bbsnnrp->nnrpout, "GROUP %-.*s\r\n",
	    rcptr->namelen, rcptr->nameptr);
    printf("GROUP %-.*s\r\n", rcptr->namelen, rcptr->nameptr);
    verboselog("nnrpPut: GROUP %-.*s\n", rcptr->namelen, rcptr->nameptr);
    fflush(bbsnnrp->nnrpout);
    NNRPgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
    verboselog("nnrpGet: %s\n", NNRPbuffer);
    printf("%s\n", NNRPbuffer);
    sscanf(NNRPbuffer, "%d %d %ld %ld", &code, &size, low, high);
    if (*low > *high) {
	tmp = *low;
	*low = *high;
	*high = tmp;
    }
    return code;
}


readnews(server, bbsnnrp)
    char           *server;
    nnrp_t         *bbsnnrp;
{
    int             i;
    char            buffer[4096];
    ULONG           low, high;

    fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
    verboselog("innbbsGet: %s", buffer);
    if (atoi(buffer) != INNBBSconnectOK) {
	fprintf(stderr, "INNBBS server not OK\n");
	return;
    }
#ifdef BBSNNRPDEBUG
    printf("%s", buffer);
#endif
    fgets(buffer, sizeof buffer, bbsnnrp->nnrpin);
    verboselog("nnrpGet: %s", buffer);
    if (buffer[0] != '2') {
	/*
	 * if (atoi(buffer) != NNRPconnectOK && atoi(buffer) !=
	 * NNTP_NOPOSTOK_VAL) {
	 */
	fprintf(stderr, "NNRP server not OK\n");
	return;
    }
#ifdef BBSNNRPDEBUG
    printf("%s", buffer);
#endif
    fputs("MODE READER\r\n", bbsnnrp->nnrpout);
    fflush(bbsnnrp->nnrpout);
    verboselog("nnrpPut: MODE READER\n");
    fgets(buffer, sizeof buffer, bbsnnrp->nnrpin);
    verboselog("nnrpGet: %s", buffer);

    if (DefaultNntpProtocol == NntpPostProtocol) {
	fputs("MODE READER\r\n", bbsnnrp->innbbsout);
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: MODE READER\n");
	fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
	verboselog("innbbsGet: %s", buffer);
    }
#ifdef BBSNNRPDEBUG
    printf("%s", buffer);
#endif

    if (StatHistory == 0) {
	fputs("MIDCHECK OFF\r\n", bbsnnrp->innbbsout);
	fflush(bbsnnrp->innbbsout);
	verboselog("innbbsPut: MIDCHECK OFF\n");
	fgets(buffer, sizeof buffer, bbsnnrp->innbbsin);
	verboselog("innbbsGet: %s", buffer);
    }
    bbsnnrp->actdirty = 0;
    for (i = 0; i < ACT_COUNT; i++) {
	int             code = NNRPgroup(bbsnnrp, i, &low, &high);
	newsrc_t       *rcptr = &bbsnnrp->newsrc[i];
	int             j;
	ULONG           artno;
	char           *mid;
	int             artcount;

#ifdef BBSNNRPDEBUG
	printf("got reply %d %ld %ld\n", code, low, high);
#endif
	artcount = 0;
	if (code == 411) {
	    FILE           *ff = fopen(BBSHOME "/innd/log/badgroup.log", "a");
	    fprintf(ff, "%s\t%-.*s\r\n", server, rcptr->namelen, rcptr->nameptr);
	    fclose(ff);
	} else if (code == NNRPGroupOK) {
	    int             xcount;
	    ULONG           maxartno = rcptr->high;
	    int             isCancelControl = (strncmp(rcptr->nameptr, "control", rcptr->namelen) == 0)
	    ||
	    (strncmp(rcptr->nameptr, "control.cancel", rcptr->namelen) == 0);

	    /* less than or equal to high, for server renumber */
	    if (rcptr->low != low) {
		bbsnnrp->actdirty = 1;
		rcptr->low = low;
		updaterc(rcptr->lowptr, rcptr->lowlen, rcptr->low);
	    }
	    if (ResetActive) {
		if (rcptr->high != high) {
		    bbsnnrp->actdirty = 1;
		    rcptr->high = high;
		    updaterc(rcptr->highptr, rcptr->highlen, rcptr->high);
		}
	    } else if (rcptr->high < high) {
		int             xhdrcode;
		ULONG           maxget = high;
		int             exception = 0;
		if (rcptr->high < low) {
		    bbsnnrp->actdirty = 1;
		    rcptr->high = low;
		    updaterc(rcptr->highptr, rcptr->highlen, low);
		}
		if (high > rcptr->high + Max_Stats) {
		    maxget = rcptr->high + Max_Stats;
		}
		if (isCancelControl)
		    xhdrcode = NNRPxhdr("Control", bbsnnrp, i, rcptr->high + 1, maxget);
		else
		    xhdrcode = NNRPxhdr("Message-ID", bbsnnrp, i, rcptr->high + 1, maxget);

		maxartno = maxget;
		if (xhdrcode == NNRPXhdrOK) {
		    while (NNRPxhdrget(&artno, &mid, isCancelControl)) {
			/* #define DEBUG */
#ifdef DEBUG
			printf("no %d id %s\n", artno, mid);
#endif
			if (artcount < Max_Arts) {
			    if (mid != NULL && !isCancelControl) {
				if (!StatHistory || INNBBSstat(bbsnnrp, i, mid) != INNBBSstatOK) {
				    printf("** %d ** %d need it %s\n", artcount, artno, mid);
				    XHDR[artcount].artno = artno;
				    XHDR[artcount].header = restrdup(XHDR[artcount].header, mid);
				    /* INNBBSihave(bbsnnrp,i,artno,mid); */
				    /* to get it */
				    artcount++;
				}
			    } else if (mid != NULL) {
				if (INNBBSstat(bbsnnrp, i, mid) == INNBBSstatOK) {
				    printf("** %d ** %d need cancel %s\n", artcount, artno, mid);
				    XHDR[artcount].artno = artno;
				    XHDR[artcount].header = restrdup(XHDR[artcount].header, mid);
				    artcount++;
				}
			    }
			    maxartno = artno;
			}
		    }
		}		/* while xhdr OK */
		exception = 0;
		for (xcount = 0; xcount < artcount; xcount++) {
		    ULONG           artno;
		    char           *mid;
		    artno = XHDR[xcount].artno;
		    mid = XHDR[xcount].header;
		    if (isCancelControl) {
			if (NNRPstat(bbsnnrp, artno, &mid) == NNRPstatOK) {
			}
		    }
		    printf("** %d ** %d i have it %s\n", xcount, artno, mid);
		    if (!ResetActive && mid != NULL)
			exception = INNBBSihave(bbsnnrp, artno, mid);
		    if (exception == -1)
			break;
		    if (rcptr->high != artno) {
			rcptr->high = artno;
			updaterc(rcptr->highptr, rcptr->highlen, rcptr->high);
		    }
		}
		if (rcptr->high != maxartno && exception != -1) {
		    bbsnnrp->actdirty = 1;
		    rcptr->high = maxartno;
		    updaterc(rcptr->highptr, rcptr->highlen, maxartno);
		}
	    }
	}
	/*
	 * flushrc(bbsnnrp);
	 */
    }
    fprintf(bbsnnrp->innbbsout, "quit\r\n");
    fprintf(bbsnnrp->nnrpout, "quit\r\n");
    fflush(bbsnnrp->innbbsout);
    fflush(bbsnnrp->nnrpout);
    fgets(NNRPbuffer, sizeof NNRPbuffer, bbsnnrp->nnrpin);
    fgets(INNBBSbuffer, sizeof INNBBSbuffer, bbsnnrp->innbbsin);

    /*
     * bbsnnrp->newsrc[0].high = 1900; updaterc(bbsnnrp->newsrc[0].highptr,
     * bbsnnrp->newsrc[0].highlen, bbsnnrp->newsrc[0].high);
     */
}

INNBBSDhalt()
{
}
